mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-04-01 18:40:57 -06:00
Merge remote-tracking branch 'origin/main' into imgui-trans
This commit is contained in:
commit
945eeffd39
77
.github/ISSUE_TEMPLATE/rfc.yaml
vendored
Normal file
77
.github/ISSUE_TEMPLATE/rfc.yaml
vendored
Normal file
@ -0,0 +1,77 @@
|
||||
# SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Docs - https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema
|
||||
name: Request For Comments (RFC)
|
||||
description: Ask for feedback on major architectural changes or design choices
|
||||
title: "[RFC]: "
|
||||
labels: ["RFC"]
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## Important: Read First
|
||||
|
||||
RFCs are for major architectural changes, design direction, or changes that benefit from structured discussion before merge.
|
||||
|
||||
Please make an effort to search for an existing RFC or issue before opening a new one.
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: Checklist
|
||||
options:
|
||||
- label: I have searched for a similar RFC or issue in this repository and did not find one.
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: motivation
|
||||
attributes:
|
||||
label: Motivation
|
||||
description: |
|
||||
Explain the problem this RFC is trying to solve.
|
||||
|
||||
Describe why the current design is insufficient and why this change is worth discussing now.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: proposed_change
|
||||
attributes:
|
||||
label: Proposed Change
|
||||
description: |
|
||||
Describe the proposed change in enough detail for maintainers and contributors to evaluate it.
|
||||
|
||||
Include the high-level design, affected areas, and any important constraints.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: feedback_period
|
||||
attributes:
|
||||
label: Feedback Period
|
||||
description: |
|
||||
State the intended review window for this RFC.
|
||||
|
||||
Example: one week, two weeks, or until specific maintainers have reviewed it.
|
||||
placeholder: "Example: 1 week"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: cc_list
|
||||
attributes:
|
||||
label: CC List
|
||||
description: |
|
||||
List any maintainers or contributors you want to explicitly notify for feedback.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: additional_context
|
||||
attributes:
|
||||
label: Any Other Things
|
||||
description: |
|
||||
Add any other relevant context, tradeoffs, diagrams, migration notes, or links to related work.
|
||||
validations:
|
||||
required: false
|
||||
203
.github/workflows/build.yml
vendored
203
.github/workflows/build.yml
vendored
@ -14,6 +14,8 @@ on:
|
||||
- "documents/**"
|
||||
- "**/*.md"
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.event_name }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'push' }}
|
||||
@ -26,14 +28,14 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: fsfe/reuse-action@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: fsfe/reuse-action@v6
|
||||
|
||||
clang-format:
|
||||
runs-on: ubuntu-24.04
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install
|
||||
@ -46,7 +48,7 @@ jobs:
|
||||
env:
|
||||
COMMIT_RANGE: ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}
|
||||
run: ./.ci/clang-format.sh
|
||||
|
||||
|
||||
get-info:
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
@ -54,7 +56,7 @@ jobs:
|
||||
shorthash: ${{ steps.vars.outputs.shorthash }}
|
||||
fullhash: ${{ steps.vars.outputs.fullhash }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Get date and git hash
|
||||
id: vars
|
||||
run: |
|
||||
@ -65,27 +67,102 @@ jobs:
|
||||
echo "shorthash=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
echo "fullhash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
test:
|
||||
name: Run C++ Tests on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||
include:
|
||||
- os: windows-latest
|
||||
compiler_cxx: clang-cl
|
||||
compiler_c: clang-cl
|
||||
- os: ubuntu-latest
|
||||
compiler_cxx: clang++
|
||||
compiler_c: clang
|
||||
- os: macos-latest
|
||||
compiler_cxx: clang++
|
||||
compiler_c: clang
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup CMake
|
||||
uses: lukka/get-cmake@latest
|
||||
|
||||
- name: Setup Visual Studio shell (Windows only)
|
||||
if: runner.os == 'Windows'
|
||||
uses: egor-tensin/vs-shell@v2
|
||||
with:
|
||||
arch: x64
|
||||
|
||||
- name: Install dependencies (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ninja-build libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libxcursor-dev libxi-dev libxss-dev libxtst-dev libxrandr-dev libxfixes-dev libudev-dev uuid-dev uuid-dev
|
||||
|
||||
- name: Install dependencies (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
brew install ninja
|
||||
|
||||
- name: Configure CMake
|
||||
run: |
|
||||
cmake -B build -G Ninja \
|
||||
-DCMAKE_CXX_COMPILER="${{ matrix.compiler_cxx }}" \
|
||||
-DCMAKE_C_COMPILER="${{ matrix.compiler_c }}" \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DENABLE_TESTS=ON \
|
||||
${{ runner.os == 'macOS' && '-DCMAKE_OSX_ARCHITECTURES=x86_64' || '' }}
|
||||
shell: bash
|
||||
|
||||
- name: Create shadPS4 user data directory (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: mkdir -p ~/.local/share/shadPS4
|
||||
|
||||
- name: Create shadPS4 user data directory (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: mkdir -p ~/Library/Application\ Support/shadPS4
|
||||
|
||||
- name: Create shadPS4 user data directory (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
run: mkdir -p "$APPDATA/shadPS4"
|
||||
shell: bash
|
||||
|
||||
- name: Build all tests
|
||||
run: cmake --build build
|
||||
shell: bash
|
||||
|
||||
- name: Run tests with CTest
|
||||
run: ctest --test-dir build --output-on-failure --progress
|
||||
shell: bash
|
||||
|
||||
windows-sdl:
|
||||
runs-on: windows-2025
|
||||
needs: get-info
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Cache CMake Configuration
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
env:
|
||||
cache-name: ${{ runner.os }}-sdl-ninja-cache-cmake-configuration
|
||||
with:
|
||||
path: |
|
||||
path: |
|
||||
${{github.workspace}}/build
|
||||
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
|
||||
restore-keys: |
|
||||
${{ env.cache-name }}-
|
||||
|
||||
- name: Cache CMake Build
|
||||
uses: hendrikmuhs/ccache-action@v1.2.19
|
||||
uses: hendrikmuhs/ccache-action@v1.2.21
|
||||
env:
|
||||
cache-name: ${{ runner.os }}-sdl-cache-cmake-build
|
||||
with:
|
||||
@ -99,7 +176,7 @@ jobs:
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $env:NUMBER_OF_PROCESSORS
|
||||
|
||||
- name: Upload Windows SDL artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: shadps4-win64-sdl-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
|
||||
path: ${{github.workspace}}/build/shadPS4.exe
|
||||
@ -108,7 +185,7 @@ jobs:
|
||||
runs-on: macos-15
|
||||
needs: get-info
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@ -118,18 +195,18 @@ jobs:
|
||||
xcode-version: latest
|
||||
|
||||
- name: Cache CMake Configuration
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
uses: actions/cache@v5
|
||||
env:
|
||||
cache-name: ${{ runner.os }}-sdl-cache-cmake-configuration
|
||||
with:
|
||||
path: |
|
||||
${{github.workspace}}/build
|
||||
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
|
||||
restore-keys: |
|
||||
${{ env.cache-name }}-
|
||||
with:
|
||||
path: |
|
||||
${{github.workspace}}/build
|
||||
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
|
||||
restore-keys: |
|
||||
${{ env.cache-name }}-
|
||||
|
||||
- name: Cache CMake Build
|
||||
uses: hendrikmuhs/ccache-action@v1.2.19
|
||||
uses: hendrikmuhs/ccache-action@v1.2.21
|
||||
env:
|
||||
cache-name: ${{runner.os}}-sdl-cache-cmake-build
|
||||
with:
|
||||
@ -150,7 +227,7 @@ jobs:
|
||||
mv ${{github.workspace}}/build/shadps4 upload
|
||||
mv ${{github.workspace}}/build/MoltenVK_icd.json upload
|
||||
mv ${{github.workspace}}/build/libMoltenVK.dylib upload
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: shadps4-macos-sdl-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
|
||||
path: upload/
|
||||
@ -159,7 +236,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
needs: get-info
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@ -172,18 +249,18 @@ jobs:
|
||||
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang-19 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev libxcursor-dev libxi-dev libxss-dev libxtst-dev
|
||||
|
||||
- name: Cache CMake Configuration
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
uses: actions/cache@v5
|
||||
env:
|
||||
cache-name: ${{ runner.os }}-sdl-cache-cmake-configuration
|
||||
with:
|
||||
path: |
|
||||
${{github.workspace}}/build
|
||||
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
|
||||
restore-keys: |
|
||||
${{ env.cache-name }}-
|
||||
with:
|
||||
path: |
|
||||
${{github.workspace}}/build
|
||||
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
|
||||
restore-keys: |
|
||||
${{ env.cache-name }}-
|
||||
|
||||
- name: Cache CMake Build
|
||||
uses: hendrikmuhs/ccache-action@v1.2.19
|
||||
uses: hendrikmuhs/ccache-action@v1.2.21
|
||||
env:
|
||||
cache-name: ${{ runner.os }}-sdl-cache-cmake-build
|
||||
with:
|
||||
@ -195,23 +272,23 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(nproc)
|
||||
|
||||
- name: Package and Upload Linux(ubuntu64) SDL artifact
|
||||
|
||||
- name: Package and Upload Linux(ubuntu64) SDL artifact
|
||||
run: |
|
||||
ls -la ${{ github.workspace }}/build/shadps4
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: shadps4-ubuntu64-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
|
||||
path: ${{ github.workspace }}/build/shadps4
|
||||
|
||||
- name: Run AppImage packaging script
|
||||
run: ./.github/linux-appimage-sdl.sh
|
||||
|
||||
|
||||
- name: Package and Upload Linux SDL artifact
|
||||
run: |
|
||||
tar cf shadps4-linux-sdl.tar.gz -C ${{github.workspace}}/build shadps4
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: shadps4-linux-sdl-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
|
||||
path: Shadps4-sdl.AppImage
|
||||
@ -220,7 +297,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
needs: get-info
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@ -228,18 +305,18 @@ jobs:
|
||||
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 gcc-14 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev libxcursor-dev libxi-dev libxss-dev libxtst-dev
|
||||
|
||||
- name: Cache CMake Configuration
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
uses: actions/cache@v5
|
||||
env:
|
||||
cache-name: ${{ runner.os }}-sdl-gcc-cache-cmake-configuration
|
||||
with:
|
||||
path: |
|
||||
${{github.workspace}}/build
|
||||
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
|
||||
restore-keys: |
|
||||
${{ env.cache-name }}-
|
||||
with:
|
||||
path: |
|
||||
${{github.workspace}}/build
|
||||
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
|
||||
restore-keys: |
|
||||
${{ env.cache-name }}-
|
||||
|
||||
- name: Cache CMake Build
|
||||
uses: hendrikmuhs/ccache-action@v1.2.19
|
||||
uses: hendrikmuhs/ccache-action@v1.2.21
|
||||
env:
|
||||
cache-name: ${{ runner.os }}-sdl-gcc-cache-cmake-build
|
||||
with:
|
||||
@ -258,7 +335,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: ./artifacts
|
||||
|
||||
@ -266,7 +343,7 @@ jobs:
|
||||
run: |
|
||||
chmod -R a+x ./artifacts/shadps4-linux-sdl-*
|
||||
chmod -R a+x ./artifacts/shadps4-macos-sdl-*
|
||||
|
||||
|
||||
- name: Compress individual directories (without parent directory)
|
||||
run: |
|
||||
cd ./artifacts
|
||||
@ -277,7 +354,7 @@ jobs:
|
||||
(cd "$dir_name" && zip -r "../${dir_name}.zip" .)
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
- name: Get latest release information
|
||||
id: get_latest_release
|
||||
env:
|
||||
@ -351,52 +428,52 @@ jobs:
|
||||
upload_url="https://uploads.github.com/repos/$REPO/releases/$release_id/assets?name=$filename"
|
||||
curl -X POST -H "Authorization: token $GITHUB_TOKEN" -H "Content-Type: application/octet-stream" --data-binary @"$file" "$upload_url"
|
||||
done
|
||||
|
||||
|
||||
- name: Get current pre-release information
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.SHADPS4_TOKEN_REPO }}
|
||||
run: |
|
||||
api_url="https://api.github.com/repos/${{ github.repository }}/releases"
|
||||
|
||||
|
||||
# Get all releases (sorted by date)
|
||||
releases=$(curl -H "Authorization: token $GITHUB_TOKEN" "$api_url")
|
||||
|
||||
|
||||
# Capture the most recent pre-release (assuming the first one is the latest)
|
||||
current_release=$(echo "$releases" | jq -c '.[] | select(.prerelease == true) | .published_at' | sort -r | head -n 1)
|
||||
|
||||
|
||||
# Remove extra quotes from captured date
|
||||
current_release=$(echo $current_release | tr -d '"')
|
||||
|
||||
|
||||
# Export the current published_at to be available for the next step
|
||||
echo "CURRENT_PUBLISHED_AT=$current_release" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: Delete old pre-releases and tags
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.SHADPS4_TOKEN_REPO }}
|
||||
run: |
|
||||
api_url="https://api.github.com/repos/${{ github.repository }}/releases"
|
||||
|
||||
|
||||
# Get current pre-releases
|
||||
releases=$(curl -H "Authorization: token $GITHUB_TOKEN" "$api_url")
|
||||
|
||||
|
||||
# Remove extra quotes from captured date
|
||||
CURRENT_PUBLISHED_AT=$(echo $CURRENT_PUBLISHED_AT | tr -d '"')
|
||||
|
||||
|
||||
# Convert CURRENT_PUBLISHED_AT para timestamp Unix
|
||||
current_published_ts=$(date -d "$CURRENT_PUBLISHED_AT" +%s)
|
||||
|
||||
|
||||
# Identify pre-releases
|
||||
echo "$releases" | jq -c '.[] | select(.prerelease == true)' | while read -r release; do
|
||||
release_date=$(echo "$release" | jq -r '.published_at')
|
||||
release_id=$(echo "$release" | jq -r '.id')
|
||||
release_tag=$(echo "$release" | jq -r '.tag_name')
|
||||
|
||||
|
||||
# Remove extra quotes from captured date
|
||||
release_date=$(echo $release_date | tr -d '"')
|
||||
|
||||
|
||||
# Convert release_date para timestamp Unix
|
||||
release_date_ts=$(date -d "$release_date" +%s)
|
||||
|
||||
|
||||
# Compare timestamps and delete old pre-releases
|
||||
if [[ "$release_date_ts" -lt "$current_published_ts" ]]; then
|
||||
echo "Deleting old pre-release: $release_id from $release_date with tag: $release_tag"
|
||||
|
||||
@ -33,6 +33,7 @@ endif()
|
||||
|
||||
option(ENABLE_DISCORD_RPC "Enable the Discord RPC integration" ON)
|
||||
option(ENABLE_UPDATER "Enables the options to updater" ON)
|
||||
option(ENABLE_TESTS "Build unit tests (requires GTest)" OFF)
|
||||
|
||||
# First, determine whether to use CMAKE_OSX_ARCHITECTURES or CMAKE_SYSTEM_PROCESSOR.
|
||||
if (APPLE AND CMAKE_OSX_ARCHITECTURES)
|
||||
@ -202,7 +203,7 @@ execute_process(
|
||||
|
||||
# Set Version
|
||||
set(EMULATOR_VERSION_MAJOR "0")
|
||||
set(EMULATOR_VERSION_MINOR "14")
|
||||
set(EMULATOR_VERSION_MINOR "15")
|
||||
set(EMULATOR_VERSION_PATCH "1")
|
||||
|
||||
set_source_files_properties(src/shadps4.rc PROPERTIES COMPILE_DEFINITIONS "EMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR};EMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR};EMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}")
|
||||
@ -295,8 +296,15 @@ set(AUDIO_LIB src/core/libraries/audio/audioin.cpp
|
||||
src/core/libraries/audio/audioout_backend.h
|
||||
src/core/libraries/audio/audioout_error.h
|
||||
src/core/libraries/audio/sdl_audio_out.cpp
|
||||
src/core/libraries/audio/openal_audio_out.cpp
|
||||
src/core/libraries/audio/openal_manager.h
|
||||
src/core/libraries/ngs2/ngs2.cpp
|
||||
src/core/libraries/ngs2/ngs2.h
|
||||
src/core/libraries/audio3d/audio3d.cpp
|
||||
src/core/libraries/audio3d/audio3d_openal.cpp
|
||||
src/core/libraries/audio3d/audio3d_openal.h
|
||||
src/core/libraries/audio3d/audio3d.h
|
||||
src/core/libraries/audio3d/audio3d_error.h
|
||||
)
|
||||
|
||||
set(GNM_LIB src/core/libraries/gnmdriver/gnmdriver.cpp
|
||||
@ -414,9 +422,12 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp
|
||||
src/core/libraries/save_data/dialog/savedatadialog.h
|
||||
src/core/libraries/save_data/dialog/savedatadialog_ui.cpp
|
||||
src/core/libraries/save_data/dialog/savedatadialog_ui.h
|
||||
src/core/libraries/system/sysmodule.cpp
|
||||
src/core/libraries/system/sysmodule.h
|
||||
src/core/libraries/system/system_error.h
|
||||
src/core/libraries/sysmodule/sysmodule.cpp
|
||||
src/core/libraries/sysmodule/sysmodule.h
|
||||
src/core/libraries/sysmodule/sysmodule_internal.cpp
|
||||
src/core/libraries/sysmodule/sysmodule_internal.h
|
||||
src/core/libraries/sysmodule/sysmodule_error.h
|
||||
src/core/libraries/sysmodule/sysmodule_table.h
|
||||
src/core/libraries/system/systemservice.cpp
|
||||
src/core/libraries/system/systemservice.h
|
||||
src/core/libraries/system/systemservice_error.h
|
||||
@ -868,6 +879,12 @@ set(CORE src/core/aerolib/stubs.cpp
|
||||
src/core/tls.h
|
||||
src/core/emulator_state.cpp
|
||||
src/core/emulator_state.h
|
||||
src/core/emulator_settings.cpp
|
||||
src/core/emulator_settings.h
|
||||
src/core/user_manager.cpp
|
||||
src/core/user_manager.h
|
||||
src/core/user_settings.cpp
|
||||
src/core/user_settings.h
|
||||
)
|
||||
|
||||
if (ARCHITECTURE STREQUAL "x86_64")
|
||||
@ -1099,6 +1116,8 @@ set(EMULATOR src/emulator.cpp
|
||||
src/sdl_window.cpp
|
||||
)
|
||||
|
||||
if(NOT ENABLE_TESTS)
|
||||
|
||||
add_executable(shadps4
|
||||
${AUDIO_CORE}
|
||||
${IMGUI}
|
||||
@ -1252,3 +1271,8 @@ endif()
|
||||
|
||||
# Install rules
|
||||
install(TARGETS shadps4 BUNDLE DESTINATION .)
|
||||
|
||||
else()
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
5
dist/net.shadps4.shadPS4.metainfo.xml
vendored
5
dist/net.shadps4.shadPS4.metainfo.xml
vendored
@ -38,7 +38,10 @@
|
||||
<category translate="no">Game</category>
|
||||
</categories>
|
||||
<releases>
|
||||
<release version="0.14.0" date="2026-02-07">
|
||||
<release version="0.15.0" date="2026-03-17">
|
||||
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.15.0</url>
|
||||
</release>
|
||||
<release version="0.14.0" date="2026-02-07">
|
||||
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.14.0</url>
|
||||
</release>
|
||||
<release version="0.13.0" date="2025-12-24">
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "assert.h"
|
||||
#include "bit_field.h"
|
||||
@ -73,6 +74,7 @@ class ElfInfo {
|
||||
|
||||
std::filesystem::path splash_path{};
|
||||
std::filesystem::path game_folder{};
|
||||
std::vector<std::string> npCommIds{};
|
||||
|
||||
public:
|
||||
static constexpr u32 FW_10 = 0x1000000;
|
||||
@ -88,7 +90,10 @@ public:
|
||||
static constexpr u32 FW_50 = 0x5000000;
|
||||
static constexpr u32 FW_55 = 0x5500000;
|
||||
static constexpr u32 FW_60 = 0x6000000;
|
||||
static constexpr u32 FW_70 = 0x7000000;
|
||||
static constexpr u32 FW_75 = 0x7500000;
|
||||
static constexpr u32 FW_80 = 0x8000000;
|
||||
static constexpr u32 FW_115 = 0x11500000;
|
||||
|
||||
static ElfInfo& Instance() {
|
||||
return *Singleton<ElfInfo>::Instance();
|
||||
@ -136,6 +141,10 @@ public:
|
||||
[[nodiscard]] const std::filesystem::path& GetGameFolder() const {
|
||||
return game_folder;
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::vector<std::string> GetNpCommIds() const {
|
||||
return npCommIds;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@ -14,7 +14,6 @@
|
||||
#endif
|
||||
|
||||
#include "common/bounded_threadsafe_queue.h"
|
||||
#include "common/config.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/io_file.h"
|
||||
#include "common/logging/backend.h"
|
||||
@ -24,6 +23,7 @@
|
||||
#include "common/path_util.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/emulator_settings.h"
|
||||
|
||||
namespace Common::Log {
|
||||
|
||||
@ -141,7 +141,7 @@ public:
|
||||
const auto& log_dir = GetUserPath(PathType::LogDir);
|
||||
std::filesystem::create_directory(log_dir);
|
||||
Filter filter;
|
||||
filter.ParseFilterString(Config::getLogFilter());
|
||||
filter.ParseFilterString(EmulatorSettings.GetLogFilter());
|
||||
const auto& log_file_path = log_file.empty() ? LOG_FILE : log_file;
|
||||
instance = std::unique_ptr<Impl, decltype(&Deleter)>(
|
||||
new Impl(log_dir / log_file_path, filter), Deleter);
|
||||
@ -185,7 +185,7 @@ public:
|
||||
|
||||
void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
|
||||
const char* function, const char* format, const fmt::format_args& args) {
|
||||
if (!filter.CheckMessage(log_class, log_level) || !Config::getLoggingEnabled()) {
|
||||
if (!filter.CheckMessage(log_class, log_level) || !EmulatorSettings.IsLogEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -213,7 +213,7 @@ public:
|
||||
using std::chrono::microseconds;
|
||||
using std::chrono::steady_clock;
|
||||
|
||||
if (Config::groupIdenticalLogs()) {
|
||||
if (EmulatorSettings.IsIdenticalLogGrouped()) {
|
||||
std::unique_lock entry_loc(_mutex);
|
||||
|
||||
if (_last_entry.message == message) {
|
||||
@ -226,7 +226,7 @@ public:
|
||||
}
|
||||
|
||||
if (_last_entry.counter >= 1) {
|
||||
if (Config::getLogType() == "async") {
|
||||
if (EmulatorSettings.GetLogType() == "async") {
|
||||
message_queue.EmplaceWait(_last_entry);
|
||||
} else {
|
||||
ForEachBackend([this](auto& backend) { backend.Write(this->_last_entry); });
|
||||
@ -258,7 +258,7 @@ public:
|
||||
.counter = 1,
|
||||
};
|
||||
|
||||
if (Config::getLogType() == "async") {
|
||||
if (EmulatorSettings.GetLogType() == "async") {
|
||||
message_queue.EmplaceWait(entry);
|
||||
} else {
|
||||
ForEachBackend([&entry](auto& backend) { backend.Write(entry); });
|
||||
@ -296,14 +296,14 @@ private:
|
||||
}
|
||||
|
||||
void StopBackendThread() {
|
||||
if (Config::groupIdenticalLogs()) {
|
||||
if (EmulatorSettings.IsIdenticalLogGrouped()) {
|
||||
// log last message
|
||||
if (_last_entry.counter >= 2) {
|
||||
_last_entry.message += " x" + std::to_string(_last_entry.counter);
|
||||
}
|
||||
|
||||
if (_last_entry.counter >= 1) {
|
||||
if (Config::getLogType() == "async") {
|
||||
if (EmulatorSettings.GetLogType() == "async") {
|
||||
message_queue.EmplaceWait(_last_entry);
|
||||
} else {
|
||||
ForEachBackend([this](auto& backend) { backend.Write(this->_last_entry); });
|
||||
|
||||
@ -68,6 +68,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
|
||||
CLS(Common) \
|
||||
SUB(Common, Filesystem) \
|
||||
SUB(Common, Memory) \
|
||||
CLS(KeyManager) \
|
||||
CLS(Core) \
|
||||
SUB(Core, Linker) \
|
||||
SUB(Core, Devices) \
|
||||
@ -80,7 +81,6 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
|
||||
SUB(Kernel, Event) \
|
||||
SUB(Kernel, Sce) \
|
||||
CLS(Lib) \
|
||||
SUB(Lib, LibC) \
|
||||
SUB(Lib, LibcInternal) \
|
||||
SUB(Lib, Kernel) \
|
||||
SUB(Lib, Pad) \
|
||||
@ -117,7 +117,6 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
|
||||
SUB(Lib, NpSnsFacebookDialog) \
|
||||
SUB(Lib, NpPartner) \
|
||||
SUB(Lib, Screenshot) \
|
||||
SUB(Lib, LibCInternal) \
|
||||
SUB(Lib, AppContent) \
|
||||
SUB(Lib, Rtc) \
|
||||
SUB(Lib, Rudp) \
|
||||
@ -163,7 +162,6 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
|
||||
CLS(ImGui) \
|
||||
CLS(Input) \
|
||||
CLS(Tty) \
|
||||
CLS(KeyManager) \
|
||||
CLS(Loader)
|
||||
|
||||
// GetClassName is a macro defined by Windows.h, grrr...
|
||||
|
||||
@ -34,6 +34,7 @@ enum class Class : u8 {
|
||||
Common, ///< Library routines
|
||||
Common_Filesystem, ///< Filesystem interface library
|
||||
Common_Memory, ///< Memory mapping and management functions
|
||||
KeyManager, ///< Key management system
|
||||
Core, ///< LLE emulation core
|
||||
Core_Linker, ///< The module linker
|
||||
Core_Devices, ///< Devices emulation
|
||||
@ -44,10 +45,9 @@ enum class Class : u8 {
|
||||
Kernel_Fs, ///< The filesystem implementation of the kernel.
|
||||
Kernel_Vmm, ///< The virtual memory implementation of the kernel.
|
||||
Kernel_Event, ///< The event management implementation of the kernel.
|
||||
Kernel_Sce, ///< The sony specific interfaces provided by the kernel.
|
||||
Kernel_Sce, ///< The Sony-specific interfaces provided by the kernel.
|
||||
Lib, ///< HLE implementation of system library. Each major library
|
||||
///< should have its own subclass.
|
||||
Lib_LibC, ///< The LibC implementation.
|
||||
Lib_LibcInternal, ///< The LibcInternal implementation.
|
||||
Lib_Kernel, ///< The LibKernel implementation.
|
||||
Lib_Pad, ///< The LibScePad implementation.
|
||||
@ -83,7 +83,6 @@ enum class Class : u8 {
|
||||
Lib_NpProfileDialog, ///< The LibSceNpProfileDialog implementation
|
||||
Lib_NpSnsFacebookDialog, ///< The LibSceNpSnsFacebookDialog implementation
|
||||
Lib_Screenshot, ///< The LibSceScreenshot implementation
|
||||
Lib_LibCInternal, ///< The LibCInternal implementation.
|
||||
Lib_AppContent, ///< The LibSceAppContent implementation.
|
||||
Lib_Rtc, ///< The LibSceRtc implementation.
|
||||
Lib_Rudp, ///< The LibSceRudp implementation.
|
||||
@ -131,7 +130,6 @@ enum class Class : u8 {
|
||||
Loader, ///< ROM loader
|
||||
Input, ///< Input emulation
|
||||
Tty, ///< Debug output from emu
|
||||
KeyManager, ///< Key management system
|
||||
Count ///< Total number of logging classes
|
||||
};
|
||||
|
||||
|
||||
@ -8,7 +8,6 @@
|
||||
#include <string>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <pugixml.hpp>
|
||||
#include "common/config.h"
|
||||
#include "common/elf_info.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/path_util.h"
|
||||
|
||||
@ -129,6 +129,7 @@ static auto UserPaths = [] {
|
||||
create_path(PathType::CustomConfigs, user_dir / CUSTOM_CONFIGS);
|
||||
create_path(PathType::CacheDir, user_dir / CACHE_DIR);
|
||||
create_path(PathType::FontsDir, user_dir / FONTS_DIR);
|
||||
create_path(PathType::HomeDir, user_dir / HOME_DIR);
|
||||
|
||||
std::ofstream notice_file(user_dir / CUSTOM_TROPHY / "Notice.txt");
|
||||
if (notice_file.is_open()) {
|
||||
|
||||
@ -26,6 +26,7 @@ enum class PathType {
|
||||
CustomConfigs, // Where custom files for different games are stored.
|
||||
CacheDir, // Where pipeline and shader cache is stored.
|
||||
FontsDir, // Where dumped system fonts are stored.
|
||||
HomeDir, // PS4 home directory
|
||||
};
|
||||
|
||||
constexpr auto PORTABLE_DIR = "user";
|
||||
@ -46,6 +47,7 @@ constexpr auto CUSTOM_TROPHY = "custom_trophy";
|
||||
constexpr auto CUSTOM_CONFIGS = "custom_configs";
|
||||
constexpr auto CACHE_DIR = "cache";
|
||||
constexpr auto FONTS_DIR = "fonts";
|
||||
constexpr auto HOME_DIR = "home";
|
||||
|
||||
// Filenames
|
||||
constexpr auto LOG_FILE = "shad_log.txt";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@ -7,6 +7,7 @@
|
||||
#include "common/types.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace Serialization {
|
||||
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <map>
|
||||
#include "common/alignment.h"
|
||||
#include "common/arch.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/config.h"
|
||||
#include "common/elf_info.h"
|
||||
#include "common/error.h"
|
||||
#include "core/address_space.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/kernel/memory.h"
|
||||
#include "core/memory.h"
|
||||
#include "libraries/error_codes.h"
|
||||
@ -187,7 +187,7 @@ struct AddressSpace::Impl {
|
||||
user_size = supported_user_max - USER_MIN - 1;
|
||||
|
||||
// Increase BackingSize to account for config options.
|
||||
BackingSize += Config::getExtraDmemInMbytes() * 1_MB;
|
||||
BackingSize += EmulatorSettings.GetExtraDmemInMBytes() * 1_MB;
|
||||
|
||||
// Allocate backing file that represents the total physical memory.
|
||||
backing_handle = CreateFileMapping2(INVALID_HANDLE_VALUE, nullptr, FILE_MAP_ALL_ACCESS,
|
||||
@ -606,7 +606,7 @@ enum PosixPageProtection {
|
||||
|
||||
struct AddressSpace::Impl {
|
||||
Impl() {
|
||||
BackingSize += Config::getExtraDmemInMbytes() * 1_MB;
|
||||
BackingSize += EmulatorSettings.GetExtraDmemInMBytes() * 1_MB;
|
||||
// Allocate virtual address placeholder for our address space.
|
||||
system_managed_size = SystemManagedSize;
|
||||
system_reserved_size = SystemReservedSize;
|
||||
|
||||
@ -19,7 +19,7 @@ namespace Core::AeroLib {
|
||||
// and to longer compile / CI times
|
||||
//
|
||||
// Must match STUBS_LIST define
|
||||
constexpr u32 MAX_STUBS = 1024;
|
||||
constexpr u32 MAX_STUBS = 2048;
|
||||
|
||||
u64 UnresolvedStub() {
|
||||
LOG_ERROR(Core, "Returning zero to {}", __builtin_return_address(0));
|
||||
@ -61,8 +61,9 @@ static u32 UsedStubEntries;
|
||||
#define XREP_256(x) XREP_128(x) XREP_128(x + 128)
|
||||
#define XREP_512(x) XREP_256(x) XREP_256(x + 256)
|
||||
#define XREP_1024(x) XREP_512(x) XREP_512(x + 512)
|
||||
#define XREP_2048(x) XREP_1024(x) XREP_1024(x + 1024)
|
||||
|
||||
#define STUBS_LIST XREP_1024(0)
|
||||
#define STUBS_LIST XREP_2048(0)
|
||||
|
||||
static u64 (*stub_handlers[MAX_STUBS])() = {STUBS_LIST};
|
||||
|
||||
|
||||
@ -788,14 +788,11 @@ static bool PatchesIllegalInstructionHandler(void* context) {
|
||||
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
|
||||
const auto status =
|
||||
Common::Decoder::Instance()->decodeInstruction(instruction, operands, code_address);
|
||||
if (ZYAN_SUCCESS(status) && instruction.mnemonic == ZydisMnemonic::ZYDIS_MNEMONIC_UD2)
|
||||
[[unlikely]] {
|
||||
UNREACHABLE_MSG("ud2 at code address {:#x}", reinterpret_cast<u64>(code_address));
|
||||
}
|
||||
UNREACHABLE_MSG("Failed to patch address {:x} -- mnemonic: {}",
|
||||
reinterpret_cast<u64>(code_address),
|
||||
ZYAN_SUCCESS(status) ? ZydisMnemonicGetString(instruction.mnemonic)
|
||||
: "Failed to decode");
|
||||
LOG_ERROR(Core, "Failed to patch address {:x} -- mnemonic: {}",
|
||||
reinterpret_cast<u64>(code_address),
|
||||
ZYAN_SUCCESS(status) ? ZydisMnemonicGetString(instruction.mnemonic)
|
||||
: "Failed to decode");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -7,10 +7,10 @@
|
||||
#include <imgui.h>
|
||||
|
||||
#include "SDL3/SDL_log.h"
|
||||
#include "common/config.h"
|
||||
#include "common/singleton.h"
|
||||
#include "common/types.h"
|
||||
#include "core/debug_state.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/emulator_state.h"
|
||||
#include "imgui/imgui_std.h"
|
||||
#include "imgui_internal.h"
|
||||
@ -110,11 +110,11 @@ void L::DrawMenuBar() {
|
||||
EndDisabled();
|
||||
|
||||
if (Button("Save")) {
|
||||
Config::setFsrEnabled(fsr.enable);
|
||||
Config::setRcasEnabled(fsr.use_rcas);
|
||||
Config::setRcasAttenuation(static_cast<int>(fsr.rcas_attenuation * 1000));
|
||||
Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) /
|
||||
"config.toml");
|
||||
EmulatorSettings.SetFsrEnabled(fsr.enable);
|
||||
EmulatorSettings.SetRcasEnabled(fsr.use_rcas);
|
||||
EmulatorSettings.SetRcasAttenuation(
|
||||
static_cast<int>(fsr.rcas_attenuation * 1000));
|
||||
EmulatorSettings.Save();
|
||||
CloseCurrentPopup();
|
||||
}
|
||||
|
||||
@ -311,7 +311,7 @@ static void LoadSettings(const char* line) {
|
||||
|
||||
void L::SetupSettings() {
|
||||
frame_graph.is_open = true;
|
||||
show_simple_fps = Config::getShowFpsCounter();
|
||||
show_simple_fps = EmulatorSettings.IsShowFpsCounter();
|
||||
|
||||
using SettingLoader = void (*)(const char*);
|
||||
|
||||
@ -472,7 +472,7 @@ void L::Draw() {
|
||||
if (ImGui::Begin("Volume Window", &show_volume,
|
||||
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration |
|
||||
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking)) {
|
||||
Text("Volume: %d", Config::getVolumeSlider());
|
||||
Text("Volume: %d", EmulatorSettings.GetVolumeSlider());
|
||||
}
|
||||
End();
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "frame_graph.h"
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/debug_state.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "imgui.h"
|
||||
#include "imgui_internal.h"
|
||||
|
||||
@ -29,7 +29,7 @@ void FrameGraph::DrawFrameGraph() {
|
||||
return;
|
||||
}
|
||||
|
||||
float target_dt = 1.0f / (float)Config::vblankFreq();
|
||||
float target_dt = 1.0f / (float)EmulatorSettings.GetVblankFrequency();
|
||||
float cur_pos_x = pos.x + full_width;
|
||||
pos.y += FRAME_GRAPH_PADDING_Y;
|
||||
const float final_pos_y = pos.y + FRAME_GRAPH_HEIGHT;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@ -8,9 +8,9 @@
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "common/config.h"
|
||||
#include "common/elf_info.h"
|
||||
#include "common/path_util.h"
|
||||
#include "core/emulator_settings.h"
|
||||
|
||||
namespace Core::Devtools::Widget {
|
||||
|
||||
@ -23,7 +23,7 @@ public:
|
||||
bool open = false;
|
||||
|
||||
static bool IsSystemModule(const std::filesystem::path& path) {
|
||||
const auto sys_modules_path = Config::getSysModulesPath();
|
||||
const auto sys_modules_path = EmulatorSettings.GetSysModulesDir();
|
||||
|
||||
const auto abs_path = std::filesystem::absolute(path).lexically_normal();
|
||||
const auto abs_sys_path = std::filesystem::absolute(sys_modules_path).lexically_normal();
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <fstream>
|
||||
@ -8,11 +8,11 @@
|
||||
#include <imgui.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "common/config.h"
|
||||
#include "common/path_util.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/debug_state.h"
|
||||
#include "core/devtools/options.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "imgui/imgui_std.h"
|
||||
#include "sdl_window.h"
|
||||
#include "video_core/renderer_vulkan/vk_presenter.h"
|
||||
@ -244,8 +244,8 @@ void ShaderList::Draw() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Config::collectShadersForDebug()) {
|
||||
DrawCenteredText("Enable 'CollectShader' in config to see shaders");
|
||||
if (!EmulatorSettings.IsShaderCollect()) {
|
||||
DrawCenteredText("Enable 'shader_collect' in config to see shaders");
|
||||
End();
|
||||
return;
|
||||
}
|
||||
|
||||
688
src/core/emulator_settings.cpp
Normal file
688
src/core/emulator_settings.cpp
Normal file
@ -0,0 +1,688 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
#include <common/path_util.h>
|
||||
#include <common/scm_rev.h>
|
||||
#include <toml.hpp>
|
||||
#include "common/logging/log.h"
|
||||
#include "emulator_settings.h"
|
||||
#include "emulator_state.h"
|
||||
|
||||
#include <SDL3/SDL_messagebox.h>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// ── Singleton storage ─────────────────────────────────────────────────
|
||||
std::shared_ptr<EmulatorSettingsImpl> EmulatorSettingsImpl::s_instance = nullptr;
|
||||
std::mutex EmulatorSettingsImpl::s_mutex;
|
||||
|
||||
// ── nlohmann helpers for std::filesystem::path ───────────────────────
|
||||
namespace nlohmann {
|
||||
template <>
|
||||
struct adl_serializer<std::filesystem::path> {
|
||||
static void to_json(json& j, const std::filesystem::path& p) {
|
||||
const auto u8 = p.u8string();
|
||||
j = std::string(reinterpret_cast<const char*>(u8.data()), u8.size());
|
||||
}
|
||||
static void from_json(const json& j, std::filesystem::path& p) {
|
||||
const std::string s = j.get<std::string>();
|
||||
p = std::filesystem::path(
|
||||
std::u8string_view(reinterpret_cast<const char8_t*>(s.data()), s.size()));
|
||||
}
|
||||
};
|
||||
} // namespace nlohmann
|
||||
|
||||
namespace toml {
|
||||
// why is it so hard to avoid exceptions with this library
|
||||
template <typename T>
|
||||
std::optional<T> get_optional(const toml::value& v, const std::string& key) {
|
||||
if (!v.is_table())
|
||||
return std::nullopt;
|
||||
const auto& tbl = v.as_table();
|
||||
auto it = tbl.find(key);
|
||||
if (it == tbl.end())
|
||||
return std::nullopt;
|
||||
|
||||
if constexpr (std::is_same_v<T, int>) {
|
||||
if (it->second.is_integer()) {
|
||||
return static_cast<int>(toml::get<int>(it->second));
|
||||
}
|
||||
} else if constexpr (std::is_same_v<T, unsigned int>) {
|
||||
if (it->second.is_integer()) {
|
||||
return static_cast<u32>(toml::get<unsigned int>(it->second));
|
||||
}
|
||||
} else if constexpr (std::is_same_v<T, double>) {
|
||||
if (it->second.is_floating()) {
|
||||
return toml::get<double>(it->second);
|
||||
}
|
||||
} else if constexpr (std::is_same_v<T, std::string>) {
|
||||
if (it->second.is_string()) {
|
||||
return toml::get<std::string>(it->second);
|
||||
}
|
||||
} else if constexpr (std::is_same_v<T, std::filesystem::path>) {
|
||||
if (it->second.is_string()) {
|
||||
return toml::get<std::string>(it->second);
|
||||
}
|
||||
} else if constexpr (std::is_same_v<T, bool>) {
|
||||
if (it->second.is_boolean()) {
|
||||
return toml::get<bool>(it->second);
|
||||
}
|
||||
} else {
|
||||
static_assert([] { return false; }(), "Unsupported type in get_optional<T>");
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace toml
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
void EmulatorSettingsImpl::PrintChangedSummary(const std::vector<std::string>& changed) {
|
||||
if (changed.empty()) {
|
||||
LOG_DEBUG(Config, "No game-specific overrides applied");
|
||||
return;
|
||||
}
|
||||
LOG_DEBUG(Config, "Game-specific overrides applied:");
|
||||
for (const auto& k : changed)
|
||||
LOG_DEBUG(Config, " * {}", k);
|
||||
}
|
||||
|
||||
// ── Singleton ────────────────────────────────────────────────────────
|
||||
EmulatorSettingsImpl::EmulatorSettingsImpl() = default;
|
||||
|
||||
EmulatorSettingsImpl::~EmulatorSettingsImpl() {
|
||||
if (m_loaded)
|
||||
Save();
|
||||
}
|
||||
|
||||
std::shared_ptr<EmulatorSettingsImpl> EmulatorSettingsImpl::GetInstance() {
|
||||
std::lock_guard lock(s_mutex);
|
||||
if (!s_instance)
|
||||
s_instance = std::make_shared<EmulatorSettingsImpl>();
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
void EmulatorSettingsImpl::SetInstance(std::shared_ptr<EmulatorSettingsImpl> instance) {
|
||||
std::lock_guard lock(s_mutex);
|
||||
s_instance = std::move(instance);
|
||||
}
|
||||
|
||||
// --------------------
|
||||
// General helpers
|
||||
// --------------------
|
||||
bool EmulatorSettingsImpl::AddGameInstallDir(const std::filesystem::path& dir, bool enabled) {
|
||||
for (const auto& d : m_general.install_dirs.value)
|
||||
if (d.path == dir)
|
||||
return false;
|
||||
m_general.install_dirs.value.push_back({dir, enabled});
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> EmulatorSettingsImpl::GetGameInstallDirs() const {
|
||||
std::vector<std::filesystem::path> out;
|
||||
for (const auto& d : m_general.install_dirs.value)
|
||||
if (d.enabled)
|
||||
out.push_back(d.path);
|
||||
return out;
|
||||
}
|
||||
|
||||
const std::vector<GameInstallDir>& EmulatorSettingsImpl::GetAllGameInstallDirs() const {
|
||||
return m_general.install_dirs.value;
|
||||
}
|
||||
|
||||
void EmulatorSettingsImpl::SetAllGameInstallDirs(const std::vector<GameInstallDir>& dirs) {
|
||||
m_general.install_dirs.value = dirs;
|
||||
}
|
||||
|
||||
void EmulatorSettingsImpl::RemoveGameInstallDir(const std::filesystem::path& dir) {
|
||||
auto iterator =
|
||||
std::find_if(m_general.install_dirs.value.begin(), m_general.install_dirs.value.end(),
|
||||
[&dir](const GameInstallDir& install_dir) { return install_dir.path == dir; });
|
||||
if (iterator != m_general.install_dirs.value.end()) {
|
||||
m_general.install_dirs.value.erase(iterator);
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatorSettingsImpl::SetGameInstallDirEnabled(const std::filesystem::path& dir,
|
||||
bool enabled) {
|
||||
auto iterator =
|
||||
std::find_if(m_general.install_dirs.value.begin(), m_general.install_dirs.value.end(),
|
||||
[&dir](const GameInstallDir& install_dir) { return install_dir.path == dir; });
|
||||
if (iterator != m_general.install_dirs.value.end()) {
|
||||
iterator->enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatorSettingsImpl::SetGameInstallDirs(
|
||||
const std::vector<std::filesystem::path>& dirs_config) {
|
||||
m_general.install_dirs.value.clear();
|
||||
for (const auto& dir : dirs_config) {
|
||||
m_general.install_dirs.value.push_back({dir, true});
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<bool> EmulatorSettingsImpl::GetGameInstallDirsEnabled() {
|
||||
std::vector<bool> enabled_dirs;
|
||||
for (const auto& dir : m_general.install_dirs.value) {
|
||||
enabled_dirs.push_back(dir.enabled);
|
||||
}
|
||||
return enabled_dirs;
|
||||
}
|
||||
|
||||
std::filesystem::path EmulatorSettingsImpl::GetHomeDir() {
|
||||
if (m_general.home_dir.value.empty()) {
|
||||
return Common::FS::GetUserPath(Common::FS::PathType::HomeDir);
|
||||
}
|
||||
return m_general.home_dir.value;
|
||||
}
|
||||
|
||||
void EmulatorSettingsImpl::SetHomeDir(const std::filesystem::path& dir) {
|
||||
m_general.home_dir.value = dir;
|
||||
}
|
||||
|
||||
std::filesystem::path EmulatorSettingsImpl::GetSysModulesDir() {
|
||||
if (m_general.sys_modules_dir.value.empty()) {
|
||||
return Common::FS::GetUserPath(Common::FS::PathType::SysModuleDir);
|
||||
}
|
||||
return m_general.sys_modules_dir.value;
|
||||
}
|
||||
|
||||
void EmulatorSettingsImpl::SetSysModulesDir(const std::filesystem::path& dir) {
|
||||
m_general.sys_modules_dir.value = dir;
|
||||
}
|
||||
|
||||
std::filesystem::path EmulatorSettingsImpl::GetFontsDir() {
|
||||
if (m_general.font_dir.value.empty()) {
|
||||
return Common::FS::GetUserPath(Common::FS::PathType::FontsDir);
|
||||
}
|
||||
return m_general.font_dir.value;
|
||||
}
|
||||
|
||||
void EmulatorSettingsImpl::SetFontsDir(const std::filesystem::path& dir) {
|
||||
m_general.font_dir.value = dir;
|
||||
}
|
||||
|
||||
// ── Game-specific override management ────────────────────────────────
|
||||
void EmulatorSettingsImpl::ClearGameSpecificOverrides() {
|
||||
ClearGroupOverrides(m_general);
|
||||
ClearGroupOverrides(m_debug);
|
||||
ClearGroupOverrides(m_input);
|
||||
ClearGroupOverrides(m_audio);
|
||||
ClearGroupOverrides(m_gpu);
|
||||
ClearGroupOverrides(m_vulkan);
|
||||
LOG_DEBUG(Config, "All game-specific overrides cleared");
|
||||
}
|
||||
|
||||
void EmulatorSettingsImpl::ResetGameSpecificValue(const std::string& key) {
|
||||
// Walk every overrideable group until we find the matching key.
|
||||
auto tryGroup = [&key](auto& group) {
|
||||
for (auto& item : group.GetOverrideableFields()) {
|
||||
if (key == item.key) {
|
||||
item.reset_game_specific(&group);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (tryGroup(m_general))
|
||||
return;
|
||||
if (tryGroup(m_debug))
|
||||
return;
|
||||
if (tryGroup(m_input))
|
||||
return;
|
||||
if (tryGroup(m_audio))
|
||||
return;
|
||||
if (tryGroup(m_gpu))
|
||||
return;
|
||||
if (tryGroup(m_vulkan))
|
||||
return;
|
||||
LOG_WARNING(Config, "ResetGameSpecificValue: key '{}' not found", key);
|
||||
}
|
||||
|
||||
bool EmulatorSettingsImpl::Save(const std::string& serial) {
|
||||
try {
|
||||
if (!serial.empty()) {
|
||||
const auto cfgDir = Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs);
|
||||
std::filesystem::create_directories(cfgDir);
|
||||
const auto path = cfgDir / (serial + ".json");
|
||||
|
||||
json j = json::object();
|
||||
|
||||
json generalObj = json::object();
|
||||
SaveGroupGameSpecific(m_general, generalObj);
|
||||
j["General"] = generalObj;
|
||||
|
||||
json debugObj = json::object();
|
||||
SaveGroupGameSpecific(m_debug, debugObj);
|
||||
j["Debug"] = debugObj;
|
||||
|
||||
json inputObj = json::object();
|
||||
SaveGroupGameSpecific(m_input, inputObj);
|
||||
j["Input"] = inputObj;
|
||||
|
||||
json audioObj = json::object();
|
||||
SaveGroupGameSpecific(m_audio, audioObj);
|
||||
j["Audio"] = audioObj;
|
||||
|
||||
json gpuObj = json::object();
|
||||
SaveGroupGameSpecific(m_gpu, gpuObj);
|
||||
j["GPU"] = gpuObj;
|
||||
|
||||
json vulkanObj = json::object();
|
||||
SaveGroupGameSpecific(m_vulkan, vulkanObj);
|
||||
j["Vulkan"] = vulkanObj;
|
||||
|
||||
std::ofstream out(path);
|
||||
if (!out) {
|
||||
LOG_ERROR(Config, "Failed to open game config for writing: {}", path.string());
|
||||
return false;
|
||||
}
|
||||
out << std::setw(2) << j;
|
||||
return !out.fail();
|
||||
|
||||
} else {
|
||||
// ── Global config.json ─────────────────────────────────────
|
||||
const auto path =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.json";
|
||||
|
||||
SetConfigVersion(Common::g_scm_rev);
|
||||
|
||||
json j;
|
||||
j["General"] = m_general;
|
||||
j["Debug"] = m_debug;
|
||||
j["Input"] = m_input;
|
||||
j["Audio"] = m_audio;
|
||||
j["GPU"] = m_gpu;
|
||||
j["Vulkan"] = m_vulkan;
|
||||
|
||||
// Read the existing file so we can preserve keys unknown to this build
|
||||
json existing = json::object();
|
||||
if (std::ifstream existingIn{path}; existingIn.good()) {
|
||||
try {
|
||||
existingIn >> existing;
|
||||
} catch (...) {
|
||||
existing = json::object();
|
||||
}
|
||||
}
|
||||
|
||||
// Merge: update each section's known keys, but leave unknown keys intact
|
||||
for (auto& [section, val] : j.items()) {
|
||||
if (existing.contains(section) && existing[section].is_object() && val.is_object())
|
||||
existing[section].update(val); // overwrites known keys, keeps unknown ones
|
||||
else
|
||||
existing[section] = val;
|
||||
}
|
||||
|
||||
std::ofstream out(path);
|
||||
if (!out) {
|
||||
LOG_ERROR(Config, "Failed to open config for writing: {}", path.string());
|
||||
return false;
|
||||
}
|
||||
out << std::setw(2) << existing;
|
||||
return !out.fail();
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(Config, "Error saving settings: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Load ──────────────────────────────────────────────────────────────
|
||||
|
||||
bool EmulatorSettingsImpl::Load(const std::string& serial) {
|
||||
try {
|
||||
if (serial.empty()) {
|
||||
// ── Global config ──────────────────────────────────────────
|
||||
const auto userDir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
||||
const auto configPath = userDir / "config.json";
|
||||
LOG_DEBUG(Config, "Loading global config from: {}", configPath.string());
|
||||
|
||||
if (std::ifstream in{configPath}; in.good()) {
|
||||
json gj;
|
||||
in >> gj;
|
||||
|
||||
auto mergeGroup = [&gj](auto& group, const char* section) {
|
||||
if (!gj.contains(section))
|
||||
return;
|
||||
json current = group;
|
||||
current.update(gj.at(section));
|
||||
group = current.get<std::remove_reference_t<decltype(group)>>();
|
||||
};
|
||||
|
||||
mergeGroup(m_general, "General");
|
||||
mergeGroup(m_debug, "Debug");
|
||||
mergeGroup(m_input, "Input");
|
||||
mergeGroup(m_audio, "Audio");
|
||||
mergeGroup(m_gpu, "GPU");
|
||||
mergeGroup(m_vulkan, "Vulkan");
|
||||
|
||||
LOG_DEBUG(Config, "Global config loaded successfully");
|
||||
} else {
|
||||
if (std::filesystem::exists(Common::FS::GetUserPath(Common::FS::PathType::UserDir) /
|
||||
"config.toml")) {
|
||||
SDL_MessageBoxButtonData btns[2]{
|
||||
{0, 0, "Defaults"},
|
||||
{0, 1, "Update"},
|
||||
};
|
||||
SDL_MessageBoxData msg_box{
|
||||
0,
|
||||
nullptr,
|
||||
"Config Migration",
|
||||
"The shadPS4 config backend has been updated, and you only have "
|
||||
"the old version of the config. Do you wish to update it "
|
||||
"automatically, or continue with the default config?",
|
||||
2,
|
||||
btns,
|
||||
nullptr,
|
||||
};
|
||||
int result = 1;
|
||||
SDL_ShowMessageBox(&msg_box, &result);
|
||||
if (result == 1) {
|
||||
if (TransferSettings()) {
|
||||
m_loaded = true;
|
||||
Save();
|
||||
return true;
|
||||
} else {
|
||||
SDL_ShowSimpleMessageBox(0, "Config Migration",
|
||||
"Error transferring settings, exiting.",
|
||||
nullptr);
|
||||
std::quick_exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG_DEBUG(Config, "Global config not found - using defaults");
|
||||
SetDefaultValues();
|
||||
Save();
|
||||
}
|
||||
if (GetConfigVersion() != Common::g_scm_rev) {
|
||||
Save();
|
||||
}
|
||||
m_loaded = true;
|
||||
return true;
|
||||
} else {
|
||||
// ── Per-game override file ─────────────────────────────────
|
||||
// Never reloads global settings. Only applies
|
||||
// game_specific_value overrides on top of the already-loaded
|
||||
// base configuration.
|
||||
const auto gamePath =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (serial + ".json");
|
||||
LOG_DEBUG(Config, "Applying game config: {}", gamePath.string());
|
||||
|
||||
if (!std::filesystem::exists(gamePath)) {
|
||||
LOG_DEBUG(Config, "No game-specific config found for {}", serial);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ifstream in(gamePath);
|
||||
if (!in) {
|
||||
LOG_ERROR(Config, "Failed to open game config: {}", gamePath.string());
|
||||
return false;
|
||||
}
|
||||
|
||||
json gj;
|
||||
in >> gj;
|
||||
|
||||
std::vector<std::string> changed;
|
||||
|
||||
// ApplyGroupOverrides now correctly stores values as
|
||||
// game_specific_value (see make_override in the header).
|
||||
// ConfigMode::Default will then resolve them at getter call
|
||||
// time without ever touching the base values.
|
||||
if (gj.contains("General"))
|
||||
ApplyGroupOverrides(m_general, gj.at("General"), changed);
|
||||
if (gj.contains("Debug"))
|
||||
ApplyGroupOverrides(m_debug, gj.at("Debug"), changed);
|
||||
if (gj.contains("Input"))
|
||||
ApplyGroupOverrides(m_input, gj.at("Input"), changed);
|
||||
if (gj.contains("Audio"))
|
||||
ApplyGroupOverrides(m_audio, gj.at("Audio"), changed);
|
||||
if (gj.contains("GPU"))
|
||||
ApplyGroupOverrides(m_gpu, gj.at("GPU"), changed);
|
||||
if (gj.contains("Vulkan"))
|
||||
ApplyGroupOverrides(m_vulkan, gj.at("Vulkan"), changed);
|
||||
|
||||
PrintChangedSummary(changed);
|
||||
EmulatorState::GetInstance()->SetGameSpecifigConfigUsed(true);
|
||||
return true;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(Config, "Error loading settings: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatorSettingsImpl::SetDefaultValues() {
|
||||
m_general = GeneralSettings{};
|
||||
m_debug = DebugSettings{};
|
||||
m_input = InputSettings{};
|
||||
m_audio = AudioSettings{};
|
||||
m_gpu = GPUSettings{};
|
||||
m_vulkan = VulkanSettings{};
|
||||
}
|
||||
|
||||
bool EmulatorSettingsImpl::TransferSettings() {
|
||||
toml::value og_data;
|
||||
json new_data = json::object();
|
||||
try {
|
||||
auto path = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml";
|
||||
std::ifstream ifs;
|
||||
ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit);
|
||||
ifs.open(path, std::ios_base::binary);
|
||||
og_data = toml::parse(ifs, std::string{fmt::UTF(path.filename().u8string()).data});
|
||||
} catch (std::exception& ex) {
|
||||
fmt::print("Got exception trying to load config file. Exception: {}\n", ex.what());
|
||||
return false;
|
||||
}
|
||||
auto setFromToml = [&]<typename T>(Setting<T>& n, toml::value const& t, std::string k) {
|
||||
n = toml::get_optional<T>(t, k).value_or(n.default_value);
|
||||
};
|
||||
if (og_data.contains("General")) {
|
||||
const toml::value& general = og_data.at("General");
|
||||
auto& s = m_general;
|
||||
|
||||
setFromToml(s.volume_slider, general, "volumeSlider");
|
||||
setFromToml(s.neo_mode, general, "isPS4Pro");
|
||||
setFromToml(s.dev_kit_mode, general, "isDevKit");
|
||||
setFromToml(s.psn_signed_in, general, "isPSNSignedIn");
|
||||
setFromToml(s.trophy_popup_disabled, general, "isTrophyPopupDisabled");
|
||||
setFromToml(s.trophy_notification_duration, general, "trophyNotificationDuration");
|
||||
setFromToml(s.discord_rpc_enabled, general, "enableDiscordRPC");
|
||||
setFromToml(s.log_filter, general, "logFilter");
|
||||
setFromToml(s.log_type, general, "logType");
|
||||
setFromToml(s.identical_log_grouped, general, "isIdenticalLogGrouped");
|
||||
setFromToml(s.show_splash, general, "showSplash");
|
||||
setFromToml(s.trophy_notification_side, general, "sideTrophy");
|
||||
setFromToml(s.connected_to_network, general, "isConnectedToNetwork");
|
||||
setFromToml(s.sys_modules_dir, general, "sysModulesPath");
|
||||
setFromToml(s.font_dir, general, "fontsPath");
|
||||
// setFromToml(, general, "userName");
|
||||
// setFromToml(s.defaultControllerID, general, "defaultControllerID");
|
||||
}
|
||||
|
||||
if (og_data.contains("Input")) {
|
||||
const toml::value& input = og_data.at("Input");
|
||||
auto& s = m_input;
|
||||
|
||||
setFromToml(s.cursor_state, input, "cursorState");
|
||||
setFromToml(s.cursor_hide_timeout, input, "cursorHideTimeout");
|
||||
setFromToml(s.use_special_pad, input, "useSpecialPad");
|
||||
setFromToml(s.special_pad_class, input, "specialPadClass");
|
||||
setFromToml(s.motion_controls_enabled, input, "isMotionControlsEnabled");
|
||||
setFromToml(s.use_unified_input_config, input, "useUnifiedInputConfig");
|
||||
setFromToml(s.background_controller_input, input, "backgroundControllerInput");
|
||||
setFromToml(s.usb_device_backend, input, "usbDeviceBackend");
|
||||
}
|
||||
|
||||
if (og_data.contains("Audio")) {
|
||||
const toml::value& audio = og_data.at("Audio");
|
||||
auto& s = m_audio;
|
||||
|
||||
setFromToml(s.sdl_mic_device, audio, "micDevice");
|
||||
setFromToml(s.sdl_main_output_device, audio, "mainOutputDevice");
|
||||
setFromToml(s.sdl_padSpk_output_device, audio, "padSpkOutputDevice");
|
||||
}
|
||||
|
||||
if (og_data.contains("GPU")) {
|
||||
const toml::value& gpu = og_data.at("GPU");
|
||||
auto& s = m_gpu;
|
||||
|
||||
setFromToml(s.window_width, gpu, "screenWidth");
|
||||
setFromToml(s.window_height, gpu, "screenHeight");
|
||||
setFromToml(s.internal_screen_width, gpu, "internalScreenWidth");
|
||||
setFromToml(s.internal_screen_height, gpu, "internalScreenHeight");
|
||||
setFromToml(s.null_gpu, gpu, "nullGpu");
|
||||
setFromToml(s.copy_gpu_buffers, gpu, "copyGPUBuffers");
|
||||
setFromToml(s.readbacks_mode, gpu, "readbacksMode");
|
||||
setFromToml(s.readback_linear_images_enabled, gpu, "readbackLinearImages");
|
||||
setFromToml(s.direct_memory_access_enabled, gpu, "directMemoryAccess");
|
||||
setFromToml(s.dump_shaders, gpu, "dumpShaders");
|
||||
setFromToml(s.patch_shaders, gpu, "patchShaders");
|
||||
setFromToml(s.vblank_frequency, gpu, "vblankFrequency");
|
||||
setFromToml(s.full_screen, gpu, "Fullscreen");
|
||||
setFromToml(s.full_screen_mode, gpu, "FullscreenMode");
|
||||
setFromToml(s.present_mode, gpu, "presentMode");
|
||||
setFromToml(s.hdr_allowed, gpu, "allowHDR");
|
||||
setFromToml(s.fsr_enabled, gpu, "fsrEnabled");
|
||||
setFromToml(s.rcas_enabled, gpu, "rcasEnabled");
|
||||
setFromToml(s.rcas_attenuation, gpu, "rcasAttenuation");
|
||||
}
|
||||
|
||||
if (og_data.contains("Vulkan")) {
|
||||
const toml::value& vk = og_data.at("Vulkan");
|
||||
auto& s = m_vulkan;
|
||||
|
||||
setFromToml(s.gpu_id, vk, "gpuId");
|
||||
setFromToml(s.vkvalidation_enabled, vk, "validation");
|
||||
setFromToml(s.vkvalidation_core_enabled, vk, "validation_core");
|
||||
setFromToml(s.vkvalidation_sync_enabled, vk, "validation_sync");
|
||||
setFromToml(s.vkvalidation_gpu_enabled, vk, "validation_gpu");
|
||||
setFromToml(s.vkcrash_diagnostic_enabled, vk, "crashDiagnostic");
|
||||
setFromToml(s.vkhost_markers, vk, "hostMarkers");
|
||||
setFromToml(s.vkguest_markers, vk, "guestMarkers");
|
||||
setFromToml(s.renderdoc_enabled, vk, "rdocEnable");
|
||||
setFromToml(s.pipeline_cache_enabled, vk, "pipelineCacheEnable");
|
||||
setFromToml(s.pipeline_cache_archived, vk, "pipelineCacheArchive");
|
||||
}
|
||||
|
||||
if (og_data.contains("Debug")) {
|
||||
const toml::value& debug = og_data.at("Debug");
|
||||
auto& s = m_debug;
|
||||
|
||||
setFromToml(s.debug_dump, debug, "DebugDump");
|
||||
setFromToml(s.separate_logging_enabled, debug, "isSeparateLogFilesEnabled");
|
||||
setFromToml(s.shader_collect, debug, "CollectShader");
|
||||
setFromToml(s.log_enabled, debug, "logEnabled");
|
||||
setFromToml(m_general.show_fps_counter, debug, "showFpsCounter");
|
||||
}
|
||||
|
||||
if (og_data.contains("Settings")) {
|
||||
const toml::value& settings = og_data.at("Settings");
|
||||
auto& s = m_general;
|
||||
setFromToml(s.console_language, settings, "consoleLanguage");
|
||||
}
|
||||
|
||||
if (og_data.contains("GUI")) {
|
||||
const toml::value& gui = og_data.at("GUI");
|
||||
auto& s = m_general;
|
||||
|
||||
// Transfer install directories
|
||||
try {
|
||||
const auto install_dir_array =
|
||||
toml::find_or<std::vector<std::string>>(gui, "installDirs", {});
|
||||
std::vector<bool> install_dirs_enabled;
|
||||
|
||||
try {
|
||||
install_dirs_enabled = toml::find<std::vector<bool>>(gui, "installDirsEnabled");
|
||||
} catch (...) {
|
||||
// If it does not exist, assume that all are enabled.
|
||||
install_dirs_enabled.resize(install_dir_array.size(), true);
|
||||
}
|
||||
|
||||
if (install_dirs_enabled.size() < install_dir_array.size()) {
|
||||
install_dirs_enabled.resize(install_dir_array.size(), true);
|
||||
}
|
||||
|
||||
std::vector<GameInstallDir> settings_install_dirs;
|
||||
for (size_t i = 0; i < install_dir_array.size(); i++) {
|
||||
settings_install_dirs.push_back(
|
||||
{std::filesystem::path{install_dir_array[i]}, install_dirs_enabled[i]});
|
||||
}
|
||||
s.install_dirs.value = settings_install_dirs;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_WARNING(Config, "Failed to transfer install directories: {}", e.what());
|
||||
}
|
||||
|
||||
// Transfer addon install directory
|
||||
try {
|
||||
std::string addon_install_dir_str;
|
||||
if (gui.contains("addonInstallDir")) {
|
||||
const auto& addon_value = gui.at("addonInstallDir");
|
||||
if (addon_value.is_string()) {
|
||||
addon_install_dir_str = toml::get<std::string>(addon_value);
|
||||
if (!addon_install_dir_str.empty()) {
|
||||
s.addon_install_dir.value = std::filesystem::path{addon_install_dir_str};
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LOG_WARNING(Config, "Failed to transfer addon install directory: {}", e.what());
|
||||
}
|
||||
}
|
||||
if (og_data.contains("General")) {
|
||||
const toml::value& general = og_data.at("General");
|
||||
auto& s = m_general;
|
||||
// Transfer sysmodules install directory
|
||||
try {
|
||||
std::string sysmodules_install_dir_str;
|
||||
if (general.contains("sysModulesPath")) {
|
||||
const auto& sysmodule_value = general.at("sysModulesPath");
|
||||
if (sysmodule_value.is_string()) {
|
||||
sysmodules_install_dir_str = toml::get<std::string>(sysmodule_value);
|
||||
if (!sysmodules_install_dir_str.empty()) {
|
||||
s.sys_modules_dir.value = std::filesystem::path{sysmodules_install_dir_str};
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LOG_WARNING(Config, "Failed to transfer sysmodules install directory: {}", e.what());
|
||||
}
|
||||
|
||||
// Transfer font install directory
|
||||
try {
|
||||
std::string font_install_dir_str;
|
||||
if (general.contains("fontsPath")) {
|
||||
const auto& font_value = general.at("fontsPath");
|
||||
if (font_value.is_string()) {
|
||||
font_install_dir_str = toml::get<std::string>(font_value);
|
||||
if (!font_install_dir_str.empty()) {
|
||||
s.font_dir.value = std::filesystem::path{font_install_dir_str};
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LOG_WARNING(Config, "Failed to transfer font install directory: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::string> EmulatorSettingsImpl::GetAllOverrideableKeys() const {
|
||||
std::vector<std::string> keys;
|
||||
auto addGroup = [&keys](const auto& fields) {
|
||||
for (const auto& item : fields)
|
||||
keys.push_back(item.key);
|
||||
};
|
||||
addGroup(m_general.GetOverrideableFields());
|
||||
addGroup(m_debug.GetOverrideableFields());
|
||||
addGroup(m_input.GetOverrideableFields());
|
||||
addGroup(m_audio.GetOverrideableFields());
|
||||
addGroup(m_gpu.GetOverrideableFields());
|
||||
addGroup(m_vulkan.GetOverrideableFields());
|
||||
return keys;
|
||||
}
|
||||
639
src/core/emulator_settings.h
Normal file
639
src/core/emulator_settings.h
Normal file
@ -0,0 +1,639 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/types.h"
|
||||
|
||||
#define EmulatorSettings (*EmulatorSettingsImpl::GetInstance())
|
||||
|
||||
enum HideCursorState : int {
|
||||
Never,
|
||||
Idle,
|
||||
Always,
|
||||
};
|
||||
|
||||
enum UsbBackendType : int {
|
||||
Real,
|
||||
SkylandersPortal,
|
||||
InfinityBase,
|
||||
DimensionsToypad,
|
||||
};
|
||||
|
||||
enum GpuReadbacksMode : int {
|
||||
Disabled,
|
||||
Relaxed,
|
||||
Precise,
|
||||
};
|
||||
|
||||
enum class ConfigMode {
|
||||
Default,
|
||||
Global,
|
||||
Clean,
|
||||
};
|
||||
|
||||
enum AudioBackend : int {
|
||||
SDL,
|
||||
OpenAL,
|
||||
// Add more backends as needed
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct Setting {
|
||||
T default_value{};
|
||||
T value{};
|
||||
std::optional<T> game_specific_value{};
|
||||
|
||||
Setting() = default;
|
||||
// Single-argument ctor: initialises both default_value and value so
|
||||
// that CleanMode can always recover the intended factory default.
|
||||
/*implicit*/ Setting(T init) : default_value(std::move(init)), value(default_value) {}
|
||||
|
||||
/// Return the active value under the given mode.
|
||||
T get(ConfigMode mode = ConfigMode::Default) const {
|
||||
switch (mode) {
|
||||
case ConfigMode::Default:
|
||||
return game_specific_value.value_or(value);
|
||||
case ConfigMode::Global:
|
||||
return value;
|
||||
case ConfigMode::Clean:
|
||||
return default_value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/// Write v to the base layer.
|
||||
/// Game-specific overrides are applied exclusively via Load(serial)
|
||||
void set(const T& v) {
|
||||
value = v;
|
||||
}
|
||||
|
||||
/// Discard the game-specific override; subsequent get(Default) will
|
||||
/// fall back to the base value.
|
||||
void reset_game_specific() {
|
||||
game_specific_value = std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void to_json(nlohmann::json& j, const Setting<T>& s) {
|
||||
j = s.value;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void from_json(const nlohmann::json& j, Setting<T>& s) {
|
||||
s.value = j.get<T>();
|
||||
}
|
||||
|
||||
struct OverrideItem {
|
||||
const char* key;
|
||||
std::function<void(void* group_ptr, const nlohmann::json& entry,
|
||||
std::vector<std::string>& changed)>
|
||||
apply;
|
||||
/// Return the value that should be written to the per-game config file.
|
||||
/// Falls back to base value if no game-specific override is set.
|
||||
std::function<nlohmann::json(const void* group_ptr)> get_for_save;
|
||||
|
||||
/// Clear game_specific_value for this field.
|
||||
std::function<void(void* group_ptr)> reset_game_specific;
|
||||
};
|
||||
|
||||
template <typename Struct, typename T>
|
||||
inline OverrideItem make_override(const char* key, Setting<T> Struct::* member) {
|
||||
return OverrideItem{
|
||||
key,
|
||||
[member, key](void* base, const nlohmann::json& entry, std::vector<std::string>& changed) {
|
||||
LOG_DEBUG(Config, "[make_override] Processing key: {}", key);
|
||||
LOG_DEBUG(Config, "[make_override] Entry JSON: {}", entry.dump());
|
||||
Struct* obj = reinterpret_cast<Struct*>(base);
|
||||
Setting<T>& dst = obj->*member;
|
||||
try {
|
||||
T newValue = entry.get<T>();
|
||||
LOG_DEBUG(Config, "[make_override] Parsed value: {}", newValue);
|
||||
LOG_DEBUG(Config, "[make_override] Current value: {}", dst.value);
|
||||
if (dst.value != newValue) {
|
||||
std::ostringstream oss;
|
||||
oss << key << " ( " << dst.value << " → " << newValue << " )";
|
||||
changed.push_back(oss.str());
|
||||
LOG_DEBUG(Config, "[make_override] Recorded change: {}", oss.str());
|
||||
}
|
||||
dst.game_specific_value = newValue;
|
||||
LOG_DEBUG(Config, "[make_override] Successfully updated {}", key);
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(Config, "[make_override] ERROR parsing {}: {}", key, e.what());
|
||||
LOG_ERROR(Config, "[make_override] Entry was: {}", entry.dump());
|
||||
LOG_ERROR(Config, "[make_override] Type name: {}", entry.type_name());
|
||||
}
|
||||
},
|
||||
|
||||
// --- get_for_save -------------------------------------------
|
||||
// Returns game_specific_value when present, otherwise base value.
|
||||
// This means a freshly-opened game-specific dialog still shows
|
||||
// useful (current-global) values rather than empty entries.
|
||||
[member](const void* base) -> nlohmann::json {
|
||||
const Struct* obj = reinterpret_cast<const Struct*>(base);
|
||||
const Setting<T>& src = obj->*member;
|
||||
return nlohmann::json(src.game_specific_value.value_or(src.value));
|
||||
},
|
||||
|
||||
// --- reset_game_specific ------------------------------------
|
||||
[member](void* base) {
|
||||
Struct* obj = reinterpret_cast<Struct*>(base);
|
||||
(obj->*member).reset_game_specific();
|
||||
}};
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
// Support types
|
||||
// -------------------------------
|
||||
struct GameInstallDir {
|
||||
std::filesystem::path path;
|
||||
bool enabled;
|
||||
};
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GameInstallDir, path, enabled)
|
||||
|
||||
// -------------------------------
|
||||
// General settings
|
||||
// -------------------------------
|
||||
struct GeneralSettings {
|
||||
Setting<std::vector<GameInstallDir>> install_dirs;
|
||||
Setting<std::filesystem::path> addon_install_dir;
|
||||
Setting<std::filesystem::path> home_dir;
|
||||
Setting<std::filesystem::path> sys_modules_dir;
|
||||
Setting<std::filesystem::path> font_dir;
|
||||
|
||||
Setting<int> volume_slider{100};
|
||||
Setting<bool> neo_mode{false};
|
||||
Setting<bool> dev_kit_mode{false};
|
||||
Setting<int> extra_dmem_in_mbytes{0};
|
||||
Setting<bool> psn_signed_in{false};
|
||||
Setting<bool> trophy_popup_disabled{false};
|
||||
Setting<double> trophy_notification_duration{6.0};
|
||||
Setting<std::string> trophy_notification_side{"right"};
|
||||
Setting<std::string> log_filter{""};
|
||||
Setting<std::string> log_type{"sync"};
|
||||
Setting<bool> show_splash{false};
|
||||
Setting<bool> identical_log_grouped{true};
|
||||
Setting<bool> connected_to_network{false};
|
||||
Setting<bool> discord_rpc_enabled{false};
|
||||
Setting<bool> show_fps_counter{false};
|
||||
Setting<int> console_language{1};
|
||||
|
||||
// return a vector of override descriptors (runtime, but tiny)
|
||||
std::vector<OverrideItem> GetOverrideableFields() const {
|
||||
return std::vector<OverrideItem>{
|
||||
make_override<GeneralSettings>("volume_slider", &GeneralSettings::volume_slider),
|
||||
make_override<GeneralSettings>("neo_mode", &GeneralSettings::neo_mode),
|
||||
make_override<GeneralSettings>("dev_kit_mode", &GeneralSettings::dev_kit_mode),
|
||||
make_override<GeneralSettings>("extra_dmem_in_mbytes",
|
||||
&GeneralSettings::extra_dmem_in_mbytes),
|
||||
make_override<GeneralSettings>("psn_signed_in", &GeneralSettings::psn_signed_in),
|
||||
make_override<GeneralSettings>("trophy_popup_disabled",
|
||||
&GeneralSettings::trophy_popup_disabled),
|
||||
make_override<GeneralSettings>("trophy_notification_duration",
|
||||
&GeneralSettings::trophy_notification_duration),
|
||||
make_override<GeneralSettings>("log_filter", &GeneralSettings::log_filter),
|
||||
make_override<GeneralSettings>("log_type", &GeneralSettings::log_type),
|
||||
make_override<GeneralSettings>("identical_log_grouped",
|
||||
&GeneralSettings::identical_log_grouped),
|
||||
make_override<GeneralSettings>("show_splash", &GeneralSettings::show_splash),
|
||||
make_override<GeneralSettings>("trophy_notification_side",
|
||||
&GeneralSettings::trophy_notification_side),
|
||||
make_override<GeneralSettings>("connected_to_network",
|
||||
&GeneralSettings::connected_to_network)};
|
||||
}
|
||||
};
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GeneralSettings, install_dirs, addon_install_dir, home_dir,
|
||||
sys_modules_dir, font_dir, volume_slider, neo_mode, dev_kit_mode,
|
||||
extra_dmem_in_mbytes, psn_signed_in, trophy_popup_disabled,
|
||||
trophy_notification_duration, log_filter, log_type, show_splash,
|
||||
identical_log_grouped, trophy_notification_side,
|
||||
connected_to_network, discord_rpc_enabled, show_fps_counter,
|
||||
console_language)
|
||||
|
||||
// -------------------------------
|
||||
// Debug settings
|
||||
// -------------------------------
|
||||
struct DebugSettings {
|
||||
Setting<bool> separate_logging_enabled{false}; // specific
|
||||
Setting<bool> debug_dump{false}; // specific
|
||||
Setting<bool> shader_collect{false}; // specific
|
||||
Setting<bool> log_enabled{true}; // specific
|
||||
Setting<std::string> config_version{""}; // specific
|
||||
|
||||
std::vector<OverrideItem> GetOverrideableFields() const {
|
||||
return std::vector<OverrideItem>{
|
||||
make_override<DebugSettings>("debug_dump", &DebugSettings::debug_dump),
|
||||
make_override<DebugSettings>("shader_collect", &DebugSettings::shader_collect),
|
||||
make_override<DebugSettings>("separate_logging_enabled",
|
||||
&DebugSettings::separate_logging_enabled),
|
||||
make_override<DebugSettings>("log_enabled", &DebugSettings::log_enabled)};
|
||||
}
|
||||
};
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(DebugSettings, separate_logging_enabled, debug_dump,
|
||||
shader_collect, log_enabled, config_version)
|
||||
|
||||
// -------------------------------
|
||||
// Input settings
|
||||
// -------------------------------
|
||||
|
||||
struct InputSettings {
|
||||
Setting<int> cursor_state{HideCursorState::Idle}; // specific
|
||||
Setting<int> cursor_hide_timeout{5}; // specific
|
||||
Setting<int> usb_device_backend{UsbBackendType::Real}; // specific
|
||||
Setting<bool> use_special_pad{false};
|
||||
Setting<int> special_pad_class{1};
|
||||
Setting<bool> motion_controls_enabled{true}; // specific
|
||||
Setting<bool> use_unified_input_config{true};
|
||||
Setting<std::string> default_controller_id{""};
|
||||
Setting<bool> background_controller_input{false}; // specific
|
||||
Setting<s32> camera_id{-1};
|
||||
|
||||
std::vector<OverrideItem> GetOverrideableFields() const {
|
||||
return std::vector<OverrideItem>{
|
||||
make_override<InputSettings>("cursor_state", &InputSettings::cursor_state),
|
||||
make_override<InputSettings>("cursor_hide_timeout",
|
||||
&InputSettings::cursor_hide_timeout),
|
||||
make_override<InputSettings>("usb_device_backend", &InputSettings::usb_device_backend),
|
||||
make_override<InputSettings>("motion_controls_enabled",
|
||||
&InputSettings::motion_controls_enabled),
|
||||
make_override<InputSettings>("background_controller_input",
|
||||
&InputSettings::background_controller_input),
|
||||
make_override<InputSettings>("camera_id", &InputSettings::camera_id)};
|
||||
}
|
||||
};
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(InputSettings, cursor_state, cursor_hide_timeout,
|
||||
usb_device_backend, use_special_pad, special_pad_class,
|
||||
motion_controls_enabled, use_unified_input_config,
|
||||
default_controller_id, background_controller_input, camera_id)
|
||||
// -------------------------------
|
||||
// Audio settings
|
||||
// -------------------------------
|
||||
struct AudioSettings {
|
||||
Setting<u32> audio_backend{AudioBackend::SDL};
|
||||
Setting<std::string> sdl_mic_device{"Default Device"};
|
||||
Setting<std::string> sdl_main_output_device{"Default Device"};
|
||||
Setting<std::string> sdl_padSpk_output_device{"Default Device"};
|
||||
Setting<std::string> openal_mic_device{"Default Device"};
|
||||
Setting<std::string> openal_main_output_device{"Default Device"};
|
||||
Setting<std::string> openal_padSpk_output_device{"Default Device"};
|
||||
|
||||
std::vector<OverrideItem> GetOverrideableFields() const {
|
||||
return std::vector<OverrideItem>{
|
||||
make_override<AudioSettings>("audio_backend", &AudioSettings::audio_backend),
|
||||
make_override<AudioSettings>("sdl_mic_device", &AudioSettings::sdl_mic_device),
|
||||
make_override<AudioSettings>("sdl_main_output_device",
|
||||
&AudioSettings::sdl_main_output_device),
|
||||
make_override<AudioSettings>("sdl_padSpk_output_device",
|
||||
&AudioSettings::sdl_padSpk_output_device),
|
||||
make_override<AudioSettings>("openal_mic_device", &AudioSettings::openal_mic_device),
|
||||
make_override<AudioSettings>("openal_main_output_device",
|
||||
&AudioSettings::openal_main_output_device),
|
||||
make_override<AudioSettings>("openal_padSpk_output_device",
|
||||
&AudioSettings::openal_padSpk_output_device)};
|
||||
}
|
||||
};
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(AudioSettings, audio_backend, sdl_mic_device,
|
||||
sdl_main_output_device, sdl_padSpk_output_device,
|
||||
openal_mic_device, openal_main_output_device,
|
||||
openal_padSpk_output_device)
|
||||
|
||||
// -------------------------------
|
||||
// GPU settings
|
||||
// -------------------------------
|
||||
struct GPUSettings {
|
||||
Setting<u32> window_width{1280};
|
||||
Setting<u32> window_height{720};
|
||||
Setting<u32> internal_screen_width{1280};
|
||||
Setting<u32> internal_screen_height{720};
|
||||
Setting<bool> null_gpu{false};
|
||||
Setting<bool> copy_gpu_buffers{false};
|
||||
Setting<u32> readbacks_mode{GpuReadbacksMode::Disabled};
|
||||
Setting<bool> readback_linear_images_enabled{false};
|
||||
Setting<bool> direct_memory_access_enabled{false};
|
||||
Setting<bool> dump_shaders{false};
|
||||
Setting<bool> patch_shaders{false};
|
||||
Setting<u32> vblank_frequency{60};
|
||||
Setting<bool> full_screen{false};
|
||||
Setting<std::string> full_screen_mode{"Windowed"};
|
||||
Setting<std::string> present_mode{"Mailbox"};
|
||||
Setting<bool> hdr_allowed{false};
|
||||
Setting<bool> fsr_enabled{false};
|
||||
Setting<bool> rcas_enabled{true};
|
||||
Setting<int> rcas_attenuation{250};
|
||||
// TODO add overrides
|
||||
std::vector<OverrideItem> GetOverrideableFields() const {
|
||||
return std::vector<OverrideItem>{
|
||||
make_override<GPUSettings>("null_gpu", &GPUSettings::null_gpu),
|
||||
make_override<GPUSettings>("copy_gpu_buffers", &GPUSettings::copy_gpu_buffers),
|
||||
make_override<GPUSettings>("full_screen", &GPUSettings::full_screen),
|
||||
make_override<GPUSettings>("full_screen_mode", &GPUSettings::full_screen_mode),
|
||||
make_override<GPUSettings>("present_mode", &GPUSettings::present_mode),
|
||||
make_override<GPUSettings>("window_height", &GPUSettings::window_height),
|
||||
make_override<GPUSettings>("window_width", &GPUSettings::window_width),
|
||||
make_override<GPUSettings>("hdr_allowed", &GPUSettings::hdr_allowed),
|
||||
make_override<GPUSettings>("fsr_enabled", &GPUSettings::fsr_enabled),
|
||||
make_override<GPUSettings>("rcas_enabled", &GPUSettings::rcas_enabled),
|
||||
make_override<GPUSettings>("rcas_attenuation", &GPUSettings::rcas_attenuation),
|
||||
make_override<GPUSettings>("dump_shaders", &GPUSettings::dump_shaders),
|
||||
make_override<GPUSettings>("patch_shaders", &GPUSettings::patch_shaders),
|
||||
make_override<GPUSettings>("readbacks_mode", &GPUSettings::readbacks_mode),
|
||||
make_override<GPUSettings>("readback_linear_images_enabled",
|
||||
&GPUSettings::readback_linear_images_enabled),
|
||||
make_override<GPUSettings>("direct_memory_access_enabled",
|
||||
&GPUSettings::direct_memory_access_enabled),
|
||||
make_override<GPUSettings>("vblank_frequency", &GPUSettings::vblank_frequency),
|
||||
};
|
||||
}
|
||||
};
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GPUSettings, window_width, window_height, internal_screen_width,
|
||||
internal_screen_height, null_gpu, copy_gpu_buffers,
|
||||
readbacks_mode, readback_linear_images_enabled,
|
||||
direct_memory_access_enabled, dump_shaders, patch_shaders,
|
||||
vblank_frequency, full_screen, full_screen_mode, present_mode,
|
||||
hdr_allowed, fsr_enabled, rcas_enabled, rcas_attenuation)
|
||||
// -------------------------------
|
||||
// Vulkan settings
|
||||
// -------------------------------
|
||||
struct VulkanSettings {
|
||||
Setting<s32> gpu_id{-1};
|
||||
Setting<bool> renderdoc_enabled{false};
|
||||
Setting<bool> vkvalidation_enabled{false};
|
||||
Setting<bool> vkvalidation_core_enabled{true};
|
||||
Setting<bool> vkvalidation_sync_enabled{false};
|
||||
Setting<bool> vkvalidation_gpu_enabled{false};
|
||||
Setting<bool> vkcrash_diagnostic_enabled{false};
|
||||
Setting<bool> vkhost_markers{false};
|
||||
Setting<bool> vkguest_markers{false};
|
||||
Setting<bool> pipeline_cache_enabled{false};
|
||||
Setting<bool> pipeline_cache_archived{false};
|
||||
std::vector<OverrideItem> GetOverrideableFields() const {
|
||||
return std::vector<OverrideItem>{
|
||||
make_override<VulkanSettings>("gpu_id", &VulkanSettings::gpu_id),
|
||||
make_override<VulkanSettings>("renderdoc_enabled", &VulkanSettings::renderdoc_enabled),
|
||||
make_override<VulkanSettings>("vkvalidation_enabled",
|
||||
&VulkanSettings::vkvalidation_enabled),
|
||||
make_override<VulkanSettings>("vkvalidation_core_enabled",
|
||||
&VulkanSettings::vkvalidation_core_enabled),
|
||||
make_override<VulkanSettings>("vkvalidation_sync_enabled",
|
||||
&VulkanSettings::vkvalidation_sync_enabled),
|
||||
make_override<VulkanSettings>("vkvalidation_gpu_enabled",
|
||||
&VulkanSettings::vkvalidation_gpu_enabled),
|
||||
make_override<VulkanSettings>("vkcrash_diagnostic_enabled",
|
||||
&VulkanSettings::vkcrash_diagnostic_enabled),
|
||||
make_override<VulkanSettings>("vkhost_markers", &VulkanSettings::vkhost_markers),
|
||||
make_override<VulkanSettings>("vkguest_markers", &VulkanSettings::vkguest_markers),
|
||||
make_override<VulkanSettings>("pipeline_cache_enabled",
|
||||
&VulkanSettings::pipeline_cache_enabled),
|
||||
make_override<VulkanSettings>("pipeline_cache_archived",
|
||||
&VulkanSettings::pipeline_cache_archived),
|
||||
};
|
||||
}
|
||||
};
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(VulkanSettings, gpu_id, renderdoc_enabled, vkvalidation_enabled,
|
||||
vkvalidation_core_enabled, vkvalidation_sync_enabled,
|
||||
vkvalidation_gpu_enabled, vkcrash_diagnostic_enabled,
|
||||
vkhost_markers, vkguest_markers, pipeline_cache_enabled,
|
||||
pipeline_cache_archived)
|
||||
|
||||
// -------------------------------
|
||||
// Main manager
|
||||
// -------------------------------
|
||||
class EmulatorSettingsImpl {
|
||||
public:
|
||||
EmulatorSettingsImpl();
|
||||
~EmulatorSettingsImpl();
|
||||
|
||||
static std::shared_ptr<EmulatorSettingsImpl> GetInstance();
|
||||
static void SetInstance(std::shared_ptr<EmulatorSettingsImpl> instance);
|
||||
|
||||
bool Save(const std::string& serial = "");
|
||||
bool Load(const std::string& serial = "");
|
||||
void SetDefaultValues();
|
||||
bool TransferSettings();
|
||||
|
||||
// Config mode
|
||||
ConfigMode GetConfigMode() const {
|
||||
return m_configMode;
|
||||
}
|
||||
void SetConfigMode(ConfigMode mode) {
|
||||
m_configMode = mode;
|
||||
}
|
||||
|
||||
//
|
||||
// Game-specific override management
|
||||
/// Clears all per-game overrides. Call this when a game exits so
|
||||
/// the emulator reverts to global settings.
|
||||
void ClearGameSpecificOverrides();
|
||||
|
||||
/// Reset a single field's game-specific override by its JSON ke
|
||||
void ResetGameSpecificValue(const std::string& key);
|
||||
|
||||
// general accessors
|
||||
bool AddGameInstallDir(const std::filesystem::path& dir, bool enabled = true);
|
||||
std::vector<std::filesystem::path> GetGameInstallDirs() const;
|
||||
void SetAllGameInstallDirs(const std::vector<GameInstallDir>& dirs);
|
||||
void RemoveGameInstallDir(const std::filesystem::path& dir);
|
||||
void SetGameInstallDirEnabled(const std::filesystem::path& dir, bool enabled);
|
||||
void SetGameInstallDirs(const std::vector<std::filesystem::path>& dirs_config);
|
||||
const std::vector<bool> GetGameInstallDirsEnabled();
|
||||
const std::vector<GameInstallDir>& GetAllGameInstallDirs() const;
|
||||
|
||||
std::filesystem::path GetHomeDir();
|
||||
void SetHomeDir(const std::filesystem::path& dir);
|
||||
std::filesystem::path GetSysModulesDir();
|
||||
void SetSysModulesDir(const std::filesystem::path& dir);
|
||||
std::filesystem::path GetFontsDir();
|
||||
void SetFontsDir(const std::filesystem::path& dir);
|
||||
|
||||
private:
|
||||
GeneralSettings m_general{};
|
||||
DebugSettings m_debug{};
|
||||
InputSettings m_input{};
|
||||
AudioSettings m_audio{};
|
||||
GPUSettings m_gpu{};
|
||||
VulkanSettings m_vulkan{};
|
||||
ConfigMode m_configMode{ConfigMode::Default};
|
||||
|
||||
bool m_loaded{false};
|
||||
|
||||
static std::shared_ptr<EmulatorSettingsImpl> s_instance;
|
||||
static std::mutex s_mutex;
|
||||
|
||||
/// Apply overrideable fields from groupJson into group.game_specific_value.
|
||||
template <typename Group>
|
||||
void ApplyGroupOverrides(Group& group, const nlohmann::json& groupJson,
|
||||
std::vector<std::string>& changed) {
|
||||
for (auto& item : group.GetOverrideableFields()) {
|
||||
if (!groupJson.contains(item.key))
|
||||
continue;
|
||||
item.apply(&group, groupJson.at(item.key), changed);
|
||||
}
|
||||
}
|
||||
|
||||
// Write all overrideable fields from group into out (for game-specific save).
|
||||
template <typename Group>
|
||||
static void SaveGroupGameSpecific(const Group& group, nlohmann::json& out) {
|
||||
for (auto& item : group.GetOverrideableFields())
|
||||
out[item.key] = item.get_for_save(&group);
|
||||
}
|
||||
|
||||
// Discard every game-specific override in group.
|
||||
template <typename Group>
|
||||
static void ClearGroupOverrides(Group& group) {
|
||||
for (auto& item : group.GetOverrideableFields())
|
||||
item.reset_game_specific(&group);
|
||||
}
|
||||
|
||||
static void PrintChangedSummary(const std::vector<std::string>& changed);
|
||||
|
||||
public:
|
||||
// Add these getters to access overrideable fields
|
||||
std::vector<OverrideItem> GetGeneralOverrideableFields() const {
|
||||
return m_general.GetOverrideableFields();
|
||||
}
|
||||
std::vector<OverrideItem> GetDebugOverrideableFields() const {
|
||||
return m_debug.GetOverrideableFields();
|
||||
}
|
||||
std::vector<OverrideItem> GetInputOverrideableFields() const {
|
||||
return m_input.GetOverrideableFields();
|
||||
}
|
||||
std::vector<OverrideItem> GetAudioOverrideableFields() const {
|
||||
return m_audio.GetOverrideableFields();
|
||||
}
|
||||
std::vector<OverrideItem> GetGPUOverrideableFields() const {
|
||||
return m_gpu.GetOverrideableFields();
|
||||
}
|
||||
std::vector<OverrideItem> GetVulkanOverrideableFields() const {
|
||||
return m_vulkan.GetOverrideableFields();
|
||||
}
|
||||
std::vector<std::string> GetAllOverrideableKeys() const;
|
||||
|
||||
#define SETTING_FORWARD(group, Name, field) \
|
||||
auto Get##Name() const { \
|
||||
return (group).field.get(m_configMode); \
|
||||
} \
|
||||
void Set##Name(const decltype((group).field.value)& v) { \
|
||||
(group).field.value = v; \
|
||||
}
|
||||
#define SETTING_FORWARD_BOOL(group, Name, field) \
|
||||
bool Is##Name() const { \
|
||||
return (group).field.get(m_configMode); \
|
||||
} \
|
||||
void Set##Name(bool v) { \
|
||||
(group).field.value = v; \
|
||||
}
|
||||
#define SETTING_FORWARD_BOOL_READONLY(group, Name, field) \
|
||||
bool Is##Name() const { \
|
||||
return (group).field.get(m_configMode); \
|
||||
}
|
||||
|
||||
// General settings
|
||||
SETTING_FORWARD(m_general, VolumeSlider, volume_slider)
|
||||
SETTING_FORWARD_BOOL(m_general, Neo, neo_mode)
|
||||
SETTING_FORWARD_BOOL(m_general, DevKit, dev_kit_mode)
|
||||
SETTING_FORWARD(m_general, ExtraDmemInMBytes, extra_dmem_in_mbytes)
|
||||
SETTING_FORWARD_BOOL(m_general, PSNSignedIn, psn_signed_in)
|
||||
SETTING_FORWARD_BOOL(m_general, TrophyPopupDisabled, trophy_popup_disabled)
|
||||
SETTING_FORWARD(m_general, TrophyNotificationDuration, trophy_notification_duration)
|
||||
SETTING_FORWARD(m_general, TrophyNotificationSide, trophy_notification_side)
|
||||
SETTING_FORWARD_BOOL(m_general, ShowSplash, show_splash)
|
||||
SETTING_FORWARD_BOOL(m_general, IdenticalLogGrouped, identical_log_grouped)
|
||||
SETTING_FORWARD(m_general, AddonInstallDir, addon_install_dir)
|
||||
SETTING_FORWARD(m_general, LogFilter, log_filter)
|
||||
SETTING_FORWARD(m_general, LogType, log_type)
|
||||
SETTING_FORWARD_BOOL(m_general, ConnectedToNetwork, connected_to_network)
|
||||
SETTING_FORWARD_BOOL(m_general, DiscordRPCEnabled, discord_rpc_enabled)
|
||||
SETTING_FORWARD_BOOL(m_general, ShowFpsCounter, show_fps_counter)
|
||||
SETTING_FORWARD(m_general, ConsoleLanguage, console_language)
|
||||
|
||||
// Audio settings
|
||||
SETTING_FORWARD(m_audio, AudioBackend, audio_backend)
|
||||
SETTING_FORWARD(m_audio, SDLMicDevice, sdl_mic_device)
|
||||
SETTING_FORWARD(m_audio, SDLMainOutputDevice, sdl_main_output_device)
|
||||
SETTING_FORWARD(m_audio, SDLPadSpkOutputDevice, sdl_padSpk_output_device)
|
||||
SETTING_FORWARD(m_audio, OpenALMicDevice, openal_mic_device)
|
||||
SETTING_FORWARD(m_audio, OpenALMainOutputDevice, openal_main_output_device)
|
||||
SETTING_FORWARD(m_audio, OpenALPadSpkOutputDevice, openal_padSpk_output_device)
|
||||
|
||||
// Debug settings
|
||||
SETTING_FORWARD_BOOL(m_debug, SeparateLoggingEnabled, separate_logging_enabled)
|
||||
SETTING_FORWARD_BOOL(m_debug, DebugDump, debug_dump)
|
||||
SETTING_FORWARD_BOOL(m_debug, ShaderCollect, shader_collect)
|
||||
SETTING_FORWARD_BOOL(m_debug, LogEnabled, log_enabled)
|
||||
SETTING_FORWARD(m_debug, ConfigVersion, config_version)
|
||||
|
||||
// GPU Settings
|
||||
SETTING_FORWARD_BOOL(m_gpu, NullGPU, null_gpu)
|
||||
SETTING_FORWARD_BOOL(m_gpu, DumpShaders, dump_shaders)
|
||||
SETTING_FORWARD_BOOL(m_gpu, CopyGpuBuffers, copy_gpu_buffers)
|
||||
SETTING_FORWARD_BOOL(m_gpu, FullScreen, full_screen)
|
||||
SETTING_FORWARD(m_gpu, FullScreenMode, full_screen_mode)
|
||||
SETTING_FORWARD(m_gpu, PresentMode, present_mode)
|
||||
SETTING_FORWARD(m_gpu, WindowHeight, window_height)
|
||||
SETTING_FORWARD(m_gpu, WindowWidth, window_width)
|
||||
SETTING_FORWARD(m_gpu, InternalScreenHeight, internal_screen_height)
|
||||
SETTING_FORWARD(m_gpu, InternalScreenWidth, internal_screen_width)
|
||||
SETTING_FORWARD_BOOL(m_gpu, HdrAllowed, hdr_allowed)
|
||||
SETTING_FORWARD_BOOL(m_gpu, FsrEnabled, fsr_enabled)
|
||||
SETTING_FORWARD_BOOL(m_gpu, RcasEnabled, rcas_enabled)
|
||||
SETTING_FORWARD(m_gpu, RcasAttenuation, rcas_attenuation)
|
||||
SETTING_FORWARD(m_gpu, ReadbacksMode, readbacks_mode)
|
||||
SETTING_FORWARD_BOOL(m_gpu, ReadbackLinearImagesEnabled, readback_linear_images_enabled)
|
||||
SETTING_FORWARD_BOOL(m_gpu, DirectMemoryAccessEnabled, direct_memory_access_enabled)
|
||||
SETTING_FORWARD_BOOL_READONLY(m_gpu, PatchShaders, patch_shaders)
|
||||
|
||||
u32 GetVblankFrequency() {
|
||||
if (m_gpu.vblank_frequency.value < 30) {
|
||||
return 30;
|
||||
}
|
||||
return m_gpu.vblank_frequency.get();
|
||||
}
|
||||
void SetVblankFrequency(const u32& v, bool is_specific = false) {
|
||||
u32 val = v < 30 ? 30 : v;
|
||||
if (is_specific) {
|
||||
m_gpu.vblank_frequency.game_specific_value = val;
|
||||
} else {
|
||||
m_gpu.vblank_frequency.value = val;
|
||||
}
|
||||
}
|
||||
|
||||
// Input Settings
|
||||
SETTING_FORWARD(m_input, CursorState, cursor_state)
|
||||
SETTING_FORWARD(m_input, CursorHideTimeout, cursor_hide_timeout)
|
||||
SETTING_FORWARD(m_input, UsbDeviceBackend, usb_device_backend)
|
||||
SETTING_FORWARD_BOOL(m_input, MotionControlsEnabled, motion_controls_enabled)
|
||||
SETTING_FORWARD_BOOL(m_input, BackgroundControllerInput, background_controller_input)
|
||||
SETTING_FORWARD(m_input, DefaultControllerId, default_controller_id)
|
||||
SETTING_FORWARD_BOOL(m_input, UsingSpecialPad, use_special_pad)
|
||||
SETTING_FORWARD(m_input, SpecialPadClass, special_pad_class)
|
||||
SETTING_FORWARD_BOOL(m_input, UseUnifiedInputConfig, use_unified_input_config)
|
||||
SETTING_FORWARD(m_input, CameraId, camera_id)
|
||||
|
||||
// Vulkan settings
|
||||
SETTING_FORWARD(m_vulkan, GpuId, gpu_id)
|
||||
SETTING_FORWARD_BOOL(m_vulkan, RenderdocEnabled, renderdoc_enabled)
|
||||
SETTING_FORWARD_BOOL(m_vulkan, VkValidationEnabled, vkvalidation_enabled)
|
||||
SETTING_FORWARD_BOOL(m_vulkan, VkValidationCoreEnabled, vkvalidation_core_enabled)
|
||||
SETTING_FORWARD_BOOL(m_vulkan, VkValidationSyncEnabled, vkvalidation_sync_enabled)
|
||||
SETTING_FORWARD_BOOL(m_vulkan, VkValidationGpuEnabled, vkvalidation_gpu_enabled)
|
||||
SETTING_FORWARD_BOOL(m_vulkan, VkCrashDiagnosticEnabled, vkcrash_diagnostic_enabled)
|
||||
SETTING_FORWARD_BOOL(m_vulkan, VkHostMarkersEnabled, vkhost_markers)
|
||||
SETTING_FORWARD_BOOL(m_vulkan, VkGuestMarkersEnabled, vkguest_markers)
|
||||
SETTING_FORWARD_BOOL(m_vulkan, PipelineCacheEnabled, pipeline_cache_enabled)
|
||||
SETTING_FORWARD_BOOL(m_vulkan, PipelineCacheArchived, pipeline_cache_archived)
|
||||
|
||||
#undef SETTING_FORWARD
|
||||
#undef SETTING_FORWARD_BOOL
|
||||
#undef SETTING_FORWARD_BOOL_READONLY
|
||||
};
|
||||
@ -113,6 +113,7 @@ bool PSF::Encode(const std::filesystem::path& filepath) const {
|
||||
LOG_ERROR(Core, "Failed to write PSF file. Written {} Expected {}", written,
|
||||
psf_buffer.size());
|
||||
}
|
||||
file.Close();
|
||||
return written == psf_buffer.size();
|
||||
}
|
||||
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
#include "common/key_manager.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/path_util.h"
|
||||
#include "core/file_format/npbind.h"
|
||||
#include "core/file_format/trp.h"
|
||||
|
||||
static void DecryptEFSM(std::span<const u8, 16> trophyKey, std::span<const u8, 16> NPcommID,
|
||||
@ -43,8 +42,10 @@ static void hexToBytes(const char* hex, unsigned char* dst) {
|
||||
}
|
||||
}
|
||||
|
||||
bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string titleId) {
|
||||
std::filesystem::path gameSysDir = trophyPath / "sce_sys/trophy/";
|
||||
bool TRP::Extract(const std::filesystem::path& trophyPath, int index, std::string npCommId,
|
||||
const std::filesystem::path& outputPath) {
|
||||
std::filesystem::path gameSysDir =
|
||||
trophyPath / "sce_sys/trophy/" / std::format("trophy{:02d}.trp", index);
|
||||
if (!std::filesystem::exists(gameSysDir)) {
|
||||
LOG_WARNING(Common_Filesystem, "Game trophy directory doesn't exist");
|
||||
return false;
|
||||
@ -61,117 +62,82 @@ bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string tit
|
||||
std::array<u8, 16> user_key{};
|
||||
std::copy(user_key_vec.begin(), user_key_vec.end(), user_key.begin());
|
||||
|
||||
// Load npbind.dat using the new class
|
||||
std::filesystem::path npbindPath = trophyPath / "sce_sys/npbind.dat";
|
||||
NPBindFile npbind;
|
||||
if (!npbind.Load(npbindPath.string())) {
|
||||
LOG_WARNING(Common_Filesystem, "Failed to load npbind.dat file");
|
||||
}
|
||||
|
||||
auto npCommIds = npbind.GetNpCommIds();
|
||||
if (npCommIds.empty()) {
|
||||
LOG_WARNING(Common_Filesystem, "No NPComm IDs found in npbind.dat");
|
||||
}
|
||||
|
||||
bool success = true;
|
||||
int trpFileIndex = 0;
|
||||
|
||||
try {
|
||||
// Process each TRP file in the trophy directory
|
||||
for (const auto& it : std::filesystem::directory_iterator(gameSysDir)) {
|
||||
if (!it.is_regular_file() || it.path().extension() != ".trp") {
|
||||
continue; // Skip non-TRP files
|
||||
}
|
||||
const auto& it = gameSysDir;
|
||||
if (it.extension() != ".trp") {
|
||||
return false;
|
||||
}
|
||||
Common::FS::IOFile file(it, Common::FS::FileAccessMode::Read);
|
||||
if (!file.IsOpen()) {
|
||||
LOG_ERROR(Common_Filesystem, "Unable to open trophy file: {}", it.string());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get NPCommID for this TRP file (if available)
|
||||
std::string npCommId;
|
||||
if (trpFileIndex < static_cast<int>(npCommIds.size())) {
|
||||
npCommId = npCommIds[trpFileIndex];
|
||||
LOG_DEBUG(Common_Filesystem, "Using NPCommID: {} for {}", npCommId,
|
||||
it.path().filename().string());
|
||||
} else {
|
||||
LOG_WARNING(Common_Filesystem, "No NPCommID found for TRP file index {}",
|
||||
trpFileIndex);
|
||||
}
|
||||
TrpHeader header;
|
||||
if (!file.Read(header)) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to read TRP header from {}", it.string());
|
||||
return false;
|
||||
}
|
||||
|
||||
Common::FS::IOFile file(it.path(), Common::FS::FileAccessMode::Read);
|
||||
if (!file.IsOpen()) {
|
||||
LOG_ERROR(Common_Filesystem, "Unable to open trophy file: {}", it.path().string());
|
||||
if (header.magic != TRP_MAGIC) {
|
||||
LOG_ERROR(Common_Filesystem, "Wrong trophy magic number in {}", it.string());
|
||||
return false;
|
||||
}
|
||||
|
||||
s64 seekPos = sizeof(TrpHeader);
|
||||
// Create output directories
|
||||
if (!std::filesystem::create_directories(outputPath / "Icons") ||
|
||||
!std::filesystem::create_directories(outputPath / "Xml")) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to create output directories for {}", npCommId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Process each entry in the TRP file
|
||||
for (int i = 0; i < header.entry_num; i++) {
|
||||
if (!file.Seek(seekPos)) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to seek to TRP entry offset");
|
||||
success = false;
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
seekPos += static_cast<s64>(header.entry_size);
|
||||
|
||||
TrpHeader header;
|
||||
if (!file.Read(header)) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to read TRP header from {}",
|
||||
it.path().string());
|
||||
TrpEntry entry;
|
||||
if (!file.Read(entry)) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to read TRP entry");
|
||||
success = false;
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
|
||||
if (header.magic != TRP_MAGIC) {
|
||||
LOG_ERROR(Common_Filesystem, "Wrong trophy magic number in {}", it.path().string());
|
||||
success = false;
|
||||
continue;
|
||||
}
|
||||
std::string_view name(entry.entry_name);
|
||||
|
||||
s64 seekPos = sizeof(TrpHeader);
|
||||
std::filesystem::path trpFilesPath(
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / titleId /
|
||||
"TrophyFiles" / it.path().stem());
|
||||
|
||||
// Create output directories
|
||||
if (!std::filesystem::create_directories(trpFilesPath / "Icons") ||
|
||||
!std::filesystem::create_directories(trpFilesPath / "Xml")) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to create output directories for {}", titleId);
|
||||
success = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Process each entry in the TRP file
|
||||
for (int i = 0; i < header.entry_num; i++) {
|
||||
if (!file.Seek(seekPos)) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to seek to TRP entry offset");
|
||||
if (entry.flag == ENTRY_FLAG_PNG) {
|
||||
if (!ProcessPngEntry(file, entry, outputPath, name)) {
|
||||
success = false;
|
||||
break;
|
||||
// Continue with next entry
|
||||
}
|
||||
seekPos += static_cast<s64>(header.entry_size);
|
||||
|
||||
TrpEntry entry;
|
||||
if (!file.Read(entry)) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to read TRP entry");
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
|
||||
std::string_view name(entry.entry_name);
|
||||
|
||||
if (entry.flag == ENTRY_FLAG_PNG) {
|
||||
if (!ProcessPngEntry(file, entry, trpFilesPath, name)) {
|
||||
} else if (entry.flag == ENTRY_FLAG_ENCRYPTED_XML) {
|
||||
// Check if we have a valid NPCommID for decryption
|
||||
if (npCommId.size() >= 12 && npCommId[0] == 'N' && npCommId[1] == 'P') {
|
||||
if (!ProcessEncryptedXmlEntry(file, entry, outputPath, name, user_key,
|
||||
npCommId)) {
|
||||
success = false;
|
||||
// Continue with next entry
|
||||
}
|
||||
} else if (entry.flag == ENTRY_FLAG_ENCRYPTED_XML) {
|
||||
// Check if we have a valid NPCommID for decryption
|
||||
if (npCommId.size() >= 12 && npCommId[0] == 'N' && npCommId[1] == 'P') {
|
||||
if (!ProcessEncryptedXmlEntry(file, entry, trpFilesPath, name, user_key,
|
||||
npCommId)) {
|
||||
success = false;
|
||||
// Continue with next entry
|
||||
}
|
||||
} else {
|
||||
LOG_WARNING(Common_Filesystem,
|
||||
"Skipping encrypted XML entry - invalid NPCommID");
|
||||
// Skip this entry but continue
|
||||
}
|
||||
} else {
|
||||
LOG_DEBUG(Common_Filesystem, "Unknown entry flag: {} for {}",
|
||||
static_cast<unsigned int>(entry.flag), name);
|
||||
LOG_WARNING(Common_Filesystem,
|
||||
"Skipping encrypted XML entry - invalid NPCommID");
|
||||
// Skip this entry but continue
|
||||
}
|
||||
} else {
|
||||
LOG_DEBUG(Common_Filesystem, "Unknown entry flag: {} for {}",
|
||||
static_cast<unsigned int>(entry.flag), name);
|
||||
}
|
||||
|
||||
trpFileIndex++;
|
||||
}
|
||||
|
||||
} catch (const std::filesystem::filesystem_error& e) {
|
||||
LOG_CRITICAL(Common_Filesystem, "Filesystem error during trophy extraction: {}", e.what());
|
||||
return false;
|
||||
@ -182,7 +148,7 @@ bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string tit
|
||||
|
||||
if (success) {
|
||||
LOG_INFO(Common_Filesystem, "Successfully extracted {} trophy files for {}", trpFileIndex,
|
||||
titleId);
|
||||
npCommId);
|
||||
}
|
||||
|
||||
return success;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@ -36,7 +36,8 @@ class TRP {
|
||||
public:
|
||||
TRP();
|
||||
~TRP();
|
||||
bool Extract(const std::filesystem::path& trophyPath, const std::string titleId);
|
||||
bool Extract(const std::filesystem::path& trophyPath, int index, std::string npCommId,
|
||||
const std::filesystem::path& outputPath);
|
||||
|
||||
private:
|
||||
bool ProcessPngEntry(Common::FS::IOFile& file, const TrpEntry& entry,
|
||||
@ -45,9 +46,6 @@ private:
|
||||
const std::filesystem::path& outputPath, std::string_view name,
|
||||
const std::array<u8, 16>& user_key, const std::string& npCommId);
|
||||
|
||||
std::vector<u8> NPcommID = std::vector<u8>(12);
|
||||
std::array<u8, 16> np_comm_id{};
|
||||
std::array<u8, 16> esfmIv{};
|
||||
std::filesystem::path trpFilesPath;
|
||||
static constexpr int iv_len = 16;
|
||||
};
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include "common/config.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/file_sys/devices/logger.h"
|
||||
#include "core/file_sys/devices/nop_device.h"
|
||||
|
||||
@ -8,12 +8,12 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/memory_patcher.h"
|
||||
#include "common/thread.h"
|
||||
#include "common/types.h"
|
||||
#include "core/debug_state.h"
|
||||
#include "core/debugger.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/emulator_state.h"
|
||||
#include "core/libraries/audio/audioout.h"
|
||||
#include "input/input_handler.h"
|
||||
@ -153,7 +153,7 @@ void IPC::InputLoop() {
|
||||
} else if (cmd == "ADJUST_VOLUME") {
|
||||
int value = static_cast<int>(next_u64());
|
||||
bool is_game_specific = next_u64() != 0;
|
||||
Config::setVolumeSlider(value, is_game_specific);
|
||||
EmulatorSettings.SetVolumeSlider(value);
|
||||
Libraries::AudioOut::AdjustVol();
|
||||
} else if (cmd == "SET_FSR") {
|
||||
bool use_fsr = next_u64() != 0;
|
||||
@ -211,13 +211,6 @@ void IPC::InputLoop() {
|
||||
} else if (cmd == "RELOAD_INPUTS") {
|
||||
std::string config = next_str();
|
||||
Input::ParseInputConfig(config);
|
||||
} else if (cmd == "SET_ACTIVE_CONTROLLER") {
|
||||
std::string active_controller = next_str();
|
||||
GamepadSelect::SetSelectedGamepad(active_controller);
|
||||
SDL_Event checkGamepad;
|
||||
SDL_memset(&checkGamepad, 0, sizeof(checkGamepad));
|
||||
checkGamepad.type = SDL_EVENT_CHANGE_CONTROLLER;
|
||||
SDL_PushEvent(&checkGamepad);
|
||||
} else {
|
||||
std::cerr << ";UNKNOWN CMD: " << cmd << std::endl;
|
||||
}
|
||||
|
||||
@ -5,9 +5,9 @@
|
||||
|
||||
#include "app_content.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/file_format/psf.h"
|
||||
#include "core/file_sys/fs.h"
|
||||
#include "core/libraries/app_content/app_content_error.h"
|
||||
@ -57,7 +57,7 @@ int PS4_SYSV_ABI sceAppContentAddcontMount(u32 service_label,
|
||||
OrbisAppContentMountPoint* mount_point) {
|
||||
LOG_INFO(Lib_AppContent, "called");
|
||||
|
||||
const auto& addon_path = Config::getAddonInstallDir() / title_id;
|
||||
const auto& addon_path = EmulatorSettings.GetAddonInstallDir() / title_id;
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
|
||||
// Determine which loaded additional content this entitlement label is for.
|
||||
@ -282,7 +282,7 @@ int PS4_SYSV_ABI sceAppContentInitialize(const OrbisAppContentInitParam* initPar
|
||||
LOG_ERROR(Lib_AppContent, "(DUMMY) called");
|
||||
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
||||
|
||||
const auto addons_dir = Config::getAddonInstallDir();
|
||||
const auto addons_dir = EmulatorSettings.GetAddonInstallDir();
|
||||
if (const auto value = param_sfo->GetString("TITLE_ID"); value.has_value()) {
|
||||
title_id = *value;
|
||||
} else {
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <core/libraries/system/userservice.h>
|
||||
#include "common/types.h"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <memory>
|
||||
@ -6,10 +6,9 @@
|
||||
#include <shared_mutex>
|
||||
#include <stop_token>
|
||||
#include <thread>
|
||||
#include <core/emulator_settings.h>
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/libraries/audio/audioout.h"
|
||||
@ -206,7 +205,11 @@ s32 PS4_SYSV_ABI sceAudioOutInit() {
|
||||
return ORBIS_AUDIO_OUT_ERROR_ALREADY_INIT;
|
||||
}
|
||||
|
||||
audio = std::make_unique<SDLAudioOut>();
|
||||
if (EmulatorSettings.GetAudioBackend() == AudioBackend::OpenAL) {
|
||||
audio = std::make_unique<OpenALAudioOut>();
|
||||
} else {
|
||||
audio = std::make_unique<SDLAudioOut>();
|
||||
}
|
||||
|
||||
LOG_INFO(Lib_AudioOut, "Audio system initialized");
|
||||
return ORBIS_OK;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@ -31,4 +31,9 @@ public:
|
||||
std::unique_ptr<PortBackend> Open(PortOut& port) override;
|
||||
};
|
||||
|
||||
class OpenALAudioOut final : public AudioOutBackend {
|
||||
public:
|
||||
std::unique_ptr<PortBackend> Open(PortOut& port) override;
|
||||
};
|
||||
|
||||
} // namespace Libraries::AudioOut
|
||||
|
||||
832
src/core/libraries/audio/openal_audio_out.cpp
Normal file
832
src/core/libraries/audio/openal_audio_out.cpp
Normal file
@ -0,0 +1,832 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
#include <alext.h>
|
||||
#include <queue>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/audio/audioout.h"
|
||||
#include "core/libraries/audio/audioout_backend.h"
|
||||
#include "core/libraries/audio/openal_manager.h"
|
||||
#include "core/libraries/kernel/threads.h"
|
||||
|
||||
// SIMD support detection
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
#include <immintrin.h>
|
||||
#define HAS_SSE2
|
||||
#endif
|
||||
|
||||
namespace Libraries::AudioOut {
|
||||
|
||||
// Volume constants
|
||||
constexpr float VOLUME_0DB = 32768.0f; // 1 << 15
|
||||
constexpr float INV_VOLUME_0DB = 1.0f / VOLUME_0DB;
|
||||
constexpr float VOLUME_EPSILON = 0.001f;
|
||||
// Timing constants
|
||||
constexpr u64 VOLUME_CHECK_INTERVAL_US = 50000; // Check every 50ms
|
||||
constexpr u64 MIN_SLEEP_THRESHOLD_US = 10;
|
||||
constexpr u64 TIMING_RESYNC_THRESHOLD_US = 100000; // Resync if >100ms behind
|
||||
|
||||
// OpenAL constants
|
||||
constexpr ALsizei NUM_BUFFERS = 6;
|
||||
constexpr ALsizei BUFFER_QUEUE_THRESHOLD = 2; // Queue more buffers when below this
|
||||
|
||||
// Channel positions
|
||||
enum ChannelPos : u8 {
|
||||
FL = 0,
|
||||
FR = 1,
|
||||
FC = 2,
|
||||
LF = 3,
|
||||
SL = 4,
|
||||
SR = 5,
|
||||
BL = 6,
|
||||
BR = 7,
|
||||
STD_SL = 6,
|
||||
STD_SR = 7,
|
||||
STD_BL = 4,
|
||||
STD_BR = 5
|
||||
};
|
||||
|
||||
class OpenALPortBackend : public PortBackend {
|
||||
public:
|
||||
explicit OpenALPortBackend(const PortOut& port)
|
||||
: frame_size(port.format_info.FrameSize()), guest_buffer_size(port.BufferSize()),
|
||||
buffer_frames(port.buffer_frames), sample_rate(port.sample_rate),
|
||||
num_channels(port.format_info.num_channels), is_float(port.format_info.is_float),
|
||||
is_std(port.format_info.is_std), channel_layout(port.format_info.channel_layout),
|
||||
device_registered(false), device_name(GetDeviceName(port.type)) {
|
||||
|
||||
if (!Initialize(port.type)) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to initialize OpenAL audio backend");
|
||||
}
|
||||
}
|
||||
|
||||
~OpenALPortBackend() override {
|
||||
// Unregister port before cleanup
|
||||
if (device_registered) {
|
||||
OpenALDevice::GetInstance().UnregisterPort(device_name);
|
||||
}
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
void Output(void* ptr) override {
|
||||
if (!source || !convert) [[unlikely]] {
|
||||
return;
|
||||
}
|
||||
if (ptr == nullptr) [[unlikely]] {
|
||||
return;
|
||||
}
|
||||
if (!device_context->MakeCurrent(device_name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateVolumeIfChanged();
|
||||
const u64 current_time = Kernel::sceKernelGetProcessTime();
|
||||
|
||||
// Convert audio data ONCE per call
|
||||
if (use_native_float) {
|
||||
convert(ptr, al_buffer_float.data(), buffer_frames, nullptr);
|
||||
} else {
|
||||
convert(ptr, al_buffer_s16.data(), buffer_frames, nullptr);
|
||||
}
|
||||
|
||||
// Reclaim processed buffers
|
||||
ALint processed = 0;
|
||||
alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed);
|
||||
|
||||
while (processed > 0) {
|
||||
ALuint buffer_id;
|
||||
alSourceUnqueueBuffers(source, 1, &buffer_id);
|
||||
if (alGetError() == AL_NO_ERROR) {
|
||||
available_buffers.push_back(buffer_id);
|
||||
processed--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Queue buffer
|
||||
if (!available_buffers.empty()) {
|
||||
ALuint buffer_id = available_buffers.back();
|
||||
available_buffers.pop_back();
|
||||
|
||||
if (use_native_float) {
|
||||
alBufferData(buffer_id, format, al_buffer_float.data(), buffer_size_bytes,
|
||||
sample_rate);
|
||||
} else {
|
||||
alBufferData(buffer_id, format, al_buffer_s16.data(), buffer_size_bytes,
|
||||
sample_rate);
|
||||
}
|
||||
alSourceQueueBuffers(source, 1, &buffer_id);
|
||||
}
|
||||
|
||||
// Check state and queue health
|
||||
ALint state = 0;
|
||||
ALint queued = 0;
|
||||
alGetSourcei(source, AL_SOURCE_STATE, &state);
|
||||
alGetSourcei(source, AL_BUFFERS_QUEUED, &queued);
|
||||
|
||||
if (state != AL_PLAYING && queued > 0) {
|
||||
LOG_DEBUG(Lib_AudioOut, "Audio underrun detected (queued: {}), restarting source",
|
||||
queued);
|
||||
alSourcePlay(source);
|
||||
}
|
||||
|
||||
// Only sleep if we have healthy buffer queue
|
||||
if (queued >= 2) {
|
||||
HandleTiming(current_time);
|
||||
} else {
|
||||
next_output_time = current_time + period_us;
|
||||
}
|
||||
|
||||
last_output_time.store(current_time, std::memory_order_release);
|
||||
output_count++;
|
||||
}
|
||||
void SetVolume(const std::array<int, 8>& ch_volumes) override {
|
||||
if (!device_context->MakeCurrent(device_name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!source) [[unlikely]] {
|
||||
return;
|
||||
}
|
||||
|
||||
float max_channel_gain = 0.0f;
|
||||
const u32 channels_to_check = std::min(num_channels, 8u);
|
||||
|
||||
for (u32 i = 0; i < channels_to_check; i++) {
|
||||
const float channel_gain = static_cast<float>(ch_volumes[i]) * INV_VOLUME_0DB;
|
||||
max_channel_gain = std::max(max_channel_gain, channel_gain);
|
||||
}
|
||||
|
||||
const float slider_gain = EmulatorSettings.GetVolumeSlider() * 0.01f;
|
||||
const float total_gain = max_channel_gain * slider_gain;
|
||||
|
||||
const float current = current_gain.load(std::memory_order_acquire);
|
||||
if (std::abs(total_gain - current) < VOLUME_EPSILON) {
|
||||
return;
|
||||
}
|
||||
|
||||
alSourcef(source, AL_GAIN, total_gain);
|
||||
|
||||
ALenum error = alGetError();
|
||||
if (error == AL_NO_ERROR) {
|
||||
current_gain.store(total_gain, std::memory_order_release);
|
||||
LOG_DEBUG(Lib_AudioOut,
|
||||
"Set combined audio gain to {:.3f} (channel: {:.3f}, slider: {:.3f})",
|
||||
total_gain, max_channel_gain, slider_gain);
|
||||
} else {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to set OpenAL source gain: {}",
|
||||
GetALErrorString(error));
|
||||
}
|
||||
}
|
||||
|
||||
u64 GetLastOutputTime() const {
|
||||
return last_output_time.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
private:
|
||||
bool Initialize(OrbisAudioOutPort type) {
|
||||
// Register this port with the device manager
|
||||
if (!OpenALDevice::GetInstance().RegisterPort(device_name)) {
|
||||
if (device_name == "None") {
|
||||
LOG_INFO(Lib_AudioOut, "Audio device disabled for port type {}",
|
||||
static_cast<int>(type));
|
||||
} else {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to register OpenAL device '{}'", device_name);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
device_registered = true;
|
||||
device_context = &OpenALDevice::GetInstance();
|
||||
|
||||
// Make this device's context current
|
||||
if (!device_context->MakeCurrent(device_name)) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to make OpenAL context current for device '{}'",
|
||||
device_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Log device info
|
||||
LOG_INFO(Lib_AudioOut, "Using OpenAL device for port type {}: '{}'", static_cast<int>(type),
|
||||
device_name);
|
||||
|
||||
// Calculate timing parameters
|
||||
period_us = (1000000ULL * buffer_frames + sample_rate / 2) / sample_rate;
|
||||
|
||||
// Check for AL_EXT_FLOAT32 extension
|
||||
has_float_ext = alIsExtensionPresent("AL_EXT_FLOAT32");
|
||||
if (has_float_ext && is_float) {
|
||||
LOG_INFO(Lib_AudioOut, "AL_EXT_FLOAT32 extension detected - using native float format");
|
||||
}
|
||||
|
||||
// Determine OpenAL format
|
||||
if (!DetermineOpenALFormat()) {
|
||||
LOG_ERROR(Lib_AudioOut, "Unsupported audio format for OpenAL");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate buffers based on format
|
||||
if (use_native_float) {
|
||||
al_buffer_float.resize(buffer_frames * num_channels);
|
||||
buffer_size_bytes = buffer_frames * num_channels * sizeof(float);
|
||||
} else {
|
||||
al_buffer_s16.resize(buffer_frames * num_channels);
|
||||
buffer_size_bytes = buffer_frames * num_channels * sizeof(s16);
|
||||
}
|
||||
|
||||
// Select optimal converter function
|
||||
if (!SelectConverter()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Generate OpenAL source and buffers
|
||||
if (!CreateOpenALObjects()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize current gain
|
||||
current_gain.store(EmulatorSettings.GetVolumeSlider() * 0.01f, std::memory_order_relaxed);
|
||||
alSourcef(source, AL_GAIN, current_gain.load(std::memory_order_relaxed));
|
||||
|
||||
// Prime buffers with silence
|
||||
if (use_native_float) {
|
||||
std::vector<float> silence(buffer_frames * num_channels, 0.0f);
|
||||
for (size_t i = 0; i < buffers.size() - 1; i++) {
|
||||
ALuint buffer_id = available_buffers.back();
|
||||
available_buffers.pop_back();
|
||||
alBufferData(buffer_id, format, silence.data(), buffer_size_bytes, sample_rate);
|
||||
alSourceQueueBuffers(source, 1, &buffer_id);
|
||||
}
|
||||
} else {
|
||||
std::vector<s16> silence(buffer_frames * num_channels, 0);
|
||||
for (size_t i = 0; i < buffers.size() - 1; i++) {
|
||||
ALuint buffer_id = available_buffers.back();
|
||||
available_buffers.pop_back();
|
||||
alBufferData(buffer_id, format, silence.data(), buffer_size_bytes, sample_rate);
|
||||
alSourceQueueBuffers(source, 1, &buffer_id);
|
||||
}
|
||||
}
|
||||
|
||||
alSourcePlay(source);
|
||||
|
||||
LOG_INFO(Lib_AudioOut,
|
||||
"Initialized OpenAL backend ({} Hz, {} ch, {} format, {}) for device '{}'",
|
||||
sample_rate, num_channels, is_float ? "float" : "int16",
|
||||
use_native_float ? "native" : "converted", device_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Cleanup() {
|
||||
if (!device_context || !device_context->MakeCurrent(device_name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (source) {
|
||||
alSourceStop(source);
|
||||
|
||||
ALint queued = 0;
|
||||
alGetSourcei(source, AL_BUFFERS_QUEUED, &queued);
|
||||
while (queued-- > 0) {
|
||||
ALuint buf;
|
||||
alSourceUnqueueBuffers(source, 1, &buf);
|
||||
}
|
||||
|
||||
alDeleteSources(1, &source);
|
||||
source = 0;
|
||||
}
|
||||
|
||||
if (!buffers.empty()) {
|
||||
alDeleteBuffers(static_cast<ALsizei>(buffers.size()), buffers.data());
|
||||
buffers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
std::string GetDeviceName(OrbisAudioOutPort type) const {
|
||||
switch (type) {
|
||||
case OrbisAudioOutPort::Main:
|
||||
case OrbisAudioOutPort::Bgm:
|
||||
return EmulatorSettings.GetOpenALMainOutputDevice();
|
||||
case OrbisAudioOutPort::PadSpk:
|
||||
return EmulatorSettings.GetOpenALPadSpkOutputDevice();
|
||||
default:
|
||||
return EmulatorSettings.GetOpenALMainOutputDevice();
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateVolumeIfChanged() {
|
||||
const u64 current_time = Kernel::sceKernelGetProcessTime();
|
||||
|
||||
if (current_time - last_volume_check_time < VOLUME_CHECK_INTERVAL_US) {
|
||||
return;
|
||||
}
|
||||
|
||||
last_volume_check_time = current_time;
|
||||
|
||||
const float config_volume = EmulatorSettings.GetVolumeSlider() * 0.01f;
|
||||
const float stored_gain = current_gain.load(std::memory_order_acquire);
|
||||
|
||||
if (std::abs(config_volume - stored_gain) > VOLUME_EPSILON) {
|
||||
alSourcef(source, AL_GAIN, config_volume);
|
||||
|
||||
ALenum error = alGetError();
|
||||
if (error == AL_NO_ERROR) {
|
||||
current_gain.store(config_volume, std::memory_order_release);
|
||||
LOG_DEBUG(Lib_AudioOut, "Updated audio gain to {:.3f}", config_volume);
|
||||
} else {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to set audio gain: {}", GetALErrorString(error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HandleTiming(u64 current_time) {
|
||||
if (next_output_time == 0) [[unlikely]] {
|
||||
next_output_time = current_time + period_us;
|
||||
return;
|
||||
}
|
||||
|
||||
const s64 time_diff = static_cast<s64>(current_time - next_output_time);
|
||||
|
||||
if (time_diff > static_cast<s64>(TIMING_RESYNC_THRESHOLD_US)) [[unlikely]] {
|
||||
next_output_time = current_time + period_us;
|
||||
} else if (time_diff < 0) {
|
||||
const u64 time_to_wait = static_cast<u64>(-time_diff);
|
||||
next_output_time += period_us;
|
||||
|
||||
if (time_to_wait > MIN_SLEEP_THRESHOLD_US) {
|
||||
const u64 sleep_duration = time_to_wait - MIN_SLEEP_THRESHOLD_US;
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(sleep_duration));
|
||||
}
|
||||
} else {
|
||||
next_output_time += period_us;
|
||||
}
|
||||
}
|
||||
|
||||
bool DetermineOpenALFormat() {
|
||||
// Try to use native float formats if extension is available
|
||||
if (is_float && has_float_ext) {
|
||||
switch (num_channels) {
|
||||
case 1:
|
||||
format = AL_FORMAT_MONO_FLOAT32;
|
||||
use_native_float = true;
|
||||
return true;
|
||||
case 2:
|
||||
format = AL_FORMAT_STEREO_FLOAT32;
|
||||
use_native_float = true;
|
||||
return true;
|
||||
case 4:
|
||||
format = alGetEnumValue("AL_FORMAT_QUAD32");
|
||||
if (format != 0 && alGetError() == AL_NO_ERROR) {
|
||||
use_native_float = true;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
format = alGetEnumValue("AL_FORMAT_51CHN32");
|
||||
if (format != 0 && alGetError() == AL_NO_ERROR) {
|
||||
use_native_float = true;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
format = alGetEnumValue("AL_FORMAT_71CHN32");
|
||||
if (format != 0 && alGetError() == AL_NO_ERROR) {
|
||||
use_native_float = true;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_WARNING(
|
||||
Lib_AudioOut,
|
||||
"Float format for {} channels not supported, falling back to S16 conversion",
|
||||
num_channels);
|
||||
}
|
||||
|
||||
// Fall back to S16 formats (with conversion if needed)
|
||||
use_native_float = false;
|
||||
|
||||
if (is_float) {
|
||||
// Will need to convert float to S16
|
||||
format = AL_FORMAT_MONO16;
|
||||
|
||||
switch (num_channels) {
|
||||
case 1:
|
||||
format = AL_FORMAT_MONO16;
|
||||
break;
|
||||
case 2:
|
||||
format = AL_FORMAT_STEREO16;
|
||||
break;
|
||||
case 6:
|
||||
format = alGetEnumValue("AL_FORMAT_51CHN16");
|
||||
if (format == 0 || alGetError() != AL_NO_ERROR) {
|
||||
LOG_WARNING(Lib_AudioOut, "5.1 format not supported, falling back to stereo");
|
||||
format = AL_FORMAT_STEREO16;
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
format = alGetEnumValue("AL_FORMAT_71CHN16");
|
||||
if (format == 0 || alGetError() != AL_NO_ERROR) {
|
||||
LOG_WARNING(Lib_AudioOut, "7.1 format not supported, falling back to stereo");
|
||||
format = AL_FORMAT_STEREO16;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Lib_AudioOut, "Unsupported float channel count: {}", num_channels);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Native 16-bit integer formats
|
||||
switch (num_channels) {
|
||||
case 1:
|
||||
format = AL_FORMAT_MONO16;
|
||||
break;
|
||||
case 2:
|
||||
format = AL_FORMAT_STEREO16;
|
||||
break;
|
||||
case 6:
|
||||
format = alGetEnumValue("AL_FORMAT_51CHN16");
|
||||
if (format == 0 || alGetError() != AL_NO_ERROR) {
|
||||
LOG_WARNING(Lib_AudioOut, "5.1 format not supported, falling back to stereo");
|
||||
format = AL_FORMAT_STEREO16;
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
format = alGetEnumValue("AL_FORMAT_71CHN16");
|
||||
if (format == 0 || alGetError() != AL_NO_ERROR) {
|
||||
LOG_WARNING(Lib_AudioOut, "7.1 format not supported, falling back to stereo");
|
||||
format = AL_FORMAT_STEREO16;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Lib_AudioOut, "Unsupported S16 channel count: {}", num_channels);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CreateOpenALObjects() {
|
||||
alGenSources(1, &source);
|
||||
if (alGetError() != AL_NO_ERROR) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to generate OpenAL source");
|
||||
return false;
|
||||
}
|
||||
|
||||
buffers.resize(NUM_BUFFERS);
|
||||
alGenBuffers(static_cast<ALsizei>(buffers.size()), buffers.data());
|
||||
if (alGetError() != AL_NO_ERROR) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to generate OpenAL buffers");
|
||||
alDeleteSources(1, &source);
|
||||
source = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
available_buffers = buffers;
|
||||
|
||||
alSourcef(source, AL_PITCH, 1.0f);
|
||||
alSourcef(source, AL_GAIN, 1.0f);
|
||||
alSource3f(source, AL_POSITION, 0.0f, 0.0f, 0.0f);
|
||||
alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
|
||||
alSourcei(source, AL_LOOPING, AL_FALSE);
|
||||
alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE);
|
||||
|
||||
LOG_DEBUG(Lib_AudioOut, "Created OpenAL source {} with {} buffers", source, buffers.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SelectConverter() {
|
||||
if (is_float && use_native_float) {
|
||||
// Native float - just copy/remap if needed
|
||||
switch (num_channels) {
|
||||
case 1:
|
||||
convert = &ConvertF32Mono;
|
||||
break;
|
||||
case 2:
|
||||
convert = &ConvertF32Stereo;
|
||||
break;
|
||||
case 8:
|
||||
convert = is_std ? &ConvertF32Std8CH : &ConvertF32_8CH;
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Lib_AudioOut, "Unsupported float channel count: {}", num_channels);
|
||||
return false;
|
||||
}
|
||||
} else if (is_float && !use_native_float) {
|
||||
// Float to S16 conversion needed
|
||||
switch (num_channels) {
|
||||
case 1:
|
||||
convert = &ConvertF32ToS16Mono;
|
||||
break;
|
||||
case 2:
|
||||
#ifdef HAS_SSE2
|
||||
convert = &ConvertF32ToS16StereoSIMD;
|
||||
#else
|
||||
convert = &ConvertF32ToS16Stereo;
|
||||
#endif
|
||||
break;
|
||||
case 8:
|
||||
#ifdef HAS_SSE2
|
||||
convert = is_std ? &ConvertF32ToS16Std8CH : &ConvertF32ToS16_8CH_SIMD;
|
||||
#else
|
||||
convert = is_std ? &ConvertF32ToS16Std8CH : &ConvertF32ToS16_8CH;
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Lib_AudioOut, "Unsupported float channel count: {}", num_channels);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// S16 native - just copy
|
||||
switch (num_channels) {
|
||||
case 1:
|
||||
convert = &ConvertS16Mono;
|
||||
break;
|
||||
case 2:
|
||||
convert = &ConvertS16Stereo;
|
||||
break;
|
||||
case 8:
|
||||
convert = &ConvertS16_8CH;
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Lib_AudioOut, "Unsupported S16 channel count: {}", num_channels);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* GetALErrorString(ALenum error) {
|
||||
switch (error) {
|
||||
case AL_NO_ERROR:
|
||||
return "AL_NO_ERROR";
|
||||
case AL_INVALID_NAME:
|
||||
return "AL_INVALID_NAME";
|
||||
case AL_INVALID_ENUM:
|
||||
return "AL_INVALID_ENUM";
|
||||
case AL_INVALID_VALUE:
|
||||
return "AL_INVALID_VALUE";
|
||||
case AL_INVALID_OPERATION:
|
||||
return "AL_INVALID_OPERATION";
|
||||
case AL_OUT_OF_MEMORY:
|
||||
return "AL_OUT_OF_MEMORY";
|
||||
default:
|
||||
return "Unknown AL error";
|
||||
}
|
||||
}
|
||||
|
||||
// Converter function type
|
||||
using ConverterFunc = void (*)(const void* src, void* dst, u32 frames, const float* volumes);
|
||||
|
||||
static inline s16 OrbisFloatToS16(float v) {
|
||||
if (std::abs(v) < 1.0e-20f)
|
||||
v = 0.0f;
|
||||
|
||||
// Sony behavior: +1.0f -> 32767, -1.0f -> -32768
|
||||
const float scaled = v * 32768.0f;
|
||||
|
||||
if (scaled >= 32767.0f)
|
||||
return 32767;
|
||||
if (scaled <= -32768.0f)
|
||||
return -32768;
|
||||
|
||||
return static_cast<s16>(scaled + (scaled >= 0 ? 0.5f : -0.5f));
|
||||
}
|
||||
static void ConvertS16Mono(const void* src, void* dst, u32 frames, const float*) {
|
||||
const s16* s = static_cast<const s16*>(src);
|
||||
s16* d = static_cast<s16*>(dst);
|
||||
std::memcpy(d, s, frames * sizeof(s16));
|
||||
}
|
||||
|
||||
static void ConvertS16Stereo(const void* src, void* dst, u32 frames, const float*) {
|
||||
const s16* s = static_cast<const s16*>(src);
|
||||
s16* d = static_cast<s16*>(dst);
|
||||
|
||||
const u32 num_samples = frames << 1;
|
||||
std::memcpy(d, s, num_samples * sizeof(s16));
|
||||
}
|
||||
|
||||
static void ConvertS16_8CH(const void* src, void* dst, u32 frames, const float*) {
|
||||
const s16* s = static_cast<const s16*>(src);
|
||||
s16* d = static_cast<s16*>(dst);
|
||||
|
||||
const u32 num_samples = frames << 3;
|
||||
std::memcpy(d, s, num_samples * sizeof(s16));
|
||||
}
|
||||
|
||||
// Float passthrough converters (for AL_EXT_FLOAT32)
|
||||
static void ConvertF32Mono(const void* src, void* dst, u32 frames, const float*) {
|
||||
const float* s = static_cast<const float*>(src);
|
||||
float* d = static_cast<float*>(dst);
|
||||
std::memcpy(d, s, frames * sizeof(float));
|
||||
}
|
||||
|
||||
static void ConvertF32Stereo(const void* src, void* dst, u32 frames, const float*) {
|
||||
const float* s = static_cast<const float*>(src);
|
||||
float* d = static_cast<float*>(dst);
|
||||
std::memcpy(d, s, frames * 2 * sizeof(float));
|
||||
}
|
||||
|
||||
static void ConvertF32_8CH(const void* src, void* dst, u32 frames, const float*) {
|
||||
const float* s = static_cast<const float*>(src);
|
||||
float* d = static_cast<float*>(dst);
|
||||
std::memcpy(d, s, frames * 8 * sizeof(float));
|
||||
}
|
||||
|
||||
static void ConvertF32Std8CH(const void* src, void* dst, u32 frames, const float*) {
|
||||
const float* s = static_cast<const float*>(src);
|
||||
float* d = static_cast<float*>(dst);
|
||||
|
||||
for (u32 i = 0; i < frames; i++) {
|
||||
const u32 offset = i << 3;
|
||||
d[offset + FL] = s[offset + FL];
|
||||
d[offset + FR] = s[offset + FR];
|
||||
d[offset + FC] = s[offset + FC];
|
||||
d[offset + LF] = s[offset + LF];
|
||||
d[offset + SL] = s[offset + STD_SL];
|
||||
d[offset + SR] = s[offset + STD_SR];
|
||||
d[offset + BL] = s[offset + STD_BL];
|
||||
d[offset + BR] = s[offset + STD_BR];
|
||||
}
|
||||
}
|
||||
|
||||
// Float to S16 converters for OpenAL
|
||||
static void ConvertF32ToS16Mono(const void* src, void* dst, u32 frames, const float*) {
|
||||
const float* s = static_cast<const float*>(src);
|
||||
s16* d = static_cast<s16*>(dst);
|
||||
|
||||
for (u32 i = 0; i < frames; i++)
|
||||
d[i] = OrbisFloatToS16(s[i]);
|
||||
}
|
||||
#ifdef HAS_SSE2
|
||||
static void ConvertF32ToS16StereoSIMD(const void* src, void* dst, u32 frames, const float*) {
|
||||
const float* s = static_cast<const float*>(src);
|
||||
s16* d = static_cast<s16*>(dst);
|
||||
|
||||
const __m128 scale = _mm_set1_ps(32768.0f);
|
||||
const __m128 min_val = _mm_set1_ps(-32768.0f);
|
||||
const __m128 max_val = _mm_set1_ps(32767.0f);
|
||||
|
||||
const u32 num_samples = frames << 1;
|
||||
u32 i = 0;
|
||||
|
||||
// Process 8 samples at a time
|
||||
for (; i + 8 <= num_samples; i += 8) {
|
||||
// Load 8 floats
|
||||
__m128 f1 = _mm_loadu_ps(&s[i]);
|
||||
__m128 f2 = _mm_loadu_ps(&s[i + 4]);
|
||||
|
||||
// Scale and clamp
|
||||
f1 = _mm_mul_ps(f1, scale);
|
||||
f2 = _mm_mul_ps(f2, scale);
|
||||
f1 = _mm_max_ps(f1, min_val);
|
||||
f2 = _mm_max_ps(f2, min_val);
|
||||
f1 = _mm_min_ps(f1, max_val);
|
||||
f2 = _mm_min_ps(f2, max_val);
|
||||
|
||||
// Convert to int32
|
||||
__m128i i1 = _mm_cvtps_epi32(f1);
|
||||
__m128i i2 = _mm_cvtps_epi32(f2);
|
||||
|
||||
// Pack to int16
|
||||
__m128i packed = _mm_packs_epi32(i1, i2);
|
||||
|
||||
// Store
|
||||
_mm_storeu_si128(reinterpret_cast<__m128i*>(&d[i]), packed);
|
||||
}
|
||||
|
||||
// Handle remaining samples
|
||||
for (; i < num_samples; i++) {
|
||||
d[i] = OrbisFloatToS16(s[i]);
|
||||
}
|
||||
}
|
||||
#elif
|
||||
static void ConvertF32ToS16Stereo(const void* src, void* dst, u32 frames, const float*) {
|
||||
const float* s = static_cast<const float*>(src);
|
||||
s16* d = static_cast<s16*>(dst);
|
||||
|
||||
const u32 num_samples = frames << 1;
|
||||
for (u32 i = 0; i < num_samples; i++)
|
||||
d[i] = OrbisFloatToS16(s[i]);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAS_SSE2
|
||||
static void ConvertF32ToS16_8CH_SIMD(const void* src, void* dst, u32 frames, const float*) {
|
||||
const float* s = static_cast<const float*>(src);
|
||||
s16* d = static_cast<s16*>(dst);
|
||||
|
||||
const __m128 scale = _mm_set1_ps(32768.0f);
|
||||
const __m128 min_val = _mm_set1_ps(-32768.0f);
|
||||
const __m128 max_val = _mm_set1_ps(32767.0f);
|
||||
|
||||
const u32 num_samples = frames << 3;
|
||||
u32 i = 0;
|
||||
|
||||
// Process 8 samples at a time (1 frame of 8CH audio)
|
||||
for (; i + 8 <= num_samples; i += 8) {
|
||||
__m128 f1 = _mm_loadu_ps(&s[i]);
|
||||
__m128 f2 = _mm_loadu_ps(&s[i + 4]);
|
||||
|
||||
f1 = _mm_mul_ps(f1, scale);
|
||||
f2 = _mm_mul_ps(f2, scale);
|
||||
f1 = _mm_max_ps(_mm_min_ps(f1, max_val), min_val);
|
||||
f2 = _mm_max_ps(_mm_min_ps(f2, max_val), min_val);
|
||||
|
||||
__m128i i1 = _mm_cvtps_epi32(f1);
|
||||
__m128i i2 = _mm_cvtps_epi32(f2);
|
||||
__m128i packed = _mm_packs_epi32(i1, i2);
|
||||
|
||||
_mm_storeu_si128(reinterpret_cast<__m128i*>(&d[i]), packed);
|
||||
}
|
||||
|
||||
for (; i < num_samples; i++) {
|
||||
d[i] = OrbisFloatToS16(s[i]);
|
||||
}
|
||||
}
|
||||
#elif
|
||||
static void ConvertF32ToS16_8CH(const void* src, void* dst, u32 frames, const float*) {
|
||||
const float* s = static_cast<const float*>(src);
|
||||
s16* d = static_cast<s16*>(dst);
|
||||
|
||||
const u32 num_samples = frames << 3;
|
||||
for (u32 i = 0; i < num_samples; i++)
|
||||
d[i] = OrbisFloatToS16(s[i]);
|
||||
}
|
||||
#endif
|
||||
static void ConvertF32ToS16Std8CH(const void* src, void* dst, u32 frames, const float*) {
|
||||
const float* s = static_cast<const float*>(src);
|
||||
s16* d = static_cast<s16*>(dst);
|
||||
|
||||
for (u32 i = 0; i < frames; i++) {
|
||||
const u32 offset = i << 3;
|
||||
|
||||
d[offset + FL] = OrbisFloatToS16(s[offset + FL]);
|
||||
d[offset + FR] = OrbisFloatToS16(s[offset + FR]);
|
||||
d[offset + FC] = OrbisFloatToS16(s[offset + FC]);
|
||||
d[offset + LF] = OrbisFloatToS16(s[offset + LF]);
|
||||
d[offset + SL] = OrbisFloatToS16(s[offset + STD_SL]);
|
||||
d[offset + SR] = OrbisFloatToS16(s[offset + STD_SR]);
|
||||
d[offset + BL] = OrbisFloatToS16(s[offset + STD_BL]);
|
||||
d[offset + BR] = OrbisFloatToS16(s[offset + STD_BR]);
|
||||
}
|
||||
}
|
||||
|
||||
// Audio format parameters
|
||||
const u32 frame_size;
|
||||
const u32 guest_buffer_size;
|
||||
const u32 buffer_frames;
|
||||
const u32 sample_rate;
|
||||
const u32 num_channels;
|
||||
const bool is_float;
|
||||
const bool is_std;
|
||||
const std::array<int, 8> channel_layout;
|
||||
|
||||
alignas(64) u64 period_us{0};
|
||||
alignas(64) std::atomic<u64> last_output_time{0};
|
||||
u64 next_output_time{0};
|
||||
u64 last_volume_check_time{0};
|
||||
u32 output_count{0};
|
||||
|
||||
// OpenAL objects
|
||||
OpenALDevice* device_context{nullptr};
|
||||
ALuint source{0};
|
||||
std::vector<ALuint> buffers;
|
||||
std::vector<ALuint> available_buffers;
|
||||
ALenum format{AL_FORMAT_STEREO16};
|
||||
|
||||
// Buffer management
|
||||
u32 buffer_size_bytes{0};
|
||||
std::vector<s16> al_buffer_s16; // For S16 formats
|
||||
std::vector<float> al_buffer_float; // For float formats
|
||||
|
||||
// Extension support
|
||||
bool has_float_ext{false};
|
||||
bool use_native_float{false};
|
||||
|
||||
// Converter function pointer
|
||||
ConverterFunc convert{nullptr};
|
||||
|
||||
// Volume management
|
||||
alignas(64) std::atomic<float> current_gain{1.0f};
|
||||
|
||||
std::string device_name;
|
||||
bool device_registered;
|
||||
};
|
||||
|
||||
std::unique_ptr<PortBackend> OpenALAudioOut::Open(PortOut& port) {
|
||||
return std::make_unique<OpenALPortBackend>(port);
|
||||
}
|
||||
|
||||
} // namespace Libraries::AudioOut
|
||||
226
src/core/libraries/audio/openal_manager.h
Normal file
226
src/core/libraries/audio/openal_manager.h
Normal file
@ -0,0 +1,226 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
|
||||
namespace Libraries::AudioOut {
|
||||
|
||||
struct DeviceContext {
|
||||
ALCdevice* device{nullptr};
|
||||
ALCcontext* context{nullptr};
|
||||
std::string device_name;
|
||||
int port_count{0};
|
||||
|
||||
bool IsValid() const {
|
||||
return device != nullptr && context != nullptr;
|
||||
}
|
||||
|
||||
void Cleanup() {
|
||||
if (context) {
|
||||
alcDestroyContext(context);
|
||||
context = nullptr;
|
||||
}
|
||||
if (device) {
|
||||
alcCloseDevice(device);
|
||||
device = nullptr;
|
||||
}
|
||||
port_count = 0;
|
||||
}
|
||||
};
|
||||
|
||||
class OpenALDevice {
|
||||
public:
|
||||
static OpenALDevice& GetInstance() {
|
||||
static OpenALDevice instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Register a port that uses this device
|
||||
bool RegisterPort(const std::string& device_name) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
|
||||
// Handle "Default Device" alias
|
||||
std::string actual_device_name = device_name;
|
||||
if (actual_device_name.empty() || actual_device_name == "Default Device") {
|
||||
actual_device_name = GetDefaultDeviceName();
|
||||
}
|
||||
|
||||
// Find or create device context for this device name
|
||||
auto it = devices.find(actual_device_name);
|
||||
if (it != devices.end()) {
|
||||
// Device exists, increment count
|
||||
it->second.port_count++;
|
||||
LOG_INFO(Lib_AudioOut, "Reusing OpenAL device '{}', port count: {}", actual_device_name,
|
||||
it->second.port_count);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create new device
|
||||
DeviceContext ctx;
|
||||
if (!InitializeDevice(ctx, actual_device_name)) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to initialize OpenAL device '{}'", actual_device_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx.port_count = 1;
|
||||
devices[actual_device_name] = ctx;
|
||||
|
||||
LOG_INFO(Lib_AudioOut, "Created new OpenAL device '{}'", actual_device_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Unregister a port
|
||||
void UnregisterPort(const std::string& device_name) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
|
||||
std::string actual_device_name = device_name;
|
||||
if (actual_device_name.empty() || actual_device_name == "Default Device") {
|
||||
actual_device_name = GetDefaultDeviceName();
|
||||
}
|
||||
|
||||
auto it = devices.find(actual_device_name);
|
||||
if (it != devices.end()) {
|
||||
it->second.port_count--;
|
||||
LOG_INFO(Lib_AudioOut, "Port unregistered from '{}', remaining ports: {}",
|
||||
actual_device_name, it->second.port_count);
|
||||
|
||||
if (it->second.port_count <= 0) {
|
||||
LOG_INFO(Lib_AudioOut, "Cleaning up OpenAL device '{}'", actual_device_name);
|
||||
it->second.Cleanup();
|
||||
devices.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MakeCurrent(const std::string& device_name) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
|
||||
std::string actual_device_name = device_name;
|
||||
if (actual_device_name.empty() || actual_device_name == "Default Device") {
|
||||
actual_device_name = GetDefaultDeviceName();
|
||||
}
|
||||
|
||||
auto it = devices.find(actual_device_name);
|
||||
if (it == devices.end() || !it->second.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store current device for this thread (simplified - in practice you might want
|
||||
// thread-local storage)
|
||||
current_context = it->second.context;
|
||||
return alcMakeContextCurrent(it->second.context);
|
||||
}
|
||||
|
||||
void ReleaseContext() {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
alcMakeContextCurrent(nullptr);
|
||||
current_context = nullptr;
|
||||
}
|
||||
|
||||
// Get the default device name
|
||||
static std::string GetDefaultDeviceName() {
|
||||
const ALCchar* default_device = alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER);
|
||||
return default_device ? default_device : "Default Device";
|
||||
}
|
||||
|
||||
// Check if device enumeration is supported
|
||||
static bool IsDeviceEnumerationSupported() {
|
||||
return alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT") ||
|
||||
alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT");
|
||||
}
|
||||
|
||||
// Get list of available devices
|
||||
static std::vector<std::string> GetAvailableDevices() {
|
||||
std::vector<std::string> devices_list;
|
||||
|
||||
if (!alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT"))
|
||||
return devices_list;
|
||||
|
||||
const ALCchar* devices = nullptr;
|
||||
if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) {
|
||||
devices = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER);
|
||||
} else {
|
||||
devices = alcGetString(nullptr, ALC_DEVICE_SPECIFIER);
|
||||
}
|
||||
|
||||
if (!devices)
|
||||
return devices_list;
|
||||
|
||||
const ALCchar* ptr = devices;
|
||||
while (*ptr != '\0') {
|
||||
devices_list.emplace_back(ptr);
|
||||
ptr += std::strlen(ptr) + 1;
|
||||
}
|
||||
|
||||
return devices_list;
|
||||
}
|
||||
|
||||
private:
|
||||
OpenALDevice() = default;
|
||||
~OpenALDevice() {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
for (auto& [name, ctx] : devices) {
|
||||
ctx.Cleanup();
|
||||
}
|
||||
devices.clear();
|
||||
}
|
||||
|
||||
OpenALDevice(const OpenALDevice&) = delete;
|
||||
OpenALDevice& operator=(const OpenALDevice&) = delete;
|
||||
|
||||
bool InitializeDevice(DeviceContext& ctx, const std::string& device_name) {
|
||||
// Handle disabled audio
|
||||
if (device_name == "None") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open the requested device
|
||||
if (device_name.empty() || device_name == "Default Device") {
|
||||
ctx.device = alcOpenDevice(nullptr);
|
||||
} else {
|
||||
ctx.device = alcOpenDevice(device_name.c_str());
|
||||
if (!ctx.device) {
|
||||
LOG_WARNING(Lib_AudioOut, "Device '{}' not found, falling back to default",
|
||||
device_name);
|
||||
ctx.device = alcOpenDevice(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ctx.device) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to open OpenAL device");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create context
|
||||
ctx.context = alcCreateContext(ctx.device, nullptr);
|
||||
if (!ctx.context) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to create OpenAL context");
|
||||
alcCloseDevice(ctx.device);
|
||||
ctx.device = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get actual device name
|
||||
const ALCchar* actual_name = nullptr;
|
||||
if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) {
|
||||
actual_name = alcGetString(ctx.device, ALC_ALL_DEVICES_SPECIFIER);
|
||||
} else {
|
||||
actual_name = alcGetString(ctx.device, ALC_DEVICE_SPECIFIER);
|
||||
}
|
||||
ctx.device_name = actual_name ? actual_name : "Unknown";
|
||||
|
||||
LOG_INFO(Lib_AudioOut, "OpenAL device initialized: '{}'", ctx.device_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, DeviceContext> devices;
|
||||
mutable std::mutex mutex;
|
||||
ALCcontext* current_context{nullptr}; // For thread-local tracking
|
||||
};
|
||||
|
||||
} // namespace Libraries::AudioOut
|
||||
@ -3,8 +3,8 @@
|
||||
|
||||
#include <cstring>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <common/config.h>
|
||||
#include <common/logging/log.h>
|
||||
#include <core/emulator_settings.h>
|
||||
#include "audioin.h"
|
||||
#include "audioin_backend.h"
|
||||
|
||||
@ -21,7 +21,7 @@ public:
|
||||
fmt.channels = static_cast<Uint8>(port.channels_num);
|
||||
fmt.freq = static_cast<int>(port.freq);
|
||||
|
||||
std::string micDevStr = Config::getMicDevice();
|
||||
std::string micDevStr = EmulatorSettings.GetSDLMicDevice();
|
||||
uint32_t devId = 0;
|
||||
if (micDevStr == "None") {
|
||||
nullDevice = true;
|
||||
|
||||
@ -9,8 +9,8 @@
|
||||
#include <SDL3/SDL_audio.h>
|
||||
#include <SDL3/SDL_hints.h>
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/audio/audioout.h"
|
||||
#include "core/libraries/audio/audioout_backend.h"
|
||||
#include "core/libraries/kernel/threads.h"
|
||||
@ -110,7 +110,7 @@ public:
|
||||
max_channel_gain = std::max(max_channel_gain, channel_gain);
|
||||
}
|
||||
|
||||
const float slider_gain = Config::getVolumeSlider() * 0.01f; // Faster than /100.0f
|
||||
const float slider_gain = EmulatorSettings.GetVolumeSlider() * 0.01f; // Faster than /100.0f
|
||||
const float total_gain = max_channel_gain * slider_gain;
|
||||
|
||||
const float current = current_gain.load(std::memory_order_acquire);
|
||||
@ -156,7 +156,7 @@ private:
|
||||
}
|
||||
|
||||
// Initialize current gain
|
||||
current_gain.store(Config::getVolumeSlider() * 0.01f, std::memory_order_relaxed);
|
||||
current_gain.store(EmulatorSettings.GetVolumeSlider() * 0.01f, std::memory_order_relaxed);
|
||||
|
||||
if (!SelectConverter()) {
|
||||
FreeAlignedBuffer();
|
||||
@ -201,7 +201,7 @@ private:
|
||||
|
||||
last_volume_check_time = current_time;
|
||||
|
||||
const float config_volume = Config::getVolumeSlider() * 0.01f;
|
||||
const float config_volume = EmulatorSettings.GetVolumeSlider() * 0.01f;
|
||||
const float stored_gain = current_gain.load(std::memory_order_acquire);
|
||||
|
||||
// Only update if the difference is significant
|
||||
@ -368,11 +368,11 @@ private:
|
||||
switch (type) {
|
||||
case OrbisAudioOutPort::Main:
|
||||
case OrbisAudioOutPort::Bgm:
|
||||
return Config::getMainOutputDevice();
|
||||
return EmulatorSettings.GetSDLMainOutputDevice();
|
||||
case OrbisAudioOutPort::PadSpk:
|
||||
return Config::getPadSpkOutputDevice();
|
||||
return EmulatorSettings.GetSDLPadSpkOutputDevice();
|
||||
default:
|
||||
return Config::getMainOutputDevice();
|
||||
return EmulatorSettings.GetSDLMainOutputDevice();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
997
src/core/libraries/audio3d/audio3d_openal.cpp
Normal file
997
src/core/libraries/audio3d/audio3d_openal.cpp
Normal file
@ -0,0 +1,997 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/audio/audioout.h"
|
||||
#include "core/libraries/audio/audioout_error.h"
|
||||
#include "core/libraries/audio3d/audio3d_error.h"
|
||||
#include "core/libraries/audio3d/audio3d_openal.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
|
||||
namespace Libraries::Audio3dOpenAL {
|
||||
|
||||
static constexpr u32 AUDIO3D_SAMPLE_RATE = 48000;
|
||||
|
||||
static constexpr AudioOut::OrbisAudioOutParamFormat AUDIO3D_OUTPUT_FORMAT =
|
||||
AudioOut::OrbisAudioOutParamFormat::S16Stereo;
|
||||
static constexpr u32 AUDIO3D_OUTPUT_NUM_CHANNELS = 2;
|
||||
|
||||
static std::unique_ptr<Audio3dState> state;
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(const s32 handle) {
|
||||
LOG_INFO(Lib_Audio3d, "called, handle = {}", handle);
|
||||
|
||||
// Remove from any port that was tracking this handle.
|
||||
if (state) {
|
||||
for (auto& [port_id, port] : state->ports) {
|
||||
std::scoped_lock lock{port.mutex};
|
||||
auto& handles = port.audioout_handles;
|
||||
handles.erase(std::remove(handles.begin(), handles.end(), handle), handles.end());
|
||||
}
|
||||
}
|
||||
|
||||
return AudioOut::sceAudioOutClose(handle);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen(
|
||||
const OrbisAudio3dPortId port_id, const Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
s32 type, const s32 index, const u32 len, const u32 freq,
|
||||
const AudioOut::OrbisAudioOutParamExtendedInformation param) {
|
||||
LOG_INFO(Lib_Audio3d,
|
||||
"called, port_id = {}, user_id = {}, type = {}, index = {}, len = {}, freq = {}",
|
||||
port_id, user_id, type, index, len, freq);
|
||||
|
||||
if (!state->ports.contains(port_id)) {
|
||||
LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
|
||||
}
|
||||
|
||||
std::scoped_lock lock{state->ports[port_id].mutex};
|
||||
if (len != state->ports[port_id].parameters.granularity) {
|
||||
LOG_ERROR(Lib_Audio3d, "len != state->ports[port_id].parameters.granularity");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
const s32 handle = sceAudioOutOpen(user_id, static_cast<AudioOut::OrbisAudioOutPort>(type),
|
||||
index, len, freq, param);
|
||||
if (handle < 0) {
|
||||
return handle;
|
||||
}
|
||||
|
||||
// Track this handle in the port so sceAudio3dPortFlush can use it for sync.
|
||||
state->ports[port_id].audioout_handles.push_back(handle);
|
||||
return handle;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dAudioOutOutput(const s32 handle, void* ptr) {
|
||||
LOG_DEBUG(Lib_Audio3d, "called, handle = {}, ptr = {}", handle, ptr);
|
||||
|
||||
if (!ptr) {
|
||||
LOG_ERROR(Lib_Audio3d, "!ptr");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (handle < 0 || (handle & 0xFFFF) > 25) {
|
||||
LOG_ERROR(Lib_Audio3d, "handle < 0 || (handle & 0xFFFF) > 25");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
|
||||
}
|
||||
|
||||
return AudioOut::sceAudioOutOutput(handle, ptr);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dAudioOutOutputs(AudioOut::OrbisAudioOutOutputParam* param,
|
||||
const u32 num) {
|
||||
LOG_DEBUG(Lib_Audio3d, "called, param = {}, num = {}", static_cast<void*>(param), num);
|
||||
|
||||
if (!param || !num) {
|
||||
LOG_ERROR(Lib_Audio3d, "!param || !num");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
return AudioOut::sceAudioOutOutputs(param, num);
|
||||
}
|
||||
|
||||
static s32 ConvertAndEnqueue(std::deque<AudioData>& queue, const OrbisAudio3dPcm& pcm,
|
||||
const u32 num_channels, const u32 granularity) {
|
||||
if (!pcm.sample_buffer || !pcm.num_samples) {
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
const u32 bytes_per_sample =
|
||||
(pcm.format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16) ? sizeof(s16) : sizeof(float);
|
||||
|
||||
// Always allocate exactly granularity samples (zeroed = silence for padding).
|
||||
const u32 dst_bytes = granularity * num_channels * bytes_per_sample;
|
||||
u8* copy = static_cast<u8*>(std::calloc(1, dst_bytes));
|
||||
if (!copy) {
|
||||
return ORBIS_AUDIO3D_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// Copy min(provided, granularity) samples — extra are dropped, shortage stays zero.
|
||||
const u32 samples_to_copy = std::min(pcm.num_samples, granularity);
|
||||
std::memcpy(copy, pcm.sample_buffer, samples_to_copy * num_channels * bytes_per_sample);
|
||||
|
||||
queue.emplace_back(AudioData{
|
||||
.sample_buffer = copy,
|
||||
.num_samples = granularity,
|
||||
.num_channels = num_channels,
|
||||
.format = pcm.format,
|
||||
});
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dBedWrite(const OrbisAudio3dPortId port_id, const u32 num_channels,
|
||||
const OrbisAudio3dFormat format, void* buffer,
|
||||
const u32 num_samples) {
|
||||
return sceAudio3dBedWrite2(port_id, num_channels, format, buffer, num_samples,
|
||||
OrbisAudio3dOutputRoute::ORBIS_AUDIO3D_OUTPUT_BOTH, false);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dBedWrite2(const OrbisAudio3dPortId port_id, const u32 num_channels,
|
||||
const OrbisAudio3dFormat format, void* buffer,
|
||||
const u32 num_samples,
|
||||
const OrbisAudio3dOutputRoute output_route,
|
||||
const bool restricted) {
|
||||
LOG_DEBUG(
|
||||
Lib_Audio3d,
|
||||
"called, port_id = {}, num_channels = {}, format = {}, num_samples = {}, output_route "
|
||||
"= {}, restricted = {}",
|
||||
port_id, num_channels, magic_enum::enum_name(format), num_samples,
|
||||
magic_enum::enum_name(output_route), restricted);
|
||||
|
||||
if (!state->ports.contains(port_id)) {
|
||||
LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
|
||||
}
|
||||
|
||||
if (output_route > OrbisAudio3dOutputRoute::ORBIS_AUDIO3D_OUTPUT_BOTH) {
|
||||
LOG_ERROR(Lib_Audio3d, "output_route > ORBIS_AUDIO3D_OUTPUT_BOTH");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (format > OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_FLOAT) {
|
||||
LOG_ERROR(Lib_Audio3d, "format > ORBIS_AUDIO3D_FORMAT_FLOAT");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (num_channels != 2 && num_channels != 6 && num_channels != 8) {
|
||||
LOG_ERROR(Lib_Audio3d, "num_channels must be 2, 6, or 8");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (!buffer || !num_samples) {
|
||||
LOG_ERROR(Lib_Audio3d, "!buffer || !num_samples");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_FLOAT) {
|
||||
if ((reinterpret_cast<uintptr_t>(buffer) & 3) != 0) {
|
||||
LOG_ERROR(Lib_Audio3d, "buffer & 3 != 0");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
} else if (format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16) {
|
||||
if ((reinterpret_cast<uintptr_t>(buffer) & 1) != 0) {
|
||||
LOG_ERROR(Lib_Audio3d, "buffer & 1 != 0");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
std::scoped_lock lock{state->ports[port_id].mutex};
|
||||
return ConvertAndEnqueue(state->ports[port_id].bed_queue,
|
||||
OrbisAudio3dPcm{
|
||||
.format = format,
|
||||
.sample_buffer = buffer,
|
||||
.num_samples = num_samples,
|
||||
},
|
||||
num_channels, state->ports[port_id].parameters.granularity);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dCreateSpeakerArray() {
|
||||
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dDeleteSpeakerArray() {
|
||||
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters* params) {
|
||||
LOG_DEBUG(Lib_Audio3d, "called");
|
||||
if (params) {
|
||||
auto default_params = OrbisAudio3dOpenParameters{
|
||||
.size_this = 0x20,
|
||||
.granularity = 0x100,
|
||||
.rate = OrbisAudio3dRate::ORBIS_AUDIO3D_RATE_48000,
|
||||
.max_objects = 512,
|
||||
.queue_depth = 2,
|
||||
.buffer_mode = OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH,
|
||||
};
|
||||
memcpy(params, &default_params, 0x20);
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMemorySize() {
|
||||
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMixCoefficients() {
|
||||
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMixCoefficients2() {
|
||||
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dInitialize(const s64 reserved) {
|
||||
LOG_INFO(Lib_Audio3d, "called, reserved = {}", reserved);
|
||||
|
||||
if (reserved != 0) {
|
||||
LOG_ERROR(Lib_Audio3d, "reserved != 0");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (state) {
|
||||
LOG_ERROR(Lib_Audio3d, "already initialized");
|
||||
return ORBIS_AUDIO3D_ERROR_NOT_READY;
|
||||
}
|
||||
|
||||
state = std::make_unique<Audio3dState>();
|
||||
|
||||
if (const auto init_ret = AudioOut::sceAudioOutInit();
|
||||
init_ret < 0 && init_ret != ORBIS_AUDIO_OUT_ERROR_ALREADY_INIT) {
|
||||
return init_ret;
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dObjectReserve(const OrbisAudio3dPortId port_id,
|
||||
OrbisAudio3dObjectId* object_id) {
|
||||
LOG_INFO(Lib_Audio3d, "called, port_id = {}, object_id = {}", port_id,
|
||||
static_cast<void*>(object_id));
|
||||
|
||||
if (!object_id) {
|
||||
LOG_ERROR(Lib_Audio3d, "!object_id");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
*object_id = ORBIS_AUDIO3D_OBJECT_INVALID;
|
||||
|
||||
if (!state->ports.contains(port_id)) {
|
||||
LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
|
||||
}
|
||||
|
||||
auto& port = state->ports[port_id];
|
||||
std::scoped_lock lock{port.mutex};
|
||||
|
||||
// Enforce the max_objects limit set at PortOpen time.
|
||||
if (port.objects.size() >= port.parameters.max_objects) {
|
||||
LOG_ERROR(Lib_Audio3d, "port has no available objects (max_objects = {})",
|
||||
port.parameters.max_objects);
|
||||
return ORBIS_AUDIO3D_ERROR_OUT_OF_RESOURCES;
|
||||
}
|
||||
|
||||
// Counter lives in the Port so it resets when the port is closed and reopened.
|
||||
do {
|
||||
++port.next_object_id;
|
||||
} while (port.next_object_id == 0 ||
|
||||
port.next_object_id == static_cast<u32>(ORBIS_AUDIO3D_OBJECT_INVALID) ||
|
||||
port.objects.contains(port.next_object_id));
|
||||
|
||||
*object_id = port.next_object_id;
|
||||
port.objects.emplace(*object_id, ObjectState{});
|
||||
LOG_INFO(Lib_Audio3d, "reserved object_id = {}", *object_id);
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dObjectSetAttribute(const OrbisAudio3dPortId port_id,
|
||||
const OrbisAudio3dObjectId object_id,
|
||||
const OrbisAudio3dAttributeId attribute_id,
|
||||
const void* attribute, const u64 attribute_size) {
|
||||
LOG_DEBUG(Lib_Audio3d, "called, port_id = {}, object_id = {}, attribute_id = {:#x}, size = {}",
|
||||
port_id, object_id, static_cast<u32>(attribute_id), attribute_size);
|
||||
|
||||
if (!state->ports.contains(port_id)) {
|
||||
LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
|
||||
}
|
||||
|
||||
auto& port = state->ports[port_id];
|
||||
std::scoped_lock lock{port.mutex};
|
||||
if (!port.objects.contains(object_id)) {
|
||||
LOG_DEBUG(Lib_Audio3d, "object_id {} not reserved (race with Unreserve?), no-op",
|
||||
object_id);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
if (!attribute_size &&
|
||||
attribute_id != OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_RESET_STATE) {
|
||||
LOG_ERROR(Lib_Audio3d, "!attribute_size for non-reset attribute");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
auto& obj = port.objects[object_id];
|
||||
|
||||
// RESET_STATE clears all attributes and queued PCM; it takes no value.
|
||||
if (attribute_id == OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_RESET_STATE) {
|
||||
for (auto& data : obj.pcm_queue) {
|
||||
std::free(data.sample_buffer);
|
||||
}
|
||||
obj.pcm_queue.clear();
|
||||
obj.persistent_attributes.clear();
|
||||
LOG_DEBUG(Lib_Audio3d, "RESET_STATE for object {}", object_id);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
// Store the attribute so it's available when we implement it.
|
||||
const auto* src = static_cast<const u8*>(attribute);
|
||||
obj.persistent_attributes[static_cast<u32>(attribute_id)].assign(src, src + attribute_size);
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dObjectSetAttributes(const OrbisAudio3dPortId port_id,
|
||||
OrbisAudio3dObjectId object_id,
|
||||
const u64 num_attributes,
|
||||
const OrbisAudio3dAttribute* attribute_array) {
|
||||
LOG_DEBUG(Lib_Audio3d,
|
||||
"called, port_id = {}, object_id = {}, num_attributes = {}, attribute_array = {}",
|
||||
port_id, object_id, num_attributes, fmt::ptr(attribute_array));
|
||||
|
||||
if (!state->ports.contains(port_id)) {
|
||||
LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
|
||||
}
|
||||
|
||||
if (!num_attributes || !attribute_array) {
|
||||
LOG_ERROR(Lib_Audio3d, "!num_attributes || !attribute_array");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
auto& port = state->ports[port_id];
|
||||
std::scoped_lock lock{port.mutex};
|
||||
if (!port.objects.contains(object_id)) {
|
||||
LOG_DEBUG(Lib_Audio3d, "object_id {} not reserved", object_id);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
auto& obj = port.objects[object_id];
|
||||
|
||||
// First pass: handle RESET_STATE.
|
||||
for (u64 i = 0; i < num_attributes; i++) {
|
||||
if (attribute_array[i].attribute_id ==
|
||||
OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_RESET_STATE) {
|
||||
for (auto& data : obj.pcm_queue) {
|
||||
std::free(data.sample_buffer);
|
||||
}
|
||||
obj.pcm_queue.clear();
|
||||
obj.persistent_attributes.clear();
|
||||
LOG_DEBUG(Lib_Audio3d, "RESET_STATE for object {}", object_id);
|
||||
break; // Only one reset is needed even if listed multiple times.
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: apply all other attributes.
|
||||
for (u64 i = 0; i < num_attributes; i++) {
|
||||
const auto& attr = attribute_array[i];
|
||||
|
||||
switch (attr.attribute_id) {
|
||||
case OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_RESET_STATE:
|
||||
break; // Already applied in first pass above.
|
||||
case OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_PCM: {
|
||||
if (attr.value_size < sizeof(OrbisAudio3dPcm)) {
|
||||
LOG_ERROR(Lib_Audio3d, "PCM attribute value_size too small");
|
||||
continue;
|
||||
}
|
||||
const auto pcm = static_cast<OrbisAudio3dPcm*>(attr.value);
|
||||
// Object audio is always mono (1 channel).
|
||||
if (const auto ret =
|
||||
ConvertAndEnqueue(obj.pcm_queue, *pcm, 1, port.parameters.granularity);
|
||||
ret != ORBIS_OK) {
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// Store the other attributes in the ObjectState so they're available when we
|
||||
// implement them.
|
||||
if (attr.value && attr.value_size > 0) {
|
||||
const auto* src = static_cast<const u8*>(attr.value);
|
||||
obj.persistent_attributes[static_cast<u32>(attr.attribute_id)].assign(
|
||||
src, src + attr.value_size);
|
||||
}
|
||||
LOG_DEBUG(Lib_Audio3d, "Stored attribute {:#x} for object {}",
|
||||
static_cast<u32>(attr.attribute_id), object_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dObjectUnreserve(const OrbisAudio3dPortId port_id,
|
||||
const OrbisAudio3dObjectId object_id) {
|
||||
LOG_DEBUG(Lib_Audio3d, "called, port_id = {}, object_id = {}", port_id, object_id);
|
||||
|
||||
if (!state->ports.contains(port_id)) {
|
||||
LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
|
||||
}
|
||||
|
||||
auto& port = state->ports[port_id];
|
||||
std::scoped_lock lock{port.mutex};
|
||||
|
||||
if (!port.objects.contains(object_id)) {
|
||||
LOG_ERROR(Lib_Audio3d, "object_id not reserved");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_OBJECT;
|
||||
}
|
||||
|
||||
// Free any queued PCM audio for this object.
|
||||
for (auto& data : port.objects[object_id].pcm_queue) {
|
||||
std::free(data.sample_buffer);
|
||||
}
|
||||
|
||||
port.objects.erase(object_id);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortAdvance(const OrbisAudio3dPortId port_id) {
|
||||
LOG_DEBUG(Lib_Audio3d, "called, port_id = {}", port_id);
|
||||
|
||||
if (!state->ports.contains(port_id)) {
|
||||
LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
|
||||
}
|
||||
|
||||
auto& port = state->ports[port_id];
|
||||
|
||||
if (port.parameters.buffer_mode == OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_NO_ADVANCE) {
|
||||
LOG_ERROR(Lib_Audio3d, "port doesn't have advance capability");
|
||||
return ORBIS_AUDIO3D_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (port.mixed_queue.size() >= port.parameters.queue_depth) {
|
||||
LOG_WARNING(Lib_Audio3d, "mixed queue full (depth={}), dropping advance",
|
||||
port.parameters.queue_depth);
|
||||
return ORBIS_AUDIO3D_ERROR_NOT_READY;
|
||||
}
|
||||
|
||||
const u32 granularity = port.parameters.granularity;
|
||||
const u32 out_samples = granularity * AUDIO3D_OUTPUT_NUM_CHANNELS;
|
||||
|
||||
// ---- FLOAT MIX BUFFER ----
|
||||
float* mix_float = static_cast<float*>(std::calloc(out_samples, sizeof(float)));
|
||||
if (!mix_float)
|
||||
return ORBIS_AUDIO3D_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
auto mix_in = [&](std::deque<AudioData>& queue, const float gain) {
|
||||
if (queue.empty())
|
||||
return;
|
||||
|
||||
// default gain is 0.0 — objects with no GAIN set are silent.
|
||||
if (gain == 0.0f) {
|
||||
AudioData data = queue.front();
|
||||
queue.pop_front();
|
||||
std::free(data.sample_buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
AudioData data = queue.front();
|
||||
queue.pop_front();
|
||||
|
||||
const u32 frames = std::min(granularity, data.num_samples);
|
||||
const u32 channels = data.num_channels;
|
||||
|
||||
if (data.format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16) {
|
||||
const s16* src = reinterpret_cast<const s16*>(data.sample_buffer);
|
||||
|
||||
for (u32 i = 0; i < frames; i++) {
|
||||
float left = 0.0f;
|
||||
float right = 0.0f;
|
||||
|
||||
if (channels == 1) {
|
||||
float v = src[i] / 32768.0f;
|
||||
left = v;
|
||||
right = v;
|
||||
} else {
|
||||
left = src[i * channels + 0] / 32768.0f;
|
||||
right = src[i * channels + 1] / 32768.0f;
|
||||
}
|
||||
|
||||
mix_float[i * 2 + 0] += left * gain;
|
||||
mix_float[i * 2 + 1] += right * gain;
|
||||
}
|
||||
} else { // FLOAT input
|
||||
const float* src = reinterpret_cast<const float*>(data.sample_buffer);
|
||||
|
||||
for (u32 i = 0; i < frames; i++) {
|
||||
float left = 0.0f;
|
||||
float right = 0.0f;
|
||||
|
||||
if (channels == 1) {
|
||||
left = src[i];
|
||||
right = src[i];
|
||||
} else {
|
||||
left = src[i * channels + 0];
|
||||
right = src[i * channels + 1];
|
||||
}
|
||||
|
||||
mix_float[i * 2 + 0] += left * gain;
|
||||
mix_float[i * 2 + 1] += right * gain;
|
||||
}
|
||||
}
|
||||
|
||||
std::free(data.sample_buffer);
|
||||
};
|
||||
|
||||
// Bed is mixed at full gain (1.0).
|
||||
mix_in(port.bed_queue, 1.0f);
|
||||
|
||||
// Mix all object PCM queues, applying each object's GAIN persistent attribute.
|
||||
for (auto& [obj_id, obj] : port.objects) {
|
||||
float gain = 0.0f;
|
||||
const auto gain_key =
|
||||
static_cast<u32>(OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_GAIN);
|
||||
if (obj.persistent_attributes.contains(gain_key)) {
|
||||
const auto& blob = obj.persistent_attributes.at(gain_key);
|
||||
if (blob.size() >= sizeof(float)) {
|
||||
std::memcpy(&gain, blob.data(), sizeof(float));
|
||||
}
|
||||
}
|
||||
mix_in(obj.pcm_queue, gain);
|
||||
}
|
||||
|
||||
s16* mix_s16 = static_cast<s16*>(std::malloc(out_samples * sizeof(s16)));
|
||||
if (!mix_s16) {
|
||||
std::free(mix_float);
|
||||
return ORBIS_AUDIO3D_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < out_samples; i++) {
|
||||
float v = std::clamp(mix_float[i], -1.0f, 1.0f);
|
||||
mix_s16[i] = static_cast<s16>(v * 32767.0f);
|
||||
}
|
||||
|
||||
std::free(mix_float);
|
||||
|
||||
port.mixed_queue.push_back(AudioData{.sample_buffer = reinterpret_cast<u8*>(mix_s16),
|
||||
.num_samples = granularity,
|
||||
.num_channels = AUDIO3D_OUTPUT_NUM_CHANNELS,
|
||||
.format = OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16});
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortClose(const OrbisAudio3dPortId port_id) {
|
||||
LOG_INFO(Lib_Audio3d, "called, port_id = {}", port_id);
|
||||
|
||||
if (!state->ports.contains(port_id)) {
|
||||
LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
|
||||
}
|
||||
|
||||
auto& port = state->ports[port_id];
|
||||
{
|
||||
std::scoped_lock lock{port.mutex};
|
||||
|
||||
if (port.audio_out_handle >= 0) {
|
||||
AudioOut::sceAudioOutClose(port.audio_out_handle);
|
||||
port.audio_out_handle = -1;
|
||||
}
|
||||
|
||||
for (const s32 handle : port.audioout_handles) {
|
||||
AudioOut::sceAudioOutClose(handle);
|
||||
}
|
||||
port.audioout_handles.clear();
|
||||
|
||||
for (auto& data : port.mixed_queue) {
|
||||
std::free(data.sample_buffer);
|
||||
}
|
||||
|
||||
for (auto& data : port.bed_queue) {
|
||||
std::free(data.sample_buffer);
|
||||
}
|
||||
|
||||
for (auto& [obj_id, obj] : port.objects) {
|
||||
for (auto& data : obj.pcm_queue) {
|
||||
std::free(data.sample_buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state->ports.erase(port_id);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortCreate() {
|
||||
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortDestroy() {
|
||||
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortFlush(const OrbisAudio3dPortId port_id) {
|
||||
LOG_DEBUG(Lib_Audio3d, "called, port_id = {}", port_id);
|
||||
|
||||
if (!state->ports.contains(port_id)) {
|
||||
LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
|
||||
}
|
||||
|
||||
auto& port = state->ports[port_id];
|
||||
std::scoped_lock lock{port.mutex};
|
||||
|
||||
if (!port.audioout_handles.empty()) {
|
||||
for (const s32 handle : port.audioout_handles) {
|
||||
const s32 ret = AudioOut::sceAudioOutOutput(handle, nullptr);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
if (port.mixed_queue.empty()) {
|
||||
// Only mix if there's actually something to mix.
|
||||
if (!port.bed_queue.empty() ||
|
||||
std::any_of(port.objects.begin(), port.objects.end(),
|
||||
[](const auto& kv) { return !kv.second.pcm_queue.empty(); })) {
|
||||
const s32 ret = sceAudio3dPortAdvance(port_id);
|
||||
if (ret != ORBIS_OK && ret != ORBIS_AUDIO3D_ERROR_NOT_READY) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (port.mixed_queue.empty()) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
if (port.audio_out_handle < 0) {
|
||||
AudioOut::OrbisAudioOutParamExtendedInformation ext_info{};
|
||||
ext_info.data_format.Assign(AUDIO3D_OUTPUT_FORMAT);
|
||||
port.audio_out_handle =
|
||||
AudioOut::sceAudioOutOpen(0xFF, AudioOut::OrbisAudioOutPort::Audio3d, 0,
|
||||
port.parameters.granularity, AUDIO3D_SAMPLE_RATE, ext_info);
|
||||
if (port.audio_out_handle < 0) {
|
||||
return port.audio_out_handle;
|
||||
}
|
||||
}
|
||||
|
||||
// Drain all queued mixed frames, blocking on each until consumed.
|
||||
while (!port.mixed_queue.empty()) {
|
||||
AudioData frame = port.mixed_queue.front();
|
||||
port.mixed_queue.pop_front();
|
||||
const s32 ret = AudioOut::sceAudioOutOutput(port.audio_out_handle, frame.sample_buffer);
|
||||
std::free(frame.sample_buffer);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortFreeState() {
|
||||
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortGetAttributesSupported() {
|
||||
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortGetList() {
|
||||
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortGetParameters() {
|
||||
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortGetQueueLevel(const OrbisAudio3dPortId port_id, u32* queue_level,
|
||||
u32* queue_available) {
|
||||
LOG_DEBUG(Lib_Audio3d, "called, port_id = {}, queue_level = {}, queue_available = {}", port_id,
|
||||
static_cast<void*>(queue_level), static_cast<void*>(queue_available));
|
||||
|
||||
if (!state->ports.contains(port_id)) {
|
||||
LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
|
||||
}
|
||||
|
||||
if (!queue_level && !queue_available) {
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
const auto& port = state->ports[port_id];
|
||||
std::scoped_lock lock{port.mutex};
|
||||
const size_t size = port.mixed_queue.size();
|
||||
|
||||
if (queue_level) {
|
||||
*queue_level = static_cast<u32>(size);
|
||||
}
|
||||
|
||||
if (queue_available) {
|
||||
const u32 depth = port.parameters.queue_depth;
|
||||
*queue_available = (size < depth) ? static_cast<u32>(depth - size) : 0u;
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortGetState() {
|
||||
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortGetStatus() {
|
||||
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortOpen(const Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
const OrbisAudio3dOpenParameters* parameters,
|
||||
OrbisAudio3dPortId* port_id) {
|
||||
LOG_INFO(Lib_Audio3d, "called, user_id = {}, parameters = {}, id = {}", user_id,
|
||||
static_cast<const void*>(parameters), static_cast<void*>(port_id));
|
||||
|
||||
if (!state) {
|
||||
LOG_ERROR(Lib_Audio3d, "!initialized");
|
||||
return ORBIS_AUDIO3D_ERROR_NOT_READY;
|
||||
}
|
||||
|
||||
if (!parameters || !port_id) {
|
||||
LOG_ERROR(Lib_Audio3d, "!parameters || !id");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
const int id = static_cast<int>(state->ports.size()) + 1;
|
||||
|
||||
if (id > 3) {
|
||||
LOG_ERROR(Lib_Audio3d, "id > 3");
|
||||
return ORBIS_AUDIO3D_ERROR_OUT_OF_RESOURCES;
|
||||
}
|
||||
|
||||
*port_id = id;
|
||||
auto& port = state->ports[id];
|
||||
std::memcpy(
|
||||
&port.parameters, parameters,
|
||||
std::min(parameters->size_this, static_cast<u64>(sizeof(OrbisAudio3dOpenParameters))));
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortPush(const OrbisAudio3dPortId port_id,
|
||||
const OrbisAudio3dBlocking blocking) {
|
||||
LOG_DEBUG(Lib_Audio3d, "called, port_id = {}, blocking = {}", port_id,
|
||||
magic_enum::enum_name(blocking));
|
||||
|
||||
if (!state->ports.contains(port_id)) {
|
||||
LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
|
||||
}
|
||||
|
||||
auto& port = state->ports[port_id];
|
||||
|
||||
if (port.parameters.buffer_mode !=
|
||||
OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH) {
|
||||
LOG_ERROR(Lib_Audio3d, "port doesn't have push capability");
|
||||
return ORBIS_AUDIO3D_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
const u32 depth = port.parameters.queue_depth;
|
||||
|
||||
if (port.audio_out_handle < 0) {
|
||||
AudioOut::OrbisAudioOutParamExtendedInformation ext_info{};
|
||||
ext_info.data_format.Assign(AUDIO3D_OUTPUT_FORMAT);
|
||||
|
||||
port.audio_out_handle =
|
||||
AudioOut::sceAudioOutOpen(0xFF, AudioOut::OrbisAudioOutPort::Audio3d, 0,
|
||||
port.parameters.granularity, AUDIO3D_SAMPLE_RATE, ext_info);
|
||||
|
||||
if (port.audio_out_handle < 0)
|
||||
return port.audio_out_handle;
|
||||
}
|
||||
|
||||
// Function that submits exactly one frame (if available).
|
||||
auto submit_one_frame = [&](bool& submitted) -> s32 {
|
||||
AudioData frame;
|
||||
{
|
||||
std::scoped_lock lock{port.mutex};
|
||||
|
||||
if (port.mixed_queue.empty()) {
|
||||
submitted = false;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
frame = port.mixed_queue.front();
|
||||
port.mixed_queue.pop_front();
|
||||
}
|
||||
|
||||
const s32 ret = AudioOut::sceAudioOutOutput(port.audio_out_handle, frame.sample_buffer);
|
||||
std::free(frame.sample_buffer);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
submitted = true;
|
||||
return ORBIS_OK;
|
||||
};
|
||||
|
||||
// If not full, return immediately.
|
||||
{
|
||||
std::scoped_lock lock{port.mutex};
|
||||
if (port.mixed_queue.size() < depth) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
// Submit one frame to free space.
|
||||
bool submitted = false;
|
||||
s32 ret = submit_one_frame(submitted);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (!submitted)
|
||||
return ORBIS_OK;
|
||||
|
||||
// ASYNC: free exactly one slot and return.
|
||||
if (blocking == OrbisAudio3dBlocking::ORBIS_AUDIO3D_BLOCKING_ASYNC) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
// SYNC: ensure at least one slot is free (drain until size < depth).
|
||||
while (true) {
|
||||
{
|
||||
std::scoped_lock lock{port.mutex};
|
||||
if (port.mixed_queue.size() < depth)
|
||||
break;
|
||||
}
|
||||
|
||||
bool drained = false;
|
||||
ret = submit_one_frame(drained);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (!drained)
|
||||
break;
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortQueryDebug() {
|
||||
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortSetAttribute(const OrbisAudio3dPortId port_id,
|
||||
const OrbisAudio3dAttributeId attribute_id,
|
||||
void* attribute, const u64 attribute_size) {
|
||||
LOG_INFO(Lib_Audio3d,
|
||||
"called, port_id = {}, attribute_id = {}, attribute = {}, attribute_size = {}",
|
||||
port_id, static_cast<u32>(attribute_id), attribute, attribute_size);
|
||||
|
||||
if (!state->ports.contains(port_id)) {
|
||||
LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
|
||||
}
|
||||
|
||||
if (!attribute) {
|
||||
LOG_ERROR(Lib_Audio3d, "!attribute");
|
||||
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
// TODO
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dReportRegisterHandler() {
|
||||
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dReportUnregisterHandler() {
|
||||
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dSetGpuRenderer() {
|
||||
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dStrError() {
|
||||
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dTerminate() {
|
||||
LOG_INFO(Lib_Audio3d, "called");
|
||||
|
||||
if (!state) {
|
||||
return ORBIS_AUDIO3D_ERROR_NOT_READY;
|
||||
}
|
||||
|
||||
std::vector<OrbisAudio3dPortId> port_ids;
|
||||
for (const auto& [id, _] : state->ports) {
|
||||
port_ids.push_back(id);
|
||||
}
|
||||
for (const auto id : port_ids) {
|
||||
sceAudio3dPortClose(id);
|
||||
}
|
||||
|
||||
state.reset();
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("pZlOm1aF3aA", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dAudioOutClose);
|
||||
LIB_FUNCTION("ucEsi62soTo", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dAudioOutOpen);
|
||||
LIB_FUNCTION("7NYEzJ9SJbM", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dAudioOutOutput);
|
||||
LIB_FUNCTION("HbxYY27lK6E", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dAudioOutOutputs);
|
||||
LIB_FUNCTION("9tEwE0GV0qo", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dBedWrite);
|
||||
LIB_FUNCTION("xH4Q9UILL3o", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dBedWrite2);
|
||||
LIB_FUNCTION("lvWMW6vEqFU", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dCreateSpeakerArray);
|
||||
LIB_FUNCTION("8hm6YdoQgwg", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dDeleteSpeakerArray);
|
||||
LIB_FUNCTION("Im+jOoa5WAI", "libSceAudio3d", 1, "libSceAudio3d",
|
||||
sceAudio3dGetDefaultOpenParameters);
|
||||
LIB_FUNCTION("kEqqyDkmgdI", "libSceAudio3d", 1, "libSceAudio3d",
|
||||
sceAudio3dGetSpeakerArrayMemorySize);
|
||||
LIB_FUNCTION("-R1DukFq7Dk", "libSceAudio3d", 1, "libSceAudio3d",
|
||||
sceAudio3dGetSpeakerArrayMixCoefficients);
|
||||
LIB_FUNCTION("-Re+pCWvwjQ", "libSceAudio3d", 1, "libSceAudio3d",
|
||||
sceAudio3dGetSpeakerArrayMixCoefficients2);
|
||||
LIB_FUNCTION("UmCvjSmuZIw", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dInitialize);
|
||||
LIB_FUNCTION("jO2tec4dJ2M", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dObjectReserve);
|
||||
LIB_FUNCTION("V1FBFpNIAzk", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dObjectSetAttribute);
|
||||
LIB_FUNCTION("4uyHN9q4ZeU", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dObjectSetAttributes);
|
||||
LIB_FUNCTION("1HXxo-+1qCw", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dObjectUnreserve);
|
||||
LIB_FUNCTION("lw0qrdSjZt8", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dPortAdvance);
|
||||
LIB_FUNCTION("OyVqOeVNtSk", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dPortClose);
|
||||
LIB_FUNCTION("UHFOgVNz0kk", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dPortCreate);
|
||||
LIB_FUNCTION("Mw9mRQtWepY", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dPortDestroy);
|
||||
LIB_FUNCTION("ZOGrxWLgQzE", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dPortFlush);
|
||||
LIB_FUNCTION("uJ0VhGcxCTQ", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dPortFreeState);
|
||||
LIB_FUNCTION("9ZA23Ia46Po", "libSceAudio3d", 1, "libSceAudio3d",
|
||||
sceAudio3dPortGetAttributesSupported);
|
||||
LIB_FUNCTION("SEggctIeTcI", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dPortGetList);
|
||||
LIB_FUNCTION("flPcUaXVXcw", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dPortGetParameters);
|
||||
LIB_FUNCTION("YaaDbDwKpFM", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dPortGetQueueLevel);
|
||||
LIB_FUNCTION("CKHlRW2E9dA", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dPortGetState);
|
||||
LIB_FUNCTION("iRX6GJs9tvE", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dPortGetStatus);
|
||||
LIB_FUNCTION("XeDDK0xJWQA", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dPortOpen);
|
||||
LIB_FUNCTION("VEVhZ9qd4ZY", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dPortPush);
|
||||
LIB_FUNCTION("-pzYDZozm+M", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dPortQueryDebug);
|
||||
LIB_FUNCTION("Yq9bfUQ0uJg", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dPortSetAttribute);
|
||||
LIB_FUNCTION("QfNXBrKZeI0", "libSceAudio3d", 1, "libSceAudio3d",
|
||||
sceAudio3dReportRegisterHandler);
|
||||
LIB_FUNCTION("psv2gbihC1A", "libSceAudio3d", 1, "libSceAudio3d",
|
||||
sceAudio3dReportUnregisterHandler);
|
||||
LIB_FUNCTION("yEYXcbAGK14", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dSetGpuRenderer);
|
||||
LIB_FUNCTION("Aacl5qkRU6U", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dStrError);
|
||||
LIB_FUNCTION("WW1TS2iz5yc", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dTerminate);
|
||||
}
|
||||
|
||||
} // namespace Libraries::Audio3dOpenAL
|
||||
181
src/core/libraries/audio3d/audio3d_openal.h
Normal file
181
src/core/libraries/audio3d/audio3d_openal.h
Normal file
@ -0,0 +1,181 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/audio/audioout.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
namespace Libraries::Audio3dOpenAL {
|
||||
|
||||
constexpr int ORBIS_AUDIO3D_OBJECT_INVALID = 0xFFFFFFFF;
|
||||
|
||||
enum class OrbisAudio3dRate : u32 {
|
||||
ORBIS_AUDIO3D_RATE_48000 = 0,
|
||||
};
|
||||
|
||||
enum class OrbisAudio3dBufferMode : u32 {
|
||||
ORBIS_AUDIO3D_BUFFER_NO_ADVANCE = 0,
|
||||
ORBIS_AUDIO3D_BUFFER_ADVANCE_NO_PUSH = 1,
|
||||
ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH = 2,
|
||||
};
|
||||
|
||||
struct OrbisAudio3dOpenParameters {
|
||||
u64 size_this;
|
||||
u32 granularity;
|
||||
OrbisAudio3dRate rate;
|
||||
u32 max_objects;
|
||||
u32 queue_depth;
|
||||
OrbisAudio3dBufferMode buffer_mode;
|
||||
int : 32;
|
||||
u32 num_beds;
|
||||
};
|
||||
|
||||
enum class OrbisAudio3dFormat : u32 {
|
||||
ORBIS_AUDIO3D_FORMAT_S16 = 0,
|
||||
ORBIS_AUDIO3D_FORMAT_FLOAT = 1,
|
||||
};
|
||||
|
||||
enum class OrbisAudio3dOutputRoute : u32 {
|
||||
ORBIS_AUDIO3D_OUTPUT_BOTH = 0,
|
||||
ORBIS_AUDIO3D_OUTPUT_HMU_ONLY = 1,
|
||||
ORBIS_AUDIO3D_OUTPUT_TV_ONLY = 2,
|
||||
};
|
||||
|
||||
enum class OrbisAudio3dBlocking : u32 {
|
||||
ORBIS_AUDIO3D_BLOCKING_ASYNC = 0,
|
||||
ORBIS_AUDIO3D_BLOCKING_SYNC = 1,
|
||||
};
|
||||
|
||||
struct OrbisAudio3dPcm {
|
||||
OrbisAudio3dFormat format;
|
||||
void* sample_buffer;
|
||||
u32 num_samples;
|
||||
};
|
||||
|
||||
enum class OrbisAudio3dAttributeId : u32 {
|
||||
ORBIS_AUDIO3D_ATTRIBUTE_PCM = 1,
|
||||
ORBIS_AUDIO3D_ATTRIBUTE_POSITION = 2,
|
||||
ORBIS_AUDIO3D_ATTRIBUTE_GAIN = 3,
|
||||
ORBIS_AUDIO3D_ATTRIBUTE_SPREAD = 4,
|
||||
ORBIS_AUDIO3D_ATTRIBUTE_PRIORITY = 5,
|
||||
ORBIS_AUDIO3D_ATTRIBUTE_PASSTHROUGH = 6,
|
||||
ORBIS_AUDIO3D_ATTRIBUTE_AMBISONICS = 7,
|
||||
ORBIS_AUDIO3D_ATTRIBUTE_APPLICATION_SPECIFIC = 8,
|
||||
ORBIS_AUDIO3D_ATTRIBUTE_RESET_STATE = 9,
|
||||
ORBIS_AUDIO3D_ATTRIBUTE_RESTRICTED = 10,
|
||||
ORBIS_AUDIO3D_ATTRIBUTE_OUTPUT_ROUTE = 11,
|
||||
};
|
||||
|
||||
using OrbisAudio3dPortId = u32;
|
||||
using OrbisAudio3dObjectId = u32;
|
||||
using OrbisAudio3dAmbisonics = u32;
|
||||
|
||||
struct OrbisAudio3dAttribute {
|
||||
OrbisAudio3dAttributeId attribute_id;
|
||||
int : 32;
|
||||
void* value;
|
||||
u64 value_size;
|
||||
};
|
||||
|
||||
struct AudioData {
|
||||
u8* sample_buffer;
|
||||
u32 num_samples;
|
||||
u32 num_channels{1};
|
||||
OrbisAudio3dFormat format{OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16};
|
||||
};
|
||||
|
||||
struct ObjectState {
|
||||
std::deque<AudioData> pcm_queue;
|
||||
std::unordered_map<u32, std::vector<u8>> persistent_attributes;
|
||||
};
|
||||
|
||||
struct Port {
|
||||
mutable std::recursive_mutex mutex;
|
||||
OrbisAudio3dOpenParameters parameters{};
|
||||
// Opened lazily on the first sceAudio3dPortPush call.
|
||||
s32 audio_out_handle{-1};
|
||||
// Handles explicitly opened by the game via sceAudio3dAudioOutOpen.
|
||||
std::vector<s32> audioout_handles;
|
||||
// Reserved objects and their state.
|
||||
std::unordered_map<OrbisAudio3dObjectId, ObjectState> objects;
|
||||
// Increasing counter for generating unique object IDs within this port.
|
||||
OrbisAudio3dObjectId next_object_id{0};
|
||||
// Bed audio queue.
|
||||
std::deque<AudioData> bed_queue;
|
||||
// Mixed stereo frames ready to be consumed by sceAudio3dPortPush.
|
||||
std::deque<AudioData> mixed_queue;
|
||||
};
|
||||
|
||||
struct Audio3dState {
|
||||
std::unordered_map<OrbisAudio3dPortId, Port> ports;
|
||||
};
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(s32 handle);
|
||||
s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen(OrbisAudio3dPortId port_id,
|
||||
Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
s32 type, s32 index, u32 len, u32 freq,
|
||||
AudioOut::OrbisAudioOutParamExtendedInformation param);
|
||||
s32 PS4_SYSV_ABI sceAudio3dAudioOutOutput(s32 handle, void* ptr);
|
||||
s32 PS4_SYSV_ABI sceAudio3dAudioOutOutputs(AudioOut::OrbisAudioOutOutputParam* param, u32 num);
|
||||
s32 PS4_SYSV_ABI sceAudio3dBedWrite(OrbisAudio3dPortId port_id, u32 num_channels,
|
||||
OrbisAudio3dFormat format, void* buffer, u32 num_samples);
|
||||
s32 PS4_SYSV_ABI sceAudio3dBedWrite2(OrbisAudio3dPortId port_id, u32 num_channels,
|
||||
OrbisAudio3dFormat format, void* buffer, u32 num_samples,
|
||||
OrbisAudio3dOutputRoute output_route, bool restricted);
|
||||
s32 PS4_SYSV_ABI sceAudio3dCreateSpeakerArray();
|
||||
s32 PS4_SYSV_ABI sceAudio3dDeleteSpeakerArray();
|
||||
s32 PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters* params);
|
||||
s32 PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMemorySize();
|
||||
s32 PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMixCoefficients();
|
||||
s32 PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMixCoefficients2();
|
||||
s32 PS4_SYSV_ABI sceAudio3dInitialize(s64 reserved);
|
||||
s32 PS4_SYSV_ABI sceAudio3dObjectReserve(OrbisAudio3dPortId port_id,
|
||||
OrbisAudio3dObjectId* object_id);
|
||||
s32 PS4_SYSV_ABI sceAudio3dObjectSetAttribute(OrbisAudio3dPortId port_id,
|
||||
OrbisAudio3dObjectId object_id,
|
||||
OrbisAudio3dAttributeId attribute_id,
|
||||
const void* attribute, u64 attribute_size);
|
||||
s32 PS4_SYSV_ABI sceAudio3dObjectSetAttributes(OrbisAudio3dPortId port_id,
|
||||
OrbisAudio3dObjectId object_id, u64 num_attributes,
|
||||
const OrbisAudio3dAttribute* attribute_array);
|
||||
s32 PS4_SYSV_ABI sceAudio3dObjectUnreserve(OrbisAudio3dPortId port_id,
|
||||
OrbisAudio3dObjectId object_id);
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortAdvance(OrbisAudio3dPortId port_id);
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortClose(OrbisAudio3dPortId port_id);
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortCreate();
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortDestroy();
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortFlush(OrbisAudio3dPortId port_id);
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortFreeState();
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortGetAttributesSupported();
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortGetList();
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortGetParameters();
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortGetQueueLevel(OrbisAudio3dPortId port_id, u32* queue_level,
|
||||
u32* queue_available);
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortGetState();
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortGetStatus();
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortOpen(Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
const OrbisAudio3dOpenParameters* parameters,
|
||||
OrbisAudio3dPortId* port_id);
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortPush(OrbisAudio3dPortId port_id, OrbisAudio3dBlocking blocking);
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortQueryDebug();
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortSetAttribute(OrbisAudio3dPortId port_id,
|
||||
OrbisAudio3dAttributeId attribute_id, void* attribute,
|
||||
u64 attribute_size);
|
||||
s32 PS4_SYSV_ABI sceAudio3dReportRegisterHandler();
|
||||
s32 PS4_SYSV_ABI sceAudio3dReportUnregisterHandler();
|
||||
s32 PS4_SYSV_ABI sceAudio3dSetGpuRenderer();
|
||||
s32 PS4_SYSV_ABI sceAudio3dStrError();
|
||||
s32 PS4_SYSV_ABI sceAudio3dTerminate();
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::Audio3dOpenAL
|
||||
@ -3,17 +3,27 @@
|
||||
|
||||
#include "common/elf_info.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/camera/camera.h"
|
||||
#include "core/libraries/camera/camera_error.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/kernel/process.h"
|
||||
#include "core/libraries/libs.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <thread>
|
||||
#include "SDL3/SDL_camera.h"
|
||||
|
||||
namespace Libraries::Camera {
|
||||
|
||||
static bool g_library_opened = false;
|
||||
static s32 g_firmware_version = 0;
|
||||
static s32 g_handles = 0;
|
||||
static constexpr s32 c_width = 1280, c_height = 800;
|
||||
|
||||
SDL_Camera* sdl_camera = nullptr;
|
||||
OrbisCameraConfigExtention output_config0, output_config1;
|
||||
|
||||
s32 PS4_SYSV_ABI sceCameraAccGetData() {
|
||||
LOG_ERROR(Lib_Camera, "(STUBBED) called");
|
||||
@ -325,16 +335,126 @@ s32 PS4_SYSV_ABI sceCameraGetExposureGain(s32 handle, OrbisCameraChannel channel
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
static std::vector<u16> raw16_buffer1, raw16_buffer2;
|
||||
static std::vector<u8> raw8_buffer1, raw8_buffer2;
|
||||
|
||||
static void ConvertRGBA8888ToRAW16(const u8* src, u16* dst, int width, int height) {
|
||||
for (int y = 0; y < height; ++y) {
|
||||
const u8* row = src + y * width * 4;
|
||||
u16* outRow = dst + y * width;
|
||||
|
||||
for (int x = 0; x < width; ++x) {
|
||||
const u8* px = row + x * 4;
|
||||
|
||||
u16 b = u16(px[1]) << 4;
|
||||
u16 g = u16(px[2]) << 4;
|
||||
u16 r = u16(px[3]) << 4;
|
||||
|
||||
// BGGR Bayer layout
|
||||
// B G
|
||||
// G R
|
||||
bool evenRow = (y & 1) == 0;
|
||||
bool evenCol = (x & 1) == 0;
|
||||
|
||||
if (evenRow && evenCol) {
|
||||
outRow[x] = b;
|
||||
} else if (evenRow && !evenCol) {
|
||||
outRow[x] = g;
|
||||
} else if (!evenRow && evenCol) {
|
||||
outRow[x] = g;
|
||||
} else {
|
||||
outRow[x] = r;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ConvertRGBA8888ToRAW8(const u8* src, u8* dst, int width, int height) {
|
||||
for (int y = 0; y < height; ++y) {
|
||||
const u8* row = src + y * width * 4;
|
||||
u8* outRow = dst + y * width;
|
||||
|
||||
for (int x = 0; x < width; ++x) {
|
||||
const u8* px = row + x * 4;
|
||||
|
||||
u8 b = px[1];
|
||||
u8 g = px[2];
|
||||
u8 r = px[3];
|
||||
|
||||
// BGGR Bayer layout
|
||||
// B G
|
||||
// G R
|
||||
bool evenRow = (y & 1) == 0;
|
||||
bool evenCol = (x & 1) == 0;
|
||||
|
||||
if (evenRow && evenCol) {
|
||||
outRow[x] = b;
|
||||
} else if (evenRow && !evenCol) {
|
||||
outRow[x] = g;
|
||||
} else if (!evenRow && evenCol) {
|
||||
outRow[x] = g;
|
||||
} else {
|
||||
outRow[x] = r;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceCameraGetFrameData(s32 handle, OrbisCameraFrameData* frame_data) {
|
||||
LOG_DEBUG(Lib_Camera, "called");
|
||||
if (handle < 1 || frame_data == nullptr || frame_data->sizeThis > 584) {
|
||||
return ORBIS_CAMERA_ERROR_PARAM;
|
||||
}
|
||||
if (!g_library_opened) {
|
||||
if (!g_library_opened || !sdl_camera) {
|
||||
return ORBIS_CAMERA_ERROR_NOT_OPEN;
|
||||
}
|
||||
if (EmulatorSettings.GetCameraId() == -1) {
|
||||
return ORBIS_CAMERA_ERROR_NOT_CONNECTED;
|
||||
}
|
||||
Uint64 timestampNS = 0;
|
||||
static SDL_Surface* frame = nullptr;
|
||||
if (frame) { // release previous frame, if it exists
|
||||
SDL_ReleaseCameraFrame(sdl_camera, frame);
|
||||
}
|
||||
frame = SDL_AcquireCameraFrame(sdl_camera, ×tampNS);
|
||||
|
||||
return ORBIS_CAMERA_ERROR_NOT_CONNECTED;
|
||||
if (!frame) {
|
||||
return ORBIS_CAMERA_ERROR_BUSY;
|
||||
}
|
||||
|
||||
switch (output_config0.format.formatLevel0) {
|
||||
case ORBIS_CAMERA_FORMAT_YUV422:
|
||||
frame_data->pFramePointerList[0][0] = frame->pixels;
|
||||
break;
|
||||
case ORBIS_CAMERA_FORMAT_RAW16:
|
||||
ConvertRGBA8888ToRAW16((u8*)frame->pixels, raw16_buffer1.data(), c_width, c_height);
|
||||
frame_data->pFramePointerList[0][0] = raw16_buffer1.data();
|
||||
break;
|
||||
case ORBIS_CAMERA_FORMAT_RAW8:
|
||||
ConvertRGBA8888ToRAW8((u8*)frame->pixels, raw8_buffer1.data(), c_width, c_height);
|
||||
frame_data->pFramePointerList[0][0] = raw8_buffer1.data();
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
switch (output_config1.format.formatLevel0) {
|
||||
case ORBIS_CAMERA_FORMAT_YUV422:
|
||||
frame_data->pFramePointerList[1][0] = frame->pixels;
|
||||
break;
|
||||
case ORBIS_CAMERA_FORMAT_RAW16:
|
||||
ConvertRGBA8888ToRAW16((u8*)frame->pixels, raw16_buffer2.data(), c_width, c_height);
|
||||
frame_data->pFramePointerList[1][0] = raw16_buffer2.data();
|
||||
break;
|
||||
case ORBIS_CAMERA_FORMAT_RAW8:
|
||||
ConvertRGBA8888ToRAW8((u8*)frame->pixels, raw8_buffer2.data(), c_width, c_height);
|
||||
frame_data->pFramePointerList[1][0] = raw8_buffer2.data();
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
frame_data->meta.format[0][0] = output_config0.format.formatLevel0;
|
||||
frame_data->meta.format[1][0] = output_config1.format.formatLevel0;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceCameraGetGamma(s32 handle, OrbisCameraChannel channel, OrbisCameraGamma* gamma,
|
||||
@ -499,7 +619,7 @@ s32 PS4_SYSV_ABI sceCameraIsAttached(s32 index) {
|
||||
return ORBIS_CAMERA_ERROR_PARAM;
|
||||
}
|
||||
// 0 = disconnected, 1 = connected
|
||||
return 0;
|
||||
return EmulatorSettings.GetCameraId() == -1 ? 0 : 1;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceCameraIsConfigChangeDone() {
|
||||
@ -516,16 +636,16 @@ s32 PS4_SYSV_ABI sceCameraIsValidFrameData(s32 handle, OrbisCameraFrameData* fra
|
||||
return ORBIS_CAMERA_ERROR_NOT_OPEN;
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
return 1; // valid
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceCameraOpen(Libraries::UserService::OrbisUserServiceUserId user_id, s32 type,
|
||||
s32 index, OrbisCameraOpenParameter* param) {
|
||||
LOG_INFO(Lib_Camera, "called");
|
||||
if (user_id != Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_SYSTEM || type != 0 ||
|
||||
index != 0) {
|
||||
return ORBIS_CAMERA_ERROR_PARAM;
|
||||
}
|
||||
LOG_WARNING(Lib_Camera, "Cameras are not supported yet");
|
||||
|
||||
g_library_opened = true;
|
||||
return ++g_handles;
|
||||
@ -609,15 +729,44 @@ s32 PS4_SYSV_ABI sceCameraSetCalibData() {
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceCameraSetConfig(s32 handle, OrbisCameraConfig* config) {
|
||||
LOG_DEBUG(Lib_Camera, "called");
|
||||
LOG_INFO(Lib_Camera, "called");
|
||||
if (handle < 1 || config == nullptr || config->sizeThis != sizeof(OrbisCameraConfig)) {
|
||||
return ORBIS_CAMERA_ERROR_PARAM;
|
||||
}
|
||||
if (!g_library_opened) {
|
||||
return ORBIS_CAMERA_ERROR_NOT_OPEN;
|
||||
}
|
||||
if (EmulatorSettings.GetCameraId() == -1) {
|
||||
return ORBIS_CAMERA_ERROR_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
return ORBIS_CAMERA_ERROR_NOT_CONNECTED;
|
||||
switch (config->configType) {
|
||||
case ORBIS_CAMERA_CONFIG_TYPE1:
|
||||
case ORBIS_CAMERA_CONFIG_TYPE2:
|
||||
case ORBIS_CAMERA_CONFIG_TYPE3:
|
||||
case ORBIS_CAMERA_CONFIG_TYPE4:
|
||||
output_config0 = camera_config_types[config->configType - 1][0];
|
||||
output_config1 = camera_config_types[config->configType - 1][1];
|
||||
break;
|
||||
case ORBIS_CAMERA_CONFIG_TYPE5:
|
||||
int sdk_ver;
|
||||
Libraries::Kernel::sceKernelGetCompiledSdkVersion(&sdk_ver);
|
||||
if (sdk_ver < Common::ElfInfo::FW_45) {
|
||||
return ORBIS_CAMERA_ERROR_UNKNOWN_CONFIG;
|
||||
}
|
||||
output_config0 = camera_config_types[config->configType - 1][0];
|
||||
output_config1 = camera_config_types[config->configType - 1][1];
|
||||
break;
|
||||
case ORBIS_CAMERA_CONFIG_EXTENTION:
|
||||
output_config0 = config->configExtention[0];
|
||||
output_config1 = config->configExtention[1];
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Lib_Camera, "Invalid config type {}", std::to_underlying(config->configType));
|
||||
return ORBIS_CAMERA_ERROR_PARAM;
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceCameraSetConfigInternal(s32 handle, OrbisCameraConfig* config) {
|
||||
@ -851,7 +1000,7 @@ s32 PS4_SYSV_ABI sceCameraSetWhiteBalance(s32 handle, OrbisCameraChannel channel
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceCameraStart(s32 handle, OrbisCameraStartParameter* param) {
|
||||
LOG_DEBUG(Lib_Camera, "called");
|
||||
LOG_INFO(Lib_Camera, "called");
|
||||
if (handle < 1 || param == nullptr || param->sizeThis != sizeof(OrbisCameraStartParameter)) {
|
||||
return ORBIS_CAMERA_ERROR_PARAM;
|
||||
}
|
||||
@ -864,6 +1013,79 @@ s32 PS4_SYSV_ABI sceCameraStart(s32 handle, OrbisCameraStartParameter* param) {
|
||||
return ORBIS_CAMERA_ERROR_FORMAT_UNKNOWN;
|
||||
}
|
||||
|
||||
if (param->formatLevel[0] > 1 || param->formatLevel[1] > 1) {
|
||||
LOG_ERROR(Lib_Camera, "Downscaled image retrieval isn't supported yet!");
|
||||
}
|
||||
|
||||
SDL_CameraID* devices = NULL;
|
||||
int devcount = 0;
|
||||
devices = SDL_GetCameras(&devcount);
|
||||
if (devices == NULL) {
|
||||
LOG_ERROR(Lib_Camera, "Couldn't enumerate camera devices: {}", SDL_GetError());
|
||||
return ORBIS_CAMERA_ERROR_FATAL;
|
||||
} else if (devcount == 0) {
|
||||
LOG_INFO(Lib_Camera, "No camera devices connected");
|
||||
return ORBIS_CAMERA_ERROR_NOT_CONNECTED;
|
||||
}
|
||||
raw8_buffer1.resize(c_width * c_height);
|
||||
raw16_buffer1.resize(c_width * c_height);
|
||||
raw8_buffer2.resize(c_width * c_height);
|
||||
raw16_buffer2.resize(c_width * c_height);
|
||||
SDL_CameraSpec cam_spec{};
|
||||
switch (output_config0.format.formatLevel0) {
|
||||
case ORBIS_CAMERA_FORMAT_YUV422:
|
||||
cam_spec.format = SDL_PIXELFORMAT_YUY2;
|
||||
break;
|
||||
case ORBIS_CAMERA_FORMAT_RAW8:
|
||||
cam_spec.format = SDL_PIXELFORMAT_RGBA8888; // to be swizzled
|
||||
break;
|
||||
case ORBIS_CAMERA_FORMAT_RAW16:
|
||||
cam_spec.format = SDL_PIXELFORMAT_RGBA8888; // to be swizzled
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_ERROR(Lib_Camera, "Invalid format {}",
|
||||
std::to_underlying(output_config0.format.formatLevel0));
|
||||
break;
|
||||
}
|
||||
cam_spec.height = c_height;
|
||||
cam_spec.width = c_width;
|
||||
cam_spec.framerate_numerator = 60;
|
||||
cam_spec.framerate_denominator = 1;
|
||||
sdl_camera = SDL_OpenCamera(devices[EmulatorSettings.GetCameraId()], &cam_spec);
|
||||
LOG_INFO(Lib_Camera, "SDL backend in use: {}", SDL_GetCurrentCameraDriver());
|
||||
char const* camera_name = SDL_GetCameraName(devices[EmulatorSettings.GetCameraId()]);
|
||||
if (camera_name)
|
||||
LOG_INFO(Lib_Camera, "SDL camera name: {}", camera_name);
|
||||
SDL_CameraSpec spec;
|
||||
SDL_GetCameraFormat(sdl_camera, &spec);
|
||||
LOG_INFO(Lib_Camera, "SDL camera format: {:#x}", std::to_underlying(spec.format));
|
||||
LOG_INFO(Lib_Camera, "SDL camera framerate: {}",
|
||||
(float)spec.framerate_numerator / (float)spec.framerate_denominator);
|
||||
LOG_INFO(Lib_Camera, "SDL camera dimensions: {}x{}", spec.width, spec.height);
|
||||
|
||||
SDL_free(devices);
|
||||
|
||||
// "warm up" the device, as recommended by SDL
|
||||
u64 timestamp;
|
||||
SDL_Surface* frame = nullptr;
|
||||
frame = SDL_AcquireCameraFrame(sdl_camera, ×tamp);
|
||||
if (!frame) {
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
frame = SDL_AcquireCameraFrame(sdl_camera, ×tamp);
|
||||
if (frame) {
|
||||
SDL_ReleaseCameraFrame(sdl_camera, frame);
|
||||
break;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::nanoseconds(10));
|
||||
}
|
||||
}
|
||||
|
||||
if (!sdl_camera) {
|
||||
LOG_ERROR(Lib_Camera, "Failed to open camera: {}", SDL_GetError());
|
||||
return ORBIS_CAMERA_ERROR_FATAL;
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
||||
@ -102,6 +102,123 @@ struct OrbisCameraConfig {
|
||||
OrbisCameraConfigExtention configExtention[ORBIS_CAMERA_MAX_DEVICE_NUM];
|
||||
};
|
||||
|
||||
constexpr OrbisCameraConfigExtention camera_config_types[5][ORBIS_CAMERA_MAX_DEVICE_NUM]{
|
||||
{
|
||||
// type 1
|
||||
{
|
||||
.format =
|
||||
{
|
||||
.formatLevel0 = ORBIS_CAMERA_FORMAT_YUV422,
|
||||
.formatLevel1 = ORBIS_CAMERA_SCALE_FORMAT_Y8,
|
||||
.formatLevel2 = ORBIS_CAMERA_SCALE_FORMAT_Y8,
|
||||
.formatLevel3 = ORBIS_CAMERA_SCALE_FORMAT_Y8,
|
||||
},
|
||||
.framerate = ORBIS_CAMERA_FRAMERATE_60,
|
||||
},
|
||||
{
|
||||
.format =
|
||||
{
|
||||
.formatLevel0 = ORBIS_CAMERA_FORMAT_RAW16,
|
||||
.formatLevel1 = ORBIS_CAMERA_SCALE_FORMAT_Y8,
|
||||
.formatLevel2 = ORBIS_CAMERA_SCALE_FORMAT_Y8,
|
||||
.formatLevel3 = ORBIS_CAMERA_SCALE_FORMAT_Y8,
|
||||
},
|
||||
.framerate = ORBIS_CAMERA_FRAMERATE_60,
|
||||
},
|
||||
},
|
||||
{
|
||||
// type 2
|
||||
{
|
||||
.format =
|
||||
{
|
||||
.formatLevel0 = ORBIS_CAMERA_FORMAT_YUV422,
|
||||
.formatLevel1 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
|
||||
.formatLevel2 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
|
||||
.formatLevel3 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
|
||||
},
|
||||
.framerate = ORBIS_CAMERA_FRAMERATE_60,
|
||||
},
|
||||
{
|
||||
.format =
|
||||
{
|
||||
.formatLevel0 = ORBIS_CAMERA_FORMAT_YUV422,
|
||||
.formatLevel1 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
|
||||
.formatLevel2 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
|
||||
.formatLevel3 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
|
||||
},
|
||||
.framerate = ORBIS_CAMERA_FRAMERATE_60,
|
||||
},
|
||||
},
|
||||
{
|
||||
// type 3
|
||||
{
|
||||
.format =
|
||||
{
|
||||
.formatLevel0 = ORBIS_CAMERA_FORMAT_YUV422,
|
||||
.formatLevel1 = ORBIS_CAMERA_SCALE_FORMAT_Y8,
|
||||
.formatLevel2 = ORBIS_CAMERA_SCALE_FORMAT_Y8,
|
||||
.formatLevel3 = ORBIS_CAMERA_SCALE_FORMAT_Y8,
|
||||
},
|
||||
.framerate = ORBIS_CAMERA_FRAMERATE_60,
|
||||
},
|
||||
{
|
||||
.format =
|
||||
{
|
||||
.formatLevel0 = ORBIS_CAMERA_FORMAT_YUV422,
|
||||
.formatLevel1 = ORBIS_CAMERA_SCALE_FORMAT_Y8,
|
||||
.formatLevel2 = ORBIS_CAMERA_SCALE_FORMAT_Y8,
|
||||
.formatLevel3 = ORBIS_CAMERA_SCALE_FORMAT_Y8,
|
||||
},
|
||||
.framerate = ORBIS_CAMERA_FRAMERATE_60,
|
||||
},
|
||||
},
|
||||
{
|
||||
// type 4
|
||||
{
|
||||
.format =
|
||||
{
|
||||
.formatLevel0 = ORBIS_CAMERA_FORMAT_RAW16,
|
||||
.formatLevel1 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
|
||||
.formatLevel2 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
|
||||
.formatLevel3 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
|
||||
},
|
||||
.framerate = ORBIS_CAMERA_FRAMERATE_60,
|
||||
},
|
||||
{
|
||||
.format =
|
||||
{
|
||||
.formatLevel0 = ORBIS_CAMERA_FORMAT_RAW16,
|
||||
.formatLevel1 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
|
||||
.formatLevel2 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
|
||||
.formatLevel3 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
|
||||
},
|
||||
.framerate = ORBIS_CAMERA_FRAMERATE_60,
|
||||
},
|
||||
},
|
||||
{
|
||||
// type 5
|
||||
{
|
||||
.format =
|
||||
{
|
||||
.formatLevel0 = ORBIS_CAMERA_FORMAT_YUV422,
|
||||
.formatLevel1 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
|
||||
.formatLevel2 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
|
||||
.formatLevel3 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
|
||||
},
|
||||
.framerate = ORBIS_CAMERA_FRAMERATE_60,
|
||||
},
|
||||
{
|
||||
.format =
|
||||
{
|
||||
.formatLevel0 = ORBIS_CAMERA_FORMAT_RAW16,
|
||||
.formatLevel1 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
|
||||
.formatLevel2 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
|
||||
.formatLevel3 = ORBIS_CAMERA_SCALE_FORMAT_YUV422,
|
||||
},
|
||||
.framerate = ORBIS_CAMERA_FRAMERATE_60,
|
||||
},
|
||||
}};
|
||||
|
||||
enum OrbisCameraAecAgcTarget {
|
||||
ORBIS_CAMERA_ATTRIBUTE_AECAGC_TARGET_DEF = 0x00,
|
||||
ORBIS_CAMERA_ATTRIBUTE_AECAGC_TARGET_2_0 = 0x20,
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "gnm_error.h"
|
||||
#include "gnmdriver.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/config.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/elf_info.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/slot_vector.h"
|
||||
#include "core/address_space.h"
|
||||
#include "core/debug_state.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/gnmdriver/gnm_error.h"
|
||||
#include "core/libraries/gnmdriver/gnmdriver_init.h"
|
||||
#include "core/libraries/kernel/orbis_error.h"
|
||||
@ -1172,13 +1172,14 @@ bool PS4_SYSV_ABI sceGnmIsUserPaEnabled() {
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceGnmLogicalCuIndexToPhysicalCuIndex() {
|
||||
LOG_ERROR(Lib_GnmDriver, "(STUBBED) called");
|
||||
LOG_TRACE(Lib_GnmDriver, "called");
|
||||
// Not available in retail firmware
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceGnmLogicalCuMaskToPhysicalCuMask() {
|
||||
LOG_ERROR(Lib_GnmDriver, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
s32 PS4_SYSV_ABI sceGnmLogicalCuMaskToPhysicalCuMask(s64, s32 logical_cu_mask) {
|
||||
LOG_INFO(Lib_GnmDriver, "called, logical_cu_mask: {}", logical_cu_mask);
|
||||
return logical_cu_mask;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceGnmLogicalTcaUnitToPhysical() {
|
||||
@ -2874,7 +2875,7 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
sdk_version = 0;
|
||||
}
|
||||
|
||||
if (Config::copyGPUCmdBuffers()) {
|
||||
if (EmulatorSettings.IsCopyGpuBuffers()) {
|
||||
liverpool->ReserveCopyBufferSpace();
|
||||
}
|
||||
|
||||
|
||||
@ -121,7 +121,7 @@ s32 PS4_SYSV_ABI sceGnmInsertWaitFlipDone(u32* cmdbuf, u32 size, s32 vo_handle,
|
||||
int PS4_SYSV_ABI sceGnmIsCoredumpValid();
|
||||
bool PS4_SYSV_ABI sceGnmIsUserPaEnabled();
|
||||
int PS4_SYSV_ABI sceGnmLogicalCuIndexToPhysicalCuIndex();
|
||||
int PS4_SYSV_ABI sceGnmLogicalCuMaskToPhysicalCuMask();
|
||||
s32 PS4_SYSV_ABI sceGnmLogicalCuMaskToPhysicalCuMask(s64, s32 logical_cu_mask);
|
||||
int PS4_SYSV_ABI sceGnmLogicalTcaUnitToPhysical();
|
||||
int PS4_SYSV_ABI sceGnmMapComputeQueue(u32 pipe_id, u32 queue_id, VAddr ring_base_addr,
|
||||
u32 ring_size_dw, u32* read_ptr_addr);
|
||||
|
||||
@ -87,11 +87,13 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
|
||||
if (!read && !write && !rdwr) {
|
||||
// Start by checking for invalid flags.
|
||||
*__Error() = POSIX_EINVAL;
|
||||
LOG_ERROR(Kernel_Fs, "Opening path {} failed, invalid flags {:#x}", raw_path, flags);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (strlen(raw_path) > 255) {
|
||||
*__Error() = POSIX_ENAMETOOLONG;
|
||||
LOG_ERROR(Kernel_Fs, "Opening path {} failed, path is too long", raw_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -137,6 +139,7 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
|
||||
// Error if file exists
|
||||
h->DeleteHandle(handle);
|
||||
*__Error() = POSIX_EEXIST;
|
||||
LOG_ERROR(Kernel_Fs, "Creating {} failed, file already exists", raw_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -145,6 +148,7 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
|
||||
// Can't create files in a read only directory
|
||||
h->DeleteHandle(handle);
|
||||
*__Error() = POSIX_EROFS;
|
||||
LOG_ERROR(Kernel_Fs, "Creating {} failed, path is read-only", raw_path);
|
||||
return -1;
|
||||
}
|
||||
// Create a file if it doesn't exist
|
||||
@ -154,6 +158,7 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
|
||||
// If we're not creating a file, and it doesn't exist, return ENOENT
|
||||
h->DeleteHandle(handle);
|
||||
*__Error() = POSIX_ENOENT;
|
||||
LOG_ERROR(Kernel_Fs, "Opening path {} failed, file does not exist", raw_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -169,6 +174,7 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
|
||||
// This will trigger when create & directory is specified, this is expected.
|
||||
h->DeleteHandle(handle);
|
||||
*__Error() = POSIX_ENOTDIR;
|
||||
LOG_ERROR(Kernel_Fs, "Opening directory {} failed, file is not a directory", raw_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -176,6 +182,8 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
|
||||
// Cannot open directories with any type of write access
|
||||
h->DeleteHandle(handle);
|
||||
*__Error() = POSIX_EISDIR;
|
||||
LOG_ERROR(Kernel_Fs, "Opening directory {} failed, cannot open directories for writing",
|
||||
raw_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -183,6 +191,8 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
|
||||
// Cannot open directories with truncate
|
||||
h->DeleteHandle(handle);
|
||||
*__Error() = POSIX_EISDIR;
|
||||
LOG_ERROR(Kernel_Fs, "Opening directory {} failed, cannot truncate directories",
|
||||
raw_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -201,6 +211,7 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
|
||||
// Can't open files with truncate flag in a read only directory
|
||||
h->DeleteHandle(handle);
|
||||
*__Error() = POSIX_EROFS;
|
||||
LOG_ERROR(Kernel_Fs, "Truncating {} failed, path is read-only", raw_path);
|
||||
return -1;
|
||||
} else if (truncate) {
|
||||
// Open the file as read-write so we can truncate regardless of flags.
|
||||
@ -219,6 +230,7 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
|
||||
// Can't open files with write/read-write access in a read only directory
|
||||
h->DeleteHandle(handle);
|
||||
*__Error() = POSIX_EROFS;
|
||||
LOG_ERROR(Kernel_Fs, "Opening {} for writing failed, path is read-only", raw_path);
|
||||
return -1;
|
||||
} else if (write) {
|
||||
if (append) {
|
||||
@ -244,6 +256,7 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
|
||||
// Open failed in platform-specific code, errno needs to be converted.
|
||||
h->DeleteHandle(handle);
|
||||
SetPosixErrno(e);
|
||||
LOG_ERROR(Kernel_Fs, "Opening {} failed, error = {}", raw_path, *__Error());
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -258,7 +271,6 @@ s32 PS4_SYSV_ABI posix_open(const char* filename, s32 flags, u16 mode) {
|
||||
s32 PS4_SYSV_ABI sceKernelOpen(const char* path, s32 flags, /* SceKernelMode*/ u16 mode) {
|
||||
s32 result = open(path, flags, mode);
|
||||
if (result < 0) {
|
||||
LOG_ERROR(Kernel_Fs, "error = {}", *__Error());
|
||||
return ErrnoToSceKernelError(*__Error());
|
||||
}
|
||||
return result;
|
||||
|
||||
@ -37,7 +37,7 @@ struct OrbisWrapperImpl<PS4_SYSV_ABI R (*)(Args...), f> {
|
||||
|
||||
#define ORBIS(func) (Libraries::Kernel::OrbisWrapperImpl<decltype(&(func)), func>::wrap)
|
||||
|
||||
#define CURRENT_FIRMWARE_VERSION 0x13020011
|
||||
#define CURRENT_FIRMWARE_VERSION 0x13500011
|
||||
|
||||
s32* PS4_SYSV_ABI __Error();
|
||||
|
||||
|
||||
@ -106,3 +106,5 @@ constexpr int ORBIS_KERNEL_ERROR_ECAPMODE = 0x8002005E;
|
||||
constexpr int ORBIS_KERNEL_ERROR_ENOBLK = 0x8002005F;
|
||||
constexpr int ORBIS_KERNEL_ERROR_EICV = 0x80020060;
|
||||
constexpr int ORBIS_KERNEL_ERROR_ENOPLAYGOENT = 0x80020061;
|
||||
constexpr int ORBIS_KERNEL_ERROR_ESDKVERSION = 0x80020063;
|
||||
constexpr int ORBIS_KERNEL_ERROR_ESTART = 0x80020064;
|
||||
@ -1,9 +1,9 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/elf_info.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/file_sys/fs.h"
|
||||
#include "core/libraries/kernel/orbis_error.h"
|
||||
#include "core/libraries/kernel/process.h"
|
||||
@ -17,19 +17,19 @@ s32 PS4_SYSV_ABI sceKernelIsInSandbox() {
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelIsNeoMode() {
|
||||
return Config::isNeoModeConsole() &&
|
||||
return EmulatorSettings.IsNeo() &&
|
||||
Common::ElfInfo::Instance().GetPSFAttributes().support_neo_mode;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelHasNeoMode() {
|
||||
return Config::isNeoModeConsole();
|
||||
return EmulatorSettings.IsNeo();
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelGetMainSocId() {
|
||||
// These hardcoded values are based on hardware observations.
|
||||
// Different models of PS4/PS4 Pro likely return slightly different values.
|
||||
LOG_DEBUG(Lib_Kernel, "called");
|
||||
if (Config::isNeoModeConsole()) {
|
||||
if (EmulatorSettings.IsNeo()) {
|
||||
return 0x740f30;
|
||||
}
|
||||
return 0x710f10;
|
||||
|
||||
@ -2,7 +2,9 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/libraries/kernel/kernel.h"
|
||||
#include "core/libraries/kernel/orbis_error.h"
|
||||
#include "core/libraries/kernel/posix_error.h"
|
||||
#include "core/libraries/kernel/threads/exception.h"
|
||||
#include "core/libraries/kernel/threads/pthread.h"
|
||||
#include "core/libraries/libs.h"
|
||||
@ -13,23 +15,24 @@
|
||||
#else
|
||||
#include <csignal>
|
||||
#endif
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Libraries::Kernel {
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
// Windows doesn't have native versions of these, and we don't need to use them either.
|
||||
static s32 NativeToOrbisSignal(s32 s) {
|
||||
s32 NativeToOrbisSignal(s32 s) {
|
||||
return s;
|
||||
}
|
||||
|
||||
static s32 OrbisToNativeSignal(s32 s) {
|
||||
s32 OrbisToNativeSignal(s32 s) {
|
||||
return s;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static s32 NativeToOrbisSignal(s32 s) {
|
||||
s32 NativeToOrbisSignal(s32 s) {
|
||||
switch (s) {
|
||||
case SIGHUP:
|
||||
return POSIX_SIGHUP;
|
||||
@ -89,12 +92,21 @@ static s32 NativeToOrbisSignal(s32 s) {
|
||||
return POSIX_SIGUSR1;
|
||||
case SIGUSR2:
|
||||
return POSIX_SIGUSR2;
|
||||
case _SIGEMT:
|
||||
return POSIX_SIGEMT;
|
||||
case _SIGINFO:
|
||||
return POSIX_SIGINFO;
|
||||
case 0:
|
||||
return 128;
|
||||
default:
|
||||
if (s > 0 && s < 128) {
|
||||
return s;
|
||||
}
|
||||
UNREACHABLE_MSG("Unknown signal {}", s);
|
||||
}
|
||||
}
|
||||
|
||||
static s32 OrbisToNativeSignal(s32 s) {
|
||||
s32 OrbisToNativeSignal(s32 s) {
|
||||
switch (s) {
|
||||
case POSIX_SIGHUP:
|
||||
return SIGHUP;
|
||||
@ -108,6 +120,8 @@ static s32 OrbisToNativeSignal(s32 s) {
|
||||
return SIGTRAP;
|
||||
case POSIX_SIGABRT:
|
||||
return SIGABRT;
|
||||
case POSIX_SIGEMT:
|
||||
return _SIGEMT;
|
||||
case POSIX_SIGFPE:
|
||||
return SIGFPE;
|
||||
case POSIX_SIGKILL:
|
||||
@ -150,22 +164,33 @@ static s32 OrbisToNativeSignal(s32 s) {
|
||||
return SIGPROF;
|
||||
case POSIX_SIGWINCH:
|
||||
return SIGWINCH;
|
||||
case POSIX_SIGINFO:
|
||||
return _SIGINFO;
|
||||
case POSIX_SIGUSR1:
|
||||
return SIGUSR1;
|
||||
case POSIX_SIGUSR2:
|
||||
return SIGUSR2;
|
||||
case 128:
|
||||
return 0;
|
||||
default:
|
||||
if (s > 0 && s < 128) {
|
||||
return s;
|
||||
}
|
||||
UNREACHABLE_MSG("Unknown signal {}", s);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
std::array<SceKernelExceptionHandler, 32> Handlers{};
|
||||
#ifdef __APPLE__
|
||||
#define sigisemptyset(x) (*(x) == 0)
|
||||
#endif
|
||||
|
||||
std::array<OrbisKernelExceptionHandler, 130> Handlers{};
|
||||
|
||||
#ifndef _WIN64
|
||||
void SigactionHandler(int native_signum, siginfo_t* inf, ucontext_t* raw_context) {
|
||||
const auto handler = Handlers[native_signum];
|
||||
const auto handler = Handlers[NativeToOrbisSignal(native_signum)];
|
||||
if (handler) {
|
||||
auto ctx = Ucontext{};
|
||||
#ifdef __APPLE__
|
||||
@ -214,6 +239,8 @@ void SigactionHandler(int native_signum, siginfo_t* inf, ucontext_t* raw_context
|
||||
ctx.uc_mcontext.mc_addr = reinterpret_cast<uint64_t>(inf->si_addr);
|
||||
#endif
|
||||
handler(NativeToOrbisSignal(native_signum), &ctx);
|
||||
} else {
|
||||
UNREACHABLE_MSG("Unhandled exception");
|
||||
}
|
||||
}
|
||||
#else
|
||||
@ -221,7 +248,7 @@ void ExceptionHandler(void* arg1, void* arg2, void* arg3, PCONTEXT context) {
|
||||
const char* thrName = (char*)arg1;
|
||||
int native_signum = reinterpret_cast<uintptr_t>(arg2);
|
||||
LOG_INFO(Lib_Kernel, "Exception raised successfully on thread '{}'", thrName);
|
||||
const auto handler = Handlers[native_signum];
|
||||
const auto handler = Handlers[NativeToOrbisSignal(native_signum)];
|
||||
if (handler) {
|
||||
auto ctx = Ucontext{};
|
||||
ctx.uc_mcontext.mc_r8 = context->R8;
|
||||
@ -243,76 +270,105 @@ void ExceptionHandler(void* arg1, void* arg2, void* arg3, PCONTEXT context) {
|
||||
ctx.uc_mcontext.mc_fs = context->SegFs;
|
||||
ctx.uc_mcontext.mc_gs = context->SegGs;
|
||||
handler(NativeToOrbisSignal(native_signum), &ctx);
|
||||
} else {
|
||||
UNREACHABLE_MSG("Unhandled exception");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
int PS4_SYSV_ABI sceKernelInstallExceptionHandler(s32 signum, SceKernelExceptionHandler handler) {
|
||||
if (signum > POSIX_SIGUSR2) {
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
s32 PS4_SYSV_ABI posix_sigemptyset(Sigset* s) {
|
||||
s->bits[0] = 0;
|
||||
s->bits[1] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool PS4_SYSV_ABI posix_sigisemptyset(Sigset* s) {
|
||||
return s->bits[0] == 0 && s->bits[1] == 0;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI posix_sigaction(s32 sig, Sigaction* act, Sigaction* oact) {
|
||||
if (sig < 1 || sig > 128 || sig == POSIX_SIGTHR || sig == POSIX_SIGKILL ||
|
||||
sig == POSIX_SIGSTOP) {
|
||||
*__Error() = POSIX_EINVAL;
|
||||
return ORBIS_FAIL;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
LOG_ERROR(Lib_Kernel, "(STUBBED) called, sig: {}", sig);
|
||||
Handlers[sig] = reinterpret_cast<OrbisKernelExceptionHandler>(
|
||||
act ? act->__sigaction_handler.sigaction : nullptr);
|
||||
#else
|
||||
s32 native_sig = OrbisToNativeSignal(sig);
|
||||
if (native_sig == SIGVTALRM) {
|
||||
LOG_ERROR(Lib_Kernel, "Guest is attempting to use the HLE-reserved signal {}!", sig);
|
||||
*__Error() = POSIX_EINVAL;
|
||||
return ORBIS_FAIL;
|
||||
}
|
||||
#ifndef __APPLE__
|
||||
if (native_sig >= __SIGRTMIN && native_sig < SIGRTMIN) {
|
||||
LOG_ERROR(Lib_Kernel, "Guest is attempting to use the HLE libc-reserved signal {}!", sig);
|
||||
*__Error() = POSIX_EINVAL;
|
||||
return ORBIS_FAIL;
|
||||
}
|
||||
#else
|
||||
if (native_sig > SIGUSR2) {
|
||||
LOG_ERROR(Lib_Kernel,
|
||||
"Guest is attempting to use SIGRT signals, which aren't available on this "
|
||||
"platform (signal: {})!",
|
||||
sig);
|
||||
}
|
||||
LOG_INFO(Lib_Kernel, "Installing signal handler for {}", signum);
|
||||
int const native_signum = OrbisToNativeSignal(signum);
|
||||
#ifdef __APPLE__
|
||||
ASSERT_MSG(native_signum != SIGVTALRM, "SIGVTALRM is HLE-reserved on macOS!");
|
||||
#endif
|
||||
ASSERT_MSG(!Handlers[native_signum], "Invalid parameters");
|
||||
Handlers[native_signum] = handler;
|
||||
#ifndef _WIN64
|
||||
if (native_signum == SIGSEGV || native_signum == SIGBUS || native_signum == SIGILL) {
|
||||
LOG_INFO(Lib_Kernel, "called, sig: {}, native sig: {}", sig, native_sig);
|
||||
struct sigaction native_act{};
|
||||
if (act) {
|
||||
native_act.sa_flags = act->sa_flags; // todo check compatibility, on Linux it seems fine
|
||||
native_act.sa_sigaction =
|
||||
reinterpret_cast<decltype(native_act.sa_sigaction)>(SigactionHandler);
|
||||
if (!posix_sigisemptyset(&act->sa_mask)) {
|
||||
LOG_ERROR(Lib_Kernel, "Unhandled sa_mask: {:x}", act->sa_mask.bits[0]);
|
||||
}
|
||||
}
|
||||
auto const prev_handler = Handlers[sig];
|
||||
Handlers[sig] = reinterpret_cast<OrbisKernelExceptionHandler>(
|
||||
act ? act->__sigaction_handler.sigaction : nullptr);
|
||||
|
||||
if (native_sig == SIGSEGV || native_sig == SIGBUS || native_sig == SIGILL) {
|
||||
return ORBIS_OK; // These are handled in Core::SignalHandler
|
||||
}
|
||||
struct sigaction act = {};
|
||||
act.sa_flags = SA_SIGINFO | SA_RESTART;
|
||||
act.sa_sigaction = reinterpret_cast<decltype(act.sa_sigaction)>(SigactionHandler);
|
||||
sigemptyset(&act.sa_mask);
|
||||
sigaction(native_signum, &act, nullptr);
|
||||
#endif
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceKernelRemoveExceptionHandler(s32 signum) {
|
||||
if (signum > POSIX_SIGUSR2) {
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
if (native_sig > 127) {
|
||||
LOG_WARNING(Lib_Kernel, "We can't install a handler for native signal {}!", native_sig);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
int const native_signum = OrbisToNativeSignal(signum);
|
||||
if (!Handlers[native_signum]) {
|
||||
LOG_WARNING(Lib_Kernel, "removing non-installed handler for signum {}", signum);
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
struct sigaction native_oact{};
|
||||
s32 ret = sigaction(native_sig, act ? &native_act : nullptr, oact ? &native_oact : nullptr);
|
||||
if (oact) {
|
||||
oact->sa_flags = native_oact.sa_flags;
|
||||
oact->__sigaction_handler.sigaction =
|
||||
reinterpret_cast<decltype(oact->__sigaction_handler.sigaction)>(prev_handler);
|
||||
if (!sigisemptyset(&native_oact.sa_mask)) {
|
||||
LOG_ERROR(Lib_Kernel, "Unhandled sa_mask");
|
||||
}
|
||||
}
|
||||
Handlers[native_signum] = nullptr;
|
||||
#ifndef _WIN64
|
||||
if (native_signum == SIGSEGV || native_signum == SIGBUS || native_signum == SIGILL) {
|
||||
struct sigaction action{};
|
||||
action.sa_sigaction = Core::SignalHandler;
|
||||
action.sa_flags = SA_SIGINFO | SA_ONSTACK;
|
||||
sigemptyset(&action.sa_mask);
|
||||
|
||||
ASSERT_MSG(sigaction(native_signum, &action, nullptr) == 0,
|
||||
"Failed to reinstate original signal handler for signal {}", native_signum);
|
||||
} else {
|
||||
struct sigaction act = {};
|
||||
act.sa_flags = SA_SIGINFO | SA_RESTART;
|
||||
act.sa_sigaction = nullptr;
|
||||
sigemptyset(&act.sa_mask);
|
||||
sigaction(native_signum, &act, nullptr);
|
||||
if (ret < 0) {
|
||||
LOG_ERROR(Lib_Kernel, "sigaction failed: {}", strerror(errno));
|
||||
*__Error() = ErrnoToSceKernelError(errno);
|
||||
return ORBIS_FAIL;
|
||||
}
|
||||
#endif
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceKernelRaiseException(PthreadT thread, int signum) {
|
||||
if (signum != POSIX_SIGUSR1) {
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
s32 PS4_SYSV_ABI posix_pthread_kill(PthreadT thread, s32 sig) {
|
||||
if (sig < 1 || sig > 128) { // off-by-one error?
|
||||
return POSIX_EINVAL;
|
||||
}
|
||||
LOG_WARNING(Lib_Kernel, "Raising exception on thread '{}'", thread->name);
|
||||
int const native_signum = OrbisToNativeSignal(signum);
|
||||
LOG_WARNING(Lib_Kernel, "Raising signal {} on thread '{}'", sig, thread->name);
|
||||
int const native_signum = OrbisToNativeSignal(sig);
|
||||
#ifndef _WIN64
|
||||
const auto pthr = reinterpret_cast<pthread_t>(thread->native_thr.GetHandle());
|
||||
const auto ret = pthread_kill(pthr, native_signum);
|
||||
if (ret != 0) {
|
||||
LOG_ERROR(Kernel, "Failed to send exception signal to thread '{}': {}", thread->name,
|
||||
strerror(ret));
|
||||
strerror(errno));
|
||||
}
|
||||
#else
|
||||
USER_APC_OPTION option;
|
||||
@ -326,6 +382,67 @@ int PS4_SYSV_ABI sceKernelRaiseException(PthreadT thread, int signum) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
// libkernel has a check in sceKernelInstallExceptionHandler and sceKernelRemoveExceptionHandler for
|
||||
// validating if the application requested a handler for an allowed signal or not. However, that is
|
||||
// just a wrapper for sigaction, which itself does not have any such restrictions, and therefore
|
||||
// this check is ridiculously trivial to go around. This, however, means that we need to support all
|
||||
// 127 - 3 possible signals, even if realistically, only homebrew will use most of them.
|
||||
static std::unordered_set<s32> orbis_allowed_signals{
|
||||
POSIX_SIGHUP, POSIX_SIGILL, POSIX_SIGFPE, POSIX_SIGBUS, POSIX_SIGSEGV, POSIX_SIGUSR1,
|
||||
};
|
||||
|
||||
int PS4_SYSV_ABI sceKernelInstallExceptionHandler(s32 signum, OrbisKernelExceptionHandler handler) {
|
||||
if (!orbis_allowed_signals.contains(signum)) {
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if (Handlers[signum] != nullptr) {
|
||||
return ORBIS_KERNEL_ERROR_EAGAIN;
|
||||
}
|
||||
LOG_INFO(Lib_Kernel, "Installing signal handler for {}", signum);
|
||||
Sigaction act = {};
|
||||
act.sa_flags = POSIX_SA_SIGINFO | POSIX_SA_RESTART;
|
||||
act.__sigaction_handler.sigaction =
|
||||
reinterpret_cast<decltype(act.__sigaction_handler.sigaction)>(handler);
|
||||
posix_sigemptyset(&act.sa_mask);
|
||||
s32 ret = posix_sigaction(signum, &act, nullptr);
|
||||
if (ret < 0) {
|
||||
LOG_ERROR(Lib_Kernel, "Failed to add handler for signal {}: {}", signum,
|
||||
strerror(*__Error()));
|
||||
return ErrnoToSceKernelError(*__Error());
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceKernelRemoveExceptionHandler(s32 signum) {
|
||||
if (!orbis_allowed_signals.contains(signum)) {
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
int const native_signum = OrbisToNativeSignal(signum);
|
||||
Handlers[signum] = nullptr;
|
||||
Sigaction act = {};
|
||||
act.sa_flags = POSIX_SA_SIGINFO;
|
||||
act.__sigaction_handler.sigaction = nullptr;
|
||||
posix_sigemptyset(&act.sa_mask);
|
||||
s32 ret = posix_sigaction(signum, &act, nullptr);
|
||||
if (ret < 0) {
|
||||
LOG_ERROR(Lib_Kernel, "Failed to remove handler for signal {}: {}", signum,
|
||||
strerror(*__Error()));
|
||||
return ErrnoToSceKernelError(*__Error());
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceKernelRaiseException(PthreadT thread, int signum) {
|
||||
if (signum != POSIX_SIGUSR1) {
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
s32 ret = posix_pthread_kill(thread, signum);
|
||||
if (ret < 0) {
|
||||
return ErrnoToSceKernelError(ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelDebugRaiseException(s32 error, s64 unk) {
|
||||
if (unk != 0) {
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
@ -352,6 +469,13 @@ void RegisterException(Core::Loader::SymbolsResolver* sym) {
|
||||
sceKernelDebugRaiseExceptionOnReleaseMode);
|
||||
LIB_FUNCTION("WkwEd3N7w0Y", "libkernel", 1, "libkernel", sceKernelInstallExceptionHandler);
|
||||
LIB_FUNCTION("Qhv5ARAoOEc", "libkernel", 1, "libkernel", sceKernelRemoveExceptionHandler);
|
||||
|
||||
LIB_FUNCTION("KiJEPEWRyUY", "libkernel", 1, "libkernel", posix_sigaction);
|
||||
LIB_FUNCTION("+F7C-hdk7+E", "libkernel", 1, "libkernel", posix_sigemptyset);
|
||||
LIB_FUNCTION("yH-uQW3LbX0", "libkernel", 1, "libkernel", posix_pthread_kill);
|
||||
LIB_FUNCTION("KiJEPEWRyUY", "libScePosix", 1, "libkernel", posix_sigaction);
|
||||
LIB_FUNCTION("+F7C-hdk7+E", "libScePosix", 1, "libkernel", posix_sigemptyset);
|
||||
LIB_FUNCTION("yH-uQW3LbX0", "libScePosix", 1, "libkernel", posix_pthread_kill);
|
||||
}
|
||||
|
||||
} // namespace Libraries::Kernel
|
||||
|
||||
@ -11,7 +11,7 @@ class SymbolsResolver;
|
||||
|
||||
namespace Libraries::Kernel {
|
||||
|
||||
using SceKernelExceptionHandler = PS4_SYSV_ABI void (*)(int, void*);
|
||||
using OrbisKernelExceptionHandler = PS4_SYSV_ABI void (*)(int, void*);
|
||||
|
||||
constexpr s32 POSIX_SIGHUP = 1;
|
||||
constexpr s32 POSIX_SIGINT = 2;
|
||||
@ -47,6 +47,23 @@ constexpr s32 POSIX_SIGUSR2 = 31;
|
||||
constexpr s32 POSIX_SIGTHR = 32;
|
||||
constexpr s32 POSIX_SIGLIBRT = 33;
|
||||
|
||||
#ifdef __linux__
|
||||
constexpr s32 _SIGEMT = 128;
|
||||
constexpr s32 _SIGINFO = 129;
|
||||
#elif !defined(_WIN32)
|
||||
constexpr s32 _SIGEMT = SIGEMT;
|
||||
constexpr s32 _SIGINFO = SIGINFO;
|
||||
#endif
|
||||
|
||||
constexpr s32 POSIX_SA_NOCLDSTOP = 1;
|
||||
constexpr s32 POSIX_SA_NOCLDWAIT = 2;
|
||||
constexpr s32 POSIX_SA_SIGINFO = 4;
|
||||
constexpr s32 POSIX_SA_ONSTACK = 0x08000000;
|
||||
constexpr s32 POSIX_SA_RESTART = 0x10000000;
|
||||
constexpr s32 POSIX_SA_NODEFER = 0x40000000;
|
||||
constexpr s32 POSIX_SA_RESETHAND = 0x80000000;
|
||||
constexpr s32 POSIX_SA_RESTORER = 0x04000000;
|
||||
|
||||
struct Mcontext {
|
||||
u64 mc_onstack;
|
||||
u64 mc_rdi;
|
||||
@ -101,17 +118,74 @@ struct Sigset {
|
||||
u64 bits[2];
|
||||
};
|
||||
|
||||
union Sigval {
|
||||
/* Members as suggested by Annex C of POSIX 1003.1b. */
|
||||
int sival_int;
|
||||
void* sival_ptr;
|
||||
/* 6.0 compatibility */
|
||||
int sigval_int;
|
||||
void* sigval_ptr;
|
||||
};
|
||||
|
||||
struct Siginfo {
|
||||
int _si_signo; /* signal number */
|
||||
int _si_errno; /* errno association */
|
||||
/*
|
||||
* Cause of signal, one of the SI_ macros or signal-specific
|
||||
* values, i.e. one of the FPE_... values for SIGFPE. This
|
||||
* value is equivalent to the second argument to an old-style
|
||||
* FreeBSD signal handler.
|
||||
*/
|
||||
int _si_code; /* signal code */
|
||||
s32 _si_pid; /* sending process */
|
||||
u32 _si_uid; /* sender's ruid */
|
||||
int _si_status; /* exit value */
|
||||
void* _si_addr; /* faulting instruction */
|
||||
union Sigval _si_value; /* signal value */
|
||||
union {
|
||||
struct {
|
||||
int _trapno; /* machine specific trap code */
|
||||
} _fault;
|
||||
struct {
|
||||
int _timerid;
|
||||
int _overrun;
|
||||
} _timer;
|
||||
struct {
|
||||
int _mqd;
|
||||
} _mesgq;
|
||||
struct {
|
||||
long _band; /* band event for SIGPOLL */
|
||||
} _poll; /* was this ever used ? */
|
||||
struct {
|
||||
long __spare1__;
|
||||
int __spare2__[7];
|
||||
} __spare__;
|
||||
} _reason;
|
||||
};
|
||||
|
||||
struct Sigaction {
|
||||
union {
|
||||
void (*handler)(int);
|
||||
void (*sigaction)(int, struct Siginfo*, void*);
|
||||
} __sigaction_handler;
|
||||
int sa_flags;
|
||||
Sigset sa_mask;
|
||||
};
|
||||
|
||||
struct Ucontext {
|
||||
struct Sigset uc_sigmask;
|
||||
int field1_0x10[12];
|
||||
struct Mcontext uc_mcontext;
|
||||
struct Ucontext* uc_link;
|
||||
struct ExStack uc_stack;
|
||||
Mcontext uc_mcontext;
|
||||
Ucontext* uc_link;
|
||||
ExStack uc_stack;
|
||||
int uc_flags;
|
||||
int __spare[4];
|
||||
int field7_0x4f4[3];
|
||||
};
|
||||
|
||||
s32 NativeToOrbisSignal(s32 s);
|
||||
s32 OrbisToNativeSignal(s32 s);
|
||||
|
||||
void RegisterException(Core::Loader::SymbolsResolver* sym);
|
||||
|
||||
} // namespace Libraries::Kernel
|
||||
|
||||
@ -242,7 +242,7 @@ int PS4_SYSV_ABI posix_pthread_create_name_np(PthreadT* thread, const PthreadAtt
|
||||
new_thread->attr.sched_policy = curthread->attr.sched_policy;
|
||||
}
|
||||
|
||||
static int TidCounter = 1;
|
||||
static std::atomic<int> TidCounter = 1;
|
||||
new_thread->tid = ++TidCounter;
|
||||
|
||||
if (new_thread->attr.stackaddr_attr == nullptr) {
|
||||
@ -665,6 +665,7 @@ void RegisterThread(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("Z4QosVuAsA0", "libkernel", 1, "libkernel", posix_pthread_once);
|
||||
LIB_FUNCTION("EotR8a3ASf4", "libkernel", 1, "libkernel", posix_pthread_self);
|
||||
LIB_FUNCTION("OxhIB8LB-PQ", "libkernel", 1, "libkernel", posix_pthread_create);
|
||||
LIB_FUNCTION("Jmi+9w9u0E4", "libkernel", 1, "libkernel", posix_pthread_create_name_np);
|
||||
LIB_FUNCTION("lZzFeSxPl08", "libkernel", 1, "libkernel", posix_pthread_setcancelstate);
|
||||
LIB_FUNCTION("CBNtXOoef-E", "libkernel", 1, "libkernel", posix_sched_get_priority_max);
|
||||
LIB_FUNCTION("m0iS6jNsXds", "libkernel", 1, "libkernel", posix_sched_get_priority_min);
|
||||
|
||||
@ -199,7 +199,11 @@ OrbisFILE* PS4_SYSV_ABI internal_fopen(const char* path, const char* mode) {
|
||||
std::scoped_lock lk{g_file_mtx};
|
||||
LOG_INFO(Lib_LibcInternal, "called, path {}, mode {}", path, mode);
|
||||
OrbisFILE* file = internal__Fofind();
|
||||
return internal__Foprep(path, mode, file, -1, 0, 0);
|
||||
OrbisFILE* ret_file = internal__Foprep(path, mode, file, -1, 0, 0);
|
||||
if (ret_file == nullptr) {
|
||||
LOG_ERROR(Lib_LibcInternal, "failed to open file {}", path);
|
||||
}
|
||||
return ret_file;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI internal_fflush(OrbisFILE* file) {
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/config.h"
|
||||
#include "core/libraries/ajm/ajm.h"
|
||||
#include "core/libraries/app_content/app_content.h"
|
||||
#include "core/libraries/audio/audioin.h"
|
||||
#include "core/libraries/audio/audioout.h"
|
||||
#include "core/libraries/audio3d/audio3d.h"
|
||||
#include "core/libraries/audio3d/audio3d_openal.h"
|
||||
#include "core/libraries/avplayer/avplayer.h"
|
||||
#include "core/libraries/camera/camera.h"
|
||||
#include "core/libraries/companion/companion_httpd.h"
|
||||
@ -57,10 +57,10 @@
|
||||
#include "core/libraries/screenshot/screenshot.h"
|
||||
#include "core/libraries/share_play/shareplay.h"
|
||||
#include "core/libraries/signin_dialog/signindialog.h"
|
||||
#include "core/libraries/sysmodule/sysmodule.h"
|
||||
#include "core/libraries/system/commondialog.h"
|
||||
#include "core/libraries/system/msgdialog.h"
|
||||
#include "core/libraries/system/posix.h"
|
||||
#include "core/libraries/system/sysmodule.h"
|
||||
#include "core/libraries/system/systemservice.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
#include "core/libraries/system_gesture/system_gesture.h"
|
||||
@ -127,7 +127,11 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
|
||||
Libraries::AvPlayer::RegisterLib(sym);
|
||||
Libraries::Videodec::RegisterLib(sym);
|
||||
Libraries::Videodec2::RegisterLib(sym);
|
||||
Libraries::Audio3d::RegisterLib(sym);
|
||||
if (EmulatorSettings.GetAudioBackend() == AudioBackend::OpenAL) {
|
||||
Libraries::Audio3dOpenAL::RegisterLib(sym);
|
||||
} else {
|
||||
Libraries::Audio3d::RegisterLib(sym);
|
||||
}
|
||||
Libraries::Ime::RegisterLib(sym);
|
||||
Libraries::GameLiveStreaming::RegisterLib(sym);
|
||||
Libraries::SharePlay::RegisterLib(sym);
|
||||
|
||||
@ -1447,7 +1447,7 @@ int PS4_SYSV_ABI sceNetResolverStartNtoa(OrbisNetId resolverid, const char* host
|
||||
return ORBIS_NET_ERROR_EBADF;
|
||||
}
|
||||
|
||||
if (!Config::getIsConnectedToNetwork()) {
|
||||
if (!EmulatorSettings.IsConnectedToNetwork()) {
|
||||
*sceNetErrnoLoc() = ORBIS_NET_RESOLVER_ENODNS;
|
||||
file->resolver->resolution_error = ORBIS_NET_ERROR_RESOLVER_ENODNS;
|
||||
return ORBIS_NET_ERROR_RESOLVER_ENODNS;
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/network/net_ctl_codes.h"
|
||||
#include "core/libraries/network/net_ctl_obj.h"
|
||||
#include "core/tls.h"
|
||||
@ -46,8 +46,9 @@ s32 NetCtlInternal::RegisterNpToolkitCallback(OrbisNetCtlCallbackForNpToolkit fu
|
||||
|
||||
void NetCtlInternal::CheckCallback() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
const auto event = Config::getIsConnectedToNetwork() ? ORBIS_NET_CTL_EVENT_TYPE_IPOBTAINED
|
||||
: ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED;
|
||||
const auto event = EmulatorSettings.IsConnectedToNetwork()
|
||||
? ORBIS_NET_CTL_EVENT_TYPE_IPOBTAINED
|
||||
: ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED;
|
||||
for (const auto [func, arg] : callbacks) {
|
||||
if (func != nullptr) {
|
||||
func(event, arg);
|
||||
@ -57,8 +58,9 @@ void NetCtlInternal::CheckCallback() {
|
||||
|
||||
void NetCtlInternal::CheckNpToolkitCallback() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
const auto event = Config::getIsConnectedToNetwork() ? ORBIS_NET_CTL_EVENT_TYPE_IPOBTAINED
|
||||
: ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED;
|
||||
const auto event = EmulatorSettings.IsConnectedToNetwork()
|
||||
? ORBIS_NET_CTL_EVENT_TYPE_IPOBTAINED
|
||||
: ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED;
|
||||
for (const auto [func, arg] : nptool_callbacks) {
|
||||
if (func != nullptr) {
|
||||
func(event, arg);
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/config.h"
|
||||
#include "common/singleton.h"
|
||||
#include "common/types.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "net_error.h"
|
||||
#include "net_resolver.h"
|
||||
@ -27,7 +27,7 @@ int Resolver::ResolveAsync(const char* hostname, OrbisNetInAddr* addr, int timeo
|
||||
}
|
||||
|
||||
void Resolver::Resolve() {
|
||||
if (!Config::getIsConnectedToNetwork()) {
|
||||
if (!EmulatorSettings.IsConnectedToNetwork()) {
|
||||
resolution_error = ORBIS_NET_ERROR_RESOLVER_ENODNS;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#ifdef WIN32
|
||||
@ -13,8 +13,8 @@
|
||||
#endif
|
||||
|
||||
#include <common/singleton.h>
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/network/net_ctl_codes.h"
|
||||
@ -162,7 +162,7 @@ int PS4_SYSV_ABI sceNetCtlGetIfStat() {
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetInfo(int code, OrbisNetCtlInfo* info) {
|
||||
LOG_DEBUG(Lib_NetCtl, "code = {}", code);
|
||||
if (!Config::getIsConnectedToNetwork()) {
|
||||
if (!EmulatorSettings.IsConnectedToNetwork()) {
|
||||
return ORBIS_NET_CTL_ERROR_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
@ -180,8 +180,8 @@ int PS4_SYSV_ABI sceNetCtlGetInfo(int code, OrbisNetCtlInfo* info) {
|
||||
info->mtu = 1500; // default value
|
||||
break;
|
||||
case ORBIS_NET_CTL_INFO_LINK:
|
||||
info->link = Config::getIsConnectedToNetwork() ? ORBIS_NET_CTL_LINK_CONNECTED
|
||||
: ORBIS_NET_CTL_LINK_DISCONNECTED;
|
||||
info->link = EmulatorSettings.IsConnectedToNetwork() ? ORBIS_NET_CTL_LINK_CONNECTED
|
||||
: ORBIS_NET_CTL_LINK_DISCONNECTED;
|
||||
break;
|
||||
case ORBIS_NET_CTL_INFO_IP_ADDRESS: {
|
||||
strcpy(info->ip_address,
|
||||
@ -318,7 +318,7 @@ int PS4_SYSV_ABI sceNetCtlGetScanInfoForSsidScanIpcInt() {
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlGetState(int* state) {
|
||||
const auto connected = Config::getIsConnectedToNetwork();
|
||||
const auto connected = EmulatorSettings.IsConnectedToNetwork();
|
||||
LOG_DEBUG(Lib_NetCtl, "connected = {}", connected);
|
||||
const auto current_state =
|
||||
connected ? ORBIS_NET_CTL_STATE_IPOBTAINED : ORBIS_NET_CTL_STATE_DISCONNECTED;
|
||||
|
||||
@ -114,7 +114,13 @@ int PS4_SYSV_ABI sceSslFreeCaCerts(s32 ssl_ctx_id, OrbisSslCaCerts* certs) {
|
||||
if (certs == nullptr) {
|
||||
return ORBIS_SSL_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
delete (certs->certs);
|
||||
if (certs->certs != nullptr) {
|
||||
for (s32 data = 0; data < certs->num; data++) {
|
||||
free(certs->certs[data].ptr);
|
||||
}
|
||||
delete (certs->certs);
|
||||
}
|
||||
|
||||
// delete (certs->pool);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@ -139,7 +145,12 @@ int PS4_SYSV_ABI sceSslGetCaCerts(s32 ssl_ctx_id, OrbisSslCaCerts* certs) {
|
||||
if (certs == nullptr) {
|
||||
return ORBIS_SSL_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
certs->certs = new OrbisSslData{nullptr, 0};
|
||||
// Allocate a buffer to store dummy data in.
|
||||
const char* dummy_data = "dummy";
|
||||
u64 dummy_length = strlen(dummy_data) + 1;
|
||||
char* data = static_cast<char*>(malloc(dummy_length));
|
||||
strncpy(data, dummy_data, dummy_length);
|
||||
certs->certs = new OrbisSslData{data, dummy_length};
|
||||
certs->num = 1;
|
||||
certs->pool = nullptr;
|
||||
return ORBIS_OK;
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <mutex>
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/np/np_auth.h"
|
||||
@ -363,7 +363,7 @@ s32 PS4_SYSV_ABI sceNpAuthDeleteRequest(s32 req_id) {
|
||||
}
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
g_signed_in = Config::getPSNSignedIn();
|
||||
g_signed_in = EmulatorSettings.IsPSNSignedIn();
|
||||
|
||||
LIB_FUNCTION("6bwFkosYRQg", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthCreateRequest);
|
||||
LIB_FUNCTION("N+mr7GjTvr8", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthCreateAsyncRequest);
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <variant>
|
||||
|
||||
#include "common/config.h"
|
||||
#include <core/user_settings.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/np/np_error.h"
|
||||
@ -631,7 +632,8 @@ s32 PS4_SYSV_ABI sceNpGetNpId(Libraries::UserService::OrbisUserServiceUserId use
|
||||
return ORBIS_NP_ERROR_SIGNED_OUT;
|
||||
}
|
||||
memset(np_id, 0, sizeof(OrbisNpId));
|
||||
strncpy(np_id->handle.data, Config::getUserName().c_str(), sizeof(np_id->handle.data));
|
||||
strncpy(np_id->handle.data, UserManagement.GetDefaultUser().user_name.c_str(),
|
||||
sizeof(np_id->handle.data));
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -645,7 +647,8 @@ s32 PS4_SYSV_ABI sceNpGetOnlineId(Libraries::UserService::OrbisUserServiceUserId
|
||||
return ORBIS_NP_ERROR_SIGNED_OUT;
|
||||
}
|
||||
memset(online_id, 0, sizeof(OrbisNpOnlineId));
|
||||
strncpy(online_id->data, Config::getUserName().c_str(), sizeof(online_id->data));
|
||||
strncpy(online_id->data, UserManagement.GetDefaultUser().user_name.c_str(),
|
||||
sizeof(online_id->data));
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -784,7 +787,7 @@ void DeregisterNpCallback(std::string key) {
|
||||
}
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
g_signed_in = Config::getPSNSignedIn();
|
||||
g_signed_in = EmulatorSettings.IsPSNSignedIn();
|
||||
|
||||
LIB_FUNCTION("GpLQDNKICac", "libSceNpManager", 1, "libSceNpManager", sceNpCreateRequest);
|
||||
LIB_FUNCTION("eiqMCt9UshI", "libSceNpManager", 1, "libSceNpManager", sceNpCreateAsyncRequest);
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/np/np_manager.h"
|
||||
@ -376,7 +376,7 @@ int PS4_SYSV_ABI sceNpMatching2ContextStart(OrbisNpMatching2ContextId ctxId, u64
|
||||
}
|
||||
|
||||
std::scoped_lock lk{g_events_mutex};
|
||||
if (Config::getIsConnectedToNetwork() && Config::getPSNSignedIn()) {
|
||||
if (EmulatorSettings.IsConnectedToNetwork() && EmulatorSettings.IsPSNSignedIn()) {
|
||||
g_ctx_events.emplace_back(ctxId, ORBIS_NP_MATCHING2_CONTEXT_EVENT_STARTED,
|
||||
ORBIS_NP_MATCHING2_EVENT_CAUSE_CONTEXT_ACTION, 0);
|
||||
} else {
|
||||
|
||||
@ -1,22 +1,118 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <unordered_map>
|
||||
#include <pugixml.hpp>
|
||||
|
||||
#include "common/elf_info.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/path_util.h"
|
||||
#include "common/slot_vector.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/np/np_error.h"
|
||||
#include "core/libraries/np/np_trophy.h"
|
||||
#include "core/libraries/np/np_trophy_error.h"
|
||||
#include "core/libraries/np/trophy_ui.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace Libraries::Np::NpTrophy {
|
||||
|
||||
std::string game_serial;
|
||||
// PS4 system language IDs map directly to TROP00.XML .. TROP30.XML.
|
||||
// Index = OrbisSystemServiceParamId language value reported by the system.
|
||||
// clang-format off
|
||||
static constexpr std::array<std::string_view, 31> s_language_xml_names = {
|
||||
"TROP_00.XML", // 00 Japanese
|
||||
"TROP_01.XML", // 01 English (US)
|
||||
"TROP_02.XML", // 02 French
|
||||
"TROP_03.XML", // 03 Spanish (ES)
|
||||
"TROP_04.XML", // 04 German
|
||||
"TROP_05.XML", // 05 Italian
|
||||
"TROP_06.XML", // 06 Dutch
|
||||
"TROP_07.XML", // 07 Portuguese (PT)
|
||||
"TROP_08.XML", // 08 Russian
|
||||
"TROP_09.XML", // 09 Korean
|
||||
"TROP_10.XML", // 10 Traditional Chinese
|
||||
"TROP_11.XML", // 11 Simplified Chinese
|
||||
"TROP_12.XML", // 12 Finnish
|
||||
"TROP_13.XML", // 13 Swedish
|
||||
"TROP_14.XML", // 14 Danish
|
||||
"TROP_15.XML", // 15 Norwegian
|
||||
"TROP_16.XML", // 16 Polish
|
||||
"TROP_17.XML", // 17 Portuguese (BR)
|
||||
"TROP_18.XML", // 18 English (GB)
|
||||
"TROP_19.XML", // 19 Turkish
|
||||
"TROP_20.XML", // 20 Spanish (LA)
|
||||
"TROP_21.XML", // 21 Arabic
|
||||
"TROP_22.XML", // 22 French (CA)
|
||||
"TROP_23.XML", // 23 Czech
|
||||
"TROP_24.XML", // 24 Hungarian
|
||||
"TROP_25.XML", // 25 Greek
|
||||
"TROP_26.XML", // 26 Romanian
|
||||
"TROP_27.XML", // 27 Thai
|
||||
"TROP_28.XML", // 28 Vietnamese
|
||||
"TROP_29.XML", // 29 Indonesian
|
||||
"TROP_30.XML", // 30 Unkrainian
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
// Returns the best available trophy XML path for the current system language.
|
||||
// Resolution order:
|
||||
// 1. TROP_XX.XML for the active system language (e.g. TROP01.XML for English)
|
||||
// 2. TROP.XML (master / language-neutral fallback)
|
||||
static std::filesystem::path GetTrophyXmlPath(const std::filesystem::path& xml_dir,
|
||||
int system_language) {
|
||||
// Try the exact language file first.
|
||||
if (system_language >= 0 && system_language < static_cast<int>(s_language_xml_names.size())) {
|
||||
auto lang_path = xml_dir / s_language_xml_names[system_language];
|
||||
if (std::filesystem::exists(lang_path)) {
|
||||
return lang_path;
|
||||
}
|
||||
}
|
||||
// Final fallback: master TROP.XML (always present).
|
||||
return xml_dir / "TROP.XML";
|
||||
}
|
||||
|
||||
static void ApplyUnlockToXmlFile(const std::filesystem::path& xml_path, OrbisNpTrophyId trophyId,
|
||||
u64 trophyTimestamp, bool unlock_platinum,
|
||||
OrbisNpTrophyId platinumId, u64 platinumTimestamp) {
|
||||
pugi::xml_document doc;
|
||||
if (!doc.load_file(xml_path.native().c_str())) {
|
||||
LOG_WARNING(Lib_NpTrophy, "ApplyUnlock: failed to load {}", xml_path.string());
|
||||
return;
|
||||
}
|
||||
|
||||
auto trophyconf = doc.child("trophyconf");
|
||||
for (pugi::xml_node& node : trophyconf.children()) {
|
||||
if (std::string_view(node.name()) != "trophy") {
|
||||
continue;
|
||||
}
|
||||
int id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
|
||||
|
||||
auto set_unlock = [&](u64 ts) {
|
||||
if (node.attribute("unlockstate").empty()) {
|
||||
node.append_attribute("unlockstate") = "true";
|
||||
} else {
|
||||
node.attribute("unlockstate").set_value("true");
|
||||
}
|
||||
const auto ts_str = std::to_string(ts);
|
||||
if (node.attribute("timestamp").empty()) {
|
||||
node.append_attribute("timestamp") = ts_str.c_str();
|
||||
} else {
|
||||
node.attribute("timestamp").set_value(ts_str.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
if (id == trophyId) {
|
||||
set_unlock(trophyTimestamp);
|
||||
} else if (unlock_platinum && id == platinumId) {
|
||||
set_unlock(platinumTimestamp);
|
||||
}
|
||||
}
|
||||
|
||||
doc.save_file(xml_path.native().c_str());
|
||||
}
|
||||
|
||||
static constexpr auto MaxTrophyHandles = 4u;
|
||||
static constexpr auto MaxTrophyContexts = 8u;
|
||||
@ -30,6 +126,11 @@ struct ContextKeyHash {
|
||||
|
||||
struct TrophyContext {
|
||||
u32 context_id;
|
||||
bool registered = false;
|
||||
std::filesystem::path trophy_xml_path; // resolved once at CreateContext
|
||||
std::filesystem::path xml_dir; // .../Xml/
|
||||
std::filesystem::path xml_save_file; // The actual file for tracking progress per-user.
|
||||
std::filesystem::path icons_dir; // .../Icons/
|
||||
};
|
||||
static Common::SlotVector<OrbisNpTrophyHandle> trophy_handles{};
|
||||
static Common::SlotVector<ContextKey> trophy_contexts{};
|
||||
@ -94,66 +195,10 @@ OrbisNpTrophyGrade GetTrophyGradeFromChar(char trophyType) {
|
||||
}
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyAbortHandle(OrbisNpTrophyHandle handle) {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyCaptureScreenshot() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyDetails() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyFlagArray() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyGroupArray() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyGroupDetails() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetInfo() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetInfoInGroup() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetVersion() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyTitleDetails() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigHasGroupFeature() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context,
|
||||
Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
uint32_t service_label, u64 options) {
|
||||
ASSERT(options == 0ull);
|
||||
if (!context) {
|
||||
if (!context || options != 0ull) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
@ -169,7 +214,20 @@ s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context,
|
||||
const auto ctx_id = trophy_contexts.insert(user_id, service_label);
|
||||
|
||||
*context = ctx_id.index + 1;
|
||||
contexts_internal[key].context_id = *context;
|
||||
|
||||
auto& ctx = contexts_internal[key];
|
||||
ctx.context_id = *context;
|
||||
|
||||
// Resolve and cache all paths once so callers never recompute them.
|
||||
const std::string np_comm_id = Common::ElfInfo::Instance().GetNpCommIds()[service_label];
|
||||
const auto trophy_base =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "trophy" / np_comm_id;
|
||||
ctx.xml_save_file = Common::FS::GetUserPath(Common::FS::PathType::HomeDir) /
|
||||
std::to_string(user_id) / "trophy" / (np_comm_id + ".xml");
|
||||
ctx.xml_dir = trophy_base / "Xml";
|
||||
ctx.icons_dir = trophy_base / "Icons";
|
||||
ctx.trophy_xml_path = GetTrophyXmlPath(ctx.xml_dir, EmulatorSettings.GetConsoleLanguage());
|
||||
|
||||
LOG_INFO(Lib_NpTrophy, "New context = {}, user_id = {} service label = {}", *context, user_id,
|
||||
service_label);
|
||||
|
||||
@ -206,6 +264,10 @@ int PS4_SYSV_ABI sceNpTrophyDestroyContext(OrbisNpTrophyContext context) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
if (!trophy_contexts.is_allocated(contextId)) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
ContextKey contextkey = trophy_contexts[contextId];
|
||||
trophy_contexts.erase(contextId);
|
||||
contexts_internal.erase(contextkey);
|
||||
@ -251,12 +313,10 @@ int PS4_SYSV_ABI sceNpTrophyGetGameIcon(OrbisNpTrophyContext context, OrbisNpTro
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
ContextKey contextkey = trophy_contexts[contextId];
|
||||
char trophy_folder[9];
|
||||
snprintf(trophy_folder, sizeof(trophy_folder), "trophy%02d", contextkey.second);
|
||||
|
||||
const auto trophy_dir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
|
||||
auto icon_file = trophy_dir / trophy_folder / "Icons" / "ICON0.PNG";
|
||||
const auto& ctx = contexts_internal[contextkey];
|
||||
|
||||
auto icon_file = ctx.icons_dir / "ICON0.PNG";
|
||||
|
||||
Common::FS::IOFile icon(icon_file, Common::FS::FileAccessMode::Read);
|
||||
if (!icon.IsOpen()) {
|
||||
@ -304,12 +364,11 @@ int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTro
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
ContextKey contextkey = trophy_contexts[contextId];
|
||||
char trophy_folder[9];
|
||||
snprintf(trophy_folder, sizeof(trophy_folder), "trophy%02d", contextkey.second);
|
||||
|
||||
const auto trophy_dir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
|
||||
auto trophy_file = trophy_dir / trophy_folder / "Xml" / "TROP.XML";
|
||||
const auto& ctx = contexts_internal[contextkey];
|
||||
if (!ctx.registered)
|
||||
return ORBIS_NP_TROPHY_ERROR_NOT_REGISTERED;
|
||||
const auto& trophy_file = ctx.trophy_xml_path;
|
||||
const auto& trophy_save_file = ctx.xml_save_file;
|
||||
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
|
||||
@ -336,7 +395,18 @@ int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTro
|
||||
|
||||
if (node_name == "group")
|
||||
game_info.num_groups++;
|
||||
}
|
||||
|
||||
pugi::xml_document save_doc;
|
||||
pugi::xml_parse_result save_result = save_doc.load_file(ctx.xml_save_file.native().c_str());
|
||||
|
||||
if (!save_result) {
|
||||
LOG_ERROR(Lib_NpTrophy, "Failed to parse user trophy xml : {}", result.description());
|
||||
return ORBIS_OK;
|
||||
}
|
||||
auto save_trophyconf = save_doc.child("trophyconf");
|
||||
for (const pugi::xml_node& node : save_trophyconf.children()) {
|
||||
std::string_view node_name = node.name();
|
||||
if (node_name == "trophy") {
|
||||
bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
|
||||
std::string_view current_trophy_grade = node.attribute("ttype").value();
|
||||
@ -368,8 +438,9 @@ int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTro
|
||||
data->unlocked_silver = game_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_SILVER];
|
||||
data->unlocked_bronze = game_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
|
||||
|
||||
// maybe this should be 1 instead of 100?
|
||||
data->progress_percentage = 100;
|
||||
data->progress_percentage = (game_info.num_trophies > 0)
|
||||
? (game_info.unlocked_trophies * 100u) / game_info.num_trophies
|
||||
: 0;
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@ -411,12 +482,10 @@ int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTr
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
ContextKey contextkey = trophy_contexts[contextId];
|
||||
char trophy_folder[9];
|
||||
snprintf(trophy_folder, sizeof(trophy_folder), "trophy%02d", contextkey.second);
|
||||
|
||||
const auto trophy_dir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
|
||||
auto trophy_file = trophy_dir / trophy_folder / "Xml" / "TROP.XML";
|
||||
const auto& ctx = contexts_internal[contextkey];
|
||||
if (!ctx.registered)
|
||||
return ORBIS_NP_TROPHY_ERROR_NOT_REGISTERED;
|
||||
const auto& trophy_file = ctx.trophy_xml_path;
|
||||
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
|
||||
@ -450,7 +519,18 @@ int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTr
|
||||
|
||||
details->group_id = groupId;
|
||||
data->group_id = groupId;
|
||||
}
|
||||
|
||||
pugi::xml_document save_doc;
|
||||
pugi::xml_parse_result save_result = save_doc.load_file(ctx.xml_save_file.native().c_str());
|
||||
|
||||
if (!save_result) {
|
||||
LOG_ERROR(Lib_NpTrophy, "Failed to parse user trophy xml : {}", result.description());
|
||||
return ORBIS_OK;
|
||||
}
|
||||
auto save_trophyconf = save_doc.child("trophyconf");
|
||||
for (const pugi::xml_node& node : save_trophyconf.children()) {
|
||||
std::string_view node_name = node.name();
|
||||
if (node_name == "trophy") {
|
||||
bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
|
||||
std::string_view current_trophy_grade = node.attribute("ttype").value();
|
||||
@ -484,15 +564,84 @@ int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTr
|
||||
data->unlocked_silver = group_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_SILVER];
|
||||
data->unlocked_bronze = group_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
|
||||
|
||||
// maybe this should be 1 instead of 100?
|
||||
data->progress_percentage = 100;
|
||||
data->progress_percentage =
|
||||
(group_info.num_trophies > 0)
|
||||
? (group_info.unlocked_trophies * 100u) / group_info.num_trophies
|
||||
: 0;
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyGetTrophyIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
|
||||
OrbisNpTrophyId trophyId, void* buffer, u64* size) {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
if (size == nullptr)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
if (trophyId < 0 || trophyId >= ORBIS_NP_TROPHY_NUM_MAX)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
|
||||
|
||||
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
|
||||
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
|
||||
Common::SlotId contextId;
|
||||
contextId.index = context - 1;
|
||||
if (contextId.index >= trophy_contexts.size() || !trophy_contexts.is_allocated(contextId)) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
s32 handle_index = handle - 1;
|
||||
if (handle_index >= trophy_handles.size() ||
|
||||
!trophy_handles.is_allocated({static_cast<u32>(handle_index)})) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
ContextKey contextkey = trophy_contexts[contextId];
|
||||
const auto& ctx = contexts_internal[contextkey];
|
||||
if (!ctx.registered)
|
||||
return ORBIS_NP_TROPHY_ERROR_NOT_REGISTERED;
|
||||
|
||||
// Check that the trophy is unlocked and icons are only available for earned trophies.
|
||||
pugi::xml_document doc;
|
||||
if (!doc.load_file(ctx.xml_save_file.native().c_str())) {
|
||||
LOG_ERROR(Lib_NpTrophy, "Failed to open trophy XML: {}", ctx.xml_save_file.string());
|
||||
return ORBIS_NP_TROPHY_ERROR_ICON_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
bool unlocked = false;
|
||||
bool found = false;
|
||||
for (const pugi::xml_node& node : doc.child("trophyconf").children()) {
|
||||
if (std::string_view(node.name()) != "trophy")
|
||||
continue;
|
||||
if (node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID) == trophyId) {
|
||||
found = true;
|
||||
unlocked = node.attribute("unlockstate").as_bool();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
|
||||
|
||||
if (!unlocked)
|
||||
return ORBIS_NP_TROPHY_ERROR_TROPHY_NOT_UNLOCKED;
|
||||
|
||||
const std::string icon_name = fmt::format("TROP{:03d}.PNG", trophyId);
|
||||
const auto icon_path = ctx.icons_dir / icon_name;
|
||||
|
||||
Common::FS::IOFile icon(icon_path, Common::FS::FileAccessMode::Read);
|
||||
if (!icon.IsOpen()) {
|
||||
LOG_ERROR(Lib_NpTrophy, "Failed to open trophy icon: {}", icon_path.string());
|
||||
return ORBIS_NP_TROPHY_ERROR_ICON_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (buffer != nullptr) {
|
||||
ReadFile(icon, buffer, *size);
|
||||
} else {
|
||||
*size = icon.GetSize();
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -507,7 +656,7 @@ int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpT
|
||||
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
|
||||
if (trophyId >= 127)
|
||||
if (trophyId >= ORBIS_NP_TROPHY_NUM_MAX)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
|
||||
|
||||
if (details == nullptr || data == nullptr)
|
||||
@ -522,12 +671,10 @@ int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpT
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
ContextKey contextkey = trophy_contexts[contextId];
|
||||
char trophy_folder[9];
|
||||
snprintf(trophy_folder, sizeof(trophy_folder), "trophy%02d", contextkey.second);
|
||||
|
||||
const auto trophy_dir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
|
||||
auto trophy_file = trophy_dir / trophy_folder / "Xml" / "TROP.XML";
|
||||
const auto& ctx = contexts_internal[contextkey];
|
||||
if (!ctx.registered)
|
||||
return ORBIS_NP_TROPHY_ERROR_NOT_REGISTERED;
|
||||
const auto& trophy_file = ctx.trophy_xml_path;
|
||||
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
|
||||
@ -545,12 +692,34 @@ int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpT
|
||||
if (node_name == "trophy") {
|
||||
int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
|
||||
if (current_trophy_id == trophyId) {
|
||||
bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
|
||||
std::string_view current_trophy_grade = node.attribute("ttype").value();
|
||||
std::string_view current_trophy_name = node.child("name").text().as_string();
|
||||
std::string_view current_trophy_description =
|
||||
node.child("detail").text().as_string();
|
||||
|
||||
strncpy(details->name, current_trophy_name.data(), ORBIS_NP_TROPHY_NAME_MAX_SIZE);
|
||||
strncpy(details->description, current_trophy_description.data(),
|
||||
ORBIS_NP_TROPHY_DESCR_MAX_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pugi::xml_document save_doc;
|
||||
pugi::xml_parse_result save_result = save_doc.load_file(ctx.xml_save_file.native().c_str());
|
||||
|
||||
if (!save_result) {
|
||||
LOG_ERROR(Lib_NpTrophy, "Failed to parse user trophy xml : {}", result.description());
|
||||
return ORBIS_OK;
|
||||
}
|
||||
auto save_trophyconf = save_doc.child("trophyconf");
|
||||
for (const pugi::xml_node& node : save_trophyconf.children()) {
|
||||
std::string_view node_name = node.name();
|
||||
|
||||
if (node_name == "trophy") {
|
||||
int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
|
||||
if (current_trophy_id == trophyId) {
|
||||
bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
|
||||
std::string_view current_trophy_grade = node.attribute("ttype").value();
|
||||
|
||||
uint64_t current_trophy_timestamp = node.attribute("timestamp").as_ullong();
|
||||
int current_trophy_groupid = node.attribute("gid").as_int(-1);
|
||||
bool current_trophy_hidden = node.attribute("hidden").as_bool();
|
||||
@ -560,10 +729,6 @@ int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpT
|
||||
details->group_id = current_trophy_groupid;
|
||||
details->hidden = current_trophy_hidden;
|
||||
|
||||
strncpy(details->name, current_trophy_name.data(), ORBIS_NP_TROPHY_NAME_MAX_SIZE);
|
||||
strncpy(details->description, current_trophy_description.data(),
|
||||
ORBIS_NP_TROPHY_DESCR_MAX_SIZE);
|
||||
|
||||
data->trophy_id = trophyId;
|
||||
data->unlocked = current_trophy_unlockstate;
|
||||
data->timestamp.tick = current_trophy_timestamp;
|
||||
@ -579,29 +744,34 @@ s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context,
|
||||
OrbisNpTrophyFlagArray* flags, u32* count) {
|
||||
LOG_INFO(Lib_NpTrophy, "called");
|
||||
|
||||
if (flags == nullptr || count == nullptr)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
|
||||
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
|
||||
if (flags == nullptr || count == nullptr)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
ORBIS_NP_TROPHY_FLAG_ZERO(flags);
|
||||
|
||||
Common::SlotId contextId;
|
||||
contextId.index = context - 1;
|
||||
if (contextId.index >= trophy_contexts.size()) {
|
||||
if (contextId.index >= trophy_contexts.size() || !trophy_contexts.is_allocated(contextId)) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
ContextKey contextkey = trophy_contexts[contextId];
|
||||
char trophy_folder[9];
|
||||
snprintf(trophy_folder, sizeof(trophy_folder), "trophy%02d", contextkey.second);
|
||||
|
||||
const auto trophy_dir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
|
||||
auto trophy_file = trophy_dir / trophy_folder / "Xml" / "TROP.XML";
|
||||
s32 handle_index = handle - 1;
|
||||
if (handle_index >= trophy_handles.size() ||
|
||||
!trophy_handles.is_allocated({static_cast<u32>(handle_index)})) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
ContextKey contextkey = trophy_contexts[contextId];
|
||||
const auto& ctx = contexts_internal[contextkey];
|
||||
if (!ctx.registered)
|
||||
return ORBIS_NP_TROPHY_ERROR_NOT_REGISTERED;
|
||||
const auto& trophy_file = ctx.xml_save_file;
|
||||
|
||||
ORBIS_NP_TROPHY_FLAG_ZERO(flags);
|
||||
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
|
||||
@ -622,10 +792,9 @@ s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context,
|
||||
|
||||
if (node_name == "trophy") {
|
||||
num_trophies++;
|
||||
}
|
||||
|
||||
if (current_trophy_unlockstate) {
|
||||
ORBIS_NP_TROPHY_FLAG_SET(current_trophy_id, flags);
|
||||
if (current_trophy_unlockstate) {
|
||||
ORBIS_NP_TROPHY_FLAG_SET(current_trophy_id, flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -633,6 +802,200 @@ s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context,
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyRegisterContext(OrbisNpTrophyContext context,
|
||||
OrbisNpTrophyHandle handle, uint64_t options) {
|
||||
if (options != 0ull)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
|
||||
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
|
||||
Common::SlotId contextId;
|
||||
contextId.index = context - 1;
|
||||
if (contextId.index >= trophy_contexts.size() || !trophy_contexts.is_allocated(contextId)) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
s32 handle_index = handle - 1;
|
||||
if (handle_index >= trophy_handles.size() ||
|
||||
!trophy_handles.is_allocated({static_cast<u32>(handle_index)})) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
ContextKey contextkey = trophy_contexts[contextId];
|
||||
auto& ctx = contexts_internal[contextkey];
|
||||
|
||||
if (ctx.registered)
|
||||
return ORBIS_NP_TROPHY_ERROR_ALREADY_REGISTERED;
|
||||
|
||||
if (!std::filesystem::exists(ctx.trophy_xml_path))
|
||||
return ORBIS_NP_TROPHY_ERROR_TITLE_CONF_NOT_INSTALLED;
|
||||
|
||||
ctx.registered = true;
|
||||
LOG_INFO(Lib_NpTrophy, "Context {} registered", context);
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyUnlockTrophy(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
|
||||
OrbisNpTrophyId trophyId, OrbisNpTrophyId* platinumId) {
|
||||
LOG_INFO(Lib_NpTrophy, "Unlocking trophy id {}", trophyId);
|
||||
|
||||
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
|
||||
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
|
||||
if (trophyId >= ORBIS_NP_TROPHY_NUM_MAX)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
|
||||
|
||||
if (platinumId == nullptr)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
Common::SlotId contextId;
|
||||
contextId.index = context - 1;
|
||||
if (contextId.index >= trophy_contexts.size() || !trophy_contexts.is_allocated(contextId)) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
s32 handle_index = handle - 1;
|
||||
if (handle_index >= trophy_handles.size() ||
|
||||
!trophy_handles.is_allocated({static_cast<u32>(handle_index)})) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
ContextKey contextkey = trophy_contexts[contextId];
|
||||
const auto& ctx = contexts_internal[contextkey];
|
||||
if (!ctx.registered)
|
||||
return ORBIS_NP_TROPHY_ERROR_NOT_REGISTERED;
|
||||
const auto& xml_dir = ctx.xml_dir;
|
||||
const auto& trophy_file = ctx.trophy_xml_path;
|
||||
|
||||
pugi::xml_document save_doc;
|
||||
pugi::xml_parse_result save_result = save_doc.load_file(ctx.xml_save_file.native().c_str());
|
||||
|
||||
if (!save_result) {
|
||||
LOG_ERROR(Lib_NpTrophy, "Failed to parse user trophy xml : {}", save_result.description());
|
||||
return ORBIS_OK;
|
||||
}
|
||||
auto save_trophyconf = save_doc.child("trophyconf");
|
||||
for (const pugi::xml_node& node : save_trophyconf.children()) {
|
||||
std::string_view node_name = node.name();
|
||||
if (std::string_view(node.name()) != "trophy")
|
||||
continue;
|
||||
|
||||
int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
|
||||
bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
|
||||
|
||||
if (current_trophy_id == trophyId) {
|
||||
if (current_trophy_unlockstate) {
|
||||
LOG_INFO(Lib_NpTrophy, "Trophy already unlocked");
|
||||
return ORBIS_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
|
||||
|
||||
if (!result) {
|
||||
LOG_ERROR(Lib_NpTrophy, "Failed to parse trophy xml : {}", result.description());
|
||||
return ORBIS_NP_TROPHY_ERROR_TITLE_NOT_FOUND;
|
||||
}
|
||||
|
||||
*platinumId = ORBIS_NP_TROPHY_INVALID_TROPHY_ID;
|
||||
|
||||
int num_trophies = 0;
|
||||
int num_trophies_unlocked = 0;
|
||||
pugi::xml_node platinum_node;
|
||||
|
||||
// Outputs filled during the scan.
|
||||
bool trophy_found = false;
|
||||
const char* trophy_name = "";
|
||||
std::string_view trophy_type;
|
||||
std::filesystem::path trophy_icon_path;
|
||||
|
||||
auto trophyconf = doc.child("trophyconf");
|
||||
|
||||
for (pugi::xml_node& node : trophyconf.children()) {
|
||||
if (std::string_view(node.name()) != "trophy")
|
||||
continue;
|
||||
|
||||
int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
|
||||
bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
|
||||
std::string_view current_trophy_type = node.attribute("ttype").value();
|
||||
|
||||
if (current_trophy_type == "P") {
|
||||
platinum_node = node;
|
||||
if (trophyId == current_trophy_id) {
|
||||
return ORBIS_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK;
|
||||
}
|
||||
}
|
||||
|
||||
if (node.attribute("pid").as_int(-1) != ORBIS_NP_TROPHY_INVALID_TROPHY_ID) {
|
||||
num_trophies++;
|
||||
if (current_trophy_unlockstate) {
|
||||
num_trophies_unlocked++;
|
||||
}
|
||||
}
|
||||
|
||||
if (current_trophy_id == trophyId) {
|
||||
trophy_found = true;
|
||||
trophy_name = node.child("name").text().as_string();
|
||||
trophy_type = current_trophy_type;
|
||||
|
||||
const std::string icon_file = fmt::format("TROP{:03d}.PNG", current_trophy_id);
|
||||
trophy_icon_path = ctx.icons_dir / icon_file;
|
||||
}
|
||||
}
|
||||
|
||||
if (!trophy_found)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
|
||||
|
||||
// Capture timestamps once so every file gets the exact same value.
|
||||
const auto now_secs = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
const u64 trophy_timestamp = static_cast<u64>(now_secs);
|
||||
|
||||
// Decide platinum.
|
||||
bool unlock_platinum = false;
|
||||
OrbisNpTrophyId platinum_id = ORBIS_NP_TROPHY_INVALID_TROPHY_ID;
|
||||
u64 platinum_timestamp = 0;
|
||||
const char* platinum_name = "";
|
||||
std::filesystem::path platinum_icon_path;
|
||||
|
||||
if (!platinum_node.attribute("unlockstate").as_bool()) {
|
||||
if ((num_trophies - 1) == num_trophies_unlocked) {
|
||||
unlock_platinum = true;
|
||||
platinum_id = platinum_node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
|
||||
platinum_timestamp = trophy_timestamp; // same second is fine
|
||||
platinum_name = platinum_node.child("name").text().as_string();
|
||||
|
||||
const std::string plat_icon_file = fmt::format("TROP{:03d}.PNG", platinum_id);
|
||||
platinum_icon_path = ctx.icons_dir / plat_icon_file;
|
||||
|
||||
*platinumId = platinum_id;
|
||||
}
|
||||
}
|
||||
|
||||
// Queue UI notifications (only once, using the primary XML's strings).
|
||||
AddTrophyToQueue(trophy_icon_path, trophy_name, trophy_type);
|
||||
if (unlock_platinum) {
|
||||
AddTrophyToQueue(platinum_icon_path, platinum_name, "P");
|
||||
}
|
||||
|
||||
ApplyUnlockToXmlFile(ctx.xml_save_file, trophyId, trophy_timestamp, unlock_platinum,
|
||||
platinum_id, platinum_timestamp);
|
||||
LOG_INFO(Lib_NpTrophy, "Trophy {} successfully saved.", trophyId);
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyGroupArrayGetNum() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
@ -698,19 +1061,6 @@ int PS4_SYSV_ABI sceNpTrophyNumInfoGetTotal() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyRegisterContext(OrbisNpTrophyContext context,
|
||||
OrbisNpTrophyHandle handle, uint64_t options) {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
|
||||
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
|
||||
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophySetInfoGetTrophyFlagArray() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
@ -942,147 +1292,58 @@ int PS4_SYSV_ABI sceNpTrophySystemSetDbgParamInt() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyUnlockTrophy(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
|
||||
OrbisNpTrophyId trophyId, OrbisNpTrophyId* platinumId) {
|
||||
LOG_INFO(Lib_NpTrophy, "Unlocking trophy id {}", trophyId);
|
||||
int PS4_SYSV_ABI sceNpTrophyAbortHandle(OrbisNpTrophyHandle handle) {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
int PS4_SYSV_ABI sceNpTrophyCaptureScreenshot() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyDetails() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
if (trophyId >= 127)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyFlagArray() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
if (platinumId == nullptr)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyGroupArray() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
Common::SlotId contextId;
|
||||
contextId.index = context - 1;
|
||||
if (contextId.index >= trophy_contexts.size()) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
ContextKey contextkey = trophy_contexts[contextId];
|
||||
char trophy_folder[9];
|
||||
snprintf(trophy_folder, sizeof(trophy_folder), "trophy%02d", contextkey.second);
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyGroupDetails() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
const auto trophy_dir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
|
||||
auto trophy_file = trophy_dir / trophy_folder / "Xml" / "TROP.XML";
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetInfo() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetInfoInGroup() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
LOG_ERROR(Lib_NpTrophy, "Failed to parse trophy xml : {}", result.description());
|
||||
return ORBIS_OK;
|
||||
}
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetVersion() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
*platinumId = ORBIS_NP_TROPHY_INVALID_TROPHY_ID;
|
||||
|
||||
int num_trophies = 0;
|
||||
int num_trophies_unlocked = 0;
|
||||
pugi::xml_node platinum_node;
|
||||
|
||||
auto trophyconf = doc.child("trophyconf");
|
||||
|
||||
for (pugi::xml_node& node : trophyconf.children()) {
|
||||
int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
|
||||
bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
|
||||
const char* current_trophy_name = node.child("name").text().as_string();
|
||||
std::string_view current_trophy_description = node.child("detail").text().as_string();
|
||||
std::string_view current_trophy_type = node.attribute("ttype").value();
|
||||
|
||||
if (current_trophy_type == "P") {
|
||||
platinum_node = node;
|
||||
if (trophyId == current_trophy_id) {
|
||||
return ORBIS_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK;
|
||||
}
|
||||
}
|
||||
|
||||
if (std::string_view(node.name()) == "trophy") {
|
||||
if (node.attribute("pid").as_int(-1) != ORBIS_NP_TROPHY_INVALID_TROPHY_ID) {
|
||||
num_trophies++;
|
||||
if (current_trophy_unlockstate) {
|
||||
num_trophies_unlocked++;
|
||||
}
|
||||
}
|
||||
|
||||
if (current_trophy_id == trophyId) {
|
||||
if (current_trophy_unlockstate) {
|
||||
LOG_INFO(Lib_NpTrophy, "Trophy already unlocked");
|
||||
return ORBIS_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED;
|
||||
} else {
|
||||
if (node.attribute("unlockstate").empty()) {
|
||||
node.append_attribute("unlockstate") = "true";
|
||||
} else {
|
||||
node.attribute("unlockstate").set_value("true");
|
||||
}
|
||||
|
||||
auto trophyTimestamp = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
|
||||
if (node.attribute("timestamp").empty()) {
|
||||
node.append_attribute("timestamp") =
|
||||
std::to_string(trophyTimestamp).c_str();
|
||||
} else {
|
||||
node.attribute("timestamp")
|
||||
.set_value(std::to_string(trophyTimestamp).c_str());
|
||||
}
|
||||
|
||||
std::string trophy_icon_file = "TROP";
|
||||
trophy_icon_file.append(node.attribute("id").value());
|
||||
trophy_icon_file.append(".PNG");
|
||||
|
||||
std::filesystem::path current_icon_path =
|
||||
trophy_dir / trophy_folder / "Icons" / trophy_icon_file;
|
||||
|
||||
AddTrophyToQueue(current_icon_path, current_trophy_name, current_trophy_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!platinum_node.attribute("unlockstate").as_bool()) {
|
||||
if ((num_trophies - 1) == num_trophies_unlocked) {
|
||||
if (platinum_node.attribute("unlockstate").empty()) {
|
||||
platinum_node.append_attribute("unlockstate") = "true";
|
||||
} else {
|
||||
platinum_node.attribute("unlockstate").set_value("true");
|
||||
}
|
||||
|
||||
auto trophyTimestamp = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
|
||||
if (platinum_node.attribute("timestamp").empty()) {
|
||||
platinum_node.append_attribute("timestamp") =
|
||||
std::to_string(trophyTimestamp).c_str();
|
||||
} else {
|
||||
platinum_node.attribute("timestamp")
|
||||
.set_value(std::to_string(trophyTimestamp).c_str());
|
||||
}
|
||||
|
||||
int platinum_trophy_id =
|
||||
platinum_node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
|
||||
const char* platinum_trophy_name = platinum_node.child("name").text().as_string();
|
||||
|
||||
std::string platinum_icon_file = "TROP";
|
||||
platinum_icon_file.append(platinum_node.attribute("id").value());
|
||||
platinum_icon_file.append(".PNG");
|
||||
|
||||
std::filesystem::path platinum_icon_path =
|
||||
trophy_dir / trophy_folder / "Icons" / platinum_icon_file;
|
||||
|
||||
*platinumId = platinum_trophy_id;
|
||||
AddTrophyToQueue(platinum_icon_path, platinum_trophy_name, "P");
|
||||
}
|
||||
}
|
||||
|
||||
doc.save_file((trophy_dir / trophy_folder / "Xml" / "TROP.XML").native().c_str());
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyTitleDetails() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigHasGroupFeature() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
||||
@ -13,8 +13,6 @@ class SymbolsResolver;
|
||||
|
||||
namespace Libraries::Np::NpTrophy {
|
||||
|
||||
extern std::string game_serial;
|
||||
|
||||
constexpr int ORBIS_NP_TROPHY_FLAG_SETSIZE = 128;
|
||||
constexpr int ORBIS_NP_TROPHY_FLAG_BITS_SHIFT = 5;
|
||||
|
||||
|
||||
@ -47,3 +47,4 @@ constexpr int ORBIS_NP_TROPHY_ERROR_INCONSISTENT_TITLE_CONF = 0x80551628;
|
||||
constexpr int ORBIS_NP_TROPHY_ERROR_TITLE_BACKGROUND = 0x80551629;
|
||||
constexpr int ORBIS_NP_TROPHY_ERROR_SCREENSHOT_DISABLED = 0x8055162B;
|
||||
constexpr int ORBIS_NP_TROPHY_ERROR_SCREENSHOT_DISPLAY_BUFFER_NOT_IN_USE = 0x8055162D;
|
||||
constexpr int ORBIS_NP_TROPHY_ERROR_TITLE_NOT_FOUND = 0x805516C2;
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/np/np_web_api2.h"
|
||||
@ -115,10 +115,10 @@ s32 PS4_SYSV_ABI sceNpWebApi2IntInitialize2(const OrbisNpWebApi2IntInitialize2Ar
|
||||
if (args == nullptr || args->struct_size != sizeof(OrbisNpWebApi2IntInitialize2Args)) {
|
||||
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
LOG_ERROR(
|
||||
Lib_NpWebApi2,
|
||||
"(STUBBED) called, lib_http_ctx_id = {:#x}, pool_size = {:#x}, name = '{}', group = {:#x}",
|
||||
args->lib_http_ctx_id, args->pool_size, args->name, args->push_config_group);
|
||||
LOG_ERROR(Lib_NpWebApi2,
|
||||
"(STUBBED) called, lib_http_ctx_id = {:#x}, pool_size = {:#x}, name = '{}', "
|
||||
"group = {:#x}",
|
||||
args->lib_http_ctx_id, args->pool_size, args->name, args->push_config_group);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -207,7 +207,7 @@ s32 PS4_SYSV_ABI sceNpWebApi2SendMultipartRequest() {
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpWebApi2SendRequest() {
|
||||
if (!Config::getPSNSignedIn()) {
|
||||
if (!EmulatorSettings.IsPSNSignedIn()) {
|
||||
LOG_INFO(Lib_NpWebApi2, "called, returning PSN signed out.");
|
||||
return ORBIS_NP_WEBAPI2_ERROR_NOT_SIGNED_IN;
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/elf_info.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/kernel/process.h"
|
||||
#include "core/libraries/kernel/time.h"
|
||||
#include "core/libraries/network/http.h"
|
||||
@ -606,7 +606,7 @@ s32 sendRequest(s64 requestId, s32 partIndex, const void* pData, u64 dataSize, s
|
||||
unlockContext(context);
|
||||
|
||||
// Stubbing sceNpManagerIntGetSigninState call with a config check.
|
||||
if (!Config::getPSNSignedIn()) {
|
||||
if (!EmulatorSettings.IsPSNSignedIn()) {
|
||||
releaseRequest(request);
|
||||
releaseUserContext(user_context);
|
||||
releaseContext(context);
|
||||
@ -1025,7 +1025,7 @@ s32 createServicePushEventFilterInternal(
|
||||
auto& handle = context->handles[handleId];
|
||||
handle->userCount++;
|
||||
|
||||
if (pNpServiceName != nullptr && !Config::getPSNSignedIn()) {
|
||||
if (pNpServiceName != nullptr && !EmulatorSettings.IsPSNSignedIn()) {
|
||||
// Seems sceNpManagerIntGetUserList fails?
|
||||
LOG_DEBUG(Lib_NpWebApi, "Cannot create service push event while PSN is disabled");
|
||||
handle->userCount--;
|
||||
@ -1202,7 +1202,7 @@ s32 createExtendedPushEventFilterInternal(
|
||||
auto& handle = context->handles[handleId];
|
||||
handle->userCount++;
|
||||
|
||||
if (pNpServiceName != nullptr && !Config::getPSNSignedIn()) {
|
||||
if (pNpServiceName != nullptr && !EmulatorSettings.IsPSNSignedIn()) {
|
||||
// Seems sceNpManagerIntGetUserList fails?
|
||||
LOG_DEBUG(Lib_NpWebApi, "Cannot create extended push event while PSN is disabled");
|
||||
handle->userCount--;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <chrono>
|
||||
@ -7,15 +7,10 @@
|
||||
#include <mutex>
|
||||
#include <cmrc/cmrc.hpp>
|
||||
#include <imgui.h>
|
||||
|
||||
#ifdef ENABLE_QT_GUI
|
||||
#include <qt_gui/background_music_player.h>
|
||||
#endif
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/config.h"
|
||||
#include "common/path_util.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/np/trophy_ui.h"
|
||||
#include "imgui/imgui_std.h"
|
||||
|
||||
@ -36,9 +31,9 @@ TrophyUI::TrophyUI(const std::filesystem::path& trophyIconPath, const std::strin
|
||||
const std::string_view& rarity)
|
||||
: trophy_name(trophyName), trophy_type(rarity) {
|
||||
|
||||
side = Config::sideTrophy();
|
||||
side = EmulatorSettings.GetTrophyNotificationSide();
|
||||
|
||||
trophy_timer = Config::getTrophyNotificationDuration();
|
||||
trophy_timer = EmulatorSettings.GetTrophyNotificationDuration();
|
||||
|
||||
if (std::filesystem::exists(trophyIconPath)) {
|
||||
trophy_icon = RefCountedTexture::DecodePngFile(trophyIconPath);
|
||||
@ -98,7 +93,7 @@ TrophyUI::TrophyUI(const std::filesystem::path& trophyIconPath, const std::strin
|
||||
return;
|
||||
}
|
||||
|
||||
MIX_SetMasterGain(mixer, static_cast<float>(Config::getVolumeSlider() / 100.f));
|
||||
MIX_SetMasterGain(mixer, static_cast<float>(EmulatorSettings.GetVolumeSlider() / 100.f));
|
||||
auto musicPathMp3 = CustomTrophy_Dir / "trophy.mp3";
|
||||
auto musicPathWav = CustomTrophy_Dir / "trophy.wav";
|
||||
|
||||
@ -284,7 +279,7 @@ void AddTrophyToQueue(const std::filesystem::path& trophyIconPath, const std::st
|
||||
const std::string_view& rarity) {
|
||||
std::lock_guard<std::mutex> lock(queueMtx);
|
||||
|
||||
if (Config::getisTrophyPopupDisabled()) {
|
||||
if (EmulatorSettings.IsTrophyPopupDisabled()) {
|
||||
return;
|
||||
} else if (current_trophy_ui.has_value()) {
|
||||
current_trophy_ui.reset();
|
||||
|
||||
@ -1,20 +1,24 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/pad/pad_errors.h"
|
||||
#include "core/user_settings.h"
|
||||
#include "input/controller.h"
|
||||
#include "pad.h"
|
||||
|
||||
namespace Libraries::Pad {
|
||||
|
||||
using Input::GameController;
|
||||
using Input::GameControllers;
|
||||
using namespace Libraries::UserService;
|
||||
|
||||
static bool g_initialized = false;
|
||||
static bool g_opened = false;
|
||||
static std::unordered_map<OrbisUserServiceUserId, s32> user_id_pad_handle_map{};
|
||||
static constexpr s32 tv_remote_handle = 5;
|
||||
|
||||
int PS4_SYSV_ABI scePadClose(s32 handle) {
|
||||
LOG_ERROR(Lib_Pad, "(STUBBED) called");
|
||||
@ -30,8 +34,8 @@ int PS4_SYSV_ABI scePadDeviceClassGetExtendedInformation(
|
||||
s32 handle, OrbisPadDeviceClassExtendedInformation* pExtInfo) {
|
||||
LOG_ERROR(Lib_Pad, "(STUBBED) called");
|
||||
std::memset(pExtInfo, 0, sizeof(OrbisPadDeviceClassExtendedInformation));
|
||||
if (Config::getUseSpecialPad()) {
|
||||
pExtInfo->deviceClass = (OrbisPadDeviceClass)Config::getSpecialPadClass();
|
||||
if (EmulatorSettings.IsUsingSpecialPad()) {
|
||||
pExtInfo->deviceClass = (OrbisPadDeviceClass)EmulatorSettings.GetSpecialPadClass();
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@ -107,9 +111,9 @@ int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerIn
|
||||
return ORBIS_OK;
|
||||
}
|
||||
pInfo->connected = true;
|
||||
if (Config::getUseSpecialPad()) {
|
||||
if (EmulatorSettings.IsUsingSpecialPad()) {
|
||||
pInfo->connectionType = ORBIS_PAD_PORT_TYPE_SPECIAL;
|
||||
pInfo->deviceClass = (OrbisPadDeviceClass)Config::getSpecialPadClass();
|
||||
pInfo->deviceClass = (OrbisPadDeviceClass)EmulatorSettings.GetSpecialPadClass();
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@ -156,11 +160,16 @@ int PS4_SYSV_ABI scePadGetHandle(Libraries::UserService::OrbisUserServiceUserId
|
||||
if (!g_initialized) {
|
||||
return ORBIS_PAD_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (userId == -1 || !g_opened) {
|
||||
if (userId == -1) {
|
||||
return ORBIS_PAD_ERROR_DEVICE_NO_HANDLE;
|
||||
}
|
||||
LOG_DEBUG(Lib_Pad, "(DUMMY) called");
|
||||
return 1;
|
||||
auto it = user_id_pad_handle_map.find(userId);
|
||||
if (it == user_id_pad_handle_map.end()) {
|
||||
return ORBIS_PAD_ERROR_DEVICE_NO_HANDLE;
|
||||
}
|
||||
s32 pad_handle = it->second;
|
||||
LOG_DEBUG(Lib_Pad, "called, userid: {}, out pad handle: {}", userId, pad_handle);
|
||||
return pad_handle;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadGetIdleCount() {
|
||||
@ -168,8 +177,19 @@ int PS4_SYSV_ABI scePadGetIdleCount() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadGetInfo() {
|
||||
LOG_ERROR(Lib_Pad, "(STUBBED) called");
|
||||
int PS4_SYSV_ABI scePadGetInfo(u32* data) {
|
||||
LOG_WARNING(Lib_Pad, "(DUMMY) called");
|
||||
if (!data) {
|
||||
return ORBIS_PAD_ERROR_INVALID_ARG;
|
||||
}
|
||||
data[0] = 0x1; // index but starting from one?
|
||||
data[1] = 0x0; // index?
|
||||
data[2] = 1; // pad handle
|
||||
data[3] = 0x0101; // ???
|
||||
data[4] = 0x0; // ?
|
||||
data[5] = 0x0; // ?
|
||||
data[6] = 0x00ff0000; // colour(?)
|
||||
data[7] = 0x0; // ?
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -254,34 +274,61 @@ int PS4_SYSV_ABI scePadOpen(Libraries::UserService::OrbisUserServiceUserId userI
|
||||
if (!g_initialized) {
|
||||
return ORBIS_PAD_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (userId == -1) {
|
||||
return ORBIS_PAD_ERROR_DEVICE_NO_HANDLE;
|
||||
if (userId < 0) {
|
||||
return ORBIS_DEVICE_SERVICE_ERROR_INVALID_USER;
|
||||
}
|
||||
if (Config::getUseSpecialPad()) {
|
||||
if (userId == ORBIS_USER_SERVICE_USER_ID_SYSTEM) {
|
||||
if (type == ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL) {
|
||||
LOG_INFO(Lib_Pad, "Opened a TV remote device");
|
||||
user_id_pad_handle_map[ORBIS_USER_SERVICE_USER_ID_SYSTEM] = tv_remote_handle;
|
||||
return tv_remote_handle;
|
||||
}
|
||||
return ORBIS_DEVICE_SERVICE_ERROR_INVALID_USER;
|
||||
}
|
||||
if (type == ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL) {
|
||||
return ORBIS_PAD_ERROR_INVALID_ARG;
|
||||
}
|
||||
if (EmulatorSettings.IsUsingSpecialPad()) {
|
||||
if (type != ORBIS_PAD_PORT_TYPE_SPECIAL)
|
||||
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
|
||||
} else {
|
||||
if (type != ORBIS_PAD_PORT_TYPE_STANDARD && type != ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL)
|
||||
if (type != ORBIS_PAD_PORT_TYPE_STANDARD)
|
||||
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
|
||||
}
|
||||
LOG_INFO(Lib_Pad, "(DUMMY) called user_id = {} type = {} index = {}", userId, type, index);
|
||||
g_opened = true;
|
||||
scePadResetLightBar(userId);
|
||||
scePadResetOrientation(userId);
|
||||
return 1; // dummy
|
||||
auto u = UserManagement.GetUserByID(userId);
|
||||
if (!u) {
|
||||
return ORBIS_DEVICE_SERVICE_ERROR_USER_NOT_LOGIN;
|
||||
}
|
||||
s32 pad_handle = u->player_index;
|
||||
LOG_INFO(Lib_Pad, "called user_id = {} type = {} index = {}, pad_handle = {}", userId, type,
|
||||
index, pad_handle);
|
||||
scePadResetLightBar(pad_handle);
|
||||
scePadResetOrientation(pad_handle);
|
||||
user_id_pad_handle_map[userId] = pad_handle;
|
||||
return pad_handle;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadOpenExt(Libraries::UserService::OrbisUserServiceUserId userId, s32 type,
|
||||
s32 index, const OrbisPadOpenExtParam* pParam) {
|
||||
LOG_ERROR(Lib_Pad, "(STUBBED) called");
|
||||
if (Config::getUseSpecialPad()) {
|
||||
if (EmulatorSettings.IsUsingSpecialPad()) {
|
||||
if (type != ORBIS_PAD_PORT_TYPE_SPECIAL)
|
||||
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
|
||||
} else {
|
||||
if (type != ORBIS_PAD_PORT_TYPE_STANDARD && type != ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL)
|
||||
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
|
||||
}
|
||||
return 1; // dummy
|
||||
auto u = UserManagement.GetUserByID(userId);
|
||||
if (!u) {
|
||||
return ORBIS_DEVICE_SERVICE_ERROR_USER_NOT_LOGIN;
|
||||
}
|
||||
s32 pad_handle = u->player_index;
|
||||
LOG_INFO(Lib_Pad, "called user_id = {} type = {} index = {}, pad_handle = {}", userId, type,
|
||||
index, pad_handle);
|
||||
scePadResetLightBar(pad_handle);
|
||||
scePadResetOrientation(pad_handle);
|
||||
user_id_pad_handle_map[userId] = pad_handle;
|
||||
return pad_handle;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadOpenExt2() {
|
||||
@ -294,8 +341,8 @@ int PS4_SYSV_ABI scePadOutputReport() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int ProcessStates(s32 handle, OrbisPadData* pData, Input::State* states, s32 num, bool connected,
|
||||
u32 connected_count) {
|
||||
int ProcessStates(s32 handle, OrbisPadData* pData, Input::GameController& controller,
|
||||
Input::State* states, s32 num, bool connected, u32 connected_count) {
|
||||
if (!connected) {
|
||||
pData[0] = {};
|
||||
pData[0].orientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
@ -319,61 +366,57 @@ int ProcessStates(s32 handle, OrbisPadData* pData, Input::State* states, s32 num
|
||||
pData[i].angularVelocity.z = states[i].angularVelocity.z;
|
||||
pData[i].orientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
|
||||
auto* controller = Common::Singleton<GameController>::Instance();
|
||||
const auto* engine = controller->GetEngine();
|
||||
if (engine && handle == 1) {
|
||||
const auto gyro_poll_rate = engine->GetAccelPollRate();
|
||||
if (gyro_poll_rate != 0.0f) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
float deltaTime = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
now - controller->GetLastUpdate())
|
||||
.count() /
|
||||
1000000.0f;
|
||||
controller->SetLastUpdate(now);
|
||||
Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation();
|
||||
Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity,
|
||||
deltaTime, lastOrientation, outputOrientation);
|
||||
pData[i].orientation = outputOrientation;
|
||||
controller->SetLastOrientation(outputOrientation);
|
||||
}
|
||||
const auto gyro_poll_rate = controller.accel_poll_rate;
|
||||
if (gyro_poll_rate != 0.0f) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
float deltaTime = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
now - controller.GetLastUpdate())
|
||||
.count() /
|
||||
1000000.0f;
|
||||
controller.SetLastUpdate(now);
|
||||
Libraries::Pad::OrbisFQuaternion lastOrientation = controller.GetLastOrientation();
|
||||
Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
GameControllers::CalculateOrientation(pData->acceleration, pData->angularVelocity,
|
||||
deltaTime, lastOrientation, outputOrientation);
|
||||
pData[i].orientation = outputOrientation;
|
||||
controller.SetLastOrientation(outputOrientation);
|
||||
}
|
||||
pData[i].touchData.touchNum =
|
||||
(states[i].touchpad[0].state ? 1 : 0) + (states[i].touchpad[1].state ? 1 : 0);
|
||||
|
||||
if (handle == 1) {
|
||||
if (controller->GetTouchCount() >= 127) {
|
||||
controller->SetTouchCount(0);
|
||||
if (controller.GetTouchCount() >= 127) {
|
||||
controller.SetTouchCount(0);
|
||||
}
|
||||
|
||||
if (controller->GetSecondaryTouchCount() >= 127) {
|
||||
controller->SetSecondaryTouchCount(0);
|
||||
if (controller.GetSecondaryTouchCount() >= 127) {
|
||||
controller.SetSecondaryTouchCount(0);
|
||||
}
|
||||
|
||||
if (pData->touchData.touchNum == 1 && controller->GetPreviousTouchNum() == 0) {
|
||||
controller->SetTouchCount(controller->GetTouchCount() + 1);
|
||||
controller->SetSecondaryTouchCount(controller->GetTouchCount());
|
||||
} else if (pData->touchData.touchNum == 2 && controller->GetPreviousTouchNum() == 1) {
|
||||
controller->SetSecondaryTouchCount(controller->GetSecondaryTouchCount() + 1);
|
||||
} else if (pData->touchData.touchNum == 0 && controller->GetPreviousTouchNum() > 0) {
|
||||
if (controller->GetTouchCount() < controller->GetSecondaryTouchCount()) {
|
||||
controller->SetTouchCount(controller->GetSecondaryTouchCount());
|
||||
if (pData->touchData.touchNum == 1 && controller.GetPreviousTouchNum() == 0) {
|
||||
controller.SetTouchCount(controller.GetTouchCount() + 1);
|
||||
controller.SetSecondaryTouchCount(controller.GetTouchCount());
|
||||
} else if (pData->touchData.touchNum == 2 && controller.GetPreviousTouchNum() == 1) {
|
||||
controller.SetSecondaryTouchCount(controller.GetSecondaryTouchCount() + 1);
|
||||
} else if (pData->touchData.touchNum == 0 && controller.GetPreviousTouchNum() > 0) {
|
||||
if (controller.GetTouchCount() < controller.GetSecondaryTouchCount()) {
|
||||
controller.SetTouchCount(controller.GetSecondaryTouchCount());
|
||||
} else {
|
||||
if (controller->WasSecondaryTouchReset()) {
|
||||
controller->SetTouchCount(controller->GetSecondaryTouchCount());
|
||||
controller->UnsetSecondaryTouchResetBool();
|
||||
if (controller.WasSecondaryTouchReset()) {
|
||||
controller.SetTouchCount(controller.GetSecondaryTouchCount());
|
||||
controller.UnsetSecondaryTouchResetBool();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
controller->SetPreviousTouchNum(pData->touchData.touchNum);
|
||||
controller.SetPreviousTouchNum(pData->touchData.touchNum);
|
||||
|
||||
if (pData->touchData.touchNum == 1) {
|
||||
states[i].touchpad[0].ID = controller->GetTouchCount();
|
||||
states[i].touchpad[0].ID = controller.GetTouchCount();
|
||||
states[i].touchpad[1].ID = 0;
|
||||
} else if (pData->touchData.touchNum == 2) {
|
||||
states[i].touchpad[0].ID = controller->GetTouchCount();
|
||||
states[i].touchpad[1].ID = controller->GetSecondaryTouchCount();
|
||||
states[i].touchpad[0].ID = controller.GetTouchCount();
|
||||
states[i].touchpad[1].ID = controller.GetSecondaryTouchCount();
|
||||
}
|
||||
} else {
|
||||
states[i].touchpad[0].ID = 1;
|
||||
@ -397,16 +440,18 @@ int ProcessStates(s32 handle, OrbisPadData* pData, Input::State* states, s32 num
|
||||
|
||||
int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) {
|
||||
LOG_TRACE(Lib_Pad, "called");
|
||||
if (handle < 1) {
|
||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
int connected_count = 0;
|
||||
bool connected = false;
|
||||
std::vector<Input::State> states(64);
|
||||
auto* controller = Common::Singleton<GameController>::Instance();
|
||||
const auto* engine = controller->GetEngine();
|
||||
int ret_num = controller->ReadStates(states.data(), num, &connected, &connected_count);
|
||||
return ProcessStates(handle, pData, states.data(), ret_num, connected, connected_count);
|
||||
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
|
||||
if (!controller_id) {
|
||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
auto& controllers = *Common::Singleton<GameControllers>::Instance();
|
||||
auto& controller = *controllers[*controller_id];
|
||||
int ret_num = controller.ReadStates(states.data(), num, &connected, &connected_count);
|
||||
return ProcessStates(handle, pData, controller, states.data(), ret_num, connected,
|
||||
connected_count);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadReadBlasterForTracker() {
|
||||
@ -430,17 +475,18 @@ int PS4_SYSV_ABI scePadReadHistory() {
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) {
|
||||
LOG_TRACE(Lib_Pad, "called");
|
||||
if (handle < 1) {
|
||||
LOG_TRACE(Lib_Pad, "handle: {}", handle);
|
||||
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
|
||||
if (!controller_id) {
|
||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
auto* controller = Common::Singleton<GameController>::Instance();
|
||||
const auto* engine = controller->GetEngine();
|
||||
auto& controllers = *Common::Singleton<GameControllers>::Instance();
|
||||
auto& controller = *controllers[*controller_id];
|
||||
int connected_count = 0;
|
||||
bool connected = false;
|
||||
Input::State state;
|
||||
controller->ReadState(&state, &connected, &connected_count);
|
||||
ProcessStates(handle, pData, &state, 1, connected, connected_count);
|
||||
controller.ReadState(&state, &connected, &connected_count);
|
||||
ProcessStates(handle, pData, controller, &state, 1, connected, connected_count);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -450,13 +496,30 @@ int PS4_SYSV_ABI scePadReadStateExt() {
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadResetLightBar(s32 handle) {
|
||||
LOG_INFO(Lib_Pad, "(DUMMY) called");
|
||||
if (handle != 1) {
|
||||
LOG_DEBUG(Lib_Pad, "called, handle: {}", handle);
|
||||
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
|
||||
if (!controller_id) {
|
||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
auto* controller = Common::Singleton<GameController>::Instance();
|
||||
int* rgb = Config::GetControllerCustomColor();
|
||||
controller->SetLightBarRGB(rgb[0], rgb[1], rgb[2]);
|
||||
auto& controllers = *Common::Singleton<GameControllers>::Instance();
|
||||
s32 colour_index = UserManagement.GetUserByPlayerIndex(handle)->user_color - 1;
|
||||
Input::Colour colour{255, 0, 0};
|
||||
if (colour_index >= 0 && colour_index <= 3) {
|
||||
static constexpr Input::Colour colours[4]{
|
||||
{0, 0, 255}, // blue
|
||||
{255, 0, 0}, // red
|
||||
{0, 255, 0}, // green
|
||||
{255, 0, 255}, // pink
|
||||
};
|
||||
colour = colours[colour_index];
|
||||
} else {
|
||||
LOG_ERROR(Lib_Pad, "Invalid user colour value {} for controller {}, falling back to blue",
|
||||
colour_index, handle);
|
||||
}
|
||||
if (auto oc = GameControllers::GetControllerCustomColor(*controller_id)) {
|
||||
colour = *oc;
|
||||
}
|
||||
controllers[*controller_id]->SetLightBarRGB(colour.r, colour.g, colour.b);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -473,14 +536,15 @@ int PS4_SYSV_ABI scePadResetLightBarAllByPortType() {
|
||||
int PS4_SYSV_ABI scePadResetOrientation(s32 handle) {
|
||||
LOG_INFO(Lib_Pad, "scePadResetOrientation called handle = {}", handle);
|
||||
|
||||
if (handle != 1) {
|
||||
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
|
||||
if (!controller_id) {
|
||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
auto* controller = Common::Singleton<GameController>::Instance();
|
||||
auto& controllers = *Common::Singleton<GameControllers>::Instance();
|
||||
Libraries::Pad::OrbisFQuaternion defaultOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
controller->SetLastOrientation(defaultOrientation);
|
||||
controller->SetLastUpdate(std::chrono::steady_clock::now());
|
||||
controllers[*controller_id]->SetLastOrientation(defaultOrientation);
|
||||
controllers[*controller_id]->SetLastUpdate(std::chrono::steady_clock::now());
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@ -526,7 +590,11 @@ int PS4_SYSV_ABI scePadSetForceIntercepted() {
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pParam) {
|
||||
if (Config::GetOverrideControllerColor()) {
|
||||
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
|
||||
if (!controller_id) {
|
||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
if (GameControllers::GetControllerCustomColor(*controller_id)) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
if (pParam != nullptr) {
|
||||
@ -538,8 +606,8 @@ int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pPar
|
||||
return ORBIS_PAD_ERROR_INVALID_LIGHTBAR_SETTING;
|
||||
}
|
||||
|
||||
auto* controller = Common::Singleton<GameController>::Instance();
|
||||
controller->SetLightBarRGB(pParam->r, pParam->g, pParam->b);
|
||||
auto& controllers = *Common::Singleton<GameControllers>::Instance();
|
||||
controllers[*controller_id]->SetLightBarRGB(pParam->r, pParam->g, pParam->b);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
return ORBIS_PAD_ERROR_INVALID_ARG;
|
||||
@ -555,8 +623,14 @@ int PS4_SYSV_ABI scePadSetLightBarBlinking() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadSetLightBarForTracker() {
|
||||
LOG_ERROR(Lib_Pad, "(STUBBED) called");
|
||||
int PS4_SYSV_ABI scePadSetLightBarForTracker(s32 handle, const OrbisPadLightBarParam* pParam) {
|
||||
LOG_INFO(Lib_Pad, "called, r: {} g: {} b: {}", pParam->r, pParam->g, pParam->b);
|
||||
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
|
||||
if (!controller_id) {
|
||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
auto& controllers = *Common::Singleton<GameControllers>::Instance();
|
||||
controllers[*controller_id]->SetLightBarRGB(pParam->r, pParam->g, pParam->b);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -603,11 +677,15 @@ int PS4_SYSV_ABI scePadSetUserColor() {
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadSetVibration(s32 handle, const OrbisPadVibrationParam* pParam) {
|
||||
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
|
||||
if (!controller_id) {
|
||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
if (pParam != nullptr) {
|
||||
LOG_DEBUG(Lib_Pad, "scePadSetVibration called handle = {} data = {} , {}", handle,
|
||||
pParam->smallMotor, pParam->largeMotor);
|
||||
auto* controller = Common::Singleton<GameController>::Instance();
|
||||
controller->SetVibration(pParam->smallMotor, pParam->largeMotor);
|
||||
auto& controllers = *Common::Singleton<GameControllers>::Instance();
|
||||
controllers[*controller_id]->SetVibration(pParam->smallMotor, pParam->largeMotor);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
return ORBIS_PAD_ERROR_INVALID_ARG;
|
||||
|
||||
@ -280,7 +280,7 @@ int PS4_SYSV_ABI scePadGetFeatureReport();
|
||||
int PS4_SYSV_ABI scePadGetHandle(Libraries::UserService::OrbisUserServiceUserId userId, s32 type,
|
||||
s32 index);
|
||||
int PS4_SYSV_ABI scePadGetIdleCount();
|
||||
int PS4_SYSV_ABI scePadGetInfo();
|
||||
int PS4_SYSV_ABI scePadGetInfo(u32* data);
|
||||
int PS4_SYSV_ABI scePadGetInfoByPortType();
|
||||
int PS4_SYSV_ABI scePadGetLicenseControllerInformation();
|
||||
int PS4_SYSV_ABI scePadGetMotionSensorPosition();
|
||||
@ -324,7 +324,7 @@ int PS4_SYSV_ABI scePadSetForceIntercepted();
|
||||
int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pParam);
|
||||
int PS4_SYSV_ABI scePadSetLightBarBaseBrightness();
|
||||
int PS4_SYSV_ABI scePadSetLightBarBlinking();
|
||||
int PS4_SYSV_ABI scePadSetLightBarForTracker();
|
||||
int PS4_SYSV_ABI scePadSetLightBarForTracker(s32 handle, const OrbisPadLightBarParam* pParam);
|
||||
int PS4_SYSV_ABI scePadSetLoginUserNumber();
|
||||
int PS4_SYSV_ABI scePadSetMotionSensorState(s32 handle, bool bEnable);
|
||||
int PS4_SYSV_ABI scePadSetProcessFocus();
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@ -20,3 +20,6 @@ constexpr int ORBIS_PAD_ERROR_INVALID_BUFFER_LENGTH = 0x80920102;
|
||||
constexpr int ORBIS_PAD_ERROR_INVALID_REPORT_LENGTH = 0x80920103;
|
||||
constexpr int ORBIS_PAD_ERROR_INVALID_REPORT_ID = 0x80920104;
|
||||
constexpr int ORBIS_PAD_ERROR_SEND_AGAIN = 0x80920105;
|
||||
|
||||
constexpr s32 ORBIS_DEVICE_SERVICE_ERROR_INVALID_USER = 0x809b0001;
|
||||
constexpr s32 ORBIS_DEVICE_SERVICE_ERROR_USER_NOT_LOGIN = 0x809b0081;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <iostream>
|
||||
@ -6,9 +6,9 @@
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/config.h"
|
||||
#include "common/path_util.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/file_sys/fs.h"
|
||||
#include "save_backup.h"
|
||||
#include "save_instance.h"
|
||||
@ -48,12 +48,13 @@ namespace Libraries::SaveData {
|
||||
|
||||
fs::path SaveInstance::MakeTitleSavePath(Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
std::string_view game_serial) {
|
||||
return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial;
|
||||
return EmulatorSettings.GetHomeDir() / std::to_string(user_id) / "savedata" / game_serial;
|
||||
}
|
||||
|
||||
fs::path SaveInstance::MakeDirSavePath(Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
std::string_view game_serial, std::string_view dir_name) {
|
||||
return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial / dir_name;
|
||||
fs::path SaveInstance::MakeDirSavePath(OrbisUserServiceUserId user_id, std::string_view game_serial,
|
||||
std::string_view dir_name) {
|
||||
return EmulatorSettings.GetHomeDir() / std::to_string(user_id) / "savedata" / game_serial /
|
||||
dir_name;
|
||||
}
|
||||
|
||||
uint64_t SaveInstance::GetMaxBlockFromSFO(const PSF& psf) {
|
||||
@ -71,7 +72,7 @@ fs::path SaveInstance::GetParamSFOPath(const fs::path& dir_path) {
|
||||
|
||||
void SaveInstance::SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name,
|
||||
std::string game_serial) {
|
||||
int locale = Config::GetLanguage();
|
||||
int locale = EmulatorSettings.GetConsoleLanguage();
|
||||
if (!default_title.contains(locale)) {
|
||||
locale = 1; // default to en_US if not found
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
#include <span>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
@ -8,13 +9,13 @@
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/config.h"
|
||||
#include "common/cstring.h"
|
||||
#include "common/elf_info.h"
|
||||
#include "common/enum.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/path_util.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/file_format/psf.h"
|
||||
#include "core/file_sys/fs.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
@ -441,7 +442,8 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
|
||||
LOG_INFO(Lib_SaveData, "called with invalid block size");
|
||||
}
|
||||
|
||||
const auto root_save = Config::GetSaveDataPath();
|
||||
const auto root_save =
|
||||
EmulatorSettings.GetHomeDir() / std::to_string(mount_info->userId) / "savedata";
|
||||
fs::create_directories(root_save);
|
||||
const auto available = fs::space(root_save).available;
|
||||
|
||||
@ -489,7 +491,9 @@ static Error Umount(const OrbisSaveDataMountPoint* mountPoint, bool call_backup
|
||||
return Error::PARAMETER;
|
||||
}
|
||||
LOG_DEBUG(Lib_SaveData, "Umount mountPoint:{}", mountPoint->data.to_view());
|
||||
const std::string_view mount_point_str{mountPoint->data};
|
||||
|
||||
std::string mount_point_str = mountPoint->data.to_string();
|
||||
|
||||
for (auto& instance : g_mount_slots) {
|
||||
if (instance.has_value()) {
|
||||
const auto& slot_name = instance->GetMountPoint();
|
||||
|
||||
204
src/core/libraries/sysmodule/sysmodule.cpp
Normal file
204
src/core/libraries/sysmodule/sysmodule.cpp
Normal file
@ -0,0 +1,204 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "common/elf_info.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/kernel/orbis_error.h"
|
||||
#include "core/libraries/kernel/process.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/sysmodule/sysmodule.h"
|
||||
#include "core/libraries/sysmodule/sysmodule_error.h"
|
||||
#include "core/libraries/sysmodule/sysmodule_internal.h"
|
||||
#include "core/linker.h"
|
||||
|
||||
namespace Libraries::SysModule {
|
||||
|
||||
static std::mutex g_mutex{};
|
||||
|
||||
s32 PS4_SYSV_ABI sceSysmoduleGetModuleHandleInternal(OrbisSysModuleInternal id, s32* handle) {
|
||||
LOG_INFO(Lib_SysModule, "called");
|
||||
if ((id & 0x7fffffff) == 0) {
|
||||
return ORBIS_SYSMODULE_INVALID_ID;
|
||||
}
|
||||
|
||||
std::scoped_lock lk{g_mutex};
|
||||
return getModuleHandle(id, handle);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceSysmoduleGetModuleInfoForUnwind(VAddr addr, s32 flags,
|
||||
Kernel::OrbisModuleInfoForUnwind* info) {
|
||||
LOG_TRACE(Lib_SysModule, "sceSysmoduleGetModuleInfoForUnwind called");
|
||||
s32 res = Kernel::sceKernelGetModuleInfoForUnwind(addr, flags, info);
|
||||
if (res != ORBIS_OK) {
|
||||
return res;
|
||||
}
|
||||
|
||||
if (shouldHideName(info->name.data())) {
|
||||
std::ranges::fill(info->name, '\0');
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceSysmoduleIsCalledFromSysModule() {
|
||||
LOG_ERROR(Lib_SysModule, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceSysmoduleIsCameraPreloaded() {
|
||||
LOG_ERROR(Lib_SysModule, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceSysmoduleIsLoaded(OrbisSysModule id) {
|
||||
if (id == 0) {
|
||||
return ORBIS_SYSMODULE_INVALID_ID;
|
||||
}
|
||||
|
||||
std::scoped_lock lk{g_mutex};
|
||||
return getModuleHandle(id, nullptr);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceSysmoduleIsLoadedInternal(OrbisSysModuleInternal id) {
|
||||
if ((id & 0x7fffffff) == 0) {
|
||||
return ORBIS_SYSMODULE_INVALID_ID;
|
||||
}
|
||||
|
||||
std::scoped_lock lk{g_mutex};
|
||||
return getModuleHandle(id, nullptr);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceSysmoduleLoadModule(OrbisSysModule id) {
|
||||
LOG_INFO(Lib_SysModule, "called, id = {:#x}", id);
|
||||
s32 result = validateModuleId(id);
|
||||
if (result < ORBIS_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Only locks for internal loadModule call.
|
||||
{
|
||||
std::scoped_lock lk{g_mutex};
|
||||
result = loadModule(id, 0, nullptr, nullptr);
|
||||
}
|
||||
|
||||
if (result == ORBIS_KERNEL_ERROR_ESTART) {
|
||||
s32 sdk_ver = 0;
|
||||
result = Kernel::sceKernelGetCompiledSdkVersion(&sdk_ver);
|
||||
if (sdk_ver < Common::ElfInfo::FW_115 || result != ORBIS_OK) {
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
} else {
|
||||
return ORBIS_KERNEL_ERROR_ESTART;
|
||||
}
|
||||
}
|
||||
|
||||
// The real library has some weird workaround for CUSA01478 and CUSA01495 here.
|
||||
// Unless this is proven necessary, I don't plan to handle this.
|
||||
return result;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceSysmoduleLoadModuleByNameInternal() {
|
||||
LOG_ERROR(Lib_SysModule, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceSysmoduleLoadModuleInternal(OrbisSysModuleInternal id) {
|
||||
LOG_INFO(Lib_SysModule, "called, id = {:#x}", id);
|
||||
s32 result = validateModuleId(id);
|
||||
if (result < ORBIS_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// This specific module ID is loaded unlocked.
|
||||
if (id == 0x80000039) {
|
||||
return loadModule(id, 0, nullptr, nullptr);
|
||||
}
|
||||
std::scoped_lock lk{g_mutex};
|
||||
return loadModule(id, 0, nullptr, nullptr);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceSysmoduleLoadModuleInternalWithArg(OrbisSysModuleInternal id, s32 argc,
|
||||
const void* argv, u64 unk, s32* res_out) {
|
||||
LOG_INFO(Lib_SysModule, "called, id = {:#x}", id);
|
||||
s32 result = validateModuleId(id);
|
||||
if (result < ORBIS_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (unk != 0) {
|
||||
return ORBIS_SYSMODULE_INVALID_ID;
|
||||
}
|
||||
|
||||
std::scoped_lock lk{g_mutex};
|
||||
return loadModule(id, argc, argv, res_out);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceSysmoduleMapLibcForLibkernel() {
|
||||
LOG_ERROR(Lib_SysModule, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceSysmodulePreloadModuleForLibkernel() {
|
||||
LOG_DEBUG(Lib_SysModule, "called");
|
||||
return preloadModulesForLibkernel();
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceSysmoduleUnloadModule(OrbisSysModule id) {
|
||||
LOG_ERROR(Lib_SysModule, "(STUBBED) called, id = {:#x}", id);
|
||||
if (id == 0) {
|
||||
return ORBIS_SYSMODULE_INVALID_ID;
|
||||
}
|
||||
|
||||
std::scoped_lock lk{g_mutex};
|
||||
return unloadModule(id, 0, nullptr, nullptr, false);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceSysmoduleUnloadModuleByNameInternal() {
|
||||
LOG_ERROR(Lib_SysModule, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceSysmoduleUnloadModuleInternal() {
|
||||
LOG_ERROR(Lib_SysModule, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceSysmoduleUnloadModuleInternalWithArg() {
|
||||
LOG_ERROR(Lib_SysModule, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("D8cuU4d72xM", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmoduleGetModuleHandleInternal);
|
||||
LIB_FUNCTION("4fU5yvOkVG4", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmoduleGetModuleInfoForUnwind);
|
||||
LIB_FUNCTION("ctfO7dQ7geg", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmoduleIsCalledFromSysModule);
|
||||
LIB_FUNCTION("no6T3EfiS3E", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmoduleIsCameraPreloaded);
|
||||
LIB_FUNCTION("fMP5NHUOaMk", "libSceSysmodule", 1, "libSceSysmodule", sceSysmoduleIsLoaded);
|
||||
LIB_FUNCTION("ynFKQ5bfGks", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmoduleIsLoadedInternal);
|
||||
LIB_FUNCTION("g8cM39EUZ6o", "libSceSysmodule", 1, "libSceSysmodule", sceSysmoduleLoadModule);
|
||||
LIB_FUNCTION("CU8m+Qs+HN4", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmoduleLoadModuleByNameInternal);
|
||||
LIB_FUNCTION("39iV5E1HoCk", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmoduleLoadModuleInternal);
|
||||
LIB_FUNCTION("hHrGoGoNf+s", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmoduleLoadModuleInternalWithArg);
|
||||
LIB_FUNCTION("lZ6RvVl0vo0", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmoduleMapLibcForLibkernel);
|
||||
LIB_FUNCTION("DOO+zuW1lrE", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmodulePreloadModuleForLibkernel);
|
||||
LIB_FUNCTION("eR2bZFAAU0Q", "libSceSysmodule", 1, "libSceSysmodule", sceSysmoduleUnloadModule);
|
||||
LIB_FUNCTION("vpTHmA6Knvg", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmoduleUnloadModuleByNameInternal);
|
||||
LIB_FUNCTION("vXZhrtJxkGc", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmoduleUnloadModuleInternal);
|
||||
LIB_FUNCTION("aKa6YfBKZs4", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmoduleUnloadModuleInternalWithArg);
|
||||
};
|
||||
|
||||
} // namespace Libraries::SysModule
|
||||
38
src/core/libraries/sysmodule/sysmodule.h
Normal file
38
src/core/libraries/sysmodule/sysmodule.h
Normal file
@ -0,0 +1,38 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/kernel/process.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
namespace Libraries::SysModule {
|
||||
|
||||
using OrbisSysModule = u16;
|
||||
using OrbisSysModuleInternal = u32;
|
||||
|
||||
s32 PS4_SYSV_ABI sceSysmoduleGetModuleHandleInternal(OrbisSysModuleInternal id, s32* handle);
|
||||
s32 PS4_SYSV_ABI sceSysmoduleGetModuleInfoForUnwind(VAddr addr, s32 flags,
|
||||
Kernel::OrbisModuleInfoForUnwind* info);
|
||||
s32 PS4_SYSV_ABI sceSysmoduleIsCalledFromSysModule();
|
||||
s32 PS4_SYSV_ABI sceSysmoduleIsCameraPreloaded();
|
||||
s32 PS4_SYSV_ABI sceSysmoduleIsLoaded(OrbisSysModule id);
|
||||
s32 PS4_SYSV_ABI sceSysmoduleIsLoadedInternal(OrbisSysModuleInternal id);
|
||||
s32 PS4_SYSV_ABI sceSysmoduleLoadModule(OrbisSysModule id);
|
||||
s32 PS4_SYSV_ABI sceSysmoduleLoadModuleByNameInternal();
|
||||
s32 PS4_SYSV_ABI sceSysmoduleLoadModuleInternal(OrbisSysModuleInternal id);
|
||||
s32 PS4_SYSV_ABI sceSysmoduleLoadModuleInternalWithArg(OrbisSysModuleInternal id, s32 argc,
|
||||
const void* argv, u64 unk, s32* res_out);
|
||||
s32 PS4_SYSV_ABI sceSysmoduleMapLibcForLibkernel();
|
||||
s32 PS4_SYSV_ABI sceSysmodulePreloadModuleForLibkernel();
|
||||
s32 PS4_SYSV_ABI sceSysmoduleUnloadModule(OrbisSysModule id);
|
||||
s32 PS4_SYSV_ABI sceSysmoduleUnloadModuleByNameInternal();
|
||||
s32 PS4_SYSV_ABI sceSysmoduleUnloadModuleInternal();
|
||||
s32 PS4_SYSV_ABI sceSysmoduleUnloadModuleInternalWithArg();
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::SysModule
|
||||
10
src/core/libraries/sysmodule/sysmodule_error.h
Normal file
10
src/core/libraries/sysmodule/sysmodule_error.h
Normal file
@ -0,0 +1,10 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
constexpr s32 ORBIS_SYSMODULE_INVALID_ID = 0x805A1000;
|
||||
constexpr s32 ORBIS_SYSMODULE_NOT_LOADED = 0x805A1001;
|
||||
constexpr s32 ORBIS_SYSMODULE_LOCK_FAILED = 0x805A10FF;
|
||||
444
src/core/libraries/sysmodule/sysmodule_internal.cpp
Normal file
444
src/core/libraries/sysmodule/sysmodule_internal.cpp
Normal file
@ -0,0 +1,444 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/elf_info.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/file_sys/fs.h"
|
||||
#include "core/libraries/disc_map/disc_map.h"
|
||||
#include "core/libraries/font/font.h"
|
||||
#include "core/libraries/font/fontft.h"
|
||||
#include "core/libraries/jpeg/jpegenc.h"
|
||||
#include "core/libraries/kernel/kernel.h"
|
||||
#include "core/libraries/kernel/orbis_error.h"
|
||||
#include "core/libraries/libc_internal/libc_internal.h"
|
||||
#include "core/libraries/libpng/pngenc.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/ngs2/ngs2.h"
|
||||
#include "core/libraries/rtc/rtc.h"
|
||||
#include "core/libraries/sysmodule/sysmodule_error.h"
|
||||
#include "core/libraries/sysmodule/sysmodule_internal.h"
|
||||
#include "core/libraries/sysmodule/sysmodule_table.h"
|
||||
#include "core/linker.h"
|
||||
#include "emulator.h"
|
||||
|
||||
namespace Libraries::SysModule {
|
||||
|
||||
s32 getModuleHandle(s32 id, s32* handle) {
|
||||
if (id == 0) {
|
||||
return ORBIS_SYSMODULE_INVALID_ID;
|
||||
}
|
||||
for (OrbisSysmoduleModuleInternal mod : g_modules_array) {
|
||||
if (mod.id != id) {
|
||||
continue;
|
||||
}
|
||||
if (mod.is_loaded < 1) {
|
||||
return ORBIS_SYSMODULE_NOT_LOADED;
|
||||
}
|
||||
if (handle != nullptr) {
|
||||
*handle = mod.handle;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
return ORBIS_SYSMODULE_INVALID_ID;
|
||||
}
|
||||
|
||||
bool shouldHideName(const char* module_name) {
|
||||
for (u64 i = 0; i < g_num_modules; i++) {
|
||||
OrbisSysmoduleModuleInternal mod = g_modules_array[i];
|
||||
if ((mod.flags & OrbisSysmoduleModuleInternalFlags::IsGame) == 0) {
|
||||
continue;
|
||||
}
|
||||
u64 name_length = std::strlen(mod.name);
|
||||
char name_copy[0x100];
|
||||
std::strncpy(name_copy, mod.name, sizeof(name_copy));
|
||||
// Module table stores names without extensions, so check with .prx appended to the name.
|
||||
std::strncpy(&name_copy[name_length], ".prx", 4);
|
||||
s32 result = std::strncmp(module_name, name_copy, sizeof(name_copy));
|
||||
if (result == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// libSceFios2 and libc are checked as both sprx or prx modules.
|
||||
if (i == 3) {
|
||||
result = std::strncmp(module_name, "libSceFios2.sprx", sizeof(name_copy));
|
||||
} else if (i == 4) {
|
||||
result = std::strncmp(module_name, "libc.sprx", sizeof(name_copy));
|
||||
}
|
||||
|
||||
if (result == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isDebugModule(s32 id) {
|
||||
for (OrbisSysmoduleModuleInternal mod : g_modules_array) {
|
||||
if (mod.id == id && (mod.flags & OrbisSysmoduleModuleInternalFlags::IsDebug) != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool validateModuleId(s32 id) {
|
||||
if ((id & 0x7fffffff) == 0) {
|
||||
return ORBIS_SYSMODULE_INVALID_ID;
|
||||
}
|
||||
|
||||
s32 sdk_ver = 0;
|
||||
ASSERT_MSG(!Kernel::sceKernelGetCompiledSdkVersion(&sdk_ver),
|
||||
"Failed to retrieve compiled SDK version");
|
||||
|
||||
// libSceGameCustomDialog isn't loadable on SDK >= 7.50
|
||||
if (id == 0xb8 && sdk_ver >= Common::ElfInfo::FW_75) {
|
||||
return ORBIS_SYSMODULE_INVALID_ID;
|
||||
}
|
||||
|
||||
// libSceNpSnsFacebookDialog isn't loadable on SDK >= 7.00
|
||||
if (id == 0xb0 && sdk_ver >= Common::ElfInfo::FW_70) {
|
||||
return ORBIS_SYSMODULE_INVALID_ID;
|
||||
}
|
||||
|
||||
// libSceJson isn't loadable on SDK >= 3.00
|
||||
if (id == 0x80 && sdk_ver >= Common::ElfInfo::FW_30) {
|
||||
return ORBIS_SYSMODULE_INVALID_ID;
|
||||
}
|
||||
|
||||
// Cannot load debug modules on retail hardware.
|
||||
if (isDebugModule(id) && !EmulatorSettings.IsDevKit()) {
|
||||
return ORBIS_SYSMODULE_INVALID_ID;
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 loadModuleInternal(s32 index, s32 argc, const void* argv, s32* res_out) {
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
auto* game_info = Common::Singleton<Common::ElfInfo>::Instance();
|
||||
|
||||
// If the module is already loaded, increment is_loaded and return ORBIS_OK.
|
||||
OrbisSysmoduleModuleInternal& mod = g_modules_array[index];
|
||||
if (mod.is_loaded > 0) {
|
||||
mod.is_loaded++;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 start_result = 0;
|
||||
// Most of the logic the actual module has here is to get the correct location of this module.
|
||||
// Since we only care about a small subset of LLEs, we can simplify this logic.
|
||||
if ((mod.flags & OrbisSysmoduleModuleInternalFlags::IsGame) != 0) {
|
||||
std::string guest_path = std::string("/app0/sce_module/").append(mod.name);
|
||||
guest_path.append(".prx");
|
||||
const auto& host_path = mnt->GetHostPath(guest_path);
|
||||
|
||||
// For convenience, load through linker directly instead of loading through libkernel calls.
|
||||
s32 result = linker->LoadAndStartModule(host_path, argc, argv, &start_result);
|
||||
// If the module is missing, the library prints a very helpful message for developers.
|
||||
// We'll just log an error.
|
||||
if (result < 0) {
|
||||
LOG_ERROR(Lib_SysModule, "Failed to load game library {}", guest_path);
|
||||
return result;
|
||||
} else {
|
||||
// On success, the library validates module params and the module SDK version.
|
||||
// We don't store the information this uses, so skip the proper checks.
|
||||
mod.handle = result;
|
||||
mod.is_loaded++;
|
||||
}
|
||||
} else {
|
||||
// This is not a game library. We'll need to perform some checks,
|
||||
// but we don't need to perform the path resolution logic the actual library has.
|
||||
std::string mod_name = std::string(mod.name);
|
||||
|
||||
// libSceGnmDriver case
|
||||
if (index == 0xd && EmulatorSettings.IsDevKit()) {
|
||||
// There are some other checks involved here that I am not familiar with.
|
||||
// Since we're not exactly running libSceGnmDriver LLE, this shouldn't matter too much.
|
||||
mod_name.append("_padebug");
|
||||
}
|
||||
|
||||
// libSceSsl2 case
|
||||
if (index == 0x27 && false /*needs legacy ssl*/) {
|
||||
// Replaces module name with libSceSsl (index 0x15)
|
||||
mod_name.clear();
|
||||
mod_name.append(g_modules_array[0x15].name);
|
||||
}
|
||||
|
||||
// libSceVrTracker case
|
||||
if (index == 0xb3 && EmulatorSettings.IsDevKit()) {
|
||||
mod_name.append("_debug");
|
||||
}
|
||||
|
||||
if ((mod.flags & OrbisSysmoduleModuleInternalFlags::IsNeo) == 0 &&
|
||||
(mod.flags & OrbisSysmoduleModuleInternalFlags::IsNeoMode) != 0 &&
|
||||
Kernel::sceKernelIsNeoMode() == 1) {
|
||||
// PS4 Pro running in enhanced mode
|
||||
mod_name.append("ForNeoMode");
|
||||
} else if ((mod.flags & OrbisSysmoduleModuleInternalFlags::IsNeo) != 0 &&
|
||||
EmulatorSettings.IsNeo()) {
|
||||
// PS4 Pro running in base mode
|
||||
mod_name.append("ForNeo");
|
||||
}
|
||||
|
||||
// Append .sprx extension.
|
||||
mod_name.append(".sprx");
|
||||
|
||||
// Now we need to check if the requested library is allowed to LLE.
|
||||
// First, we allow all modules from game-specific sys_modules
|
||||
const auto& sys_module_path = EmulatorSettings.GetSysModulesDir();
|
||||
const auto& game_specific_module_path =
|
||||
sys_module_path / game_info->GameSerial() / mod_name;
|
||||
if (std::filesystem::exists(game_specific_module_path)) {
|
||||
// The requested module is present in the game-specific sys_modules, load it.
|
||||
LOG_INFO(Loader, "Loading {} from game serial file {}", mod_name,
|
||||
game_info->GameSerial());
|
||||
s32 handle =
|
||||
linker->LoadAndStartModule(game_specific_module_path, argc, argv, &start_result);
|
||||
ASSERT_MSG(handle >= 0, "Failed to load module {}", mod_name);
|
||||
mod.handle = handle;
|
||||
mod.is_loaded++;
|
||||
if (res_out != nullptr) {
|
||||
*res_out = start_result;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
// We need to check a few things here.
|
||||
// First, check if this is a module we allow LLE for.
|
||||
static s32 stub_handle = 100;
|
||||
constexpr auto ModulesToLoad = std::to_array<Core::SysModules>(
|
||||
{{"libSceNgs2.sprx", &Libraries::Ngs2::RegisterLib},
|
||||
{"libSceUlt.sprx", nullptr},
|
||||
{"libSceRtc.sprx", &Libraries::Rtc::RegisterLib},
|
||||
{"libSceJpegDec.sprx", nullptr},
|
||||
{"libSceJpegEnc.sprx", &Libraries::JpegEnc::RegisterLib},
|
||||
{"libScePngEnc.sprx", &Libraries::PngEnc::RegisterLib},
|
||||
{"libSceJson.sprx", nullptr},
|
||||
{"libSceJson2.sprx", nullptr},
|
||||
{"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterLib},
|
||||
{"libSceCesCs.sprx", nullptr},
|
||||
{"libSceAudiodec.sprx", nullptr},
|
||||
{"libSceFont.sprx", &Libraries::Font::RegisterlibSceFont},
|
||||
{"libSceFontFt.sprx", &Libraries::FontFt::RegisterlibSceFontFt},
|
||||
{"libSceFreeTypeOt.sprx", nullptr}});
|
||||
|
||||
// Iterate through the allowed array
|
||||
const auto it = std::ranges::find_if(
|
||||
ModulesToLoad, [&](Core::SysModules module) { return mod_name == module.module_name; });
|
||||
if (it == ModulesToLoad.end()) {
|
||||
// Not an allowed LLE, stub success without warning.
|
||||
mod.is_loaded++;
|
||||
// Some internal checks rely on a handle, stub a valid one.
|
||||
mod.handle = stub_handle++;
|
||||
if (res_out != nullptr) {
|
||||
*res_out = ORBIS_OK;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
// Allowed module, check if it exists
|
||||
const auto& module_path = sys_module_path / mod_name;
|
||||
if (std::filesystem::exists(module_path)) {
|
||||
LOG_INFO(Loader, "Loading {}", mod_name);
|
||||
s32 handle = linker->LoadAndStartModule(module_path, argc, argv, &start_result);
|
||||
ASSERT_MSG(handle >= 0, "Failed to load module {}", mod_name);
|
||||
mod.handle = handle;
|
||||
} else {
|
||||
// Allowed LLE that isn't present, log message
|
||||
auto& [name, init_func] = *it;
|
||||
if (init_func) {
|
||||
LOG_INFO(Loader, "Can't Load {} switching to HLE", mod_name);
|
||||
init_func(&linker->GetHLESymbols());
|
||||
|
||||
// When loading HLEs, we need to relocate imports
|
||||
// This ensures later module loads can see our HLE functions.
|
||||
linker->RelocateAllImports();
|
||||
} else {
|
||||
LOG_INFO(Loader, "No HLE available for {} module", mod_name);
|
||||
}
|
||||
mod.handle = stub_handle++;
|
||||
}
|
||||
|
||||
// Mark module as loaded.
|
||||
mod.is_loaded++;
|
||||
}
|
||||
|
||||
// Only successful loads will reach here
|
||||
if (res_out != nullptr) {
|
||||
*res_out = start_result;
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 loadModule(s32 id, s32 argc, const void* argv, s32* res_out) {
|
||||
// Retrieve the module to load from the table
|
||||
OrbisSysmoduleModuleInternal requested_module{};
|
||||
for (OrbisSysmoduleModuleInternal mod : g_modules_array) {
|
||||
if (mod.id == id) {
|
||||
requested_module = mod;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (requested_module.id != id || requested_module.id == 0) {
|
||||
return ORBIS_SYSMODULE_INVALID_ID;
|
||||
}
|
||||
|
||||
// Every module has a pointer to an array of indexes to modules that need loading.
|
||||
if (requested_module.to_load == nullptr) {
|
||||
// Seems like ORBIS_SYSMODULE_LOCK_FAILED is a generic internal error.
|
||||
return ORBIS_SYSMODULE_LOCK_FAILED;
|
||||
}
|
||||
|
||||
LOG_INFO(Lib_SysModule, "Loading {}", requested_module.name);
|
||||
|
||||
// Loop through every module that requires loading, in reverse order
|
||||
for (s64 i = requested_module.num_to_load - 1; i >= 0; i--) {
|
||||
// Modules flagged as debug modules only load for devkits
|
||||
u32 mod_index = requested_module.to_load[i];
|
||||
if ((!EmulatorSettings.IsDevKit() &&
|
||||
g_modules_array[mod_index].flags & OrbisSysmoduleModuleInternalFlags::IsDebug) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Arguments and result should only be applied to the requested module
|
||||
// Dependencies don't receive these values.
|
||||
s32 result = 0;
|
||||
if (i != 0) {
|
||||
result = loadModuleInternal(mod_index, 0, nullptr, nullptr);
|
||||
} else {
|
||||
result = loadModuleInternal(mod_index, argc, argv, res_out);
|
||||
}
|
||||
|
||||
// If loading any module fails, abort there.
|
||||
if (result != ORBIS_OK) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 unloadModule(s32 id, s32 argc, const void* argv, s32* res_out, bool is_internal) {
|
||||
OrbisSysmoduleModuleInternal mod{};
|
||||
for (s32 i = 0; i < g_modules_array.size(); i++) {
|
||||
mod = g_modules_array[i];
|
||||
if (mod.id != id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skips checking libSceDiscMap
|
||||
if (i == 0x22) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the module is loaded once, and is part of the second preload list,
|
||||
// then return OK and do nothing.
|
||||
for (s32 index : g_preload_list_2) {
|
||||
if (index == i && mod.is_loaded == 1) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
// Found the correct module.
|
||||
break;
|
||||
}
|
||||
|
||||
// If we failed to locate the module, return invalid id.
|
||||
if (mod.id != id || mod.id == 0) {
|
||||
return ORBIS_SYSMODULE_INVALID_ID;
|
||||
}
|
||||
|
||||
// If the module has no dependencies, then return an internal error.
|
||||
if (mod.num_to_load == 0 || mod.to_load == nullptr) {
|
||||
return ORBIS_SYSMODULE_LOCK_FAILED;
|
||||
}
|
||||
|
||||
// Unload the module and it's dependencies
|
||||
for (s64 i = 0; i < mod.num_to_load; i++) {
|
||||
OrbisSysmoduleModuleInternal dep_mod = g_modules_array[mod.to_load[i]];
|
||||
// If this is a debug module and we're not emulating a devkit, skip it.
|
||||
if ((dep_mod.flags & OrbisSysmoduleModuleInternalFlags::IsDebug) != 0 &&
|
||||
!EmulatorSettings.IsDevKit()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the module to unload is marked as unloaded, then return not loaded
|
||||
if (dep_mod.is_loaded == 0) {
|
||||
return ORBIS_SYSMODULE_NOT_LOADED;
|
||||
}
|
||||
|
||||
// By this point, all necessary checks are performed, decrement the load count.
|
||||
dep_mod.is_loaded--;
|
||||
|
||||
// Normally, this is where the real library would actually unload the module,
|
||||
// through a call to sceKernelStopUnloadModule.
|
||||
// As we don't implement module unloading, this behavior is skipped.
|
||||
|
||||
// Stub success during requested module unload.
|
||||
if (i == 0 && res_out != nullptr) {
|
||||
*res_out = ORBIS_OK;
|
||||
}
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 preloadModulesForLibkernel() {
|
||||
// For now, default to loading g_preload_list_3.
|
||||
// As far as I can tell, g_preload_list_1 seems to be some sort of list with libs
|
||||
// that games don't typically use, and g_preload_list_2 is just a reorganized version of 3.
|
||||
s32 sdk_ver = 0;
|
||||
ASSERT_MSG(Kernel::sceKernelGetCompiledSdkVersion(&sdk_ver) == 0,
|
||||
"Failed to get compiled SDK version");
|
||||
for (u32 module_index : g_preload_list_3) {
|
||||
// As per usual, these are arrays of indexes for g_modules_array
|
||||
// libSceDbg, libScePerf, libSceMat, and libSceRazorCpu_debug.
|
||||
// These are skipped unless this console is a devkit.
|
||||
if ((module_index == 0x12 || module_index == 0x1e || module_index == 0x24 ||
|
||||
module_index == 0x26) &&
|
||||
!EmulatorSettings.IsDevKit()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// libSceDiscMap case, skipped on newer SDK versions.
|
||||
if (module_index == 0x22 && sdk_ver >= Common::ElfInfo::FW_20) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// libSceDbgAssist is skipped on non-testkit consoles.
|
||||
// For now, stub check to non-devkit.
|
||||
if (module_index == 0x23 && !EmulatorSettings.IsDevKit()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// libSceRazorCpu, skipped for old non-devkit consoles.
|
||||
if (module_index == 0x25 && sdk_ver < Common::ElfInfo::FW_45 &&
|
||||
!EmulatorSettings.IsDevKit()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// libSceHttp2, skipped for SDK versions below 7.00.
|
||||
if (module_index == 0x28 && sdk_ver < Common::ElfInfo::FW_70) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// libSceNpWebApi2 and libSceNpGameIntent, skipped for SDK versions below 7.50
|
||||
if ((module_index == 0x29 || module_index == 0x2a) && sdk_ver < Common::ElfInfo::FW_75) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Load the actual module
|
||||
s32 result = loadModuleInternal(module_index, 0, nullptr, nullptr);
|
||||
if (result != ORBIS_OK) {
|
||||
// On real hardware, module preloading must succeed or the game will abort.
|
||||
// To enable users to test homebrew easier, we'll log a critical error instead.
|
||||
LOG_CRITICAL(Lib_SysModule, "Failed to preload {}, expect crashes",
|
||||
g_modules_array[module_index].name);
|
||||
}
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
} // namespace Libraries::SysModule
|
||||
20
src/core/libraries/sysmodule/sysmodule_internal.h
Normal file
20
src/core/libraries/sysmodule/sysmodule_internal.h
Normal file
@ -0,0 +1,20 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/kernel/process.h"
|
||||
|
||||
namespace Libraries::SysModule {
|
||||
|
||||
s32 getModuleHandle(s32 id, s32* handle);
|
||||
bool shouldHideName(const char* module_name);
|
||||
bool isDebugModule(s32 id);
|
||||
bool validateModuleId(s32 id);
|
||||
s32 loadModuleInternal(s32 index, s32 argc, const void* argv, s32* res_out);
|
||||
s32 loadModule(s32 id, s32 argc, const void* argv, s32* res_out);
|
||||
s32 unloadModule(s32 id, s32 argc, const void* argv, s32* res_out, bool is_internal);
|
||||
s32 preloadModulesForLibkernel();
|
||||
|
||||
} // namespace Libraries::SysModule
|
||||
684
src/core/libraries/sysmodule/sysmodule_table.h
Normal file
684
src/core/libraries/sysmodule/sysmodule_table.h
Normal file
@ -0,0 +1,684 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Libraries::SysModule {
|
||||
|
||||
/**
|
||||
* libSceSysmodule hardcodes an array of valuable data about loading each PS4 module.
|
||||
* This header stores the contents of this array, as dumped from 12.52's libSceSysmodule,
|
||||
* and altered to fit within my simplified internal module struct.
|
||||
*/
|
||||
|
||||
// This is an internal struct. Doesn't match the real one exactly.
|
||||
struct OrbisSysmoduleModuleInternal {
|
||||
u32 id; // User requested ID
|
||||
s32 handle; // Handle of the module, once loaded
|
||||
s32 is_loaded; // 0 by default, set to 1 once loaded.
|
||||
s32 flags; // Miscellaneous details about the module
|
||||
const char* name; // Name of the actual SPRX/PRX library
|
||||
const u16* to_load; // Pointer to an array of modules to load
|
||||
s32 num_to_load; // Number of indicies in the array of modules
|
||||
};
|
||||
|
||||
// This enum contains helpful identifiers for some bits used in the flags of a module.
|
||||
enum OrbisSysmoduleModuleInternalFlags : s32 {
|
||||
IsCommon = 1, // Module is located in /system/common/lib
|
||||
IsPriv = 2, // Module is located in /system/priv/lib
|
||||
IsGame = 4, // Module is located in /app0/sce_module
|
||||
IsDebug = 8, // Module should only be loaded on devkit/testkit consoles
|
||||
IsNeo = 0x200, // Module should only be loaded on PS4 Pro consoles
|
||||
IsNeoMode = 0x400, // Module should only be loaded for PS4 Pro running in enhanced mode
|
||||
IsCommonEx = 0x1000, // Module is located in /system_ex/common_ex/lib
|
||||
IsPrivEx = 0x2000, // Module is located in /system_ex/priv_ex/lib
|
||||
};
|
||||
|
||||
// Array of module indexes to load in sceSysmodulePreloadModuleForLibkernel.
|
||||
// The library has three versions of this array
|
||||
u32 g_preload_list_1[36] = {0x24, 3, 4, 5, 6, 7, 8, 9, 0x25, 0xb, 0xc, 0xd,
|
||||
0xe, 0xf, 0x10, 0x11, 0x1f, 0x12, 0x13, 0x14, 0x27, 0x28, 0x16, 0x17,
|
||||
0x2a, 0x18, 0x29, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x26, 0x1e, 0x20, 0x21};
|
||||
u32 g_preload_list_2[38] = {1, 2, 0x24, 0x22, 3, 4, 5, 6, 7, 8,
|
||||
9, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x1f, 0x12,
|
||||
0x23, 0x13, 0x14, 0x27, 0x28, 0x16, 0x17, 0x2a, 0x18, 0x29,
|
||||
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x25, 0x26, 0x1e};
|
||||
u32 g_preload_list_3[38] = {1, 2, 0x24, 0x22, 3, 4, 5, 6, 7, 8,
|
||||
9, 0x25, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x1f,
|
||||
0x12, 0x23, 0x13, 0x14, 0x27, 0x28, 0x16, 0x17, 0x2a, 0x18,
|
||||
0x29, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x26, 0x1e};
|
||||
|
||||
// Arrays of modules to load for each module.
|
||||
// The stored values are valid indices to modules in g_modules_array.
|
||||
u16 g_libSceNet_modules[1] = {5};
|
||||
u16 g_libSceIpmi_modules[1] = {6};
|
||||
u16 g_libSceMbus_modules[2] = {7, 6};
|
||||
u16 g_libSceRegMgr_modules[1] = {8};
|
||||
u16 g_libSceRtc_modules[1] = {9};
|
||||
u16 g_libSceAvSetting_modules[3] = {11, 7, 6};
|
||||
u16 g_libSceVideoOut_modules[3] = {12, 11, 7};
|
||||
u16 g_libSceGnmDriver_modules[4] = {13, 12, 8, 37};
|
||||
u16 g_libSceAudioOut_modules[4] = {14, 11, 7, 6};
|
||||
u16 g_libSceAudioIn_modules[4] = {15, 14, 7, 6};
|
||||
u16 g_libSceAjm_modules[1] = {16};
|
||||
u16 g_libScePad_modules[2] = {17, 7};
|
||||
u16 g_libSceDbg_debug_modules[1] = {18};
|
||||
u16 g_libSceNetCtl_modules[2] = {19, 6};
|
||||
u16 g_libSceHttp_modules[5] = {20, 39, 9, 19, 5};
|
||||
u16 g_libSceSsl_modules[3] = {21, 9, 5};
|
||||
u16 g_libSceNpCommon_modules[8] = {22, 20, 39, 19, 9, 8, 6, 5};
|
||||
u16 g_libSceNpManager_modules[7] = {23, 22, 20, 39, 19, 9, 5};
|
||||
u16 g_libSceNpWebApi_modules[7] = {24, 23, 22, 20, 39, 9, 5};
|
||||
u16 g_libSceSaveData_modules[4] = {25, 27, 9, 6};
|
||||
u16 g_libSceSystemService_modules[3] = {26, 8, 6};
|
||||
u16 g_libSceUserService_modules[2] = {27, 6};
|
||||
u16 g_libSceCommonDialog_modules[1] = {28};
|
||||
u16 g_libSceSysUtil_modules[2] = {29, 8};
|
||||
u16 g_libScePerf_debug_modules[3] = {30, 38, 37};
|
||||
u16 g_libSceCamera_modules[2] = {31, 7};
|
||||
u16 g_libSceDiscMap_modules[1] = {34};
|
||||
u16 g_libSceDbgAssist_modules[1] = {35};
|
||||
u16 g_libSceMat_debug_modules[1] = {36};
|
||||
u16 g_libSceRazorCpu_modules[1] = {37};
|
||||
u16 g_libSceRazorCpu_debug_debug_modules[2] = {38, 37};
|
||||
u16 g_libSceSsl2_modules[3] = {39, 9, 5};
|
||||
u16 g_libSceHttp2_modules[13] = {40, 20, 39, 9, 19, 5, 39, 9, 5, 9, 19, 6, 5};
|
||||
u16 g_libSceNpWebApi2_modules[39] = {41, 23, 22, 20, 39, 19, 9, 5, 22, 20, 39, 19, 9,
|
||||
8, 6, 5, 40, 20, 39, 9, 19, 5, 39, 9, 5, 9,
|
||||
19, 6, 5, 20, 39, 9, 19, 5, 39, 9, 5, 9, 5};
|
||||
u16 g_libSceNpGameIntent_modules[10] = {42, 22, 20, 39, 19, 9, 8, 6, 5, 6};
|
||||
u16 g_libSceFiber_modules[5] = {49, 114, 30, 38, 37};
|
||||
u16 g_libSceUlt_modules[6] = {50, 49, 114, 30, 38, 37};
|
||||
u16 g_libSceNgs2_modules[2] = {51, 16};
|
||||
u16 g_libSceXml_modules[1] = {52};
|
||||
u16 g_libSceNpUtility_modules[5] = {53, 22, 20, 19, 5};
|
||||
u16 g_libSceVoice_modules[4] = {54, 16, 15, 14};
|
||||
u16 g_libSceNpMatching2_modules[7] = {55, 23, 22, 20, 39, 19, 5};
|
||||
u16 g_libSceNpScoreRanking_modules[3] = {56, 23, 22};
|
||||
u16 g_libSceRudp_modules[1] = {57};
|
||||
u16 g_libSceNpTus_modules[3] = {58, 23, 22};
|
||||
u16 g_libSceFace_modules[1] = {59};
|
||||
u16 g_libSceSmart_modules[1] = {60};
|
||||
u16 g_libSceJson_modules[1] = {61};
|
||||
u16 g_libSceGameLiveStreaming_modules[2] = {62, 6};
|
||||
u16 g_libSceCompanionUtil_modules[3] = {63, 7, 6};
|
||||
u16 g_libScePlayGo_modules[1] = {64};
|
||||
u16 g_libSceFont_modules[1] = {65};
|
||||
u16 g_libSceVideoRecording_modules[2] = {66, 82};
|
||||
u16 g_libSceAudiodec_modules[2] = {67, 16};
|
||||
u16 g_libSceJpegDec_modules[1] = {68};
|
||||
u16 g_libSceJpegEnc_modules[1] = {69};
|
||||
u16 g_libScePngDec_modules[1] = {70};
|
||||
u16 g_libScePngEnc_modules[1] = {71};
|
||||
u16 g_libSceVideodec_modules[3] = {72, 80, 161};
|
||||
u16 g_libSceMove_modules[1] = {73};
|
||||
u16 g_libScePadTracker_modules[2] = {75, 17};
|
||||
u16 g_libSceDepth_modules[2] = {76, 31};
|
||||
u16 g_libSceHand_modules[1] = {77};
|
||||
u16 g_libSceIme_modules[2] = {78, 6};
|
||||
u16 g_libSceImeDialog_modules[2] = {79, 6};
|
||||
u16 g_libSceVdecCore_modules[1] = {80};
|
||||
u16 g_libSceNpParty_modules[2] = {81, 6};
|
||||
u16 g_libSceAvcap_modules[2] = {82, 6};
|
||||
u16 g_libSceFontFt_modules[1] = {83};
|
||||
u16 g_libSceFreeTypeOt_modules[1] = {84};
|
||||
u16 g_libSceFreeTypeOl_modules[1] = {85};
|
||||
u16 g_libSceFreeTypeOptOl_modules[1] = {86};
|
||||
u16 g_libSceScreenShot_modules[3] = {87, 29, 6};
|
||||
u16 g_libSceNpAuth_modules[3] = {88, 22, 23};
|
||||
u16 g_libSceVoiceQos_modules[5] = {89, 54, 16, 15, 14};
|
||||
u16 g_libSceSysCore_modules[2] = {90, 6};
|
||||
u16 g_libSceM4aacEnc_modules[2] = {91, 16};
|
||||
u16 g_libSceAudiodecCpu_modules[1] = {92};
|
||||
u16 g_libSceCdlgUtilServer_modules[2] = {93, 26};
|
||||
u16 g_libSceSulpha_debug_modules[1] = {94};
|
||||
u16 g_libSceSaveDataDialog_modules[4] = {95, 9, 28, 26};
|
||||
u16 g_libSceInvitationDialog_modules[1] = {96};
|
||||
u16 g_libSceKeyboard_debug_modules[1] = {97};
|
||||
u16 g_libSceKeyboard_modules[1] = {98};
|
||||
u16 g_libSceMsgDialog_modules[1] = {99};
|
||||
u16 g_libSceAvPlayer_modules[1] = {100};
|
||||
u16 g_libSceContentExport_modules[1] = {101};
|
||||
u16 g_libSceVisionManager_modules[1] = {102};
|
||||
u16 g_libSceAc3Enc_modules[2] = {103, 16};
|
||||
u16 g_libSceAppInstUtil_modules[1] = {104};
|
||||
u16 g_libSceVencCore_modules[1] = {105};
|
||||
u16 g_libSceAudio3d_modules[1] = {106};
|
||||
u16 g_libSceNpCommerce_modules[1] = {107};
|
||||
u16 g_libSceHidControl_modules[1] = {108};
|
||||
u16 g_libSceMouse_modules[1] = {109};
|
||||
u16 g_libSceCompanionHttpd_modules[1] = {110};
|
||||
u16 g_libSceWebBrowserDialog_modules[1] = {111};
|
||||
u16 g_libSceErrorDialog_modules[1] = {112};
|
||||
u16 g_libSceNpTrophy_modules[1] = {113};
|
||||
u16 g_ulobjmgr_modules[1] = {114};
|
||||
u16 g_libSceVideoCoreInterface_modules[1] = {115};
|
||||
u16 g_libSceVideoCoreServerInterface_modules[1] = {116};
|
||||
u16 g_libSceNpSns_modules[1] = {117};
|
||||
u16 g_libSceNpSnsFacebookDialog_modules[2] = {118, 117};
|
||||
u16 g_libSceMoveTracker_modules[1] = {119};
|
||||
u16 g_libSceNpProfileDialog_modules[1] = {120};
|
||||
u16 g_libSceNpFriendListDialog_modules[1] = {121};
|
||||
u16 g_libSceAppContent_modules[1] = {122};
|
||||
u16 g_libSceMarlin_modules[1] = {123};
|
||||
u16 g_libSceDtsEnc_modules[2] = {124, 16};
|
||||
u16 g_libSceNpSignaling_modules[1] = {125};
|
||||
u16 g_libSceRemoteplay_modules[1] = {126};
|
||||
u16 g_libSceUsbd_modules[1] = {127};
|
||||
u16 g_libSceGameCustomDataDialog_modules[1] = {128};
|
||||
u16 g_libSceNpEulaDialog_modules[1] = {129};
|
||||
u16 g_libSceRandom_modules[1] = {130};
|
||||
u16 g_libSceDipsw_modules[1] = {131};
|
||||
u16 g_libSceS3DConversion_modules[1] = {132};
|
||||
u16 g_libSceOttvCapture_debug_modules[1] = {133};
|
||||
u16 g_libSceBgft_modules[1] = {134};
|
||||
u16 g_libSceAudiodecCpuDdp_modules[1] = {135};
|
||||
u16 g_libSceAudiodecCpuM4aac_modules[1] = {136};
|
||||
u16 g_libSceAudiodecCpuDts_modules[1] = {137};
|
||||
u16 g_libSceAudiodecCpuDtsHdLbr_modules[1] = {138};
|
||||
u16 g_libSceAudiodecCpuDtsHdMa_modules[1] = {139};
|
||||
u16 g_libSceAudiodecCpuLpcm_modules[1] = {140};
|
||||
u16 g_libSceBemp2sys_modules[1] = {141};
|
||||
u16 g_libSceBeisobmf_modules[1] = {142};
|
||||
u16 g_libScePlayReady_modules[1] = {143};
|
||||
u16 g_libSceVideoNativeExtEssential_modules[1] = {144};
|
||||
u16 g_libSceZlib_modules[1] = {145};
|
||||
u16 g_libSceIduUtil_modules[1] = {146};
|
||||
u16 g_libScePsm_modules[1] = {147};
|
||||
u16 g_libSceDtcpIp_modules[1] = {148};
|
||||
u16 g_libSceKbEmulate_modules[1] = {149};
|
||||
u16 g_libSceAppChecker_modules[1] = {150};
|
||||
u16 g_libSceNpGriefReport_modules[1] = {151};
|
||||
u16 g_libSceContentSearch_modules[1] = {152};
|
||||
u16 g_libSceShareUtility_modules[1] = {153};
|
||||
u16 g_libSceWeb_modules[6] = {154, 155, 147, 192, 27, 6};
|
||||
u16 g_libSceWebKit2_modules[30] = {155, 266, 90, 6, 8, 255, 192, 116, 266, 90, 6, 8, 12, 11, 7,
|
||||
17, 7, 26, 8, 6, 257, 130, 39, 9, 5, 19, 6, 5, 8, 9};
|
||||
u16 g_libSceDeci4h_debug_modules[1] = {156};
|
||||
u16 g_libSceHeadTracker_modules[1] = {157};
|
||||
u16 g_libSceGameUpdate_modules[2] = {158, 6};
|
||||
u16 g_libSceAutoMounterClient_modules[2] = {159, 6};
|
||||
u16 g_libSceSystemGesture_modules[1] = {160};
|
||||
u16 g_libSceVdecSavc_modules[1] = {161};
|
||||
u16 g_libSceVdecSavc2_modules[1] = {162};
|
||||
u16 g_libSceVideodec2_modules[3] = {163, 80, 162};
|
||||
u16 g_libSceVdecwrap_modules[2] = {164, 80};
|
||||
u16 g_libSceVshctl_modules[1] = {165};
|
||||
u16 g_libSceAt9Enc_modules[1] = {166};
|
||||
u16 g_libSceConvertKeycode_modules[1] = {167};
|
||||
u16 g_libSceGpuException_modules[1] = {168};
|
||||
u16 g_libSceSharePlay_modules[1] = {169};
|
||||
u16 g_libSceAudiodReport_modules[1] = {170};
|
||||
u16 g_libSceSulphaDrv_modules[1] = {171};
|
||||
u16 g_libSceHmd_modules[1] = {172};
|
||||
u16 g_libSceUsbStorage_modules[2] = {173, 6};
|
||||
u16 g_libSceVdecShevc_modules[1] = {174};
|
||||
u16 g_libSceUsbStorageDialog_modules[1] = {175};
|
||||
u16 g_libSceFaceTracker_modules[2] = {176, 59};
|
||||
u16 g_libSceHandTracker_modules[1] = {177};
|
||||
u16 g_libSceNpSnsYouTubeDialog_modules[2] = {178, 117};
|
||||
u16 g_libSceVrTracker_modules[6] = {179, 6, 172, 31, 17, 73};
|
||||
u16 g_libSceProfileCacheExternal_modules[2] = {180, 6};
|
||||
u16 g_libSceBackupRestoreUtil_modules[1] = {181};
|
||||
u16 g_libSceMusicPlayerService_modules[2] = {182, 183};
|
||||
u16 g_libSceMusicCoreServerClientJsEx_modules[1] = {183};
|
||||
u16 g_libSceSpSysCallWrapper_modules[3] = {184, 19, 6};
|
||||
u16 g_libScePs2EmuMenuDialog_modules[1] = {185};
|
||||
u16 g_libSceNpSnsDailyMotionDialog_modules[1] = {186};
|
||||
u16 g_libSceAudiodecCpuHevag_modules[1] = {187};
|
||||
u16 g_libSceLoginDialog_modules[2] = {188, 6};
|
||||
u16 g_libSceLoginService_modules[2] = {189, 6};
|
||||
u16 g_libSceSigninDialog_modules[2] = {190, 6};
|
||||
u16 g_libSceVdecsw_modules[3] = {191, 80, 162};
|
||||
u16 g_libSceOrbisCompat_modules[24] = {192, 116, 266, 90, 6, 8, 12, 11, 7, 17, 7, 26,
|
||||
8, 6, 257, 130, 39, 9, 5, 19, 6, 5, 8, 9};
|
||||
u16 g_libSceCoreIPC_modules[1] = {193};
|
||||
u16 g_libSceCustomMusicCore_modules[12] = {194, 29, 8, 27, 6, 14, 11, 7, 6, 11, 7, 6};
|
||||
u16 g_libSceJson2_modules[1] = {195};
|
||||
u16 g_libSceAudioLatencyEstimation_modules[1] = {196};
|
||||
u16 g_libSceWkFontConfig_modules[1] = {197};
|
||||
u16 g_libSceVorbisDec_modules[3] = {198, 67, 16};
|
||||
u16 g_libSceTtsCoreEnUs_modules[1] = {199};
|
||||
u16 g_libSceTtsCoreJp_modules[1] = {200};
|
||||
u16 g_libSceOpusCeltEnc_modules[2] = {201, 16};
|
||||
u16 g_libSceOpusCeltDec_modules[2] = {202, 16};
|
||||
u16 g_libSceLoginMgrServer_modules[1] = {203};
|
||||
u16 g_libSceHmdSetupDialog_modules[1] = {204};
|
||||
u16 g_libSceVideoOutSecondary_modules[6] = {205, 82, 6, 12, 11, 7};
|
||||
u16 g_libSceContentDelete_modules[1] = {206};
|
||||
u16 g_libSceImeBackend_modules[1] = {207};
|
||||
u16 g_libSceNetCtlApDialog_modules[1] = {208};
|
||||
u16 g_libSceGnmResourceRegistration_modules[1] = {209};
|
||||
u16 g_libScePlayGoDialog_modules[1] = {210};
|
||||
u16 g_libSceSocialScreen_modules[7] = {211, 205, 82, 6, 12, 11, 7};
|
||||
u16 g_libSceEditMp4_modules[1] = {212};
|
||||
u16 g_libScePsmKitSystem_modules[1] = {221};
|
||||
u16 g_libSceTextToSpeech_modules[1] = {222};
|
||||
u16 g_libSceNpToolkit_modules[1] = {223};
|
||||
u16 g_libSceCustomMusicService_modules[2] = {224, 183};
|
||||
u16 g_libSceClSysCallWrapper_modules[11] = {225, 20, 39, 9, 19, 5, 39, 9, 5, 67, 16};
|
||||
u16 g_libSceScm_modules[1] = {226};
|
||||
u16 g_libSceSystemLogger_modules[2] = {227, 6};
|
||||
u16 g_libSceBluetoothHid_modules[1] = {228};
|
||||
u16 g_libSceAvPlayerStreaming_modules[1] = {229};
|
||||
u16 g_libSceAudiodecCpuAlac_modules[1] = {230};
|
||||
u16 g_libSceVideoDecoderArbitration_modules[1] = {231};
|
||||
u16 g_libSceVrServiceDialog_modules[1] = {232};
|
||||
u16 g_libSceJobManager_modules[2] = {233, 114};
|
||||
u16 g_libSceAudiodecCpuFlac_modules[1] = {234};
|
||||
u16 g_libSceSrcUtl_modules[2] = {235, 16};
|
||||
u16 g_libSceS3da_modules[1] = {236};
|
||||
u16 g_libSceDseehx_modules[1] = {237};
|
||||
u16 g_libSceShareFactoryUtil_modules[1] = {238};
|
||||
u16 g_libSceDataTransfer_modules[1] = {239};
|
||||
u16 g_libSceSocialScreenDialog_modules[1] = {240};
|
||||
u16 g_libSceAbstractStorage_modules[1] = {241};
|
||||
u16 g_libSceImageUtil_modules[1] = {242};
|
||||
u16 g_libSceMetadataReaderWriter_modules[1] = {243};
|
||||
u16 g_libSceJpegParser_modules[1] = {244};
|
||||
u16 g_libSceGvMp4Parser_modules[1] = {245};
|
||||
u16 g_libScePngParser_modules[1] = {246};
|
||||
u16 g_libSceGifParser_modules[1] = {247};
|
||||
u16 g_libSceNpSnsDialog_modules[2] = {248, 117};
|
||||
u16 g_libSceAbstractLocal_modules[1] = {249};
|
||||
u16 g_libSceAbstractFacebook_modules[1] = {250};
|
||||
u16 g_libSceAbstractYoutube_modules[1] = {251};
|
||||
u16 g_libSceAbstractTwitter_modules[1] = {252};
|
||||
u16 g_libSceAbstractDailymotion_modules[1] = {253};
|
||||
u16 g_libSceNpToolkit2_modules[1] = {254};
|
||||
u16 g_libScePrecompiledShaders_modules[1] = {255};
|
||||
u16 g_libSceDiscId_modules[1] = {256};
|
||||
u16 g_libSceLibreSsl_modules[2] = {257, 130};
|
||||
u16 g_libSceFsInternalForVsh_modules[1] = {258};
|
||||
u16 g_libSceNpUniversalDataSystem_modules[1] = {259};
|
||||
u16 g_libSceDolbyVision_modules[1] = {260};
|
||||
u16 g_libSceOpusSilkEnc_modules[2] = {261, 16};
|
||||
u16 g_libSceOpusDec_modules[2] = {262, 16};
|
||||
u16 g_libSceWebKit2Secure_modules[34] = {263, 265, 26, 8, 6, 266, 90, 6, 8, 255, 192, 116,
|
||||
266, 90, 6, 8, 12, 11, 7, 17, 7, 26, 8, 6,
|
||||
257, 130, 39, 9, 5, 19, 6, 5, 8, 9};
|
||||
u16 g_libSceJscCompiler_modules[1] = {264};
|
||||
u16 g_libSceJitBridge_modules[4] = {265, 26, 8, 6};
|
||||
u16 g_libScePigletv2VSH_modules[4] = {266, 90, 6, 8};
|
||||
u16 g_libSceJitBridge_common_ex_modules[4] = {267, 26, 8, 6};
|
||||
u16 g_libSceJscCompiler_common_ex_modules[1] = {268};
|
||||
u16 g_libSceOrbisCompat_common_ex_modules[24] = {269, 116, 266, 90, 6, 8, 12, 11, 7, 17, 7, 26,
|
||||
8, 6, 257, 130, 39, 9, 5, 19, 6, 5, 8, 9};
|
||||
u16 g_libSceWeb_common_ex_modules[6] = {270, 271, 147, 269, 27, 6};
|
||||
u16 g_libSceWebKit2_common_ex_modules[30] = {271, 266, 90, 6, 8, 273, 269, 116, 266, 90,
|
||||
6, 8, 12, 11, 7, 17, 7, 26, 8, 6,
|
||||
257, 130, 39, 9, 5, 19, 6, 5, 8, 9};
|
||||
u16 g_libSceWebKit2Secure_common_ex_modules[34] = {
|
||||
272, 267, 26, 8, 6, 266, 90, 6, 8, 273, 269, 116, 266, 90, 6, 8, 12,
|
||||
11, 7, 17, 7, 26, 8, 6, 257, 130, 39, 9, 5, 19, 6, 5, 8, 9};
|
||||
u16 g_libScePrecompiledShaders_common_ex_modules[1] = {273};
|
||||
u16 g_libSceGic_modules[1] = {274};
|
||||
u16 g_libSceRnpsAppMgr_modules[1] = {275};
|
||||
u16 g_libSceAsyncStorageInternal_modules[1] = {276};
|
||||
u16 g_libSceHttpCache_modules[1] = {277};
|
||||
u16 g_libScePlayReady2_modules[1] = {278};
|
||||
u16 g_libSceHdrScopes_debug_modules[1] = {279};
|
||||
u16 g_libSceNKWeb_modules[1] = {280};
|
||||
u16 g_libSceNKWebKit_modules[2] = {281, 282};
|
||||
u16 g_libSceNKWebKitRequirements_modules[1] = {282};
|
||||
u16 g_libSceVnaInternal_modules[1] = {283};
|
||||
u16 g_libSceVnaWebsocket_modules[1] = {284};
|
||||
u16 g_libSceCesCs_modules[1] = {285};
|
||||
u16 g_libSceComposite_modules[1] = {286};
|
||||
u16 g_libSceCompositeExt_modules[1] = {287};
|
||||
u16 g_libSceHubAppUtil_modules[1] = {288};
|
||||
u16 g_libScePosixForWebKit_modules[1] = {289};
|
||||
u16 g_libSceNpPartner001_modules[1] = {290};
|
||||
u16 g_libSceNpSessionSignaling_modules[75] = {
|
||||
291, 41, 23, 22, 20, 39, 19, 9, 5, 22, 20, 39, 19, 9, 8, 6, 5, 40, 20, 39, 9, 19, 5, 39, 9,
|
||||
5, 9, 19, 6, 5, 20, 39, 9, 19, 5, 39, 9, 5, 9, 5, 22, 20, 39, 19, 9, 8, 6, 5, 23, 22,
|
||||
20, 39, 19, 9, 5, 40, 20, 39, 9, 19, 5, 39, 9, 5, 9, 19, 6, 5, 39, 9, 5, 19, 6, 5, 9};
|
||||
u16 g_libScePlayerInvitationDialog_modules[1] = {292};
|
||||
u16 g_libSceNpCppWebApi_modules[42] = {293, 195, 41, 23, 22, 20, 39, 19, 9, 5, 22, 20, 39, 19,
|
||||
9, 8, 6, 5, 40, 20, 39, 9, 19, 5, 39, 9, 5, 9,
|
||||
19, 6, 5, 20, 39, 9, 19, 5, 39, 9, 5, 9, 5, 9};
|
||||
u16 g_libSceNpEntitlementAccess_modules[1] = {294};
|
||||
u16 g_libSceNpRemotePlaySessionSignaling_modules[76] = {
|
||||
295, 291, 41, 23, 22, 20, 39, 19, 9, 5, 22, 20, 39, 19, 9, 8, 6, 5, 40,
|
||||
20, 39, 9, 19, 5, 39, 9, 5, 9, 19, 6, 5, 20, 39, 9, 19, 5, 39, 9,
|
||||
5, 9, 5, 22, 20, 39, 19, 9, 8, 6, 5, 23, 22, 20, 39, 19, 9, 5, 40,
|
||||
20, 39, 9, 19, 5, 39, 9, 5, 9, 19, 6, 5, 39, 9, 5, 19, 6, 5, 9};
|
||||
u16 g_libSceLibreSsl3_modules[2] = {296, 130};
|
||||
u16 g_libcurl_modules[2] = {297, 289};
|
||||
u16 g_libicu_modules[2] = {298, 289};
|
||||
u16 g_libcairo_modules[9] = {299, 300, 301, 302, 303, 289, 298, 289, 289};
|
||||
u16 g_libfontconfig_modules[1] = {300};
|
||||
u16 g_libfreetype_modules[1] = {301};
|
||||
u16 g_libharfbuzz_modules[1] = {302};
|
||||
u16 g_libpng16_modules[2] = {303, 289};
|
||||
u16 g_libSceFontGs_modules[1] = {304};
|
||||
u16 g_libSceGLSlimClientVSH_modules[1] = {305};
|
||||
u16 g_libSceGLSlimServerVSH_modules[1] = {306};
|
||||
u16 g_libSceFontGsm_modules[1] = {307};
|
||||
u16 g_libSceNpPartnerSubscription_modules[1] = {308};
|
||||
u16 g_libSceNpAuthAuthorizedAppDialog_modules[1] = {309};
|
||||
|
||||
// This is the actual array of modules.
|
||||
constexpr u64 g_num_modules = 310;
|
||||
std::array<OrbisSysmoduleModuleInternal, g_num_modules> g_modules_array = std::to_array<
|
||||
OrbisSysmoduleModuleInternal>(
|
||||
{{0x0, -1, 0, 0, nullptr, nullptr, 0},
|
||||
{0x0, -1, 0, 1, "libkernel", nullptr, 0},
|
||||
{0x0, -1, 0, 1, "libSceLibcInternal", nullptr, 0},
|
||||
{0x0, -1, 0, 4, "libSceFios2", nullptr, 0},
|
||||
{0x0, -1, 0, 4, "libc", nullptr, 0},
|
||||
{0x8000001c, -1, 0, 1, "libSceNet", g_libSceNet_modules, 1},
|
||||
{0x8000001d, -1, 0, 1, "libSceIpmi", g_libSceIpmi_modules, 1},
|
||||
{0x8000001e, -1, 0, 1, "libSceMbus", g_libSceMbus_modules, 2},
|
||||
{0x8000001f, -1, 0, 1, "libSceRegMgr", g_libSceRegMgr_modules, 1},
|
||||
{0x80000020, -1, 0, 1, "libSceRtc", g_libSceRtc_modules, 1},
|
||||
{0x0, -1, 0, 0, nullptr, nullptr, 0},
|
||||
{0x80000021, -1, 0, 1, "libSceAvSetting", g_libSceAvSetting_modules, 3},
|
||||
{0x80000022, -1, 0, 1, "libSceVideoOut", g_libSceVideoOut_modules, 3},
|
||||
{0x80000052, -1, 0, 1025, "libSceGnmDriver", g_libSceGnmDriver_modules, 4},
|
||||
{0x80000001, -1, 0, 1, "libSceAudioOut", g_libSceAudioOut_modules, 4},
|
||||
{0x80000002, -1, 0, 1, "libSceAudioIn", g_libSceAudioIn_modules, 4},
|
||||
{0x80000023, -1, 0, 1, "libSceAjm", g_libSceAjm_modules, 1},
|
||||
{0x80000024, -1, 0, 1, "libScePad", g_libScePad_modules, 2},
|
||||
{0x80000025, -1, 0, 9, "libSceDbg", g_libSceDbg_debug_modules, 1},
|
||||
{0x80000009, -1, 0, 1, "libSceNetCtl", g_libSceNetCtl_modules, 2},
|
||||
{0x8000000a, -1, 0, 1, "libSceHttp", g_libSceHttp_modules, 5},
|
||||
{0x0, -1, 0, 1, "libSceSsl", g_libSceSsl_modules, 3},
|
||||
{0x8000000c, -1, 0, 1, "libSceNpCommon", g_libSceNpCommon_modules, 8},
|
||||
{0x8000000d, -1, 0, 1, "libSceNpManager", g_libSceNpManager_modules, 7},
|
||||
{0x8000000e, -1, 0, 1, "libSceNpWebApi", g_libSceNpWebApi_modules, 7},
|
||||
{0x8000000f, -1, 0, 1, "libSceSaveData", g_libSceSaveData_modules, 4},
|
||||
{0x80000010, -1, 0, 1, "libSceSystemService", g_libSceSystemService_modules, 3},
|
||||
{0x80000011, -1, 0, 1, "libSceUserService", g_libSceUserService_modules, 2},
|
||||
{0x80000018, -1, 0, 1, "libSceCommonDialog", g_libSceCommonDialog_modules, 1},
|
||||
{0x80000026, -1, 0, 1, "libSceSysUtil", g_libSceSysUtil_modules, 2},
|
||||
{0x80000019, -1, 0, 9, "libScePerf", g_libScePerf_debug_modules, 3},
|
||||
{0x8000001a, -1, 0, 1, "libSceCamera", g_libSceCamera_modules, 2},
|
||||
{0x0, -1, 0, 1, "libSceWebKit2ForVideoService", nullptr, 0},
|
||||
{0x0, -1, 0, 1, "libSceOrbisCompatForVideoService", nullptr, 0},
|
||||
{0xd7, -1, 0, 1, "libSceDiscMap", g_libSceDiscMap_modules, 1},
|
||||
{0x8000003d, -1, 0, 129, "libSceDbgAssist", g_libSceDbgAssist_modules, 1},
|
||||
{0x80000048, -1, 0, 9, "libSceMat", g_libSceMat_debug_modules, 1},
|
||||
{0x0, -1, 0, 1, "libSceRazorCpu", g_libSceRazorCpu_modules, 1},
|
||||
{0x80000075, -1, 0, 9, "libSceRazorCpu_debug", g_libSceRazorCpu_debug_debug_modules, 2},
|
||||
{0x8000000b, -1, 0, 1, "libSceSsl2", g_libSceSsl2_modules, 3},
|
||||
{0x8000008c, -1, 0, 1, "libSceHttp2", g_libSceHttp2_modules, 13},
|
||||
{0x8000008f, -1, 0, 1, "libSceNpWebApi2", g_libSceNpWebApi2_modules, 39},
|
||||
{0x8000008d, -1, 0, 1, "libSceNpGameIntent", g_libSceNpGameIntent_modules, 10},
|
||||
{0x0, -1, 0, 0, nullptr, nullptr, 0},
|
||||
{0x0, -1, 0, 0, nullptr, nullptr, 0},
|
||||
{0x0, -1, 0, 0, nullptr, nullptr, 0},
|
||||
{0x0, -1, 0, 0, nullptr, nullptr, 0},
|
||||
{0x0, -1, 0, 0, nullptr, nullptr, 0},
|
||||
{0x0, -1, 0, 0, nullptr, nullptr, 0},
|
||||
{0x6, -1, 0, 1, "libSceFiber", g_libSceFiber_modules, 5},
|
||||
{0x7, -1, 0, 1, "libSceUlt", g_libSceUlt_modules, 6},
|
||||
{0xb, -1, 0, 1, "libSceNgs2", g_libSceNgs2_modules, 2},
|
||||
{0x17, -1, 0, 1, "libSceXml", g_libSceXml_modules, 1},
|
||||
{0x19, -1, 0, 1, "libSceNpUtility", g_libSceNpUtility_modules, 5},
|
||||
{0x1a, -1, 0, 1, "libSceVoice", g_libSceVoice_modules, 4},
|
||||
{0x1c, -1, 0, 1, "libSceNpMatching2", g_libSceNpMatching2_modules, 7},
|
||||
{0x1e, -1, 0, 1, "libSceNpScoreRanking", g_libSceNpScoreRanking_modules, 3},
|
||||
{0x21, -1, 0, 1, "libSceRudp", g_libSceRudp_modules, 1},
|
||||
{0x2c, -1, 0, 1, "libSceNpTus", g_libSceNpTus_modules, 3},
|
||||
{0x38, -1, 0, 4, "libSceFace", g_libSceFace_modules, 1},
|
||||
{0x39, -1, 0, 4, "libSceSmart", g_libSceSmart_modules, 1},
|
||||
{0x80, -1, 0, 1, "libSceJson", g_libSceJson_modules, 1},
|
||||
{0x81, -1, 0, 1, "libSceGameLiveStreaming", g_libSceGameLiveStreaming_modules, 2},
|
||||
{0x82, -1, 0, 1, "libSceCompanionUtil", g_libSceCompanionUtil_modules, 3},
|
||||
{0x83, -1, 0, 1, "libScePlayGo", g_libScePlayGo_modules, 1},
|
||||
{0x84, -1, 0, 1, "libSceFont", g_libSceFont_modules, 1},
|
||||
{0x85, -1, 0, 1, "libSceVideoRecording", g_libSceVideoRecording_modules, 2},
|
||||
{0x88, -1, 0, 1, "libSceAudiodec", g_libSceAudiodec_modules, 2},
|
||||
{0x8a, -1, 0, 1, "libSceJpegDec", g_libSceJpegDec_modules, 1},
|
||||
{0x8b, -1, 0, 1, "libSceJpegEnc", g_libSceJpegEnc_modules, 1},
|
||||
{0x8c, -1, 0, 1, "libScePngDec", g_libScePngDec_modules, 1},
|
||||
{0x8d, -1, 0, 1, "libScePngEnc", g_libScePngEnc_modules, 1},
|
||||
{0x8e, -1, 0, 2049, "libSceVideodec", g_libSceVideodec_modules, 3},
|
||||
{0x8f, -1, 0, 1, "libSceMove", g_libSceMove_modules, 1},
|
||||
{0x0, -1, 0, 0, nullptr, nullptr, 0},
|
||||
{0x91, -1, 0, 1, "libScePadTracker", g_libScePadTracker_modules, 2},
|
||||
{0x92, -1, 0, 1, "libSceDepth", g_libSceDepth_modules, 2},
|
||||
{0x93, -1, 0, 4, "libSceHand", g_libSceHand_modules, 1},
|
||||
{0x95, -1, 0, 1, "libSceIme", g_libSceIme_modules, 2},
|
||||
{0x96, -1, 0, 1, "libSceImeDialog", g_libSceImeDialog_modules, 2},
|
||||
{0x80000015, -1, 0, 1, "libSceVdecCore", g_libSceVdecCore_modules, 1},
|
||||
{0x97, -1, 0, 1, "libSceNpParty", g_libSceNpParty_modules, 2},
|
||||
{0x80000003, -1, 0, 1, "libSceAvcap", g_libSceAvcap_modules, 2},
|
||||
{0x98, -1, 0, 1, "libSceFontFt", g_libSceFontFt_modules, 1},
|
||||
{0x99, -1, 0, 1, "libSceFreeTypeOt", g_libSceFreeTypeOt_modules, 1},
|
||||
{0x9a, -1, 0, 1, "libSceFreeTypeOl", g_libSceFreeTypeOl_modules, 1},
|
||||
{0x9b, -1, 0, 1, "libSceFreeTypeOptOl", g_libSceFreeTypeOptOl_modules, 1},
|
||||
{0x9c, -1, 0, 1, "libSceScreenShot", g_libSceScreenShot_modules, 3},
|
||||
{0x9d, -1, 0, 1, "libSceNpAuth", g_libSceNpAuth_modules, 3},
|
||||
{0x1b, -1, 0, 1, "libSceVoiceQos", g_libSceVoiceQos_modules, 5},
|
||||
{0x80000004, -1, 0, 1, "libSceSysCore", g_libSceSysCore_modules, 2},
|
||||
{0xbc, -1, 0, 1, "libSceM4aacEnc", g_libSceM4aacEnc_modules, 2},
|
||||
{0xbd, -1, 0, 1, "libSceAudiodecCpu", g_libSceAudiodecCpu_modules, 1},
|
||||
{0x80000007, -1, 0, 1, "libSceCdlgUtilServer", g_libSceCdlgUtilServer_modules, 2},
|
||||
{0x9f, -1, 0, 9, "libSceSulpha", g_libSceSulpha_debug_modules, 1},
|
||||
{0xa0, -1, 0, 1, "libSceSaveDataDialog", g_libSceSaveDataDialog_modules, 4},
|
||||
{0xa2, -1, 0, 1, "libSceInvitationDialog", g_libSceInvitationDialog_modules, 1},
|
||||
{0xa3, -1, 0, 2057, "libSceKeyboard", g_libSceKeyboard_debug_modules, 1},
|
||||
{0x106, -1, 0, 2049, "libSceKeyboard", g_libSceKeyboard_modules, 1},
|
||||
{0xa4, -1, 0, 1, "libSceMsgDialog", g_libSceMsgDialog_modules, 1},
|
||||
{0xa5, -1, 0, 1, "libSceAvPlayer", g_libSceAvPlayer_modules, 1},
|
||||
{0xa6, -1, 0, 1, "libSceContentExport", g_libSceContentExport_modules, 1},
|
||||
{0x80000012, -1, 0, 2, "libSceVisionManager", g_libSceVisionManager_modules, 1},
|
||||
{0x80000013, -1, 0, 2, "libSceAc3Enc", g_libSceAc3Enc_modules, 2},
|
||||
{0x80000014, -1, 0, 1, "libSceAppInstUtil", g_libSceAppInstUtil_modules, 1},
|
||||
{0x80000016, -1, 0, 514, "libSceVencCore", g_libSceVencCore_modules, 1},
|
||||
{0xa7, -1, 0, 1, "libSceAudio3d", g_libSceAudio3d_modules, 1},
|
||||
{0xa8, -1, 0, 1, "libSceNpCommerce", g_libSceNpCommerce_modules, 1},
|
||||
{0x80000017, -1, 0, 1, "libSceHidControl", g_libSceHidControl_modules, 1},
|
||||
{0xa9, -1, 0, 1, "libSceMouse", g_libSceMouse_modules, 1},
|
||||
{0xaa, -1, 0, 1, "libSceCompanionHttpd", g_libSceCompanionHttpd_modules, 1},
|
||||
{0xab, -1, 0, 1, "libSceWebBrowserDialog", g_libSceWebBrowserDialog_modules, 1},
|
||||
{0xac, -1, 0, 1, "libSceErrorDialog", g_libSceErrorDialog_modules, 1},
|
||||
{0xad, -1, 0, 1, "libSceNpTrophy", g_libSceNpTrophy_modules, 1},
|
||||
{0x0, -1, 0, 1, "ulobjmgr", g_ulobjmgr_modules, 1},
|
||||
{0xae, -1, 0, 1, "libSceVideoCoreInterface", g_libSceVideoCoreInterface_modules, 1},
|
||||
{0xaf, -1, 0, 1, "libSceVideoCoreServerInterface", g_libSceVideoCoreServerInterface_modules,
|
||||
1},
|
||||
{0x8000001b, -1, 0, 1, "libSceNpSns", g_libSceNpSns_modules, 1},
|
||||
{0xb0, -1, 0, 1, "libSceNpSnsFacebookDialog", g_libSceNpSnsFacebookDialog_modules, 2},
|
||||
{0xb1, -1, 0, 1, "libSceMoveTracker", g_libSceMoveTracker_modules, 1},
|
||||
{0xb2, -1, 0, 1, "libSceNpProfileDialog", g_libSceNpProfileDialog_modules, 1},
|
||||
{0xb3, -1, 0, 1, "libSceNpFriendListDialog", g_libSceNpFriendListDialog_modules, 1},
|
||||
{0xb4, -1, 0, 1, "libSceAppContent", g_libSceAppContent_modules, 1},
|
||||
{0x80000027, -1, 0, 2, "libSceMarlin", g_libSceMarlin_modules, 1},
|
||||
{0x80000028, -1, 0, 2, "libSceDtsEnc", g_libSceDtsEnc_modules, 2},
|
||||
{0xb5, -1, 0, 1, "libSceNpSignaling", g_libSceNpSignaling_modules, 1},
|
||||
{0xb6, -1, 0, 1, "libSceRemoteplay", g_libSceRemoteplay_modules, 1},
|
||||
{0xb7, -1, 0, 1, "libSceUsbd", g_libSceUsbd_modules, 1},
|
||||
{0xb8, -1, 0, 1, "libSceGameCustomDataDialog", g_libSceGameCustomDataDialog_modules, 1},
|
||||
{0xb9, -1, 0, 1, "libSceNpEulaDialog", g_libSceNpEulaDialog_modules, 1},
|
||||
{0xba, -1, 0, 1, "libSceRandom", g_libSceRandom_modules, 1},
|
||||
{0x80000029, -1, 0, 2, "libSceDipsw", g_libSceDipsw_modules, 1},
|
||||
{0x86, -1, 0, 4, "libSceS3DConversion", g_libSceS3DConversion_modules, 1},
|
||||
{0x8000003e, -1, 0, 9, "libSceOttvCapture", g_libSceOttvCapture_debug_modules, 1},
|
||||
{0x8000002a, -1, 0, 1, "libSceBgft", g_libSceBgft_modules, 1},
|
||||
{0xbe, -1, 0, 1, "libSceAudiodecCpuDdp", g_libSceAudiodecCpuDdp_modules, 1},
|
||||
{0xc0, -1, 0, 1, "libSceAudiodecCpuM4aac", g_libSceAudiodecCpuM4aac_modules, 1},
|
||||
{0x8000002b, -1, 0, 2, "libSceAudiodecCpuDts", g_libSceAudiodecCpuDts_modules, 1},
|
||||
{0xc9, -1, 0, 1, "libSceAudiodecCpuDtsHdLbr", g_libSceAudiodecCpuDtsHdLbr_modules, 1},
|
||||
{0x8000002d, -1, 0, 2, "libSceAudiodecCpuDtsHdMa", g_libSceAudiodecCpuDtsHdMa_modules, 1},
|
||||
{0x8000002e, -1, 0, 2, "libSceAudiodecCpuLpcm", g_libSceAudiodecCpuLpcm_modules, 1},
|
||||
{0xc1, -1, 0, 1, "libSceBemp2sys", g_libSceBemp2sys_modules, 1},
|
||||
{0xc2, -1, 0, 1, "libSceBeisobmf", g_libSceBeisobmf_modules, 1},
|
||||
{0xc3, -1, 0, 1, "libScePlayReady", g_libScePlayReady_modules, 1},
|
||||
{0xc4, -1, 0, 1, "libSceVideoNativeExtEssential", g_libSceVideoNativeExtEssential_modules, 1},
|
||||
{0xc5, -1, 0, 1, "libSceZlib", g_libSceZlib_modules, 1},
|
||||
{0x8000002f, -1, 0, 1, "libSceIduUtil", g_libSceIduUtil_modules, 1},
|
||||
{0x80000030, -1, 0, 1, "libScePsm", g_libScePsm_modules, 1},
|
||||
{0xc6, -1, 0, 1, "libSceDtcpIp", g_libSceDtcpIp_modules, 1},
|
||||
{0x80000031, -1, 0, 1, "libSceKbEmulate", g_libSceKbEmulate_modules, 1},
|
||||
{0x80000032, -1, 0, 2, "libSceAppChecker", g_libSceAppChecker_modules, 1},
|
||||
{0x80000033, -1, 0, 1, "libSceNpGriefReport", g_libSceNpGriefReport_modules, 1},
|
||||
{0xc7, -1, 0, 1, "libSceContentSearch", g_libSceContentSearch_modules, 1},
|
||||
{0xc8, -1, 0, 1, "libSceShareUtility", g_libSceShareUtility_modules, 1},
|
||||
{0x80000034, -1, 0, 1, "libSceWeb", g_libSceWeb_modules, 6},
|
||||
{0x8000006a, -1, 0, 1, "libSceWebKit2", g_libSceWebKit2_modules, 30},
|
||||
{0xca, -1, 0, 9, "libSceDeci4h", g_libSceDeci4h_debug_modules, 1},
|
||||
{0xcb, -1, 0, 4, "libSceHeadTracker", g_libSceHeadTracker_modules, 1},
|
||||
{0xcc, -1, 0, 1, "libSceGameUpdate", g_libSceGameUpdate_modules, 2},
|
||||
{0xcd, -1, 0, 1, "libSceAutoMounterClient", g_libSceAutoMounterClient_modules, 2},
|
||||
{0xce, -1, 0, 1, "libSceSystemGesture", g_libSceSystemGesture_modules, 1},
|
||||
{0x80000035, -1, 0, 1, "libSceVdecSavc", g_libSceVdecSavc_modules, 1},
|
||||
{0x80000036, -1, 0, 1, "libSceVdecSavc2", g_libSceVdecSavc2_modules, 1},
|
||||
{0xcf, -1, 0, 2049, "libSceVideodec2", g_libSceVideodec2_modules, 3},
|
||||
{0xd0, -1, 0, 1, "libSceVdecwrap", g_libSceVdecwrap_modules, 2},
|
||||
{0x80000037, -1, 0, 1, "libSceVshctl", g_libSceVshctl_modules, 1},
|
||||
{0xd1, -1, 0, 1, "libSceAt9Enc", g_libSceAt9Enc_modules, 1},
|
||||
{0xd2, -1, 0, 1, "libSceConvertKeycode", g_libSceConvertKeycode_modules, 1},
|
||||
{0x80000039, -1, 0, 1, "libSceGpuException", g_libSceGpuException_modules, 1},
|
||||
{0xd3, -1, 0, 1, "libSceSharePlay", g_libSceSharePlay_modules, 1},
|
||||
{0x8000003a, -1, 0, 2, "libSceAudiodReport", g_libSceAudiodReport_modules, 1},
|
||||
{0x8000003b, -1, 0, 2, "libSceSulphaDrv", g_libSceSulphaDrv_modules, 1},
|
||||
{0xd4, -1, 0, 1, "libSceHmd", g_libSceHmd_modules, 1},
|
||||
{0xd5, -1, 0, 1, "libSceUsbStorage", g_libSceUsbStorage_modules, 2},
|
||||
{0x8000003c, -1, 0, 1, "libSceVdecShevc", g_libSceVdecShevc_modules, 1},
|
||||
{0xd6, -1, 0, 1, "libSceUsbStorageDialog", g_libSceUsbStorageDialog_modules, 1},
|
||||
{0xd8, -1, 0, 4, "libSceFaceTracker", g_libSceFaceTracker_modules, 2},
|
||||
{0xd9, -1, 0, 4, "libSceHandTracker", g_libSceHandTracker_modules, 1},
|
||||
{0xda, -1, 0, 1, "libSceNpSnsYouTubeDialog", g_libSceNpSnsYouTubeDialog_modules, 2},
|
||||
{0xed, -1, 0, 1, "libSceVrTracker", g_libSceVrTracker_modules, 6},
|
||||
{0xdc, -1, 0, 1, "libSceProfileCacheExternal", g_libSceProfileCacheExternal_modules, 2},
|
||||
{0x8000003f, -1, 0, 1, "libSceBackupRestoreUtil", g_libSceBackupRestoreUtil_modules, 1},
|
||||
{0xdd, -1, 0, 1, "libSceMusicPlayerService", g_libSceMusicPlayerService_modules, 2},
|
||||
{0x0, -1, 0, 1, "libSceMusicCoreServerClientJsEx", g_libSceMusicCoreServerClientJsEx_modules,
|
||||
1},
|
||||
{0xde, -1, 0, 1, "libSceSpSysCallWrapper", g_libSceSpSysCallWrapper_modules, 3},
|
||||
{0xdf, -1, 0, 1, "libScePs2EmuMenuDialog", g_libScePs2EmuMenuDialog_modules, 1},
|
||||
{0xe0, -1, 0, 1, "libSceNpSnsDailyMotionDialog", g_libSceNpSnsDailyMotionDialog_modules, 1},
|
||||
{0xe1, -1, 0, 1, "libSceAudiodecCpuHevag", g_libSceAudiodecCpuHevag_modules, 1},
|
||||
{0xe2, -1, 0, 1, "libSceLoginDialog", g_libSceLoginDialog_modules, 2},
|
||||
{0xe3, -1, 0, 1, "libSceLoginService", g_libSceLoginService_modules, 2},
|
||||
{0xe4, -1, 0, 1, "libSceSigninDialog", g_libSceSigninDialog_modules, 2},
|
||||
{0xe5, -1, 0, 1, "libSceVdecsw", g_libSceVdecsw_modules, 3},
|
||||
{0x8000006d, -1, 0, 1, "libSceOrbisCompat", g_libSceOrbisCompat_modules, 24},
|
||||
{0x0, -1, 0, 1, "libSceCoreIPC", g_libSceCoreIPC_modules, 1},
|
||||
{0xe6, -1, 0, 1, "libSceCustomMusicCore", g_libSceCustomMusicCore_modules, 12},
|
||||
{0xe7, -1, 0, 1, "libSceJson2", g_libSceJson2_modules, 1},
|
||||
{0xe8, -1, 0, 4, "libSceAudioLatencyEstimation", g_libSceAudioLatencyEstimation_modules, 1},
|
||||
{0xe9, -1, 0, 1, "libSceWkFontConfig", g_libSceWkFontConfig_modules, 1},
|
||||
{0xea, -1, 0, 2, "libSceVorbisDec", g_libSceVorbisDec_modules, 3},
|
||||
{0x80000041, -1, 0, 1, "libSceTtsCoreEnUs", g_libSceTtsCoreEnUs_modules, 1},
|
||||
{0x80000042, -1, 0, 1, "libSceTtsCoreJp", g_libSceTtsCoreJp_modules, 1},
|
||||
{0x80000043, -1, 0, 1, "libSceOpusCeltEnc", g_libSceOpusCeltEnc_modules, 2},
|
||||
{0x80000044, -1, 0, 1, "libSceOpusCeltDec", g_libSceOpusCeltDec_modules, 2},
|
||||
{0x80000045, -1, 0, 2, "libSceLoginMgrServer", g_libSceLoginMgrServer_modules, 1},
|
||||
{0xeb, -1, 0, 1, "libSceHmdSetupDialog", g_libSceHmdSetupDialog_modules, 1},
|
||||
{0x80000046, -1, 0, 1, "libSceVideoOutSecondary", g_libSceVideoOutSecondary_modules, 6},
|
||||
{0xee, -1, 0, 1, "libSceContentDelete", g_libSceContentDelete_modules, 1},
|
||||
{0xef, -1, 0, 1, "libSceImeBackend", g_libSceImeBackend_modules, 1},
|
||||
{0xf0, -1, 0, 1, "libSceNetCtlApDialog", g_libSceNetCtlApDialog_modules, 1},
|
||||
{0x80000047, -1, 0, 1, "libSceGnmResourceRegistration",
|
||||
g_libSceGnmResourceRegistration_modules, 1},
|
||||
{0xf1, -1, 0, 1, "libScePlayGoDialog", g_libScePlayGoDialog_modules, 1},
|
||||
{0xf2, -1, 0, 1, "libSceSocialScreen", g_libSceSocialScreen_modules, 7},
|
||||
{0xf3, -1, 0, 1, "libSceEditMp4", g_libSceEditMp4_modules, 1},
|
||||
{0x0, -1, 0, 0, nullptr, nullptr, 0},
|
||||
{0x0, -1, 0, 0, nullptr, nullptr, 0},
|
||||
{0x0, -1, 0, 0, nullptr, nullptr, 0},
|
||||
{0x0, -1, 0, 0, nullptr, nullptr, 0},
|
||||
{0x0, -1, 0, 0, nullptr, nullptr, 0},
|
||||
{0x0, -1, 0, 0, nullptr, nullptr, 0},
|
||||
{0x0, -1, 0, 0, nullptr, nullptr, 0},
|
||||
{0x0, -1, 0, 0, nullptr, nullptr, 0},
|
||||
{0xf5, -1, 0, 1, "libScePsmKitSystem", g_libScePsmKitSystem_modules, 1},
|
||||
{0xf6, -1, 0, 1, "libSceTextToSpeech", g_libSceTextToSpeech_modules, 1},
|
||||
{0xf7, -1, 0, 2052, "libSceNpToolkit", g_libSceNpToolkit_modules, 1},
|
||||
{0xf8, -1, 0, 1, "libSceCustomMusicService", g_libSceCustomMusicService_modules, 2},
|
||||
{0xf9, -1, 0, 1, "libSceClSysCallWrapper", g_libSceClSysCallWrapper_modules, 11},
|
||||
{0x80000049, -1, 0, 1, "libSceScm", g_libSceScm_modules, 1},
|
||||
{0xfa, -1, 0, 1, "libSceSystemLogger", g_libSceSystemLogger_modules, 2},
|
||||
{0xfb, -1, 0, 1, "libSceBluetoothHid", g_libSceBluetoothHid_modules, 1},
|
||||
{0x80000050, -1, 0, 1, "libSceAvPlayerStreaming", g_libSceAvPlayerStreaming_modules, 1},
|
||||
{0x80000051, -1, 0, 2, "libSceAudiodecCpuAlac", g_libSceAudiodecCpuAlac_modules, 1},
|
||||
{0xfc, -1, 0, 1, "libSceVideoDecoderArbitration", g_libSceVideoDecoderArbitration_modules, 1},
|
||||
{0xfd, -1, 0, 1, "libSceVrServiceDialog", g_libSceVrServiceDialog_modules, 1},
|
||||
{0xfe, -1, 0, 4, "libSceJobManager", g_libSceJobManager_modules, 2},
|
||||
{0x80000053, -1, 0, 2, "libSceAudiodecCpuFlac", g_libSceAudiodecCpuFlac_modules, 1},
|
||||
{0x103, -1, 0, 1, "libSceSrcUtl", g_libSceSrcUtl_modules, 2},
|
||||
{0x80000055, -1, 0, 2, "libSceS3da", g_libSceS3da_modules, 1},
|
||||
{0x80000056, -1, 0, 2, "libSceDseehx", g_libSceDseehx_modules, 1},
|
||||
{0xff, -1, 0, 1, "libSceShareFactoryUtil", g_libSceShareFactoryUtil_modules, 1},
|
||||
{0x80000057, -1, 0, 1, "libSceDataTransfer", g_libSceDataTransfer_modules, 1},
|
||||
{0x100, -1, 0, 1, "libSceSocialScreenDialog", g_libSceSocialScreenDialog_modules, 1},
|
||||
{0x80000058, -1, 0, 1, "libSceAbstractStorage", g_libSceAbstractStorage_modules, 1},
|
||||
{0x80000059, -1, 0, 1, "libSceImageUtil", g_libSceImageUtil_modules, 1},
|
||||
{0x8000005a, -1, 0, 1, "libSceMetadataReaderWriter", g_libSceMetadataReaderWriter_modules, 1},
|
||||
{0x8000005b, -1, 0, 1, "libSceJpegParser", g_libSceJpegParser_modules, 1},
|
||||
{0x8000005c, -1, 0, 1, "libSceGvMp4Parser", g_libSceGvMp4Parser_modules, 1},
|
||||
{0x8000005d, -1, 0, 1, "libScePngParser", g_libScePngParser_modules, 1},
|
||||
{0x8000005e, -1, 0, 1, "libSceGifParser", g_libSceGifParser_modules, 1},
|
||||
{0x101, -1, 0, 1, "libSceNpSnsDialog", g_libSceNpSnsDialog_modules, 2},
|
||||
{0x8000005f, -1, 0, 1, "libSceAbstractLocal", g_libSceAbstractLocal_modules, 1},
|
||||
{0x80000060, -1, 0, 1, "libSceAbstractFacebook", g_libSceAbstractFacebook_modules, 1},
|
||||
{0x80000061, -1, 0, 1, "libSceAbstractYoutube", g_libSceAbstractYoutube_modules, 1},
|
||||
{0x80000062, -1, 0, 1, "libSceAbstractTwitter", g_libSceAbstractTwitter_modules, 1},
|
||||
{0x80000063, -1, 0, 1, "libSceAbstractDailymotion", g_libSceAbstractDailymotion_modules, 1},
|
||||
{0x102, -1, 0, 2052, "libSceNpToolkit2", g_libSceNpToolkit2_modules, 1},
|
||||
{0x80000064, -1, 0, 1, "libScePrecompiledShaders", g_libScePrecompiledShaders_modules, 1},
|
||||
{0x104, -1, 0, 1, "libSceDiscId", g_libSceDiscId_modules, 1},
|
||||
{0x80000065, -1, 0, 1, "libSceLibreSsl", g_libSceLibreSsl_modules, 2},
|
||||
{0x80000066, -1, 0, 2, "libSceFsInternalForVsh", g_libSceFsInternalForVsh_modules, 1},
|
||||
{0x105, -1, 0, 1, "libSceNpUniversalDataSystem", g_libSceNpUniversalDataSystem_modules, 1},
|
||||
{0x80000067, -1, 0, 1, "libSceDolbyVision", g_libSceDolbyVision_modules, 1},
|
||||
{0x80000068, -1, 0, 1, "libSceOpusSilkEnc", g_libSceOpusSilkEnc_modules, 2},
|
||||
{0x80000069, -1, 0, 1, "libSceOpusDec", g_libSceOpusDec_modules, 2},
|
||||
{0x8000006b, -1, 0, 1, "libSceWebKit2Secure", g_libSceWebKit2Secure_modules, 34},
|
||||
{0x8000006c, -1, 0, 1, "libSceJscCompiler", g_libSceJscCompiler_modules, 1},
|
||||
{0x8000006e, -1, 0, 1, "libSceJitBridge", g_libSceJitBridge_modules, 4},
|
||||
{0x0, -1, 0, 1, "libScePigletv2VSH", g_libScePigletv2VSH_modules, 4},
|
||||
{0x8000006f, -1, 0, 4096, "libSceJitBridge", g_libSceJitBridge_common_ex_modules, 4},
|
||||
{0x80000070, -1, 0, 4096, "libSceJscCompiler", g_libSceJscCompiler_common_ex_modules, 1},
|
||||
{0x80000071, -1, 0, 4096, "libSceOrbisCompat", g_libSceOrbisCompat_common_ex_modules, 24},
|
||||
{0x80000072, -1, 0, 4096, "libSceWeb", g_libSceWeb_common_ex_modules, 6},
|
||||
{0x80000073, -1, 0, 4096, "libSceWebKit2", g_libSceWebKit2_common_ex_modules, 30},
|
||||
{0x80000074, -1, 0, 4096, "libSceWebKit2Secure", g_libSceWebKit2Secure_common_ex_modules, 34},
|
||||
{0x0, -1, 0, 4096, "libScePrecompiledShaders", g_libScePrecompiledShaders_common_ex_modules,
|
||||
1},
|
||||
{0x107, -1, 0, 1, "libSceGic", g_libSceGic_modules, 1},
|
||||
{0x80000076, -1, 0, 1, "libSceRnpsAppMgr", g_libSceRnpsAppMgr_modules, 1},
|
||||
{0x80000077, -1, 0, 1, "libSceAsyncStorageInternal", g_libSceAsyncStorageInternal_modules, 1},
|
||||
{0x80000078, -1, 0, 1, "libSceHttpCache", g_libSceHttpCache_modules, 1},
|
||||
{0x108, -1, 0, 1, "libScePlayReady2", g_libScePlayReady2_modules, 1},
|
||||
{0x109, -1, 0, 9, "libSceHdrScopes", g_libSceHdrScopes_debug_modules, 1},
|
||||
{0x80000079, -1, 0, 1, "libSceNKWeb", g_libSceNKWeb_modules, 1},
|
||||
{0x8000007a, -1, 0, 1, "libSceNKWebKit", g_libSceNKWebKit_modules, 2},
|
||||
{0x0, -1, 0, 1, "libSceNKWebKitRequirements", g_libSceNKWebKitRequirements_modules, 1},
|
||||
{0x8000007c, -1, 0, 1, "libSceVnaInternal", g_libSceVnaInternal_modules, 1},
|
||||
{0x8000007d, -1, 0, 1, "libSceVnaWebsocket", g_libSceVnaWebsocket_modules, 1},
|
||||
{0x10c, -1, 0, 1, "libSceCesCs", g_libSceCesCs_modules, 1},
|
||||
{0x8000008a, -1, 0, 2, "libSceComposite", g_libSceComposite_modules, 1},
|
||||
{0x8000008b, -1, 0, 1, "libSceCompositeExt", g_libSceCompositeExt_modules, 1},
|
||||
{0x116, -1, 0, 1, "libSceHubAppUtil", g_libSceHubAppUtil_modules, 1},
|
||||
{0x80000098, -1, 0, 1, "libScePosixForWebKit", g_libScePosixForWebKit_modules, 1},
|
||||
{0x11a, -1, 0, 1, "libSceNpPartner001", g_libSceNpPartner001_modules, 1},
|
||||
{0x112, -1, 0, 1, "libSceNpSessionSignaling", g_libSceNpSessionSignaling_modules, 75},
|
||||
{0x10d, -1, 0, 1, "libScePlayerInvitationDialog", g_libScePlayerInvitationDialog_modules, 1},
|
||||
{0x115, -1, 0, 4, "libSceNpCppWebApi", g_libSceNpCppWebApi_modules, 42},
|
||||
{0x113, -1, 0, 1, "libSceNpEntitlementAccess", g_libSceNpEntitlementAccess_modules, 1},
|
||||
{0x8000009a, -1, 0, 2, "libSceNpRemotePlaySessionSignaling",
|
||||
g_libSceNpRemotePlaySessionSignaling_modules, 76},
|
||||
{0x800000b8, -1, 0, 1, "libSceLibreSsl3", g_libSceLibreSsl3_modules, 2},
|
||||
{0x800000b1, -1, 0, 1, "libcurl", g_libcurl_modules, 2},
|
||||
{0x800000aa, -1, 0, 1, "libicu", g_libicu_modules, 2},
|
||||
{0x800000ac, -1, 0, 1, "libcairo", g_libcairo_modules, 9},
|
||||
{0x0, -1, 0, 1, "libfontconfig", g_libfontconfig_modules, 1},
|
||||
{0x0, -1, 0, 1, "libfreetype", g_libfreetype_modules, 1},
|
||||
{0x0, -1, 0, 1, "libharfbuzz", g_libharfbuzz_modules, 1},
|
||||
{0x800000ab, -1, 0, 1, "libpng16", g_libpng16_modules, 2},
|
||||
{0x12f, -1, 0, 1, "libSceFontGs", g_libSceFontGs_modules, 1},
|
||||
{0x800000c0, -1, 0, 1, "libSceGLSlimClientVSH", g_libSceGLSlimClientVSH_modules, 1},
|
||||
{0x800000c1, -1, 0, 1, "libSceGLSlimServerVSH", g_libSceGLSlimServerVSH_modules, 1},
|
||||
{0x135, -1, 0, 4, "libSceFontGsm", g_libSceFontGsm_modules, 1},
|
||||
{0x138, -1, 0, 1, "libSceNpPartnerSubscription", g_libSceNpPartnerSubscription_modules, 1},
|
||||
{0x139, -1, 0, 1, "libSceNpAuthAuthorizedAppDialog", g_libSceNpAuthAuthorizedAppDialog_modules,
|
||||
1}});
|
||||
|
||||
} // namespace Libraries::SysModule
|
||||
@ -1,169 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#define MAGIC_ENUM_RANGE_MIN 0
|
||||
#define MAGIC_ENUM_RANGE_MAX 300
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/kernel/process.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/system/sysmodule.h"
|
||||
#include "core/libraries/system/system_error.h"
|
||||
|
||||
namespace Libraries::SysModule {
|
||||
|
||||
int PS4_SYSV_ABI sceSysmoduleGetModuleHandleInternal() {
|
||||
LOG_ERROR(Lib_SysModule, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceSysmoduleGetModuleInfoForUnwind(VAddr addr, s32 flags,
|
||||
Kernel::OrbisModuleInfoForUnwind* info) {
|
||||
LOG_TRACE(Lib_SysModule, "sceSysmoduleGetModuleInfoForUnwind(addr=0x{:X}, flags=0x{:X})", addr,
|
||||
flags);
|
||||
|
||||
s32 res = Kernel::sceKernelGetModuleInfoForUnwind(addr, flags, info);
|
||||
if (res != 0) {
|
||||
return res;
|
||||
}
|
||||
|
||||
static constexpr std::array<std::string_view, 17> modules_to_hide = {
|
||||
"libc.prx",
|
||||
"libc.sprx",
|
||||
"libSceAudioLatencyEstimation.prx",
|
||||
"libSceFace.prx",
|
||||
"libSceFaceTracker.prx",
|
||||
"libSceFios2.prx",
|
||||
"libSceFios2.sprx",
|
||||
"libSceFontGsm.prx",
|
||||
"libSceHand.prx",
|
||||
"libSceHandTracker.prx",
|
||||
"libSceHeadTracker.prx",
|
||||
"libSceJobManager.prx",
|
||||
"libSceNpCppWebApi.prx",
|
||||
"libSceNpToolkit.prx",
|
||||
"libSceNpToolkit2.prx",
|
||||
"libSceS3DConversion.prx",
|
||||
"libSceSmart.prx",
|
||||
};
|
||||
|
||||
const std::string_view module_name = info->name.data();
|
||||
if (std::ranges::find(modules_to_hide, module_name) != modules_to_hide.end()) {
|
||||
std::ranges::fill(info->name, '\0');
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSysmoduleIsCalledFromSysModule() {
|
||||
LOG_ERROR(Lib_SysModule, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSysmoduleIsCameraPreloaded() {
|
||||
LOG_ERROR(Lib_SysModule, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSysmoduleIsLoaded(OrbisSysModule id) {
|
||||
LOG_ERROR(Lib_SysModule, "(DUMMY) called module = {}", magic_enum::enum_name(id));
|
||||
if (static_cast<u16>(id) == 0) {
|
||||
LOG_ERROR(Lib_SysModule, "Invalid sysmodule ID: {:#x}", static_cast<u16>(id));
|
||||
return ORBIS_SYSMODULE_INVALID_ID;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSysmoduleIsLoadedInternal(OrbisSysModuleInternal id) {
|
||||
LOG_ERROR(Lib_SysModule, "(DUMMY) called module = {:#x}", static_cast<u32>(id));
|
||||
if ((static_cast<u32>(id) & 0x7FFFFFFF) == 0) {
|
||||
LOG_ERROR(Lib_SysModule, "Invalid internal sysmodule ID: {:#x}", static_cast<u32>(id));
|
||||
return ORBIS_SYSMODULE_INVALID_ID;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSysmoduleLoadModule(OrbisSysModule id) {
|
||||
LOG_ERROR(Lib_SysModule, "(DUMMY) called module = {}", magic_enum::enum_name(id));
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSysmoduleLoadModuleByNameInternal() {
|
||||
LOG_ERROR(Lib_SysModule, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSysmoduleLoadModuleInternal() {
|
||||
LOG_ERROR(Lib_SysModule, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSysmoduleLoadModuleInternalWithArg() {
|
||||
LOG_ERROR(Lib_SysModule, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSysmoduleMapLibcForLibkernel() {
|
||||
LOG_ERROR(Lib_SysModule, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSysmodulePreloadModuleForLibkernel() {
|
||||
LOG_ERROR(Lib_SysModule, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSysmoduleUnloadModule() {
|
||||
LOG_ERROR(Lib_SysModule, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSysmoduleUnloadModuleByNameInternal() {
|
||||
LOG_ERROR(Lib_SysModule, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSysmoduleUnloadModuleInternal() {
|
||||
LOG_ERROR(Lib_SysModule, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSysmoduleUnloadModuleInternalWithArg() {
|
||||
LOG_ERROR(Lib_SysModule, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("D8cuU4d72xM", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmoduleGetModuleHandleInternal);
|
||||
LIB_FUNCTION("4fU5yvOkVG4", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmoduleGetModuleInfoForUnwind);
|
||||
LIB_FUNCTION("ctfO7dQ7geg", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmoduleIsCalledFromSysModule);
|
||||
LIB_FUNCTION("no6T3EfiS3E", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmoduleIsCameraPreloaded);
|
||||
LIB_FUNCTION("fMP5NHUOaMk", "libSceSysmodule", 1, "libSceSysmodule", sceSysmoduleIsLoaded);
|
||||
LIB_FUNCTION("ynFKQ5bfGks", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmoduleIsLoadedInternal);
|
||||
LIB_FUNCTION("g8cM39EUZ6o", "libSceSysmodule", 1, "libSceSysmodule", sceSysmoduleLoadModule);
|
||||
LIB_FUNCTION("CU8m+Qs+HN4", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmoduleLoadModuleByNameInternal);
|
||||
LIB_FUNCTION("39iV5E1HoCk", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmoduleLoadModuleInternal);
|
||||
LIB_FUNCTION("hHrGoGoNf+s", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmoduleLoadModuleInternalWithArg);
|
||||
LIB_FUNCTION("lZ6RvVl0vo0", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmoduleMapLibcForLibkernel);
|
||||
LIB_FUNCTION("DOO+zuW1lrE", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmodulePreloadModuleForLibkernel);
|
||||
LIB_FUNCTION("eR2bZFAAU0Q", "libSceSysmodule", 1, "libSceSysmodule", sceSysmoduleUnloadModule);
|
||||
LIB_FUNCTION("vpTHmA6Knvg", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmoduleUnloadModuleByNameInternal);
|
||||
LIB_FUNCTION("vXZhrtJxkGc", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmoduleUnloadModuleInternal);
|
||||
LIB_FUNCTION("aKa6YfBKZs4", "libSceSysmodule", 1, "libSceSysmodule",
|
||||
sceSysmoduleUnloadModuleInternalWithArg);
|
||||
};
|
||||
|
||||
} // namespace Libraries::SysModule
|
||||
@ -1,194 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/kernel/process.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
namespace Libraries::SysModule {
|
||||
|
||||
enum class OrbisSysModule : u16 {
|
||||
ORBIS_SYSMODULE_INVALID = 0x0000,
|
||||
ORBIS_SYSMODULE_FIBER = 0x0006, // libSceFiber.sprx
|
||||
ORBIS_SYSMODULE_ULT = 0x0007, // libSceUlt.sprx
|
||||
ORBIS_SYSMODULE_NGS2 = 0x000B, // libSceNgs2.sprx
|
||||
ORBIS_SYSMODULE_XML = 0x0017, // libSceXml.sprx
|
||||
ORBIS_SYSMODULE_NP_UTILITY = 0x0019, // libSceNpUtility.sprx
|
||||
ORBIS_SYSMODULE_VOICE = 0x001A, // libSceVoice.sprx
|
||||
ORBIS_SYSMODULE_VOICEQOS = 0x001B, // libSceVoiceQos.sprx
|
||||
ORBIS_SYSMODULE_NP_MATCHING2 = 0x001C, // libSceNpMatching2.sprx
|
||||
ORBIS_SYSMODULE_NP_SCORE_RANKING = 0x001E, // libSceNpScoreRanking.sprx
|
||||
ORBIS_SYSMODULE_RUDP = 0x0021, // libSceRudp.sprx
|
||||
ORBIS_SYSMODULE_NP_TUS = 0x002C, // libSceNpTus.sprx
|
||||
ORBIS_SYSMODULE_FACE = 0x0038, // libSceFace.sprx
|
||||
ORBIS_SYSMODULE_SMART = 0x0039, // libSceSmart.sprx
|
||||
ORBIS_SYSMODULE_JSON = 0x0080, // libSceJson.sprx
|
||||
ORBIS_SYSMODULE_GAME_LIVE_STREAMING = 0x0081, // libSceGameLiveStreaming.sprx
|
||||
ORBIS_SYSMODULE_COMPANION_UTIL = 0x0082, // libSceCompanionUtil.sprx
|
||||
ORBIS_SYSMODULE_PLAYGO = 0x0083, // libScePlayGo.sprx
|
||||
ORBIS_SYSMODULE_FONT = 0x0084, // libSceFont.sprx
|
||||
ORBIS_SYSMODULE_VIDEO_RECORDING = 0x0085, // libSceVideoRecording.sprx
|
||||
ORBIS_SYSMODULE_S3DCONVERSION = 0x0086, // libSceS3DConversion
|
||||
ORBIS_SYSMODULE_AUDIODEC = 0x0088, // libSceAudiodec.sprx
|
||||
ORBIS_SYSMODULE_JPEG_DEC = 0x008A, // libSceJpegDec.sprx
|
||||
ORBIS_SYSMODULE_JPEG_ENC = 0x008B, // libSceJpegEnc.sprx
|
||||
ORBIS_SYSMODULE_PNG_DEC = 0x008C, // libScePngDec.sprx
|
||||
ORBIS_SYSMODULE_PNG_ENC = 0x008D, // libScePngEnc.sprx
|
||||
ORBIS_SYSMODULE_VIDEODEC = 0x008E, // libSceVideodec.sprx
|
||||
ORBIS_SYSMODULE_MOVE = 0x008F, // libSceMove.sprx
|
||||
ORBIS_SYSMODULE_PAD_TRACKER = 0x0091, // libScePadTracker.sprx
|
||||
ORBIS_SYSMODULE_DEPTH = 0x0092, // libSceDepth.sprx
|
||||
ORBIS_SYSMODULE_HAND = 0x0093, // libSceHand.sprx
|
||||
ORBIS_SYSMODULE_LIBIME = 0x0095, // libSceIme.sprx
|
||||
ORBIS_SYSMODULE_IME_DIALOG = 0x0096, // libSceImeDialog.sprx
|
||||
ORBIS_SYSMODULE_NP_PARTY = 0x0097, // libSceNpParty.sprx
|
||||
ORBIS_SYSMODULE_FONT_FT = 0x0098, // libSceFontFt.sprx
|
||||
ORBIS_SYSMODULE_FREETYPE_OT = 0x0099, // libSceFreeTypeOt.sprx
|
||||
ORBIS_SYSMODULE_FREETYPE_OL = 0x009A, // libSceFreeTypeOl.sprx
|
||||
ORBIS_SYSMODULE_FREETYPE_OPT_OL = 0x009B, // libSceFreeTypeOptOl.sprx
|
||||
ORBIS_SYSMODULE_SCREEN_SHOT = 0x009C, // libSceScreenShot.sprx
|
||||
ORBIS_SYSMODULE_NP_AUTH = 0x009D, // libSceNpAuth.sprx
|
||||
ORBIS_SYSMODULE_SULPHA = 0x009F,
|
||||
ORBIS_SYSMODULE_SAVE_DATA_DIALOG = 0x00A0, // libSceSaveDataDialog.sprx
|
||||
ORBIS_SYSMODULE_INVITATION_DIALOG = 0x00A2, // libSceInvitationDialog.sprx
|
||||
ORBIS_SYSMODULE_DEBUG_KEYBOARD = 0x00A3,
|
||||
ORBIS_SYSMODULE_MESSAGE_DIALOG = 0x00A4, // libSceMsgDialog.sprx
|
||||
ORBIS_SYSMODULE_AV_PLAYER = 0x00A5, // libSceAvPlayer.sprx
|
||||
ORBIS_SYSMODULE_CONTENT_EXPORT = 0x00A6, // libSceContentExport.sprx
|
||||
ORBIS_SYSMODULE_AUDIO_3D = 0x00A7, // libSceAudio3d.sprx
|
||||
ORBIS_SYSMODULE_NP_COMMERCE = 0x00A8, // libSceNpCommerce.sprx
|
||||
ORBIS_SYSMODULE_MOUSE = 0x00A9, // libSceMouse.sprx
|
||||
ORBIS_SYSMODULE_COMPANION_HTTPD = 0x00AA, // libSceCompanionHttpd.sprx
|
||||
ORBIS_SYSMODULE_WEB_BROWSER_DIALOG = 0x00AB, // libSceWebBrowserDialog.sprx
|
||||
ORBIS_SYSMODULE_ERROR_DIALOG = 0x00AC, // libSceErrorDialog.sprx
|
||||
ORBIS_SYSMODULE_NP_TROPHY = 0x00AD, // libSceNpTrophy.sprx
|
||||
ORBIS_SYSMODULE_VIDEO_CORE_IF = 0x00AE, // libSceVideoCoreInterface.sprx
|
||||
ORBIS_SYSMODULE_VIDEO_CORE_SERVER_IF = 0x00AF, // libSceVideoCoreServerInterface.sprx
|
||||
ORBIS_SYSMODULE_NP_SNS_FACEBOOK = 0x00B0, // libSceNpSnsFacebookDialog.sprx
|
||||
ORBIS_SYSMODULE_MOVE_TRACKER = 0x00B1, // libSceMoveTracker.sprx
|
||||
ORBIS_SYSMODULE_NP_PROFILE_DIALOG = 0x00B2, // libSceNpProfileDialog.sprx
|
||||
ORBIS_SYSMODULE_NP_FRIEND_LIST_DIALOG = 0x00B3, // libSceNpFriendListDialog.sprx
|
||||
ORBIS_SYSMODULE_APP_CONTENT = 0x00B4, // libSceAppContent.sprx
|
||||
ORBIS_SYSMODULE_NP_SIGNALING = 0x00B5, // libSceNpSignaling.sprx
|
||||
ORBIS_SYSMODULE_REMOTE_PLAY = 0x00B6, // libSceRemoteplay.sprx
|
||||
ORBIS_SYSMODULE_USBD = 0x00B7, // libSceUsbd.sprx
|
||||
ORBIS_SYSMODULE_GAME_CUSTOM_DATA_DIALOG = 0x00B8, // libSceGameCustomDataDialog.sprx
|
||||
ORBIS_SYSMODULE_NP_EULA_DIALOG = 0x00B9, // libSceNpEulaDialog.sprx
|
||||
ORBIS_SYSMODULE_RANDOM = 0x00BA, // libSceRandom.sprx
|
||||
ORBIS_SYSMODULE_RESERVED2 = 0x00BB,
|
||||
ORBIS_SYSMODULE_M4AAC_ENC = 0x00BC, // libSceM4aacEnc.sprx
|
||||
ORBIS_SYSMODULE_AUDIODEC_CPU = 0x00BD, // libSceAudiodecCpu.sprx
|
||||
ORBIS_SYSMODULE_AUDIODEC_CPU_DDP = 0x00BE, // libSceAudiodecCpuDdp.sprx
|
||||
ORBIS_SYSMODULE_AUDIODEC_CPU_M4AAC = 0x00C0, // libSceAudiodecCpuM4aac.sprx
|
||||
ORBIS_SYSMODULE_BEMP2_SYS = 0x00C1, // libSceBemp2sys.sprx
|
||||
ORBIS_SYSMODULE_BEISOBMF = 0x00C2, // libSceBeisobmf.sprx
|
||||
ORBIS_SYSMODULE_PLAY_READY = 0x00C3, // libScePlayReady.sprx
|
||||
ORBIS_SYSMODULE_VIDEO_NATIVE_EXT_ESSENTIAL = 0x00C4, // libSceVideoNativeExtEssential.sprx
|
||||
ORBIS_SYSMODULE_ZLIB = 0x00C5, // libSceZlib.sprx
|
||||
ORBIS_SYSMODULE_DTCP_IP = 0x00C6, // libSceDtcpIp.sprx
|
||||
ORBIS_SYSMODULE_CONTENT_SEARCH = 0x00C7, // libSceContentSearch.sprx
|
||||
ORBIS_SYSMODULE_SHARE_UTILITY = 0x00C8, // libSceShareUtility.sprx
|
||||
ORBIS_SYSMODULE_AUDIODEC_CPU_DTS_HD_LBR = 0x00C9, // libSceAudiodecCpuDtsHdLbr.sprx
|
||||
ORBIS_SYSMODULE_DECI4H = 0x00CA,
|
||||
ORBIS_SYSMODULE_HEAD_TRACKER = 0x00CB, // libSceHeadTracker.sprx
|
||||
ORBIS_SYSMODULE_GAME_UPDATE = 0x00CC, // libSceGameUpdate.sprx
|
||||
ORBIS_SYSMODULE_AUTO_MOUNTER_CLIENT = 0x00CD, // libSceAutoMounterClient.sprx
|
||||
ORBIS_SYSMODULE_SYSTEM_GESTURE = 0x00CE, // libSceSystemGesture.sprx
|
||||
ORBIS_SYSMODULE_VIDEODEC2 = 0x00CF, // libSceVideodec2.sprx
|
||||
ORBIS_SYSMODULE_VDECWRAP = 0x00D0, // libSceVdecwrap.sprx
|
||||
ORBIS_SYSMODULE_AT9_ENC = 0x00D1, // libSceAt9Enc.sprx
|
||||
ORBIS_SYSMODULE_CONVERT_KEYCODE = 0x00D2, // libSceConvertKeycode.sprx
|
||||
ORBIS_SYSMODULE_SHARE_PLAY = 0x00D3, // libSceSharePlay.sprx
|
||||
ORBIS_SYSMODULE_HMD = 0x00D4, // libSceHmd.sprx
|
||||
ORBIS_SYSMODULE_USB_STORAGE = 0x00D5, // libSceUsbStorage.sprx
|
||||
ORBIS_SYSMODULE_USB_STORAGE_DIALOG = 0x00D6, // libSceUsbStorageDialog.sprx
|
||||
ORBIS_SYSMODULE_DISC_MAP = 0x00D7, // libSceDiscMap.sprx
|
||||
ORBIS_SYSMODULE_FACE_TRACKER = 0x00D8, // libSceFaceTracker.sprx
|
||||
ORBIS_SYSMODULE_HAND_TRACKER = 0x00D9, // libSceHandTracker.sprx
|
||||
ORBIS_SYSMODULE_NP_SNS_YOUTUBE_DIALOG = 0x00DA, // libSceNpSnsYouTubeDialog.sprx
|
||||
ORBIS_SYSMODULE_PROFILE_CACHE_EXTERNAL = 0x00DC, // libSceProfileCacheExternal.sprx
|
||||
ORBIS_SYSMODULE_MUSIC_PLAYER_SERVICE = 0x00DD, // libSceMusicPlayerService.sprx
|
||||
ORBIS_SYSMODULE_SP_SYS_CALL_WRAPPER = 0x00DE, // libSceSpSysCallWrapper.sprx
|
||||
ORBIS_SYSMODULE_PS2_EMU_MENU_DIALOG = 0x00DF, // libScePs2EmuMenuDialog.sprx
|
||||
ORBIS_SYSMODULE_NP_SNS_DAILYMOTION_DIALOG = 0x00E0, // libSceNpSnsDailyMotionDialog.sprx
|
||||
ORBIS_SYSMODULE_AUDIODEC_CPU_HEVAG = 0x00E1, // libSceAudiodecCpuHevag.sprx
|
||||
ORBIS_SYSMODULE_LOGIN_DIALOG = 0x00E2, // libSceLoginDialog.sprx
|
||||
ORBIS_SYSMODULE_LOGIN_SERVICE = 0x00E3, // libSceLoginService.sprx
|
||||
ORBIS_SYSMODULE_SIGNIN_DIALOG = 0x00E4, // libSceSigninDialog.sprx
|
||||
ORBIS_SYSMODULE_VDECSW = 0x00E5, // libSceVdecsw.sprx
|
||||
ORBIS_SYSMODULE_CUSTOM_MUSIC_CORE = 0x00E6, // libSceCustomMusicCore.sprx
|
||||
ORBIS_SYSMODULE_JSON2 = 0x00E7, // libSceJson2.sprx
|
||||
ORBIS_SYSMODULE_AUDIO_LATENCY_ESTIMATION = 0x00E8, // libSceAudioLatencyEstimation.sprx
|
||||
ORBIS_SYSMODULE_WK_FONT_CONFIG = 0x00E9, // libSceWkFontConfig.sprx
|
||||
ORBIS_SYSMODULE_VORBIS_DEC = 0x00EA, // libSceVorbisDec.sprx
|
||||
ORBIS_SYSMODULE_HMD_SETUP_DIALOG = 0x00EB, // libSceHmdSetupDialog.sprx
|
||||
ORBIS_SYSMODULE_RESERVED28 = 0x00EC,
|
||||
ORBIS_SYSMODULE_VR_TRACKER = 0x00ED, // libSceVrTracker.sprx
|
||||
ORBIS_SYSMODULE_CONTENT_DELETE = 0x00EE, // libSceContentDelete.sprx
|
||||
ORBIS_SYSMODULE_IME_BACKEND = 0x00EF, // libSceImeBackend.sprx
|
||||
ORBIS_SYSMODULE_NET_CTL_AP_DIALOG = 0x00F0, // libSceNetCtlApDialog.sprx
|
||||
ORBIS_SYSMODULE_PLAYGO_DIALOG = 0x00F1, // libScePlayGoDialog.sprx
|
||||
ORBIS_SYSMODULE_SOCIAL_SCREEN = 0x00F2, // libSceSocialScreen.sprx
|
||||
ORBIS_SYSMODULE_EDIT_MP4 = 0x00F3, // libSceEditMp4.sprx
|
||||
ORBIS_SYSMODULE_PSM_KIT_SYSTEM = 0x00F5, // libScePsmKitSystem.sprx
|
||||
ORBIS_SYSMODULE_TEXT_TO_SPEECH = 0x00F6, // libSceTextToSpeech.sprx
|
||||
ORBIS_SYSMODULE_NP_TOOLKIT = 0x00F7, // libSceNpToolkit.sprx
|
||||
ORBIS_SYSMODULE_CUSTOM_MUSIC_SERVICE = 0x00F8, // libSceCustomMusicService.sprx
|
||||
ORBIS_SYSMODULE_CL_SYS_CALL_WRAPPER = 0x00F9, // libSceClSysCallWrapper.sprx
|
||||
ORBIS_SYSMODULE_SYSTEM_LOGGER = 0x00FA, // libSceSystemLogger.sprx
|
||||
ORBIS_SYSMODULE_BLUETOOTH_HID = 0x00FB, // libSceBluetoothHid.sprx
|
||||
ORBIS_SYSMODULE_VIDEO_DECODER_ARBITRATION = 0x00FC, // libSceVideoDecoderArbitration.sprx
|
||||
ORBIS_SYSMODULE_VR_SERVICE_DIALOG = 0x00FD, // libSceVrServiceDialog.sprx
|
||||
ORBIS_SYSMODULE_JOB_MANAGER = 0x00FE, // libSceJobManager.sprx
|
||||
ORBIS_SYSMODULE_SHARE_FACTORY_UTIL = 0x00FF, // libSceShareFactoryUtil.sprx
|
||||
ORBIS_SYSMODULE_SOCIAL_SCREEN_DIALOG = 0x0100, // libSceSocialScreenDialog.sprx
|
||||
ORBIS_SYSMODULE_NP_SNS_DIALOG = 0x0101, // libSceNpSnsDialog.sprx
|
||||
ORBIS_SYSMODULE_NP_TOOLKIT2 = 0x0102, // libSceNpToolkit2.sprx
|
||||
ORBIS_SYSMODULE_SRC_UTL = 0x0103, // libSceSrcUtl.sprx
|
||||
ORBIS_SYSMODULE_DISC_ID = 0x0104, // libSceDiscId.sprx
|
||||
ORBIS_SYSMODULE_NP_UNIVERSAL_DATA_SYSTEM = 0x0105, // libSceNpUniversalDataSystem.sprx
|
||||
ORBIS_SYSMODULE_KEYBOARD = 0x0106, // libSceKeyboard.sprx
|
||||
ORBIS_SYSMODULE_GIC = 0x0107, // libSceGic.sprx
|
||||
ORBIS_SYSMODULE_PLAY_READY2 = 0x0108, // libScePlayReady2.sprx
|
||||
ORBIS_SYSMODULE_CES_CS = 0x010c, // libSceCesCs.sprx
|
||||
ORBIS_SYSMODULE_PLAYER_INVITATION_DIALOG = 0x010d, // libScePlayerInvitationDialog.sprx
|
||||
ORBIS_SYSMODULE_NP_SESSION_SIGNALING = 0x0112, // libSceNpSessionSignaling.sprx
|
||||
ORBIS_SYSMODULE_NP_ENTITLEMENT_ACCESS = 0x0113, // libSceNpEntitlementAccess.sprx
|
||||
ORBIS_SYSMODULE_NP_CPP_WEB_API = 0x0115, // libSceNpCppWebApi.sprx
|
||||
ORBIS_SYSMODULE_HUB_APP_UTIL = 0x0116, // libSceHubAppUtil.sprx
|
||||
ORBIS_SYSMODULE_NP_PARTNER001 = 0x011a, // libSceNpPartner001.sprx
|
||||
ORBIS_SYSMODULE_FONT_GS = 0x012f, // libSceFontGs.sprx
|
||||
ORBIS_SYSMODULE_FONT_GSM = 0x0135, // libSceFontGsm.sprx
|
||||
ORBIS_SYSMODULE_NP_PARTNER_SUBSCRIPTION = 0x0138, // libSceNpPartnerSubscription.sprx
|
||||
ORBIS_SYSMODULE_NP_AUTH_AUTHORIZED_APP_DIALOG = 0x0139, // libSceNpAuthAuthorizedAppDialog.sprx
|
||||
};
|
||||
|
||||
enum class OrbisSysModuleInternal : u32 {
|
||||
ORBIS_SYSMODULE_INTERNAL_RAZOR_CPU = 0x80000019, // libSceRazorCpu.sprx
|
||||
};
|
||||
|
||||
int PS4_SYSV_ABI sceSysmoduleGetModuleHandleInternal();
|
||||
s32 PS4_SYSV_ABI sceSysmoduleGetModuleInfoForUnwind(VAddr addr, s32 flags,
|
||||
Kernel::OrbisModuleInfoForUnwind* info);
|
||||
int PS4_SYSV_ABI sceSysmoduleIsCalledFromSysModule();
|
||||
int PS4_SYSV_ABI sceSysmoduleIsCameraPreloaded();
|
||||
int PS4_SYSV_ABI sceSysmoduleIsLoaded(OrbisSysModule id);
|
||||
int PS4_SYSV_ABI sceSysmoduleIsLoadedInternal(OrbisSysModuleInternal id);
|
||||
int PS4_SYSV_ABI sceSysmoduleLoadModule(OrbisSysModule id);
|
||||
int PS4_SYSV_ABI sceSysmoduleLoadModuleByNameInternal();
|
||||
int PS4_SYSV_ABI sceSysmoduleLoadModuleInternal();
|
||||
int PS4_SYSV_ABI sceSysmoduleLoadModuleInternalWithArg();
|
||||
int PS4_SYSV_ABI sceSysmoduleMapLibcForLibkernel();
|
||||
int PS4_SYSV_ABI sceSysmodulePreloadModuleForLibkernel();
|
||||
int PS4_SYSV_ABI sceSysmoduleUnloadModule();
|
||||
int PS4_SYSV_ABI sceSysmoduleUnloadModuleByNameInternal();
|
||||
int PS4_SYSV_ABI sceSysmoduleUnloadModuleInternal();
|
||||
int PS4_SYSV_ABI sceSysmoduleUnloadModuleInternalWithArg();
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::SysModule
|
||||
@ -1,8 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
constexpr int ORBIS_SYSMODULE_INVALID_ID = 0x805A1000;
|
||||
constexpr int ORBIS_SYSMODULE_NOT_LOADED = 0x805A1001;
|
||||
constexpr int ORBIS_SYSMODULE_LOCK_FAILED = 0x805A10FF;
|
||||
@ -1,10 +1,9 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstdlib>
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/file_sys/fs.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/system/systemservice.h"
|
||||
@ -18,7 +17,7 @@ std::queue<OrbisSystemServiceEvent> g_event_queue;
|
||||
std::mutex g_event_queue_mutex;
|
||||
|
||||
bool IsSplashVisible() {
|
||||
return Config::showSplash() && g_splash_status;
|
||||
return EmulatorSettings.IsShowSplash() && g_splash_status;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAppMessagingClearEventFlag() {
|
||||
@ -1918,7 +1917,7 @@ s32 PS4_SYSV_ABI sceSystemServiceParamGetInt(OrbisSystemServiceParamId param_id,
|
||||
}
|
||||
switch (param_id) {
|
||||
case OrbisSystemServiceParamId::Lang:
|
||||
*value = Config::GetLanguage();
|
||||
*value = EmulatorSettings.GetConsoleLanguage();
|
||||
break;
|
||||
case OrbisSystemServiceParamId::DateFormat:
|
||||
*value = u32(OrbisSystemParamDateFormat::FmtDDMMYYYY);
|
||||
|
||||
@ -1,12 +1,19 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/config.h"
|
||||
#include <queue>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
|
||||
#include <core/user_settings.h>
|
||||
#include <queue>
|
||||
#include "common/singleton.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
#include "core/libraries/system/userservice_error.h"
|
||||
#include "core/tls.h"
|
||||
#include "input/controller.h"
|
||||
|
||||
namespace Libraries::UserService {
|
||||
|
||||
@ -105,15 +112,23 @@ int PS4_SYSV_ABI sceUserServiceGetDiscPlayerFlag() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUserServiceGetEvent(OrbisUserServiceEvent* event) {
|
||||
LOG_TRACE(Lib_UserService, "(DUMMY) called");
|
||||
// fake a loggin event
|
||||
static bool logged_in = false;
|
||||
std::queue<OrbisUserServiceEvent> user_service_event_queue = {};
|
||||
|
||||
if (!logged_in) {
|
||||
logged_in = true;
|
||||
event->event = OrbisUserServiceEventType::Login;
|
||||
event->userId = 1;
|
||||
void AddUserServiceEvent(const OrbisUserServiceEvent e) {
|
||||
LOG_DEBUG(Lib_UserService, "Event added to queue: {} {}", (u8)e.event, e.userId);
|
||||
user_service_event_queue.push(e);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUserServiceGetEvent(OrbisUserServiceEvent* event) {
|
||||
LOG_TRACE(Lib_UserService, "called");
|
||||
|
||||
if (!user_service_event_queue.empty()) {
|
||||
OrbisUserServiceEvent& temp = user_service_event_queue.front();
|
||||
event->event = temp.event;
|
||||
event->userId = temp.userId;
|
||||
user_service_event_queue.pop();
|
||||
LOG_INFO(Lib_UserService, "Event processed by the game: {} {}", (u8)temp.event,
|
||||
temp.userId);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -496,8 +511,7 @@ s32 PS4_SYSV_ABI sceUserServiceGetInitialUser(int* user_id) {
|
||||
LOG_ERROR(Lib_UserService, "user_id is null");
|
||||
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
// select first user (TODO add more)
|
||||
*user_id = 1;
|
||||
*user_id = UserManagement.GetDefaultUser().user_id;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -567,20 +581,29 @@ int PS4_SYSV_ABI sceUserServiceGetLoginFlag() {
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUserServiceGetLoginUserIdList(OrbisUserServiceLoginUserIdList* userIdList) {
|
||||
LOG_DEBUG(Lib_UserService, "called");
|
||||
if (userIdList == nullptr) {
|
||||
LOG_ERROR(Lib_UserService, "user_id is null");
|
||||
LOG_ERROR(Lib_UserService, "userIdList is null");
|
||||
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
// TODO only first user, do the others as well
|
||||
userIdList->user_id[0] = 1;
|
||||
userIdList->user_id[1] = ORBIS_USER_SERVICE_USER_ID_INVALID;
|
||||
userIdList->user_id[2] = ORBIS_USER_SERVICE_USER_ID_INVALID;
|
||||
userIdList->user_id[3] = ORBIS_USER_SERVICE_USER_ID_INVALID;
|
||||
|
||||
// Initialize all slots to invalid (-1)
|
||||
for (int i = 0; i < ORBIS_USER_SERVICE_MAX_LOGIN_USERS; i++) {
|
||||
userIdList->user_id[i] = ORBIS_USER_SERVICE_USER_ID_INVALID;
|
||||
}
|
||||
|
||||
auto& user_manager = UserManagement;
|
||||
|
||||
auto logged_in_users = user_manager.GetLoggedInUsers();
|
||||
|
||||
for (int i = 0; i < ORBIS_USER_SERVICE_MAX_LOGIN_USERS; i++) {
|
||||
s32 id =
|
||||
logged_in_users[i] ? logged_in_users[i]->user_id : ORBIS_USER_SERVICE_USER_ID_INVALID;
|
||||
userIdList->user_id[i] = id;
|
||||
LOG_DEBUG(Lib_UserService, "Slot {}: User ID {} (port {})", i, id,
|
||||
logged_in_users[i] ? logged_in_users[i]->player_index : -1);
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceUserServiceGetMicLevel() {
|
||||
LOG_ERROR(Lib_UserService, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
@ -1048,7 +1071,7 @@ s32 PS4_SYSV_ABI sceUserServiceGetUserColor(int user_id, OrbisUserServiceUserCol
|
||||
LOG_ERROR(Lib_UserService, "color is null");
|
||||
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
*color = OrbisUserServiceUserColor::Blue;
|
||||
*color = (OrbisUserServiceUserColor)UserManagement.GetUserByID(user_id)->user_color;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -1068,12 +1091,18 @@ int PS4_SYSV_ABI sceUserServiceGetUserGroupNum() {
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUserServiceGetUserName(int user_id, char* user_name, std::size_t size) {
|
||||
LOG_DEBUG(Lib_UserService, "called user_id = {} ,size = {} ", user_id, size);
|
||||
LOG_DEBUG(Lib_UserService, "called user_id = {}, size = {} ", user_id, size);
|
||||
if (user_name == nullptr) {
|
||||
LOG_ERROR(Lib_UserService, "user_name is null");
|
||||
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
std::string name = Config::getUserName();
|
||||
std::string name = "shadPS4";
|
||||
auto const* u = UserManagement.GetUserByID(user_id);
|
||||
if (u != nullptr) {
|
||||
name = u->user_name;
|
||||
} else {
|
||||
LOG_ERROR(Lib_UserService, "No user found");
|
||||
}
|
||||
if (size < name.length()) {
|
||||
LOG_ERROR(Lib_UserService, "buffer is too short");
|
||||
return ORBIS_USER_SERVICE_ERROR_BUFFER_TOO_SHORT;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// reference :
|
||||
// https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/master/include/orbis/_types/user.h
|
||||
@ -57,6 +57,8 @@ struct OrbisUserServiceEvent {
|
||||
OrbisUserServiceUserId userId;
|
||||
};
|
||||
|
||||
void AddUserServiceEvent(const OrbisUserServiceEvent e);
|
||||
|
||||
int PS4_SYSV_ABI sceUserServiceInitializeForShellCore();
|
||||
int PS4_SYSV_ABI sceUserServiceTerminateForShellCore();
|
||||
int PS4_SYSV_ABI sceUserServiceDestroyUser();
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/libraries/libs.h"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
@ -9,7 +9,7 @@
|
||||
#include <fmt/format.h>
|
||||
#include <libusb.h>
|
||||
|
||||
#include "common/config.h"
|
||||
#include "core/emulator_settings.h"
|
||||
|
||||
namespace Libraries::Usbd {
|
||||
|
||||
@ -457,14 +457,14 @@ int PS4_SYSV_ABI Func_D56B43060720B1E0() {
|
||||
}
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
switch (Config::getUsbDeviceBackend()) {
|
||||
case Config::SkylandersPortal:
|
||||
switch (EmulatorSettings.GetUsbDeviceBackend()) {
|
||||
case UsbBackendType::SkylandersPortal:
|
||||
usb_backend = std::make_shared<SkylandersPortalBackend>();
|
||||
break;
|
||||
case Config::InfinityBase:
|
||||
case UsbBackendType::InfinityBase:
|
||||
usb_backend = std::make_shared<InfinityBaseBackend>();
|
||||
break;
|
||||
case Config::DimensionsToypad:
|
||||
case UsbBackendType::DimensionsToypad:
|
||||
usb_backend = std::make_shared<DimensionsToypadBackend>();
|
||||
break;
|
||||
default:
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/config.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/debug_state.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/kernel/time.h"
|
||||
#include "core/libraries/videoout/driver.h"
|
||||
#include "core/libraries/videoout/videoout_error.h"
|
||||
@ -268,7 +268,8 @@ void VideoOutDriver::SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_
|
||||
}
|
||||
|
||||
void VideoOutDriver::PresentThread(std::stop_token token) {
|
||||
const std::chrono::nanoseconds vblank_period(1000000000 / Config::vblankFreq());
|
||||
const std::chrono::nanoseconds vblank_period(1000000000 /
|
||||
EmulatorSettings.GetVblankFrequency());
|
||||
|
||||
Common::SetCurrentThreadName("shadPS4:PresentThread");
|
||||
Common::SetCurrentThreadRealtime(vblank_period);
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/config.h"
|
||||
#include "common/elf_info.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
#include "core/libraries/videoout/driver.h"
|
||||
@ -455,8 +455,8 @@ s32 PS4_SYSV_ABI sceVideoOutSetWindowModeMargins(s32 handle, s32 top, s32 bottom
|
||||
}
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
driver = std::make_unique<VideoOutDriver>(Config::getInternalScreenWidth(),
|
||||
Config::getInternalScreenHeight());
|
||||
driver = std::make_unique<VideoOutDriver>(EmulatorSettings.GetInternalScreenWidth(),
|
||||
EmulatorSettings.GetInternalScreenHeight());
|
||||
|
||||
LIB_FUNCTION("SbU3dwp80lQ", "libSceVideoOut", 1, "libSceVideoOut", sceVideoOutGetFlipStatus);
|
||||
LIB_FUNCTION("U46NwOiJpys", "libSceVideoOut", 1, "libSceVideoOut", sceVideoOutSubmitFlip);
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
#include "common/alignment.h"
|
||||
#include "common/arch.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/config.h"
|
||||
#include "common/elf_info.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/path_util.h"
|
||||
@ -13,14 +12,20 @@
|
||||
#include "core/aerolib/aerolib.h"
|
||||
#include "core/aerolib/stubs.h"
|
||||
#include "core/devtools/widget/module_list.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/kernel/kernel.h"
|
||||
#include "core/libraries/kernel/memory.h"
|
||||
#include "core/libraries/kernel/threads.h"
|
||||
#include "core/libraries/sysmodule/sysmodule.h"
|
||||
#include "core/linker.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/tls.h"
|
||||
#include "ipc/ipc.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
namespace Core {
|
||||
|
||||
static PS4_SYSV_ABI void ProgramExitFunc() {
|
||||
@ -56,7 +61,7 @@ Linker::Linker() : memory{Memory::Instance()} {}
|
||||
Linker::~Linker() = default;
|
||||
|
||||
void Linker::Execute(const std::vector<std::string>& args) {
|
||||
if (Config::debugDump()) {
|
||||
if (EmulatorSettings.IsDebugDump()) {
|
||||
DebugDump();
|
||||
}
|
||||
|
||||
@ -106,11 +111,17 @@ void Linker::Execute(const std::vector<std::string>& args) {
|
||||
|
||||
main_thread.Run([this, module, &args](std::stop_token) {
|
||||
Common::SetCurrentThreadName("Game:Main");
|
||||
#ifndef _WIN32 // Clear any existing signal mask for game threads.
|
||||
sigset_t emptyset;
|
||||
sigemptyset(&emptyset);
|
||||
pthread_sigmask(SIG_SETMASK, &emptyset, nullptr);
|
||||
#endif
|
||||
if (auto& ipc = IPC::Instance()) {
|
||||
ipc.WaitForStart();
|
||||
}
|
||||
|
||||
LoadSharedLibraries();
|
||||
// Have libSceSysmodule preload our libraries.
|
||||
Libraries::SysModule::sceSysmodulePreloadModuleForLibkernel();
|
||||
|
||||
// Simulate libSceGnmDriver initialization, which maps a chunk of direct memory.
|
||||
// Some games fail without accurately emulating this behavior.
|
||||
@ -350,8 +361,10 @@ bool Linker::Resolve(const std::string& name, Loader::SymbolType sym_type, Modul
|
||||
return_info->virtual_address = AeroLib::GetStub(sr.name.c_str());
|
||||
return_info->name = "Unknown !!!";
|
||||
}
|
||||
LOG_ERROR(Core_Linker, "Linker: Stub resolved {} as {} (lib: {}, mod: {})", sr.name,
|
||||
return_info->name, library->name, module->name);
|
||||
if (library->name != "libc" && library->name != "libSceFios2") {
|
||||
LOG_WARNING(Core_Linker, "Linker: Stub resolved {} as {} (lib: {}, mod: {})", sr.name,
|
||||
return_info->name, library->name, module->name);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -54,17 +54,16 @@ struct EntryParams {
|
||||
};
|
||||
|
||||
struct HeapAPI {
|
||||
PS4_SYSV_ABI void* (*heap_malloc)(size_t);
|
||||
PS4_SYSV_ABI void* (*heap_malloc)(u64);
|
||||
PS4_SYSV_ABI void (*heap_free)(void*);
|
||||
PS4_SYSV_ABI void* (*heap_calloc)(size_t, size_t);
|
||||
PS4_SYSV_ABI void* (*heap_realloc)(void*, size_t);
|
||||
PS4_SYSV_ABI void* (*heap_memalign)(size_t, size_t);
|
||||
PS4_SYSV_ABI int (*heap_posix_memalign)(void**, size_t, size_t);
|
||||
// NOTE: Fields below may be inaccurate
|
||||
PS4_SYSV_ABI int (*heap_reallocalign)(void);
|
||||
PS4_SYSV_ABI void (*heap_malloc_stats)(void);
|
||||
PS4_SYSV_ABI int (*heap_malloc_stats_fast)(void);
|
||||
PS4_SYSV_ABI size_t (*heap_malloc_usable_size)(void*);
|
||||
PS4_SYSV_ABI void* (*heap_calloc)(u64, u64);
|
||||
PS4_SYSV_ABI void* (*heap_realloc)(void*, u64);
|
||||
PS4_SYSV_ABI void* (*heap_memalign)(u64, u64);
|
||||
PS4_SYSV_ABI s32 (*heap_posix_memalign)(void**, u64, u64);
|
||||
PS4_SYSV_ABI s32 (*heap_reallocalign)(void*, u64, u64);
|
||||
PS4_SYSV_ABI s32 (*heap_malloc_stats)(void*);
|
||||
PS4_SYSV_ABI s32 (*heap_malloc_stats_fast)(void*);
|
||||
PS4_SYSV_ABI u64 (*heap_malloc_usable_size)(void*);
|
||||
};
|
||||
|
||||
using AppHeapAPI = HeapAPI*;
|
||||
@ -125,11 +124,10 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void LoadSharedLibraries() {
|
||||
void RelocateAllImports() {
|
||||
std::scoped_lock lk{mutex};
|
||||
for (auto& module : m_modules) {
|
||||
if (module->IsSharedLib()) {
|
||||
module->Start(0, nullptr, nullptr);
|
||||
}
|
||||
Relocate(module.get());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,9 +3,9 @@
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/config.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/elf_info.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/file_sys/fs.h"
|
||||
#include "core/libraries/kernel/memory.h"
|
||||
#include "core/libraries/kernel/orbis_error.h"
|
||||
@ -37,11 +37,11 @@ void MemoryManager::SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1
|
||||
bool use_extended_mem2) {
|
||||
const bool is_neo = ::Libraries::Kernel::sceKernelIsNeoMode();
|
||||
auto total_size = is_neo ? ORBIS_KERNEL_TOTAL_MEM_PRO : ORBIS_KERNEL_TOTAL_MEM;
|
||||
if (Config::isDevKitConsole()) {
|
||||
if (EmulatorSettings.IsDevKit()) {
|
||||
total_size = is_neo ? ORBIS_KERNEL_TOTAL_MEM_DEV_PRO : ORBIS_KERNEL_TOTAL_MEM_DEV;
|
||||
}
|
||||
s32 extra_dmem = Config::getExtraDmemInMbytes();
|
||||
if (Config::getExtraDmemInMbytes() != 0) {
|
||||
s32 extra_dmem = EmulatorSettings.GetExtraDmemInMBytes();
|
||||
if (extra_dmem != 0) {
|
||||
LOG_WARNING(Kernel_Vmm,
|
||||
"extraDmemInMbytes is {} MB! Old Direct Size: {:#x} -> New Direct Size: {:#x}",
|
||||
extra_dmem, total_size, total_size + extra_dmem * 1_MB);
|
||||
@ -1223,13 +1223,16 @@ s32 MemoryManager::SetDirectMemoryType(VAddr addr, u64 size, s32 memory_type) {
|
||||
// Increment phys_handle
|
||||
phys_handle++;
|
||||
}
|
||||
|
||||
// Check if VMA can be merged with adjacent areas after physical area modifications.
|
||||
vma_handle = MergeAdjacent(vma_map, vma_handle);
|
||||
}
|
||||
current_addr += size_in_vma;
|
||||
remaining_size -= size_in_vma;
|
||||
vma_handle++;
|
||||
|
||||
// Check if VMA can be merged with adjacent areas after modifications.
|
||||
vma_handle = MergeAdjacent(vma_map, vma_handle);
|
||||
if (vma_handle->second.base + vma_handle->second.size <= current_addr) {
|
||||
// If we're now in the next VMA, then go to the next handle.
|
||||
vma_handle++;
|
||||
}
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
@ -1262,10 +1265,15 @@ void MemoryManager::NameVirtualRange(VAddr virtual_addr, u64 size, std::string_v
|
||||
vma.name = name;
|
||||
}
|
||||
}
|
||||
it = MergeAdjacent(vma_map, it);
|
||||
remaining_size -= size_in_vma;
|
||||
current_addr += size_in_vma;
|
||||
it++;
|
||||
|
||||
// Check if VMA can be merged with adjacent areas after modifications.
|
||||
it = MergeAdjacent(vma_map, it);
|
||||
if (it->second.base + it->second.size <= current_addr) {
|
||||
// If we're now in the next VMA, then go to the next handle.
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "common/config.h"
|
||||
#include "common/types.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/loader/elf.h"
|
||||
#include "core/loader/symbols_resolver.h"
|
||||
|
||||
@ -166,7 +166,7 @@ public:
|
||||
}
|
||||
|
||||
bool IsSystemLib() {
|
||||
auto system_path = Config::getSysModulesPath();
|
||||
auto system_path = EmulatorSettings.GetSysModulesDir();
|
||||
if (file.string().starts_with(system_path.string().c_str())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
#ifndef _WIN32
|
||||
namespace Libraries::Kernel {
|
||||
void SigactionHandler(int native_signum, siginfo_t* inf, ucontext_t* raw_context);
|
||||
extern std::array<SceKernelExceptionHandler, 32> Handlers;
|
||||
extern std::array<OrbisKernelExceptionHandler, 32> Handlers;
|
||||
} // namespace Libraries::Kernel
|
||||
#endif
|
||||
|
||||
@ -86,7 +86,7 @@ void SignalHandler(int sig, siginfo_t* info, void* raw_context) {
|
||||
if (!signals->DispatchAccessViolation(raw_context, info->si_addr)) {
|
||||
// If the guest has installed a custom signal handler, and the access violation didn't
|
||||
// come from HLE memory tracking, pass the signal on
|
||||
if (Libraries::Kernel::Handlers[sig]) {
|
||||
if (Libraries::Kernel::Handlers[Libraries::Kernel::NativeToOrbisSignal(sig)]) {
|
||||
Libraries::Kernel::SigactionHandler(sig, info,
|
||||
reinterpret_cast<ucontext_t*>(raw_context));
|
||||
return;
|
||||
@ -99,7 +99,7 @@ void SignalHandler(int sig, siginfo_t* info, void* raw_context) {
|
||||
}
|
||||
case SIGILL:
|
||||
if (!signals->DispatchIllegalInstruction(raw_context)) {
|
||||
if (Libraries::Kernel::Handlers[sig]) {
|
||||
if (Libraries::Kernel::Handlers[Libraries::Kernel::NativeToOrbisSignal(sig)]) {
|
||||
Libraries::Kernel::SigactionHandler(sig, info,
|
||||
reinterpret_cast<ucontext_t*>(raw_context));
|
||||
return;
|
||||
|
||||
@ -10,10 +10,8 @@
|
||||
|
||||
#ifdef _WIN32
|
||||
#define SIGSLEEP -1
|
||||
#elif defined(__APPLE__)
|
||||
#define SIGSLEEP SIGVTALRM
|
||||
#else
|
||||
#define SIGSLEEP SIGRTMAX
|
||||
#define SIGSLEEP SIGVTALRM
|
||||
#endif
|
||||
namespace Core {
|
||||
|
||||
|
||||
198
src/core/user_manager.cpp
Normal file
198
src/core/user_manager.cpp
Normal file
@ -0,0 +1,198 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <common/path_util.h>
|
||||
#include "emulator_settings.h"
|
||||
#include "libraries/system/userservice.h"
|
||||
#include "user_manager.h"
|
||||
#include "user_settings.h"
|
||||
|
||||
bool UserManager::AddUser(const User& user) {
|
||||
for (const auto& u : m_users.user) {
|
||||
if (u.user_id == user.user_id)
|
||||
return false; // already exists
|
||||
}
|
||||
|
||||
m_users.user.push_back(user);
|
||||
|
||||
// Create user home directory and subfolders
|
||||
const auto user_dir = EmulatorSettings.GetHomeDir() / std::to_string(user.user_id);
|
||||
|
||||
std::error_code ec;
|
||||
if (!std::filesystem::exists(user_dir)) {
|
||||
std::filesystem::create_directory(user_dir, ec);
|
||||
std::filesystem::create_directory(user_dir / "savedata", ec);
|
||||
std::filesystem::create_directory(user_dir / "trophy", ec);
|
||||
std::filesystem::create_directory(user_dir / "inputs", ec);
|
||||
}
|
||||
|
||||
Save();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UserManager::RemoveUser(s32 user_id) {
|
||||
auto it = std::remove_if(m_users.user.begin(), m_users.user.end(),
|
||||
[user_id](const User& u) { return u.user_id == user_id; });
|
||||
if (it == m_users.user.end())
|
||||
return false; // not found
|
||||
|
||||
const auto user_dir = EmulatorSettings.GetHomeDir() / std::to_string(user_id);
|
||||
|
||||
if (std::filesystem::exists(user_dir)) {
|
||||
std::error_code ec;
|
||||
std::filesystem::remove_all(user_dir, ec);
|
||||
}
|
||||
|
||||
m_users.user.erase(it, m_users.user.end());
|
||||
Save();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UserManager::RenameUser(s32 user_id, const std::string& new_name) {
|
||||
// Find user in the internal list
|
||||
for (auto& user : m_users.user) {
|
||||
if (user.user_id == user_id) {
|
||||
if (user.user_name == new_name)
|
||||
return true; // no change
|
||||
|
||||
user.user_name = new_name;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Save();
|
||||
return false;
|
||||
}
|
||||
|
||||
User* UserManager::GetUserByID(s32 user_id) {
|
||||
for (auto& u : m_users.user) {
|
||||
if (u.user_id == user_id)
|
||||
return &u;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
User* UserManager::GetUserByPlayerIndex(s32 index) {
|
||||
for (auto& u : m_users.user) {
|
||||
if (u.player_index == index)
|
||||
return &u;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::vector<User>& UserManager::GetAllUsers() const {
|
||||
return m_users.user;
|
||||
}
|
||||
|
||||
Users UserManager::CreateDefaultUsers() {
|
||||
Users default_users;
|
||||
default_users.user = {
|
||||
{
|
||||
.user_id = 1000,
|
||||
.user_name = "shadPS4",
|
||||
.user_color = 1,
|
||||
.player_index = 1,
|
||||
},
|
||||
{
|
||||
.user_id = 1001,
|
||||
.user_name = "shadPS4-2",
|
||||
.user_color = 2,
|
||||
.player_index = 2,
|
||||
},
|
||||
{
|
||||
.user_id = 1002,
|
||||
.user_name = "shadPS4-3",
|
||||
.user_color = 3,
|
||||
.player_index = 3,
|
||||
},
|
||||
{
|
||||
.user_id = 1003,
|
||||
.user_name = "shadPS4-4",
|
||||
.user_color = 4,
|
||||
.player_index = 4,
|
||||
},
|
||||
};
|
||||
|
||||
for (auto& u : default_users.user) {
|
||||
const auto user_dir = EmulatorSettings.GetHomeDir() / std::to_string(u.user_id);
|
||||
|
||||
if (!std::filesystem::exists(user_dir)) {
|
||||
std::filesystem::create_directory(user_dir);
|
||||
std::filesystem::create_directory(user_dir / "savedata");
|
||||
std::filesystem::create_directory(user_dir / "trophy");
|
||||
std::filesystem::create_directory(user_dir / "inputs");
|
||||
}
|
||||
}
|
||||
|
||||
return default_users;
|
||||
}
|
||||
|
||||
bool UserManager::SetDefaultUser(u32 user_id) {
|
||||
auto it = std::find_if(m_users.user.begin(), m_users.user.end(),
|
||||
[user_id](const User& u) { return u.user_id == user_id; });
|
||||
if (it == m_users.user.end())
|
||||
return false;
|
||||
|
||||
SetControllerPort(user_id, 1); // Set default user to port 1
|
||||
return Save();
|
||||
}
|
||||
|
||||
User UserManager::GetDefaultUser() {
|
||||
return *GetUserByPlayerIndex(1);
|
||||
}
|
||||
|
||||
void UserManager::SetControllerPort(u32 user_id, int port) {
|
||||
for (auto& u : m_users.user) {
|
||||
if (u.user_id != user_id && u.player_index == port)
|
||||
u.player_index = -1;
|
||||
if (u.user_id == user_id)
|
||||
u.player_index = port;
|
||||
}
|
||||
Save();
|
||||
}
|
||||
// Returns a list of users that have valid home directories
|
||||
std::vector<User> UserManager::GetValidUsers() const {
|
||||
std::vector<User> result;
|
||||
result.reserve(m_users.user.size());
|
||||
|
||||
const auto home_dir = EmulatorSettings.GetHomeDir();
|
||||
|
||||
for (const auto& user : m_users.user) {
|
||||
const auto user_dir = home_dir / std::to_string(user.user_id);
|
||||
if (std::filesystem::exists(user_dir)) {
|
||||
result.push_back(user);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
LoggedInUsers UserManager::GetLoggedInUsers() const {
|
||||
return logged_in_users;
|
||||
}
|
||||
|
||||
using namespace Libraries::UserService;
|
||||
|
||||
void UserManager::LoginUser(User* u, s32 player_index) {
|
||||
if (!u) {
|
||||
return;
|
||||
}
|
||||
u->logged_in = true;
|
||||
// u->player_index = player_index;
|
||||
AddUserServiceEvent({OrbisUserServiceEventType::Login, u->user_id});
|
||||
logged_in_users[player_index - 1] = u;
|
||||
}
|
||||
|
||||
void UserManager::LogoutUser(User* u) {
|
||||
if (!u) {
|
||||
return;
|
||||
}
|
||||
u->logged_in = false;
|
||||
AddUserServiceEvent({OrbisUserServiceEventType::Logout, u->user_id});
|
||||
logged_in_users[u->player_index - 1] = {};
|
||||
}
|
||||
|
||||
bool UserManager::Save() const {
|
||||
return UserSettings.Save();
|
||||
}
|
||||
60
src/core/user_manager.h
Normal file
60
src/core/user_manager.h
Normal file
@ -0,0 +1,60 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "common/types.h"
|
||||
|
||||
struct User {
|
||||
s32 user_id = -1;
|
||||
std::string user_name = "";
|
||||
u32 user_color;
|
||||
int player_index = 0; // 1-4
|
||||
|
||||
bool logged_in = false;
|
||||
};
|
||||
|
||||
struct Users {
|
||||
std::vector<User> user{};
|
||||
std::string commit_hash{};
|
||||
};
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(User, user_id, user_color, user_name, player_index)
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Users, user, commit_hash)
|
||||
|
||||
using LoggedInUsers = std::array<User*, 4>;
|
||||
|
||||
class UserManager {
|
||||
public:
|
||||
UserManager() = default;
|
||||
|
||||
bool AddUser(const User& user);
|
||||
bool RemoveUser(s32 user_id);
|
||||
bool RenameUser(s32 user_id, const std::string& new_name);
|
||||
User* GetUserByID(s32 user_id);
|
||||
User* GetUserByPlayerIndex(s32 index);
|
||||
const std::vector<User>& GetAllUsers() const;
|
||||
Users CreateDefaultUsers();
|
||||
bool SetDefaultUser(u32 user_id);
|
||||
User GetDefaultUser();
|
||||
void SetControllerPort(u32 user_id, int port);
|
||||
std::vector<User> GetValidUsers() const;
|
||||
LoggedInUsers GetLoggedInUsers() const;
|
||||
void LoginUser(User* u, s32 player_index);
|
||||
void LogoutUser(User* u);
|
||||
|
||||
Users& GetUsers() {
|
||||
return m_users;
|
||||
}
|
||||
const Users& GetUsers() const {
|
||||
return m_users;
|
||||
}
|
||||
|
||||
bool Save() const;
|
||||
|
||||
private:
|
||||
Users m_users;
|
||||
LoggedInUsers logged_in_users{};
|
||||
};
|
||||
110
src/core/user_settings.cpp
Normal file
110
src/core/user_settings.cpp
Normal file
@ -0,0 +1,110 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
#include <common/path_util.h>
|
||||
#include <common/scm_rev.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "user_settings.h"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// Singleton storage
|
||||
std::shared_ptr<UserSettingsImpl> UserSettingsImpl::s_instance = nullptr;
|
||||
std::mutex UserSettingsImpl::s_mutex;
|
||||
|
||||
// Singleton
|
||||
UserSettingsImpl::UserSettingsImpl() = default;
|
||||
|
||||
UserSettingsImpl::~UserSettingsImpl() {
|
||||
Save();
|
||||
}
|
||||
|
||||
std::shared_ptr<UserSettingsImpl> UserSettingsImpl::GetInstance() {
|
||||
std::lock_guard lock(s_mutex);
|
||||
if (!s_instance)
|
||||
s_instance = std::make_shared<UserSettingsImpl>();
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
void UserSettingsImpl::SetInstance(std::shared_ptr<UserSettingsImpl> instance) {
|
||||
std::lock_guard lock(s_mutex);
|
||||
s_instance = std::move(instance);
|
||||
}
|
||||
|
||||
bool UserSettingsImpl::Save() const {
|
||||
const auto path = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "users.json";
|
||||
try {
|
||||
json j;
|
||||
j["Users"] = m_userManager.GetUsers();
|
||||
j["Users"]["commit_hash"] = std::string(Common::g_scm_rev);
|
||||
|
||||
std::ofstream out(path);
|
||||
if (!out) {
|
||||
LOG_ERROR(Config, "Failed to open user settings for writing: {}", path.string());
|
||||
return false;
|
||||
}
|
||||
out << std::setw(2) << j;
|
||||
return !out.fail();
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(Config, "Error saving user settings: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool UserSettingsImpl::Load() {
|
||||
const auto path = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "users.json";
|
||||
try {
|
||||
if (!std::filesystem::exists(path)) {
|
||||
LOG_DEBUG(Config, "User settings file not found: {}", path.string());
|
||||
// Create default user if no file exists
|
||||
if (m_userManager.GetUsers().user.empty()) {
|
||||
m_userManager.GetUsers() = m_userManager.CreateDefaultUsers();
|
||||
}
|
||||
Save(); // Save default users
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ifstream in(path);
|
||||
if (!in) {
|
||||
LOG_ERROR(Config, "Failed to open user settings: {}", path.string());
|
||||
return false;
|
||||
}
|
||||
|
||||
json j;
|
||||
in >> j;
|
||||
|
||||
// Create a default Users object
|
||||
auto default_users = m_userManager.CreateDefaultUsers();
|
||||
|
||||
// Convert default_users to json for merging
|
||||
json default_json;
|
||||
default_json["Users"] = default_users;
|
||||
|
||||
// Merge the loaded json with defaults (preserves existing data, adds missing fields)
|
||||
if (j.contains("Users")) {
|
||||
json current = default_json["Users"];
|
||||
current.update(j["Users"]);
|
||||
m_userManager.GetUsers() = current.get<Users>();
|
||||
} else {
|
||||
m_userManager.GetUsers() = default_users;
|
||||
}
|
||||
|
||||
if (m_userManager.GetUsers().commit_hash != Common::g_scm_rev) {
|
||||
Save();
|
||||
}
|
||||
|
||||
LOG_DEBUG(Config, "User settings loaded successfully");
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(Config, "Error loading user settings: {}", e.what());
|
||||
// Fall back to defaults
|
||||
if (m_userManager.GetUsers().user.empty()) {
|
||||
m_userManager.GetUsers() = m_userManager.CreateDefaultUsers();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
46
src/core/user_settings.h
Normal file
46
src/core/user_settings.h
Normal file
@ -0,0 +1,46 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/types.h"
|
||||
#include "core/user_manager.h"
|
||||
|
||||
#define UserSettings (*UserSettingsImpl::GetInstance())
|
||||
|
||||
#define UserManagement UserSettings.GetUserManager()
|
||||
|
||||
// -------------------------------
|
||||
// User settings
|
||||
// -------------------------------
|
||||
|
||||
class UserSettingsImpl {
|
||||
public:
|
||||
UserSettingsImpl();
|
||||
~UserSettingsImpl();
|
||||
|
||||
UserManager& GetUserManager() {
|
||||
return m_userManager;
|
||||
}
|
||||
|
||||
bool Save() const;
|
||||
bool Load();
|
||||
|
||||
static std::shared_ptr<UserSettingsImpl> GetInstance();
|
||||
static void SetInstance(std::shared_ptr<UserSettingsImpl> instance);
|
||||
|
||||
private:
|
||||
UserManager m_userManager;
|
||||
|
||||
static std::shared_ptr<UserSettingsImpl> s_instance;
|
||||
static std::mutex s_mutex;
|
||||
};
|
||||
185
src/emulator.cpp
185
src/emulator.cpp
@ -10,11 +10,11 @@
|
||||
#include <fmt/xchar.h>
|
||||
#include <hwinfo/hwinfo.h>
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/ipc/ipc.h"
|
||||
#ifdef ENABLE_DISCORD_RPC
|
||||
#include "common/discord_rpc_handler.h"
|
||||
@ -28,24 +28,18 @@
|
||||
#include "common/singleton.h"
|
||||
#include "core/debugger.h"
|
||||
#include "core/devtools/widget/module_list.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/emulator_state.h"
|
||||
#include "core/file_format/psf.h"
|
||||
#include "core/file_format/trp.h"
|
||||
#include "core/file_sys/fs.h"
|
||||
#include "core/libraries/disc_map/disc_map.h"
|
||||
#include "core/libraries/font/font.h"
|
||||
#include "core/libraries/font/fontft.h"
|
||||
#include "core/libraries/jpeg/jpegenc.h"
|
||||
#include "core/libraries/kernel/kernel.h"
|
||||
#include "core/libraries/libc_internal/libc_internal.h"
|
||||
#include "core/libraries/libpng/pngenc.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/ngs2/ngs2.h"
|
||||
#include "core/libraries/np/np_trophy.h"
|
||||
#include "core/libraries/rtc/rtc.h"
|
||||
#include "core/libraries/save_data/save_backup.h"
|
||||
#include "core/linker.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/user_settings.h"
|
||||
#include "emulator.h"
|
||||
#include "video_core/cache_storage.h"
|
||||
#include "video_core/renderdoc.h"
|
||||
@ -58,6 +52,7 @@
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <core/file_format/npbind.h>
|
||||
|
||||
Frontend::WindowSDL* g_window = nullptr;
|
||||
|
||||
@ -204,19 +199,23 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
||||
}
|
||||
|
||||
game_info.game_folder = game_folder;
|
||||
|
||||
Config::load(Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (id + ".toml"),
|
||||
true);
|
||||
|
||||
if (std::filesystem::exists(Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) /
|
||||
(id + ".toml"))) {
|
||||
EmulatorState::GetInstance()->SetGameSpecifigConfigUsed(true);
|
||||
std::filesystem::path npbindPath = game_folder / "sce_sys/npbind.dat";
|
||||
NPBindFile npbind;
|
||||
if (!npbind.Load(npbindPath.string())) {
|
||||
LOG_WARNING(Common_Filesystem, "Failed to load npbind.dat file");
|
||||
} else {
|
||||
EmulatorState::GetInstance()->SetGameSpecifigConfigUsed(false);
|
||||
auto npCommIds = npbind.GetNpCommIds();
|
||||
if (npCommIds.empty()) {
|
||||
LOG_WARNING(Common_Filesystem, "No NPComm IDs found in npbind.dat");
|
||||
} else {
|
||||
game_info.npCommIds = std::move(npCommIds);
|
||||
}
|
||||
}
|
||||
|
||||
EmulatorSettings.Load(id);
|
||||
|
||||
// Initialize logging as soon as possible
|
||||
if (!id.empty() && Config::getSeparateLogFilesEnabled()) {
|
||||
if (!id.empty() && EmulatorSettings.IsSeparateLoggingEnabled()) {
|
||||
Common::Log::Initialize(id + ".log");
|
||||
} else {
|
||||
Common::Log::Initialize();
|
||||
@ -234,32 +233,35 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
||||
LOG_INFO(Loader, "Description {}", Common::g_scm_desc);
|
||||
LOG_INFO(Loader, "Remote {}", Common::g_scm_remote_url);
|
||||
|
||||
const bool has_game_config = std::filesystem::exists(
|
||||
Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (id + ".toml"));
|
||||
LOG_INFO(Config, "Game-specific config exists: {}", has_game_config);
|
||||
LOG_INFO(Config, "Game-specific config used: {}",
|
||||
EmulatorState::GetInstance()->IsGameSpecifigConfigUsed());
|
||||
|
||||
LOG_INFO(Config, "General LogType: {}", Config::getLogType());
|
||||
LOG_INFO(Config, "General isIdenticalLogGrouped: {}", Config::groupIdenticalLogs());
|
||||
LOG_INFO(Config, "General isNeo: {}", Config::isNeoModeConsole());
|
||||
LOG_INFO(Config, "General isDevKit: {}", Config::isDevKitConsole());
|
||||
LOG_INFO(Config, "General isConnectedToNetwork: {}", Config::getIsConnectedToNetwork());
|
||||
LOG_INFO(Config, "General isPsnSignedIn: {}", Config::getPSNSignedIn());
|
||||
LOG_INFO(Config, "GPU isNullGpu: {}", Config::nullGpu());
|
||||
LOG_INFO(Config, "GPU readbacksMode: {}", Config::getReadbacksMode());
|
||||
LOG_INFO(Config, "GPU readbackLinearImages: {}", Config::readbackLinearImages());
|
||||
LOG_INFO(Config, "GPU directMemoryAccess: {}", Config::directMemoryAccess());
|
||||
LOG_INFO(Config, "GPU shouldDumpShaders: {}", Config::dumpShaders());
|
||||
LOG_INFO(Config, "GPU vblankFrequency: {}", Config::vblankFreq());
|
||||
LOG_INFO(Config, "GPU shouldCopyGPUBuffers: {}", Config::copyGPUCmdBuffers());
|
||||
LOG_INFO(Config, "Vulkan gpuId: {}", Config::getGpuId());
|
||||
LOG_INFO(Config, "Vulkan vkValidation: {}", Config::vkValidationEnabled());
|
||||
LOG_INFO(Config, "Vulkan vkValidationCore: {}", Config::vkValidationCoreEnabled());
|
||||
LOG_INFO(Config, "Vulkan vkValidationSync: {}", Config::vkValidationSyncEnabled());
|
||||
LOG_INFO(Config, "Vulkan vkValidationGpu: {}", Config::vkValidationGpuEnabled());
|
||||
LOG_INFO(Config, "Vulkan crashDiagnostics: {}", Config::getVkCrashDiagnosticEnabled());
|
||||
LOG_INFO(Config, "Vulkan hostMarkers: {}", Config::getVkHostMarkersEnabled());
|
||||
LOG_INFO(Config, "Vulkan guestMarkers: {}", Config::getVkGuestMarkersEnabled());
|
||||
LOG_INFO(Config, "Vulkan rdocEnable: {}", Config::isRdocEnabled());
|
||||
LOG_INFO(Config, "General LogType: {}", EmulatorSettings.GetLogType());
|
||||
LOG_INFO(Config, "General isIdenticalLogGrouped: {}", EmulatorSettings.IsIdenticalLogGrouped());
|
||||
LOG_INFO(Config, "General isNeo: {}", EmulatorSettings.IsNeo());
|
||||
LOG_INFO(Config, "General isDevKit: {}", EmulatorSettings.IsDevKit());
|
||||
LOG_INFO(Config, "General isConnectedToNetwork: {}", EmulatorSettings.IsConnectedToNetwork());
|
||||
LOG_INFO(Config, "General isPsnSignedIn: {}", EmulatorSettings.IsPSNSignedIn());
|
||||
LOG_INFO(Config, "GPU isNullGpu: {}", EmulatorSettings.IsNullGPU());
|
||||
LOG_INFO(Config, "GPU readbacksMode: {}", EmulatorSettings.GetReadbacksMode());
|
||||
LOG_INFO(Config, "GPU readbackLinearImages: {}",
|
||||
EmulatorSettings.IsReadbackLinearImagesEnabled());
|
||||
LOG_INFO(Config, "GPU directMemoryAccess: {}", EmulatorSettings.IsDirectMemoryAccessEnabled());
|
||||
LOG_INFO(Config, "GPU shouldDumpShaders: {}", EmulatorSettings.IsDumpShaders());
|
||||
LOG_INFO(Config, "GPU vblankFrequency: {}", EmulatorSettings.GetVblankFrequency());
|
||||
LOG_INFO(Config, "GPU shouldCopyGPUBuffers: {}", EmulatorSettings.IsCopyGpuBuffers());
|
||||
LOG_INFO(Config, "Vulkan gpuId: {}", EmulatorSettings.GetGpuId());
|
||||
LOG_INFO(Config, "Vulkan vkValidation: {}", EmulatorSettings.IsVkValidationEnabled());
|
||||
LOG_INFO(Config, "Vulkan vkValidationCore: {}", EmulatorSettings.IsVkValidationCoreEnabled());
|
||||
LOG_INFO(Config, "Vulkan vkValidationSync: {}", EmulatorSettings.IsVkValidationSyncEnabled());
|
||||
LOG_INFO(Config, "Vulkan vkValidationGpu: {}", EmulatorSettings.IsVkValidationGpuEnabled());
|
||||
LOG_INFO(Config, "Vulkan crashDiagnostics: {}", EmulatorSettings.IsVkCrashDiagnosticEnabled());
|
||||
LOG_INFO(Config, "Vulkan hostMarkers: {}", EmulatorSettings.IsVkHostMarkersEnabled());
|
||||
LOG_INFO(Config, "Vulkan guestMarkers: {}", EmulatorSettings.IsVkGuestMarkersEnabled());
|
||||
LOG_INFO(Config, "Vulkan rdocEnable: {}", EmulatorSettings.IsRenderdocEnabled());
|
||||
LOG_INFO(Config, "Vulkan PipelineCacheEnabled: {}", EmulatorSettings.IsPipelineCacheEnabled());
|
||||
LOG_INFO(Config, "Vulkan PipelineCacheArchived: {}",
|
||||
EmulatorSettings.IsPipelineCacheArchived());
|
||||
|
||||
hwinfo::Memory ram;
|
||||
hwinfo::OS os;
|
||||
@ -295,7 +297,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
||||
|
||||
// Initialize components
|
||||
memory = Core::Memory::Instance();
|
||||
controller = Common::Singleton<Input::GameController>::Instance();
|
||||
controllers = Common::Singleton<Input::GameControllers>::Instance();
|
||||
linker = Common::Singleton<Core::Linker>::Instance();
|
||||
|
||||
// Load renderdoc module
|
||||
@ -304,15 +306,28 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
||||
// Initialize patcher and trophies
|
||||
if (!id.empty()) {
|
||||
MemoryPatcher::g_game_serial = id;
|
||||
Libraries::Np::NpTrophy::game_serial = id;
|
||||
|
||||
const auto trophyDir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / id / "TrophyFiles";
|
||||
if (!std::filesystem::exists(trophyDir)) {
|
||||
TRP trp;
|
||||
if (!trp.Extract(game_folder, id)) {
|
||||
LOG_ERROR(Loader, "Couldn't extract trophies");
|
||||
int index = 0;
|
||||
for (std::string npCommId : game_info.npCommIds) {
|
||||
const auto trophyDir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "trophy" / npCommId;
|
||||
if (!std::filesystem::exists(trophyDir)) {
|
||||
TRP trp;
|
||||
if (!trp.Extract(game_folder, index, npCommId, trophyDir)) {
|
||||
LOG_ERROR(Loader, "Couldn't extract trophies");
|
||||
}
|
||||
}
|
||||
for (User user : UserSettings.GetUserManager().GetValidUsers()) {
|
||||
auto const user_trophy_file =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::HomeDir) /
|
||||
std::to_string(user.user_id) / "trophy" / (npCommId + ".xml");
|
||||
if (!std::filesystem::exists(user_trophy_file)) {
|
||||
std::error_code discard;
|
||||
std::filesystem::copy_file(trophyDir / "Xml" / "TROPCONF.XML", user_trophy_file,
|
||||
discard);
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
@ -336,8 +351,9 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
||||
Common::g_scm_branch, Common::g_scm_desc, game_title);
|
||||
}
|
||||
}
|
||||
window = std::make_unique<Frontend::WindowSDL>(
|
||||
Config::getWindowWidth(), Config::getWindowHeight(), controller, window_title);
|
||||
window = std::make_unique<Frontend::WindowSDL>(EmulatorSettings.GetWindowWidth(),
|
||||
EmulatorSettings.GetWindowHeight(), controllers,
|
||||
window_title);
|
||||
|
||||
g_window = window.get();
|
||||
|
||||
@ -368,7 +384,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
||||
VideoCore::SetOutputDir(mount_captures_dir, id);
|
||||
|
||||
// Mount system fonts
|
||||
const auto& fonts_dir = Config::getFontsPath();
|
||||
const auto& fonts_dir = EmulatorSettings.GetFontsDir();
|
||||
if (!std::filesystem::exists(fonts_dir)) {
|
||||
std::filesystem::create_directory(fonts_dir);
|
||||
}
|
||||
@ -405,20 +421,9 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
||||
std::quick_exit(0);
|
||||
}
|
||||
|
||||
// check if we have system modules to load
|
||||
LoadSystemModules(game_info.game_serial);
|
||||
|
||||
// Load all prx from game's sce_module folder
|
||||
mnt->IterateDirectory("/app0/sce_module", [this](const auto& path, const auto is_file) {
|
||||
if (is_file) {
|
||||
LOG_INFO(Loader, "Loading {}", fmt::UTF(path.u8string()));
|
||||
linker->LoadModule(path);
|
||||
}
|
||||
});
|
||||
|
||||
#ifdef ENABLE_DISCORD_RPC
|
||||
// Discord RPC
|
||||
if (Config::getEnableDiscordRPC()) {
|
||||
if (EmulatorSettings.IsDiscordRPCEnabled()) {
|
||||
auto* rpc = Common::Singleton<DiscordRPCHandler::RPC>::Instance();
|
||||
if (rpc->getRPCEnabled() == false) {
|
||||
rpc->init();
|
||||
@ -556,54 +561,6 @@ void Emulator::Restart(std::filesystem::path eboot_path,
|
||||
std::quick_exit(0);
|
||||
}
|
||||
|
||||
void Emulator::LoadSystemModules(const std::string& game_serial) {
|
||||
constexpr auto ModulesToLoad = std::to_array<SysModules>(
|
||||
{{"libSceNgs2.sprx", &Libraries::Ngs2::RegisterLib},
|
||||
{"libSceUlt.sprx", nullptr},
|
||||
{"libSceRtc.sprx", &Libraries::Rtc::RegisterLib},
|
||||
{"libSceJpegDec.sprx", nullptr},
|
||||
{"libSceJpegEnc.sprx", &Libraries::JpegEnc::RegisterLib},
|
||||
{"libScePngEnc.sprx", &Libraries::PngEnc::RegisterLib},
|
||||
{"libSceJson.sprx", nullptr},
|
||||
{"libSceJson2.sprx", nullptr},
|
||||
{"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterLib},
|
||||
{"libSceCesCs.sprx", nullptr},
|
||||
{"libSceAudiodec.sprx", nullptr},
|
||||
{"libSceFont.sprx", &Libraries::Font::RegisterlibSceFont},
|
||||
{"libSceFontFt.sprx", &Libraries::FontFt::RegisterlibSceFontFt},
|
||||
{"libSceFreeTypeOt.sprx", nullptr}});
|
||||
|
||||
std::vector<std::filesystem::path> found_modules;
|
||||
const auto& sys_module_path = Config::getSysModulesPath();
|
||||
for (const auto& entry : std::filesystem::directory_iterator(sys_module_path)) {
|
||||
found_modules.push_back(entry.path());
|
||||
}
|
||||
for (const auto& [module_name, init_func] : ModulesToLoad) {
|
||||
const auto it = std::ranges::find_if(
|
||||
found_modules, [&](const auto& path) { return path.filename() == module_name; });
|
||||
if (it != found_modules.end()) {
|
||||
LOG_INFO(Loader, "Loading {}", it->string());
|
||||
if (linker->LoadModule(*it) != -1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (init_func) {
|
||||
LOG_INFO(Loader, "Can't Load {} switching to HLE", module_name);
|
||||
init_func(&linker->GetHLESymbols());
|
||||
} else {
|
||||
LOG_INFO(Loader, "No HLE available for {} module", module_name);
|
||||
}
|
||||
}
|
||||
if (!game_serial.empty() && std::filesystem::exists(sys_module_path / game_serial)) {
|
||||
for (const auto& entry :
|
||||
std::filesystem::directory_iterator(sys_module_path / game_serial)) {
|
||||
LOG_INFO(Loader, "Loading {} from game serial file {}", entry.path().string(),
|
||||
game_serial);
|
||||
linker->LoadModule(entry.path());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Emulator::UpdatePlayTime(const std::string& serial) {
|
||||
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
||||
const auto filePath = (user_dir / "play_time.txt").string();
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@ -43,7 +43,7 @@ private:
|
||||
void LoadSystemModules(const std::string& game_serial);
|
||||
|
||||
Core::MemoryManager* memory;
|
||||
Input::GameController* controller;
|
||||
Input::GameControllers* controllers;
|
||||
Core::Linker* linker;
|
||||
std::unique_ptr<Frontend::WindowSDL> window;
|
||||
std::chrono::steady_clock::time_point start_time;
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <SDL3/SDL_events.h>
|
||||
#include <imgui.h>
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/path_util.h"
|
||||
#include "core/debug_state.h"
|
||||
#include "core/devtools/layer.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "imgui/imgui_layer.h"
|
||||
#include "imgui_core.h"
|
||||
#include "imgui_impl_sdl3.h"
|
||||
@ -219,7 +219,7 @@ void Render(const vk::CommandBuffer& cmdbuf, const vk::ImageView& image_view,
|
||||
return;
|
||||
}
|
||||
|
||||
if (Config::getVkHostMarkersEnabled()) {
|
||||
if (EmulatorSettings.IsVkHostMarkersEnabled()) {
|
||||
cmdbuf.beginDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{
|
||||
.pLabelName = "ImGui Render",
|
||||
});
|
||||
@ -244,7 +244,7 @@ void Render(const vk::CommandBuffer& cmdbuf, const vk::ImageView& image_view,
|
||||
cmdbuf.beginRendering(render_info);
|
||||
Vulkan::RenderDrawData(*draw_data, cmdbuf);
|
||||
cmdbuf.endRendering();
|
||||
if (Config::getVkHostMarkersEnabled()) {
|
||||
if (EmulatorSettings.IsVkHostMarkersEnabled()) {
|
||||
cmdbuf.endDebugUtilsLabelEXT();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on imgui_impl_sdl3.cpp from Dear ImGui repository
|
||||
|
||||
#include <imgui.h>
|
||||
#include "common/config.h"
|
||||
#include "core/debug_state.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/memory.h"
|
||||
#include "imgui_impl_sdl3.h"
|
||||
#include "input/controller.h"
|
||||
@ -396,7 +396,7 @@ bool ProcessEvent(const SDL_Event* event) {
|
||||
if (mouse_pos.x != bd->prev_mouse_pos.x || mouse_pos.y != bd->prev_mouse_pos.y) {
|
||||
bd->prev_mouse_pos.x = mouse_pos.x;
|
||||
bd->prev_mouse_pos.y = mouse_pos.y;
|
||||
if (Config::getCursorState() == Config::HideCursorState::Idle) {
|
||||
if (EmulatorSettings.GetCursorState() == HideCursorState::Idle) {
|
||||
bd->lastCursorMoveTime = bd->time;
|
||||
}
|
||||
}
|
||||
@ -656,16 +656,16 @@ static void UpdateMouseCursor() {
|
||||
return;
|
||||
SdlData* bd = GetBackendData();
|
||||
|
||||
s16 cursorState = Config::getCursorState();
|
||||
s16 cursorState = EmulatorSettings.GetCursorState();
|
||||
ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
|
||||
if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None ||
|
||||
cursorState == Config::HideCursorState::Always) {
|
||||
cursorState == HideCursorState::Always) {
|
||||
// Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
|
||||
SDL_HideCursor();
|
||||
|
||||
} else if (cursorState == Config::HideCursorState::Idle &&
|
||||
} else if (cursorState == HideCursorState::Idle &&
|
||||
bd->time - bd->lastCursorMoveTime >=
|
||||
Config::getCursorHideTimeout() * SDL_GetPerformanceFrequency()) {
|
||||
EmulatorSettings.GetCursorHideTimeout() * SDL_GetPerformanceFrequency()) {
|
||||
|
||||
bool wasCursorVisible = SDL_CursorVisible();
|
||||
SDL_HideCursor();
|
||||
@ -737,9 +737,8 @@ static void UpdateGamepads() {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
SdlData* bd = GetBackendData();
|
||||
|
||||
auto controller = Common::Singleton<Input::GameController>::Instance();
|
||||
auto engine = controller->GetEngine();
|
||||
SDL_Gamepad* SDLGamepad = engine->m_gamepad;
|
||||
auto& controllers = *Common::Singleton<Input::GameControllers>::Instance();
|
||||
SDL_Gamepad* SDLGamepad = controllers[0]->m_sdl_gamepad;
|
||||
// Update list of gamepads to use
|
||||
if (bd->want_update_gamepads_list && bd->gamepad_mode != ImGui_ImplSDL3_GamepadMode_Manual) {
|
||||
if (SDLGamepad) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <deque>
|
||||
@ -6,11 +6,11 @@
|
||||
|
||||
#include <imgui.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/config.h"
|
||||
#include "common/io_file.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/stb.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "imgui_impl_vulkan.h"
|
||||
#include "texture_manager.h"
|
||||
|
||||
@ -152,7 +152,7 @@ void WorkerLoop() {
|
||||
g_job_list.pop_front();
|
||||
g_job_list_mtx.unlock();
|
||||
|
||||
if (Config::getVkCrashDiagnosticEnabled()) {
|
||||
if (EmulatorSettings.IsVkCrashDiagnosticEnabled()) {
|
||||
// FIXME: Crash diagnostic hangs when building the command buffer here
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1,15 +1,20 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <sstream>
|
||||
#include <unordered_set>
|
||||
#include <SDL3/SDL.h>
|
||||
#include "common/config.h"
|
||||
#include <common/elf_info.h>
|
||||
#include <common/singleton.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "controller.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/kernel/time.h"
|
||||
#include "core/libraries/pad/pad.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
#include "core/user_settings.h"
|
||||
#include "input/controller.h"
|
||||
|
||||
static std::string SelectedGamepad = "";
|
||||
|
||||
namespace Input {
|
||||
|
||||
using Libraries::Pad::OrbisPadButtonDataOffset;
|
||||
@ -22,7 +27,15 @@ void State::OnButton(OrbisPadButtonDataOffset button, bool isPressed) {
|
||||
}
|
||||
}
|
||||
|
||||
void State::OnAxis(Axis axis, int value) {
|
||||
void State::OnAxis(Axis axis, int value, bool smooth) {
|
||||
auto const i = std::to_underlying(axis);
|
||||
// forcibly finish the previous smoothing task by jumping to the end
|
||||
axes[i] = axis_smoothing_end_values[i];
|
||||
|
||||
axis_smoothing_start_times[i] = time;
|
||||
axis_smoothing_start_values[i] = axes[i];
|
||||
axis_smoothing_end_values[i] = value;
|
||||
axis_smoothing_flags[i] = smooth;
|
||||
const auto toggle = [&](const auto button) {
|
||||
if (value > 0) {
|
||||
buttonsState |= button;
|
||||
@ -40,7 +53,6 @@ void State::OnAxis(Axis axis, int value) {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
axes[static_cast<int>(axis)] = value;
|
||||
}
|
||||
|
||||
void State::OnTouchpad(int touchIndex, bool isDown, float x, float y) {
|
||||
@ -61,6 +73,22 @@ void State::OnAccel(const float accel[3]) {
|
||||
acceleration.z = accel[2];
|
||||
}
|
||||
|
||||
void State::UpdateAxisSmoothing() {
|
||||
for (int i = 0; i < std::to_underlying(Axis::AxisMax); i++) {
|
||||
// if it's not to be smoothed or close enough, just jump to the end
|
||||
if (!axis_smoothing_flags[i] || std::abs(axes[i] - axis_smoothing_end_values[i]) < 16) {
|
||||
if (axes[i] != axis_smoothing_end_values[i]) {
|
||||
axes[i] = axis_smoothing_end_values[i];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
auto now = Libraries::Kernel::sceKernelGetProcessTime();
|
||||
f32 t =
|
||||
std::clamp((now - axis_smoothing_start_times[i]) / f32{axis_smoothing_time}, 0.f, 1.f);
|
||||
axes[i] = s32(axis_smoothing_start_values[i] * (1 - t) + axis_smoothing_end_values[i] * t);
|
||||
}
|
||||
}
|
||||
|
||||
GameController::GameController() : m_states_queue(64) {}
|
||||
|
||||
void GameController::ReadState(State* state, bool* isConnected, int* connectedCount) {
|
||||
@ -88,31 +116,82 @@ int GameController::ReadStates(State* states, int states_num, bool* isConnected,
|
||||
return ret_num;
|
||||
}
|
||||
|
||||
void GameController::Button(int id, OrbisPadButtonDataOffset button, bool is_pressed) {
|
||||
void GameController::Button(OrbisPadButtonDataOffset button, bool is_pressed) {
|
||||
m_state.OnButton(button, is_pressed);
|
||||
PushState();
|
||||
}
|
||||
|
||||
void GameController::Axis(int id, Input::Axis axis, int value) {
|
||||
m_state.OnAxis(axis, value);
|
||||
void GameController::Axis(Input::Axis axis, int value, bool smooth) {
|
||||
m_state.OnAxis(axis, value, smooth);
|
||||
PushState();
|
||||
}
|
||||
|
||||
void GameController::Gyro(int id, const float gyro[3]) {
|
||||
m_state.OnGyro(gyro);
|
||||
void GameController::Gyro(int id) {
|
||||
m_state.OnGyro(gyro_buf);
|
||||
PushState();
|
||||
}
|
||||
|
||||
void GameController::Acceleration(int id, const float acceleration[3]) {
|
||||
m_state.OnAccel(acceleration);
|
||||
void GameController::Acceleration(int id) {
|
||||
m_state.OnAccel(accel_buf);
|
||||
PushState();
|
||||
}
|
||||
|
||||
void GameController::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration,
|
||||
Libraries::Pad::OrbisFVector3& angularVelocity,
|
||||
float deltaTime,
|
||||
Libraries::Pad::OrbisFQuaternion& lastOrientation,
|
||||
Libraries::Pad::OrbisFQuaternion& orientation) {
|
||||
void GameController::UpdateGyro(const float gyro[3]) {
|
||||
std::scoped_lock l(m_states_queue_mutex);
|
||||
std::memcpy(gyro_buf, gyro, sizeof(gyro_buf));
|
||||
}
|
||||
|
||||
void GameController::UpdateAcceleration(const float acceleration[3]) {
|
||||
std::scoped_lock l(m_states_queue_mutex);
|
||||
std::memcpy(accel_buf, acceleration, sizeof(accel_buf));
|
||||
}
|
||||
|
||||
void GameController::UpdateAxisSmoothing() {
|
||||
m_state.UpdateAxisSmoothing();
|
||||
}
|
||||
|
||||
void GameController::SetLightBarRGB(u8 r, u8 g, u8 b) {
|
||||
colour = {r, g, b};
|
||||
if (m_sdl_gamepad != nullptr) {
|
||||
SDL_SetGamepadLED(m_sdl_gamepad, r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
void GameController::PollLightColour() {
|
||||
if (m_sdl_gamepad != nullptr) {
|
||||
SDL_SetGamepadLED(m_sdl_gamepad, colour.r, colour.g, colour.b);
|
||||
}
|
||||
}
|
||||
|
||||
bool GameController::SetVibration(u8 smallMotor, u8 largeMotor) {
|
||||
if (m_sdl_gamepad != nullptr) {
|
||||
return SDL_RumbleGamepad(m_sdl_gamepad, (smallMotor / 255.0f) * 0xFFFF,
|
||||
(largeMotor / 255.0f) * 0xFFFF, -1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void GameController::SetTouchpadState(int touchIndex, bool touchDown, float x, float y) {
|
||||
if (touchIndex < 2) {
|
||||
m_state.OnTouchpad(touchIndex, touchDown, x, y);
|
||||
PushState();
|
||||
}
|
||||
}
|
||||
|
||||
std::array<std::optional<Colour>, 4> GameControllers::controller_override_colors{
|
||||
std::nullopt, std::nullopt, std::nullopt, std::nullopt};
|
||||
|
||||
void GameControllers::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration,
|
||||
Libraries::Pad::OrbisFVector3& angularVelocity,
|
||||
float deltaTime,
|
||||
Libraries::Pad::OrbisFQuaternion& lastOrientation,
|
||||
Libraries::Pad::OrbisFQuaternion& orientation) {
|
||||
// avoid wildly off values coming from elapsed time between two samples
|
||||
// being too high, such as on the first time the controller is polled
|
||||
if (deltaTime > 1.0f) {
|
||||
orientation = lastOrientation;
|
||||
return;
|
||||
}
|
||||
Libraries::Pad::OrbisFQuaternion q = lastOrientation;
|
||||
Libraries::Pad::OrbisFQuaternion ω = {angularVelocity.x, angularVelocity.y, angularVelocity.z,
|
||||
0.0f};
|
||||
@ -143,27 +222,100 @@ void GameController::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceler
|
||||
orientation.y, orientation.z, orientation.w);
|
||||
}
|
||||
|
||||
void GameController::SetLightBarRGB(u8 r, u8 g, u8 b) {
|
||||
if (!m_engine) {
|
||||
return;
|
||||
}
|
||||
m_engine->SetLightBarRGB(r, g, b);
|
||||
}
|
||||
bool is_first_check = true;
|
||||
|
||||
void GameController::SetVibration(u8 smallMotor, u8 largeMotor) {
|
||||
if (!m_engine) {
|
||||
return;
|
||||
}
|
||||
m_engine->SetVibration(smallMotor, largeMotor);
|
||||
}
|
||||
void GameControllers::TryOpenSDLControllers() {
|
||||
using namespace Libraries::UserService;
|
||||
int controller_count;
|
||||
s32 move_count = 0;
|
||||
SDL_JoystickID* new_joysticks = SDL_GetGamepads(&controller_count);
|
||||
LOG_INFO(Input, "{} controllers are currently connected", controller_count);
|
||||
|
||||
void GameController::SetTouchpadState(int touchIndex, bool touchDown, float x, float y) {
|
||||
if (touchIndex < 2) {
|
||||
m_state.OnTouchpad(touchIndex, touchDown, x, y);
|
||||
PushState();
|
||||
}
|
||||
}
|
||||
std::unordered_set<SDL_JoystickID> assigned_ids;
|
||||
std::array<bool, 4> slot_taken{false, false, false, false};
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
SDL_Gamepad* pad = controllers[i]->m_sdl_gamepad;
|
||||
if (pad) {
|
||||
SDL_JoystickID id = SDL_GetGamepadID(pad);
|
||||
bool still_connected = false;
|
||||
ControllerType type = ControllerType::Standard;
|
||||
for (int j = 0; j < controller_count; j++) {
|
||||
if (new_joysticks[j] == id) {
|
||||
still_connected = true;
|
||||
assigned_ids.insert(id);
|
||||
slot_taken[i] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!still_connected) {
|
||||
auto u = UserManagement.GetUserByID(controllers[i]->user_id);
|
||||
UserManagement.LogoutUser(u);
|
||||
SDL_CloseGamepad(pad);
|
||||
controllers[i]->m_sdl_gamepad = nullptr;
|
||||
controllers[i]->user_id = -1;
|
||||
slot_taken[i] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < controller_count; j++) {
|
||||
SDL_JoystickID id = new_joysticks[j];
|
||||
if (assigned_ids.contains(id))
|
||||
continue;
|
||||
|
||||
SDL_Gamepad* pad = SDL_OpenGamepad(id);
|
||||
if (!pad) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (!slot_taken[i]) {
|
||||
auto u = UserManagement.GetUserByPlayerIndex(i + 1);
|
||||
if (!u) {
|
||||
LOG_INFO(Input, "User {} not found", i + 1);
|
||||
continue; // for now, if you don't specify who Player N is in the config,
|
||||
// Player N won't be registered at all
|
||||
}
|
||||
auto* c = controllers[i];
|
||||
c->m_sdl_gamepad = pad;
|
||||
LOG_INFO(Input, "Gamepad registered for slot {}! Handle: {}", i,
|
||||
SDL_GetGamepadID(pad));
|
||||
c->user_id = u->user_id;
|
||||
slot_taken[i] = true;
|
||||
UserManagement.LoginUser(u, i + 1);
|
||||
if (EmulatorSettings.IsMotionControlsEnabled()) {
|
||||
if (SDL_SetGamepadSensorEnabled(c->m_sdl_gamepad, SDL_SENSOR_GYRO, true)) {
|
||||
c->gyro_poll_rate =
|
||||
SDL_GetGamepadSensorDataRate(c->m_sdl_gamepad, SDL_SENSOR_GYRO);
|
||||
LOG_INFO(Input, "Gyro initialized, poll rate: {}", c->gyro_poll_rate);
|
||||
} else {
|
||||
LOG_ERROR(Input, "Failed to initialize gyro controls for gamepad {}",
|
||||
c->user_id);
|
||||
}
|
||||
if (SDL_SetGamepadSensorEnabled(c->m_sdl_gamepad, SDL_SENSOR_ACCEL, true)) {
|
||||
c->accel_poll_rate =
|
||||
SDL_GetGamepadSensorDataRate(c->m_sdl_gamepad, SDL_SENSOR_ACCEL);
|
||||
LOG_INFO(Input, "Accel initialized, poll rate: {}", c->accel_poll_rate);
|
||||
} else {
|
||||
LOG_ERROR(Input, "Failed to initialize accel controls for gamepad {}",
|
||||
c->user_id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_first_check) [[unlikely]] {
|
||||
is_first_check = false;
|
||||
if (controller_count - move_count == 0) {
|
||||
auto u = UserManagement.GetUserByPlayerIndex(1);
|
||||
controllers[0]->user_id = u->user_id;
|
||||
UserManagement.LoginUser(u, 1);
|
||||
}
|
||||
}
|
||||
SDL_free(new_joysticks);
|
||||
}
|
||||
u8 GameController::GetTouchCount() {
|
||||
return m_touch_count;
|
||||
}
|
||||
@ -215,73 +367,37 @@ void GameController::SetLastUpdate(std::chrono::steady_clock::time_point lastUpd
|
||||
m_last_update = lastUpdate;
|
||||
}
|
||||
|
||||
void GameController::SetEngine(std::unique_ptr<Engine> engine) {
|
||||
m_engine = std::move(engine);
|
||||
if (m_engine) {
|
||||
m_engine->Init();
|
||||
}
|
||||
}
|
||||
|
||||
Engine* GameController::GetEngine() {
|
||||
return m_engine.get();
|
||||
}
|
||||
|
||||
void GameController::PushState() {
|
||||
std::lock_guard lg(m_states_queue_mutex);
|
||||
m_state.time = Libraries::Kernel::sceKernelGetProcessTime();
|
||||
m_states_queue.Push(m_state);
|
||||
}
|
||||
|
||||
u32 GameController::Poll() {
|
||||
if (m_connected) {
|
||||
PushState();
|
||||
}
|
||||
return 33;
|
||||
}
|
||||
|
||||
} // namespace Input
|
||||
|
||||
namespace GamepadSelect {
|
||||
|
||||
int GetDefaultGamepad(SDL_JoystickID* gamepadIDs, int gamepadCount) {
|
||||
char GUIDbuf[33];
|
||||
if (Config::getDefaultControllerID() != "") {
|
||||
for (int i = 0; i < gamepadCount; i++) {
|
||||
SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepadIDs[i]), GUIDbuf, 33);
|
||||
std::string currentGUID = std::string(GUIDbuf);
|
||||
if (currentGUID == Config::getDefaultControllerID()) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int GetIndexfromGUID(SDL_JoystickID* gamepadIDs, int gamepadCount, std::string GUID) {
|
||||
char GUIDbuf[33];
|
||||
for (int i = 0; i < gamepadCount; i++) {
|
||||
SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepadIDs[i]), GUIDbuf, 33);
|
||||
std::string currentGUID = std::string(GUIDbuf);
|
||||
if (currentGUID == GUID) {
|
||||
u8 GameControllers::GetGamepadIndexFromJoystickId(SDL_JoystickID id) {
|
||||
auto g = SDL_GetGamepadFromID(id);
|
||||
ASSERT(g != nullptr);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (controllers[i]->m_sdl_gamepad == g) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
// LOG_TRACE(Input, "Gamepad index: {}", index);
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string GetGUIDString(SDL_JoystickID* gamepadIDs, int index) {
|
||||
char GUIDbuf[33];
|
||||
SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepadIDs[index]), GUIDbuf, 33);
|
||||
std::string GUID = std::string(GUIDbuf);
|
||||
return GUID;
|
||||
std::optional<u8> GameControllers::GetControllerIndexFromUserID(s32 user_id) {
|
||||
auto const u = UserManagement.GetUserByID(user_id);
|
||||
if (!u) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return u->player_index - 1;
|
||||
}
|
||||
|
||||
std::string GetSelectedGamepad() {
|
||||
return SelectedGamepad;
|
||||
std::optional<u8> GameControllers::GetControllerIndexFromControllerID(s32 controller_id) {
|
||||
if (controller_id < 1 || controller_id > 5) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return controller_id - 1;
|
||||
}
|
||||
|
||||
void SetSelectedGamepad(std::string GUID) {
|
||||
SelectedGamepad = GUID;
|
||||
}
|
||||
|
||||
} // namespace GamepadSelect
|
||||
} // namespace Input
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user