mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-01-31 00:55:19 +01:00
Compare commits
132 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0987ecc73 | ||
|
|
d1150ad303 | ||
|
|
8b46650fb2 | ||
|
|
4f11a8c979 | ||
|
|
5bc4183e36 | ||
|
|
4f3aabd7af | ||
|
|
39a7738d04 | ||
|
|
25d175fd41 | ||
|
|
f14bad729c | ||
|
|
cc70fa8bb5 | ||
|
|
9314633573 | ||
|
|
c81ebe6418 | ||
|
|
1473b2358a | ||
|
|
b17ac0fdda | ||
|
|
1e059cac04 | ||
|
|
4ba0e62670 | ||
|
|
514e363472 | ||
|
|
1e99c4b506 | ||
|
|
fa497f6bfd | ||
|
|
c8b45e5ebc | ||
|
|
46a7c4e1f5 | ||
|
|
fecfbb6b4a | ||
|
|
508bad87d5 | ||
|
|
62813c0106 | ||
|
|
0d5c5f81a6 | ||
|
|
3e1f5a0bfb | ||
|
|
c898071b72 | ||
|
|
950d390daf | ||
|
|
220e5f67e7 | ||
|
|
dce9c04383 | ||
|
|
11ee79a333 | ||
|
|
cdf3c468b6 | ||
|
|
1a99ab7b09 | ||
|
|
acb8d06636 | ||
|
|
108cefaf53 | ||
|
|
540fdcc812 | ||
|
|
910f737079 | ||
|
|
3844a2fb54 | ||
|
|
ae47dd5d54 | ||
|
|
12cd27d0b7 | ||
|
|
ed73d2b02c | ||
|
|
b84c5ed110 | ||
|
|
f42a566cef | ||
|
|
be99cb4d5b | ||
|
|
f7a473b391 | ||
|
|
233192fa95 | ||
|
|
240c1d6441 | ||
|
|
299159b1e0 | ||
|
|
42220dfbba | ||
|
|
55e2b0f520 | ||
|
|
256397aa3b | ||
|
|
35da0506b6 | ||
|
|
975b1d312a | ||
|
|
df844f8c02 | ||
|
|
954cc77110 | ||
|
|
9a3e4ea56b | ||
|
|
aa227cae57 | ||
|
|
c414d1f5a1 | ||
|
|
05f14e3682 | ||
|
|
138425fdf4 | ||
|
|
2bbb04ff55 | ||
|
|
eae5e0ad55 | ||
|
|
9e287564ce | ||
|
|
9e7df6ae54 | ||
|
|
de6c5bbb83 | ||
|
|
65f0b07c34 | ||
|
|
2a5910ed51 | ||
|
|
391d30cbb1 | ||
|
|
d3ad728ac0 | ||
|
|
5183cbe686 | ||
|
|
9e80cde60d | ||
|
|
98fd0689ac | ||
|
|
9db4642f66 | ||
|
|
b135a056ba | ||
|
|
dc6013cf0e | ||
|
|
e5ea55e425 | ||
|
|
c3f7a4301c | ||
|
|
a5f9280841 | ||
|
|
cf866ab294 | ||
|
|
052f3260f3 | ||
|
|
78e301c3db | ||
|
|
a9f8eaf778 | ||
|
|
f9ef57f74b | ||
|
|
1394852791 | ||
|
|
6295c32e5c | ||
|
|
14d71a155a | ||
|
|
2577dfde7e | ||
|
|
8123c44ad1 | ||
|
|
f1a8b7d85e | ||
|
|
f6ae5544fd | ||
|
|
4922d526fe | ||
|
|
56109a1331 | ||
|
|
544a22a431 | ||
|
|
6612a32523 | ||
|
|
3f86c2e94a | ||
|
|
5b699090e6 | ||
|
|
aa5c045555 | ||
|
|
ed14359c87 | ||
|
|
6a9f9abda0 | ||
|
|
2f55636626 | ||
|
|
94d0f2e7ed | ||
|
|
f557c6ac64 | ||
|
|
93c340c6e1 | ||
|
|
25344a3b89 | ||
|
|
bbd985fe4b | ||
|
|
ee2bc97248 | ||
|
|
bebfee58d6 | ||
|
|
5ddabda2b8 | ||
|
|
3ce1ac5e86 | ||
|
|
b4628b80e2 | ||
|
|
2f022a462d | ||
|
|
f5505daaca | ||
|
|
8c1ec863da | ||
|
|
604f8d9398 | ||
|
|
19e974bf21 | ||
|
|
7031f5968e | ||
|
|
ff8869262f | ||
|
|
683e5f3b04 | ||
|
|
08fe66a97f | ||
|
|
bc44865cda | ||
|
|
a42ae46553 | ||
|
|
a4c3c665fe | ||
|
|
caccc05fb2 | ||
|
|
8238ecf88a | ||
|
|
8bbb3956a2 | ||
|
|
f466352dde | ||
|
|
430f2e4700 | ||
|
|
6c7c5eb59c | ||
|
|
493cda07c0 | ||
|
|
eda6be746f | ||
|
|
ed9ffbfb64 | ||
|
|
5cabd6ddd8 |
33
.github/linux-appimage-qt.sh
vendored
33
.github/linux-appimage-qt.sh
vendored
@@ -1,33 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
if [[ -z $GITHUB_WORKSPACE ]]; then
|
||||
GITHUB_WORKSPACE="${PWD%/*}"
|
||||
fi
|
||||
|
||||
export Qt6_DIR="/usr/lib/qt6"
|
||||
export PATH="$Qt6_DIR/bin:$PATH"
|
||||
export EXTRA_QT_PLUGINS="waylandcompositor"
|
||||
export EXTRA_PLATFORM_PLUGINS="libqwayland-egl.so;libqwayland-generic.so"
|
||||
|
||||
# Prepare Tools for building the AppImage
|
||||
wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
|
||||
wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage
|
||||
wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-checkrt/releases/download/continuous/linuxdeploy-plugin-checkrt-x86_64.sh
|
||||
|
||||
chmod a+x linuxdeploy-x86_64.AppImage
|
||||
chmod a+x linuxdeploy-plugin-qt-x86_64.AppImage
|
||||
chmod a+x linuxdeploy-plugin-checkrt-x86_64.sh
|
||||
|
||||
# Build AppImage
|
||||
./linuxdeploy-x86_64.AppImage --appdir AppDir
|
||||
./linuxdeploy-plugin-checkrt-x86_64.sh --appdir AppDir
|
||||
|
||||
cp -a "$GITHUB_WORKSPACE/build/translations" AppDir/usr/bin
|
||||
|
||||
./linuxdeploy-x86_64.AppImage --appdir AppDir -d "$GITHUB_WORKSPACE"/dist/net.shadps4.shadPS4.desktop -e "$GITHUB_WORKSPACE"/build/shadps4 -i "$GITHUB_WORKSPACE"/src/images/net.shadps4.shadPS4.svg --plugin qt
|
||||
rm AppDir/usr/plugins/multimedia/libgstreamermediaplugin.so
|
||||
./linuxdeploy-x86_64.AppImage --appdir AppDir --output appimage
|
||||
mv shadPS4-x86_64.AppImage Shadps4-qt.AppImage
|
||||
213
.github/workflows/build.yml
vendored
213
.github/workflows/build.yml
vendored
@@ -95,64 +95,6 @@ jobs:
|
||||
name: shadps4-win64-sdl-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
|
||||
path: ${{github.workspace}}/build/shadPS4.exe
|
||||
|
||||
windows-qt:
|
||||
runs-on: windows-2025
|
||||
needs: get-info
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup Qt
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: 6.9.3
|
||||
host: windows
|
||||
target: desktop
|
||||
arch: win64_msvc2022_64
|
||||
archives: qtbase qttools
|
||||
modules: qtmultimedia
|
||||
|
||||
- name: Cache CMake Configuration
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
cache-name: ${{ runner.os }}-qt-ninja-cache-cmake-configuration
|
||||
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
|
||||
env:
|
||||
cache-name: ${{ runner.os }}-qt-cache-cmake-build
|
||||
with:
|
||||
append-timestamp: false
|
||||
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
||||
|
||||
- name: Build
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $env:NUMBER_OF_PROCESSORS
|
||||
|
||||
- name: Deploy and Package
|
||||
run: |
|
||||
mkdir upload
|
||||
mkdir upload/qtplugins
|
||||
move build/shadPS4.exe upload
|
||||
cp dist/qt.conf upload/qt.conf
|
||||
windeployqt --plugindir upload/qtplugins --no-compiler-runtime --no-system-d3d-compiler --no-system-dxc-compiler --dir upload upload/shadPS4.exe
|
||||
Compress-Archive -Path upload/* -DestinationPath shadps4-win64-qt-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}.zip
|
||||
|
||||
- name: Upload Windows Qt artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: shadps4-win64-qt-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
|
||||
path: upload/
|
||||
|
||||
macos-sdl:
|
||||
runs-on: macos-15
|
||||
needs: get-info
|
||||
@@ -204,67 +146,6 @@ jobs:
|
||||
name: shadps4-macos-sdl-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
|
||||
path: upload/
|
||||
|
||||
macos-qt:
|
||||
runs-on: macos-15
|
||||
needs: get-info
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup latest Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: latest
|
||||
|
||||
- name: Setup Qt
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: 6.9.3
|
||||
host: mac
|
||||
target: desktop
|
||||
arch: clang_64
|
||||
archives: qtbase qttools
|
||||
modules: qtmultimedia
|
||||
|
||||
- name: Cache CMake Configuration
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
cache-name: ${{ runner.os }}-qt-cache-cmake-configuration
|
||||
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
|
||||
env:
|
||||
cache-name: ${{runner.os}}-qt-cache-cmake-build
|
||||
with:
|
||||
append-timestamp: false
|
||||
create-symlink: true
|
||||
key: ${{env.cache-name}}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
|
||||
variant: sccache
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache
|
||||
|
||||
- name: Build
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(sysctl -n hw.ncpu)
|
||||
|
||||
- name: Package and Upload macOS Qt artifact
|
||||
run: |
|
||||
mkdir upload
|
||||
mv ${{github.workspace}}/build/shadps4.app upload
|
||||
macdeployqt upload/shadps4.app
|
||||
tar cf shadps4-macos-qt.tar.gz -C upload .
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: shadps4-macos-qt-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
|
||||
path: shadps4-macos-qt.tar.gz
|
||||
|
||||
linux-sdl:
|
||||
runs-on: ubuntu-24.04
|
||||
needs: get-info
|
||||
@@ -279,7 +160,7 @@ jobs:
|
||||
sudo add-apt-repository 'deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main'
|
||||
|
||||
- name: Install dependencies
|
||||
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
|
||||
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
|
||||
@@ -326,58 +207,6 @@ jobs:
|
||||
name: shadps4-linux-sdl-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
|
||||
path: Shadps4-sdl.AppImage
|
||||
|
||||
linux-qt:
|
||||
runs-on: ubuntu-24.04
|
||||
needs: get-info
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Add LLVM repository
|
||||
run: |
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
||||
sudo add-apt-repository 'deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main'
|
||||
|
||||
- name: Install dependencies
|
||||
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 qt6-base-dev qt6-tools-dev qt6-multimedia-dev libasound2-dev libpulse-dev libopenal-dev libudev-dev
|
||||
|
||||
- name: Cache CMake Configuration
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
cache-name: ${{ runner.os }}-qt-cache-cmake-configuration
|
||||
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
|
||||
env:
|
||||
cache-name: ${{ runner.os }}-qt-cache-cmake-build
|
||||
with:
|
||||
append-timestamp: false
|
||||
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=clang-19 -DCMAKE_CXX_COMPILER=clang++-19 -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=mold" -DCMAKE_SHARED_LINKER_FLAGS="-fuse-ld=mold" -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
||||
|
||||
- name: Build
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(nproc)
|
||||
|
||||
- name: Run AppImage packaging script
|
||||
run: ./.github/linux-appimage-qt.sh
|
||||
|
||||
- name: Package and Upload Linux Qt artifact
|
||||
run: |
|
||||
tar cf shadps4-linux-qt.tar.gz -C ${{github.workspace}}/build shadps4
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: shadps4-linux-qt-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
|
||||
path: Shadps4-qt.AppImage
|
||||
|
||||
linux-sdl-gcc:
|
||||
runs-on: ubuntu-24.04
|
||||
needs: get-info
|
||||
@@ -387,7 +216,7 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install dependencies
|
||||
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
|
||||
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
|
||||
@@ -414,45 +243,9 @@ jobs:
|
||||
- name: Build
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(nproc)
|
||||
|
||||
linux-qt-gcc:
|
||||
runs-on: ubuntu-24.04
|
||||
needs: get-info
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install dependencies
|
||||
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 qt6-base-dev qt6-tools-dev qt6-multimedia-dev libasound2-dev libpulse-dev libopenal-dev libudev-dev
|
||||
|
||||
- name: Cache CMake Configuration
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
cache-name: ${{ runner.os }}-qt-gcc-cache-cmake-configuration
|
||||
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
|
||||
env:
|
||||
cache-name: ${{ runner.os }}-qt-gcc-cache-cmake-build
|
||||
with:
|
||||
append-timestamp: false
|
||||
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=mold" -DCMAKE_SHARED_LINKER_FLAGS="-fuse-ld=mold" -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
||||
|
||||
- name: Build
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(nproc)
|
||||
|
||||
pre-release:
|
||||
if: github.ref == 'refs/heads/main' && github.repository == 'shadps4-emu/shadPS4' && github.event_name == 'push'
|
||||
needs: [get-info, windows-sdl, windows-qt, macos-sdl, macos-qt, linux-sdl, linux-qt]
|
||||
needs: [get-info, windows-sdl, macos-sdl, linux-sdl]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
|
||||
26
.gitmodules
vendored
26
.gitmodules
vendored
@@ -2,10 +2,6 @@
|
||||
path = externals/zlib-ng
|
||||
url = https://github.com/shadps4-emu/ext-zlib-ng.git
|
||||
shallow = true
|
||||
[submodule "externals/sdl3"]
|
||||
path = externals/sdl3
|
||||
url = https://github.com/shadps4-emu/ext-SDL.git
|
||||
shallow = true
|
||||
[submodule "externals/fmt"]
|
||||
path = externals/fmt
|
||||
url = https://github.com/shadps4-emu/ext-fmt.git
|
||||
@@ -108,5 +104,25 @@
|
||||
branch = dist
|
||||
[submodule "externals/MoltenVK"]
|
||||
path = externals/MoltenVK
|
||||
url = https://github.com/KhronosGroup/MoltenVK.git
|
||||
url = https://github.com/shadPS4-emu/ext-MoltenVK.git
|
||||
shallow = true
|
||||
[submodule "externals/json"]
|
||||
path = externals/json
|
||||
url = https://github.com/nlohmann/json.git
|
||||
[submodule "externals/sdl3_mixer"]
|
||||
path = externals/sdl3_mixer
|
||||
url = https://github.com/libsdl-org/SDL_mixer
|
||||
shallow = true
|
||||
[submodule "externals/miniz"]
|
||||
path = externals/miniz
|
||||
url = https://github.com/richgel999/miniz
|
||||
[submodule "externals/aacdec/fdk-aac"]
|
||||
path = externals/aacdec/fdk-aac
|
||||
url = https://android.googlesource.com/platform/external/aac
|
||||
[submodule "externals/CLI11"]
|
||||
path = externals/CLI11
|
||||
url = https://github.com/shadexternals/CLI11.git
|
||||
[submodule "externals/sdl3"]
|
||||
path = externals/sdl3
|
||||
url = https://github.com/shadexternals/sdl3.git
|
||||
|
||||
|
||||
273
CMakeLists.txt
273
CMakeLists.txt
@@ -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
|
||||
|
||||
# Version 3.24 needed for FetchContent OVERRIDE_FIND_PACKAGE
|
||||
@@ -31,7 +31,6 @@ if(UNIX AND NOT APPLE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
option(ENABLE_QT_GUI "Enable the Qt GUI. If not selected then the emulator uses a minimal SDL-based UI instead" OFF)
|
||||
option(ENABLE_DISCORD_RPC "Enable the Discord RPC integration" ON)
|
||||
option(ENABLE_UPDATER "Enables the options to updater" ON)
|
||||
|
||||
@@ -203,13 +202,13 @@ execute_process(
|
||||
|
||||
# Set Version
|
||||
set(EMULATOR_VERSION_MAJOR "0")
|
||||
set(EMULATOR_VERSION_MINOR "12")
|
||||
set(EMULATOR_VERSION_PATCH "0")
|
||||
set(EMULATOR_VERSION_MINOR "13")
|
||||
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}")
|
||||
|
||||
set(APP_VERSION "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH}")
|
||||
set(APP_IS_RELEASE true)
|
||||
set(APP_VERSION "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH} WIP")
|
||||
set(APP_IS_RELEASE false)
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/src/common/scm_rev.cpp" @ONLY)
|
||||
|
||||
message("-- end git things, remote: ${GIT_REMOTE_NAME}, branch: ${GIT_BRANCH}, link: ${GIT_REMOTE_URL}")
|
||||
@@ -220,10 +219,6 @@ if(NOT (GIT_REMOTE_URL_LOWER MATCHES "shadps4-emu/shadps4" AND (GIT_BRANCH STREQ
|
||||
set(ENABLE_UPDATER OFF)
|
||||
endif()
|
||||
|
||||
if(WIN32 AND ENABLE_QT_GUI AND NOT CMAKE_PREFIX_PATH)
|
||||
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/DetectQtInstallation.cmake")
|
||||
endif ()
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
find_package(Boost 1.84.0 CONFIG)
|
||||
find_package(FFmpeg 5.1.2 MODULE)
|
||||
@@ -233,7 +228,10 @@ find_package(half 1.12.0 MODULE)
|
||||
find_package(magic_enum 0.9.7 CONFIG)
|
||||
find_package(PNG 1.6 MODULE)
|
||||
find_package(RenderDoc 1.6.0 MODULE)
|
||||
find_package(SDL3 3.1.2 CONFIG)
|
||||
find_package(SDL3_mixer 2.8.1 CONFIG)
|
||||
if (SDL3_mixer_FOUND)
|
||||
find_package(SDL3 3.1.2 CONFIG)
|
||||
endif()
|
||||
find_package(stb MODULE)
|
||||
find_package(toml11 4.2.0 CONFIG)
|
||||
find_package(tsl-robin-map 1.3.0 CONFIG)
|
||||
@@ -262,32 +260,10 @@ endif()
|
||||
add_subdirectory(externals)
|
||||
include_directories(src)
|
||||
|
||||
if(ENABLE_QT_GUI)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Widgets Concurrent LinguistTools Network Multimedia)
|
||||
qt_standard_project_setup()
|
||||
set(CMAKE_AUTORCC ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
|
||||
set(QT_TRANSLATIONS "${PROJECT_SOURCE_DIR}/src/qt_gui/translations")
|
||||
file(GLOB_RECURSE TRANSLATIONS_TS ${QT_TRANSLATIONS}/*.ts)
|
||||
|
||||
set_source_files_properties(${TRANSLATIONS_TS} PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/translations")
|
||||
qt_add_translation(TRANSLATIONS_QM ${TRANSLATIONS_TS})
|
||||
|
||||
set(TRANSLATIONS_QRC ${CMAKE_CURRENT_BINARY_DIR}/translations/translations.qrc)
|
||||
file(WRITE ${TRANSLATIONS_QRC} "<RCC><qresource prefix=\"translations\">\n")
|
||||
foreach (QM ${TRANSLATIONS_QM})
|
||||
get_filename_component(QM_FILE ${QM} NAME)
|
||||
file(APPEND ${TRANSLATIONS_QRC} "<file>${QM_FILE}</file>\n")
|
||||
endforeach (QM)
|
||||
file(APPEND ${TRANSLATIONS_QRC} "</qresource></RCC>")
|
||||
|
||||
qt_add_resources(TRANSLATIONS ${TRANSLATIONS_QRC})
|
||||
endif()
|
||||
|
||||
set(AJM_LIB src/core/libraries/ajm/ajm.cpp
|
||||
src/core/libraries/ajm/ajm.h
|
||||
src/core/libraries/ajm/ajm_aac.cpp
|
||||
src/core/libraries/ajm/ajm_aac.h
|
||||
src/core/libraries/ajm/ajm_at9.cpp
|
||||
src/core/libraries/ajm/ajm_at9.h
|
||||
src/core/libraries/ajm/ajm_batch.cpp
|
||||
@@ -448,6 +424,8 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp
|
||||
src/core/libraries/rtc/rtc.cpp
|
||||
src/core/libraries/rtc/rtc.h
|
||||
src/core/libraries/rtc/rtc_error.h
|
||||
src/core/libraries/rudp/rudp.cpp
|
||||
src/core/libraries/rudp/rudp.h
|
||||
src/core/libraries/disc_map/disc_map.cpp
|
||||
src/core/libraries/disc_map/disc_map.h
|
||||
src/core/libraries/disc_map/disc_map_codes.h
|
||||
@@ -490,6 +468,12 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp
|
||||
src/core/libraries/mouse/mouse.h
|
||||
src/core/libraries/web_browser_dialog/webbrowserdialog.cpp
|
||||
src/core/libraries/web_browser_dialog/webbrowserdialog.h
|
||||
src/core/libraries/font/font.cpp
|
||||
src/core/libraries/font/font.h
|
||||
src/core/libraries/font/fontft.cpp
|
||||
src/core/libraries/font/fontft.h
|
||||
src/core/libraries/font/font_error.h
|
||||
|
||||
)
|
||||
|
||||
set(VIDEOOUT_LIB src/core/libraries/videoout/buffer.h
|
||||
@@ -532,7 +516,7 @@ set(PAD_LIB src/core/libraries/pad/pad.cpp
|
||||
src/core/libraries/pad/pad_errors.h
|
||||
)
|
||||
|
||||
set(SYSTEM_GESTURE_LIB
|
||||
set(SYSTEM_GESTURE_LIB
|
||||
src/core/libraries/system_gesture/system_gesture.cpp
|
||||
src/core/libraries/system_gesture/system_gesture.h
|
||||
)
|
||||
@@ -540,6 +524,9 @@ set(SYSTEM_GESTURE_LIB
|
||||
set(PNG_LIB src/core/libraries/libpng/pngdec.cpp
|
||||
src/core/libraries/libpng/pngdec.h
|
||||
src/core/libraries/libpng/pngdec_error.h
|
||||
src/core/libraries/libpng/pngenc.cpp
|
||||
src/core/libraries/libpng/pngenc.h
|
||||
src/core/libraries/libpng/pngenc_error.h
|
||||
)
|
||||
|
||||
set(JPEG_LIB src/core/libraries/jpeg/jpeg_error.h
|
||||
@@ -561,6 +548,13 @@ set(RANDOM_LIB src/core/libraries/random/random.cpp
|
||||
|
||||
set(USBD_LIB src/core/libraries/usbd/usbd.cpp
|
||||
src/core/libraries/usbd/usbd.h
|
||||
src/core/libraries/usbd/usb_backend.h
|
||||
src/core/libraries/usbd/emulated/dimensions.cpp
|
||||
src/core/libraries/usbd/emulated/dimensions.h
|
||||
src/core/libraries/usbd/emulated/infinity.cpp
|
||||
src/core/libraries/usbd/emulated/infinity.h
|
||||
src/core/libraries/usbd/emulated/skylander.cpp
|
||||
src/core/libraries/usbd/emulated/skylander.h
|
||||
)
|
||||
|
||||
set(FIBER_LIB src/core/libraries/fiber/fiber_context.s
|
||||
@@ -569,6 +563,8 @@ set(FIBER_LIB src/core/libraries/fiber/fiber_context.s
|
||||
src/core/libraries/fiber/fiber_error.h
|
||||
)
|
||||
|
||||
set_source_files_properties(src/core/libraries/fiber/fiber_context.s PROPERTIES COMPILE_OPTIONS -Wno-unused-command-line-argument)
|
||||
|
||||
set(VDEC_LIB src/core/libraries/videodec/videodec2_impl.cpp
|
||||
src/core/libraries/videodec/videodec2_impl.h
|
||||
src/core/libraries/videodec/videodec2.cpp
|
||||
@@ -584,16 +580,24 @@ set(VDEC_LIB src/core/libraries/videodec/videodec2_impl.cpp
|
||||
set(NP_LIBS src/core/libraries/np/np_error.h
|
||||
src/core/libraries/np/np_common.cpp
|
||||
src/core/libraries/np/np_common.h
|
||||
src/core/libraries/np/np_commerce.cpp
|
||||
src/core/libraries/np/np_commerce.h
|
||||
src/core/libraries/np/np_manager.cpp
|
||||
src/core/libraries/np/np_manager.h
|
||||
src/core/libraries/np/np_matching2.cpp
|
||||
src/core/libraries/np/np_matching2.h
|
||||
src/core/libraries/np/np_score.cpp
|
||||
src/core/libraries/np/np_score.h
|
||||
src/core/libraries/np/np_trophy.cpp
|
||||
src/core/libraries/np/np_trophy.h
|
||||
src/core/libraries/np/np_tus.cpp
|
||||
src/core/libraries/np/np_tus.h
|
||||
src/core/libraries/np/trophy_ui.cpp
|
||||
src/core/libraries/np/trophy_ui.h
|
||||
src/core/libraries/np/np_web_api.cpp
|
||||
src/core/libraries/np/np_web_api.h
|
||||
src/core/libraries/np/np_web_api2.cpp
|
||||
src/core/libraries/np/np_web_api2.h
|
||||
src/core/libraries/np/np_party.cpp
|
||||
src/core/libraries/np/np_party.h
|
||||
src/core/libraries/np/np_auth.cpp
|
||||
@@ -602,6 +606,8 @@ set(NP_LIBS src/core/libraries/np/np_error.h
|
||||
src/core/libraries/np/np_profile_dialog.h
|
||||
src/core/libraries/np/np_sns_facebook_dialog.cpp
|
||||
src/core/libraries/np/np_sns_facebook_dialog.h
|
||||
src/core/libraries/np/np_partner.cpp
|
||||
src/core/libraries/np/np_partner.h
|
||||
)
|
||||
|
||||
set(ZLIB_LIB src/core/libraries/zlib/zlib.cpp
|
||||
@@ -643,6 +649,7 @@ set(COMPANION_LIBS src/core/libraries/companion/companion_httpd.cpp
|
||||
)
|
||||
set(DEV_TOOLS src/core/devtools/layer.cpp
|
||||
src/core/devtools/layer.h
|
||||
src/core/devtools/layer_extra.cpp
|
||||
src/core/devtools/options.cpp
|
||||
src/core/devtools/options.h
|
||||
src/core/devtools/gcn/gcn_context_regs.cpp
|
||||
@@ -703,7 +710,6 @@ set(COMMON src/common/logging/backend.cpp
|
||||
src/common/lru_cache.h
|
||||
src/common/error.cpp
|
||||
src/common/error.h
|
||||
src/common/scope_exit.h
|
||||
src/common/fixed_value.h
|
||||
src/common/func_traits.h
|
||||
src/common/native_clock.cpp
|
||||
@@ -717,6 +723,8 @@ set(COMMON src/common/logging/backend.cpp
|
||||
src/common/rdtsc.h
|
||||
src/common/recursive_lock.cpp
|
||||
src/common/recursive_lock.h
|
||||
src/common/scope_exit.h
|
||||
src/common/serdes.h
|
||||
src/common/sha1.h
|
||||
src/common/shared_first_mutex.h
|
||||
src/common/signal_context.h
|
||||
@@ -745,6 +753,8 @@ set(COMMON src/common/logging/backend.cpp
|
||||
src/common/memory_patcher.cpp
|
||||
${CMAKE_CURRENT_BINARY_DIR}/src/common/scm_rev.cpp
|
||||
src/common/scm_rev.h
|
||||
src/common/key_manager.cpp
|
||||
src/common/key_manager.h
|
||||
)
|
||||
|
||||
if (ENABLE_DISCORD_RPC)
|
||||
@@ -788,6 +798,8 @@ set(CORE src/core/aerolib/stubs.cpp
|
||||
src/core/file_format/playgo_chunk.h
|
||||
src/core/file_format/trp.cpp
|
||||
src/core/file_format/trp.h
|
||||
src/core/file_format/npbind.cpp
|
||||
src/core/file_format/npbind.h
|
||||
src/core/file_sys/fs.cpp
|
||||
src/core/file_sys/fs.h
|
||||
src/core/ipc/ipc.cpp
|
||||
@@ -843,6 +855,8 @@ set(CORE src/core/aerolib/stubs.cpp
|
||||
src/core/thread.h
|
||||
src/core/tls.cpp
|
||||
src/core/tls.h
|
||||
src/core/emulator_state.cpp
|
||||
src/core/emulator_state.h
|
||||
)
|
||||
|
||||
if (ARCHITECTURE STREQUAL "x86_64")
|
||||
@@ -911,6 +925,7 @@ set(SHADER_RECOMPILER src/shader_recompiler/profile.h
|
||||
src/shader_recompiler/ir/passes/flatten_extended_userdata_pass.cpp
|
||||
src/shader_recompiler/ir/passes/hull_shader_transform.cpp
|
||||
src/shader_recompiler/ir/passes/identity_removal_pass.cpp
|
||||
src/shader_recompiler/ir/passes/inject_clip_distance_attributes.cpp
|
||||
src/shader_recompiler/ir/passes/ir_passes.h
|
||||
src/shader_recompiler/ir/passes/lower_buffer_format_to_raw.cpp
|
||||
src/shader_recompiler/ir/passes/lower_fp64_to_fp32.cpp
|
||||
@@ -974,6 +989,8 @@ set(VIDEO_CORE src/video_core/amdgpu/cb_db_extent.h
|
||||
src/video_core/buffer_cache/buffer.h
|
||||
src/video_core/buffer_cache/buffer_cache.cpp
|
||||
src/video_core/buffer_cache/buffer_cache.h
|
||||
src/video_core/buffer_cache/fault_manager.cpp
|
||||
src/video_core/buffer_cache/fault_manager.h
|
||||
src/video_core/buffer_cache/memory_tracker.h
|
||||
src/video_core/buffer_cache/range_set.h
|
||||
src/video_core/buffer_cache/region_definitions.h
|
||||
@@ -994,6 +1011,8 @@ set(VIDEO_CORE src/video_core/amdgpu/cb_db_extent.h
|
||||
src/video_core/renderer_vulkan/vk_pipeline_cache.h
|
||||
src/video_core/renderer_vulkan/vk_pipeline_common.cpp
|
||||
src/video_core/renderer_vulkan/vk_pipeline_common.h
|
||||
src/video_core/renderer_vulkan/vk_pipeline_serialization.cpp
|
||||
src/video_core/renderer_vulkan/vk_pipeline_serialization.h
|
||||
src/video_core/renderer_vulkan/vk_platform.cpp
|
||||
src/video_core/renderer_vulkan/vk_platform.h
|
||||
src/video_core/renderer_vulkan/vk_presenter.cpp
|
||||
@@ -1031,6 +1050,8 @@ set(VIDEO_CORE src/video_core/amdgpu/cb_db_extent.h
|
||||
src/video_core/texture_cache/tile_manager.cpp
|
||||
src/video_core/texture_cache/tile_manager.h
|
||||
src/video_core/texture_cache/types.h
|
||||
src/video_core/cache_storage.cpp
|
||||
src/video_core/cache_storage.h
|
||||
src/video_core/page_manager.cpp
|
||||
src/video_core/page_manager.h
|
||||
src/video_core/multi_level_page_table.h
|
||||
@@ -1066,112 +1087,27 @@ set(EMULATOR src/emulator.cpp
|
||||
src/sdl_window.cpp
|
||||
)
|
||||
|
||||
# The above is shared in SDL and Qt version (TODO share them all)
|
||||
|
||||
if(ENABLE_QT_GUI)
|
||||
qt_add_resources(RESOURCE_FILES src/shadps4.qrc)
|
||||
|
||||
if (ENABLE_UPDATER)
|
||||
set(UPDATER src/qt_gui/check_update.cpp
|
||||
src/qt_gui/check_update.h
|
||||
)
|
||||
endif()
|
||||
|
||||
set(QT_GUI src/qt_gui/about_dialog.cpp
|
||||
src/qt_gui/about_dialog.h
|
||||
src/qt_gui/about_dialog.ui
|
||||
src/qt_gui/background_music_player.cpp
|
||||
src/qt_gui/background_music_player.h
|
||||
src/qt_gui/cheats_patches.cpp
|
||||
src/qt_gui/cheats_patches.h
|
||||
src/qt_gui/compatibility_info.cpp
|
||||
src/qt_gui/compatibility_info.h
|
||||
src/qt_gui/control_settings.cpp
|
||||
src/qt_gui/control_settings.h
|
||||
src/qt_gui/control_settings.ui
|
||||
src/qt_gui/kbm_gui.cpp
|
||||
src/qt_gui/kbm_gui.h
|
||||
src/qt_gui/kbm_gui.ui
|
||||
src/qt_gui/main_window_ui.h
|
||||
src/qt_gui/main_window.cpp
|
||||
src/qt_gui/main_window.h
|
||||
src/qt_gui/gui_context_menus.h
|
||||
src/qt_gui/game_list_utils.h
|
||||
src/qt_gui/game_info.cpp
|
||||
src/qt_gui/game_info.h
|
||||
src/qt_gui/game_list_frame.cpp
|
||||
src/qt_gui/game_list_frame.h
|
||||
src/qt_gui/game_grid_frame.cpp
|
||||
src/qt_gui/game_grid_frame.h
|
||||
src/qt_gui/game_install_dialog.cpp
|
||||
src/qt_gui/game_install_dialog.h
|
||||
src/qt_gui/trophy_viewer.cpp
|
||||
src/qt_gui/trophy_viewer.h
|
||||
src/qt_gui/elf_viewer.cpp
|
||||
src/qt_gui/elf_viewer.h
|
||||
src/qt_gui/kbm_config_dialog.cpp
|
||||
src/qt_gui/kbm_config_dialog.h
|
||||
src/qt_gui/kbm_help_dialog.cpp
|
||||
src/qt_gui/kbm_help_dialog.h
|
||||
src/qt_gui/main_window_themes.cpp
|
||||
src/qt_gui/main_window_themes.h
|
||||
src/qt_gui/log_presets_dialog.cpp
|
||||
src/qt_gui/log_presets_dialog.h
|
||||
src/qt_gui/settings_dialog.cpp
|
||||
src/qt_gui/settings_dialog.h
|
||||
src/qt_gui/settings_dialog.ui
|
||||
src/qt_gui/main.cpp
|
||||
src/qt_gui/gui_settings.cpp
|
||||
src/qt_gui/gui_settings.h
|
||||
src/qt_gui/settings.cpp
|
||||
src/qt_gui/settings.h
|
||||
src/qt_gui/sdl_event_wrapper.cpp
|
||||
src/qt_gui/sdl_event_wrapper.h
|
||||
src/qt_gui/hotkeys.h
|
||||
src/qt_gui/hotkeys.cpp
|
||||
src/qt_gui/hotkeys.ui
|
||||
${EMULATOR}
|
||||
${RESOURCE_FILES}
|
||||
${TRANSLATIONS}
|
||||
${UPDATER}
|
||||
add_executable(shadps4
|
||||
${AUDIO_CORE}
|
||||
${IMGUI}
|
||||
${INPUT}
|
||||
${COMMON}
|
||||
${CORE}
|
||||
${SHADER_RECOMPILER}
|
||||
${VIDEO_CORE}
|
||||
${EMULATOR}
|
||||
src/main.cpp
|
||||
src/emulator.cpp
|
||||
src/emulator.h
|
||||
src/sdl_window.h
|
||||
src/sdl_window.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if (ENABLE_QT_GUI)
|
||||
qt_add_executable(shadps4
|
||||
${AUDIO_CORE}
|
||||
${IMGUI}
|
||||
${INPUT}
|
||||
${QT_GUI}
|
||||
${COMMON}
|
||||
${CORE}
|
||||
${SHADER_RECOMPILER}
|
||||
${VIDEO_CORE}
|
||||
${EMULATOR}
|
||||
src/images/shadPS4.icns
|
||||
)
|
||||
else()
|
||||
add_executable(shadps4
|
||||
${AUDIO_CORE}
|
||||
${IMGUI}
|
||||
${INPUT}
|
||||
${COMMON}
|
||||
${CORE}
|
||||
${SHADER_RECOMPILER}
|
||||
${VIDEO_CORE}
|
||||
${EMULATOR}
|
||||
src/main.cpp
|
||||
src/emulator.cpp
|
||||
src/emulator.h
|
||||
src/sdl_window.h
|
||||
src/sdl_window.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
create_target_directory_groups(shadps4)
|
||||
|
||||
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG)
|
||||
target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 pugixml::pugixml stb::headers libusb::usb lfreist-hwinfo::hwinfo)
|
||||
target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 SDL3_mixer::SDL3_mixer pugixml::pugixml)
|
||||
target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz fdk-aac CLI11::CLI11)
|
||||
|
||||
target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h")
|
||||
target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h")
|
||||
@@ -1191,19 +1127,8 @@ endif()
|
||||
|
||||
if (APPLE)
|
||||
# Include MoltenVK, along with an ICD file so it can be found by the system Vulkan loader if used for loading layers.
|
||||
if (ENABLE_QT_GUI)
|
||||
set(MVK_BUNDLE_PATH "Resources/vulkan/icd.d")
|
||||
set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path/../${MVK_BUNDLE_PATH}")
|
||||
set(MVK_DST ${CMAKE_CURRENT_BINARY_DIR}/shadps4.app/Contents/${MVK_BUNDLE_PATH})
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${MVK_DST}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${MVK_DST})
|
||||
else()
|
||||
set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path")
|
||||
set(MVK_DST ${CMAKE_CURRENT_BINARY_DIR})
|
||||
endif()
|
||||
|
||||
set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path")
|
||||
set(MVK_DST ${CMAKE_CURRENT_BINARY_DIR})
|
||||
set(MVK_DYLIB_SRC ${CMAKE_CURRENT_BINARY_DIR}/externals/MoltenVK/MoltenVK/libMoltenVK.dylib)
|
||||
set(MVK_DYLIB_DST ${MVK_DST}/libMoltenVK.dylib)
|
||||
set(MVK_ICD_SRC ${CMAKE_CURRENT_SOURCE_DIR}/externals/MoltenVK/MoltenVK/icd/MoltenVK_icd.json)
|
||||
@@ -1230,31 +1155,23 @@ if (APPLE)
|
||||
target_link_libraries(shadps4 PRIVATE date::date-tz epoll-shim)
|
||||
endif()
|
||||
|
||||
if (ENABLE_QT_GUI)
|
||||
target_link_libraries(shadps4 PRIVATE Qt6::Widgets Qt6::Concurrent Qt6::Network Qt6::Multimedia)
|
||||
add_definitions(-DENABLE_QT_GUI)
|
||||
if (ENABLE_UPDATER)
|
||||
add_definitions(-DENABLE_UPDATER)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
target_link_libraries(shadps4 PRIVATE mincore wepoll)
|
||||
target_link_libraries(shadps4 PRIVATE mincore wepoll wbemuuid)
|
||||
|
||||
if (MSVC)
|
||||
# MSVC likes putting opinions on what people can use, disable:
|
||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS)
|
||||
add_compile_definitions(_CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE _SCL_SECURE_NO_WARNINGS)
|
||||
endif()
|
||||
|
||||
add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN)
|
||||
add_compile_definitions(NOMINMAX WIN32_LEAN_AND_MEAN)
|
||||
|
||||
if (MSVC)
|
||||
# Needed for conflicts with time.h of windows.h
|
||||
add_definitions(-D_TIMESPEC_DEFINED)
|
||||
add_compile_definitions(_TIMESPEC_DEFINED)
|
||||
endif()
|
||||
|
||||
# Target Windows 10 RS5
|
||||
add_definitions(-DNTDDI_VERSION=0x0A000006 -D_WIN32_WINNT=0x0A00 -DWINVER=0x0A00)
|
||||
add_compile_definitions(NTDDI_VERSION=0x0A000006 _WIN32_WINNT=0x0A00 WINVER=0x0A00)
|
||||
|
||||
if (MSVC)
|
||||
target_link_libraries(shadps4 PRIVATE clang_rt.builtins-x86_64.lib)
|
||||
@@ -1286,7 +1203,7 @@ if (WIN32)
|
||||
target_sources(shadps4 PRIVATE src/shadps4.rc)
|
||||
endif()
|
||||
|
||||
add_definitions(-DBOOST_ASIO_STANDALONE)
|
||||
add_compile_definitions(BOOST_ASIO_STANDALONE)
|
||||
|
||||
target_include_directories(shadps4 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
@@ -1315,25 +1232,6 @@ add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/imgui/renderer)
|
||||
add_dependencies(shadps4 ImGui_Resources)
|
||||
target_include_directories(shadps4 PRIVATE ${IMGUI_RESOURCES_INCLUDE})
|
||||
|
||||
if (ENABLE_QT_GUI)
|
||||
set_target_properties(shadps4 PROPERTIES
|
||||
# WIN32_EXECUTABLE ON
|
||||
MACOSX_BUNDLE ON
|
||||
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/dist/MacOSBundleInfo.plist.in"
|
||||
MACOSX_BUNDLE_ICON_FILE "shadPS4.icns"
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APP_VERSION}"
|
||||
)
|
||||
|
||||
set_source_files_properties(src/images/shadPS4.icns PROPERTIES
|
||||
MACOSX_PACKAGE_LOCATION Resources)
|
||||
endif()
|
||||
|
||||
if (UNIX AND NOT APPLE)
|
||||
if (ENABLE_QT_GUI)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
target_link_libraries(shadps4 PRIVATE ${OPENSSL_LIBRARIES})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Discord RPC
|
||||
if (ENABLE_DISCORD_RPC)
|
||||
@@ -1342,10 +1240,3 @@ endif()
|
||||
|
||||
# Install rules
|
||||
install(TARGETS shadps4 BUNDLE DESTINATION .)
|
||||
|
||||
if (ENABLE_QT_GUI AND CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
install(FILES "dist/net.shadps4.shadPS4.desktop" DESTINATION "share/applications")
|
||||
install(FILES "dist/net.shadps4.shadPS4.metainfo.xml" DESTINATION "share/metainfo")
|
||||
install(FILES ".github/shadps4.png" DESTINATION "share/icons/hicolor/512x512/apps" RENAME "net.shadps4.shadPS4.png")
|
||||
install(FILES "src/images/net.shadps4.shadPS4.svg" DESTINATION "share/icons/hicolor/scalable/apps")
|
||||
endif()
|
||||
|
||||
@@ -15,15 +15,6 @@
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "x64-Clang-Debug-Qt",
|
||||
"displayName": "Clang x64 Debug with Qt",
|
||||
"inherits": ["x64-Clang-Base"],
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"ENABLE_QT_GUI": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "x64-Clang-Release",
|
||||
"displayName": "Clang x64 Release",
|
||||
@@ -32,15 +23,6 @@
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "x64-Clang-Release-Qt",
|
||||
"displayName": "Clang x64 Release with Qt",
|
||||
"inherits": ["x64-Clang-Base"],
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release",
|
||||
"ENABLE_QT_GUI": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "x64-Clang-RelWithDebInfo",
|
||||
"displayName": "Clang x64 RelWithDebInfo",
|
||||
@@ -48,15 +30,6 @@
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "x64-Clang-RelWithDebInfo-Qt",
|
||||
"displayName": "Clang x64 RelWithDebInfo with Qt",
|
||||
"inherits": ["x64-Clang-Base"],
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
|
||||
"ENABLE_QT_GUI": "ON"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -12,18 +12,6 @@
|
||||
"inheritEnvironments": [ "clang_cl_x64_x64" ],
|
||||
"intelliSenseMode": "windows-clang-x64"
|
||||
},
|
||||
{
|
||||
"name": "x64-Clang-Release-Qt",
|
||||
"generator": "Ninja",
|
||||
"configurationType": "Release",
|
||||
"buildRoot": "${projectDir}\\Build\\${name}",
|
||||
"installRoot": "${projectDir}\\Install\\${name}",
|
||||
"cmakeCommandArgs": "-DENABLE_QT_GUI=ON",
|
||||
"buildCommandArgs": "",
|
||||
"ctestCommandArgs": "",
|
||||
"inheritEnvironments": [ "clang_cl_x64_x64" ],
|
||||
"intelliSenseMode": "windows-clang-x64"
|
||||
},
|
||||
{
|
||||
"name": "x64-Clang-Debug",
|
||||
"generator": "Ninja",
|
||||
@@ -36,18 +24,6 @@
|
||||
"inheritEnvironments": [ "clang_cl_x64_x64" ],
|
||||
"intelliSenseMode": "windows-clang-x64"
|
||||
},
|
||||
{
|
||||
"name": "x64-Clang-Debug-Qt",
|
||||
"generator": "Ninja",
|
||||
"configurationType": "Debug",
|
||||
"buildRoot": "${projectDir}\\Build\\${name}",
|
||||
"installRoot": "${projectDir}\\Install\\${name}",
|
||||
"cmakeCommandArgs": "-DENABLE_QT_GUI=ON",
|
||||
"buildCommandArgs": "",
|
||||
"ctestCommandArgs": "",
|
||||
"inheritEnvironments": [ "clang_cl_x64_x64" ],
|
||||
"intelliSenseMode": "windows-clang-x64"
|
||||
},
|
||||
{
|
||||
"name": "x64-Clang-RelWithDebInfo",
|
||||
"generator": "Ninja",
|
||||
@@ -59,18 +35,6 @@
|
||||
"ctestCommandArgs": "",
|
||||
"inheritEnvironments": [ "clang_cl_x64_x64" ],
|
||||
"intelliSenseMode": "windows-clang-x64"
|
||||
},
|
||||
{
|
||||
"name": "x64-Clang-RelWithDebInfo-Qt",
|
||||
"generator": "Ninja",
|
||||
"configurationType": "RelWithDebInfo",
|
||||
"buildRoot": "${projectDir}\\Build\\${name}",
|
||||
"installRoot": "${projectDir}\\Install\\${name}",
|
||||
"cmakeCommandArgs": "-DENABLE_QT_GUI=ON",
|
||||
"buildCommandArgs": "",
|
||||
"ctestCommandArgs": "",
|
||||
"inheritEnvironments": [ "clang_cl_x64_x64" ],
|
||||
"intelliSenseMode": "windows-clang-x64"
|
||||
}
|
||||
]
|
||||
}
|
||||
42
README.md
42
README.md
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project
|
||||
SPDX-FileCopyrightText: 2026 shadPS4 Emulator Project
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
-->
|
||||
|
||||
@@ -36,6 +36,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
**shadPS4** is an early **PlayStation 4** emulator for **Windows**, **Linux** and **macOS** written in C++.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This is the emulator core, which does not include a GUI. If you just want to use the emulator as an end user, download the [**QtLauncher**](https://github.com/shadps4-emu/shadps4-qtlauncher/releases) instead.
|
||||
|
||||
If you encounter problems or have doubts, do not hesitate to look at the [**Quickstart**](https://github.com/shadps4-emu/shadPS4/wiki/I.-Quick-start-%5BUsers%5D).\
|
||||
To verify that a game works, you can look at [**shadPS4 Game Compatibility**](https://github.com/shadps4-compatibility/shadps4-game-compatibility).\
|
||||
To discuss shadPS4 development, suggest ideas or to ask for help, join our [**Discord server**](https://discord.gg/bFJxfftGW6).\
|
||||
@@ -55,8 +58,10 @@ This project began for fun. Given our limited free time, it may take some time b
|
||||
|
||||
# Building
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If you want to use shadPS4 to play your games, you don't have to follow the build instructions, you can simply download the emulator from either the [**release tab**](https://github.com/shadps4-emu/shadPS4/releases) or the [**action tab**](https://github.com/shadps4-emu/shadPS4/actions).
|
||||
## Docker
|
||||
|
||||
For building shadPS4 in a containerized environment using Docker and VSCode, check the instructions here:
|
||||
[**Docker Build Instructions**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-docker.md)
|
||||
|
||||
## Windows
|
||||
|
||||
@@ -73,6 +78,22 @@ Check the build instructions for [**macOS**](https://github.com/shadps4-emu/shad
|
||||
> [!IMPORTANT]
|
||||
> macOS users need at least macOS 15.4 to run shadPS4. Due to GPU issues there are currently heavy bugs on Intel Macs.
|
||||
|
||||
# Usage examples
|
||||
|
||||
> [!IMPORTANT]
|
||||
> For a user-friendly GUI, download the [**QtLauncher**](https://github.com/shadps4-emu/shadps4-qtlauncher/releases).
|
||||
|
||||
To get the list of all available commands and also a more detailed description of what each command does, please refer to the `--help` flag's output.
|
||||
|
||||
Below is a list of commonly used command patterns:
|
||||
```sh
|
||||
shadPS4 CUSA00001 # Searches for a game folder called CUSA00001 in the list of game install folders, and boots it.
|
||||
shadPS4 --fullscreen true --config-clean CUSA00001 # the game argument is always the last one,
|
||||
shadPS4 -g CUSA00001 --fullscreen true --config-clean # ...unless manually specified otherwise.
|
||||
shadPS4 /path/to/game.elf # Boots a PS4 ELF file directly. Useful if you want to boot an executable that is not named eboot.bin.
|
||||
shadPS4 CUSA00001 -- -flag1 -flag2 # Passes '-flag1' and '-flag2' to the game executable in argv.
|
||||
```
|
||||
|
||||
# Debugging and reporting issues
|
||||
|
||||
For more information on how to test, debug and report issues with the emulator or games, read the [**Debugging documentation**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Debugging/Debugging.md).
|
||||
@@ -132,9 +153,9 @@ The following firmware modules are supported and must be placed in shadPS4's `sy
|
||||
| Modules | Modules | Modules | Modules |
|
||||
|-------------------------|-------------------------|-------------------------|-------------------------|
|
||||
| libSceCesCs.sprx | libSceFont.sprx | libSceFontFt.sprx | libSceFreeTypeOt.sprx |
|
||||
| libSceJson.sprx | libSceJson2.sprx | libSceLibcInternal.sprx | libSceNgs2.sprx |
|
||||
| libSceUlt.sprx | | | |
|
||||
|
||||
| libSceJpegDec.sprx | libSceJpegEnc.sprx | libSceJson.sprx | libSceJson2.sprx |
|
||||
| libSceLibcInternal.sprx | libSceNgs2.sprx | libScePngEnc.sprx | libSceRtc.sprx |
|
||||
| libSceUlt.sprx | libSceAudiodec.sprx | | |
|
||||
</div>
|
||||
|
||||
> [!Caution]
|
||||
@@ -160,15 +181,6 @@ Logo is done by [**Xphalnos**](https://github.com/Xphalnos)
|
||||
If you want to contribute, please read the [**CONTRIBUTING.md**](https://github.com/shadps4-emu/shadPS4/blob/main/CONTRIBUTING.md) file.\
|
||||
Open a PR and we'll check it :)
|
||||
|
||||
# Translations
|
||||
|
||||
If you want to translate shadPS4 to your language we use [**Crowdin**](https://crowdin.com/project/shadps4-emulator).
|
||||
# Contributors
|
||||
|
||||
<a href="https://github.com/shadps4-emu/shadPS4/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=shadps4-emu/shadPS4&max=24">
|
||||
</a>
|
||||
|
||||
|
||||
# Special Thanks
|
||||
|
||||
|
||||
@@ -74,7 +74,6 @@ path = [
|
||||
"src/images/trophy.wav",
|
||||
"src/images/hotkey.png",
|
||||
"src/images/game_settings.png",
|
||||
"src/shadps4.qrc",
|
||||
"src/shadps4.rc",
|
||||
]
|
||||
precedence = "aggregate"
|
||||
@@ -130,9 +129,4 @@ SPDX-License-Identifier = "MIT"
|
||||
[[annotations]]
|
||||
path = "src/video_core/host_shaders/fsr/*"
|
||||
SPDX-FileCopyrightText = "Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved."
|
||||
SPDX-License-Identifier = "MIT"
|
||||
|
||||
[[annotations]]
|
||||
path = "dist/qt.conf"
|
||||
SPDX-FileCopyrightText = "shadPS4 Emulator Project"
|
||||
SPDX-License-Identifier = "GPL-2.0-or-later"
|
||||
SPDX-License-Identifier = "MIT"
|
||||
@@ -1,28 +0,0 @@
|
||||
# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(highest_version "0")
|
||||
set(CANDIDATE_DRIVES A B C D E F G H I J K L M N O P Q R S T U V W X Y Z)
|
||||
|
||||
foreach(drive ${CANDIDATE_DRIVES})
|
||||
file(GLOB kits LIST_DIRECTORIES true CONFIGURE_DEPENDS "${drive}:/Qt/*/msvc*_64")
|
||||
foreach(kit IN LISTS kits)
|
||||
get_filename_component(version_dir "${kit}" DIRECTORY)
|
||||
get_filename_component(kit_version "${version_dir}" NAME)
|
||||
|
||||
message(STATUS "DetectQtInstallation.cmake: Detected Qt: ${kit}")
|
||||
|
||||
if (kit_version VERSION_GREATER highest_version)
|
||||
set(highest_version "${kit_version}")
|
||||
set(QT_PREFIX "${kit}")
|
||||
|
||||
endif()
|
||||
endforeach()
|
||||
endforeach()
|
||||
|
||||
if(QT_PREFIX)
|
||||
set(CMAKE_PREFIX_PATH "${QT_PREFIX}" CACHE PATH "Qt prefix auto‑detected" FORCE)
|
||||
message(STATUS "DetectQtInstallation.cmake: Choose newest Qt: ${QT_PREFIX}")
|
||||
else()
|
||||
message(STATUS "DetectQtInstallation.cmake: No Qt‑Directory found in <drive>:/Qt – please set CMAKE_PREFIX_PATH manually")
|
||||
endif()
|
||||
15
dist/net.shadps4.shadPS4.metainfo.xml
vendored
15
dist/net.shadps4.shadPS4.metainfo.xml
vendored
@@ -11,6 +11,7 @@
|
||||
<project_license translate="no">GPL-2.0</project_license>
|
||||
<launchable type="desktop-id" translate="no">net.shadps4.shadPS4.desktop</launchable>
|
||||
<url type="homepage" translate="no">https://shadps4.net/</url>
|
||||
<url type="vcs-browser" translate="no">https://github.com/shadps4-emu/shadPS4</url>
|
||||
<description>
|
||||
<p>shadPS4 is an early PlayStation 4 emulator for Windows, Linux and macOS written in C++.</p>
|
||||
<p>The emulator is still early in development, so don't expect a flawless experience. Nonetheless, the emulator can already run a number of commercial games.</p>
|
||||
@@ -18,25 +19,31 @@
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image type="source" translate="no" >https://cdn.jsdelivr.net/gh/shadps4-emu/shadps4@main/documents/Screenshots/1.png</image>
|
||||
<caption>Bloodborne</caption>
|
||||
<caption>Bloodborne by From Software</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image type="source" translate="no">https://cdn.jsdelivr.net/gh/shadps4-emu/shadps4@main/documents/Screenshots/2.png</image>
|
||||
<caption>Hatsune Miku: Project DIVA Future Tone</caption>
|
||||
<caption>Hatsune Miku Project DIVA Future Tone by SEGA</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image type="source" translate="no">https://cdn.jsdelivr.net/gh/shadps4-emu/shadps4@main/documents/Screenshots/3.png</image>
|
||||
<caption>Yakuza 0</caption>
|
||||
<caption>Yakuza 0 by SEGA</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image type="source" translate="no">https://cdn.jsdelivr.net/gh/shadps4-emu/shadps4@main/documents/Screenshots/4.png</image>
|
||||
<caption>Persona 4 Golden</caption>
|
||||
<caption>DRIVECLUB™ by Evolution Studios</caption>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<categories>
|
||||
<category translate="no">Game</category>
|
||||
</categories>
|
||||
<releases>
|
||||
<release version="0.13.0" date="2025-12-24">
|
||||
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.13.0</url>
|
||||
</release>
|
||||
<release version="0.12.5" date="2025-11-07">
|
||||
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.12.5</url>
|
||||
</release>
|
||||
<release version="0.12.0" date="2025-10-31">
|
||||
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.12.0</url>
|
||||
</release>
|
||||
|
||||
2
dist/qt.conf
vendored
2
dist/qt.conf
vendored
@@ -1,2 +0,0 @@
|
||||
[Paths]
|
||||
plugins = "./qtplugins"
|
||||
51
documents/Docker Builder/.devcontainer/devcontainer.json
Normal file
51
documents/Docker Builder/.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,51 @@
|
||||
// SPDX-FileCopyrightText: 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
{
|
||||
"name": "shadPS4-dev",
|
||||
"dockerComposeFile": [
|
||||
"../docker-compose.yml"
|
||||
],
|
||||
"containerEnv": {
|
||||
"GITHUB_TOKEN": "${localEnv:GITHUB_TOKEN}",
|
||||
"GITHUB_USER": "${localEnv:GITHUB_USER}"
|
||||
},
|
||||
"service": "shadps4",
|
||||
"workspaceFolder": "/workspaces/shadPS4",
|
||||
"remoteUser": "root",
|
||||
"shutdownAction": "none",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"llvm-vs-code-extensions.vscode-clangd",
|
||||
"ms-vscode.cmake-tools",
|
||||
"xaver.clang-format"
|
||||
],
|
||||
"settings": {
|
||||
"clangd.arguments": [
|
||||
"--background-index",
|
||||
"--clang-tidy",
|
||||
"--completion-style=detailed",
|
||||
"--header-insertion=never",
|
||||
"--compile-commands-dir=/workspaces/shadPS4/Build/x64-Clang-Release"
|
||||
],
|
||||
"C_Cpp.intelliSenseEngine": "Disabled"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"cmake.configureOnOpen": false,
|
||||
"cmake.generator": "Unix Makefiles",
|
||||
"cmake.environment": {
|
||||
"CC": "clang",
|
||||
"CXX": "clang++"
|
||||
},
|
||||
"cmake.configureEnvironment": {
|
||||
"CMAKE_CXX_STANDARD": "23",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
||||
},
|
||||
"editor.formatOnSave": true,
|
||||
"clang-format.executable": "clang-format-19"
|
||||
}
|
||||
}
|
||||
45
documents/Docker Builder/.docker/Dockerfile
Normal file
45
documents/Docker Builder/.docker/Dockerfile
Normal file
@@ -0,0 +1,45 @@
|
||||
# SPDX-FileCopyrightText: 2026 shadPS4 Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
FROM archlinux:latest
|
||||
|
||||
RUN pacman-key --init && \
|
||||
pacman-key --populate archlinux && \
|
||||
pacman -Syu --noconfirm
|
||||
|
||||
RUN pacman -S --noconfirm \
|
||||
base-devel \
|
||||
clang \
|
||||
clang19 \
|
||||
ninja \
|
||||
git \
|
||||
ca-certificates \
|
||||
wget \
|
||||
alsa-lib \
|
||||
libpulse \
|
||||
openal \
|
||||
openssl \
|
||||
zlib \
|
||||
libedit \
|
||||
systemd-libs \
|
||||
libevdev \
|
||||
sdl2 \
|
||||
jack \
|
||||
sndio \
|
||||
libxtst \
|
||||
vulkan-headers \
|
||||
vulkan-validation-layers \
|
||||
libpng \
|
||||
clang-tools-extra \
|
||||
cmake \
|
||||
libx11 \
|
||||
libxrandr \
|
||||
libxcursor \
|
||||
libxi \
|
||||
libxinerama \
|
||||
libxss \
|
||||
&& pacman -Scc --noconfirm
|
||||
|
||||
RUN ln -sf /usr/lib/llvm19/bin/clang-format /usr/bin/clang-format-19
|
||||
|
||||
WORKDIR /workspaces/shadPS4
|
||||
10
documents/Docker Builder/docker-compose.yml
Normal file
10
documents/Docker Builder/docker-compose.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
# SPDX-FileCopyrightText: 2026 shadPS4 Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
services:
|
||||
shadps4:
|
||||
build:
|
||||
context: ./.docker
|
||||
volumes:
|
||||
- ./emu:/workspaces/shadPS4:cached
|
||||
tty: true
|
||||
100
documents/building-docker.md
Normal file
100
documents/building-docker.md
Normal file
@@ -0,0 +1,100 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2026 shadPS4 Emulator Project
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
-->
|
||||
|
||||
# Building shadPS4 with Docker and VSCode Support
|
||||
|
||||
This guide explains how to build **shadPS4** using Docker while keeping full compatibility with **VSCode** development.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before starting, ensure you have:
|
||||
|
||||
- **Docker Engine** or **Docker Desktop** installed
|
||||
[Installation Guide](https://docs.docker.com/engine/install/)
|
||||
|
||||
- **Git** installed on your system.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Prepare the Docker Environment
|
||||
|
||||
Inside the container (or on your host if mounting volumes):
|
||||
|
||||
1. Navigate to the repository folder containing the Docker Builder folder:
|
||||
|
||||
```bash
|
||||
cd <path-to-repo>
|
||||
```
|
||||
|
||||
2. Start the Docker container:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
This will spin up a container with all the necessary build dependencies, including Clang, CMake, SDL2, Vulkan, and more.
|
||||
|
||||
## Step 2: Clone shadPS4 Source
|
||||
|
||||
```bash
|
||||
mkdir emu
|
||||
cd emu
|
||||
git clone --recursive https://github.com/shadps4-emu/shadPS4.git .
|
||||
|
||||
or your fork link.
|
||||
```
|
||||
|
||||
3. Initialize submodules:
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
## Step 3: Build with CMake Tools (GUI)
|
||||
|
||||
Generate build with CMake Tools.
|
||||
|
||||
1. Go `CMake Tools > Configure > '>'`
|
||||
2. And `Build > '>'`
|
||||
|
||||
Compiled executable in `Build` folder.
|
||||
|
||||
## Alternative Step 3: Build with CMake
|
||||
|
||||
Generate the build directory and configure the project using Clang:
|
||||
|
||||
```bash
|
||||
cmake -S . -B build/ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
|
||||
```
|
||||
|
||||
Then build the project:
|
||||
|
||||
```bash
|
||||
cmake --build ./build --parallel $(nproc)
|
||||
```
|
||||
|
||||
* Tip: To enable debug builds, add -DCMAKE_BUILD_TYPE=Debug to the CMake command.
|
||||
|
||||
---
|
||||
|
||||
After a successful build, the executable is located at:
|
||||
|
||||
```bash
|
||||
./build/shadps4
|
||||
```
|
||||
|
||||
## Step 4: VSCode Integration
|
||||
|
||||
1. Open the repository in VSCode.
|
||||
2. The CMake Tools extension should automatically detect the build directory inside the container or on your host.
|
||||
3. You can configure build options, build, and debug directly from the VSCode interface without extra manual setup.
|
||||
|
||||
# Notes
|
||||
|
||||
* The Docker environment contains all dependencies, so you don’t need to install anything manually.
|
||||
* Using Clang inside Docker ensures consistent builds across Linux and macOS runners.
|
||||
* GitHub Actions are recommended for cross-platform builds, including Windows .exe output, which is not trivial to produce locally without Visual Studio or clang-cl.
|
||||
@@ -17,8 +17,7 @@ First and foremost, Clang 18 is the **recommended compiler** as it is used for o
|
||||
sudo apt install build-essential clang git cmake libasound2-dev \
|
||||
libpulse-dev libopenal-dev libssl-dev zlib1g-dev libedit-dev \
|
||||
libudev-dev libevdev-dev libsdl2-dev libjack-dev libsndio-dev \
|
||||
qt6-base-dev qt6-tools-dev qt6-multimedia-dev libvulkan-dev \
|
||||
vulkan-validationlayers libpng-dev
|
||||
libvulkan-dev vulkan-validationlayers libpng-dev
|
||||
```
|
||||
|
||||
#### Fedora
|
||||
@@ -27,8 +26,6 @@ sudo apt install build-essential clang git cmake libasound2-dev \
|
||||
sudo dnf install clang git cmake libatomic alsa-lib-devel \
|
||||
pipewire-jack-audio-connection-kit-devel openal-soft-devel \
|
||||
openssl-devel libevdev-devel libudev-devel libXext-devel \
|
||||
qt6-qtbase-devel qt6-qtbase-private-devel \
|
||||
qt6-qtmultimedia-devel qt6-qtsvg-devel qt6-qttools-devel \
|
||||
vulkan-devel vulkan-validation-layers libpng-devel libuuid-devel
|
||||
```
|
||||
|
||||
@@ -36,8 +33,7 @@ sudo dnf install clang git cmake libatomic alsa-lib-devel \
|
||||
|
||||
```bash
|
||||
sudo pacman -S base-devel clang git cmake sndio jack2 openal \
|
||||
qt6-base qt6-declarative qt6-multimedia qt6-tools sdl2 \
|
||||
vulkan-validation-layers libpng
|
||||
sdl2 vulkan-validation-layers libpng
|
||||
```
|
||||
|
||||
**Note**: The `shadps4-git` AUR package is not maintained by any of the developers, and it uses the default compiler, which is often set to GCC. Use at your own discretion.
|
||||
@@ -48,9 +44,7 @@ sudo pacman -S base-devel clang git cmake sndio jack2 openal \
|
||||
sudo zypper install clang git cmake libasound2 libpulse-devel \
|
||||
libsndio7 libjack-devel openal-soft-devel libopenssl-devel \
|
||||
zlib-devel libedit-devel systemd-devel libevdev-devel \
|
||||
qt6-base-devel qt6-multimedia-devel qt6-svg-devel \
|
||||
qt6-linguist-devel qt6-gui-private-devel vulkan-devel \
|
||||
vulkan-validationlayers libpng-devel
|
||||
vulkan-devel vulkan-validationlayers libpng-devel
|
||||
```
|
||||
|
||||
#### NixOS
|
||||
@@ -90,12 +84,12 @@ There are 3 options you can choose from. Option 1 is **highly recommended**.
|
||||
1. Generate the build directory in the shadPS4 directory.
|
||||
|
||||
```bash
|
||||
cmake -S . -B build/ -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
|
||||
cmake -S . -B build/ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
|
||||
```
|
||||
|
||||
To disable the Qt GUI, remove the `-DENABLE_QT_GUI=ON` flag. To change the build type (for debugging), add `-DCMAKE_BUILD_TYPE=Debug`.
|
||||
To change the build type (for debugging), add `-DCMAKE_BUILD_TYPE=Debug`.
|
||||
|
||||
2. Use CMake to build the project:
|
||||
1. Use CMake to build the project:
|
||||
|
||||
```bash
|
||||
cmake --build ./build --parallel$(nproc)
|
||||
@@ -103,18 +97,12 @@ cmake --build ./build --parallel$(nproc)
|
||||
|
||||
If your computer freezes during this step, this could be caused by excessive system resource usage. In that case, remove `--parallel$(nproc)`.
|
||||
|
||||
Now run the emulator. If Qt was enabled at configure time:
|
||||
Now run the emulator to get the list of options:
|
||||
|
||||
```bash
|
||||
./build/shadps4
|
||||
```
|
||||
|
||||
Otherwise, specify the path to your game's boot file:
|
||||
|
||||
```bash
|
||||
./build/shadps4 /"PATH"/"TO"/"GAME"/"FOLDER"/eboot.bin
|
||||
```
|
||||
|
||||
You can also specify the Game ID as an argument for which game to boot, as long as the folder containing the games is specified in config.toml (example: Bloodborne (US) is CUSA00900).
|
||||
#### Option 2: Configuring with cmake-gui
|
||||
|
||||
@@ -142,10 +130,6 @@ Go to Settings, filter by `@ext:ms-vscode.cmake-tools configure` and disable thi
|
||||
|
||||

|
||||
|
||||
If you wish to build with the Qt GUI, add `-DENABLE_QT_GUI=ON` to the configure arguments:
|
||||
|
||||

|
||||
|
||||
On the CMake tab, change the options as you wish, but make sure that it looks similar to or exactly like this:
|
||||
|
||||

|
||||
|
||||
@@ -24,21 +24,6 @@ eval $(/opt/homebrew/bin/brew shellenv)
|
||||
brew install clang-format cmake
|
||||
```
|
||||
|
||||
Next, install x86_64 Qt. You can skip these steps and move on to **Cloning and compiling** if you do not intend to build the Qt GUI.
|
||||
|
||||
**If you are on an ARM Mac:**
|
||||
```
|
||||
# Installs x86_64 Homebrew to /usr/local
|
||||
arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
# Installs libraries.
|
||||
arch -x86_64 /usr/local/bin/brew install qt@6
|
||||
```
|
||||
|
||||
**If you are on an x86_64 Mac:**
|
||||
```
|
||||
brew install qt@6
|
||||
```
|
||||
|
||||
### Cloning and compiling:
|
||||
|
||||
Clone the repository recursively:
|
||||
@@ -52,8 +37,6 @@ Generate the build directory in the shadPS4 directory:
|
||||
cmake -S . -B build/ -DCMAKE_OSX_ARCHITECTURES=x86_64
|
||||
```
|
||||
|
||||
If you want to build the Qt GUI, add `-DENABLE_QT_GUI=ON` to the end of this command as well.
|
||||
|
||||
Enter the directory:
|
||||
```
|
||||
cd build/
|
||||
|
||||
@@ -22,23 +22,6 @@ Once you are within the installer:
|
||||
2. Go to "Individual Components" tab then search and select both `C++ Clang Compiler for Windows` and `MSBuild support for LLVM`
|
||||
3. Continue the installation
|
||||
|
||||
### (Prerequisite) Download [**Qt**](https://doc.qt.io/qt-6/get-and-install-qt.html)
|
||||
|
||||
Beware, this requires you to create a Qt account. If you do not want to do this, please follow the MSYS2/MinGW compilation method instead.
|
||||
|
||||
1. Under the current, non beta version of Qt, select the option `MSVC 2022 64-bit` or similar, as well as `QT Multimedia`.
|
||||
If you are on Windows on ARM / Qualcomm Snapdragon Elite X, select `MSVC 2022 ARM64` instead.
|
||||
|
||||
Go through the installation normally. If you know what you are doing, you may unselect individual components that eat up too much disk space.
|
||||
|
||||
2. Download and install [Qt Visual Studio Tools](https://marketplace.visualstudio.com/items?itemName=TheQtCompany.QtVisualStudioTools2022)
|
||||
|
||||
Once you are finished, you will have to configure Qt within Visual Studio:
|
||||
|
||||
1. Tools -> Options -> Qt -> Versions
|
||||
2. Add a new Qt version and navigate it to the correct folder. Should look like so: `C:\Qt\<QtVersion>\msvc2022_64`
|
||||
3. Enable the default checkmark on the new version you just created.
|
||||
|
||||
### (Prerequisite) Download [**Git for Windows**](https://git-scm.com/download/win)
|
||||
|
||||
Go through the Git for Windows installation as normal
|
||||
@@ -53,16 +36,11 @@ Go through the Git for Windows installation as normal
|
||||
|
||||
1. Open up Visual Studio, select `Open a local folder` and select the folder with the shadPS4 source code. The folder should contain `CMakeLists.txt`
|
||||
2. Change Clang x64 Debug to Clang x64 Release if you want a regular, non-debug build.
|
||||
3. If you want to build shadPS4 with the Qt Gui, simply select Clang x64 Release with Qt instead.
|
||||
4. Change the project to build to shadps4.exe
|
||||
5. Build -> Build All
|
||||
3. Change the project to build to shadps4.exe
|
||||
4. Build -> Build All
|
||||
|
||||
Your shadps4.exe will be in `C:\path\to\source\Build\x64-Clang-Release\`
|
||||
|
||||
To automatically populate the necessary files to run shadPS4.exe, run in a command prompt or terminal:
|
||||
`C:\Qt\<QtVersion>\msvc2022_64\bin\windeployqt6.exe "C:\path\to\shadps4.exe"`
|
||||
(Change Qt path if you've installed it to non-default path)
|
||||
|
||||
## Option 2: MSYS2/MinGW
|
||||
|
||||
> [!IMPORTANT]
|
||||
@@ -79,13 +57,10 @@ Normal x86-based computers, follow:
|
||||
1. Open "MSYS2 MINGW64" from your new applications
|
||||
2. Run `pacman -Syu`, let it complete;
|
||||
3. Run `pacman -S --needed git mingw-w64-x86_64-binutils mingw-w64-x86_64-clang mingw-w64-x86_64-cmake mingw-w64-x86_64-rapidjson mingw-w64-x86_64-ninja mingw-w64-x86_64-ffmpeg`
|
||||
1. Optional (Qt only): run `pacman -S --needed mingw-w64-x86_64-qt6-base mingw-w64-x86_64-qt6-tools mingw-w64-x86_64-qt6-multimedia`
|
||||
4. Run `git clone --depth 1 --recursive https://github.com/shadps4-emu/shadPS4`
|
||||
5. Run `cd shadPS4`
|
||||
6. Run `cmake -S . -B build -DCMAKE_C_COMPILER="clang.exe" -DCMAKE_CXX_COMPILER="clang++.exe" -DCMAKE_CXX_FLAGS="-O2 -march=native"`
|
||||
1. Optional (Qt only): add `-DENABLE_QT_GUI=ON`
|
||||
7. Run `cmake --build build`
|
||||
1. Optional (Qt only): run `windeployqt6 build/shadps4.exe`
|
||||
8. To run the finished product, run `./build/shadPS4.exe`
|
||||
|
||||
ARM64-based computers, follow:
|
||||
@@ -93,13 +68,10 @@ ARM64-based computers, follow:
|
||||
1. Open "MSYS2 CLANGARM64" from your new applications
|
||||
2. Run `pacman -Syu`, let it complete;
|
||||
3. Run `pacman -S --needed git mingw-w64-clang-aarch64-binutils mingw-w64-clang-aarch64-clang mingw-w64-clang-aarch64-rapidjson mingw-w64-clang-aarch64-cmake mingw-w64-clang-aarch64-ninja mingw-w64-clang-aarch64-ffmpeg`
|
||||
1. Optional (Qt only): run `pacman -S --needed mingw-w64-clang-aarch64-qt6-base mingw-w64-clang-aarch64-qt6-tools mingw-w64-clang-aarch64-qt6-multimedia`
|
||||
4. Run `git clone --depth 1 --recursive https://github.com/shadps4-emu/shadPS4`
|
||||
5. Run `cd shadPS4`
|
||||
6. Run `cmake -S . -B build -DCMAKE_C_COMPILER="clang.exe" -DCMAKE_CXX_COMPILER="clang++.exe" -DCMAKE_CXX_FLAGS="-O2 -march=native"`
|
||||
1. Optional (Qt only): add `-DENABLE_QT_GUI=ON`
|
||||
7. Run `cmake --build build`
|
||||
1. Optional (Qt only): run `windeployqt6 build/shadps4.exe`
|
||||
8. To run the finished product, run `./build/shadPS4.exe`
|
||||
|
||||
## Note on MSYS2 builds
|
||||
|
||||
1
externals/CLI11
vendored
Submodule
1
externals/CLI11
vendored
Submodule
Submodule externals/CLI11 added at bf5a16a26a
34
externals/CMakeLists.txt
vendored
34
externals/CMakeLists.txt
vendored
@@ -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
|
||||
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
@@ -63,6 +63,18 @@ if (NOT TARGET SDL3::SDL3)
|
||||
add_subdirectory(sdl3)
|
||||
endif()
|
||||
|
||||
# SDL3_mixer
|
||||
if (NOT TARGET SDL3_mixer::SDL3_mixer)
|
||||
set(SDLMIXER_FLAC OFF)
|
||||
set(SDLMIXER_OGG OFF)
|
||||
set(SDLMIXER_MOD OFF)
|
||||
set(SDLMIXER_MIDI OFF)
|
||||
set(SDLMIXER_OPUS OFF)
|
||||
set(SDLMIXER_WAVPACK OFF)
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
add_subdirectory(sdl3_mixer)
|
||||
endif()
|
||||
|
||||
# vulkan-headers
|
||||
if (NOT TARGET Vulkan::Headers)
|
||||
set(VULKAN_HEADERS_ENABLE_MODULE OFF)
|
||||
@@ -140,7 +152,7 @@ endif()
|
||||
# sirit
|
||||
add_subdirectory(sirit)
|
||||
if (WIN32)
|
||||
target_compile_options(sirit PUBLIC "-Wno-error=unused-command-line-argument")
|
||||
target_compile_options(sirit PRIVATE "-Wno-error=unused-command-line-argument")
|
||||
endif()
|
||||
|
||||
# half
|
||||
@@ -192,6 +204,7 @@ add_subdirectory(tracy)
|
||||
|
||||
# pugixml
|
||||
if (NOT TARGET pugixml::pugixml)
|
||||
option(PUGIXML_NO_EXCEPTIONS "" ON)
|
||||
add_subdirectory(pugixml)
|
||||
endif()
|
||||
|
||||
@@ -245,3 +258,20 @@ endif()
|
||||
if (WIN32)
|
||||
add_subdirectory(ext-wepoll)
|
||||
endif()
|
||||
|
||||
if (NOT TARGET fdk-aac)
|
||||
add_subdirectory(aacdec)
|
||||
endif()
|
||||
|
||||
#nlohmann json
|
||||
set(JSON_BuildTests OFF CACHE INTERNAL "")
|
||||
add_subdirectory(json)
|
||||
|
||||
# miniz
|
||||
add_subdirectory(miniz)
|
||||
|
||||
# cli11
|
||||
set(CLI11_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(CLI11_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||
|
||||
add_subdirectory(CLI11)
|
||||
2
externals/MoltenVK
vendored
2
externals/MoltenVK
vendored
Submodule externals/MoltenVK updated: b23d425346...f79c6c5690
154
externals/aacdec/CMakeLists.txt
vendored
Normal file
154
externals/aacdec/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
# SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(AACDEC_SRC
|
||||
fdk-aac/libAACdec/src/FDK_delay.cpp
|
||||
fdk-aac/libAACdec/src/aac_ram.cpp
|
||||
fdk-aac/libAACdec/src/aac_rom.cpp
|
||||
fdk-aac/libAACdec/src/aacdec_drc.cpp
|
||||
fdk-aac/libAACdec/src/aacdec_hcr.cpp
|
||||
fdk-aac/libAACdec/src/aacdec_hcr_bit.cpp
|
||||
fdk-aac/libAACdec/src/aacdec_hcrs.cpp
|
||||
fdk-aac/libAACdec/src/aacdec_pns.cpp
|
||||
fdk-aac/libAACdec/src/aacdec_tns.cpp
|
||||
fdk-aac/libAACdec/src/aacdecoder.cpp
|
||||
fdk-aac/libAACdec/src/aacdecoder_lib.cpp
|
||||
fdk-aac/libAACdec/src/block.cpp
|
||||
fdk-aac/libAACdec/src/channel.cpp
|
||||
fdk-aac/libAACdec/src/channelinfo.cpp
|
||||
fdk-aac/libAACdec/src/conceal.cpp
|
||||
fdk-aac/libAACdec/src/ldfiltbank.cpp
|
||||
fdk-aac/libAACdec/src/pulsedata.cpp
|
||||
fdk-aac/libAACdec/src/rvlc.cpp
|
||||
fdk-aac/libAACdec/src/rvlcbit.cpp
|
||||
fdk-aac/libAACdec/src/rvlcconceal.cpp
|
||||
fdk-aac/libAACdec/src/stereo.cpp
|
||||
fdk-aac/libAACdec/src/usacdec_ace_d4t64.cpp
|
||||
fdk-aac/libAACdec/src/usacdec_ace_ltp.cpp
|
||||
fdk-aac/libAACdec/src/usacdec_acelp.cpp
|
||||
fdk-aac/libAACdec/src/usacdec_fac.cpp
|
||||
fdk-aac/libAACdec/src/usacdec_lpc.cpp
|
||||
fdk-aac/libAACdec/src/usacdec_lpd.cpp
|
||||
fdk-aac/libAACdec/src/usacdec_rom.cpp
|
||||
)
|
||||
|
||||
set(FDK_SRC
|
||||
fdk-aac/libFDK/src/FDK_bitbuffer.cpp
|
||||
fdk-aac/libFDK/src/FDK_core.cpp
|
||||
fdk-aac/libFDK/src/FDK_crc.cpp
|
||||
fdk-aac/libFDK/src/FDK_decorrelate.cpp
|
||||
fdk-aac/libFDK/src/FDK_hybrid.cpp
|
||||
fdk-aac/libFDK/src/FDK_lpc.cpp
|
||||
fdk-aac/libFDK/src/FDK_matrixCalloc.cpp
|
||||
fdk-aac/libFDK/src/FDK_qmf_domain.cpp
|
||||
fdk-aac/libFDK/src/FDK_tools_rom.cpp
|
||||
fdk-aac/libFDK/src/FDK_trigFcts.cpp
|
||||
fdk-aac/libFDK/src/autocorr2nd.cpp
|
||||
fdk-aac/libFDK/src/dct.cpp
|
||||
fdk-aac/libFDK/src/fft.cpp
|
||||
fdk-aac/libFDK/src/fft_rad2.cpp
|
||||
fdk-aac/libFDK/src/fixpoint_math.cpp
|
||||
fdk-aac/libFDK/src/huff_nodes.cpp
|
||||
fdk-aac/libFDK/src/mdct.cpp
|
||||
fdk-aac/libFDK/src/nlc_dec.cpp
|
||||
fdk-aac/libFDK/src/qmf.cpp
|
||||
fdk-aac/libFDK/src/scale.cpp
|
||||
)
|
||||
|
||||
set(SYS_SRC
|
||||
fdk-aac/libSYS/src/genericStds.cpp
|
||||
fdk-aac/libSYS/src/syslib_channelMapDescr.cpp
|
||||
)
|
||||
|
||||
set(ARITHCODING_SRC
|
||||
fdk-aac/libArithCoding/src/ac_arith_coder.cpp
|
||||
)
|
||||
|
||||
set(MPEGTPDEC_SRC
|
||||
fdk-aac/libMpegTPDec/src/tpdec_adif.cpp
|
||||
fdk-aac/libMpegTPDec/src/tpdec_adts.cpp
|
||||
fdk-aac/libMpegTPDec/src/tpdec_asc.cpp
|
||||
fdk-aac/libMpegTPDec/src/tpdec_drm.cpp
|
||||
fdk-aac/libMpegTPDec/src/tpdec_latm.cpp
|
||||
fdk-aac/libMpegTPDec/src/tpdec_lib.cpp
|
||||
)
|
||||
|
||||
set(SBRDEC_SRC
|
||||
fdk-aac/libSBRdec/src/HFgen_preFlat.cpp
|
||||
fdk-aac/libSBRdec/src/env_calc.cpp
|
||||
fdk-aac/libSBRdec/src/env_dec.cpp
|
||||
fdk-aac/libSBRdec/src/env_extr.cpp
|
||||
fdk-aac/libSBRdec/src/hbe.cpp
|
||||
fdk-aac/libSBRdec/src/huff_dec.cpp
|
||||
fdk-aac/libSBRdec/src/lpp_tran.cpp
|
||||
fdk-aac/libSBRdec/src/psbitdec.cpp
|
||||
fdk-aac/libSBRdec/src/psdec.cpp
|
||||
fdk-aac/libSBRdec/src/psdec_drm.cpp
|
||||
fdk-aac/libSBRdec/src/psdecrom_drm.cpp
|
||||
fdk-aac/libSBRdec/src/pvc_dec.cpp
|
||||
fdk-aac/libSBRdec/src/sbr_deb.cpp
|
||||
fdk-aac/libSBRdec/src/sbr_dec.cpp
|
||||
fdk-aac/libSBRdec/src/sbr_ram.cpp
|
||||
fdk-aac/libSBRdec/src/sbr_rom.cpp
|
||||
fdk-aac/libSBRdec/src/sbrdec_drc.cpp
|
||||
fdk-aac/libSBRdec/src/sbrdec_freq_sca.cpp
|
||||
fdk-aac/libSBRdec/src/sbrdecoder.cpp
|
||||
)
|
||||
|
||||
set(PCMUTILS_SRC
|
||||
fdk-aac/libPCMutils/src/limiter.cpp
|
||||
fdk-aac/libPCMutils/src/pcm_utils.cpp
|
||||
fdk-aac/libPCMutils/src/pcmdmx_lib.cpp
|
||||
)
|
||||
|
||||
set(DRCDEC_SRC
|
||||
fdk-aac/libDRCdec/src/FDK_drcDecLib.cpp
|
||||
fdk-aac/libDRCdec/src/drcDec_gainDecoder.cpp
|
||||
fdk-aac/libDRCdec/src/drcDec_reader.cpp
|
||||
fdk-aac/libDRCdec/src/drcDec_rom.cpp
|
||||
fdk-aac/libDRCdec/src/drcDec_selectionProcess.cpp
|
||||
fdk-aac/libDRCdec/src/drcDec_tools.cpp
|
||||
fdk-aac/libDRCdec/src/drcGainDec_init.cpp
|
||||
fdk-aac/libDRCdec/src/drcGainDec_preprocess.cpp
|
||||
fdk-aac/libDRCdec/src/drcGainDec_process.cpp
|
||||
)
|
||||
|
||||
set(SACDEC_SRC
|
||||
fdk-aac/libSACdec/src/sac_bitdec.cpp
|
||||
fdk-aac/libSACdec/src/sac_calcM1andM2.cpp
|
||||
fdk-aac/libSACdec/src/sac_dec.cpp
|
||||
fdk-aac/libSACdec/src/sac_dec_conceal.cpp
|
||||
fdk-aac/libSACdec/src/sac_dec_lib.cpp
|
||||
fdk-aac/libSACdec/src/sac_process.cpp
|
||||
fdk-aac/libSACdec/src/sac_qmf.cpp
|
||||
fdk-aac/libSACdec/src/sac_reshapeBBEnv.cpp
|
||||
fdk-aac/libSACdec/src/sac_rom.cpp
|
||||
fdk-aac/libSACdec/src/sac_smoothing.cpp
|
||||
fdk-aac/libSACdec/src/sac_stp.cpp
|
||||
fdk-aac/libSACdec/src/sac_tsd.cpp
|
||||
)
|
||||
|
||||
add_library(fdk-aac
|
||||
${AACDEC_SRC}
|
||||
${FDK_SRC}
|
||||
${SYS_SRC}
|
||||
${ARITHCODING_SRC}
|
||||
${MPEGTPDEC_SRC}
|
||||
${SBRDEC_SRC}
|
||||
${PCMUTILS_SRC}
|
||||
${DRCDEC_SRC}
|
||||
${SACDEC_SRC}
|
||||
)
|
||||
|
||||
target_include_directories(fdk-aac
|
||||
PUBLIC
|
||||
fdk-aac/libAACdec/include
|
||||
fdk-aac/libFDK/include
|
||||
fdk-aac/libSYS/include
|
||||
fdk-aac/libArithCoding/include
|
||||
fdk-aac/libMpegTPDec/include
|
||||
fdk-aac/libSBRdec/include
|
||||
fdk-aac/libPCMutils/include
|
||||
fdk-aac/libDRCdec/include
|
||||
fdk-aac/libSACdec/include
|
||||
)
|
||||
1
externals/aacdec/fdk-aac
vendored
Submodule
1
externals/aacdec/fdk-aac
vendored
Submodule
Submodule externals/aacdec/fdk-aac added at ee76460efb
2
externals/ffmpeg-core
vendored
2
externals/ffmpeg-core
vendored
Submodule externals/ffmpeg-core updated: b0de1dcca2...94dde08c8a
2
externals/fmt
vendored
2
externals/fmt
vendored
Submodule externals/fmt updated: 64db979e38...ec73fb7247
1
externals/json
vendored
Submodule
1
externals/json
vendored
Submodule
Submodule externals/json added at 55f93686c0
1
externals/miniz
vendored
Submodule
1
externals/miniz
vendored
Submodule
Submodule externals/miniz added at 174573d602
2
externals/sdl3
vendored
2
externals/sdl3
vendored
Submodule externals/sdl3 updated: e9c2e9bfc3...bdb72bb3f0
1
externals/sdl3_mixer
vendored
Submodule
1
externals/sdl3_mixer
vendored
Submodule
Submodule externals/sdl3_mixer added at 4182794ea4
73
shell.nix
73
shell.nix
@@ -6,54 +6,47 @@ with import (fetchTarball "https://github.com/nixos/nixpkgs/archive/cfd19cdc5468
|
||||
pkgs.mkShell {
|
||||
name = "shadps4-build-env";
|
||||
|
||||
nativeBuildInputs = [
|
||||
pkgs.llvmPackages_18.clang
|
||||
pkgs.cmake
|
||||
pkgs.pkg-config
|
||||
pkgs.git
|
||||
nativeBuildInputs = with pkgs; [
|
||||
llvmPackages_18.clang
|
||||
cmake
|
||||
pkg-config
|
||||
git
|
||||
util-linux
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
pkgs.alsa-lib
|
||||
pkgs.libpulseaudio
|
||||
pkgs.openal
|
||||
pkgs.openssl
|
||||
pkgs.zlib
|
||||
pkgs.libedit
|
||||
pkgs.udev
|
||||
pkgs.libevdev
|
||||
pkgs.SDL2
|
||||
pkgs.jack2
|
||||
pkgs.sndio
|
||||
pkgs.qt6.qtbase
|
||||
pkgs.qt6.qttools
|
||||
pkgs.qt6.qtmultimedia
|
||||
buildInputs = with pkgs; [
|
||||
alsa-lib
|
||||
libpulseaudio
|
||||
openal
|
||||
zlib
|
||||
libedit
|
||||
udev
|
||||
libevdev
|
||||
SDL2
|
||||
jack2
|
||||
sndio
|
||||
|
||||
pkgs.vulkan-headers
|
||||
pkgs.vulkan-utility-libraries
|
||||
pkgs.vulkan-tools
|
||||
vulkan-headers
|
||||
vulkan-utility-libraries
|
||||
vulkan-tools
|
||||
|
||||
pkgs.ffmpeg
|
||||
pkgs.fmt
|
||||
pkgs.glslang
|
||||
pkgs.libxkbcommon
|
||||
pkgs.wayland
|
||||
pkgs.xorg.libxcb
|
||||
pkgs.xorg.xcbutil
|
||||
pkgs.xorg.xcbutilkeysyms
|
||||
pkgs.xorg.xcbutilwm
|
||||
pkgs.sdl3
|
||||
pkgs.stb
|
||||
pkgs.qt6.qtwayland
|
||||
pkgs.wayland-protocols
|
||||
pkgs.libpng
|
||||
ffmpeg
|
||||
fmt
|
||||
glslang
|
||||
libxkbcommon
|
||||
wayland
|
||||
xorg.libxcb
|
||||
xorg.xcbutil
|
||||
xorg.xcbutilkeysyms
|
||||
xorg.xcbutilwm
|
||||
sdl3
|
||||
stb
|
||||
wayland-protocols
|
||||
libpng
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
echo "Entering shadPS4 dev shell"
|
||||
export QT_QPA_PLATFORM="wayland"
|
||||
export QT_PLUGIN_PATH="${pkgs.qt6.qtwayland}/lib/qt-6/plugins:${pkgs.qt6.qtbase}/lib/qt-6/plugins"
|
||||
export QML2_IMPORT_PATH="${pkgs.qt6.qtbase}/lib/qt-6/qml"
|
||||
export CMAKE_PREFIX_PATH="${pkgs.vulkan-headers}:$CMAKE_PREFIX_PATH"
|
||||
|
||||
# OpenGL
|
||||
|
||||
@@ -139,13 +139,10 @@ static ConfigEntry<double> trophyNotificationDuration(6.0);
|
||||
static ConfigEntry<string> logFilter("");
|
||||
static ConfigEntry<string> logType("sync");
|
||||
static ConfigEntry<string> userName("shadPS4");
|
||||
static ConfigEntry<string> chooseHomeTab("General");
|
||||
static ConfigEntry<bool> isShowSplash(false);
|
||||
static ConfigEntry<string> isSideTrophy("right");
|
||||
static ConfigEntry<bool> isConnectedToNetwork(false);
|
||||
static bool enableDiscordRPC = false;
|
||||
static bool checkCompatibilityOnStartup = false;
|
||||
static bool compatibilityData = false;
|
||||
static std::filesystem::path sys_modules_path = {};
|
||||
|
||||
// Input
|
||||
@@ -180,7 +177,7 @@ static ConfigEntry<bool> isFullscreen(false);
|
||||
static ConfigEntry<string> fullscreenMode("Windowed");
|
||||
static ConfigEntry<string> presentMode("Mailbox");
|
||||
static ConfigEntry<bool> isHDRAllowed(false);
|
||||
static ConfigEntry<bool> fsrEnabled(true);
|
||||
static ConfigEntry<bool> fsrEnabled(false);
|
||||
static ConfigEntry<bool> rcasEnabled(true);
|
||||
static ConfigEntry<int> rcasAttenuation(250);
|
||||
|
||||
@@ -194,16 +191,17 @@ static ConfigEntry<bool> vkCrashDiagnostic(false);
|
||||
static ConfigEntry<bool> vkHostMarkers(false);
|
||||
static ConfigEntry<bool> vkGuestMarkers(false);
|
||||
static ConfigEntry<bool> rdocEnable(false);
|
||||
static ConfigEntry<bool> pipelineCacheEnable(false);
|
||||
static ConfigEntry<bool> pipelineCacheArchive(false);
|
||||
|
||||
// Debug
|
||||
static ConfigEntry<bool> isDebugDump(false);
|
||||
static ConfigEntry<bool> isShaderDebug(false);
|
||||
static ConfigEntry<bool> isSeparateLogFilesEnabled(false);
|
||||
static ConfigEntry<bool> isFpsColor(true);
|
||||
static ConfigEntry<bool> showFpsCounter(false);
|
||||
static ConfigEntry<bool> logEnabled(true);
|
||||
|
||||
// GUI
|
||||
static bool load_game_size = true;
|
||||
static std::vector<GameInstallDir> settings_install_dirs = {};
|
||||
std::vector<bool> install_dirs_enabled = {};
|
||||
std::filesystem::path settings_addon_install_dir = {};
|
||||
@@ -212,13 +210,16 @@ std::filesystem::path save_data_path = {};
|
||||
// Settings
|
||||
ConfigEntry<u32> m_language(1); // english
|
||||
|
||||
// USB Device
|
||||
static ConfigEntry<int> usbDeviceBackend(UsbBackendType::Real);
|
||||
|
||||
// Keys
|
||||
static string trophyKey = "";
|
||||
|
||||
// Config version, used to determine if a user's config file is outdated.
|
||||
static string config_version = Common::g_scm_rev;
|
||||
|
||||
// These two entries aren't stored in the config
|
||||
// These entries aren't stored in the config
|
||||
static bool overrideControllerColor = false;
|
||||
static int controllerCustomColorRGB[3] = {0, 0, 255};
|
||||
|
||||
@@ -278,10 +279,6 @@ void setTrophyKey(string key) {
|
||||
trophyKey = key;
|
||||
}
|
||||
|
||||
bool GetLoadGameSizeEnabled() {
|
||||
return load_game_size;
|
||||
}
|
||||
|
||||
std::filesystem::path GetSaveDataPath() {
|
||||
if (save_data_path.empty()) {
|
||||
return Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "savedata";
|
||||
@@ -293,10 +290,6 @@ void setVolumeSlider(int volumeValue, bool is_game_specific) {
|
||||
volumeSlider.set(volumeValue, is_game_specific);
|
||||
}
|
||||
|
||||
void setLoadGameSizeEnabled(bool enable) {
|
||||
load_game_size = enable;
|
||||
}
|
||||
|
||||
bool isNeoModeConsole() {
|
||||
return isNeo.get();
|
||||
}
|
||||
@@ -309,8 +302,10 @@ int getExtraDmemInMbytes() {
|
||||
return extraDmemInMbytes.get();
|
||||
}
|
||||
|
||||
void setExtraDmemInMbytes(int value) {
|
||||
extraDmemInMbytes.base_value = 0;
|
||||
void setExtraDmemInMbytes(int value, bool is_game_specific) {
|
||||
// Disable setting in global config
|
||||
is_game_specific ? extraDmemInMbytes.game_specific_value = value
|
||||
: extraDmemInMbytes.base_value = 0;
|
||||
}
|
||||
|
||||
bool getIsFullscreen() {
|
||||
@@ -389,10 +384,6 @@ string getUserName() {
|
||||
return userName.get();
|
||||
}
|
||||
|
||||
string getChooseHomeTab() {
|
||||
return chooseHomeTab.get();
|
||||
}
|
||||
|
||||
bool getUseSpecialPad() {
|
||||
return useSpecialPad.get();
|
||||
}
|
||||
@@ -453,8 +444,20 @@ bool isRdocEnabled() {
|
||||
return rdocEnable.get();
|
||||
}
|
||||
|
||||
bool fpsColor() {
|
||||
return isFpsColor.get();
|
||||
bool isPipelineCacheEnabled() {
|
||||
return pipelineCacheEnable.get();
|
||||
}
|
||||
|
||||
bool isPipelineCacheArchived() {
|
||||
return pipelineCacheArchive.get();
|
||||
}
|
||||
|
||||
bool getShowFpsCounter() {
|
||||
return showFpsCounter.get();
|
||||
}
|
||||
|
||||
void setShowFpsCounter(bool enable, bool is_game_specific) {
|
||||
showFpsCounter.set(enable, is_game_specific);
|
||||
}
|
||||
|
||||
bool isLoggingEnabled() {
|
||||
@@ -508,14 +511,6 @@ void setVkGuestMarkersEnabled(bool enable, bool is_game_specific) {
|
||||
vkGuestMarkers.set(enable, is_game_specific);
|
||||
}
|
||||
|
||||
bool getCompatibilityEnabled() {
|
||||
return compatibilityData;
|
||||
}
|
||||
|
||||
bool getCheckCompatibilityOnStartup() {
|
||||
return checkCompatibilityOnStartup;
|
||||
}
|
||||
|
||||
bool getIsConnectedToNetwork() {
|
||||
return isConnectedToNetwork.get();
|
||||
}
|
||||
@@ -600,10 +595,26 @@ void setVkSyncValidation(bool enable, bool is_game_specific) {
|
||||
vkValidationSync.set(enable, is_game_specific);
|
||||
}
|
||||
|
||||
void setVkCoreValidation(bool enable, bool is_game_specific) {
|
||||
vkValidationCore.set(enable, is_game_specific);
|
||||
}
|
||||
|
||||
void setVkGpuValidation(bool enable, bool is_game_specific) {
|
||||
vkValidationGpu.set(enable, is_game_specific);
|
||||
}
|
||||
|
||||
void setRdocEnabled(bool enable, bool is_game_specific) {
|
||||
rdocEnable.set(enable, is_game_specific);
|
||||
}
|
||||
|
||||
void setPipelineCacheEnabled(bool enable, bool is_game_specific) {
|
||||
pipelineCacheEnable.set(enable, is_game_specific);
|
||||
}
|
||||
|
||||
void setPipelineCacheArchived(bool enable, bool is_game_specific) {
|
||||
pipelineCacheArchive.set(enable, is_game_specific);
|
||||
}
|
||||
|
||||
void setVblankFreq(u32 value, bool is_game_specific) {
|
||||
vblankFrequency.set(value, is_game_specific);
|
||||
}
|
||||
@@ -680,10 +691,6 @@ void setUserName(const string& name, bool is_game_specific) {
|
||||
userName.set(name, is_game_specific);
|
||||
}
|
||||
|
||||
void setChooseHomeTab(const string& type, bool is_game_specific) {
|
||||
chooseHomeTab.set(type, is_game_specific);
|
||||
}
|
||||
|
||||
void setUseSpecialPad(bool use) {
|
||||
useSpecialPad.base_value = use;
|
||||
}
|
||||
@@ -696,14 +703,6 @@ void setIsMotionControlsEnabled(bool use, bool is_game_specific) {
|
||||
isMotionControlsEnabled.set(use, is_game_specific);
|
||||
}
|
||||
|
||||
void setCompatibilityEnabled(bool use) {
|
||||
compatibilityData = use;
|
||||
}
|
||||
|
||||
void setCheckCompatibilityOnStartup(bool use) {
|
||||
checkCompatibilityOnStartup = use;
|
||||
}
|
||||
|
||||
bool addGameInstallDir(const std::filesystem::path& dir, bool enabled) {
|
||||
for (const auto& install_dir : settings_install_dirs) {
|
||||
if (install_dir.path == dir) {
|
||||
@@ -833,6 +832,14 @@ void setRcasAttenuation(int value, bool is_game_specific) {
|
||||
rcasAttenuation.set(value, is_game_specific);
|
||||
}
|
||||
|
||||
int getUsbDeviceBackend() {
|
||||
return usbDeviceBackend.get();
|
||||
}
|
||||
|
||||
void setUsbDeviceBackend(int value, bool is_game_specific) {
|
||||
usbDeviceBackend.set(value, is_game_specific);
|
||||
}
|
||||
|
||||
void load(const std::filesystem::path& path, bool is_game_specific) {
|
||||
// If the configuration file does not exist, create it and return, unless it is game specific
|
||||
std::error_code error;
|
||||
@@ -874,12 +881,8 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
|
||||
userName.setFromToml(general, "userName", is_game_specific);
|
||||
isShowSplash.setFromToml(general, "showSplash", is_game_specific);
|
||||
isSideTrophy.setFromToml(general, "sideTrophy", is_game_specific);
|
||||
compatibilityData = toml::find_or<bool>(general, "compatibilityEnabled", compatibilityData);
|
||||
checkCompatibilityOnStartup = toml::find_or<bool>(general, "checkCompatibilityOnStartup",
|
||||
checkCompatibilityOnStartup);
|
||||
|
||||
isConnectedToNetwork.setFromToml(general, "isConnectedToNetwork", is_game_specific);
|
||||
chooseHomeTab.setFromToml(general, "chooseHomeTab", is_game_specific);
|
||||
defaultControllerID.setFromToml(general, "defaultControllerID", is_game_specific);
|
||||
sys_modules_path = toml::find_fs_path_or(general, "sysModulesPath", sys_modules_path);
|
||||
}
|
||||
@@ -894,6 +897,7 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
|
||||
isMotionControlsEnabled.setFromToml(input, "isMotionControlsEnabled", is_game_specific);
|
||||
useUnifiedInputConfig.setFromToml(input, "useUnifiedInputConfig", is_game_specific);
|
||||
backgroundControllerInput.setFromToml(input, "backgroundControllerInput", is_game_specific);
|
||||
usbDeviceBackend.setFromToml(input, "usbDeviceBackend", is_game_specific);
|
||||
}
|
||||
|
||||
if (data.contains("Audio")) {
|
||||
@@ -940,6 +944,8 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
|
||||
vkHostMarkers.setFromToml(vk, "hostMarkers", is_game_specific);
|
||||
vkGuestMarkers.setFromToml(vk, "guestMarkers", is_game_specific);
|
||||
rdocEnable.setFromToml(vk, "rdocEnable", is_game_specific);
|
||||
pipelineCacheEnable.setFromToml(vk, "pipelineCacheEnable", is_game_specific);
|
||||
pipelineCacheArchive.setFromToml(vk, "pipelineCacheArchive", is_game_specific);
|
||||
}
|
||||
|
||||
string current_version = {};
|
||||
@@ -949,7 +955,7 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
|
||||
isDebugDump.setFromToml(debug, "DebugDump", is_game_specific);
|
||||
isSeparateLogFilesEnabled.setFromToml(debug, "isSeparateLogFilesEnabled", is_game_specific);
|
||||
isShaderDebug.setFromToml(debug, "CollectShader", is_game_specific);
|
||||
isFpsColor.setFromToml(debug, "FPSColor", is_game_specific);
|
||||
showFpsCounter.setFromToml(debug, "showFpsCounter", is_game_specific);
|
||||
logEnabled.setFromToml(debug, "logEnabled", is_game_specific);
|
||||
current_version = toml::find_or<std::string>(debug, "ConfigVersion", current_version);
|
||||
}
|
||||
@@ -957,8 +963,6 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
|
||||
if (data.contains("GUI")) {
|
||||
const toml::value& gui = data.at("GUI");
|
||||
|
||||
load_game_size = toml::find_or<bool>(gui, "loadGameSizeEnabled", load_game_size);
|
||||
|
||||
const auto install_dir_array =
|
||||
toml::find_or<std::vector<std::u8string>>(gui, "installDirs", {});
|
||||
|
||||
@@ -1062,7 +1066,6 @@ void save(const std::filesystem::path& path, bool is_game_specific) {
|
||||
logFilter.setTomlValue(data, "General", "logFilter", is_game_specific);
|
||||
logType.setTomlValue(data, "General", "logType", is_game_specific);
|
||||
userName.setTomlValue(data, "General", "userName", is_game_specific);
|
||||
chooseHomeTab.setTomlValue(data, "General", "chooseHomeTab", is_game_specific);
|
||||
isShowSplash.setTomlValue(data, "General", "showSplash", is_game_specific);
|
||||
isSideTrophy.setTomlValue(data, "General", "sideTrophy", is_game_specific);
|
||||
isNeo.setTomlValue(data, "General", "isPS4Pro", is_game_specific);
|
||||
@@ -1079,6 +1082,7 @@ void save(const std::filesystem::path& path, bool is_game_specific) {
|
||||
is_game_specific);
|
||||
backgroundControllerInput.setTomlValue(data, "Input", "backgroundControllerInput",
|
||||
is_game_specific);
|
||||
usbDeviceBackend.setTomlValue(data, "Input", "usbDeviceBackend", is_game_specific);
|
||||
|
||||
micDevice.setTomlValue(data, "Audio", "micDevice", is_game_specific);
|
||||
mainOutputDevice.setTomlValue(data, "Audio", "mainOutputDevice", is_game_specific);
|
||||
@@ -1104,10 +1108,14 @@ void save(const std::filesystem::path& path, bool is_game_specific) {
|
||||
gpuId.setTomlValue(data, "Vulkan", "gpuId", is_game_specific);
|
||||
vkValidation.setTomlValue(data, "Vulkan", "validation", is_game_specific);
|
||||
vkValidationSync.setTomlValue(data, "Vulkan", "validation_sync", is_game_specific);
|
||||
vkValidationCore.setTomlValue(data, "Vulkan", "validation_core", is_game_specific);
|
||||
vkValidationGpu.setTomlValue(data, "Vulkan", "validation_gpu", is_game_specific);
|
||||
vkCrashDiagnostic.setTomlValue(data, "Vulkan", "crashDiagnostic", is_game_specific);
|
||||
vkHostMarkers.setTomlValue(data, "Vulkan", "hostMarkers", is_game_specific);
|
||||
vkGuestMarkers.setTomlValue(data, "Vulkan", "guestMarkers", is_game_specific);
|
||||
rdocEnable.setTomlValue(data, "Vulkan", "rdocEnable", is_game_specific);
|
||||
pipelineCacheEnable.setTomlValue(data, "Vulkan", "pipelineCacheEnable", is_game_specific);
|
||||
pipelineCacheArchive.setTomlValue(data, "Vulkan", "pipelineCacheArchive", is_game_specific);
|
||||
|
||||
isDebugDump.setTomlValue(data, "Debug", "DebugDump", is_game_specific);
|
||||
isShaderDebug.setTomlValue(data, "Debug", "CollectShader", is_game_specific);
|
||||
@@ -1149,13 +1157,10 @@ void save(const std::filesystem::path& path, bool is_game_specific) {
|
||||
|
||||
// Non game-specific entries
|
||||
data["General"]["enableDiscordRPC"] = enableDiscordRPC;
|
||||
data["General"]["compatibilityEnabled"] = compatibilityData;
|
||||
data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup;
|
||||
data["General"]["sysModulesPath"] = string{fmt::UTF(sys_modules_path.u8string()).data};
|
||||
data["GUI"]["installDirs"] = install_dirs;
|
||||
data["GUI"]["installDirsEnabled"] = install_dirs_enabled;
|
||||
data["GUI"]["saveDataPath"] = string{fmt::UTF(save_data_path.u8string()).data};
|
||||
data["GUI"]["loadGameSizeEnabled"] = load_game_size;
|
||||
data["GUI"]["addonInstallDir"] =
|
||||
string{fmt::UTF(settings_addon_install_dir.u8string()).data};
|
||||
data["Debug"]["ConfigVersion"] = config_version;
|
||||
@@ -1169,9 +1174,7 @@ void save(const std::filesystem::path& path, bool is_game_specific) {
|
||||
data["GPU"]["internalScreenWidth"] = internalScreenWidth.base_value;
|
||||
data["GPU"]["internalScreenHeight"] = internalScreenHeight.base_value;
|
||||
data["GPU"]["patchShaders"] = shouldPatchShaders.base_value;
|
||||
data["Vulkan"]["validation_core"] = vkValidationCore.base_value;
|
||||
data["Vulkan"]["validation_gpu"] = vkValidationGpu.base_value;
|
||||
data["Debug"]["FPSColor"] = isFpsColor.base_value;
|
||||
data["Debug"]["showFpsCounter"] = showFpsCounter.base_value;
|
||||
}
|
||||
|
||||
// Sorting of TOML sections
|
||||
@@ -1205,7 +1208,6 @@ void setDefaultValues(bool is_game_specific) {
|
||||
logFilter.set("", is_game_specific);
|
||||
logType.set("sync", is_game_specific);
|
||||
userName.set("shadPS4", is_game_specific);
|
||||
chooseHomeTab.set("General", is_game_specific);
|
||||
isShowSplash.set(false, is_game_specific);
|
||||
isSideTrophy.set("right", is_game_specific);
|
||||
|
||||
@@ -1214,6 +1216,7 @@ void setDefaultValues(bool is_game_specific) {
|
||||
cursorHideTimeout.set(5, is_game_specific);
|
||||
isMotionControlsEnabled.set(true, is_game_specific);
|
||||
backgroundControllerInput.set(false, is_game_specific);
|
||||
usbDeviceBackend.set(UsbBackendType::Real, is_game_specific);
|
||||
|
||||
// GS - Audio
|
||||
micDevice.set("Default Device", is_game_specific);
|
||||
@@ -1243,6 +1246,8 @@ void setDefaultValues(bool is_game_specific) {
|
||||
vkHostMarkers.set(false, is_game_specific);
|
||||
vkGuestMarkers.set(false, is_game_specific);
|
||||
rdocEnable.set(false, is_game_specific);
|
||||
pipelineCacheEnable.set(false, is_game_specific);
|
||||
pipelineCacheArchive.set(false, is_game_specific);
|
||||
|
||||
// GS - Debug
|
||||
isDebugDump.set(false, is_game_specific);
|
||||
@@ -1258,8 +1263,6 @@ void setDefaultValues(bool is_game_specific) {
|
||||
|
||||
// General
|
||||
enableDiscordRPC = false;
|
||||
compatibilityData = false;
|
||||
checkCompatibilityOnStartup = false;
|
||||
|
||||
// Input
|
||||
useSpecialPad.base_value = false;
|
||||
@@ -1278,11 +1281,8 @@ void setDefaultValues(bool is_game_specific) {
|
||||
internalScreenWidth.base_value = 1280;
|
||||
internalScreenHeight.base_value = 720;
|
||||
|
||||
// GUI
|
||||
load_game_size = true;
|
||||
|
||||
// Debug
|
||||
isFpsColor.base_value = true;
|
||||
showFpsCounter.base_value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1297,6 +1297,7 @@ hotkey_pause = f9
|
||||
hotkey_reload_inputs = f8
|
||||
hotkey_toggle_mouse_to_joystick = f7
|
||||
hotkey_toggle_mouse_to_gyro = f6
|
||||
hotkey_toggle_mouse_to_touchpad = delete
|
||||
hotkey_quit = lctrl, lshift, end
|
||||
)";
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ void load(const std::filesystem::path& path, bool is_game_specific = false);
|
||||
void save(const std::filesystem::path& path, bool is_game_specific = false);
|
||||
void resetGameSpecificValue(std::string entry);
|
||||
|
||||
bool getGameRunning();
|
||||
void setGameRunning(bool running);
|
||||
int getVolumeSlider();
|
||||
void setVolumeSlider(int volumeValue, bool is_game_specific = false);
|
||||
std::string getTrophyKey();
|
||||
@@ -79,6 +81,10 @@ bool vkValidationEnabled();
|
||||
void setVkValidation(bool enable, bool is_game_specific = false);
|
||||
bool vkValidationSyncEnabled();
|
||||
void setVkSyncValidation(bool enable, bool is_game_specific = false);
|
||||
bool vkValidationGpuEnabled();
|
||||
void setVkGpuValidation(bool enable, bool is_game_specific = false);
|
||||
bool vkValidationCoreEnabled();
|
||||
void setVkCoreValidation(bool enable, bool is_game_specific = false);
|
||||
bool getVkCrashDiagnosticEnabled();
|
||||
void setVkCrashDiagnosticEnabled(bool enable, bool is_game_specific = false);
|
||||
bool getVkHostMarkersEnabled();
|
||||
@@ -88,7 +94,11 @@ void setVkGuestMarkersEnabled(bool enable, bool is_game_specific = false);
|
||||
bool getEnableDiscordRPC();
|
||||
void setEnableDiscordRPC(bool enable);
|
||||
bool isRdocEnabled();
|
||||
bool isPipelineCacheEnabled();
|
||||
bool isPipelineCacheArchived();
|
||||
void setRdocEnabled(bool enable, bool is_game_specific = false);
|
||||
void setPipelineCacheEnabled(bool enable, bool is_game_specific = false);
|
||||
void setPipelineCacheArchived(bool enable, bool is_game_specific = false);
|
||||
std::string getLogType();
|
||||
void setLogType(const std::string& type, bool is_game_specific = false);
|
||||
std::string getLogFilter();
|
||||
@@ -97,11 +107,10 @@ double getTrophyNotificationDuration();
|
||||
void setTrophyNotificationDuration(double newTrophyNotificationDuration,
|
||||
bool is_game_specific = false);
|
||||
int getCursorHideTimeout();
|
||||
void setCursorHideTimeout(int newcursorHideTimeout);
|
||||
std::string getMainOutputDevice();
|
||||
void setMainOutputDevice(std::string device);
|
||||
void setMainOutputDevice(std::string device, bool is_game_specific = false);
|
||||
std::string getPadSpkOutputDevice();
|
||||
void setPadSpkOutputDevice(std::string device);
|
||||
void setPadSpkOutputDevice(std::string device, bool is_game_specific = false);
|
||||
std::string getMicDevice();
|
||||
void setCursorHideTimeout(int newcursorHideTimeout, bool is_game_specific = false);
|
||||
void setMicDevice(std::string device, bool is_game_specific = false);
|
||||
@@ -116,16 +125,15 @@ int getSpecialPadClass();
|
||||
bool getPSNSignedIn();
|
||||
void setPSNSignedIn(bool sign, bool is_game_specific = false);
|
||||
bool patchShaders(); // no set
|
||||
bool fpsColor(); // no set
|
||||
bool getShowFpsCounter();
|
||||
void setShowFpsCounter(bool enable, bool is_game_specific = false);
|
||||
bool isNeoModeConsole();
|
||||
void setNeoMode(bool enable, bool is_game_specific = false);
|
||||
bool isDevKitConsole();
|
||||
void setDevKitConsole(bool enable, bool is_game_specific = false);
|
||||
|
||||
bool vkValidationCoreEnabled(); // no set
|
||||
bool vkValidationGpuEnabled(); // no set
|
||||
int getExtraDmemInMbytes();
|
||||
void setExtraDmemInMbytes(int value);
|
||||
void setExtraDmemInMbytes(int value, bool is_game_specific = false);
|
||||
bool getIsMotionControlsEnabled();
|
||||
void setIsMotionControlsEnabled(bool use, bool is_game_specific = false);
|
||||
std::string getDefaultControllerID();
|
||||
@@ -143,18 +151,16 @@ void setRcasAttenuation(int value, bool is_game_specific = false);
|
||||
bool getIsConnectedToNetwork();
|
||||
void setConnectedToNetwork(bool enable, bool is_game_specific = false);
|
||||
void setUserName(const std::string& name, bool is_game_specific = false);
|
||||
void setChooseHomeTab(const std::string& type, bool is_game_specific = false);
|
||||
std::filesystem::path getSysModulesPath();
|
||||
void setSysModulesPath(const std::filesystem::path& path);
|
||||
|
||||
enum UsbBackendType : int { Real, SkylandersPortal, InfinityBase, DimensionsToypad };
|
||||
int getUsbDeviceBackend();
|
||||
void setUsbDeviceBackend(int value, bool is_game_specific = false);
|
||||
|
||||
// TODO
|
||||
bool GetLoadGameSizeEnabled();
|
||||
std::filesystem::path GetSaveDataPath();
|
||||
void setLoadGameSizeEnabled(bool enable);
|
||||
bool getCompatibilityEnabled();
|
||||
bool getCheckCompatibilityOnStartup();
|
||||
std::string getUserName();
|
||||
std::string getChooseHomeTab();
|
||||
bool GetUseUnifiedInputConfig();
|
||||
void SetUseUnifiedInputConfig(bool use);
|
||||
bool GetOverrideControllerColor();
|
||||
@@ -164,8 +170,6 @@ void SetControllerCustomColor(int r, int b, int g);
|
||||
void setGameInstallDirs(const std::vector<std::filesystem::path>& dirs_config);
|
||||
void setAllGameInstallDirs(const std::vector<GameInstallDir>& dirs_config);
|
||||
void setSaveDataPath(const std::filesystem::path& path);
|
||||
void setCompatibilityEnabled(bool use);
|
||||
void setCheckCompatibilityOnStartup(bool use);
|
||||
// Gui
|
||||
bool addGameInstallDir(const std::filesystem::path& dir, bool enabled = true);
|
||||
void removeGameInstallDir(const std::filesystem::path& dir);
|
||||
|
||||
@@ -68,6 +68,7 @@ class ElfInfo {
|
||||
std::string app_ver{};
|
||||
u32 firmware_ver = 0;
|
||||
u32 raw_firmware_ver = 0;
|
||||
u32 sdk_ver = 0;
|
||||
PSFAttributes psf_attributes{};
|
||||
|
||||
std::filesystem::path splash_path{};
|
||||
@@ -117,6 +118,11 @@ public:
|
||||
return raw_firmware_ver;
|
||||
}
|
||||
|
||||
[[nodiscard]] u32 CompiledSdkVer() const {
|
||||
ASSERT(initialized);
|
||||
return sdk_ver;
|
||||
}
|
||||
|
||||
[[nodiscard]] const PSFAttributes& GetPSFAttributes() const {
|
||||
ASSERT(initialized);
|
||||
return psf_attributes;
|
||||
|
||||
@@ -40,28 +40,30 @@ namespace {
|
||||
switch (mode) {
|
||||
case FileAccessMode::Read:
|
||||
return L"rb";
|
||||
case FileAccessMode::Write:
|
||||
return L"wb";
|
||||
case FileAccessMode::Append:
|
||||
return L"ab";
|
||||
case FileAccessMode::Write:
|
||||
case FileAccessMode::ReadWrite:
|
||||
return L"r+b";
|
||||
case FileAccessMode::ReadAppend:
|
||||
return L"a+b";
|
||||
case FileAccessMode::Create:
|
||||
return L"wb";
|
||||
}
|
||||
break;
|
||||
case FileType::TextFile:
|
||||
switch (mode) {
|
||||
case FileAccessMode::Read:
|
||||
return L"r";
|
||||
case FileAccessMode::Write:
|
||||
return L"w";
|
||||
case FileAccessMode::Append:
|
||||
return L"a";
|
||||
case FileAccessMode::Write:
|
||||
case FileAccessMode::ReadWrite:
|
||||
return L"r+";
|
||||
case FileAccessMode::ReadAppend:
|
||||
return L"a+";
|
||||
case FileAccessMode::Create:
|
||||
return L"w";
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -91,28 +93,30 @@ namespace {
|
||||
switch (mode) {
|
||||
case FileAccessMode::Read:
|
||||
return "rb";
|
||||
case FileAccessMode::Write:
|
||||
return "wb";
|
||||
case FileAccessMode::Append:
|
||||
return "ab";
|
||||
case FileAccessMode::Write:
|
||||
case FileAccessMode::ReadWrite:
|
||||
return "r+b";
|
||||
case FileAccessMode::ReadAppend:
|
||||
return "a+b";
|
||||
case FileAccessMode::Create:
|
||||
return "wb";
|
||||
}
|
||||
break;
|
||||
case FileType::TextFile:
|
||||
switch (mode) {
|
||||
case FileAccessMode::Read:
|
||||
return "r";
|
||||
case FileAccessMode::Write:
|
||||
return "w";
|
||||
case FileAccessMode::Append:
|
||||
return "a";
|
||||
case FileAccessMode::Write:
|
||||
case FileAccessMode::ReadWrite:
|
||||
return "r+";
|
||||
case FileAccessMode::ReadAppend:
|
||||
return "a+";
|
||||
case FileAccessMode::Create:
|
||||
return "w";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -21,9 +21,8 @@ enum class FileAccessMode {
|
||||
*/
|
||||
Read = 1 << 0,
|
||||
/**
|
||||
* If the file at path exists, the existing contents of the file are erased.
|
||||
* The empty file is then opened for writing.
|
||||
* If the file at path does not exist, it creates and opens a new empty file for writing.
|
||||
* If the file at path exists, it opens the file for writing.
|
||||
* If the file at path does not exist, it fails to open the file.
|
||||
*/
|
||||
Write = 1 << 1,
|
||||
/**
|
||||
@@ -42,6 +41,12 @@ enum class FileAccessMode {
|
||||
* reading and appending.
|
||||
*/
|
||||
ReadAppend = Read | Append,
|
||||
/**
|
||||
* If the file at path exists, the existing contents of the file are erased.
|
||||
* The empty file is then opened for writing.
|
||||
* If the file at path does not exist, it creates and opens a new empty file for writing.
|
||||
*/
|
||||
Create = 1 << 3,
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(FileAccessMode);
|
||||
|
||||
@@ -102,6 +107,11 @@ public:
|
||||
return file != nullptr;
|
||||
}
|
||||
|
||||
bool IsWriteOnly() const {
|
||||
return file_access_mode == FileAccessMode::Append ||
|
||||
file_access_mode == FileAccessMode::Write;
|
||||
}
|
||||
|
||||
uintptr_t GetFileMapping();
|
||||
|
||||
int Open(const std::filesystem::path& path, FileAccessMode mode,
|
||||
@@ -210,7 +220,7 @@ public:
|
||||
}
|
||||
|
||||
static size_t WriteBytes(const std::filesystem::path path, const auto& data) {
|
||||
IOFile out(path, FileAccessMode::Write);
|
||||
IOFile out(path, FileAccessMode::Create);
|
||||
return out.Write(data);
|
||||
}
|
||||
std::FILE* file = nullptr;
|
||||
|
||||
161
src/common/key_manager.cpp
Normal file
161
src/common/key_manager.cpp
Normal file
@@ -0,0 +1,161 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include "common/logging/log.h"
|
||||
#include "key_manager.h"
|
||||
#include "path_util.h"
|
||||
|
||||
std::shared_ptr<KeyManager> KeyManager::s_instance = nullptr;
|
||||
std::mutex KeyManager::s_mutex;
|
||||
|
||||
// ------------------- Constructor & Singleton -------------------
|
||||
KeyManager::KeyManager() {
|
||||
SetDefaultKeys();
|
||||
}
|
||||
KeyManager::~KeyManager() {
|
||||
SaveToFile();
|
||||
}
|
||||
|
||||
std::shared_ptr<KeyManager> KeyManager::GetInstance() {
|
||||
std::lock_guard<std::mutex> lock(s_mutex);
|
||||
if (!s_instance)
|
||||
s_instance = std::make_shared<KeyManager>();
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
void KeyManager::SetInstance(std::shared_ptr<KeyManager> instance) {
|
||||
std::lock_guard<std::mutex> lock(s_mutex);
|
||||
s_instance = instance;
|
||||
}
|
||||
|
||||
// ------------------- Load / Save -------------------
|
||||
bool KeyManager::LoadFromFile() {
|
||||
try {
|
||||
const auto userDir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
||||
const auto keysPath = userDir / "keys.json";
|
||||
|
||||
if (!std::filesystem::exists(keysPath)) {
|
||||
SetDefaultKeys();
|
||||
SaveToFile();
|
||||
LOG_DEBUG(KeyManager, "Created default key file: {}", keysPath.string());
|
||||
return true;
|
||||
}
|
||||
|
||||
std::ifstream file(keysPath);
|
||||
if (!file.is_open()) {
|
||||
LOG_ERROR(KeyManager, "Could not open key file: {}", keysPath.string());
|
||||
return false;
|
||||
}
|
||||
|
||||
json j;
|
||||
file >> j;
|
||||
|
||||
SetDefaultKeys(); // start from defaults
|
||||
|
||||
if (j.contains("TrophyKeySet"))
|
||||
j.at("TrophyKeySet").get_to(m_keys.TrophyKeySet);
|
||||
|
||||
LOG_DEBUG(KeyManager, "Successfully loaded keys from: {}", keysPath.string());
|
||||
return true;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(KeyManager, "Error loading keys, using defaults: {}", e.what());
|
||||
SetDefaultKeys();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool KeyManager::SaveToFile() {
|
||||
try {
|
||||
const auto userDir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
||||
const auto keysPath = userDir / "keys.json";
|
||||
|
||||
json j;
|
||||
KeysToJson(j);
|
||||
|
||||
std::ofstream file(keysPath);
|
||||
if (!file.is_open()) {
|
||||
LOG_ERROR(KeyManager, "Could not open key file for writing: {}", keysPath.string());
|
||||
return false;
|
||||
}
|
||||
|
||||
file << std::setw(4) << j;
|
||||
file.flush();
|
||||
|
||||
if (file.fail()) {
|
||||
LOG_ERROR(KeyManager, "Failed to write keys to: {}", keysPath.string());
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_DEBUG(KeyManager, "Successfully saved keys to: {}", keysPath.string());
|
||||
return true;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(KeyManager, "Error saving keys: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------- JSON conversion -------------------
|
||||
void KeyManager::KeysToJson(json& j) const {
|
||||
j = m_keys;
|
||||
}
|
||||
void KeyManager::JsonToKeys(const json& j) {
|
||||
json current = m_keys; // serialize current defaults
|
||||
current.update(j); // merge only fields present in file
|
||||
m_keys = current.get<AllKeys>(); // deserialize back
|
||||
}
|
||||
|
||||
// ------------------- Defaults / Checks -------------------
|
||||
void KeyManager::SetDefaultKeys() {
|
||||
m_keys = AllKeys{};
|
||||
}
|
||||
|
||||
bool KeyManager::HasKeys() const {
|
||||
return !m_keys.TrophyKeySet.ReleaseTrophyKey.empty();
|
||||
}
|
||||
|
||||
// ------------------- Hex conversion -------------------
|
||||
std::vector<u8> KeyManager::HexStringToBytes(const std::string& hexStr) {
|
||||
std::vector<u8> bytes;
|
||||
if (hexStr.empty())
|
||||
return bytes;
|
||||
|
||||
if (hexStr.size() % 2 != 0)
|
||||
throw std::runtime_error("Invalid hex string length");
|
||||
|
||||
bytes.reserve(hexStr.size() / 2);
|
||||
|
||||
auto hexCharToInt = [](char c) -> u8 {
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
if (c >= 'A' && c <= 'F')
|
||||
return c - 'A' + 10;
|
||||
if (c >= 'a' && c <= 'f')
|
||||
return c - 'a' + 10;
|
||||
throw std::runtime_error("Invalid hex character");
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < hexStr.size(); i += 2) {
|
||||
u8 high = hexCharToInt(hexStr[i]);
|
||||
u8 low = hexCharToInt(hexStr[i + 1]);
|
||||
bytes.push_back((high << 4) | low);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
std::string KeyManager::BytesToHexString(const std::vector<u8>& bytes) {
|
||||
static const char hexDigits[] = "0123456789ABCDEF";
|
||||
std::string hexStr;
|
||||
hexStr.reserve(bytes.size() * 2);
|
||||
for (u8 b : bytes) {
|
||||
hexStr.push_back(hexDigits[(b >> 4) & 0xF]);
|
||||
hexStr.push_back(hexDigits[b & 0xF]);
|
||||
}
|
||||
return hexStr;
|
||||
}
|
||||
79
src/common/key_manager.h
Normal file
79
src/common/key_manager.h
Normal file
@@ -0,0 +1,79 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "common/types.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
class KeyManager {
|
||||
public:
|
||||
// ------------------- Nested keysets -------------------
|
||||
struct TrophyKeySet {
|
||||
std::vector<u8> ReleaseTrophyKey;
|
||||
};
|
||||
|
||||
struct AllKeys {
|
||||
KeyManager::TrophyKeySet TrophyKeySet;
|
||||
};
|
||||
|
||||
// ------------------- Construction -------------------
|
||||
KeyManager();
|
||||
~KeyManager();
|
||||
|
||||
// ------------------- Singleton -------------------
|
||||
static std::shared_ptr<KeyManager> GetInstance();
|
||||
static void SetInstance(std::shared_ptr<KeyManager> instance);
|
||||
|
||||
// ------------------- File operations -------------------
|
||||
bool LoadFromFile();
|
||||
bool SaveToFile();
|
||||
|
||||
// ------------------- Key operations -------------------
|
||||
void SetDefaultKeys();
|
||||
bool HasKeys() const;
|
||||
|
||||
// ------------------- Getters / Setters -------------------
|
||||
const AllKeys& GetAllKeys() const {
|
||||
return m_keys;
|
||||
}
|
||||
void SetAllKeys(const AllKeys& keys) {
|
||||
m_keys = keys;
|
||||
}
|
||||
|
||||
static std::vector<u8> HexStringToBytes(const std::string& hexStr);
|
||||
static std::string BytesToHexString(const std::vector<u8>& bytes);
|
||||
|
||||
private:
|
||||
void KeysToJson(json& j) const;
|
||||
void JsonToKeys(const json& j);
|
||||
|
||||
AllKeys m_keys{};
|
||||
|
||||
static std::shared_ptr<KeyManager> s_instance;
|
||||
static std::mutex s_mutex;
|
||||
};
|
||||
|
||||
// ------------------- NLOHMANN macros -------------------
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(KeyManager::TrophyKeySet, ReleaseTrophyKey)
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(KeyManager::AllKeys, TrophyKeySet)
|
||||
|
||||
namespace nlohmann {
|
||||
template <>
|
||||
struct adl_serializer<std::vector<u8>> {
|
||||
static void to_json(json& j, const std::vector<u8>& vec) {
|
||||
j = KeyManager::BytesToHexString(vec);
|
||||
}
|
||||
static void from_json(const json& j, std::vector<u8>& vec) {
|
||||
vec = KeyManager::HexStringToBytes(j.get<std::string>());
|
||||
}
|
||||
};
|
||||
} // namespace nlohmann
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <chrono>
|
||||
@@ -62,7 +63,7 @@ private:
|
||||
class FileBackend {
|
||||
public:
|
||||
explicit FileBackend(const std::filesystem::path& filename, bool should_append = false)
|
||||
: file{filename, should_append ? FS::FileAccessMode::Append : FS::FileAccessMode::Write,
|
||||
: file{filename, should_append ? FS::FileAccessMode::Append : FS::FileAccessMode::Create,
|
||||
FS::FileType::TextFile} {}
|
||||
|
||||
~FileBackend() = default;
|
||||
@@ -97,6 +98,7 @@ private:
|
||||
std::size_t bytes_written = 0;
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
/**
|
||||
* Backend that writes to Visual Studio's output window
|
||||
*/
|
||||
@@ -107,15 +109,14 @@ public:
|
||||
~DebuggerBackend() = default;
|
||||
|
||||
void Write(const Entry& entry) {
|
||||
#ifdef _WIN32
|
||||
::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
void Flush() {}
|
||||
|
||||
void EnableForStacktrace() {}
|
||||
};
|
||||
#endif
|
||||
|
||||
bool initialization_in_progress_suppress_logging = true;
|
||||
|
||||
@@ -182,7 +183,13 @@ public:
|
||||
}
|
||||
|
||||
void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
|
||||
const char* function, std::string message) {
|
||||
const char* function, const char* format, const fmt::format_args& args) {
|
||||
if (!filter.CheckMessage(log_class, log_level) || !Config::getLoggingEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto message = fmt::vformat(format, args);
|
||||
|
||||
// Propagate important log messages to the profiler
|
||||
if (IsProfilerConnected()) {
|
||||
const auto& msg_str = fmt::format("[{}] {}", GetLogClassName(log_class), message);
|
||||
@@ -201,10 +208,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
if (!filter.CheckMessage(log_class, log_level) || !Config::getLoggingEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
using std::chrono::duration_cast;
|
||||
using std::chrono::microseconds;
|
||||
using std::chrono::steady_clock;
|
||||
@@ -217,6 +220,7 @@ public:
|
||||
.line_num = line_num,
|
||||
.function = function,
|
||||
.message = std::move(message),
|
||||
.thread = Common::GetCurrentThreadName(),
|
||||
};
|
||||
if (Config::getLogType() == "async") {
|
||||
message_queue.EmplaceWait(entry);
|
||||
@@ -264,7 +268,9 @@ private:
|
||||
}
|
||||
|
||||
void ForEachBackend(auto lambda) {
|
||||
// lambda(debugger_backend);
|
||||
#ifdef _WIN32
|
||||
lambda(debugger_backend);
|
||||
#endif
|
||||
lambda(color_console_backend);
|
||||
lambda(file_backend);
|
||||
}
|
||||
@@ -277,7 +283,9 @@ private:
|
||||
static inline bool should_append{false};
|
||||
|
||||
Filter filter;
|
||||
#ifdef _WIN32
|
||||
DebuggerBackend debugger_backend{};
|
||||
#endif
|
||||
ColorConsoleBackend color_console_backend{};
|
||||
FileBackend file_backend;
|
||||
|
||||
@@ -324,8 +332,8 @@ void FmtLogMessageImpl(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 (!initialization_in_progress_suppress_logging) [[likely]] {
|
||||
Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function,
|
||||
fmt::vformat(format, args));
|
||||
Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function, format,
|
||||
args);
|
||||
}
|
||||
}
|
||||
} // namespace Common::Log
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
@@ -104,16 +105,22 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
|
||||
SUB(Lib, Move) \
|
||||
SUB(Lib, NpAuth) \
|
||||
SUB(Lib, NpCommon) \
|
||||
SUB(Lib, NpCommerce) \
|
||||
SUB(Lib, NpManager) \
|
||||
SUB(Lib, NpMatching2) \
|
||||
SUB(Lib, NpScore) \
|
||||
SUB(Lib, NpTrophy) \
|
||||
SUB(Lib, NpTus) \
|
||||
SUB(Lib, NpWebApi) \
|
||||
SUB(Lib, NpWebApi2) \
|
||||
SUB(Lib, NpProfileDialog) \
|
||||
SUB(Lib, NpSnsFacebookDialog) \
|
||||
SUB(Lib, NpPartner) \
|
||||
SUB(Lib, Screenshot) \
|
||||
SUB(Lib, LibCInternal) \
|
||||
SUB(Lib, AppContent) \
|
||||
SUB(Lib, Rtc) \
|
||||
SUB(Lib, Rudp) \
|
||||
SUB(Lib, DiscMap) \
|
||||
SUB(Lib, Png) \
|
||||
SUB(Lib, Jpeg) \
|
||||
@@ -140,6 +147,8 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
|
||||
SUB(Lib, NpParty) \
|
||||
SUB(Lib, Zlib) \
|
||||
SUB(Lib, Hmd) \
|
||||
SUB(Lib, Font) \
|
||||
SUB(Lib, FontFt) \
|
||||
SUB(Lib, HmdSetupDialog) \
|
||||
SUB(Lib, SigninDialog) \
|
||||
SUB(Lib, Camera) \
|
||||
@@ -154,6 +163,7 @@ 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...
|
||||
|
||||
@@ -21,6 +21,7 @@ struct Entry {
|
||||
u32 line_num = 0;
|
||||
std::string function;
|
||||
std::string message;
|
||||
std::string thread;
|
||||
};
|
||||
|
||||
} // namespace Common::Log
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
@@ -23,8 +24,8 @@ std::string FormatLogMessage(const Entry& entry) {
|
||||
const char* class_name = GetLogClassName(entry.log_class);
|
||||
const char* level_name = GetLevelName(entry.log_level);
|
||||
|
||||
return fmt::format("[{}] <{}> {}:{} {}: {}", class_name, level_name, entry.filename,
|
||||
entry.line_num, entry.function, entry.message);
|
||||
return fmt::format("[{}] <{}> ({}) {}:{} {}: {}", class_name, level_name, entry.thread,
|
||||
entry.filename, entry.line_num, entry.function, entry.message);
|
||||
}
|
||||
|
||||
void PrintMessage(const Entry& entry) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 Citra Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@@ -70,17 +71,22 @@ enum class Class : u8 {
|
||||
Lib_Http2, ///< The LibSceHttp2 implementation.
|
||||
Lib_SysModule, ///< The LibSceSysModule implementation
|
||||
Lib_NpCommon, ///< The LibSceNpCommon implementation
|
||||
Lib_NpCommerce, ///< The LibSceNpCommerce implementation
|
||||
Lib_NpAuth, ///< The LibSceNpAuth implementation
|
||||
Lib_NpManager, ///< The LibSceNpManager implementation
|
||||
Lib_NpMatching2, ///< The LibSceNpMatching2 implementation
|
||||
Lib_NpScore, ///< The LibSceNpScore implementation
|
||||
Lib_NpTrophy, ///< The LibSceNpTrophy implementation
|
||||
Lib_NpTus, ///< The LibSceNpTus implementation
|
||||
Lib_NpWebApi, ///< The LibSceWebApi implementation
|
||||
Lib_NpWebApi2, ///< The LibSceWebApi2 implementation
|
||||
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.
|
||||
Lib_DiscMap, ///< The LibSceDiscMap implementation.
|
||||
Lib_Png, ///< The LibScePng implementation.
|
||||
Lib_Jpeg, ///< The LibSceJpeg implementation.
|
||||
@@ -106,6 +112,7 @@ enum class Class : u8 {
|
||||
Lib_Mouse, ///< The LibSceMouse implementation
|
||||
Lib_WebBrowserDialog, ///< The LibSceWebBrowserDialog implementation
|
||||
Lib_NpParty, ///< The LibSceNpParty implementation
|
||||
Lib_NpPartner, ///< The LibSceNpPartner implementation
|
||||
Lib_Zlib, ///< The LibSceZlib implementation.
|
||||
Lib_Hmd, ///< The LibSceHmd implementation.
|
||||
Lib_HmdSetupDialog, ///< The LibSceHmdSetupDialog implementation.
|
||||
@@ -114,6 +121,8 @@ enum class Class : u8 {
|
||||
Lib_CompanionHttpd, ///< The LibCompanionHttpd implementation.
|
||||
Lib_CompanionUtil, ///< The LibCompanionUtil implementation.
|
||||
Lib_VrTracker, ///< The LibSceVrTracker implementation.
|
||||
Lib_Font, ///< The libSceFont implementation.
|
||||
Lib_FontFt, ///< The libSceFontFt implementation.
|
||||
Frontend, ///< Emulator UI
|
||||
Render, ///< Video Core
|
||||
Render_Vulkan, ///< Vulkan backend
|
||||
@@ -122,6 +131,7 @@ 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
|
||||
};
|
||||
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
// 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 <codecvt>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <pugixml.hpp>
|
||||
#ifdef ENABLE_QT_GUI
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QListView>
|
||||
#include <QMessageBox>
|
||||
#include <QString>
|
||||
#include <QXmlStreamReader>
|
||||
#endif
|
||||
#include "common/config.h"
|
||||
#include "common/elf_info.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/path_util.h"
|
||||
#include "core/emulator_state.h"
|
||||
#include "core/file_format/psf.h"
|
||||
#include "memory_patcher.h"
|
||||
|
||||
@@ -28,7 +21,7 @@ namespace MemoryPatcher {
|
||||
EXPORT uintptr_t g_eboot_address;
|
||||
uint64_t g_eboot_image_size;
|
||||
std::string g_game_serial;
|
||||
std::string patchFile;
|
||||
std::string patch_file;
|
||||
bool patches_applied = false;
|
||||
std::vector<patchInfo> pending_patches;
|
||||
|
||||
@@ -59,14 +52,14 @@ std::string convertValueToHex(const std::string type, const std::string valueStr
|
||||
uint32_t i;
|
||||
} floatUnion;
|
||||
floatUnion.f = std::stof(valueStr);
|
||||
result = toHex(floatUnion.i, sizeof(floatUnion.i));
|
||||
result = toHex(std::byteswap(floatUnion.i), sizeof(floatUnion.i));
|
||||
} else if (type == "float64") {
|
||||
union {
|
||||
double d;
|
||||
uint64_t i;
|
||||
} doubleUnion;
|
||||
doubleUnion.d = std::stod(valueStr);
|
||||
result = toHex(doubleUnion.i, sizeof(doubleUnion.i));
|
||||
result = toHex(std::byteswap(doubleUnion.i), sizeof(doubleUnion.i));
|
||||
} else if (type == "utf8") {
|
||||
std::vector<unsigned char> byteArray =
|
||||
std::vector<unsigned char>(valueStr.begin(), valueStr.end());
|
||||
@@ -122,256 +115,104 @@ std::string convertValueToHex(const std::string type, const std::string valueStr
|
||||
|
||||
void ApplyPendingPatches();
|
||||
|
||||
void OnGameLoaded() {
|
||||
if (!patchFile.empty()) {
|
||||
std::filesystem::path patchDir = Common::FS::GetUserPath(Common::FS::PathType::PatchesDir);
|
||||
void ApplyPatchesFromXML(std::filesystem::path path) {
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_file(path.c_str());
|
||||
|
||||
auto filePath = (patchDir / patchFile).native();
|
||||
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
||||
auto app_version = param_sfo->GetString("APP_VER").value_or("Unknown version");
|
||||
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_file(filePath.c_str());
|
||||
if (result) {
|
||||
auto patchXML = doc.child("Patch");
|
||||
for (pugi::xml_node_iterator it = patchXML.children().begin();
|
||||
it != patchXML.children().end(); ++it) {
|
||||
|
||||
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
||||
auto app_version = param_sfo->GetString("APP_VER").value_or("Unknown version");
|
||||
if (std::string(it->name()) == "Metadata") {
|
||||
if (std::string(it->attribute("isEnabled").value()) == "true") {
|
||||
std::string currentPatchName = it->attribute("Name").value();
|
||||
std::string metadataAppVer = it->attribute("AppVer").value();
|
||||
bool versionMatches = metadataAppVer == app_version;
|
||||
|
||||
if (result) {
|
||||
auto patchXML = doc.child("Patch");
|
||||
for (pugi::xml_node_iterator it = patchXML.children().begin();
|
||||
it != patchXML.children().end(); ++it) {
|
||||
auto patchList = it->first_child();
|
||||
for (pugi::xml_node_iterator patchLineIt = patchList.children().begin();
|
||||
patchLineIt != patchList.children().end(); ++patchLineIt) {
|
||||
|
||||
if (std::string(it->name()) == "Metadata") {
|
||||
if (std::string(it->attribute("isEnabled").value()) == "true") {
|
||||
std::string currentPatchName = it->attribute("Name").value();
|
||||
std::string metadataAppVer = it->attribute("AppVer").value();
|
||||
bool versionMatches = metadataAppVer == app_version;
|
||||
std::string type = patchLineIt->attribute("Type").value();
|
||||
if (!versionMatches && type != "mask" && type != "mask_jump32")
|
||||
continue;
|
||||
|
||||
auto patchList = it->first_child();
|
||||
for (pugi::xml_node_iterator patchLineIt = patchList.children().begin();
|
||||
patchLineIt != patchList.children().end(); ++patchLineIt) {
|
||||
|
||||
std::string type = patchLineIt->attribute("Type").value();
|
||||
if (!versionMatches && type != "mask" && type != "mask_jump32")
|
||||
continue;
|
||||
|
||||
std::string currentPatchName = it->attribute("Name").value();
|
||||
|
||||
for (pugi::xml_node_iterator patchLineIt = patchList.children().begin();
|
||||
patchLineIt != patchList.children().end(); ++patchLineIt) {
|
||||
|
||||
std::string type = patchLineIt->attribute("Type").value();
|
||||
std::string address = patchLineIt->attribute("Address").value();
|
||||
std::string patchValue = patchLineIt->attribute("Value").value();
|
||||
std::string maskOffsetStr =
|
||||
patchLineIt->attribute("Offset").value();
|
||||
std::string targetStr = "";
|
||||
std::string sizeStr = "";
|
||||
if (type == "mask_jump32") {
|
||||
targetStr = patchLineIt->attribute("Target").value();
|
||||
sizeStr = patchLineIt->attribute("Size").value();
|
||||
} else {
|
||||
patchValue = convertValueToHex(type, patchValue);
|
||||
}
|
||||
|
||||
bool littleEndian = false;
|
||||
|
||||
if (type == "bytes16" || type == "bytes32" || type == "bytes64") {
|
||||
littleEndian = true;
|
||||
}
|
||||
|
||||
MemoryPatcher::PatchMask patchMask = MemoryPatcher::PatchMask::None;
|
||||
int maskOffsetValue = 0;
|
||||
|
||||
if (type == "mask")
|
||||
patchMask = MemoryPatcher::PatchMask::Mask;
|
||||
|
||||
if (type == "mask_jump32")
|
||||
patchMask = MemoryPatcher::PatchMask::Mask_Jump32;
|
||||
|
||||
if ((type == "mask" || type == "mask_jump32") &&
|
||||
!maskOffsetStr.empty()) {
|
||||
maskOffsetValue = std::stoi(maskOffsetStr, 0, 10);
|
||||
}
|
||||
|
||||
MemoryPatcher::PatchMemory(currentPatchName, address, patchValue,
|
||||
targetStr, sizeStr, false, littleEndian,
|
||||
patchMask, maskOffsetValue);
|
||||
}
|
||||
std::string address = patchLineIt->attribute("Address").value();
|
||||
std::string patchValue = patchLineIt->attribute("Value").value();
|
||||
std::string maskOffsetStr = patchLineIt->attribute("Offset").value();
|
||||
std::string targetStr = "";
|
||||
std::string sizeStr = "";
|
||||
if (type == "mask_jump32") {
|
||||
targetStr = patchLineIt->attribute("Target").value();
|
||||
sizeStr = patchLineIt->attribute("Size").value();
|
||||
} else {
|
||||
patchValue = convertValueToHex(type, patchValue);
|
||||
}
|
||||
|
||||
bool littleEndian = false;
|
||||
if (type == "bytes16" || type == "bytes32" || type == "bytes64") {
|
||||
littleEndian = true;
|
||||
}
|
||||
|
||||
MemoryPatcher::PatchMask patchMask = MemoryPatcher::PatchMask::None;
|
||||
int maskOffsetValue = 0;
|
||||
|
||||
if (type == "mask")
|
||||
patchMask = MemoryPatcher::PatchMask::Mask;
|
||||
|
||||
if (type == "mask_jump32")
|
||||
patchMask = MemoryPatcher::PatchMask::Mask_Jump32;
|
||||
|
||||
if ((type == "mask" || type == "mask_jump32") && !maskOffsetStr.empty()) {
|
||||
maskOffsetValue = std::stoi(maskOffsetStr, 0, 10);
|
||||
}
|
||||
|
||||
MemoryPatcher::PatchMemory(currentPatchName, address, patchValue, targetStr,
|
||||
sizeStr, false, littleEndian, patchMask,
|
||||
maskOffsetValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(Loader, "Could not parse patch XML: {}", result.description());
|
||||
}
|
||||
}
|
||||
|
||||
void OnGameLoaded() {
|
||||
std::filesystem::path patch_dir = Common::FS::GetUserPath(Common::FS::PathType::PatchesDir);
|
||||
if (!patch_file.empty()) {
|
||||
|
||||
auto file_path = (patch_dir / patch_file).native();
|
||||
if (std::filesystem::exists(patch_file)) {
|
||||
ApplyPatchesFromXML(patch_file);
|
||||
} else {
|
||||
LOG_ERROR(Loader, "couldnt patch parse xml : {}", result.description());
|
||||
ApplyPatchesFromXML(file_path);
|
||||
}
|
||||
} else if (EmulatorState::GetInstance()->IsAutoPatchesLoadEnabled()) {
|
||||
for (auto const& repo : std::filesystem::directory_iterator(patch_dir)) {
|
||||
if (!repo.is_directory()) {
|
||||
continue;
|
||||
}
|
||||
std::ifstream json_file{repo.path() / "files.json"};
|
||||
nlohmann::json available_patches = nlohmann::json::parse(json_file);
|
||||
std::filesystem::path game_patch_file;
|
||||
for (auto const& [filename, serials] : available_patches.items()) {
|
||||
if (std::find(serials.begin(), serials.end(), g_game_serial) != serials.end()) {
|
||||
game_patch_file = repo.path() / filename;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (std::filesystem::exists(game_patch_file)) {
|
||||
ApplyPatchesFromXML(game_patch_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
ApplyPendingPatches();
|
||||
|
||||
#ifdef ENABLE_QT_GUI
|
||||
// We use the QT headers for the xml and json parsing, this define is only true on QT builds
|
||||
QString patchDir;
|
||||
Common::FS::PathToQString(patchDir, Common::FS::GetUserPath(Common::FS::PathType::PatchesDir));
|
||||
QDir dir(patchDir);
|
||||
QStringList folders = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
|
||||
for (const QString& folder : folders) {
|
||||
QString filesJsonPath = patchDir + "/" + folder + "/files.json";
|
||||
|
||||
QFile jsonFile(filesJsonPath);
|
||||
if (!jsonFile.open(QIODevice::ReadOnly)) {
|
||||
LOG_ERROR(Loader, "Unable to open files.json for reading in repository {}",
|
||||
folder.toStdString());
|
||||
continue;
|
||||
}
|
||||
|
||||
QByteArray jsonData = jsonFile.readAll();
|
||||
jsonFile.close();
|
||||
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData);
|
||||
QJsonObject jsonObject = jsonDoc.object();
|
||||
|
||||
QString selectedFileName;
|
||||
QString serial = QString::fromStdString(g_game_serial);
|
||||
|
||||
for (auto it = jsonObject.constBegin(); it != jsonObject.constEnd(); ++it) {
|
||||
QString filePath = it.key();
|
||||
QJsonArray idsArray = it.value().toArray();
|
||||
|
||||
if (idsArray.contains(QJsonValue(serial))) {
|
||||
selectedFileName = filePath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedFileName.isEmpty()) {
|
||||
LOG_ERROR(Loader, "No patch file found for the current serial in repository {}",
|
||||
folder.toStdString());
|
||||
continue;
|
||||
}
|
||||
|
||||
QString filePath = patchDir + "/" + folder + "/" + selectedFileName;
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
LOG_ERROR(Loader, "Unable to open the file for reading.");
|
||||
continue;
|
||||
}
|
||||
|
||||
QByteArray xmlData = file.readAll();
|
||||
file.close();
|
||||
|
||||
QString newXmlData;
|
||||
|
||||
QXmlStreamReader xmlReader(xmlData);
|
||||
|
||||
bool isEnabled = false;
|
||||
std::string currentPatchName;
|
||||
|
||||
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
||||
auto app_version = param_sfo->GetString("APP_VER").value_or("Unknown version");
|
||||
bool versionMatches = true;
|
||||
|
||||
while (!xmlReader.atEnd()) {
|
||||
xmlReader.readNext();
|
||||
|
||||
if (xmlReader.isStartElement()) {
|
||||
QJsonArray patchLines;
|
||||
if (xmlReader.name() == QStringLiteral("Metadata")) {
|
||||
QString name = xmlReader.attributes().value("Name").toString();
|
||||
currentPatchName = name.toStdString();
|
||||
|
||||
QString appVer = xmlReader.attributes().value("AppVer").toString();
|
||||
|
||||
// Check and update the isEnabled attribute
|
||||
isEnabled = false;
|
||||
for (const QXmlStreamAttribute& attr : xmlReader.attributes()) {
|
||||
if (attr.name() == QStringLiteral("isEnabled")) {
|
||||
isEnabled = (attr.value().toString() == "true");
|
||||
}
|
||||
}
|
||||
versionMatches = (appVer.toStdString() == app_version);
|
||||
|
||||
} else if (xmlReader.name() == QStringLiteral("PatchList")) {
|
||||
QJsonArray linesArray;
|
||||
while (!xmlReader.atEnd() &&
|
||||
!(xmlReader.tokenType() == QXmlStreamReader::EndElement &&
|
||||
xmlReader.name() == QStringLiteral("PatchList"))) {
|
||||
xmlReader.readNext();
|
||||
if (xmlReader.tokenType() == QXmlStreamReader::StartElement &&
|
||||
xmlReader.name() == QStringLiteral("Line")) {
|
||||
QXmlStreamAttributes attributes = xmlReader.attributes();
|
||||
QJsonObject lineObject;
|
||||
lineObject["Type"] = attributes.value("Type").toString();
|
||||
lineObject["Address"] = attributes.value("Address").toString();
|
||||
lineObject["Value"] = attributes.value("Value").toString();
|
||||
lineObject["Offset"] = attributes.value("Offset").toString();
|
||||
if (lineObject["Type"].toString() == "mask_jump32") {
|
||||
lineObject["Target"] = attributes.value("Target").toString();
|
||||
lineObject["Size"] = attributes.value("Size").toString();
|
||||
}
|
||||
linesArray.append(lineObject);
|
||||
}
|
||||
}
|
||||
|
||||
patchLines = linesArray;
|
||||
if (isEnabled) {
|
||||
foreach (const QJsonValue& value, patchLines) {
|
||||
QJsonObject lineObject = value.toObject();
|
||||
QString type = lineObject["Type"].toString();
|
||||
|
||||
if ((type != "mask" && type != "mask_jump32") && !versionMatches)
|
||||
continue;
|
||||
|
||||
QString address = lineObject["Address"].toString();
|
||||
QString patchValue = lineObject["Value"].toString();
|
||||
QString maskOffsetStr = lineObject["Offset"].toString();
|
||||
|
||||
QString targetStr;
|
||||
QString sizeStr;
|
||||
|
||||
if (type == "mask_jump32") {
|
||||
targetStr = lineObject["Target"].toString();
|
||||
sizeStr = lineObject["Size"].toString();
|
||||
} else {
|
||||
patchValue = QString::fromStdString(convertValueToHex(
|
||||
type.toStdString(), patchValue.toStdString()));
|
||||
}
|
||||
|
||||
bool littleEndian = false;
|
||||
|
||||
if (type == "bytes16" || type == "bytes32" || type == "bytes64")
|
||||
littleEndian = true;
|
||||
|
||||
MemoryPatcher::PatchMask patchMask = MemoryPatcher::PatchMask::None;
|
||||
int maskOffsetValue = 0;
|
||||
|
||||
if (type == "mask")
|
||||
patchMask = MemoryPatcher::PatchMask::Mask;
|
||||
|
||||
if (type == "mask_jump32")
|
||||
patchMask = MemoryPatcher::PatchMask::Mask_Jump32;
|
||||
|
||||
if ((type == "mask" || type == "mask_jump32") &&
|
||||
!maskOffsetStr.toStdString().empty()) {
|
||||
maskOffsetValue = std::stoi(maskOffsetStr.toStdString(), 0, 10);
|
||||
}
|
||||
|
||||
MemoryPatcher::PatchMemory(
|
||||
currentPatchName, address.toStdString(), patchValue.toStdString(),
|
||||
targetStr.toStdString(), sizeStr.toStdString(), false, littleEndian,
|
||||
patchMask, maskOffsetValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (xmlReader.hasError()) {
|
||||
LOG_ERROR(Loader, "Failed to parse XML for {}", g_game_serial);
|
||||
} else {
|
||||
LOG_INFO(Loader, "Patches loaded successfully, repository: {}", folder.toStdString());
|
||||
}
|
||||
ApplyPendingPatches();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AddPatchToQueue(patchInfo patchToAdd) {
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace MemoryPatcher {
|
||||
extern EXPORT uintptr_t g_eboot_address;
|
||||
extern uint64_t g_eboot_image_size;
|
||||
extern std::string g_game_serial;
|
||||
extern std::string patchFile;
|
||||
extern std::string patch_file;
|
||||
|
||||
enum PatchMask : uint8_t {
|
||||
None,
|
||||
|
||||
@@ -25,10 +25,6 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_QT_GUI
|
||||
#include <QString>
|
||||
#endif
|
||||
|
||||
namespace Common::FS {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
@@ -88,13 +84,6 @@ static std::optional<std::filesystem::path> GetBundleParentDirectory() {
|
||||
#endif
|
||||
|
||||
static auto UserPaths = [] {
|
||||
#if defined(__APPLE__) && defined(ENABLE_QT_GUI)
|
||||
// Set the current path to the directory containing the app bundle.
|
||||
if (const auto bundle_dir = GetBundleParentDirectory()) {
|
||||
std::filesystem::current_path(*bundle_dir);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Try the portable user directory first.
|
||||
auto user_dir = std::filesystem::current_path() / PORTABLE_DIR;
|
||||
if (!std::filesystem::exists(user_dir)) {
|
||||
@@ -138,6 +127,7 @@ static auto UserPaths = [] {
|
||||
create_path(PathType::MetaDataDir, user_dir / METADATA_DIR);
|
||||
create_path(PathType::CustomTrophy, user_dir / CUSTOM_TROPHY);
|
||||
create_path(PathType::CustomConfigs, user_dir / CUSTOM_CONFIGS);
|
||||
create_path(PathType::CacheDir, user_dir / CACHE_DIR);
|
||||
|
||||
std::ofstream notice_file(user_dir / CUSTOM_TROPHY / "Notice.txt");
|
||||
if (notice_file.is_open()) {
|
||||
@@ -229,22 +219,4 @@ std::optional<fs::path> FindGameByID(const fs::path& dir, const std::string& gam
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_QT_GUI
|
||||
void PathToQString(QString& result, const std::filesystem::path& path) {
|
||||
#ifdef _WIN32
|
||||
result = QString::fromStdWString(path.wstring());
|
||||
#else
|
||||
result = QString::fromStdString(path.string());
|
||||
#endif
|
||||
}
|
||||
|
||||
std::filesystem::path PathFromQString(const QString& path) {
|
||||
#ifdef _WIN32
|
||||
return std::filesystem::path(path.toStdWString());
|
||||
#else
|
||||
return std::filesystem::path(path.toStdString());
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace Common::FS
|
||||
|
||||
@@ -7,10 +7,6 @@
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#ifdef ENABLE_QT_GUI
|
||||
class QString; // to avoid including <QString> in this header
|
||||
#endif
|
||||
|
||||
namespace Common::FS {
|
||||
|
||||
enum class PathType {
|
||||
@@ -28,6 +24,7 @@ enum class PathType {
|
||||
MetaDataDir, // Where game metadata (e.g. trophies and menu backgrounds) is stored.
|
||||
CustomTrophy, // Where custom files for trophies are stored.
|
||||
CustomConfigs, // Where custom files for different games are stored.
|
||||
CacheDir, // Where pipeline and shader cache is stored.
|
||||
};
|
||||
|
||||
constexpr auto PORTABLE_DIR = "user";
|
||||
@@ -46,6 +43,7 @@ constexpr auto PATCHES_DIR = "patches";
|
||||
constexpr auto METADATA_DIR = "game_data";
|
||||
constexpr auto CUSTOM_TROPHY = "custom_trophy";
|
||||
constexpr auto CUSTOM_CONFIGS = "custom_configs";
|
||||
constexpr auto CACHE_DIR = "cache";
|
||||
|
||||
// Filenames
|
||||
constexpr auto LOG_FILE = "shad_log.txt";
|
||||
@@ -99,25 +97,6 @@ constexpr auto LOG_FILE = "shad_log.txt";
|
||||
*/
|
||||
void SetUserPath(PathType user_path, const std::filesystem::path& new_path);
|
||||
|
||||
#ifdef ENABLE_QT_GUI
|
||||
/**
|
||||
* Converts an std::filesystem::path to a QString.
|
||||
* The native underlying string of a path is wstring on Windows and string on POSIX.
|
||||
*
|
||||
* @param result The resulting QString
|
||||
* @param path The path to convert
|
||||
*/
|
||||
void PathToQString(QString& result, const std::filesystem::path& path);
|
||||
|
||||
/**
|
||||
* Converts a QString to an std::filesystem::path.
|
||||
* The native underlying string of a path is wstring on Windows and string on POSIX.
|
||||
*
|
||||
* @param path The path to convert
|
||||
*/
|
||||
[[nodiscard]] std::filesystem::path PathFromQString(const QString& path);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Recursively searches for a game directory by its ID.
|
||||
* Limits search depth to prevent excessive filesystem traversal.
|
||||
|
||||
140
src/common/serdes.h
Normal file
140
src/common/serdes.h
Normal file
@@ -0,0 +1,140 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/types.h"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace Serialization {
|
||||
|
||||
template <typename T>
|
||||
concept Container = requires(T t) {
|
||||
typename T::iterator;
|
||||
{ t.begin() } -> std::same_as<typename T::iterator>;
|
||||
{ t.end() } -> std::same_as<typename T::iterator>;
|
||||
{ t.size() } -> std::convertible_to<std::size_t>;
|
||||
};
|
||||
|
||||
struct Archive {
|
||||
void Alloc(size_t size) {
|
||||
container.resize(size);
|
||||
}
|
||||
|
||||
void Grow(size_t size) {
|
||||
container.resize(container.size() + size);
|
||||
}
|
||||
|
||||
void Merge(const Archive& ar) {
|
||||
container.insert(container.end(), ar.container.cbegin(), ar.container.cend());
|
||||
offset = container.size();
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t SizeBytes() const {
|
||||
return container.size();
|
||||
}
|
||||
|
||||
u8* CurrPtr() {
|
||||
return container.data() + offset;
|
||||
}
|
||||
|
||||
void Advance(size_t size) {
|
||||
ASSERT(offset + size <= container.size());
|
||||
offset += size;
|
||||
}
|
||||
|
||||
std::vector<u8>&& TakeOff() {
|
||||
offset = 0;
|
||||
return std::move(container);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsEoS() const {
|
||||
return offset >= container.size();
|
||||
}
|
||||
|
||||
Archive() = default;
|
||||
explicit Archive(std::vector<u8>&& v) : container{v} {}
|
||||
|
||||
private:
|
||||
u32 offset{};
|
||||
std::vector<u8> container{};
|
||||
|
||||
friend struct Writer;
|
||||
friend struct Reader;
|
||||
};
|
||||
|
||||
struct Writer {
|
||||
template <typename T>
|
||||
void Write(const T* ptr, size_t size) {
|
||||
if (ar.offset + size >= ar.container.size()) {
|
||||
ar.Grow(size);
|
||||
}
|
||||
std::memcpy(ar.CurrPtr(), reinterpret_cast<const void*>(ptr), size);
|
||||
ar.Advance(size);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires(!Container<T>)
|
||||
void Write(const T& value) {
|
||||
const auto size = sizeof(value);
|
||||
Write(&value, size);
|
||||
}
|
||||
|
||||
void Write(const auto& v) {
|
||||
Write(v.size());
|
||||
for (const auto& elem : v) {
|
||||
Write(elem);
|
||||
}
|
||||
}
|
||||
|
||||
void Write(const std::string& s) {
|
||||
Write(s.size());
|
||||
Write(s.c_str(), s.size());
|
||||
}
|
||||
|
||||
Writer() = delete;
|
||||
explicit Writer(Archive& ar_) : ar{ar_} {}
|
||||
|
||||
Archive& ar;
|
||||
};
|
||||
|
||||
struct Reader {
|
||||
template <typename T>
|
||||
void Read(T* ptr, size_t size) {
|
||||
ASSERT(ar.offset + size <= ar.container.size());
|
||||
std::memcpy(reinterpret_cast<void*>(ptr), ar.CurrPtr(), size);
|
||||
ar.Advance(size);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires(!Container<T>)
|
||||
void Read(T& value) {
|
||||
const auto size = sizeof(value);
|
||||
Read(&value, size);
|
||||
}
|
||||
|
||||
void Read(auto& v) {
|
||||
size_t num_elements{};
|
||||
Read(num_elements);
|
||||
for (int i = 0; i < num_elements; ++i) {
|
||||
v.emplace_back();
|
||||
Read(v.back());
|
||||
}
|
||||
}
|
||||
|
||||
void Read(std::string& s) {
|
||||
size_t length{};
|
||||
Read(length);
|
||||
s.resize(length);
|
||||
Read(s.data(), length);
|
||||
}
|
||||
|
||||
Reader() = delete;
|
||||
explicit Reader(Archive& ar_) : ar{ar_} {}
|
||||
|
||||
Archive& ar;
|
||||
};
|
||||
|
||||
} // namespace Serialization
|
||||
@@ -17,6 +17,15 @@ public:
|
||||
writer_active = true;
|
||||
}
|
||||
|
||||
bool try_lock() {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
if (writer_active || readers > 0) {
|
||||
return false;
|
||||
}
|
||||
writer_active = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
writer_active = false;
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "core/libraries/kernel/threads/pthread.h"
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/thread.h"
|
||||
@@ -171,6 +174,9 @@ bool AccurateSleep(const std::chrono::nanoseconds duration, std::chrono::nanosec
|
||||
|
||||
// Sets the debugger-visible name of the current thread.
|
||||
void SetCurrentThreadName(const char* name) {
|
||||
if (Libraries::Kernel::g_curthread) {
|
||||
Libraries::Kernel::g_curthread->name = std::string{name};
|
||||
}
|
||||
SetThreadDescription(GetCurrentThread(), UTF8ToUTF16W(name).data());
|
||||
}
|
||||
|
||||
@@ -183,6 +189,9 @@ void SetThreadName(void* thread, const char* name) {
|
||||
// MinGW with the POSIX threading model does not support pthread_setname_np
|
||||
#if !defined(_WIN32) || defined(_MSC_VER)
|
||||
void SetCurrentThreadName(const char* name) {
|
||||
if (Libraries::Kernel::g_curthread) {
|
||||
Libraries::Kernel::g_curthread->name = std::string{name};
|
||||
}
|
||||
#ifdef __APPLE__
|
||||
pthread_setname_np(name);
|
||||
#elif defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
|
||||
@@ -209,6 +218,9 @@ void SetThreadName(void* thread, const char* name) {
|
||||
|
||||
#if defined(_WIN32)
|
||||
void SetCurrentThreadName(const char*) {
|
||||
if (Libraries::Kernel::g_curthread) {
|
||||
Libraries::Kernel::g_curthread->name = std::string{name};
|
||||
}
|
||||
// Do Nothing on MinGW
|
||||
}
|
||||
|
||||
@@ -237,4 +249,22 @@ void AccurateTimer::End() {
|
||||
target_interval - std::chrono::duration_cast<std::chrono::nanoseconds>(now - start_time);
|
||||
}
|
||||
|
||||
std::string GetCurrentThreadName() {
|
||||
using namespace Libraries::Kernel;
|
||||
if (g_curthread && !g_curthread->name.empty()) {
|
||||
return g_curthread->name;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
PWSTR name;
|
||||
GetThreadDescription(GetCurrentThread(), &name);
|
||||
return Common::UTF16ToUTF8(name);
|
||||
#else
|
||||
char name[256];
|
||||
if (pthread_getname_np(pthread_self(), name, sizeof(name)) != 0) {
|
||||
return "<unknown name>";
|
||||
}
|
||||
return std::string{name};
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@@ -46,4 +47,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
std::string GetCurrentThreadName();
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#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/libraries/kernel/memory.h"
|
||||
@@ -92,7 +93,10 @@ static u64 BackingSize = ORBIS_KERNEL_TOTAL_MEM_DEV_PRO;
|
||||
|
||||
struct MemoryRegion {
|
||||
VAddr base;
|
||||
size_t size;
|
||||
PAddr phys_base;
|
||||
u64 size;
|
||||
u32 prot;
|
||||
s32 fd;
|
||||
bool is_mapped;
|
||||
};
|
||||
|
||||
@@ -103,8 +107,8 @@ struct AddressSpace::Impl {
|
||||
GetSystemInfo(&sys_info);
|
||||
u64 alignment = sys_info.dwAllocationGranularity;
|
||||
|
||||
// Determine the host OS build number
|
||||
// Retrieve module handle for ntdll
|
||||
// Older Windows builds have a severe performance issue with VirtualAlloc2.
|
||||
// We need to get the host's Windows version, then determine if it needs a workaround.
|
||||
auto ntdll_handle = GetModuleHandleW(L"ntdll.dll");
|
||||
ASSERT_MSG(ntdll_handle, "Failed to retrieve ntdll handle");
|
||||
|
||||
@@ -120,12 +124,20 @@ struct AddressSpace::Impl {
|
||||
u64 supported_user_max = USER_MAX;
|
||||
// This is the build number for Windows 11 22H2
|
||||
static constexpr s32 AffectedBuildNumber = 22621;
|
||||
if (os_version_info.dwBuildNumber <= AffectedBuildNumber) {
|
||||
// Older Windows builds have an issue with VirtualAlloc2 on higher addresses.
|
||||
// To prevent regressions, limit the maximum address we reserve for this platform.
|
||||
supported_user_max = 0x11000000000ULL;
|
||||
LOG_WARNING(Core, "Windows 10 detected, reducing user max to {:#x} to avoid problems",
|
||||
supported_user_max);
|
||||
|
||||
// Higher PS4 firmware versions prevent higher address mappings too.
|
||||
s32 sdk_ver = Common::ElfInfo::Instance().CompiledSdkVer();
|
||||
if (os_version_info.dwBuildNumber <= AffectedBuildNumber ||
|
||||
sdk_ver >= Common::ElfInfo::FW_30) {
|
||||
supported_user_max = 0x10000000000ULL;
|
||||
// Only log the message if we're restricting the user max due to operating system.
|
||||
// Since higher compiled SDK versions also get reduced max, we don't need to log there.
|
||||
if (sdk_ver < Common::ElfInfo::FW_30) {
|
||||
LOG_WARNING(
|
||||
Core,
|
||||
"Older Windows version detected, reducing user max to {:#x} to avoid problems",
|
||||
supported_user_max);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the free address ranges we can access.
|
||||
@@ -150,7 +162,8 @@ struct AddressSpace::Impl {
|
||||
// Restrict region size to avoid overly fragmenting the virtual memory space.
|
||||
if (info.State == MEM_FREE && info.RegionSize > 0x1000000) {
|
||||
VAddr addr = Common::AlignUp(reinterpret_cast<VAddr>(info.BaseAddress), alignment);
|
||||
regions.emplace(addr, MemoryRegion{addr, size, false});
|
||||
regions.emplace(addr,
|
||||
MemoryRegion{addr, PAddr(-1), size, PAGE_NOACCESS, -1, false});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,46 +211,52 @@ struct AddressSpace::Impl {
|
||||
~Impl() {
|
||||
if (virtual_base) {
|
||||
if (!VirtualFree(virtual_base, 0, MEM_RELEASE)) {
|
||||
LOG_CRITICAL(Render, "Failed to free virtual memory");
|
||||
LOG_CRITICAL(Core, "Failed to free virtual memory");
|
||||
}
|
||||
}
|
||||
if (backing_base) {
|
||||
if (!UnmapViewOfFile2(process, backing_base, MEM_PRESERVE_PLACEHOLDER)) {
|
||||
LOG_CRITICAL(Render, "Failed to unmap backing memory placeholder");
|
||||
LOG_CRITICAL(Core, "Failed to unmap backing memory placeholder");
|
||||
}
|
||||
if (!VirtualFreeEx(process, backing_base, 0, MEM_RELEASE)) {
|
||||
LOG_CRITICAL(Render, "Failed to free backing memory");
|
||||
LOG_CRITICAL(Core, "Failed to free backing memory");
|
||||
}
|
||||
}
|
||||
if (!CloseHandle(backing_handle)) {
|
||||
LOG_CRITICAL(Render, "Failed to free backing memory file handle");
|
||||
LOG_CRITICAL(Core, "Failed to free backing memory file handle");
|
||||
}
|
||||
}
|
||||
|
||||
void* Map(VAddr virtual_addr, PAddr phys_addr, size_t size, ULONG prot, uintptr_t fd = 0) {
|
||||
// Before mapping we must carve a placeholder with the exact properties of our mapping.
|
||||
auto* region = EnsureSplitRegionForMapping(virtual_addr, size);
|
||||
region->is_mapped = true;
|
||||
void* MapRegion(MemoryRegion* region) {
|
||||
VAddr virtual_addr = region->base;
|
||||
PAddr phys_addr = region->phys_base;
|
||||
u64 size = region->size;
|
||||
ULONG prot = region->prot;
|
||||
s32 fd = region->fd;
|
||||
|
||||
void* ptr = nullptr;
|
||||
if (phys_addr != -1) {
|
||||
HANDLE backing = fd ? reinterpret_cast<HANDLE>(fd) : backing_handle;
|
||||
if (fd && prot == PAGE_READONLY) {
|
||||
HANDLE backing = fd != -1 ? reinterpret_cast<HANDLE>(fd) : backing_handle;
|
||||
if (fd != -1 && prot == PAGE_READONLY) {
|
||||
DWORD resultvar;
|
||||
ptr = VirtualAlloc2(process, reinterpret_cast<PVOID>(virtual_addr), size,
|
||||
MEM_RESERVE | MEM_COMMIT | MEM_REPLACE_PLACEHOLDER,
|
||||
PAGE_READWRITE, nullptr, 0);
|
||||
bool ret = ReadFile(backing, ptr, size, &resultvar, NULL);
|
||||
|
||||
// phys_addr serves as an offset for file mmaps.
|
||||
// Create an OVERLAPPED with the offset, then supply that to ReadFile
|
||||
OVERLAPPED param{};
|
||||
// Offset is the least-significant 32 bits, OffsetHigh is the most-significant.
|
||||
param.Offset = phys_addr & 0xffffffffull;
|
||||
param.OffsetHigh = (phys_addr & 0xffffffff00000000ull) >> 32;
|
||||
bool ret = ReadFile(backing, ptr, size, &resultvar, ¶m);
|
||||
ASSERT_MSG(ret, "ReadFile failed. {}", Common::GetLastErrorMsg());
|
||||
ret = VirtualProtect(ptr, size, prot, &resultvar);
|
||||
ASSERT_MSG(ret, "VirtualProtect failed. {}", Common::GetLastErrorMsg());
|
||||
} else {
|
||||
ptr = MapViewOfFile3(backing, process, reinterpret_cast<PVOID>(virtual_addr),
|
||||
phys_addr, size, MEM_REPLACE_PLACEHOLDER,
|
||||
PAGE_EXECUTE_READWRITE, nullptr, 0);
|
||||
phys_addr, size, MEM_REPLACE_PLACEHOLDER, prot, nullptr, 0);
|
||||
ASSERT_MSG(ptr, "MapViewOfFile3 failed. {}", Common::GetLastErrorMsg());
|
||||
DWORD resultvar;
|
||||
bool ret = VirtualProtect(ptr, size, prot, &resultvar);
|
||||
ASSERT_MSG(ret, "VirtualProtect failed. {}", Common::GetLastErrorMsg());
|
||||
}
|
||||
} else {
|
||||
ptr =
|
||||
@@ -248,135 +267,220 @@ struct AddressSpace::Impl {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void Unmap(VAddr virtual_addr, size_t size, bool has_backing) {
|
||||
bool ret;
|
||||
if (has_backing) {
|
||||
void UnmapRegion(MemoryRegion* region) {
|
||||
VAddr virtual_addr = region->base;
|
||||
PAddr phys_base = region->phys_base;
|
||||
u64 size = region->size;
|
||||
ULONG prot = region->prot;
|
||||
s32 fd = region->fd;
|
||||
|
||||
bool ret = false;
|
||||
if ((fd != -1 && prot != PAGE_READONLY) || (fd == -1 && phys_base != -1)) {
|
||||
ret = UnmapViewOfFile2(process, reinterpret_cast<PVOID>(virtual_addr),
|
||||
MEM_PRESERVE_PLACEHOLDER);
|
||||
} else {
|
||||
ret = VirtualFreeEx(process, reinterpret_cast<PVOID>(virtual_addr), size,
|
||||
MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER);
|
||||
}
|
||||
ASSERT_MSG(ret, "Unmap operation on virtual_addr={:#X} failed: {}", virtual_addr,
|
||||
ASSERT_MSG(ret, "Unmap on virtual_addr {:#x}, size {:#x} failed: {}", virtual_addr, size,
|
||||
Common::GetLastErrorMsg());
|
||||
|
||||
// The unmap call will create a new placeholder region. We need to see if we can coalesce it
|
||||
// with neighbors.
|
||||
JoinRegionsAfterUnmap(virtual_addr, size);
|
||||
}
|
||||
|
||||
// The following code is inspired from Dolphin's MemArena
|
||||
// https://github.com/dolphin-emu/dolphin/blob/deee3ee4/Source/Core/Common/MemArenaWin.cpp#L212
|
||||
MemoryRegion* EnsureSplitRegionForMapping(VAddr address, size_t size) {
|
||||
// Find closest region that is <= the given address by using upper bound and decrementing
|
||||
auto it = regions.upper_bound(address);
|
||||
ASSERT_MSG(it != regions.begin(), "Invalid address {:#x}", address);
|
||||
--it;
|
||||
ASSERT_MSG(!it->second.is_mapped,
|
||||
"Attempt to map {:#x} with size {:#x} which overlaps with {:#x} mapping",
|
||||
address, size, it->second.base);
|
||||
auto& [base, region] = *it;
|
||||
void SplitRegion(VAddr virtual_addr, u64 size) {
|
||||
// First, get the region this range covers
|
||||
auto it = std::prev(regions.upper_bound(virtual_addr));
|
||||
|
||||
const VAddr mapping_address = region.base;
|
||||
const size_t region_size = region.size;
|
||||
if (mapping_address == address) {
|
||||
// If this region is already split up correctly we don't have to do anything
|
||||
if (region_size == size) {
|
||||
return ®ion;
|
||||
// All unmapped areas will coalesce, so there should be a region
|
||||
// containing the full requested range. If not, then something is mapped here.
|
||||
ASSERT_MSG(it->second.base + it->second.size >= virtual_addr + size,
|
||||
"Cannot fit region into one placeholder");
|
||||
|
||||
// If the region is mapped, we need to unmap first before we can modify the placeholders.
|
||||
if (it->second.is_mapped) {
|
||||
ASSERT_MSG(it->second.phys_base != -1 || !it->second.is_mapped,
|
||||
"Cannot split unbacked mapping");
|
||||
UnmapRegion(&it->second);
|
||||
}
|
||||
|
||||
// We need to split this region to create a matching placeholder.
|
||||
if (it->second.base != virtual_addr) {
|
||||
// Requested address is not the start of the containing region,
|
||||
// create a new region to represent the memory before the requested range.
|
||||
auto& region = it->second;
|
||||
u64 base_offset = virtual_addr - region.base;
|
||||
u64 next_region_size = region.size - base_offset;
|
||||
PAddr next_region_phys_base = -1;
|
||||
if (region.is_mapped) {
|
||||
next_region_phys_base = region.phys_base + base_offset;
|
||||
}
|
||||
region.size = base_offset;
|
||||
|
||||
ASSERT_MSG(region_size >= size,
|
||||
"Region with address {:#x} and size {:#x} can't fit {:#x}", mapping_address,
|
||||
region_size, size);
|
||||
|
||||
// Split the placeholder.
|
||||
if (!VirtualFreeEx(process, LPVOID(address), size,
|
||||
// Use VirtualFreeEx to create the split.
|
||||
if (!VirtualFreeEx(process, LPVOID(region.base), region.size,
|
||||
MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) {
|
||||
UNREACHABLE_MSG("Region splitting failed: {}", Common::GetLastErrorMsg());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Update tracked mappings and return the first of the two
|
||||
// If the mapping was mapped, remap the region.
|
||||
if (region.is_mapped) {
|
||||
MapRegion(®ion);
|
||||
}
|
||||
|
||||
// Store a new region matching the removed area.
|
||||
it = regions.emplace_hint(std::next(it), virtual_addr,
|
||||
MemoryRegion(virtual_addr, next_region_phys_base,
|
||||
next_region_size, region.prot, region.fd,
|
||||
region.is_mapped));
|
||||
}
|
||||
|
||||
// At this point, the region's base will match virtual_addr.
|
||||
// Now check for a size difference.
|
||||
if (it->second.size != size) {
|
||||
// The requested size is smaller than the current region placeholder.
|
||||
// Update region to match the requested region,
|
||||
// then make a new region to represent the remaining space.
|
||||
auto& region = it->second;
|
||||
VAddr next_region_addr = region.base + size;
|
||||
u64 next_region_size = region.size - size;
|
||||
PAddr next_region_phys_base = -1;
|
||||
if (region.is_mapped) {
|
||||
next_region_phys_base = region.phys_base + size;
|
||||
}
|
||||
region.size = size;
|
||||
const VAddr new_mapping_start = address + size;
|
||||
regions.emplace_hint(std::next(it), new_mapping_start,
|
||||
MemoryRegion(new_mapping_start, region_size - size, false));
|
||||
return ®ion;
|
||||
|
||||
// Store the new region matching the remaining space
|
||||
regions.emplace_hint(std::next(it), next_region_addr,
|
||||
MemoryRegion(next_region_addr, next_region_phys_base,
|
||||
next_region_size, region.prot, region.fd,
|
||||
region.is_mapped));
|
||||
|
||||
// Use VirtualFreeEx to create the split.
|
||||
if (!VirtualFreeEx(process, LPVOID(region.base), region.size,
|
||||
MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) {
|
||||
UNREACHABLE_MSG("Region splitting failed: {}", Common::GetLastErrorMsg());
|
||||
}
|
||||
|
||||
// If these regions were mapped, then map the unmapped area beyond the requested range.
|
||||
if (region.is_mapped) {
|
||||
MapRegion(&std::next(it)->second);
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT(mapping_address < address);
|
||||
|
||||
// Is there enough space to map this?
|
||||
const size_t offset_in_region = address - mapping_address;
|
||||
const size_t minimum_size = size + offset_in_region;
|
||||
ASSERT(region_size >= minimum_size);
|
||||
|
||||
// Split the placeholder.
|
||||
if (!VirtualFreeEx(process, LPVOID(address), size,
|
||||
MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) {
|
||||
UNREACHABLE_MSG("Region splitting failed: {}", Common::GetLastErrorMsg());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Do we now have two regions or three regions?
|
||||
if (region_size == minimum_size) {
|
||||
// Split into two; update tracked mappings and return the second one
|
||||
region.size = offset_in_region;
|
||||
it = regions.emplace_hint(std::next(it), address, MemoryRegion(address, size, false));
|
||||
return &it->second;
|
||||
} else {
|
||||
// Split into three; update tracked mappings and return the middle one
|
||||
region.size = offset_in_region;
|
||||
const VAddr middle_mapping_start = address;
|
||||
const size_t middle_mapping_size = size;
|
||||
const VAddr after_mapping_start = address + size;
|
||||
const size_t after_mapping_size = region_size - minimum_size;
|
||||
it = regions.emplace_hint(std::next(it), after_mapping_start,
|
||||
MemoryRegion(after_mapping_start, after_mapping_size, false));
|
||||
it = regions.emplace_hint(
|
||||
it, middle_mapping_start,
|
||||
MemoryRegion(middle_mapping_start, middle_mapping_size, false));
|
||||
return &it->second;
|
||||
// If the requested region was mapped, remap it.
|
||||
if (it->second.is_mapped) {
|
||||
MapRegion(&it->second);
|
||||
}
|
||||
}
|
||||
|
||||
void JoinRegionsAfterUnmap(VAddr address, size_t size) {
|
||||
// There should be a mapping that matches the request exactly, find it
|
||||
auto it = regions.find(address);
|
||||
ASSERT_MSG(it != regions.end() && it->second.size == size,
|
||||
"Invalid address/size given to unmap.");
|
||||
void* Map(VAddr virtual_addr, PAddr phys_addr, u64 size, ULONG prot, s32 fd = -1) {
|
||||
// Get a pointer to the region containing virtual_addr
|
||||
auto it = std::prev(regions.upper_bound(virtual_addr));
|
||||
|
||||
// If needed, split surrounding regions to create a placeholder
|
||||
if (it->first != virtual_addr || it->second.size != size) {
|
||||
SplitRegion(virtual_addr, size);
|
||||
it = std::prev(regions.upper_bound(virtual_addr));
|
||||
}
|
||||
|
||||
// Get the address and region for this range.
|
||||
auto& [base, region] = *it;
|
||||
region.is_mapped = false;
|
||||
ASSERT_MSG(!region.is_mapped, "Cannot overwrite mapped region");
|
||||
|
||||
// Check if a placeholder exists right before us.
|
||||
// Now we have a region matching the requested region, perform the actual mapping.
|
||||
region.is_mapped = true;
|
||||
region.phys_base = phys_addr;
|
||||
region.prot = prot;
|
||||
region.fd = fd;
|
||||
return MapRegion(®ion);
|
||||
}
|
||||
|
||||
void CoalesceFreeRegions(VAddr virtual_addr) {
|
||||
// First, get the region to update
|
||||
auto it = std::prev(regions.upper_bound(virtual_addr));
|
||||
ASSERT_MSG(!it->second.is_mapped, "Cannot coalesce mapped regions");
|
||||
|
||||
// Check if there are adjacent free placeholders before this area.
|
||||
bool can_coalesce = false;
|
||||
auto it_prev = it != regions.begin() ? std::prev(it) : regions.end();
|
||||
if (it_prev != regions.end() && !it_prev->second.is_mapped) {
|
||||
const size_t total_size = it_prev->second.size + size;
|
||||
if (!VirtualFreeEx(process, LPVOID(it_prev->first), total_size,
|
||||
MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS)) {
|
||||
UNREACHABLE_MSG("Region coalescing failed: {}", Common::GetLastErrorMsg());
|
||||
}
|
||||
|
||||
it_prev->second.size = total_size;
|
||||
while (it_prev != regions.end() && !it_prev->second.is_mapped &&
|
||||
it_prev->first + it_prev->second.size == it->first) {
|
||||
// If there is an earlier region, move our iterator to that and increase size.
|
||||
it_prev->second.size = it_prev->second.size + it->second.size;
|
||||
regions.erase(it);
|
||||
it = it_prev;
|
||||
|
||||
// Mark this region as coalesce-able.
|
||||
can_coalesce = true;
|
||||
|
||||
// Get the next previous region.
|
||||
it_prev = it != regions.begin() ? std::prev(it) : regions.end();
|
||||
}
|
||||
|
||||
// Check if a placeholder exists right after us.
|
||||
// Check if there are adjacent free placeholders after this area.
|
||||
auto it_next = std::next(it);
|
||||
if (it_next != regions.end() && !it_next->second.is_mapped) {
|
||||
const size_t total_size = it->second.size + it_next->second.size;
|
||||
if (!VirtualFreeEx(process, LPVOID(it->first), total_size,
|
||||
while (it_next != regions.end() && !it_next->second.is_mapped &&
|
||||
it->first + it->second.size == it_next->first) {
|
||||
// If there is a later region, increase our current region's size
|
||||
it->second.size = it->second.size + it_next->second.size;
|
||||
regions.erase(it_next);
|
||||
|
||||
// Mark this region as coalesce-able.
|
||||
can_coalesce = true;
|
||||
|
||||
// Get the next region
|
||||
it_next = std::next(it);
|
||||
}
|
||||
|
||||
// If there are placeholders to coalesce, then coalesce them.
|
||||
if (can_coalesce) {
|
||||
if (!VirtualFreeEx(process, LPVOID(it->first), it->second.size,
|
||||
MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS)) {
|
||||
UNREACHABLE_MSG("Region coalescing failed: {}", Common::GetLastErrorMsg());
|
||||
}
|
||||
|
||||
it->second.size = total_size;
|
||||
regions.erase(it_next);
|
||||
}
|
||||
}
|
||||
|
||||
void Protect(VAddr virtual_addr, size_t size, bool read, bool write, bool execute) {
|
||||
void Unmap(VAddr virtual_addr, u64 size) {
|
||||
// Loop through all regions in the requested range
|
||||
u64 remaining_size = size;
|
||||
VAddr current_addr = virtual_addr;
|
||||
while (remaining_size > 0) {
|
||||
// Get a pointer to the region containing virtual_addr
|
||||
auto it = std::prev(regions.upper_bound(current_addr));
|
||||
|
||||
// If necessary, split regions to ensure a valid unmap.
|
||||
// To prevent complication, ensure size is within the bounds of the current region.
|
||||
u64 base_offset = current_addr - it->second.base;
|
||||
u64 size_to_unmap = std::min<u64>(it->second.size - base_offset, remaining_size);
|
||||
if (current_addr != it->second.base || size_to_unmap != it->second.size) {
|
||||
SplitRegion(current_addr, size_to_unmap);
|
||||
it = std::prev(regions.upper_bound(current_addr));
|
||||
}
|
||||
|
||||
// Get the address and region corresponding to this range.
|
||||
auto& [base, region] = *it;
|
||||
|
||||
// Unmap the region if it was previously mapped
|
||||
if (region.is_mapped) {
|
||||
UnmapRegion(®ion);
|
||||
}
|
||||
|
||||
// Update region data
|
||||
region.is_mapped = false;
|
||||
region.fd = -1;
|
||||
region.phys_base = -1;
|
||||
region.prot = PAGE_NOACCESS;
|
||||
|
||||
// Update loop variables
|
||||
remaining_size -= size_to_unmap;
|
||||
current_addr += size_to_unmap;
|
||||
}
|
||||
|
||||
// Coalesce any free space produced from these unmaps.
|
||||
CoalesceFreeRegions(virtual_addr);
|
||||
}
|
||||
|
||||
void Protect(VAddr virtual_addr, u64 size, bool read, bool write, bool execute) {
|
||||
DWORD new_flags{};
|
||||
|
||||
if (write && !read) {
|
||||
@@ -406,7 +510,7 @@ struct AddressSpace::Impl {
|
||||
|
||||
// If no flags are assigned, then something's gone wrong.
|
||||
if (new_flags == 0) {
|
||||
LOG_CRITICAL(Common_Memory,
|
||||
LOG_CRITICAL(Core,
|
||||
"Unsupported protection flag combination for address {:#x}, size {}, "
|
||||
"read={}, write={}, execute={}",
|
||||
virtual_addr, size, read, write, execute);
|
||||
@@ -415,13 +519,14 @@ struct AddressSpace::Impl {
|
||||
|
||||
const VAddr virtual_end = virtual_addr + size;
|
||||
auto it = --regions.upper_bound(virtual_addr);
|
||||
ASSERT_MSG(it != regions.end(), "addr {:#x} out of bounds", virtual_addr);
|
||||
for (; it->first < virtual_end; it++) {
|
||||
if (!it->second.is_mapped) {
|
||||
continue;
|
||||
}
|
||||
const auto& region = it->second;
|
||||
const size_t range_addr = std::max(region.base, virtual_addr);
|
||||
const size_t range_size = std::min(region.base + region.size, virtual_end) - range_addr;
|
||||
const u64 range_addr = std::max(region.base, virtual_addr);
|
||||
const u64 range_size = std::min(region.base + region.size, virtual_end) - range_addr;
|
||||
DWORD old_flags{};
|
||||
if (!VirtualProtectEx(process, LPVOID(range_addr), range_size, new_flags, &old_flags)) {
|
||||
UNREACHABLE_MSG(
|
||||
@@ -444,11 +549,11 @@ struct AddressSpace::Impl {
|
||||
u8* backing_base{};
|
||||
u8* virtual_base{};
|
||||
u8* system_managed_base{};
|
||||
size_t system_managed_size{};
|
||||
u64 system_managed_size{};
|
||||
u8* system_reserved_base{};
|
||||
size_t system_reserved_size{};
|
||||
u64 system_reserved_size{};
|
||||
u8* user_base{};
|
||||
size_t user_size{};
|
||||
u64 user_size{};
|
||||
std::map<VAddr, MemoryRegion> regions;
|
||||
};
|
||||
#else
|
||||
@@ -592,7 +697,7 @@ struct AddressSpace::Impl {
|
||||
}
|
||||
}
|
||||
|
||||
void* Map(VAddr virtual_addr, PAddr phys_addr, size_t size, PosixPageProtection prot,
|
||||
void* Map(VAddr virtual_addr, PAddr phys_addr, u64 size, PosixPageProtection prot,
|
||||
int fd = -1) {
|
||||
m_free_regions.subtract({virtual_addr, virtual_addr + size});
|
||||
const int handle = phys_addr != -1 ? (fd == -1 ? backing_fd : fd) : -1;
|
||||
@@ -604,10 +709,10 @@ struct AddressSpace::Impl {
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Unmap(VAddr virtual_addr, size_t size, bool) {
|
||||
void Unmap(VAddr virtual_addr, u64 size) {
|
||||
// Check to see if we are adjacent to any regions.
|
||||
auto start_address = virtual_addr;
|
||||
auto end_address = start_address + size;
|
||||
VAddr start_address = virtual_addr;
|
||||
VAddr end_address = start_address + size;
|
||||
auto it = m_free_regions.find({start_address - 1, end_address + 1});
|
||||
|
||||
// If we are, join with them, ensuring we stay in bounds.
|
||||
@@ -625,7 +730,7 @@ struct AddressSpace::Impl {
|
||||
ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno));
|
||||
}
|
||||
|
||||
void Protect(VAddr virtual_addr, size_t size, bool read, bool write, bool execute) {
|
||||
void Protect(VAddr virtual_addr, u64 size, bool read, bool write, bool execute) {
|
||||
int flags = PROT_NONE;
|
||||
if (read) {
|
||||
flags |= PROT_READ;
|
||||
@@ -645,11 +750,11 @@ struct AddressSpace::Impl {
|
||||
int backing_fd;
|
||||
u8* backing_base{};
|
||||
u8* system_managed_base{};
|
||||
size_t system_managed_size{};
|
||||
u64 system_managed_size{};
|
||||
u8* system_reserved_base{};
|
||||
size_t system_reserved_size{};
|
||||
u64 system_reserved_size{};
|
||||
u8* user_base{};
|
||||
size_t user_size{};
|
||||
u64 user_size{};
|
||||
boost::icl::interval_set<VAddr> m_free_regions;
|
||||
};
|
||||
#endif
|
||||
@@ -666,8 +771,7 @@ AddressSpace::AddressSpace() : impl{std::make_unique<Impl>()} {
|
||||
|
||||
AddressSpace::~AddressSpace() = default;
|
||||
|
||||
void* AddressSpace::Map(VAddr virtual_addr, size_t size, u64 alignment, PAddr phys_addr,
|
||||
bool is_exec) {
|
||||
void* AddressSpace::Map(VAddr virtual_addr, u64 size, PAddr phys_addr, bool is_exec) {
|
||||
#if ARCH_X86_64
|
||||
const auto prot = is_exec ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
|
||||
#else
|
||||
@@ -678,8 +782,7 @@ void* AddressSpace::Map(VAddr virtual_addr, size_t size, u64 alignment, PAddr ph
|
||||
return impl->Map(virtual_addr, phys_addr, size, prot);
|
||||
}
|
||||
|
||||
void* AddressSpace::MapFile(VAddr virtual_addr, size_t size, size_t offset, u32 prot,
|
||||
uintptr_t fd) {
|
||||
void* AddressSpace::MapFile(VAddr virtual_addr, u64 size, u64 offset, u32 prot, uintptr_t fd) {
|
||||
#ifdef _WIN32
|
||||
return impl->Map(virtual_addr, offset, size,
|
||||
ToWindowsProt(std::bit_cast<Core::MemoryProt>(prot)), fd);
|
||||
@@ -689,31 +792,11 @@ void* AddressSpace::MapFile(VAddr virtual_addr, size_t size, size_t offset, u32
|
||||
#endif
|
||||
}
|
||||
|
||||
void AddressSpace::Unmap(VAddr virtual_addr, size_t size, VAddr start_in_vma, VAddr end_in_vma,
|
||||
PAddr phys_base, bool is_exec, bool has_backing, bool readonly_file) {
|
||||
#ifdef _WIN32
|
||||
// There does not appear to be comparable support for partial unmapping on Windows.
|
||||
// Unfortunately, a least one title was found to require this. The workaround is to unmap
|
||||
// the entire allocation and remap the portions outside of the requested unmapping range.
|
||||
impl->Unmap(virtual_addr, size, has_backing && !readonly_file);
|
||||
|
||||
// TODO: Determine if any titles require partial unmapping support for un-backed allocations.
|
||||
ASSERT_MSG(has_backing || (start_in_vma == 0 && end_in_vma == size),
|
||||
"Partial unmapping of un-backed allocations is not supported");
|
||||
|
||||
if (start_in_vma != 0) {
|
||||
Map(virtual_addr, start_in_vma, 0, phys_base, is_exec);
|
||||
}
|
||||
|
||||
if (end_in_vma != size) {
|
||||
Map(virtual_addr + end_in_vma, size - end_in_vma, 0, phys_base + end_in_vma, is_exec);
|
||||
}
|
||||
#else
|
||||
impl->Unmap(virtual_addr + start_in_vma, end_in_vma - start_in_vma, has_backing);
|
||||
#endif
|
||||
void AddressSpace::Unmap(VAddr virtual_addr, u64 size) {
|
||||
impl->Unmap(virtual_addr, size);
|
||||
}
|
||||
|
||||
void AddressSpace::Protect(VAddr virtual_addr, size_t size, MemoryPermission perms) {
|
||||
void AddressSpace::Protect(VAddr virtual_addr, u64 size, MemoryPermission perms) {
|
||||
const bool read = True(perms & MemoryPermission::Read);
|
||||
const bool write = True(perms & MemoryPermission::Write);
|
||||
const bool execute = True(perms & MemoryPermission::Execute);
|
||||
|
||||
@@ -39,7 +39,7 @@ public:
|
||||
[[nodiscard]] const u8* SystemManagedVirtualBase() const noexcept {
|
||||
return system_managed_base;
|
||||
}
|
||||
[[nodiscard]] size_t SystemManagedVirtualSize() const noexcept {
|
||||
[[nodiscard]] u64 SystemManagedVirtualSize() const noexcept {
|
||||
return system_managed_size;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ public:
|
||||
[[nodiscard]] const u8* SystemReservedVirtualBase() const noexcept {
|
||||
return system_reserved_base;
|
||||
}
|
||||
[[nodiscard]] size_t SystemReservedVirtualSize() const noexcept {
|
||||
[[nodiscard]] u64 SystemReservedVirtualSize() const noexcept {
|
||||
return system_reserved_size;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ public:
|
||||
[[nodiscard]] const u8* UserVirtualBase() const noexcept {
|
||||
return user_base;
|
||||
}
|
||||
[[nodiscard]] size_t UserVirtualSize() const noexcept {
|
||||
[[nodiscard]] u64 UserVirtualSize() const noexcept {
|
||||
return user_size;
|
||||
}
|
||||
|
||||
@@ -73,17 +73,16 @@ public:
|
||||
* If zero is provided the mapping is considered as private.
|
||||
* @return A pointer to the mapped memory.
|
||||
*/
|
||||
void* Map(VAddr virtual_addr, size_t size, u64 alignment = 0, PAddr phys_addr = -1,
|
||||
bool exec = false);
|
||||
void* Map(VAddr virtual_addr, u64 size, PAddr phys_addr = -1, bool exec = false);
|
||||
|
||||
/// Memory maps a specified file descriptor.
|
||||
void* MapFile(VAddr virtual_addr, size_t size, size_t offset, u32 prot, uintptr_t fd);
|
||||
void* MapFile(VAddr virtual_addr, u64 size, u64 offset, u32 prot, uintptr_t fd);
|
||||
|
||||
/// Unmaps specified virtual memory area.
|
||||
void Unmap(VAddr virtual_addr, size_t size, VAddr start_in_vma, VAddr end_in_vma,
|
||||
PAddr phys_base, bool is_exec, bool has_backing, bool readonly_file);
|
||||
void Unmap(VAddr virtual_addr, u64 size);
|
||||
|
||||
void Protect(VAddr virtual_addr, size_t size, MemoryPermission perms);
|
||||
/// Protects requested region.
|
||||
void Protect(VAddr virtual_addr, u64 size, MemoryPermission perms);
|
||||
|
||||
// Returns an interval set containing all usable regions.
|
||||
boost::icl::interval_set<VAddr> GetUsableRegions();
|
||||
@@ -93,11 +92,11 @@ private:
|
||||
std::unique_ptr<Impl> impl;
|
||||
u8* backing_base{};
|
||||
u8* system_managed_base{};
|
||||
size_t system_managed_size{};
|
||||
u64 system_managed_size{};
|
||||
u8* system_reserved_base{};
|
||||
size_t system_reserved_size{};
|
||||
u64 system_reserved_size{};
|
||||
u8* user_base{};
|
||||
size_t user_size{};
|
||||
u64 user_size{};
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <Zydis/Zydis.h>
|
||||
#include <xbyak/xbyak.h>
|
||||
#include <xbyak/xbyak_util.h>
|
||||
@@ -122,6 +123,30 @@ static void GenerateTcbAccess(void* /* address */, const ZydisDecodedOperand* op
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool FilterStackCheck(const ZydisDecodedOperand* operands) {
|
||||
const auto& dst_op = operands[0];
|
||||
const auto& src_op = operands[1];
|
||||
|
||||
// Some compilers emit stack checks by starting a function with
|
||||
// 'mov (64-bit register), fs:[0x28]', then checking with `xor (64-bit register), fs:[0x28]`
|
||||
return src_op.type == ZYDIS_OPERAND_TYPE_MEMORY && src_op.mem.segment == ZYDIS_REGISTER_FS &&
|
||||
src_op.mem.base == ZYDIS_REGISTER_NONE && src_op.mem.index == ZYDIS_REGISTER_NONE &&
|
||||
src_op.mem.disp.value == 0x28 && dst_op.reg.value >= ZYDIS_REGISTER_RAX &&
|
||||
dst_op.reg.value <= ZYDIS_REGISTER_R15;
|
||||
}
|
||||
|
||||
static void GenerateStackCheck(void* /* address */, const ZydisDecodedOperand* operands,
|
||||
Xbyak::CodeGenerator& c) {
|
||||
const auto dst = ZydisToXbyakRegisterOperand(operands[0]);
|
||||
c.xor_(dst, 0);
|
||||
}
|
||||
|
||||
static void GenerateStackCanary(void* /* address */, const ZydisDecodedOperand* operands,
|
||||
Xbyak::CodeGenerator& c) {
|
||||
const auto dst = ZydisToXbyakRegisterOperand(operands[0]);
|
||||
c.mov(dst, 0);
|
||||
}
|
||||
|
||||
static bool FilterNoSSE4a(const ZydisDecodedOperand*) {
|
||||
Cpu cpu;
|
||||
return !cpu.has(Cpu::tSSE4a);
|
||||
@@ -440,18 +465,26 @@ struct PatchInfo {
|
||||
bool trampoline;
|
||||
};
|
||||
|
||||
static const std::unordered_map<ZydisMnemonic, PatchInfo> Patches = {
|
||||
static const std::unordered_map<ZydisMnemonic, std::vector<PatchInfo>> Patches = {
|
||||
// SSE4a
|
||||
{ZYDIS_MNEMONIC_EXTRQ, {FilterNoSSE4a, GenerateEXTRQ, true}},
|
||||
{ZYDIS_MNEMONIC_INSERTQ, {FilterNoSSE4a, GenerateINSERTQ, true}},
|
||||
{ZYDIS_MNEMONIC_MOVNTSS, {FilterNoSSE4a, ReplaceMOVNTSS, false}},
|
||||
{ZYDIS_MNEMONIC_MOVNTSD, {FilterNoSSE4a, ReplaceMOVNTSD, false}},
|
||||
{ZYDIS_MNEMONIC_EXTRQ, {{FilterNoSSE4a, GenerateEXTRQ, true}}},
|
||||
{ZYDIS_MNEMONIC_INSERTQ, {{FilterNoSSE4a, GenerateINSERTQ, true}}},
|
||||
{ZYDIS_MNEMONIC_MOVNTSS, {{FilterNoSSE4a, ReplaceMOVNTSS, false}}},
|
||||
{ZYDIS_MNEMONIC_MOVNTSD, {{FilterNoSSE4a, ReplaceMOVNTSD, false}}},
|
||||
|
||||
#if !defined(__APPLE__)
|
||||
// FS segment patches
|
||||
// These first two patches are for accesses to the stack canary, fs:[0x28]
|
||||
{ZYDIS_MNEMONIC_XOR, {{FilterStackCheck, GenerateStackCheck, false}}},
|
||||
{ZYDIS_MNEMONIC_MOV,
|
||||
{{FilterStackCheck, GenerateStackCanary, false},
|
||||
#if defined(_WIN32)
|
||||
// Windows needs a trampoline.
|
||||
{ZYDIS_MNEMONIC_MOV, {FilterTcbAccess, GenerateTcbAccess, true}},
|
||||
#elif !defined(__APPLE__)
|
||||
{ZYDIS_MNEMONIC_MOV, {FilterTcbAccess, GenerateTcbAccess, false}},
|
||||
// Windows needs a trampoline for Tcb accesses.
|
||||
{FilterTcbAccess, GenerateTcbAccess, true}
|
||||
#else
|
||||
{FilterTcbAccess, GenerateTcbAccess, false}
|
||||
#endif
|
||||
}},
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -503,51 +536,53 @@ static std::pair<bool, u64> TryPatch(u8* code, PatchModule* module) {
|
||||
}
|
||||
|
||||
if (Patches.contains(instruction.mnemonic)) {
|
||||
const auto& patch_info = Patches.at(instruction.mnemonic);
|
||||
bool needs_trampoline = patch_info.trampoline;
|
||||
if (patch_info.filter(operands)) {
|
||||
auto& patch_gen = module->patch_gen;
|
||||
const auto& patches = Patches.at(instruction.mnemonic);
|
||||
for (const auto& patch_info : patches) {
|
||||
bool needs_trampoline = patch_info.trampoline;
|
||||
if (patch_info.filter(operands)) {
|
||||
auto& patch_gen = module->patch_gen;
|
||||
|
||||
if (needs_trampoline && instruction.length < 5) {
|
||||
// Trampoline is needed but instruction is too short to patch.
|
||||
// Return false and length to signal to AOT compilation that this instruction
|
||||
// should be skipped and handled at runtime.
|
||||
return std::make_pair(false, instruction.length);
|
||||
}
|
||||
if (needs_trampoline && instruction.length < 5) {
|
||||
// Trampoline is needed but instruction is too short to patch.
|
||||
// Return false and length to signal to AOT compilation that this instruction
|
||||
// should be skipped and handled at runtime.
|
||||
return std::make_pair(false, instruction.length);
|
||||
}
|
||||
|
||||
// Reset state and move to current code position.
|
||||
patch_gen.reset();
|
||||
patch_gen.setSize(code - patch_gen.getCode());
|
||||
// Reset state and move to current code position.
|
||||
patch_gen.reset();
|
||||
patch_gen.setSize(code - patch_gen.getCode());
|
||||
|
||||
if (needs_trampoline) {
|
||||
auto& trampoline_gen = module->trampoline_gen;
|
||||
const auto trampoline_ptr = trampoline_gen.getCurr();
|
||||
if (needs_trampoline) {
|
||||
auto& trampoline_gen = module->trampoline_gen;
|
||||
const auto trampoline_ptr = trampoline_gen.getCurr();
|
||||
|
||||
patch_info.generator(code, operands, trampoline_gen);
|
||||
patch_info.generator(code, operands, trampoline_gen);
|
||||
|
||||
// Return to the following instruction at the end of the trampoline.
|
||||
trampoline_gen.jmp(code + instruction.length);
|
||||
// Return to the following instruction at the end of the trampoline.
|
||||
trampoline_gen.jmp(code + instruction.length);
|
||||
|
||||
// Replace instruction with near jump to the trampoline.
|
||||
patch_gen.jmp(trampoline_ptr, Xbyak::CodeGenerator::LabelType::T_NEAR);
|
||||
} else {
|
||||
patch_info.generator(code, operands, patch_gen);
|
||||
}
|
||||
// Replace instruction with near jump to the trampoline.
|
||||
patch_gen.jmp(trampoline_ptr, Xbyak::CodeGenerator::LabelType::T_NEAR);
|
||||
} else {
|
||||
patch_info.generator(code, operands, patch_gen);
|
||||
}
|
||||
|
||||
const auto patch_size = patch_gen.getCurr() - code;
|
||||
if (patch_size > 0) {
|
||||
ASSERT_MSG(instruction.length >= patch_size,
|
||||
"Instruction {} with length {} is too short to replace at: {}",
|
||||
ZydisMnemonicGetString(instruction.mnemonic), instruction.length,
|
||||
fmt::ptr(code));
|
||||
const auto patch_size = patch_gen.getCurr() - code;
|
||||
if (patch_size > 0) {
|
||||
ASSERT_MSG(instruction.length >= patch_size,
|
||||
"Instruction {} with length {} is too short to replace at: {}",
|
||||
ZydisMnemonicGetString(instruction.mnemonic), instruction.length,
|
||||
fmt::ptr(code));
|
||||
|
||||
// Fill remaining space with nops.
|
||||
patch_gen.nop(instruction.length - patch_size);
|
||||
// Fill remaining space with nops.
|
||||
patch_gen.nop(instruction.length - patch_size);
|
||||
|
||||
module->patched.insert(code);
|
||||
LOG_DEBUG(Core, "Patched instruction '{}' at: {}",
|
||||
ZydisMnemonicGetString(instruction.mnemonic), fmt::ptr(code));
|
||||
return std::make_pair(true, instruction.length);
|
||||
module->patched.insert(code);
|
||||
LOG_DEBUG(Core, "Patched instruction '{}' at: {}",
|
||||
ZydisMnemonicGetString(instruction.mnemonic), fmt::ptr(code));
|
||||
return std::make_pair(true, instruction.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -755,11 +790,12 @@ static bool PatchesIllegalInstructionHandler(void* context) {
|
||||
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}", (u64)code_address);
|
||||
UNREACHABLE_MSG("ud2 at code address {:#x}", reinterpret_cast<u64>(code_address));
|
||||
}
|
||||
LOG_ERROR(Core, "Failed to patch address {:x} -- mnemonic: {}", (u64)code_address,
|
||||
ZYAN_SUCCESS(status) ? ZydisMnemonicGetString(instruction.mnemonic)
|
||||
: "Failed to decode");
|
||||
UNREACHABLE_MSG("Failed to patch address {:x} -- mnemonic: {}",
|
||||
reinterpret_cast<u64>(code_address),
|
||||
ZYAN_SUCCESS(status) ? ZydisMnemonicGetString(instruction.mnemonic)
|
||||
: "Failed to decode");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "layer.h"
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "common/singleton.h"
|
||||
#include "common/types.h"
|
||||
#include "core/debug_state.h"
|
||||
#include "core/emulator_state.h"
|
||||
#include "imgui/imgui_std.h"
|
||||
#include "imgui_internal.h"
|
||||
#include "options.h"
|
||||
@@ -273,14 +274,10 @@ void L::DrawAdvanced() {
|
||||
|
||||
void L::DrawSimple() {
|
||||
const float frameRate = DebugState.Framerate;
|
||||
if (Config::fpsColor()) {
|
||||
if (frameRate < 10) {
|
||||
PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); // Red
|
||||
} else if (frameRate >= 10 && frameRate < 20) {
|
||||
PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); // Orange
|
||||
} else {
|
||||
PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); // White
|
||||
}
|
||||
if (frameRate < 10) {
|
||||
PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); // Red
|
||||
} else if (frameRate >= 10 && frameRate < 20) {
|
||||
PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); // Orange
|
||||
} else {
|
||||
PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); // White
|
||||
}
|
||||
@@ -311,6 +308,7 @@ static void LoadSettings(const char* line) {
|
||||
|
||||
void L::SetupSettings() {
|
||||
frame_graph.is_open = true;
|
||||
show_simple_fps = Config::getShowFpsCounter();
|
||||
|
||||
using SettingLoader = void (*)(const char*);
|
||||
|
||||
@@ -460,12 +458,12 @@ void L::Draw() {
|
||||
}
|
||||
|
||||
void L::TextCentered(const std::string& text) {
|
||||
float window_width = ImGui::GetWindowSize().x;
|
||||
float text_width = ImGui::CalcTextSize(text.c_str()).x;
|
||||
float window_width = GetWindowSize().x;
|
||||
float text_width = CalcTextSize(text.c_str()).x;
|
||||
float text_indentation = (window_width - text_width) * 0.5f;
|
||||
|
||||
ImGui::SameLine(text_indentation);
|
||||
ImGui::Text("%s", text.c_str());
|
||||
SameLine(text_indentation);
|
||||
Text("%s", text.c_str());
|
||||
}
|
||||
|
||||
namespace Overlay {
|
||||
@@ -475,6 +473,11 @@ void ToggleSimpleFps() {
|
||||
visibility_toggled = true;
|
||||
}
|
||||
|
||||
void SetSimpleFps(bool enabled) {
|
||||
show_simple_fps = enabled;
|
||||
visibility_toggled = true;
|
||||
}
|
||||
|
||||
void ToggleQuitWindow() {
|
||||
show_quit_window = !show_quit_window;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@@ -9,18 +9,20 @@
|
||||
namespace Core::Devtools {
|
||||
|
||||
class Layer final : public ImGui::Layer {
|
||||
|
||||
static void DrawMenuBar();
|
||||
|
||||
static void DrawAdvanced();
|
||||
|
||||
static void DrawSimple();
|
||||
|
||||
public:
|
||||
static void SetupSettings();
|
||||
|
||||
void Draw() override;
|
||||
void TextCentered(const std::string& text);
|
||||
|
||||
// Must be inside a window
|
||||
static void DrawNullGpuNotice();
|
||||
|
||||
private:
|
||||
static void DrawMenuBar();
|
||||
static void DrawAdvanced();
|
||||
static void DrawSimple();
|
||||
|
||||
static void TextCentered(const std::string& text);
|
||||
};
|
||||
|
||||
} // namespace Core::Devtools
|
||||
@@ -28,6 +30,7 @@ public:
|
||||
namespace Overlay {
|
||||
|
||||
void ToggleSimpleFps();
|
||||
void SetSimpleFps(bool enabled);
|
||||
void ToggleQuitWindow();
|
||||
|
||||
} // namespace Overlay
|
||||
|
||||
95
src/core/devtools/layer_extra.cpp
Normal file
95
src/core/devtools/layer_extra.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "layer.h"
|
||||
|
||||
#include "imgui/imgui_std.h"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <numbers>
|
||||
|
||||
namespace Core::Devtools {
|
||||
|
||||
void Layer::DrawNullGpuNotice() {
|
||||
|
||||
auto* window = ImGui::GetCurrentWindow();
|
||||
if (window->SkipItems) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr std::string_view mainNotice = "Null GPU is enabled";
|
||||
constexpr std::string_view detailsNotice =
|
||||
"Disable the nullGpu config to show the game display";
|
||||
|
||||
auto displaySize = window->Size;
|
||||
|
||||
ImVec2 targetSize = displaySize * 0.7f;
|
||||
|
||||
float minFontSize = 1.0f;
|
||||
float maxFontSize = 200.0f;
|
||||
float optimalFontSize = minFontSize;
|
||||
|
||||
static auto lastSize = ImVec2(-1, -1);
|
||||
static float lastFontSize = -1.0f;
|
||||
|
||||
auto* font = ImGui::GetIO().Fonts->Fonts[IMGUI_FONT_TEXT_BIG];
|
||||
|
||||
if (lastSize != targetSize) {
|
||||
while (maxFontSize - minFontSize > 0.1f) {
|
||||
float testFontSize = (minFontSize + maxFontSize) / 2.0f;
|
||||
|
||||
ImVec2 textSize = font->CalcTextSizeA(testFontSize, FLT_MAX, 0.0f, &mainNotice.front(),
|
||||
&mainNotice.back() + 1);
|
||||
|
||||
if (textSize.x <= targetSize.x && textSize.y <= targetSize.y) {
|
||||
optimalFontSize = testFontSize;
|
||||
minFontSize = testFontSize;
|
||||
} else {
|
||||
maxFontSize = testFontSize;
|
||||
}
|
||||
}
|
||||
lastSize = targetSize;
|
||||
lastFontSize = optimalFontSize;
|
||||
} else {
|
||||
optimalFontSize = lastFontSize;
|
||||
}
|
||||
ImVec2 textSize = font->CalcTextSizeA(optimalFontSize, FLT_MAX, 0.0f, &mainNotice.front(),
|
||||
&mainNotice.back() + 1);
|
||||
|
||||
ImVec2 textPos = (displaySize - textSize) * 0.5f + window->Pos;
|
||||
|
||||
const float scale = optimalFontSize / font->FontSize;
|
||||
double timeAnim = -std::numbers::pi * ImGui::GetTime();
|
||||
int i = 0;
|
||||
for (auto ch : mainNotice) {
|
||||
double colorTime = sin(timeAnim + i * std::numbers::pi / 6.0) / 2.0 + 0.5;
|
||||
int color = (int)(200 * colorTime) + 55;
|
||||
|
||||
double posTime = sin(timeAnim + i * std::numbers::pi / 15.0) / 2.0 + 0.5;
|
||||
|
||||
auto pos = textPos;
|
||||
pos.y += 10.0 * (posTime < 0.5 ? std::pow(2.0, 20.0 * posTime - 10.0) / 2.0
|
||||
: (2.0 - std::pow(2.0, -20.0 * posTime + 10.0)) / 2.0);
|
||||
|
||||
window->DrawList->AddText(font, optimalFontSize, pos, IM_COL32(color, color, color, 255),
|
||||
&ch, &ch + 1);
|
||||
textPos.x += font->FindGlyph(ch)->AdvanceX * scale;
|
||||
i++;
|
||||
}
|
||||
|
||||
font = ImGui::GetIO().Fonts->Fonts[IMGUI_FONT_TEXT];
|
||||
|
||||
textPos.y += textSize.y + 1.0;
|
||||
|
||||
optimalFontSize *= 0.2;
|
||||
textSize = font->CalcTextSizeA(optimalFontSize, FLT_MAX, 0.0f, &detailsNotice.front(),
|
||||
&detailsNotice.back() + 1);
|
||||
textPos.x = window->Pos.x + (window->Size.x - textSize.x) * 0.5f;
|
||||
window->DrawList->AddText(font, optimalFontSize, textPos, IM_COL32(200, 200, 200, 255),
|
||||
&detailsNotice.front(), &detailsNotice.back() + 1);
|
||||
}
|
||||
|
||||
} // namespace Core::Devtools
|
||||
@@ -152,7 +152,7 @@ inline std::string RunDisassembler(const std::string& disassembler_cli, const T&
|
||||
}
|
||||
} else {
|
||||
cli.replace(pos, src_arg.size(), "\"" + bin_path.string() + "\"");
|
||||
Common::FS::IOFile file(bin_path, Common::FS::FileAccessMode::Write);
|
||||
Common::FS::IOFile file(bin_path, Common::FS::FileAccessMode::Create);
|
||||
file.Write(shader_code);
|
||||
file.Close();
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ void FrameDumpViewer::Draw() {
|
||||
const auto fname = fmt::format("{:%F %H-%M-%S} {}_{}_{}.bin", now_time,
|
||||
magic_enum::enum_name(selected_queue_type),
|
||||
selected_submit_num, selected_queue_num2);
|
||||
Common::FS::IOFile file(fname, Common::FS::FileAccessMode::Write);
|
||||
Common::FS::IOFile file(fname, Common::FS::FileAccessMode::Create);
|
||||
const auto& data = frame_dump->queues[selected_cmd].data;
|
||||
if (file.IsOpen()) {
|
||||
DebugState.ShowDebugMessage(fmt::format("Dumping cmd as {}", fname));
|
||||
|
||||
@@ -32,7 +32,7 @@ bool MemoryMapViewer::Iterator::DrawLine() {
|
||||
TableNextColumn();
|
||||
Text("%s", magic_enum::enum_name(m.prot).data());
|
||||
TableNextColumn();
|
||||
if (m.is_exec) {
|
||||
if (True(m.prot & MemoryProt::CpuExec)) {
|
||||
Text("X");
|
||||
}
|
||||
TableNextColumn();
|
||||
@@ -44,7 +44,7 @@ bool MemoryMapViewer::Iterator::DrawLine() {
|
||||
return false;
|
||||
}
|
||||
auto m = dmem.it->second;
|
||||
if (m.dma_type == DMAType::Free) {
|
||||
if (m.dma_type == PhysicalMemoryType::Free) {
|
||||
++dmem.it;
|
||||
return DrawLine();
|
||||
}
|
||||
@@ -56,7 +56,8 @@ bool MemoryMapViewer::Iterator::DrawLine() {
|
||||
auto type = static_cast<::Libraries::Kernel::MemoryTypes>(m.memory_type);
|
||||
Text("%s", magic_enum::enum_name(type).data());
|
||||
TableNextColumn();
|
||||
Text("%d", m.dma_type == DMAType::Pooled || m.dma_type == DMAType::Committed);
|
||||
Text("%d",
|
||||
m.dma_type == PhysicalMemoryType::Pooled || m.dma_type == PhysicalMemoryType::Committed);
|
||||
++dmem.it;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ class MemoryMapViewer {
|
||||
struct Iterator {
|
||||
bool is_vma;
|
||||
struct {
|
||||
MemoryManager::DMemMap::iterator it;
|
||||
MemoryManager::DMemMap::iterator end;
|
||||
MemoryManager::PhysMap::iterator it;
|
||||
MemoryManager::PhysMap::iterator end;
|
||||
} dmem;
|
||||
struct {
|
||||
MemoryManager::VMAMap::iterator it;
|
||||
|
||||
37
src/core/emulator_state.cpp
Normal file
37
src/core/emulator_state.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "emulator_state.h"
|
||||
|
||||
std::shared_ptr<EmulatorState> EmulatorState::s_instance = nullptr;
|
||||
std::mutex EmulatorState::s_mutex;
|
||||
|
||||
EmulatorState::EmulatorState() {}
|
||||
|
||||
EmulatorState::~EmulatorState() {}
|
||||
|
||||
std::shared_ptr<EmulatorState> EmulatorState::GetInstance() {
|
||||
std::lock_guard<std::mutex> lock(s_mutex);
|
||||
if (!s_instance)
|
||||
s_instance = std::make_shared<EmulatorState>();
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
void EmulatorState::SetInstance(std::shared_ptr<EmulatorState> instance) {
|
||||
std::lock_guard<std::mutex> lock(s_mutex);
|
||||
s_instance = instance;
|
||||
}
|
||||
|
||||
bool EmulatorState::IsGameRunning() const {
|
||||
return m_running;
|
||||
}
|
||||
void EmulatorState::SetGameRunning(bool running) {
|
||||
m_running = running;
|
||||
}
|
||||
|
||||
bool EmulatorState::IsAutoPatchesLoadEnabled() const {
|
||||
return m_load_patches_auto;
|
||||
}
|
||||
void EmulatorState::SetAutoPatchesLoadEnabled(bool enable) {
|
||||
m_load_patches_auto = enable;
|
||||
}
|
||||
29
src/core/emulator_state.h
Normal file
29
src/core/emulator_state.h
Normal file
@@ -0,0 +1,29 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
class EmulatorState {
|
||||
public:
|
||||
EmulatorState();
|
||||
~EmulatorState();
|
||||
|
||||
static std::shared_ptr<EmulatorState> GetInstance();
|
||||
static void SetInstance(std::shared_ptr<EmulatorState> instance);
|
||||
|
||||
bool IsGameRunning() const;
|
||||
void SetGameRunning(bool running);
|
||||
bool IsAutoPatchesLoadEnabled() const;
|
||||
void SetAutoPatchesLoadEnabled(bool enable);
|
||||
|
||||
private:
|
||||
static std::shared_ptr<EmulatorState> s_instance;
|
||||
static std::mutex s_mutex;
|
||||
|
||||
// state variables
|
||||
bool m_running = false;
|
||||
bool m_load_patches_auto = true;
|
||||
};
|
||||
114
src/core/file_format/npbind.cpp
Normal file
114
src/core/file_format/npbind.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include "npbind.h"
|
||||
|
||||
bool NPBindFile::Load(const std::string& path) {
|
||||
Clear(); // Clear any existing data
|
||||
|
||||
std::ifstream f(path, std::ios::binary | std::ios::ate);
|
||||
if (!f)
|
||||
return false;
|
||||
|
||||
std::streamsize sz = f.tellg();
|
||||
if (sz <= 0)
|
||||
return false;
|
||||
|
||||
f.seekg(0, std::ios::beg);
|
||||
std::vector<u8> buf(static_cast<size_t>(sz));
|
||||
if (!f.read(reinterpret_cast<char*>(buf.data()), sz))
|
||||
return false;
|
||||
|
||||
const u64 size = buf.size();
|
||||
if (size < sizeof(NpBindHeader))
|
||||
return false;
|
||||
|
||||
// Read header
|
||||
memcpy(&m_header, buf.data(), sizeof(NpBindHeader));
|
||||
if (m_header.magic != NPBIND_MAGIC)
|
||||
return false;
|
||||
|
||||
// offset start of bodies
|
||||
size_t offset = sizeof(NpBindHeader);
|
||||
|
||||
m_bodies.reserve(static_cast<size_t>(m_header.num_entries));
|
||||
|
||||
// For each body: read 4 TLV entries then skip padding (0x98 = 152 bytes)
|
||||
const u64 body_padding = 0x98; // 152
|
||||
|
||||
for (u64 bi = 0; bi < m_header.num_entries; ++bi) {
|
||||
// Ensure we have room for 4 entries' headers at least
|
||||
if (offset + 4 * 4 > size)
|
||||
return false; // 4 entries x (type+size)
|
||||
|
||||
NPBindBody body;
|
||||
|
||||
// helper lambda to read one entry
|
||||
auto read_entry = [&](NPBindEntryRaw& e) -> bool {
|
||||
if (offset + 4 > size)
|
||||
return false;
|
||||
|
||||
memcpy(&e.type, &buf[offset], 2);
|
||||
memcpy(&e.size, &buf[offset + 2], 2);
|
||||
offset += 4;
|
||||
|
||||
if (offset + e.size > size)
|
||||
return false;
|
||||
|
||||
e.data.assign(buf.begin() + offset, buf.begin() + offset + e.size);
|
||||
offset += e.size;
|
||||
return true;
|
||||
};
|
||||
|
||||
// read 4 entries in order
|
||||
if (!read_entry(body.npcommid))
|
||||
return false;
|
||||
if (!read_entry(body.trophy))
|
||||
return false;
|
||||
if (!read_entry(body.unk1))
|
||||
return false;
|
||||
if (!read_entry(body.unk2))
|
||||
return false;
|
||||
|
||||
// skip fixed padding after body if present (but don't overrun)
|
||||
if (offset + body_padding <= size) {
|
||||
offset += body_padding;
|
||||
} else {
|
||||
// If padding not fully present, allow file to end (some variants may omit)
|
||||
offset = size;
|
||||
}
|
||||
|
||||
m_bodies.push_back(std::move(body));
|
||||
}
|
||||
|
||||
// Read digest if available
|
||||
if (size >= 20) {
|
||||
// Digest is typically the last 20 bytes, independent of offset
|
||||
memcpy(m_digest, &buf[size - 20], 20);
|
||||
} else {
|
||||
memset(m_digest, 0, 20);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::string> NPBindFile::GetNpCommIds() const {
|
||||
std::vector<std::string> npcommids;
|
||||
npcommids.reserve(m_bodies.size());
|
||||
|
||||
for (const auto& body : m_bodies) {
|
||||
// Convert binary data to string directly
|
||||
if (!body.npcommid.data.empty()) {
|
||||
std::string raw_string(reinterpret_cast<const char*>(body.npcommid.data.data()),
|
||||
body.npcommid.data.size());
|
||||
npcommids.push_back(raw_string);
|
||||
}
|
||||
}
|
||||
|
||||
return npcommids;
|
||||
}
|
||||
87
src/core/file_format/npbind.h
Normal file
87
src/core/file_format/npbind.h
Normal file
@@ -0,0 +1,87 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "common/endian.h"
|
||||
#include "common/types.h"
|
||||
|
||||
#define NPBIND_MAGIC 0xD294A018u
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct NpBindHeader {
|
||||
u32_be magic;
|
||||
u32_be version;
|
||||
u64_be file_size;
|
||||
u64_be entry_size;
|
||||
u64_be num_entries;
|
||||
char padding[0x60]; // 96 bytes
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
struct NPBindEntryRaw {
|
||||
u16_be type;
|
||||
u16_be size; // includes internal padding
|
||||
std::vector<u8> data;
|
||||
};
|
||||
|
||||
struct NPBindBody {
|
||||
NPBindEntryRaw npcommid; // expected type 0x0010, size 12
|
||||
NPBindEntryRaw trophy; // expected type 0x0011, size 12
|
||||
NPBindEntryRaw unk1; // expected type 0x0012, size 176
|
||||
NPBindEntryRaw unk2; // expected type 0x0013, size 16
|
||||
// The 0x98 padding after these entries is skipped while parsing
|
||||
};
|
||||
|
||||
class NPBindFile {
|
||||
private:
|
||||
NpBindHeader m_header;
|
||||
std::vector<NPBindBody> m_bodies;
|
||||
u8 m_digest[20]; // zeroed if absent
|
||||
|
||||
public:
|
||||
NPBindFile() {
|
||||
memset(m_digest, 0, sizeof(m_digest));
|
||||
}
|
||||
|
||||
// Load from file
|
||||
bool Load(const std::string& path);
|
||||
|
||||
// Accessors
|
||||
const NpBindHeader& Header() const {
|
||||
return m_header;
|
||||
}
|
||||
const std::vector<NPBindBody>& Bodies() const {
|
||||
return m_bodies;
|
||||
}
|
||||
const u8* Digest() const {
|
||||
return m_digest;
|
||||
}
|
||||
|
||||
// Get npcommid data
|
||||
std::vector<std::string> GetNpCommIds() const;
|
||||
|
||||
// Get specific body
|
||||
const NPBindBody& GetBody(size_t index) const {
|
||||
return m_bodies.at(index);
|
||||
}
|
||||
|
||||
// Get number of bodies
|
||||
u64 BodyCount() const {
|
||||
return m_bodies.size();
|
||||
}
|
||||
|
||||
// Check if file was loaded successfully
|
||||
bool IsValid() const {
|
||||
return m_header.magic == NPBIND_MAGIC;
|
||||
}
|
||||
|
||||
// Clear all data
|
||||
void Clear() {
|
||||
m_header = NpBindHeader{};
|
||||
m_bodies.clear();
|
||||
memset(m_digest, 0, sizeof(m_digest));
|
||||
}
|
||||
};
|
||||
@@ -36,6 +36,7 @@ bool PSF::Open(const std::filesystem::path& filepath) {
|
||||
}
|
||||
|
||||
const u64 psfSize = file.GetSize();
|
||||
ASSERT_MSG(psfSize != 0, "SFO file at {} is empty!", filepath.string());
|
||||
std::vector<u8> psf(psfSize);
|
||||
file.Seek(0);
|
||||
file.Read(psf);
|
||||
@@ -99,7 +100,7 @@ bool PSF::Open(const std::vector<u8>& psf_buffer) {
|
||||
}
|
||||
|
||||
bool PSF::Encode(const std::filesystem::path& filepath) const {
|
||||
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Write);
|
||||
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Create);
|
||||
if (!file.IsOpen()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,44 +1,31 @@
|
||||
// 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/aes.h"
|
||||
#include "common/config.h"
|
||||
#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<u8, 16> trophyKey, std::span<u8, 16> NPcommID,
|
||||
std::span<u8, 16> efsmIv, std::span<u8> ciphertext,
|
||||
static void DecryptEFSM(std::span<const u8, 16> trophyKey, std::span<const u8, 16> NPcommID,
|
||||
std::span<const u8, 16> efsmIv, std::span<const u8> ciphertext,
|
||||
std::span<u8> decrypted) {
|
||||
// Step 1: Encrypt NPcommID
|
||||
std::array<u8, 16> trophyIv{};
|
||||
std::array<u8, 16> trpKey;
|
||||
// Convert spans to pointers for the aes functions
|
||||
aes::encrypt_cbc(NPcommID.data(), NPcommID.size(), trophyKey.data(), trophyKey.size(),
|
||||
trophyIv.data(), trpKey.data(), trpKey.size(), false);
|
||||
|
||||
// Step 2: Decrypt EFSM
|
||||
aes::decrypt_cbc(ciphertext.data(), ciphertext.size(), trpKey.data(), trpKey.size(),
|
||||
efsmIv.data(), decrypted.data(), decrypted.size(), nullptr);
|
||||
const_cast<u8*>(efsmIv.data()), decrypted.data(), decrypted.size(), nullptr);
|
||||
}
|
||||
|
||||
TRP::TRP() = default;
|
||||
TRP::~TRP() = default;
|
||||
|
||||
void TRP::GetNPcommID(const std::filesystem::path& trophyPath, int index) {
|
||||
std::filesystem::path trpPath = trophyPath / "sce_sys/npbind.dat";
|
||||
Common::FS::IOFile npbindFile(trpPath, Common::FS::FileAccessMode::Read);
|
||||
if (!npbindFile.IsOpen()) {
|
||||
LOG_CRITICAL(Common_Filesystem, "Failed to open npbind.dat file");
|
||||
return;
|
||||
}
|
||||
if (!npbindFile.Seek(0x84 + (index * 0x180))) {
|
||||
LOG_CRITICAL(Common_Filesystem, "Failed to seek to NPbind offset");
|
||||
return;
|
||||
}
|
||||
npbindFile.ReadRaw<u8>(np_comm_id.data(), 12);
|
||||
std::fill(np_comm_id.begin() + 12, np_comm_id.end(), 0); // fill with 0, we need 16 bytes.
|
||||
}
|
||||
|
||||
static void removePadding(std::vector<u8>& vec) {
|
||||
for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
|
||||
if (*it == '>') {
|
||||
@@ -59,95 +46,236 @@ 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/";
|
||||
if (!std::filesystem::exists(gameSysDir)) {
|
||||
LOG_CRITICAL(Common_Filesystem, "Game sce_sys directory doesn't exist");
|
||||
LOG_WARNING(Common_Filesystem, "Game trophy directory doesn't exist");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto user_key_str = Config::getTrophyKey();
|
||||
if (user_key_str.size() != 32) {
|
||||
const auto& user_key_vec =
|
||||
KeyManager::GetInstance()->GetAllKeys().TrophyKeySet.ReleaseTrophyKey;
|
||||
|
||||
if (user_key_vec.size() != 16) {
|
||||
LOG_INFO(Common_Filesystem, "Trophy decryption key is not specified");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::array<u8, 16> user_key{};
|
||||
hexToBytes(user_key_str.c_str(), user_key.data());
|
||||
std::copy(user_key_vec.begin(), user_key_vec.end(), user_key.begin());
|
||||
|
||||
for (int index = 0; const auto& it : std::filesystem::directory_iterator(gameSysDir)) {
|
||||
if (it.is_regular_file()) {
|
||||
GetNPcommID(trophyPath, index);
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
Common::FS::IOFile file(it.path(), Common::FS::FileAccessMode::Read);
|
||||
if (!file.IsOpen()) {
|
||||
LOG_CRITICAL(Common_Filesystem, "Unable to open trophy file for read");
|
||||
return false;
|
||||
LOG_ERROR(Common_Filesystem, "Unable to open trophy file: {}", it.path().string());
|
||||
success = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
TrpHeader header;
|
||||
file.Read(header);
|
||||
if (header.magic != 0xDCA24D00) {
|
||||
LOG_CRITICAL(Common_Filesystem, "Wrong trophy magic number");
|
||||
return false;
|
||||
if (!file.Read(header)) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to read TRP header from {}",
|
||||
it.path().string());
|
||||
success = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (header.magic != TRP_MAGIC) {
|
||||
LOG_ERROR(Common_Filesystem, "Wrong trophy magic number in {}", it.path().string());
|
||||
success = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
s64 seekPos = sizeof(TrpHeader);
|
||||
std::filesystem::path trpFilesPath(
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / titleId /
|
||||
"TrophyFiles" / it.path().stem());
|
||||
std::filesystem::create_directories(trpFilesPath / "Icons");
|
||||
std::filesystem::create_directory(trpFilesPath / "Xml");
|
||||
|
||||
// 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_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset");
|
||||
return false;
|
||||
LOG_ERROR(Common_Filesystem, "Failed to seek to TRP entry offset");
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
seekPos += (s64)header.entry_size;
|
||||
seekPos += static_cast<s64>(header.entry_size);
|
||||
|
||||
TrpEntry entry;
|
||||
file.Read(entry);
|
||||
std::string_view name(entry.entry_name);
|
||||
if (entry.flag == 0) { // PNG
|
||||
if (!file.Seek(entry.entry_pos)) {
|
||||
LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset");
|
||||
return false;
|
||||
}
|
||||
std::vector<u8> icon(entry.entry_len);
|
||||
file.Read(icon);
|
||||
Common::FS::IOFile::WriteBytes(trpFilesPath / "Icons" / name, icon);
|
||||
if (!file.Read(entry)) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to read TRP entry");
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
if (entry.flag == 3 && np_comm_id[0] == 'N' &&
|
||||
np_comm_id[1] == 'P') { // ESFM, encrypted.
|
||||
if (!file.Seek(entry.entry_pos)) {
|
||||
LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset");
|
||||
return false;
|
||||
|
||||
std::string_view name(entry.entry_name);
|
||||
|
||||
if (entry.flag == ENTRY_FLAG_PNG) {
|
||||
if (!ProcessPngEntry(file, entry, trpFilesPath, name)) {
|
||||
success = false;
|
||||
// Continue with next entry
|
||||
}
|
||||
file.Read(esfmIv); // get iv key.
|
||||
// Skip the first 16 bytes which are the iv key on every entry as we want a
|
||||
// clean xml file.
|
||||
std::vector<u8> ESFM(entry.entry_len - iv_len);
|
||||
std::vector<u8> XML(entry.entry_len - iv_len);
|
||||
if (!file.Seek(entry.entry_pos + iv_len)) {
|
||||
LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry + iv offset");
|
||||
return false;
|
||||
}
|
||||
file.Read(ESFM);
|
||||
DecryptEFSM(user_key, np_comm_id, esfmIv, ESFM, XML); // decrypt
|
||||
removePadding(XML);
|
||||
std::string xml_name = entry.entry_name;
|
||||
size_t pos = xml_name.find("ESFM");
|
||||
if (pos != std::string::npos)
|
||||
xml_name.replace(pos, xml_name.length(), "XML");
|
||||
std::filesystem::path path = trpFilesPath / "Xml" / xml_name;
|
||||
size_t written = Common::FS::IOFile::WriteBytes(path, XML);
|
||||
if (written != XML.size()) {
|
||||
LOG_CRITICAL(
|
||||
Common_Filesystem,
|
||||
"Trophy XML {} write failed, wanted to write {} bytes, wrote {}",
|
||||
fmt::UTF(path.u8string()), XML.size(), written);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
trpFileIndex++;
|
||||
}
|
||||
index++;
|
||||
} catch (const std::filesystem::filesystem_error& e) {
|
||||
LOG_CRITICAL(Common_Filesystem, "Filesystem error during trophy extraction: {}", e.what());
|
||||
return false;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_CRITICAL(Common_Filesystem, "Error during trophy extraction: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (success) {
|
||||
LOG_INFO(Common_Filesystem, "Successfully extracted {} trophy files for {}", trpFileIndex,
|
||||
titleId);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool TRP::ProcessPngEntry(Common::FS::IOFile& file, const TrpEntry& entry,
|
||||
const std::filesystem::path& outputPath, std::string_view name) {
|
||||
if (!file.Seek(entry.entry_pos)) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to seek to PNG entry offset");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<u8> icon(entry.entry_len);
|
||||
if (!file.Read(icon)) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to read PNG data");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto outputFile = outputPath / "Icons" / name;
|
||||
size_t written = Common::FS::IOFile::WriteBytes(outputFile, icon);
|
||||
if (written != icon.size()) {
|
||||
LOG_ERROR(Common_Filesystem, "PNG write failed: wanted {} bytes, wrote {}", icon.size(),
|
||||
written);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TRP::ProcessEncryptedXmlEntry(Common::FS::IOFile& file, const TrpEntry& entry,
|
||||
const std::filesystem::path& outputPath, std::string_view name,
|
||||
const std::array<u8, 16>& user_key,
|
||||
const std::string& npCommId) {
|
||||
constexpr size_t IV_LEN = 16;
|
||||
|
||||
if (!file.Seek(entry.entry_pos)) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to seek to encrypted XML entry offset");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::array<u8, IV_LEN> esfmIv;
|
||||
if (!file.Read(esfmIv)) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to read IV for encrypted XML");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.entry_len <= IV_LEN) {
|
||||
LOG_ERROR(Common_Filesystem, "Encrypted XML entry too small");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip to the encrypted data (after IV)
|
||||
if (!file.Seek(entry.entry_pos + IV_LEN)) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to seek to encrypted data");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<u8> ESFM(entry.entry_len - IV_LEN);
|
||||
std::vector<u8> XML(entry.entry_len - IV_LEN);
|
||||
|
||||
if (!file.Read(ESFM)) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to read encrypted XML data");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decrypt the data - FIX: Don't check return value since DecryptEFSM returns void
|
||||
std::span<const u8, 16> key_span(user_key);
|
||||
|
||||
// Convert npCommId string to span (pad or truncate to 16 bytes)
|
||||
std::array<u8, 16> npcommid_array{};
|
||||
size_t copy_len = std::min(npCommId.size(), npcommid_array.size());
|
||||
std::memcpy(npcommid_array.data(), npCommId.data(), copy_len);
|
||||
std::span<const u8, 16> npcommid_span(npcommid_array);
|
||||
|
||||
DecryptEFSM(key_span, npcommid_span, esfmIv, ESFM, XML);
|
||||
|
||||
// Remove padding
|
||||
removePadding(XML);
|
||||
|
||||
// Create output filename (replace ESFM with XML)
|
||||
std::string xml_name(entry.entry_name);
|
||||
size_t pos = xml_name.find("ESFM");
|
||||
if (pos != std::string::npos) {
|
||||
xml_name.replace(pos, 4, "XML");
|
||||
}
|
||||
|
||||
auto outputFile = outputPath / "Xml" / xml_name;
|
||||
size_t written = Common::FS::IOFile::WriteBytes(outputFile, XML);
|
||||
if (written != XML.size()) {
|
||||
LOG_ERROR(Common_Filesystem, "XML write failed: wanted {} bytes, wrote {}", XML.size(),
|
||||
written);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -8,6 +8,10 @@
|
||||
#include "common/io_file.h"
|
||||
#include "common/types.h"
|
||||
|
||||
static constexpr u32 TRP_MAGIC = 0xDCA24D00;
|
||||
static constexpr u8 ENTRY_FLAG_PNG = 0;
|
||||
static constexpr u8 ENTRY_FLAG_ENCRYPTED_XML = 3;
|
||||
|
||||
struct TrpHeader {
|
||||
u32_be magic; // (0xDCA24D00)
|
||||
u32_be version;
|
||||
@@ -33,9 +37,14 @@ public:
|
||||
TRP();
|
||||
~TRP();
|
||||
bool Extract(const std::filesystem::path& trophyPath, const std::string titleId);
|
||||
void GetNPcommID(const std::filesystem::path& trophyPath, int index);
|
||||
|
||||
private:
|
||||
bool ProcessPngEntry(Common::FS::IOFile& file, const TrpEntry& entry,
|
||||
const std::filesystem::path& outputPath, std::string_view name);
|
||||
bool ProcessEncryptedXmlEntry(Common::FS::IOFile& file, const TrpEntry& entry,
|
||||
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{};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "common/singleton.h"
|
||||
#include "core/file_sys/directories/base_directory.h"
|
||||
#include "core/file_sys/fs.h"
|
||||
#include "core/libraries/kernel/orbis_error.h"
|
||||
|
||||
namespace Core::Directories {
|
||||
|
||||
@@ -12,4 +13,35 @@ BaseDirectory::BaseDirectory() = default;
|
||||
|
||||
BaseDirectory::~BaseDirectory() = default;
|
||||
|
||||
s64 BaseDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) {
|
||||
s64 bytes_read = 0;
|
||||
for (s32 i = 0; i < iovcnt; i++) {
|
||||
const s64 result = read(iov[i].iov_base, iov[i].iov_len);
|
||||
if (result < 0) {
|
||||
return result;
|
||||
}
|
||||
bytes_read += result;
|
||||
}
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
s64 BaseDirectory::preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) {
|
||||
const u64 old_file_pointer = file_offset;
|
||||
file_offset = offset;
|
||||
const s64 bytes_read = readv(iov, iovcnt);
|
||||
file_offset = old_file_pointer;
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
s64 BaseDirectory::lseek(s64 offset, s32 whence) {
|
||||
|
||||
s64 file_offset_new = ((0 == whence) * offset) + ((1 == whence) * (file_offset + offset)) +
|
||||
((2 == whence) * (directory_size + offset));
|
||||
if (file_offset_new < 0)
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
|
||||
file_offset = file_offset_new;
|
||||
return file_offset;
|
||||
}
|
||||
|
||||
} // namespace Core::Directories
|
||||
@@ -19,6 +19,17 @@ struct OrbisKernelDirent;
|
||||
namespace Core::Directories {
|
||||
|
||||
class BaseDirectory {
|
||||
protected:
|
||||
static inline u32 fileno_pool{10};
|
||||
|
||||
static u32 next_fileno() {
|
||||
return ++fileno_pool;
|
||||
}
|
||||
|
||||
s64 file_offset = 0;
|
||||
u64 directory_size = 0;
|
||||
std::vector<u8> dirent_cache_bin{};
|
||||
|
||||
public:
|
||||
explicit BaseDirectory();
|
||||
|
||||
@@ -28,18 +39,23 @@ public:
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) {
|
||||
virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt);
|
||||
virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset);
|
||||
|
||||
virtual s64 write(const void* buf, u64 nbytes) {
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) {
|
||||
virtual s64 writev(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) {
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
virtual s64 lseek(s64 offset, s32 whence) {
|
||||
virtual s64 pwritev(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) {
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
virtual s64 lseek(s64 offset, s32 whence);
|
||||
|
||||
virtual s32 fstat(Libraries::Kernel::OrbisKernelStat* stat) {
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
@@ -15,111 +15,30 @@ std::shared_ptr<BaseDirectory> NormalDirectory::Create(std::string_view guest_di
|
||||
std::make_shared<NormalDirectory>(guest_directory));
|
||||
}
|
||||
|
||||
NormalDirectory::NormalDirectory(std::string_view guest_directory) {
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
|
||||
static s32 fileno = 0;
|
||||
mnt->IterateDirectory(guest_directory, [this](const auto& ent_path, const auto ent_is_file) {
|
||||
auto& dirent = dirents.emplace_back();
|
||||
dirent.d_fileno = ++fileno;
|
||||
dirent.d_type = (ent_is_file ? 8 : 4);
|
||||
strncpy(dirent.d_name, ent_path.filename().string().data(), MAX_LENGTH + 1);
|
||||
dirent.d_namlen = ent_path.filename().string().size();
|
||||
|
||||
// Calculate the appropriate length for this dirent.
|
||||
// Account for the null terminator in d_name too.
|
||||
dirent.d_reclen = Common::AlignUp(sizeof(dirent.d_fileno) + sizeof(dirent.d_type) +
|
||||
sizeof(dirent.d_namlen) + sizeof(dirent.d_reclen) +
|
||||
(dirent.d_namlen + 1),
|
||||
4);
|
||||
|
||||
directory_size += dirent.d_reclen;
|
||||
});
|
||||
|
||||
// The last entry of a normal directory should have d_reclen covering the remaining data.
|
||||
// Since the dirents of a folder are constant by this point, we can modify the last dirent
|
||||
// before creating the emulated file buffer.
|
||||
const u64 filler_count = Common::AlignUp(directory_size, DIRECTORY_ALIGNMENT) - directory_size;
|
||||
dirents[dirents.size() - 1].d_reclen += filler_count;
|
||||
|
||||
// Reading from standard directories seems to be based around file pointer logic.
|
||||
// Keep an internal buffer representing the raw contents of this file descriptor,
|
||||
// then emulate the various read functions with that.
|
||||
directory_size = Common::AlignUp(directory_size, DIRECTORY_ALIGNMENT);
|
||||
data_buffer.reserve(directory_size);
|
||||
memset(data_buffer.data(), 0, directory_size);
|
||||
|
||||
u8* current_dirent = data_buffer.data();
|
||||
for (const NormalDirectoryDirent& dirent : dirents) {
|
||||
NormalDirectoryDirent* dirent_to_write =
|
||||
reinterpret_cast<NormalDirectoryDirent*>(current_dirent);
|
||||
dirent_to_write->d_fileno = dirent.d_fileno;
|
||||
|
||||
// Using size d_namlen + 1 to account for null terminator.
|
||||
strncpy(dirent_to_write->d_name, dirent.d_name, dirent.d_namlen + 1);
|
||||
dirent_to_write->d_namlen = dirent.d_namlen;
|
||||
dirent_to_write->d_reclen = dirent.d_reclen;
|
||||
dirent_to_write->d_type = dirent.d_type;
|
||||
|
||||
current_dirent += dirent.d_reclen;
|
||||
}
|
||||
NormalDirectory::NormalDirectory(std::string_view guest_directory)
|
||||
: guest_directory(guest_directory) {
|
||||
RebuildDirents();
|
||||
}
|
||||
|
||||
s64 NormalDirectory::read(void* buf, u64 nbytes) {
|
||||
// Nothing left to read.
|
||||
if (file_offset >= directory_size) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
RebuildDirents();
|
||||
|
||||
const s64 remaining_data = directory_size - file_offset;
|
||||
const s64 bytes = nbytes > remaining_data ? remaining_data : nbytes;
|
||||
// data is contiguous. read goes like any regular file would: start at offset, read n bytes
|
||||
// output is always aligned up to 512 bytes with 0s
|
||||
// offset - classic. however at the end of read any unused (exceeding dirent buffer size) buffer
|
||||
// space will be left untouched
|
||||
// reclen always sums up to end of current alignment
|
||||
|
||||
std::memcpy(buf, data_buffer.data() + file_offset, bytes);
|
||||
s64 bytes_available = this->dirent_cache_bin.size() - file_offset;
|
||||
if (bytes_available <= 0)
|
||||
return 0;
|
||||
bytes_available = std::min<s64>(bytes_available, static_cast<s64>(nbytes));
|
||||
|
||||
file_offset += bytes;
|
||||
return bytes;
|
||||
}
|
||||
// data
|
||||
memcpy(buf, this->dirent_cache_bin.data() + file_offset, bytes_available);
|
||||
|
||||
s64 NormalDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) {
|
||||
s64 bytes_read = 0;
|
||||
for (s32 i = 0; i < iovcnt; i++) {
|
||||
const s64 result = read(iov[i].iov_base, iov[i].iov_len);
|
||||
if (result < 0) {
|
||||
return result;
|
||||
}
|
||||
bytes_read += result;
|
||||
}
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
s64 NormalDirectory::preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt,
|
||||
s64 offset) {
|
||||
const u64 old_file_pointer = file_offset;
|
||||
file_offset = offset;
|
||||
const s64 bytes_read = readv(iov, iovcnt);
|
||||
file_offset = old_file_pointer;
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
s64 NormalDirectory::lseek(s64 offset, s32 whence) {
|
||||
switch (whence) {
|
||||
case 0: {
|
||||
file_offset = offset;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
file_offset += offset;
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
file_offset = directory_size + offset;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
UNREACHABLE_MSG("lseek with unknown whence {}", whence);
|
||||
}
|
||||
}
|
||||
return file_offset;
|
||||
file_offset += bytes_available;
|
||||
return bytes_available;
|
||||
}
|
||||
|
||||
s32 NormalDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) {
|
||||
@@ -131,10 +50,110 @@ s32 NormalDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) {
|
||||
}
|
||||
|
||||
s64 NormalDirectory::getdents(void* buf, u64 nbytes, s64* basep) {
|
||||
if (basep != nullptr) {
|
||||
RebuildDirents();
|
||||
|
||||
if (basep)
|
||||
*basep = file_offset;
|
||||
|
||||
// same as others, we just don't need a variable
|
||||
if (file_offset >= directory_size)
|
||||
return 0;
|
||||
|
||||
s64 bytes_written = 0;
|
||||
s64 working_offset = file_offset;
|
||||
s64 dirent_buffer_offset = 0;
|
||||
s64 aligned_count = Common::AlignDown(nbytes, 512);
|
||||
|
||||
const u8* dirent_buffer = this->dirent_cache_bin.data();
|
||||
while (dirent_buffer_offset < this->dirent_cache_bin.size()) {
|
||||
const u8* normal_dirent_ptr = dirent_buffer + dirent_buffer_offset;
|
||||
const NormalDirectoryDirent* normal_dirent =
|
||||
reinterpret_cast<const NormalDirectoryDirent*>(normal_dirent_ptr);
|
||||
auto d_reclen = normal_dirent->d_reclen;
|
||||
|
||||
// bad, incomplete or OOB entry
|
||||
if (normal_dirent->d_namlen == 0)
|
||||
break;
|
||||
|
||||
if (working_offset >= d_reclen) {
|
||||
dirent_buffer_offset += d_reclen;
|
||||
working_offset -= d_reclen;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((bytes_written + d_reclen) > aligned_count)
|
||||
// dirents are aligned to the last full one
|
||||
break;
|
||||
|
||||
memcpy(static_cast<u8*>(buf) + bytes_written, normal_dirent_ptr + working_offset,
|
||||
d_reclen - working_offset);
|
||||
bytes_written += d_reclen - working_offset;
|
||||
dirent_buffer_offset += d_reclen;
|
||||
working_offset = 0;
|
||||
}
|
||||
// read behaves identically to getdents for normal directories.
|
||||
return read(buf, nbytes);
|
||||
|
||||
file_offset += bytes_written;
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
void NormalDirectory::RebuildDirents() {
|
||||
// regenerate only when target wants to read contents again
|
||||
// no reason for testing - read is always raw and dirents get processed on the go
|
||||
if (previous_file_offset == file_offset)
|
||||
return;
|
||||
previous_file_offset = file_offset;
|
||||
|
||||
constexpr u32 dirent_meta_size =
|
||||
sizeof(NormalDirectoryDirent::d_fileno) + sizeof(NormalDirectoryDirent::d_type) +
|
||||
sizeof(NormalDirectoryDirent::d_namlen) + sizeof(NormalDirectoryDirent::d_reclen);
|
||||
|
||||
u64 next_ceiling = 0;
|
||||
u64 dirent_offset = 0;
|
||||
u64 last_reclen_offset = 4;
|
||||
dirent_cache_bin.clear();
|
||||
dirent_cache_bin.reserve(512);
|
||||
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
|
||||
mnt->IterateDirectory(
|
||||
guest_directory, [this, &next_ceiling, &dirent_offset, &last_reclen_offset](
|
||||
const std::filesystem::path& ent_path, const bool ent_is_file) {
|
||||
NormalDirectoryDirent tmp{};
|
||||
std::string leaf(ent_path.filename().string());
|
||||
|
||||
// prepare dirent
|
||||
tmp.d_fileno = BaseDirectory::next_fileno();
|
||||
tmp.d_namlen = leaf.size();
|
||||
strncpy(tmp.d_name, leaf.data(), tmp.d_namlen + 1);
|
||||
tmp.d_type = (ent_is_file ? 0100000 : 0040000) >> 12;
|
||||
tmp.d_reclen = Common::AlignUp(dirent_meta_size + tmp.d_namlen + 1, 4);
|
||||
|
||||
// next element may break 512 byte alignment
|
||||
if (tmp.d_reclen + dirent_offset > next_ceiling) {
|
||||
// align previous dirent's size to the current ceiling
|
||||
*reinterpret_cast<u16*>(static_cast<u8*>(dirent_cache_bin.data()) +
|
||||
last_reclen_offset) += next_ceiling - dirent_offset;
|
||||
// set writing pointer to the aligned start position (current ceiling)
|
||||
dirent_offset = next_ceiling;
|
||||
// move the ceiling up and zero-out the buffer
|
||||
next_ceiling += 512;
|
||||
dirent_cache_bin.resize(next_ceiling);
|
||||
std::fill(dirent_cache_bin.begin() + dirent_offset,
|
||||
dirent_cache_bin.begin() + next_ceiling, 0);
|
||||
}
|
||||
|
||||
// current dirent's reclen position
|
||||
last_reclen_offset = dirent_offset + 4;
|
||||
memcpy(dirent_cache_bin.data() + dirent_offset, &tmp, tmp.d_reclen);
|
||||
dirent_offset += tmp.d_reclen;
|
||||
});
|
||||
|
||||
// last reclen, as before
|
||||
*reinterpret_cast<u16*>(static_cast<u8*>(dirent_cache_bin.data()) + last_reclen_offset) +=
|
||||
next_ceiling - dirent_offset;
|
||||
|
||||
// i have no idea if this is the case, but lseek returns size aligned to 512
|
||||
directory_size = next_ceiling;
|
||||
}
|
||||
|
||||
} // namespace Core::Directories
|
||||
@@ -19,27 +19,23 @@ public:
|
||||
~NormalDirectory() override = default;
|
||||
|
||||
virtual s64 read(void* buf, u64 nbytes) override;
|
||||
virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) override;
|
||||
virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt,
|
||||
s64 offset) override;
|
||||
virtual s64 lseek(s64 offset, s32 whence) override;
|
||||
virtual s32 fstat(Libraries::Kernel::OrbisKernelStat* stat) override;
|
||||
virtual s64 getdents(void* buf, u64 nbytes, s64* basep) override;
|
||||
|
||||
private:
|
||||
static constexpr s32 MAX_LENGTH = 255;
|
||||
static constexpr s64 DIRECTORY_ALIGNMENT = 0x200;
|
||||
#pragma pack(push, 1)
|
||||
struct NormalDirectoryDirent {
|
||||
u32 d_fileno;
|
||||
u16 d_reclen;
|
||||
u8 d_type;
|
||||
u8 d_namlen;
|
||||
char d_name[MAX_LENGTH + 1];
|
||||
char d_name[256];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
u64 directory_size = 0;
|
||||
s64 file_offset = 0;
|
||||
std::vector<u8> data_buffer;
|
||||
std::vector<NormalDirectoryDirent> dirents;
|
||||
std::string_view guest_directory{};
|
||||
s64 previous_file_offset = -1;
|
||||
|
||||
void RebuildDirents(void);
|
||||
};
|
||||
} // namespace Core::Directories
|
||||
|
||||
@@ -15,77 +15,49 @@ std::shared_ptr<BaseDirectory> PfsDirectory::Create(std::string_view guest_direc
|
||||
}
|
||||
|
||||
PfsDirectory::PfsDirectory(std::string_view guest_directory) {
|
||||
constexpr u32 dirent_meta_size =
|
||||
sizeof(PfsDirectoryDirent::d_fileno) + sizeof(PfsDirectoryDirent::d_type) +
|
||||
sizeof(PfsDirectoryDirent::d_namlen) + sizeof(PfsDirectoryDirent::d_reclen);
|
||||
|
||||
dirent_cache_bin.reserve(512);
|
||||
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
|
||||
static s32 fileno = 0;
|
||||
mnt->IterateDirectory(guest_directory, [this](const auto& ent_path, const auto ent_is_file) {
|
||||
auto& dirent = dirents.emplace_back();
|
||||
dirent.d_fileno = ++fileno;
|
||||
dirent.d_type = (ent_is_file ? 8 : 4);
|
||||
strncpy(dirent.d_name, ent_path.filename().string().data(), MAX_LENGTH + 1);
|
||||
dirent.d_namlen = ent_path.filename().string().size();
|
||||
mnt->IterateDirectory(
|
||||
guest_directory, [this](const std::filesystem::path& ent_path, const bool ent_is_file) {
|
||||
PfsDirectoryDirent tmp{};
|
||||
std::string leaf(ent_path.filename().string());
|
||||
|
||||
// Calculate the appropriate length for this dirent.
|
||||
// Account for the null terminator in d_name too.
|
||||
dirent.d_reclen = Common::AlignUp(sizeof(dirent.d_fileno) + sizeof(dirent.d_type) +
|
||||
sizeof(dirent.d_namlen) + sizeof(dirent.d_reclen) +
|
||||
(dirent.d_namlen + 1),
|
||||
8);
|
||||
tmp.d_fileno = BaseDirectory::next_fileno();
|
||||
tmp.d_namlen = leaf.size();
|
||||
strncpy(tmp.d_name, leaf.data(), tmp.d_namlen + 1);
|
||||
tmp.d_type = ent_is_file ? 2 : 4;
|
||||
tmp.d_reclen = Common::AlignUp(dirent_meta_size + tmp.d_namlen + 1, 8);
|
||||
auto dirent_ptr = reinterpret_cast<const u8*>(&tmp);
|
||||
|
||||
// To handle some obscure dirents_index behavior,
|
||||
// keep track of the "actual" length of this directory.
|
||||
directory_content_size += dirent.d_reclen;
|
||||
});
|
||||
dirent_cache_bin.insert(dirent_cache_bin.end(), dirent_ptr, dirent_ptr + tmp.d_reclen);
|
||||
});
|
||||
|
||||
directory_size = Common::AlignUp(directory_content_size, DIRECTORY_ALIGNMENT);
|
||||
directory_size = Common::AlignUp(dirent_cache_bin.size(), 0x10000);
|
||||
}
|
||||
|
||||
s64 PfsDirectory::read(void* buf, u64 nbytes) {
|
||||
if (dirents_index >= dirents.size()) {
|
||||
if (dirents_index < directory_content_size) {
|
||||
// We need to find the appropriate dirents_index to start from.
|
||||
s64 data_to_skip = dirents_index;
|
||||
u64 corrected_index = 0;
|
||||
while (data_to_skip > 0) {
|
||||
const auto dirent = dirents[corrected_index++];
|
||||
data_to_skip -= dirent.d_reclen;
|
||||
}
|
||||
dirents_index = corrected_index;
|
||||
} else {
|
||||
// Nothing left to read.
|
||||
return ORBIS_OK;
|
||||
}
|
||||
s64 bytes_available = this->dirent_cache_bin.size() - file_offset;
|
||||
if (bytes_available <= 0)
|
||||
return 0;
|
||||
|
||||
bytes_available = std::min<s64>(bytes_available, static_cast<s64>(nbytes));
|
||||
memcpy(buf, this->dirent_cache_bin.data() + file_offset, bytes_available);
|
||||
|
||||
s64 to_fill =
|
||||
(std::min<s64>(directory_size, static_cast<s64>(nbytes))) - bytes_available - file_offset;
|
||||
if (to_fill < 0) {
|
||||
LOG_ERROR(Kernel_Fs, "Dirent may have leaked {} bytes", -to_fill);
|
||||
return -to_fill + bytes_available;
|
||||
}
|
||||
|
||||
s64 bytes_remaining = nbytes > directory_size ? directory_size : nbytes;
|
||||
// read on PfsDirectories will always return the maximum possible value.
|
||||
const u64 bytes_written = bytes_remaining;
|
||||
memset(buf, 0, bytes_remaining);
|
||||
|
||||
char* current_dirent = static_cast<char*>(buf);
|
||||
PfsDirectoryDirent dirent = dirents[dirents_index];
|
||||
while (bytes_remaining > dirent.d_reclen) {
|
||||
PfsDirectoryDirent* dirent_to_write = reinterpret_cast<PfsDirectoryDirent*>(current_dirent);
|
||||
dirent_to_write->d_fileno = dirent.d_fileno;
|
||||
|
||||
// Using size d_namlen + 1 to account for null terminator.
|
||||
strncpy(dirent_to_write->d_name, dirent.d_name, dirent.d_namlen + 1);
|
||||
dirent_to_write->d_namlen = dirent.d_namlen;
|
||||
dirent_to_write->d_reclen = dirent.d_reclen;
|
||||
dirent_to_write->d_type = dirent.d_type;
|
||||
|
||||
current_dirent += dirent.d_reclen;
|
||||
bytes_remaining -= dirent.d_reclen;
|
||||
|
||||
if (dirents_index == dirents.size() - 1) {
|
||||
// Currently at the last dirent, so break out of the loop.
|
||||
dirents_index++;
|
||||
break;
|
||||
}
|
||||
dirent = dirents[++dirents_index];
|
||||
}
|
||||
|
||||
return bytes_written;
|
||||
memset(static_cast<u8*>(buf) + bytes_available, 0, to_fill);
|
||||
file_offset += to_fill + bytes_available;
|
||||
return to_fill + bytes_available;
|
||||
}
|
||||
|
||||
s64 PfsDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) {
|
||||
@@ -101,62 +73,13 @@ s64 PfsDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovc
|
||||
}
|
||||
|
||||
s64 PfsDirectory::preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) {
|
||||
const u64 old_dirent_index = dirents_index;
|
||||
dirents_index = 0;
|
||||
s64 data_to_skip = offset;
|
||||
// If offset is part-way through one dirent, that dirent is skipped.
|
||||
while (data_to_skip > 0) {
|
||||
const auto dirent = dirents[dirents_index++];
|
||||
data_to_skip -= dirent.d_reclen;
|
||||
if (dirents_index == dirents.size()) {
|
||||
// We've reached the end of the dirents, nothing more can be skipped.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const u64 old_file_pointer = file_offset;
|
||||
file_offset = offset;
|
||||
const s64 bytes_read = readv(iov, iovcnt);
|
||||
dirents_index = old_dirent_index;
|
||||
file_offset = old_file_pointer;
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
s64 PfsDirectory::lseek(s64 offset, s32 whence) {
|
||||
switch (whence) {
|
||||
// Seek start
|
||||
case 0: {
|
||||
dirents_index = 0;
|
||||
}
|
||||
case 1: {
|
||||
// There aren't any dirents left to pass through.
|
||||
if (dirents_index >= dirents.size()) {
|
||||
dirents_index = dirents_index + offset;
|
||||
break;
|
||||
}
|
||||
s64 data_to_skip = offset;
|
||||
while (data_to_skip > 0) {
|
||||
const auto dirent = dirents[dirents_index++];
|
||||
data_to_skip -= dirent.d_reclen;
|
||||
if (dirents_index == dirents.size()) {
|
||||
// We've passed through all file dirents.
|
||||
// Set dirents_index to directory_size + remaining_offset instead.
|
||||
dirents_index = directory_content_size + data_to_skip;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// Seems like real hardware gives up on tracking dirents_index if you go this route.
|
||||
dirents_index = directory_size + offset;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
UNREACHABLE_MSG("lseek with unknown whence {}", whence);
|
||||
}
|
||||
}
|
||||
|
||||
return dirents_index;
|
||||
}
|
||||
|
||||
s32 PfsDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) {
|
||||
stat->st_mode = 0000777u | 0040000u;
|
||||
stat->st_size = directory_size;
|
||||
@@ -166,55 +89,58 @@ s32 PfsDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) {
|
||||
}
|
||||
|
||||
s64 PfsDirectory::getdents(void* buf, u64 nbytes, s64* basep) {
|
||||
// basep is set at the start of the function.
|
||||
if (basep != nullptr) {
|
||||
*basep = dirents_index;
|
||||
}
|
||||
if (basep)
|
||||
*basep = file_offset;
|
||||
|
||||
if (dirents_index >= dirents.size()) {
|
||||
if (dirents_index < directory_content_size) {
|
||||
// We need to find the appropriate dirents_index to start from.
|
||||
s64 data_to_skip = dirents_index;
|
||||
u64 corrected_index = 0;
|
||||
while (data_to_skip > 0) {
|
||||
const auto dirent = dirents[corrected_index++];
|
||||
data_to_skip -= dirent.d_reclen;
|
||||
}
|
||||
dirents_index = corrected_index;
|
||||
} else {
|
||||
// Nothing left to read.
|
||||
return ORBIS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
s64 bytes_remaining = nbytes > directory_size ? directory_size : nbytes;
|
||||
memset(buf, 0, bytes_remaining);
|
||||
// same as others, we just don't need a variable
|
||||
if (file_offset >= directory_size)
|
||||
return 0;
|
||||
|
||||
u64 bytes_written = 0;
|
||||
char* current_dirent = static_cast<char*>(buf);
|
||||
// getdents has to convert pfs dirents to normal dirents
|
||||
PfsDirectoryDirent dirent = dirents[dirents_index];
|
||||
while (bytes_remaining > dirent.d_reclen) {
|
||||
NormalDirectoryDirent* dirent_to_write =
|
||||
reinterpret_cast<NormalDirectoryDirent*>(current_dirent);
|
||||
dirent_to_write->d_fileno = dirent.d_fileno;
|
||||
strncpy(dirent_to_write->d_name, dirent.d_name, dirent.d_namlen + 1);
|
||||
dirent_to_write->d_namlen = dirent.d_namlen;
|
||||
dirent_to_write->d_reclen = dirent.d_reclen;
|
||||
dirent_to_write->d_type = dirent.d_type;
|
||||
u64 starting_offset = 0;
|
||||
u64 buffer_position = 0;
|
||||
while (buffer_position < this->dirent_cache_bin.size()) {
|
||||
const PfsDirectoryDirent* pfs_dirent =
|
||||
reinterpret_cast<PfsDirectoryDirent*>(this->dirent_cache_bin.data() + buffer_position);
|
||||
|
||||
current_dirent += dirent.d_reclen;
|
||||
bytes_remaining -= dirent.d_reclen;
|
||||
bytes_written += dirent.d_reclen;
|
||||
|
||||
if (dirents_index == dirents.size() - 1) {
|
||||
// Currently at the last dirent, so set dirents_index appropriately and break.
|
||||
dirents_index = directory_size;
|
||||
// bad, incomplete or OOB entry
|
||||
if (pfs_dirent->d_namlen == 0)
|
||||
break;
|
||||
|
||||
if (starting_offset < file_offset) {
|
||||
// reading starts from the nearest full dirent
|
||||
starting_offset += pfs_dirent->d_reclen;
|
||||
buffer_position = bytes_written + starting_offset;
|
||||
continue;
|
||||
}
|
||||
dirent = dirents[++dirents_index];
|
||||
|
||||
if ((bytes_written + pfs_dirent->d_reclen) > nbytes)
|
||||
// dirents are aligned to the last full one
|
||||
break;
|
||||
|
||||
// if this dirent breaks alignment, skip
|
||||
// dirents are count-aligned here, excess data is simply not written
|
||||
// if (Common::AlignUp(buffer_position, count) !=
|
||||
// Common::AlignUp(buffer_position + pfs_dirent->d_reclen, count))
|
||||
// break;
|
||||
|
||||
// reclen for both is the same despite difference in var sizes, extra 0s are padded after
|
||||
// the name
|
||||
NormalDirectoryDirent normal_dirent{};
|
||||
normal_dirent.d_fileno = pfs_dirent->d_fileno;
|
||||
normal_dirent.d_reclen = pfs_dirent->d_reclen;
|
||||
normal_dirent.d_type = (pfs_dirent->d_type == 2) ? 8 : 4;
|
||||
normal_dirent.d_namlen = pfs_dirent->d_namlen;
|
||||
memcpy(normal_dirent.d_name, pfs_dirent->d_name, pfs_dirent->d_namlen);
|
||||
|
||||
memcpy(static_cast<u8*>(buf) + bytes_written, &normal_dirent, normal_dirent.d_reclen);
|
||||
bytes_written += normal_dirent.d_reclen;
|
||||
buffer_position = bytes_written + starting_offset;
|
||||
}
|
||||
|
||||
file_offset = (buffer_position >= this->dirent_cache_bin.size())
|
||||
? directory_size
|
||||
: (file_offset + bytes_written);
|
||||
return bytes_written;
|
||||
}
|
||||
} // namespace Core::Directories
|
||||
@@ -22,32 +22,28 @@ public:
|
||||
virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) override;
|
||||
virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt,
|
||||
s64 offset) override;
|
||||
virtual s64 lseek(s64 offset, s32 whence) override;
|
||||
virtual s32 fstat(Libraries::Kernel::OrbisKernelStat* stat) override;
|
||||
virtual s64 getdents(void* buf, u64 nbytes, s64* basep) override;
|
||||
|
||||
private:
|
||||
static constexpr s32 MAX_LENGTH = 255;
|
||||
static constexpr s32 DIRECTORY_ALIGNMENT = 0x10000;
|
||||
#pragma pack(push, 1)
|
||||
struct PfsDirectoryDirent {
|
||||
u32 d_fileno;
|
||||
u32 d_type;
|
||||
u32 d_namlen;
|
||||
u32 d_reclen;
|
||||
char d_name[MAX_LENGTH + 1];
|
||||
char d_name[256];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct NormalDirectoryDirent {
|
||||
u32 d_fileno;
|
||||
u16 d_reclen;
|
||||
u8 d_type;
|
||||
u8 d_namlen;
|
||||
char d_name[MAX_LENGTH + 1];
|
||||
char d_name[256];
|
||||
};
|
||||
|
||||
u64 directory_size = 0;
|
||||
u64 directory_content_size = 0;
|
||||
s64 dirents_index = 0;
|
||||
std::vector<PfsDirectoryDirent> dirents;
|
||||
#pragma pack(pop)
|
||||
};
|
||||
} // namespace Core::Directories
|
||||
|
||||
@@ -52,6 +52,9 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea
|
||||
pos = corrected_path.find("//", pos + 1);
|
||||
}
|
||||
|
||||
if (path.length() > 255)
|
||||
return "";
|
||||
|
||||
const MntPair* mount = GetMount(corrected_path);
|
||||
if (!mount) {
|
||||
return "";
|
||||
@@ -229,6 +232,9 @@ File* HandleTable::GetSocket(int d) {
|
||||
return nullptr;
|
||||
}
|
||||
auto file = m_files.at(d);
|
||||
if (!file) {
|
||||
return nullptr;
|
||||
}
|
||||
if (file->type != Core::FileSys::FileType::Socket) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -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 "ipc.h"
|
||||
@@ -14,9 +14,11 @@
|
||||
#include "common/types.h"
|
||||
#include "core/debug_state.h"
|
||||
#include "core/debugger.h"
|
||||
#include "core/emulator_state.h"
|
||||
#include "core/libraries/audio/audioout.h"
|
||||
#include "input/input_handler.h"
|
||||
#include "sdl_window.h"
|
||||
#include "src/core/libraries/usbd/usbd.h"
|
||||
#include "video_core/renderer_vulkan/vk_presenter.h"
|
||||
|
||||
extern std::unique_ptr<Vulkan::Presenter> presenter;
|
||||
@@ -70,6 +72,8 @@ void IPC::Init() {
|
||||
return;
|
||||
}
|
||||
|
||||
EmulatorState::GetInstance()->SetAutoPatchesLoadEnabled(false);
|
||||
|
||||
input_thread = std::jthread([this] {
|
||||
Common::SetCurrentThreadName("IPC Read thread");
|
||||
this->InputLoop();
|
||||
@@ -167,6 +171,37 @@ void IPC::InputLoop() {
|
||||
presenter->GetFsrSettingsRef().rcas_attenuation =
|
||||
static_cast<float>(value / 1000.0f);
|
||||
}
|
||||
} else if (cmd == "USB_LOAD_FIGURE") {
|
||||
const auto ref = Libraries::Usbd::usb_backend->GetImplRef();
|
||||
if (ref) {
|
||||
ref->LoadFigure(next_str(), next_u64(), next_u64());
|
||||
}
|
||||
} else if (cmd == "USB_REMOVE_FIGURE") {
|
||||
const auto ref = Libraries::Usbd::usb_backend->GetImplRef();
|
||||
if (ref) {
|
||||
ref->RemoveFigure(next_u64(), next_u64(), next_u64() != 0);
|
||||
}
|
||||
} else if (cmd == "USB_MOVE_FIGURE") {
|
||||
const auto ref = Libraries::Usbd::usb_backend->GetImplRef();
|
||||
if (ref) {
|
||||
const u8 new_pad = next_u64();
|
||||
const u8 new_index = next_u64();
|
||||
const u8 old_pad = next_u64();
|
||||
const u8 old_index = next_u64();
|
||||
ref->MoveFigure(new_pad, new_index, old_pad, old_index);
|
||||
}
|
||||
} else if (cmd == "USB_TEMP_REMOVE_FIGURE") {
|
||||
const auto ref = Libraries::Usbd::usb_backend->GetImplRef();
|
||||
if (ref) {
|
||||
const u8 index = next_u64();
|
||||
ref->TempRemoveFigure(index);
|
||||
}
|
||||
} else if (cmd == "USB_CANCEL_REMOVE_FIGURE") {
|
||||
const auto ref = Libraries::Usbd::usb_backend->GetImplRef();
|
||||
if (ref) {
|
||||
const u8 index = next_u64();
|
||||
ref->CancelRemoveFigure(index);
|
||||
}
|
||||
} else if (cmd == "RELOAD_INPUTS") {
|
||||
std::string config = next_str();
|
||||
Input::ParseInputConfig(config);
|
||||
|
||||
@@ -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"
|
||||
@@ -34,7 +34,7 @@ u32 GetChannelMask(u32 num_channels) {
|
||||
case 8:
|
||||
return ORBIS_AJM_CHANNELMASK_7POINT1;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
UNREACHABLE_MSG("Unexpected number of channels: {}", num_channels);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,9 +144,8 @@ int PS4_SYSV_ABI sceAjmInitialize(s64 reserved, u32* p_context_id) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAjmInstanceCodecType() {
|
||||
LOG_ERROR(Lib_Ajm, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
AjmCodecType PS4_SYSV_ABI sceAjmInstanceCodecType(u32 instance_id) {
|
||||
return static_cast<AjmCodecType>((instance_id >> 14) & 0x1F);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAjmInstanceCreate(u32 context_id, AjmCodecType codec_type,
|
||||
|
||||
@@ -82,8 +82,6 @@ enum class AjmStatisticsFlags : u64 {
|
||||
DECLARE_ENUM_FLAG_OPERATORS(AjmStatisticsFlags)
|
||||
|
||||
union AjmStatisticsJobFlags {
|
||||
AjmStatisticsJobFlags(AjmJobFlags job_flags) : raw(job_flags.raw) {}
|
||||
|
||||
u64 raw;
|
||||
struct {
|
||||
u64 version : 3;
|
||||
@@ -133,7 +131,7 @@ struct AjmSidebandGaplessDecode {
|
||||
|
||||
struct AjmSidebandResampleParameters {
|
||||
float ratio;
|
||||
uint32_t flags;
|
||||
u32 flags;
|
||||
};
|
||||
|
||||
struct AjmDecAt9InitializeParameters {
|
||||
@@ -217,7 +215,7 @@ int PS4_SYSV_ABI sceAjmDecMp3ParseFrame(const u8* stream, u32 stream_size, int p
|
||||
AjmDecMp3ParseFrame* frame);
|
||||
int PS4_SYSV_ABI sceAjmFinalize();
|
||||
int PS4_SYSV_ABI sceAjmInitialize(s64 reserved, u32* out_context);
|
||||
int PS4_SYSV_ABI sceAjmInstanceCodecType();
|
||||
AjmCodecType PS4_SYSV_ABI sceAjmInstanceCodecType(u32 instance_id);
|
||||
int PS4_SYSV_ABI sceAjmInstanceCreate(u32 context, AjmCodecType codec_type, AjmInstanceFlags flags,
|
||||
u32* instance);
|
||||
int PS4_SYSV_ABI sceAjmInstanceDestroy(u32 context, u32 instance);
|
||||
|
||||
218
src/core/libraries/ajm/ajm_aac.cpp
Normal file
218
src/core/libraries/ajm/ajm_aac.cpp
Normal file
@@ -0,0 +1,218 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "ajm.h"
|
||||
#include "ajm_aac.h"
|
||||
#include "ajm_result.h"
|
||||
|
||||
#include <aacdecoder_lib.h>
|
||||
// using this internal header to manually configure the decoder in RAW mode
|
||||
#include "externals/aacdec/fdk-aac/libAACdec/src/aacdecoder.h"
|
||||
|
||||
#include <algorithm> // std::transform
|
||||
#include <iterator> // std::back_inserter
|
||||
#include <limits>
|
||||
|
||||
namespace Libraries::Ajm {
|
||||
|
||||
std::span<const s16> AjmAacDecoder::GetOuputPcm(u32 skipped_pcm, u32 max_pcm) const {
|
||||
const auto pcm_data = std::span(m_pcm_buffer).subspan(skipped_pcm);
|
||||
return pcm_data.subspan(0, std::min<u32>(pcm_data.size(), max_pcm));
|
||||
}
|
||||
|
||||
template <>
|
||||
size_t AjmAacDecoder::WriteOutputSamples<float>(SparseOutputBuffer& out, std::span<const s16> pcm) {
|
||||
if (pcm.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
m_resample_buffer.clear();
|
||||
constexpr float inv_scale = 1.0f / std::numeric_limits<s16>::max();
|
||||
std::transform(pcm.begin(), pcm.end(), std::back_inserter(m_resample_buffer),
|
||||
[](auto sample) { return float(sample) * inv_scale; });
|
||||
|
||||
return out.Write(std::span(m_resample_buffer));
|
||||
}
|
||||
|
||||
AjmAacDecoder::AjmAacDecoder(AjmFormatEncoding format, AjmAacCodecFlags flags, u32 channels)
|
||||
: m_format(format), m_flags(flags), m_channels(channels), m_pcm_buffer(1024 * 8),
|
||||
m_skip_frames(True(flags & AjmAacCodecFlags::EnableNondelayOutput) ? 0 : 2) {
|
||||
m_resample_buffer.reserve(m_pcm_buffer.size());
|
||||
}
|
||||
|
||||
AjmAacDecoder::~AjmAacDecoder() {
|
||||
if (m_decoder) {
|
||||
aacDecoder_Close(m_decoder);
|
||||
}
|
||||
}
|
||||
|
||||
TRANSPORT_TYPE TransportTypeFromConfigType(ConfigType config_type) {
|
||||
switch (config_type) {
|
||||
case ConfigType::ADTS:
|
||||
return TT_MP4_ADTS;
|
||||
case ConfigType::RAW:
|
||||
return TT_MP4_RAW;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
static UINT g_freq[] = {
|
||||
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000,
|
||||
};
|
||||
|
||||
void AjmAacDecoder::Reset() {
|
||||
if (m_decoder) {
|
||||
aacDecoder_Close(m_decoder);
|
||||
}
|
||||
|
||||
m_decoder = aacDecoder_Open(TransportTypeFromConfigType(m_init_params.config_type), 1);
|
||||
if (m_init_params.config_type == ConfigType::RAW) {
|
||||
// Manually configure the decoder
|
||||
// Things may be incorrect due to limited documentation
|
||||
CSAudioSpecificConfig asc{};
|
||||
asc.m_aot = AOT_AAC_LC;
|
||||
asc.m_samplingFrequency = g_freq[m_init_params.sampling_freq_type];
|
||||
asc.m_samplingFrequencyIndex = m_init_params.sampling_freq_type;
|
||||
asc.m_samplesPerFrame = 1024;
|
||||
asc.m_epConfig = -1;
|
||||
switch (m_channels) {
|
||||
case 0:
|
||||
asc.m_channelConfiguration = 2;
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
asc.m_channelConfiguration = m_channels;
|
||||
break;
|
||||
case 7:
|
||||
asc.m_channelConfiguration = 11;
|
||||
break;
|
||||
case 8:
|
||||
asc.m_channelConfiguration = 12; // 7, 12 or 14 ?
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
UCHAR changed = 1;
|
||||
CAacDecoder_Init(m_decoder, &asc, AC_CM_ALLOC_MEM, &changed);
|
||||
}
|
||||
m_skip_frames = True(m_flags & AjmAacCodecFlags::EnableNondelayOutput) ? 0 : 2;
|
||||
}
|
||||
|
||||
void AjmAacDecoder::Initialize(const void* buffer, u32 buffer_size) {
|
||||
ASSERT(buffer_size == 8);
|
||||
m_init_params = *reinterpret_cast<const InitializeParameters*>(buffer);
|
||||
Reset();
|
||||
}
|
||||
|
||||
void AjmAacDecoder::GetInfo(void* out_info) const {
|
||||
auto* codec_info = reinterpret_cast<AjmSidebandDecM4aacCodecInfo*>(out_info);
|
||||
*codec_info = {
|
||||
.heaac = True(m_flags & AjmAacCodecFlags::EnableSbrDecode),
|
||||
};
|
||||
}
|
||||
|
||||
AjmSidebandFormat AjmAacDecoder::GetFormat() const {
|
||||
const auto* const info = aacDecoder_GetStreamInfo(m_decoder);
|
||||
return {
|
||||
.num_channels = static_cast<u32>(info->numChannels),
|
||||
.channel_mask = GetChannelMask(info->numChannels),
|
||||
.sampl_freq = static_cast<u32>(info->sampleRate),
|
||||
.sample_encoding = m_format,
|
||||
.bitrate = static_cast<u32>(info->bitRate),
|
||||
};
|
||||
}
|
||||
|
||||
u32 AjmAacDecoder::GetMinimumInputSize() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 AjmAacDecoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const {
|
||||
const auto* const info = aacDecoder_GetStreamInfo(m_decoder);
|
||||
if (info->aacSamplesPerFrame <= 0) {
|
||||
return 0;
|
||||
}
|
||||
const auto skip_samples = std::min<u32>(gapless.current.skip_samples, info->frameSize);
|
||||
const auto samples =
|
||||
gapless.init.total_samples != 0
|
||||
? std::min<u32>(gapless.current.total_samples, info->frameSize - skip_samples)
|
||||
: info->frameSize - skip_samples;
|
||||
return samples * info->numChannels * GetPCMSize(m_format);
|
||||
}
|
||||
|
||||
DecoderResult AjmAacDecoder::ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) {
|
||||
DecoderResult result{};
|
||||
|
||||
// Discard the previous contents of the internal buffer and replace them with new ones
|
||||
aacDecoder_SetParam(m_decoder, AAC_TPDEC_CLEAR_BUFFER, 1);
|
||||
UCHAR* buffers[] = {input.data()};
|
||||
const UINT sizes[] = {static_cast<UINT>(input.size())};
|
||||
UINT valid = sizes[0];
|
||||
aacDecoder_Fill(m_decoder, buffers, sizes, &valid);
|
||||
auto ret = aacDecoder_DecodeFrame(m_decoder, m_pcm_buffer.data(), m_pcm_buffer.size(), 0);
|
||||
|
||||
switch (ret) {
|
||||
case AAC_DEC_OK:
|
||||
break;
|
||||
case AAC_DEC_NOT_ENOUGH_BITS:
|
||||
result.result = ORBIS_AJM_RESULT_PARTIAL_INPUT;
|
||||
return result;
|
||||
default:
|
||||
LOG_ERROR(Lib_Ajm, "aacDecoder_DecodeFrame failed ret = {:#x}", static_cast<u32>(ret));
|
||||
result.result = ORBIS_AJM_RESULT_CODEC_ERROR | ORBIS_AJM_RESULT_FATAL;
|
||||
result.internal_result = ret;
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto* const info = aacDecoder_GetStreamInfo(m_decoder);
|
||||
auto bytes_used = info->numTotalBytes;
|
||||
|
||||
result.frames_decoded += 1;
|
||||
input = input.subspan(bytes_used);
|
||||
|
||||
if (m_skip_frames > 0) {
|
||||
--m_skip_frames;
|
||||
return result;
|
||||
}
|
||||
|
||||
u32 skip_samples = 0;
|
||||
if (gapless.current.skip_samples > 0) {
|
||||
skip_samples = std::min<u16>(info->frameSize, gapless.current.skip_samples);
|
||||
gapless.current.skip_samples -= skip_samples;
|
||||
}
|
||||
|
||||
const auto max_samples =
|
||||
gapless.init.total_samples != 0 ? gapless.current.total_samples : info->aacSamplesPerFrame;
|
||||
|
||||
size_t pcm_written = 0;
|
||||
auto pcm = GetOuputPcm(skip_samples * info->numChannels, max_samples * info->numChannels);
|
||||
switch (m_format) {
|
||||
case AjmFormatEncoding::S16:
|
||||
pcm_written = output.Write(pcm);
|
||||
break;
|
||||
case AjmFormatEncoding::S32:
|
||||
UNREACHABLE_MSG("NOT IMPLEMENTED");
|
||||
break;
|
||||
case AjmFormatEncoding::Float:
|
||||
pcm_written = WriteOutputSamples<float>(output, pcm);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
result.samples_written = pcm_written / info->numChannels;
|
||||
gapless.current.skipped_samples += info->frameSize - result.samples_written;
|
||||
if (gapless.init.total_samples != 0) {
|
||||
gapless.current.total_samples -= result.samples_written;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Libraries::Ajm
|
||||
69
src/core/libraries/ajm/ajm_aac.h
Normal file
69
src/core/libraries/ajm/ajm_aac.h
Normal file
@@ -0,0 +1,69 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/enum.h"
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/ajm/ajm_instance.h"
|
||||
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
struct AAC_DECODER_INSTANCE;
|
||||
|
||||
namespace Libraries::Ajm {
|
||||
|
||||
enum ConfigType : u32 {
|
||||
ADTS = 1,
|
||||
RAW = 2,
|
||||
};
|
||||
|
||||
enum AjmAacCodecFlags : u32 {
|
||||
EnableSbrDecode = 1 << 0,
|
||||
EnableNondelayOutput = 1 << 1,
|
||||
SurroundChannelInterleaveOrderExtlExtrLsRs = 1 << 2,
|
||||
SurroundChannelInterleaveOrderLsRsExtlExtr = 1 << 3,
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(AjmAacCodecFlags)
|
||||
|
||||
struct AjmSidebandDecM4aacCodecInfo {
|
||||
u32 heaac;
|
||||
u32 reserved;
|
||||
};
|
||||
|
||||
struct AjmAacDecoder final : AjmCodec {
|
||||
explicit AjmAacDecoder(AjmFormatEncoding format, AjmAacCodecFlags flags, u32 channels);
|
||||
~AjmAacDecoder() override;
|
||||
|
||||
void Reset() override;
|
||||
void Initialize(const void* buffer, u32 buffer_size) override;
|
||||
void GetInfo(void* out_info) const override;
|
||||
AjmSidebandFormat GetFormat() const override;
|
||||
u32 GetMinimumInputSize() const override;
|
||||
u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override;
|
||||
DecoderResult ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) override;
|
||||
|
||||
private:
|
||||
struct InitializeParameters {
|
||||
ConfigType config_type;
|
||||
u32 sampling_freq_type;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
size_t WriteOutputSamples(SparseOutputBuffer& output, std::span<const s16> pcm);
|
||||
std::span<const s16> GetOuputPcm(u32 skipped_pcm, u32 max_pcm) const;
|
||||
|
||||
const AjmFormatEncoding m_format;
|
||||
const AjmAacCodecFlags m_flags;
|
||||
const u32 m_channels;
|
||||
std::vector<s16> m_pcm_buffer;
|
||||
std::vector<float> m_resample_buffer;
|
||||
|
||||
u32 m_skip_frames = 0;
|
||||
InitializeParameters m_init_params = {};
|
||||
AAC_DECODER_INSTANCE* m_decoder = nullptr;
|
||||
};
|
||||
|
||||
} // namespace Libraries::Ajm
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "ajm_result.h"
|
||||
#include "common/assert.h"
|
||||
#include "core/libraries/ajm/ajm_at9.h"
|
||||
#include "error_codes.h"
|
||||
@@ -53,7 +54,7 @@ struct RIFFHeader {
|
||||
};
|
||||
static_assert(sizeof(RIFFHeader) == 12);
|
||||
|
||||
AjmAt9Decoder::AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags)
|
||||
AjmAt9Decoder::AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags, u32)
|
||||
: m_format(format), m_flags(flags), m_handle(Atrac9GetHandle()) {}
|
||||
|
||||
AjmAt9Decoder::~AjmAt9Decoder() {
|
||||
@@ -85,8 +86,8 @@ void AjmAt9Decoder::GetInfo(void* out_info) const {
|
||||
auto* info = reinterpret_cast<AjmSidebandDecAt9CodecInfo*>(out_info);
|
||||
info->super_frame_size = m_codec_info.superframeSize;
|
||||
info->frames_in_super_frame = m_codec_info.framesInSuperframe;
|
||||
info->next_frame_size = m_superframe_bytes_remain;
|
||||
info->frame_samples = m_codec_info.frameSamples;
|
||||
info->next_frame_size = static_cast<Atrac9Handle*>(m_handle)->Config.FrameBytes;
|
||||
}
|
||||
|
||||
u8 g_at9_guid[] = {0xD2, 0x42, 0xE1, 0x47, 0xBA, 0x36, 0x8D, 0x4D,
|
||||
@@ -133,18 +134,22 @@ void AjmAt9Decoder::ParseRIFFHeader(std::span<u8>& in_buf, AjmInstanceGapless& g
|
||||
}
|
||||
}
|
||||
|
||||
std::tuple<u32, u32, bool> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) {
|
||||
bool is_reset = false;
|
||||
u32 AjmAt9Decoder::GetMinimumInputSize() const {
|
||||
return m_superframe_bytes_remain;
|
||||
}
|
||||
|
||||
DecoderResult AjmAt9Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) {
|
||||
DecoderResult result{};
|
||||
if (True(m_flags & AjmAt9CodecFlags::ParseRiffHeader) &&
|
||||
*reinterpret_cast<u32*>(in_buf.data()) == 'FFIR') {
|
||||
ParseRIFFHeader(in_buf, gapless);
|
||||
is_reset = true;
|
||||
result.is_reset = true;
|
||||
}
|
||||
|
||||
if (!m_is_initialized) {
|
||||
return {0, 0, is_reset};
|
||||
result.result = ORBIS_AJM_RESULT_NOT_INITIALIZED;
|
||||
return result;
|
||||
}
|
||||
|
||||
int ret = 0;
|
||||
@@ -166,7 +171,14 @@ std::tuple<u32, u32, bool> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
ASSERT_MSG(ret == At9Status::ERR_SUCCESS, "Atrac9Decode failed ret = {:#x}", ret);
|
||||
if (ret != At9Status::ERR_SUCCESS) {
|
||||
LOG_ERROR(Lib_Ajm, "Atrac9Decode failed ret = {:#x}", ret);
|
||||
result.result = ORBIS_AJM_RESULT_CODEC_ERROR | ORBIS_AJM_RESULT_FATAL;
|
||||
result.internal_result = ret;
|
||||
return result;
|
||||
}
|
||||
|
||||
result.frames_decoded += 1;
|
||||
in_buf = in_buf.subspan(bytes_used);
|
||||
|
||||
m_superframe_bytes_remain -= bytes_used;
|
||||
@@ -196,10 +208,10 @@ std::tuple<u32, u32, bool> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
const auto samples_written = pcm_written / m_codec_info.channels;
|
||||
gapless.current.skipped_samples += m_codec_info.frameSamples - samples_written;
|
||||
result.samples_written = pcm_written / m_codec_info.channels;
|
||||
gapless.current.skipped_samples += m_codec_info.frameSamples - result.samples_written;
|
||||
if (gapless.init.total_samples != 0) {
|
||||
gapless.current.total_samples -= samples_written;
|
||||
gapless.current.total_samples -= result.samples_written;
|
||||
}
|
||||
|
||||
m_num_frames += 1;
|
||||
@@ -209,9 +221,23 @@ std::tuple<u32, u32, bool> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
}
|
||||
m_superframe_bytes_remain = m_codec_info.superframeSize;
|
||||
m_num_frames = 0;
|
||||
} else if (gapless.IsEnd()) {
|
||||
// Drain the remaining superframe
|
||||
std::vector<s16> buf(m_codec_info.frameSamples * m_codec_info.channels, 0);
|
||||
while ((m_num_frames % m_codec_info.framesInSuperframe) != 0) {
|
||||
ret = Atrac9Decode(m_handle, in_buf.data(), buf.data(), &bytes_used,
|
||||
True(m_flags & AjmAt9CodecFlags::NonInterleavedOutput));
|
||||
in_buf = in_buf.subspan(bytes_used);
|
||||
m_superframe_bytes_remain -= bytes_used;
|
||||
result.frames_decoded += 1;
|
||||
m_num_frames += 1;
|
||||
}
|
||||
in_buf = in_buf.subspan(m_superframe_bytes_remain);
|
||||
m_superframe_bytes_remain = m_codec_info.superframeSize;
|
||||
m_num_frames = 0;
|
||||
}
|
||||
|
||||
return {1, m_codec_info.frameSamples, is_reset};
|
||||
return result;
|
||||
}
|
||||
|
||||
AjmSidebandFormat AjmAt9Decoder::GetFormat() const {
|
||||
@@ -232,7 +258,7 @@ u32 AjmAt9Decoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const {
|
||||
const auto samples =
|
||||
gapless.init.total_samples != 0
|
||||
? std::min<u32>(gapless.current.total_samples, m_codec_info.frameSamples - skip_samples)
|
||||
: m_codec_info.frameSamples;
|
||||
: m_codec_info.frameSamples - skip_samples;
|
||||
return samples * m_codec_info.channels * GetPCMSize(m_format);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/enum.h"
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/ajm/ajm_instance.h"
|
||||
|
||||
@@ -13,8 +14,6 @@
|
||||
|
||||
namespace Libraries::Ajm {
|
||||
|
||||
constexpr s32 ORBIS_AJM_DEC_AT9_MAX_CHANNELS = 8;
|
||||
|
||||
enum AjmAt9CodecFlags : u32 {
|
||||
ParseRiffHeader = 1 << 0,
|
||||
NonInterleavedOutput = 1 << 8,
|
||||
@@ -29,16 +28,17 @@ struct AjmSidebandDecAt9CodecInfo {
|
||||
};
|
||||
|
||||
struct AjmAt9Decoder final : AjmCodec {
|
||||
explicit AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags);
|
||||
explicit AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags, u32 channels);
|
||||
~AjmAt9Decoder() override;
|
||||
|
||||
void Reset() override;
|
||||
void Initialize(const void* buffer, u32 buffer_size) override;
|
||||
void GetInfo(void* out_info) const override;
|
||||
AjmSidebandFormat GetFormat() const override;
|
||||
u32 GetMinimumInputSize() const override;
|
||||
u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override;
|
||||
std::tuple<u32, u32, bool> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) override;
|
||||
DecoderResult ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) override;
|
||||
|
||||
private:
|
||||
template <class T>
|
||||
|
||||
@@ -165,7 +165,7 @@ AjmJob AjmStatisticsJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buf
|
||||
ASSERT(job_flags.has_value());
|
||||
job.flags = job_flags.value();
|
||||
|
||||
AjmStatisticsJobFlags flags(job.flags);
|
||||
AjmStatisticsJobFlags flags{.raw = job.flags.raw};
|
||||
if (input_control_buffer.has_value()) {
|
||||
AjmBatchBuffer input_batch(input_control_buffer.value());
|
||||
if (True(flags.statistics_flags & AjmStatisticsFlags::Engine)) {
|
||||
@@ -280,9 +280,7 @@ AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) {
|
||||
job.input.resample_parameters = input_batch.Consume<AjmSidebandResampleParameters>();
|
||||
}
|
||||
if (True(control_flags & AjmJobControlFlags::Initialize)) {
|
||||
job.input.init_params = AjmDecAt9InitializeParameters{};
|
||||
std::memcpy(&job.input.init_params.value(), input_batch.GetCurrent(),
|
||||
input_batch.BytesRemaining());
|
||||
job.input.init_params = input_batch.Consume<AjmSidebandInitParameters>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Libraries::Ajm {
|
||||
|
||||
struct AjmJob {
|
||||
struct Input {
|
||||
std::optional<AjmDecAt9InitializeParameters> init_params;
|
||||
std::optional<AjmSidebandInitParameters> init_params;
|
||||
std::optional<AjmSidebandResampleParameters> resample_parameters;
|
||||
std::optional<AjmSidebandStatisticsEngineParameters> statistics_engine_parameters;
|
||||
std::optional<AjmSidebandFormat> format;
|
||||
@@ -52,6 +52,7 @@ struct AjmBatch {
|
||||
u32 id{};
|
||||
std::atomic_bool waiting{};
|
||||
std::atomic_bool canceled{};
|
||||
std::atomic_bool processed{};
|
||||
std::binary_semaphore finished{0};
|
||||
boost::container::small_vector<AjmJob, 16> jobs;
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
|
||||
namespace Libraries::Ajm {
|
||||
|
||||
static constexpr u32 ORBIS_AJM_WAIT_INFINITE = -1;
|
||||
constexpr u32 ORBIS_AJM_WAIT_INFINITE = -1;
|
||||
constexpr int INSTANCE_ID_MASK = 0x3FFF;
|
||||
|
||||
AjmContext::AjmContext() {
|
||||
worker_thread = std::jthread([this](std::stop_token stop) { this->WorkerThread(stop); });
|
||||
@@ -39,7 +40,12 @@ s32 AjmContext::BatchCancel(const u32 batch_id) {
|
||||
batch = *p_batch;
|
||||
}
|
||||
|
||||
batch->canceled = true;
|
||||
if (batch->processed) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
bool expected = false;
|
||||
batch->canceled.compare_exchange_strong(expected, true);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@@ -58,7 +64,9 @@ void AjmContext::WorkerThread(std::stop_token stop) {
|
||||
Common::SetCurrentThreadName("shadPS4:AjmWorker");
|
||||
while (!stop.stop_requested()) {
|
||||
auto batch = batch_queue.PopWait(stop);
|
||||
if (batch != nullptr) {
|
||||
if (batch != nullptr && !batch->canceled) {
|
||||
bool expected = false;
|
||||
batch->processed.compare_exchange_strong(expected, true);
|
||||
ProcessBatch(batch->id, batch->jobs);
|
||||
batch->finished.release();
|
||||
}
|
||||
@@ -77,7 +85,7 @@ void AjmContext::ProcessBatch(u32 id, std::span<AjmJob> jobs) {
|
||||
std::shared_ptr<AjmInstance> instance;
|
||||
{
|
||||
std::shared_lock lock(instances_mutex);
|
||||
auto* p_instance = instances.Get(job.instance_id);
|
||||
auto* p_instance = instances.Get(job.instance_id & INSTANCE_ID_MASK);
|
||||
ASSERT_MSG(p_instance != nullptr, "Attempting to execute job on null instance");
|
||||
instance = *p_instance;
|
||||
}
|
||||
@@ -169,15 +177,15 @@ s32 AjmContext::InstanceCreate(AjmCodecType codec_type, AjmInstanceFlags flags,
|
||||
if (!opt_index.has_value()) {
|
||||
return ORBIS_AJM_ERROR_OUT_OF_RESOURCES;
|
||||
}
|
||||
*out_instance = opt_index.value();
|
||||
*out_instance = opt_index.value() | (static_cast<u32>(codec_type) << 14);
|
||||
|
||||
LOG_INFO(Lib_Ajm, "instance = {}", *out_instance);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 AjmContext::InstanceDestroy(u32 instance) {
|
||||
s32 AjmContext::InstanceDestroy(u32 instance_id) {
|
||||
std::unique_lock lock(instances_mutex);
|
||||
if (!instances.Destroy(instance)) {
|
||||
if (!instances.Destroy(instance_id & INSTANCE_ID_MASK)) {
|
||||
return ORBIS_AJM_ERROR_INVALID_INSTANCE;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
|
||||
@@ -1,27 +1,16 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/libraries/ajm/ajm_at9.h"
|
||||
#include "core/libraries/ajm/ajm_instance.h"
|
||||
#include "core/libraries/ajm/ajm_mp3.h"
|
||||
#include "ajm_aac.h"
|
||||
#include "ajm_at9.h"
|
||||
#include "ajm_instance.h"
|
||||
#include "ajm_mp3.h"
|
||||
#include "ajm_result.h"
|
||||
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
|
||||
namespace Libraries::Ajm {
|
||||
|
||||
constexpr int ORBIS_AJM_RESULT_NOT_INITIALIZED = 0x00000001;
|
||||
constexpr int ORBIS_AJM_RESULT_INVALID_DATA = 0x00000002;
|
||||
constexpr int ORBIS_AJM_RESULT_INVALID_PARAMETER = 0x00000004;
|
||||
constexpr int ORBIS_AJM_RESULT_PARTIAL_INPUT = 0x00000008;
|
||||
constexpr int ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM = 0x00000010;
|
||||
constexpr int ORBIS_AJM_RESULT_STREAM_CHANGE = 0x00000020;
|
||||
constexpr int ORBIS_AJM_RESULT_TOO_MANY_CHANNELS = 0x00000040;
|
||||
constexpr int ORBIS_AJM_RESULT_UNSUPPORTED_FLAG = 0x00000080;
|
||||
constexpr int ORBIS_AJM_RESULT_SIDEBAND_TRUNCATED = 0x00000100;
|
||||
constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200;
|
||||
constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000;
|
||||
constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000;
|
||||
|
||||
u8 GetPCMSize(AjmFormatEncoding format) {
|
||||
switch (format) {
|
||||
case AjmFormatEncoding::S16:
|
||||
@@ -38,13 +27,18 @@ u8 GetPCMSize(AjmFormatEncoding format) {
|
||||
AjmInstance::AjmInstance(AjmCodecType codec_type, AjmInstanceFlags flags) : m_flags(flags) {
|
||||
switch (codec_type) {
|
||||
case AjmCodecType::At9Dec: {
|
||||
m_codec = std::make_unique<AjmAt9Decoder>(AjmFormatEncoding(flags.format),
|
||||
AjmAt9CodecFlags(flags.codec));
|
||||
m_codec = std::make_unique<AjmAt9Decoder>(
|
||||
AjmFormatEncoding(flags.format), AjmAt9CodecFlags(flags.codec), u32(flags.channels));
|
||||
break;
|
||||
}
|
||||
case AjmCodecType::Mp3Dec: {
|
||||
m_codec = std::make_unique<AjmMp3Decoder>(AjmFormatEncoding(flags.format),
|
||||
AjmMp3CodecFlags(flags.codec));
|
||||
m_codec = std::make_unique<AjmMp3Decoder>(
|
||||
AjmFormatEncoding(flags.format), AjmMp3CodecFlags(flags.codec), u32(flags.channels));
|
||||
break;
|
||||
}
|
||||
case AjmCodecType::M4aacDec: {
|
||||
m_codec = std::make_unique<AjmAacDecoder>(
|
||||
AjmFormatEncoding(flags.format), AjmAacCodecFlags(flags.codec), u32(flags.channels));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -60,6 +54,7 @@ void AjmInstance::Reset() {
|
||||
|
||||
void AjmInstance::ExecuteJob(AjmJob& job) {
|
||||
const auto control_flags = job.flags.control_flags;
|
||||
job.output.p_result->result = 0;
|
||||
if (True(control_flags & AjmJobControlFlags::Reset)) {
|
||||
LOG_TRACE(Lib_Ajm, "Resetting instance {}", job.instance_id);
|
||||
Reset();
|
||||
@@ -91,8 +86,7 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
|
||||
m_gapless.current.total_samples -= sample_difference;
|
||||
} else {
|
||||
LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_INVALID_PARAMETER");
|
||||
job.output.p_result->result = ORBIS_AJM_RESULT_INVALID_PARAMETER;
|
||||
return;
|
||||
job.output.p_result->result |= ORBIS_AJM_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,61 +100,59 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
|
||||
m_gapless.current.skip_samples -= sample_difference;
|
||||
} else {
|
||||
LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_INVALID_PARAMETER");
|
||||
job.output.p_result->result = ORBIS_AJM_RESULT_INVALID_PARAMETER;
|
||||
return;
|
||||
job.output.p_result->result |= ORBIS_AJM_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!job.input.buffer.empty() && !job.output.buffers.empty()) {
|
||||
std::span<u8> in_buf(job.input.buffer);
|
||||
SparseOutputBuffer out_buf(job.output.buffers);
|
||||
std::span<u8> in_buf(job.input.buffer);
|
||||
SparseOutputBuffer out_buf(job.output.buffers);
|
||||
auto in_size = in_buf.size();
|
||||
auto out_size = out_buf.Size();
|
||||
u32 frames_decoded = 0;
|
||||
|
||||
u32 frames_decoded = 0;
|
||||
auto in_size = in_buf.size();
|
||||
auto out_size = out_buf.Size();
|
||||
while (!in_buf.empty() && !out_buf.IsEmpty() && !m_gapless.IsEnd()) {
|
||||
if (!HasEnoughSpace(out_buf)) {
|
||||
if (job.output.p_mframe == nullptr || frames_decoded == 0) {
|
||||
LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM ({} < {})",
|
||||
out_buf.Size(), m_codec->GetNextFrameSize(m_gapless));
|
||||
job.output.p_result->result = ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const auto [nframes, nsamples, reset] =
|
||||
m_codec->ProcessData(in_buf, out_buf, m_gapless);
|
||||
if (reset) {
|
||||
if (!job.input.buffer.empty()) {
|
||||
for (;;) {
|
||||
if (m_flags.gapless_loop && m_gapless.IsEnd()) {
|
||||
m_gapless.Reset();
|
||||
m_total_samples = 0;
|
||||
}
|
||||
if (!nframes) {
|
||||
LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_NOT_INITIALIZED");
|
||||
job.output.p_result->result = ORBIS_AJM_RESULT_NOT_INITIALIZED;
|
||||
if (!HasEnoughSpace(out_buf)) {
|
||||
LOG_TRACE(Lib_Ajm, "ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM ({} < {})", out_buf.Size(),
|
||||
m_codec->GetNextFrameSize(m_gapless));
|
||||
job.output.p_result->result |= ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM;
|
||||
}
|
||||
if (in_buf.size() < m_codec->GetMinimumInputSize()) {
|
||||
job.output.p_result->result |= ORBIS_AJM_RESULT_PARTIAL_INPUT;
|
||||
}
|
||||
if (job.output.p_result->result != 0) {
|
||||
break;
|
||||
}
|
||||
const auto result = m_codec->ProcessData(in_buf, out_buf, m_gapless);
|
||||
if (result.is_reset) {
|
||||
m_total_samples = 0;
|
||||
} else {
|
||||
m_total_samples += result.samples_written;
|
||||
}
|
||||
frames_decoded += result.frames_decoded;
|
||||
if (result.result != 0) {
|
||||
job.output.p_result->result |= result.result;
|
||||
job.output.p_result->internal_result = result.internal_result;
|
||||
break;
|
||||
}
|
||||
frames_decoded += nframes;
|
||||
m_total_samples += nsamples;
|
||||
|
||||
if (False(job.flags.run_flags & AjmJobRunFlags::MultipleFrames)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto total_decoded_samples = m_total_samples;
|
||||
if (m_flags.gapless_loop && m_gapless.IsEnd()) {
|
||||
in_buf = in_buf.subspan(in_buf.size());
|
||||
m_gapless.Reset();
|
||||
m_codec->Reset();
|
||||
}
|
||||
if (job.output.p_mframe) {
|
||||
job.output.p_mframe->num_frames = frames_decoded;
|
||||
}
|
||||
if (job.output.p_stream) {
|
||||
job.output.p_stream->input_consumed = in_size - in_buf.size();
|
||||
job.output.p_stream->output_written = out_size - out_buf.Size();
|
||||
job.output.p_stream->total_decoded_samples = total_decoded_samples;
|
||||
}
|
||||
if (job.output.p_mframe) {
|
||||
job.output.p_mframe->num_frames = frames_decoded;
|
||||
}
|
||||
if (job.output.p_stream) {
|
||||
job.output.p_stream->input_consumed = in_size - in_buf.size();
|
||||
job.output.p_stream->output_written = out_size - out_buf.Size();
|
||||
job.output.p_stream->total_decoded_samples = m_total_samples;
|
||||
}
|
||||
|
||||
if (job.output.p_format != nullptr) {
|
||||
@@ -175,6 +167,9 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
|
||||
}
|
||||
|
||||
bool AjmInstance::HasEnoughSpace(const SparseOutputBuffer& output) const {
|
||||
if (m_gapless.IsEnd()) {
|
||||
return true;
|
||||
}
|
||||
return output.Size() >= m_codec->GetNextFrameSize(m_gapless);
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,14 @@ struct AjmInstanceGapless {
|
||||
}
|
||||
};
|
||||
|
||||
struct DecoderResult {
|
||||
s32 result = 0;
|
||||
s32 internal_result = 0;
|
||||
u32 frames_decoded = 0;
|
||||
u32 samples_written = 0;
|
||||
bool is_reset = false;
|
||||
};
|
||||
|
||||
class AjmCodec {
|
||||
public:
|
||||
virtual ~AjmCodec() = default;
|
||||
@@ -81,9 +89,10 @@ public:
|
||||
virtual void Reset() = 0;
|
||||
virtual void GetInfo(void* out_info) const = 0;
|
||||
virtual AjmSidebandFormat GetFormat() const = 0;
|
||||
virtual u32 GetMinimumInputSize() const = 0;
|
||||
virtual u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const = 0;
|
||||
virtual std::tuple<u32, u32, bool> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) = 0;
|
||||
virtual DecoderResult ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) = 0;
|
||||
};
|
||||
|
||||
class AjmInstance {
|
||||
@@ -94,7 +103,6 @@ public:
|
||||
|
||||
private:
|
||||
bool HasEnoughSpace(const SparseOutputBuffer& output) const;
|
||||
std::optional<u32> GetNumRemainingSamples() const;
|
||||
void Reset();
|
||||
|
||||
AjmInstanceFlags m_flags{};
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Libraries::Ajm {
|
||||
|
||||
void AjmInstanceStatistics::ExecuteJob(AjmJob& job) {
|
||||
if (job.output.p_engine) {
|
||||
job.output.p_engine->usage_batch = 0.01;
|
||||
job.output.p_engine->usage_batch = 0.05;
|
||||
const auto ic = job.input.statistics_engine_parameters->interval_count;
|
||||
for (u32 idx = 0; idx < ic; ++idx) {
|
||||
job.output.p_engine->usage_interval[idx] = 0.01;
|
||||
@@ -25,10 +25,12 @@ void AjmInstanceStatistics::ExecuteJob(AjmJob& job) {
|
||||
job.output.p_memory->batch_size = 0x4200;
|
||||
job.output.p_memory->input_size = 0x2000;
|
||||
job.output.p_memory->output_size = 0x2000;
|
||||
job.output.p_memory->small_size = 0x200;
|
||||
job.output.p_memory->small_size = 0x400;
|
||||
}
|
||||
}
|
||||
|
||||
void AjmInstanceStatistics::Reset() {}
|
||||
|
||||
AjmInstanceStatistics& AjmInstanceStatistics::Getinstance() {
|
||||
static AjmInstanceStatistics instance;
|
||||
return instance;
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace Libraries::Ajm {
|
||||
class AjmInstanceStatistics {
|
||||
public:
|
||||
void ExecuteJob(AjmJob& job);
|
||||
void Reset();
|
||||
|
||||
static AjmInstanceStatistics& Getinstance();
|
||||
};
|
||||
|
||||
@@ -105,7 +105,7 @@ AVFrame* AjmMp3Decoder::ConvertAudioFrame(AVFrame* frame) {
|
||||
return new_frame;
|
||||
}
|
||||
|
||||
AjmMp3Decoder::AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags)
|
||||
AjmMp3Decoder::AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags, u32)
|
||||
: m_format(format), m_flags(flags), m_codec(avcodec_find_decoder(AV_CODEC_ID_MP3)),
|
||||
m_codec_context(avcodec_alloc_context3(m_codec)), m_parser(av_parser_init(m_codec->id)) {
|
||||
int ret = avcodec_open2(m_codec_context, m_codec, nullptr);
|
||||
@@ -122,6 +122,7 @@ void AjmMp3Decoder::Reset() {
|
||||
avcodec_flush_buffers(m_codec_context);
|
||||
m_header.reset();
|
||||
m_frame_samples = 0;
|
||||
m_frame_size = 0;
|
||||
}
|
||||
|
||||
void AjmMp3Decoder::GetInfo(void* out_info) const {
|
||||
@@ -138,16 +139,28 @@ void AjmMp3Decoder::GetInfo(void* out_info) const {
|
||||
}
|
||||
}
|
||||
|
||||
std::tuple<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) {
|
||||
u32 AjmMp3Decoder::GetMinimumInputSize() const {
|
||||
// 4 bytes is for mp3 header that contains frame_size
|
||||
return std::max<u32>(m_frame_size, 4);
|
||||
}
|
||||
|
||||
DecoderResult AjmMp3Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) {
|
||||
DecoderResult result{};
|
||||
AVPacket* pkt = av_packet_alloc();
|
||||
|
||||
if ((!m_header.has_value() || m_frame_samples == 0) && in_buf.size() >= 4) {
|
||||
m_header = std::byteswap(*reinterpret_cast<u32*>(in_buf.data()));
|
||||
AjmDecMp3ParseFrame info{};
|
||||
ParseMp3Header(in_buf.data(), in_buf.size(), false, &info);
|
||||
ParseMp3Header(in_buf.data(), in_buf.size(), true, &info);
|
||||
m_frame_samples = info.samples_per_channel;
|
||||
m_frame_size = info.frame_size;
|
||||
gapless.init = {
|
||||
.total_samples = info.total_samples,
|
||||
.skip_samples = static_cast<u16>(info.encoder_delay),
|
||||
.skipped_samples = 0,
|
||||
};
|
||||
gapless.current = gapless.init;
|
||||
}
|
||||
|
||||
int ret = av_parser_parse2(m_parser, m_codec_context, &pkt->data, &pkt->size, in_buf.data(),
|
||||
@@ -155,9 +168,6 @@ std::tuple<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
ASSERT_MSG(ret >= 0, "Error while parsing {}", ret);
|
||||
in_buf = in_buf.subspan(ret);
|
||||
|
||||
u32 frames_decoded = 0;
|
||||
u32 samples_decoded = 0;
|
||||
|
||||
if (pkt->size) {
|
||||
// Send the packet with the compressed data to the decoder
|
||||
pkt->pts = m_parser->pts;
|
||||
@@ -177,9 +187,8 @@ std::tuple<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
UNREACHABLE_MSG("Error during decoding");
|
||||
}
|
||||
frame = ConvertAudioFrame(frame);
|
||||
samples_decoded += u32(frame->nb_samples);
|
||||
|
||||
frames_decoded += 1;
|
||||
result.frames_decoded += 1;
|
||||
u32 skip_samples = 0;
|
||||
if (gapless.current.skip_samples > 0) {
|
||||
skip_samples = std::min(u16(frame->nb_samples), gapless.current.skip_samples);
|
||||
@@ -211,6 +220,7 @@ std::tuple<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
if (gapless.init.total_samples != 0) {
|
||||
gapless.current.total_samples -= samples;
|
||||
}
|
||||
result.samples_written += samples;
|
||||
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
@@ -218,16 +228,16 @@ std::tuple<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
|
||||
av_packet_free(&pkt);
|
||||
|
||||
return {frames_decoded, samples_decoded, false};
|
||||
return result;
|
||||
}
|
||||
|
||||
u32 AjmMp3Decoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const {
|
||||
const auto max_samples = gapless.init.total_samples != 0
|
||||
? std::min(gapless.current.total_samples, m_frame_samples)
|
||||
: m_frame_samples;
|
||||
const auto skip_samples = std::min(u32(gapless.current.skip_samples), max_samples);
|
||||
return (max_samples - skip_samples) * m_codec_context->ch_layout.nb_channels *
|
||||
GetPCMSize(m_format);
|
||||
const auto skip_samples = std::min<u32>(gapless.current.skip_samples, m_frame_samples);
|
||||
const auto samples =
|
||||
gapless.init.total_samples != 0
|
||||
? std::min<u32>(gapless.current.total_samples, m_frame_samples - skip_samples)
|
||||
: m_frame_samples - skip_samples;
|
||||
return samples * m_codec_context->ch_layout.nb_channels * GetPCMSize(m_format);
|
||||
}
|
||||
|
||||
class BitReader {
|
||||
@@ -264,7 +274,7 @@ private:
|
||||
|
||||
int AjmMp3Decoder::ParseMp3Header(const u8* p_begin, u32 stream_size, int parse_ofl,
|
||||
AjmDecMp3ParseFrame* frame) {
|
||||
LOG_INFO(Lib_Ajm, "called stream_size = {} parse_ofl = {}", stream_size, parse_ofl);
|
||||
LOG_TRACE(Lib_Ajm, "called stream_size = {} parse_ofl = {}", stream_size, parse_ofl);
|
||||
|
||||
if (p_begin == nullptr || stream_size < 4 || frame == nullptr) {
|
||||
return ORBIS_AJM_ERROR_INVALID_PARAMETER;
|
||||
@@ -301,7 +311,8 @@ int AjmMp3Decoder::ParseMp3Header(const u8* p_begin, u32 stream_size, int parse_
|
||||
|
||||
BitReader reader(p_current);
|
||||
if (header->protection_type == 0) {
|
||||
reader.Skip(16); // crc = reader.Read<u16>(16);
|
||||
// crc = reader.Read<u16>(16);
|
||||
reader.Skip(16);
|
||||
}
|
||||
|
||||
if (header->version == Mp3AudioVersion::V1) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/enum.h"
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/ajm/ajm_instance.h"
|
||||
|
||||
@@ -63,16 +64,17 @@ struct AjmSidebandDecMp3CodecInfo {
|
||||
|
||||
class AjmMp3Decoder : public AjmCodec {
|
||||
public:
|
||||
explicit AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags);
|
||||
explicit AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags, u32 channels);
|
||||
~AjmMp3Decoder() override;
|
||||
|
||||
void Reset() override;
|
||||
void Initialize(const void* buffer, u32 buffer_size) override {}
|
||||
void GetInfo(void* out_info) const override;
|
||||
AjmSidebandFormat GetFormat() const override;
|
||||
u32 GetMinimumInputSize() const override;
|
||||
u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override;
|
||||
std::tuple<u32, u32, bool> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) override;
|
||||
DecoderResult ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) override;
|
||||
|
||||
static int ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl,
|
||||
AjmDecMp3ParseFrame* frame);
|
||||
@@ -97,6 +99,7 @@ private:
|
||||
SwrContext* m_swr_context = nullptr;
|
||||
std::optional<u32> m_header;
|
||||
u32 m_frame_samples = 0;
|
||||
u32 m_frame_size = 0;
|
||||
};
|
||||
|
||||
} // namespace Libraries::Ajm
|
||||
|
||||
17
src/core/libraries/ajm/ajm_result.h
Normal file
17
src/core/libraries/ajm/ajm_result.h
Normal file
@@ -0,0 +1,17 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
constexpr int ORBIS_AJM_RESULT_NOT_INITIALIZED = 0x00000001;
|
||||
constexpr int ORBIS_AJM_RESULT_INVALID_DATA = 0x00000002;
|
||||
constexpr int ORBIS_AJM_RESULT_INVALID_PARAMETER = 0x00000004;
|
||||
constexpr int ORBIS_AJM_RESULT_PARTIAL_INPUT = 0x00000008;
|
||||
constexpr int ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM = 0x00000010;
|
||||
constexpr int ORBIS_AJM_RESULT_STREAM_CHANGE = 0x00000020;
|
||||
constexpr int ORBIS_AJM_RESULT_TOO_MANY_CHANNELS = 0x00000040;
|
||||
constexpr int ORBIS_AJM_RESULT_UNSUPPORTED_FLAG = 0x00000080;
|
||||
constexpr int ORBIS_AJM_RESULT_SIDEBAND_TRUNCATED = 0x00000100;
|
||||
constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200;
|
||||
constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000;
|
||||
constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000;
|
||||
@@ -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 <cmath>
|
||||
@@ -345,7 +345,7 @@ int PS4_SYSV_ABI sceAppContentInitialize(const OrbisAppContentInitParam* initPar
|
||||
if (addcont_count > 0) {
|
||||
SystemService::OrbisSystemServiceEvent event{};
|
||||
event.event_type = SystemService::OrbisSystemServiceEventType::EntitlementUpdate;
|
||||
event.service_entitlement_update.user_id = 0;
|
||||
event.service_entitlement_update.userId = 0;
|
||||
event.service_entitlement_update.np_service_label = 0;
|
||||
SystemService::PushSystemServiceEvent(event);
|
||||
}
|
||||
|
||||
@@ -29,10 +29,10 @@ s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(const s32 handle) {
|
||||
return AudioOut::sceAudioOutClose(handle);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI
|
||||
sceAudio3dAudioOutOpen(const OrbisAudio3dPortId port_id, const OrbisUserServiceUserId user_id,
|
||||
s32 type, const s32 index, const u32 len, const u32 freq,
|
||||
const AudioOut::OrbisAudioOutParamExtendedInformation param) {
|
||||
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);
|
||||
@@ -189,7 +189,7 @@ s32 PS4_SYSV_ABI sceAudio3dDeleteSpeakerArray() {
|
||||
s32 PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters* params) {
|
||||
LOG_DEBUG(Lib_Audio3d, "called");
|
||||
if (params) {
|
||||
*params = OrbisAudio3dOpenParameters{
|
||||
auto default_params = OrbisAudio3dOpenParameters{
|
||||
.size_this = 0x20,
|
||||
.granularity = 0x100,
|
||||
.rate = OrbisAudio3dRate::ORBIS_AUDIO3D_RATE_48000,
|
||||
@@ -197,6 +197,7 @@ s32 PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters*
|
||||
.queue_depth = 2,
|
||||
.buffer_mode = OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH,
|
||||
};
|
||||
memcpy(params, &default_params, 0x20);
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@@ -421,7 +422,7 @@ s32 PS4_SYSV_ABI sceAudio3dPortGetStatus() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortOpen(const OrbisUserServiceUserId user_id,
|
||||
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,
|
||||
@@ -445,7 +446,7 @@ s32 PS4_SYSV_ABI sceAudio3dPortOpen(const OrbisUserServiceUserId user_id,
|
||||
}
|
||||
|
||||
*port_id = id;
|
||||
std::memcpy(&state->ports[id].parameters, parameters, sizeof(OrbisAudio3dOpenParameters));
|
||||
std::memcpy(&state->ports[id].parameters, parameters, parameters->size_this);
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -15,8 +15,6 @@ class SymbolsResolver;
|
||||
|
||||
namespace Libraries::Audio3d {
|
||||
|
||||
using OrbisUserServiceUserId = s32;
|
||||
|
||||
enum class OrbisAudio3dRate : u32 {
|
||||
ORBIS_AUDIO3D_RATE_48000 = 0,
|
||||
};
|
||||
@@ -91,7 +89,8 @@ struct Audio3dState {
|
||||
};
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(s32 handle);
|
||||
s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen(OrbisAudio3dPortId port_id, OrbisUserServiceUserId user_id,
|
||||
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);
|
||||
@@ -127,7 +126,7 @@ s32 PS4_SYSV_ABI sceAudio3dPortGetQueueLevel(OrbisAudio3dPortId port_id, u32* qu
|
||||
u32* queue_available);
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortGetState();
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortGetStatus();
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortOpen(OrbisUserServiceUserId user_id,
|
||||
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);
|
||||
|
||||
@@ -30,7 +30,15 @@ AvPlayerSourceType GetSourceType(std::string_view path) {
|
||||
}
|
||||
|
||||
// schema://server.domain/path/to/file.ext/and/beyond -> .ext/and/beyond
|
||||
auto ext = name.substr(name.rfind('.'));
|
||||
|
||||
// Find extension dot
|
||||
auto dot_pos = name.rfind('.');
|
||||
if (dot_pos == std::string_view::npos) {
|
||||
return AvPlayerSourceType::Unknown;
|
||||
}
|
||||
|
||||
// Extract extension (".ext/anything" or ".ext")
|
||||
auto ext = name.substr(dot_pos);
|
||||
if (ext.empty()) {
|
||||
return AvPlayerSourceType::Unknown;
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -16,7 +16,7 @@ s32 PS4_SYSV_ABI sceCompanionHttpdAddHeader(const char* key, const char* value,
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI
|
||||
sceCompanionHttpdGet2ndScreenStatus(Libraries::UserService::OrbisUserServiceUserId) {
|
||||
sceCompanionHttpdGet2ndScreenStatus(Libraries::UserService::OrbisUserServiceUserId userId) {
|
||||
LOG_ERROR(Lib_CompanionHttpd, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "fiber.h"
|
||||
|
||||
1611
src/core/libraries/font/font.cpp
Normal file
1611
src/core/libraries/font/font.cpp
Normal file
File diff suppressed because it is too large
Load Diff
299
src/core/libraries/font/font.h
Normal file
299
src/core/libraries/font/font.h
Normal file
@@ -0,0 +1,299 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
namespace Libraries::Font {
|
||||
|
||||
struct OrbisFontTextCharacter {
|
||||
// Other fields...
|
||||
struct OrbisFontTextCharacter* next; // Pointer to the next node 0x00
|
||||
struct OrbisFontTextCharacter* prev; // Pointer to the next node 0x08
|
||||
void* textOrder; // Field at offset 0x10 (pointer to text order info)
|
||||
u32 characterCode; // Field assumed at offset 0x28
|
||||
u8 unkn_0x31; // Offset 0x31
|
||||
u8 unkn_0x33; // Offset 0x33
|
||||
u8 charType; // Field assumed at offset 0x39
|
||||
u8 bidiLevel; // Field assumed at offset 0x3B stores the Bidi level
|
||||
u8 formatFlags; // Field at offset 0x3D (stores format-related flags)
|
||||
};
|
||||
|
||||
struct OrbisFontRenderSurface {
|
||||
void* buffer;
|
||||
s32 widthByte;
|
||||
s8 pixelSizeByte;
|
||||
u8 unkn_0xd;
|
||||
u8 styleFlag;
|
||||
u8 unkn_0xf;
|
||||
s32 width, height;
|
||||
u32 sc_x0;
|
||||
u32 sc_y0;
|
||||
u32 sc_x1;
|
||||
u32 sc_y1;
|
||||
void* unkn_28[3];
|
||||
};
|
||||
|
||||
struct OrbisFontStyleFrame {
|
||||
/*0x00*/ u16 magic; // Expected to be 0xF09
|
||||
/*0x02*/ u16 flags;
|
||||
/*0x04*/ s32 dpiX; // DPI scaling factor for width
|
||||
/*0x08*/ s32 dpiY; // DPI scaling factor for height
|
||||
/*0x0c*/ s32 scalingFlag; // Indicates whether scaling is enabled
|
||||
/*0x10*/
|
||||
/*0x14*/ float scaleWidth; // Width scaling factor
|
||||
/*0x18*/ float scaleHeight; // Height scaling factor
|
||||
/*0x1c*/ float weightXScale;
|
||||
/*0x20*/ float weightYScale;
|
||||
/*0x24*/ float slantRatio;
|
||||
};
|
||||
|
||||
s32 PS4_SYSV_ABI sceFontAttachDeviceCacheBuffer();
|
||||
s32 PS4_SYSV_ABI sceFontBindRenderer();
|
||||
s32 PS4_SYSV_ABI sceFontCharacterGetBidiLevel(OrbisFontTextCharacter* textCharacter,
|
||||
int* bidiLevel);
|
||||
s32 PS4_SYSV_ABI sceFontCharacterGetSyllableStringState();
|
||||
s32 PS4_SYSV_ABI sceFontCharacterGetTextFontCode();
|
||||
s32 PS4_SYSV_ABI sceFontCharacterGetTextOrder(OrbisFontTextCharacter* textCharacter,
|
||||
void** pTextOrder);
|
||||
u32 PS4_SYSV_ABI sceFontCharacterLooksFormatCharacters(OrbisFontTextCharacter* textCharacter);
|
||||
u32 PS4_SYSV_ABI sceFontCharacterLooksWhiteSpace(OrbisFontTextCharacter* textCharacter);
|
||||
OrbisFontTextCharacter* PS4_SYSV_ABI
|
||||
sceFontCharacterRefersTextBack(OrbisFontTextCharacter* textCharacter);
|
||||
OrbisFontTextCharacter* PS4_SYSV_ABI
|
||||
sceFontCharacterRefersTextNext(OrbisFontTextCharacter* textCharacter);
|
||||
s32 PS4_SYSV_ABI sceFontCharactersRefersTextCodes();
|
||||
s32 PS4_SYSV_ABI sceFontClearDeviceCache();
|
||||
s32 PS4_SYSV_ABI sceFontCloseFont();
|
||||
s32 PS4_SYSV_ABI sceFontControl();
|
||||
s32 PS4_SYSV_ABI sceFontCreateGraphicsDevice();
|
||||
s32 PS4_SYSV_ABI sceFontCreateGraphicsService();
|
||||
s32 PS4_SYSV_ABI sceFontCreateGraphicsServiceWithEdition();
|
||||
s32 PS4_SYSV_ABI sceFontCreateLibrary();
|
||||
s32 PS4_SYSV_ABI sceFontCreateLibraryWithEdition();
|
||||
s32 PS4_SYSV_ABI sceFontCreateRenderer();
|
||||
s32 PS4_SYSV_ABI sceFontCreateRendererWithEdition();
|
||||
s32 PS4_SYSV_ABI sceFontCreateString();
|
||||
s32 PS4_SYSV_ABI sceFontCreateWords();
|
||||
s32 PS4_SYSV_ABI sceFontCreateWritingLine();
|
||||
s32 PS4_SYSV_ABI sceFontDefineAttribute();
|
||||
s32 PS4_SYSV_ABI sceFontDeleteGlyph();
|
||||
s32 PS4_SYSV_ABI sceFontDestroyGraphicsDevice();
|
||||
s32 PS4_SYSV_ABI sceFontDestroyGraphicsService();
|
||||
s32 PS4_SYSV_ABI sceFontDestroyLibrary();
|
||||
s32 PS4_SYSV_ABI sceFontDestroyRenderer();
|
||||
s32 PS4_SYSV_ABI sceFontDestroyString();
|
||||
s32 PS4_SYSV_ABI sceFontDestroyWords();
|
||||
s32 PS4_SYSV_ABI sceFontDestroyWritingLine();
|
||||
s32 PS4_SYSV_ABI sceFontDettachDeviceCacheBuffer();
|
||||
s32 PS4_SYSV_ABI sceFontGenerateCharGlyph();
|
||||
s32 PS4_SYSV_ABI sceFontGetAttribute();
|
||||
s32 PS4_SYSV_ABI sceFontGetCharGlyphCode();
|
||||
s32 PS4_SYSV_ABI sceFontGetCharGlyphMetrics();
|
||||
s32 PS4_SYSV_ABI sceFontGetEffectSlant();
|
||||
s32 PS4_SYSV_ABI sceFontGetEffectWeight();
|
||||
s32 PS4_SYSV_ABI sceFontGetFontGlyphsCount();
|
||||
s32 PS4_SYSV_ABI sceFontGetFontGlyphsOutlineProfile();
|
||||
s32 PS4_SYSV_ABI sceFontGetFontMetrics();
|
||||
s32 PS4_SYSV_ABI sceFontGetFontResolution();
|
||||
s32 PS4_SYSV_ABI sceFontGetFontStyleInformation();
|
||||
s32 PS4_SYSV_ABI sceFontGetGlyphExpandBufferState();
|
||||
s32 PS4_SYSV_ABI sceFontGetHorizontalLayout();
|
||||
s32 PS4_SYSV_ABI sceFontGetKerning();
|
||||
s32 PS4_SYSV_ABI sceFontGetLibrary();
|
||||
s32 PS4_SYSV_ABI sceFontGetPixelResolution();
|
||||
s32 PS4_SYSV_ABI sceFontGetRenderCharGlyphMetrics();
|
||||
s32 PS4_SYSV_ABI sceFontGetRenderEffectSlant();
|
||||
s32 PS4_SYSV_ABI sceFontGetRenderEffectWeight();
|
||||
s32 PS4_SYSV_ABI sceFontGetRenderScaledKerning();
|
||||
s32 PS4_SYSV_ABI sceFontGetRenderScalePixel();
|
||||
s32 PS4_SYSV_ABI sceFontGetRenderScalePoint();
|
||||
s32 PS4_SYSV_ABI sceFontGetResolutionDpi();
|
||||
s32 PS4_SYSV_ABI sceFontGetScalePixel();
|
||||
s32 PS4_SYSV_ABI sceFontGetScalePoint();
|
||||
s32 PS4_SYSV_ABI sceFontGetScriptLanguage();
|
||||
s32 PS4_SYSV_ABI sceFontGetTypographicDesign();
|
||||
s32 PS4_SYSV_ABI sceFontGetVerticalLayout();
|
||||
s32 PS4_SYSV_ABI sceFontGlyphDefineAttribute();
|
||||
s32 PS4_SYSV_ABI sceFontGlyphGetAttribute();
|
||||
s32 PS4_SYSV_ABI sceFontGlyphGetGlyphForm();
|
||||
s32 PS4_SYSV_ABI sceFontGlyphGetMetricsForm();
|
||||
s32 PS4_SYSV_ABI sceFontGlyphGetScalePixel();
|
||||
s32 PS4_SYSV_ABI sceFontGlyphRefersMetrics();
|
||||
s32 PS4_SYSV_ABI sceFontGlyphRefersMetricsHorizontal();
|
||||
s32 PS4_SYSV_ABI sceFontGlyphRefersMetricsHorizontalAdvance();
|
||||
s32 PS4_SYSV_ABI sceFontGlyphRefersMetricsHorizontalX();
|
||||
s32 PS4_SYSV_ABI sceFontGlyphRefersOutline();
|
||||
s32 PS4_SYSV_ABI sceFontGlyphRenderImage();
|
||||
s32 PS4_SYSV_ABI sceFontGlyphRenderImageHorizontal();
|
||||
s32 PS4_SYSV_ABI sceFontGlyphRenderImageVertical();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsBeginFrame();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsDrawingCancel();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsDrawingFinish();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsEndFrame();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsExchangeResource();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsFillMethodInit();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsFillPlotInit();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsFillPlotSetLayout();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsFillPlotSetMapping();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsFillRatesInit();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsFillRatesSetFillEffect();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsFillRatesSetLayout();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsFillRatesSetMapping();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsGetDeviceUsage();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsRegionInit();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsRegionInitCircular();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsRegionInitRoundish();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsRelease();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsRenderResource();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsSetFramePolicy();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsSetupClipping();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsSetupColorRates();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsSetupFillMethod();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsSetupFillRates();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsSetupGlyphFill();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsSetupGlyphFillPlot();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsSetupHandleDefault();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsSetupLocation();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsSetupPositioning();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsSetupRotation();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsSetupScaling();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsSetupShapeFill();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsSetupShapeFillPlot();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsStructureCanvas();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsStructureCanvasSequence();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsStructureDesign();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsStructureDesignResource();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsStructureSurfaceTexture();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsUpdateClipping();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsUpdateColorRates();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsUpdateFillMethod();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsUpdateFillRates();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsUpdateGlyphFill();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsUpdateGlyphFillPlot();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsUpdateLocation();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsUpdatePositioning();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsUpdateRotation();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsUpdateScaling();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsUpdateShapeFill();
|
||||
s32 PS4_SYSV_ABI sceFontGraphicsUpdateShapeFillPlot();
|
||||
s32 PS4_SYSV_ABI sceFontMemoryInit();
|
||||
s32 PS4_SYSV_ABI sceFontMemoryTerm();
|
||||
s32 PS4_SYSV_ABI sceFontOpenFontFile();
|
||||
s32 PS4_SYSV_ABI sceFontOpenFontInstance();
|
||||
s32 PS4_SYSV_ABI sceFontOpenFontMemory();
|
||||
s32 PS4_SYSV_ABI sceFontOpenFontSet();
|
||||
s32 PS4_SYSV_ABI sceFontRebindRenderer();
|
||||
s32 PS4_SYSV_ABI sceFontRenderCharGlyphImage();
|
||||
s32 PS4_SYSV_ABI sceFontRenderCharGlyphImageHorizontal();
|
||||
s32 PS4_SYSV_ABI sceFontRenderCharGlyphImageVertical();
|
||||
s32 PS4_SYSV_ABI sceFontRendererGetOutlineBufferSize();
|
||||
s32 PS4_SYSV_ABI sceFontRendererResetOutlineBuffer();
|
||||
s32 PS4_SYSV_ABI sceFontRendererSetOutlineBufferPolicy();
|
||||
void PS4_SYSV_ABI sceFontRenderSurfaceInit(OrbisFontRenderSurface* renderSurface, void* buffer,
|
||||
int bufWidthByte, int pixelSizeByte, int widthPixel,
|
||||
int heightPixel);
|
||||
void PS4_SYSV_ABI sceFontRenderSurfaceSetScissor(OrbisFontRenderSurface* renderSurface, int x0,
|
||||
int y0, int w, int h);
|
||||
s32 PS4_SYSV_ABI sceFontRenderSurfaceSetStyleFrame(OrbisFontRenderSurface* renderSurface,
|
||||
OrbisFontStyleFrame* styleFrame);
|
||||
s32 PS4_SYSV_ABI sceFontSetEffectSlant();
|
||||
s32 PS4_SYSV_ABI sceFontSetEffectWeight();
|
||||
s32 PS4_SYSV_ABI sceFontSetFontsOpenMode();
|
||||
s32 PS4_SYSV_ABI sceFontSetResolutionDpi();
|
||||
s32 PS4_SYSV_ABI sceFontSetScalePixel();
|
||||
s32 PS4_SYSV_ABI sceFontSetScalePoint();
|
||||
s32 PS4_SYSV_ABI sceFontSetScriptLanguage();
|
||||
s32 PS4_SYSV_ABI sceFontSetTypographicDesign();
|
||||
s32 PS4_SYSV_ABI sceFontSetupRenderEffectSlant();
|
||||
s32 PS4_SYSV_ABI sceFontSetupRenderEffectWeight();
|
||||
s32 PS4_SYSV_ABI sceFontSetupRenderScalePixel();
|
||||
s32 PS4_SYSV_ABI sceFontSetupRenderScalePoint();
|
||||
s32 PS4_SYSV_ABI sceFontStringGetTerminateCode();
|
||||
s32 PS4_SYSV_ABI sceFontStringGetTerminateOrder();
|
||||
s32 PS4_SYSV_ABI sceFontStringGetWritingForm();
|
||||
s32 PS4_SYSV_ABI sceFontStringRefersRenderCharacters();
|
||||
s32 PS4_SYSV_ABI sceFontStringRefersTextCharacters();
|
||||
s32 PS4_SYSV_ABI sceFontStyleFrameGetEffectSlant(OrbisFontStyleFrame* styleFrame,
|
||||
float* slantRatio);
|
||||
s32 PS4_SYSV_ABI sceFontStyleFrameGetEffectWeight(OrbisFontStyleFrame* fontStyleFrame,
|
||||
float* weightXScale, float* weightYScale,
|
||||
uint32_t* mode);
|
||||
s32 PS4_SYSV_ABI sceFontStyleFrameGetResolutionDpi();
|
||||
s32 PS4_SYSV_ABI sceFontStyleFrameGetScalePixel(OrbisFontStyleFrame* styleFrame, float* w,
|
||||
float* h);
|
||||
s32 PS4_SYSV_ABI sceFontStyleFrameGetScalePoint();
|
||||
s32 PS4_SYSV_ABI sceFontStyleFrameInit();
|
||||
s32 PS4_SYSV_ABI sceFontStyleFrameSetEffectSlant();
|
||||
s32 PS4_SYSV_ABI sceFontStyleFrameSetEffectWeight();
|
||||
s32 PS4_SYSV_ABI sceFontStyleFrameSetResolutionDpi();
|
||||
s32 PS4_SYSV_ABI sceFontStyleFrameSetScalePixel();
|
||||
s32 PS4_SYSV_ABI sceFontStyleFrameSetScalePoint();
|
||||
s32 PS4_SYSV_ABI sceFontStyleFrameUnsetEffectSlant();
|
||||
s32 PS4_SYSV_ABI sceFontStyleFrameUnsetEffectWeight();
|
||||
s32 PS4_SYSV_ABI sceFontStyleFrameUnsetScale();
|
||||
s32 PS4_SYSV_ABI sceFontSupportExternalFonts();
|
||||
s32 PS4_SYSV_ABI sceFontSupportGlyphs();
|
||||
s32 PS4_SYSV_ABI sceFontSupportSystemFonts();
|
||||
s32 PS4_SYSV_ABI sceFontTextCodesStepBack();
|
||||
s32 PS4_SYSV_ABI sceFontTextCodesStepNext();
|
||||
s32 PS4_SYSV_ABI sceFontTextSourceInit();
|
||||
s32 PS4_SYSV_ABI sceFontTextSourceRewind();
|
||||
s32 PS4_SYSV_ABI sceFontTextSourceSetDefaultFont();
|
||||
s32 PS4_SYSV_ABI sceFontTextSourceSetWritingForm();
|
||||
s32 PS4_SYSV_ABI sceFontUnbindRenderer();
|
||||
s32 PS4_SYSV_ABI sceFontWordsFindWordCharacters();
|
||||
s32 PS4_SYSV_ABI sceFontWritingGetRenderMetrics();
|
||||
s32 PS4_SYSV_ABI sceFontWritingInit();
|
||||
s32 PS4_SYSV_ABI sceFontWritingLineClear();
|
||||
s32 PS4_SYSV_ABI sceFontWritingLineGetOrderingSpace();
|
||||
s32 PS4_SYSV_ABI sceFontWritingLineGetRenderMetrics();
|
||||
s32 PS4_SYSV_ABI sceFontWritingLineRefersRenderStep();
|
||||
s32 PS4_SYSV_ABI sceFontWritingLineWritesOrder();
|
||||
s32 PS4_SYSV_ABI sceFontWritingRefersRenderStep();
|
||||
s32 PS4_SYSV_ABI sceFontWritingRefersRenderStepCharacter();
|
||||
s32 PS4_SYSV_ABI sceFontWritingSetMaskInvisible();
|
||||
s32 PS4_SYSV_ABI Func_00F4D778F1C88CB3();
|
||||
s32 PS4_SYSV_ABI Func_03C650025FBB0DE7();
|
||||
s32 PS4_SYSV_ABI Func_07EAB8A163B27E1A();
|
||||
s32 PS4_SYSV_ABI Func_09408E88E4F97CE3();
|
||||
s32 PS4_SYSV_ABI Func_09F92905ED82A814();
|
||||
s32 PS4_SYSV_ABI Func_0D142CEE1AB21ABE();
|
||||
s32 PS4_SYSV_ABI Func_14BD2E9E119C16F2();
|
||||
s32 PS4_SYSV_ABI Func_1AC53C9EDEAE8D75();
|
||||
s32 PS4_SYSV_ABI Func_1D401185D5E24C3D();
|
||||
s32 PS4_SYSV_ABI Func_1E83CD20C2CC996F();
|
||||
s32 PS4_SYSV_ABI Func_314B1F765B9FE78A();
|
||||
s32 PS4_SYSV_ABI Func_350E6725FEDE29E1();
|
||||
s32 PS4_SYSV_ABI Func_3DB773F0A604BF39();
|
||||
s32 PS4_SYSV_ABI Func_4FF49DD21E311B1C();
|
||||
s32 PS4_SYSV_ABI Func_526287664A493981();
|
||||
s32 PS4_SYSV_ABI Func_55CA718DBC84A6E9();
|
||||
s32 PS4_SYSV_ABI Func_563FC5F0706A8B4D();
|
||||
s32 PS4_SYSV_ABI Func_569E2ECD34290F45();
|
||||
s32 PS4_SYSV_ABI Func_5A04775B6BE47685();
|
||||
s32 PS4_SYSV_ABI Func_5FD93BCAB6F79750();
|
||||
s32 PS4_SYSV_ABI Func_62B5398F864BD3B4();
|
||||
s32 PS4_SYSV_ABI Func_6F9010294D822367();
|
||||
s32 PS4_SYSV_ABI Func_7757E947423A7A67();
|
||||
s32 PS4_SYSV_ABI Func_7E06BA52077F54FA();
|
||||
s32 PS4_SYSV_ABI Func_93B36DEA021311D6();
|
||||
s32 PS4_SYSV_ABI Func_94B0891E7111598A();
|
||||
s32 PS4_SYSV_ABI Func_9785C9128C2FE7CD();
|
||||
s32 PS4_SYSV_ABI Func_97DFBC9B65FBC0E1();
|
||||
s32 PS4_SYSV_ABI Func_ACD9717405D7D3CA();
|
||||
s32 PS4_SYSV_ABI Func_B19A8AEC3FD4F16F();
|
||||
s32 PS4_SYSV_ABI Func_C10F488AD7CF103D();
|
||||
s32 PS4_SYSV_ABI Func_D0C8B5FF4A6826C7();
|
||||
s32 PS4_SYSV_ABI Func_E48D3CD01C342A33();
|
||||
s32 PS4_SYSV_ABI Func_EAC96B2186B71E14();
|
||||
s32 PS4_SYSV_ABI Func_FE4788A96EF46256();
|
||||
s32 PS4_SYSV_ABI Func_FE7E5AE95D3058F5();
|
||||
|
||||
void RegisterlibSceFont(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::Font
|
||||
44
src/core/libraries/font/font_error.h
Normal file
44
src/core/libraries/font/font_error.h
Normal file
@@ -0,0 +1,44 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/libraries/error_codes.h"
|
||||
|
||||
constexpr int ORBIS_FONT_ERROR_FATAL = 0x80460001;
|
||||
constexpr int ORBIS_FONT_ERROR_INVALID_PARAMETER = 0x80460002;
|
||||
constexpr int ORBIS_FONT_ERROR_INVALID_MEMORY = 0x80460003;
|
||||
constexpr int ORBIS_FONT_ERROR_INVALID_LIBRARY = 0x80460004;
|
||||
constexpr int ORBIS_FONT_ERROR_INVALID_FONT_HANDLE = 0x80460005;
|
||||
constexpr int ORBIS_FONT_ERROR_INVALID_GLYPH = 0x80460006;
|
||||
constexpr int ORBIS_FONT_ERROR_INVALID_RENDERER = 0x80460007;
|
||||
constexpr int ORBIS_FONT_ERROR_INVALID_TEXT_SOURCE = 0x80460008;
|
||||
constexpr int ORBIS_FONT_ERROR_INVALID_STRING = 0x80460009;
|
||||
constexpr int ORBIS_FONT_ERROR_INVALID_WRITING = 0x8046000A;
|
||||
constexpr int ORBIS_FONT_ERROR_INVALID_WORDS = 0x8046000B;
|
||||
constexpr int ORBIS_FONT_ERROR_ALLOCATION_FAILED = 0x80460010;
|
||||
constexpr int ORBIS_FONT_ERROR_FS_OPEN_FAILED = 0x80460011;
|
||||
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_LIBRARY = 0x80460018;
|
||||
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_FORMAT = 0x80460019;
|
||||
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_FUNCTION = 0x80460020;
|
||||
constexpr int ORBIS_FONT_ERROR_ALREADY_SPECIFIED = 0x80460021;
|
||||
constexpr int ORBIS_FONT_ERROR_ALREADY_ATTACHED = 0x80460022;
|
||||
constexpr int ORBIS_FONT_ERROR_ALREADY_OPENED = 0x80460023;
|
||||
constexpr int ORBIS_FONT_ERROR_NOT_ATTACHED_CACHE_BUFFER = 0x80460025;
|
||||
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_FONTSET = 0x80460031;
|
||||
constexpr int ORBIS_FONT_ERROR_FONT_OPEN_MAX = 0x80460033;
|
||||
constexpr int ORBIS_FONT_ERROR_FONT_OPEN_FAILED = 0x80460036;
|
||||
constexpr int ORBIS_FONT_ERROR_FONT_CLOSE_FAILED = 0x80460037;
|
||||
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_TYPOGRAPHY = 0x80460040;
|
||||
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_CODE = 0x80460041;
|
||||
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH = 0x80460042;
|
||||
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_SCRIPT = 0x80460043;
|
||||
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_LANGUAGE = 0x80460044;
|
||||
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_SURFACE = 0x80460050;
|
||||
constexpr int ORBIS_FONT_ERROR_UNSET_PARAMETER = 0x80460058;
|
||||
constexpr int ORBIS_FONT_ERROR_FUNCTIONAL_LIMIT = 0x8046005C;
|
||||
constexpr int ORBIS_FONT_ERROR_ALREADY_BOUND_RENDERER = 0x80460060;
|
||||
constexpr int ORBIS_FONT_ERROR_NOT_BOUND_RENDERER = 0x80460061;
|
||||
constexpr int ORBIS_FONT_ERROR_RENDERER_ALLOCATION_FAILED = 0x80460063;
|
||||
constexpr int ORBIS_FONT_ERROR_RENDERER_ALLOCATION_LIMITED = 0x80460064;
|
||||
constexpr int ORBIS_FONT_ERROR_RENDERER_RENDER_FAILED = 0x80460065;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user