mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-12-23 06:58:03 +00:00
Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
||||
|
||||
12
.gitmodules
vendored
12
.gitmodules
vendored
@ -108,5 +108,15 @@
|
||||
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
|
||||
|
||||
247
CMakeLists.txt
247
CMakeLists.txt
@ -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)
|
||||
|
||||
@ -204,12 +203,12 @@ execute_process(
|
||||
# Set Version
|
||||
set(EMULATOR_VERSION_MAJOR "0")
|
||||
set(EMULATOR_VERSION_MINOR "12")
|
||||
set(EMULATOR_VERSION_PATCH "0")
|
||||
set(EMULATOR_VERSION_PATCH "6")
|
||||
|
||||
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,30 +260,6 @@ 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_at9.cpp
|
||||
@ -490,6 +464,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 +512,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
|
||||
)
|
||||
@ -561,6 +541,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 +556,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,6 +573,8 @@ 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_score.cpp
|
||||
@ -643,6 +634,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 +695,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 +708,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
|
||||
@ -974,6 +967,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 +989,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 +1028,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 +1065,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)
|
||||
|
||||
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 +1105,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 +1133,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 +1181,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 +1210,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 +1218,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"
|
||||
}
|
||||
]
|
||||
}
|
||||
31
README.md
31
README.md
@ -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,9 +58,6 @@ 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).
|
||||
|
||||
## Windows
|
||||
|
||||
Check the build instructions for [**Windows**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-windows.md).
|
||||
@ -73,6 +73,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).
|
||||
@ -160,15 +176,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()
|
||||
11
dist/net.shadps4.shadPS4.metainfo.xml
vendored
11
dist/net.shadps4.shadPS4.metainfo.xml
vendored
@ -18,25 +18,28 @@
|
||||
<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.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"
|
||||
@ -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
|
||||
|
||||
21
externals/CMakeLists.txt
vendored
21
externals/CMakeLists.txt
vendored
@ -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
|
||||
@ -245,3 +257,10 @@ endif()
|
||||
if (WIN32)
|
||||
add_subdirectory(ext-wepoll)
|
||||
endif()
|
||||
|
||||
#nlohmann json
|
||||
set(JSON_BuildTests OFF CACHE INTERNAL "")
|
||||
add_subdirectory(json)
|
||||
|
||||
# miniz
|
||||
add_subdirectory(miniz)
|
||||
|
||||
2
externals/MoltenVK
vendored
2
externals/MoltenVK
vendored
@ -1 +1 @@
|
||||
Subproject commit b23d42534622cd9926fe526fec1b7f8795a2853c
|
||||
Subproject commit f168dec05998ab0ca09a400bab6831a95c0bdb2e
|
||||
2
externals/ffmpeg-core
vendored
2
externals/ffmpeg-core
vendored
@ -1 +1 @@
|
||||
Subproject commit b0de1dcca26c0ebfb8011b8e59dd17fc399db0ff
|
||||
Subproject commit 94dde08c8a9e4271a93a2a7e4159e9fb05d30c0a
|
||||
2
externals/fmt
vendored
2
externals/fmt
vendored
@ -1 +1 @@
|
||||
Subproject commit 64db979e38ec644b1798e41610b28c8d2c8a2739
|
||||
Subproject commit ec73fb72477d80926c758894a3ab2cb3994fd051
|
||||
1
externals/json
vendored
Submodule
1
externals/json
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 55f93686c01528224f448c19128836e7df245f72
|
||||
1
externals/miniz
vendored
Submodule
1
externals/miniz
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 174573d60290f447c13a2b1b3405de2b96e27d6c
|
||||
2
externals/sdl3
vendored
2
externals/sdl3
vendored
@ -1 +1 @@
|
||||
Subproject commit e9c2e9bfc3a6e1e70596f743fa9e1fc5fadabef7
|
||||
Subproject commit bdb72bb3f051de32c91f5deb439a50bfd51499dc
|
||||
1
externals/sdl3_mixer
vendored
Submodule
1
externals/sdl3_mixer
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 4182794ea45fe28568728670c6f1583855d0e85c
|
||||
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,18 @@ 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,15 +211,28 @@ 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};
|
||||
static bool isGameRunning = false;
|
||||
static bool load_auto_patches = true;
|
||||
|
||||
bool getGameRunning() {
|
||||
return isGameRunning;
|
||||
}
|
||||
|
||||
void setGameRunning(bool running) {
|
||||
isGameRunning = running;
|
||||
}
|
||||
|
||||
std::filesystem::path getSysModulesPath() {
|
||||
if (sys_modules_path.empty()) {
|
||||
@ -278,10 +290,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 +301,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 +313,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 +395,6 @@ string getUserName() {
|
||||
return userName.get();
|
||||
}
|
||||
|
||||
string getChooseHomeTab() {
|
||||
return chooseHomeTab.get();
|
||||
}
|
||||
|
||||
bool getUseSpecialPad() {
|
||||
return useSpecialPad.get();
|
||||
}
|
||||
@ -453,10 +455,26 @@ bool isRdocEnabled() {
|
||||
return rdocEnable.get();
|
||||
}
|
||||
|
||||
bool isPipelineCacheEnabled() {
|
||||
return pipelineCacheEnable.get();
|
||||
}
|
||||
|
||||
bool isPipelineCacheArchived() {
|
||||
return pipelineCacheArchive.get();
|
||||
}
|
||||
|
||||
bool fpsColor() {
|
||||
return isFpsColor.get();
|
||||
}
|
||||
|
||||
bool getShowFpsCounter() {
|
||||
return showFpsCounter.get();
|
||||
}
|
||||
|
||||
void setShowFpsCounter(bool enable, bool is_game_specific) {
|
||||
showFpsCounter.set(enable, is_game_specific);
|
||||
}
|
||||
|
||||
bool isLoggingEnabled() {
|
||||
return logEnabled.get();
|
||||
}
|
||||
@ -508,14 +526,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 +610,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 +706,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 +718,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 +847,21 @@ 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);
|
||||
}
|
||||
|
||||
bool getLoadAutoPatches() {
|
||||
return load_auto_patches;
|
||||
}
|
||||
void setLoadAutoPatches(bool enable) {
|
||||
load_auto_patches = enable;
|
||||
}
|
||||
|
||||
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 +903,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 +919,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 +966,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 = {};
|
||||
@ -950,6 +978,7 @@ void load(const std::filesystem::path& path, bool 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 +986,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 +1089,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 +1105,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 +1131,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 +1180,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 +1197,8 @@ 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 +1232,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 +1240,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 +1270,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 +1287,6 @@ void setDefaultValues(bool is_game_specific) {
|
||||
|
||||
// General
|
||||
enableDiscordRPC = false;
|
||||
compatibilityData = false;
|
||||
checkCompatibilityOnStartup = false;
|
||||
|
||||
// Input
|
||||
useSpecialPad.base_value = false;
|
||||
@ -1278,11 +1305,9 @@ 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 +1322,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);
|
||||
@ -117,15 +126,15 @@ 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 +152,18 @@ 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);
|
||||
bool getLoadAutoPatches();
|
||||
void setLoadAutoPatches(bool enable);
|
||||
|
||||
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 +173,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;
|
||||
|
||||
@ -62,7 +62,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;
|
||||
@ -182,7 +182,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 +207,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;
|
||||
@ -324,8 +326,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
|
||||
|
||||
@ -104,6 +104,7 @@ 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, NpScore) \
|
||||
SUB(Lib, NpTrophy) \
|
||||
@ -140,6 +141,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) \
|
||||
|
||||
@ -70,6 +70,7 @@ 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_NpScore, ///< The LibSceNpScore implementation
|
||||
@ -114,6 +115,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
|
||||
|
||||
@ -3,20 +3,12 @@
|
||||
|
||||
#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"
|
||||
@ -28,7 +20,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;
|
||||
|
||||
@ -122,256 +114,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 (Config::getLoadAutoPatches()) {
|
||||
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
|
||||
@ -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"
|
||||
@ -103,8 +104,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 +121,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.
|
||||
|
||||
@ -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 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "layer.h"
|
||||
@ -311,6 +311,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 +461,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 +476,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));
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -36,6 +36,18 @@ public:
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
virtual s64 write(const void* buf, u64 nbytes) {
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
virtual s64 writev(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) {
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
virtual s64 pwritev(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) {
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
virtual s64 lseek(s64 offset, s32 whence) {
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
@ -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 "";
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
#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 +71,8 @@ void IPC::Init() {
|
||||
return;
|
||||
}
|
||||
|
||||
Config::setLoadAutoPatches(false);
|
||||
|
||||
input_thread = std::jthread([this] {
|
||||
Common::SetCurrentThreadName("IPC Read thread");
|
||||
this->InputLoop();
|
||||
@ -167,6 +170,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 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;
|
||||
140
src/core/libraries/font/fontft.cpp
Normal file
140
src/core/libraries/font/fontft.cpp
Normal file
@ -0,0 +1,140 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/font/fontft.h"
|
||||
#include "core/libraries/libs.h"
|
||||
|
||||
namespace Libraries::FontFt {
|
||||
|
||||
s32 PS4_SYSV_ABI sceFontFtInitAliases() {
|
||||
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceFontFtSetAliasFont() {
|
||||
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceFontFtSetAliasPath() {
|
||||
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportBdf() {
|
||||
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportCid() {
|
||||
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportFontFormats() {
|
||||
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportOpenType() {
|
||||
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportOpenTypeOtf() {
|
||||
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportOpenTypeTtf() {
|
||||
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportPcf() {
|
||||
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportPfr() {
|
||||
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportSystemFonts() {
|
||||
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportTrueType() {
|
||||
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportTrueTypeGx() {
|
||||
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportType1() {
|
||||
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportType42() {
|
||||
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportWinFonts() {
|
||||
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceFontFtTermAliases() {
|
||||
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceFontSelectGlyphsFt() {
|
||||
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceFontSelectLibraryFt() {
|
||||
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceFontSelectRendererFt() {
|
||||
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
void RegisterlibSceFontFt(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("e60aorDdpB8", "libSceFontFt", 1, "libSceFontFt", sceFontFtInitAliases);
|
||||
LIB_FUNCTION("BxcmiMc3UaA", "libSceFontFt", 1, "libSceFontFt", sceFontFtSetAliasFont);
|
||||
LIB_FUNCTION("MEWjebIzDEI", "libSceFontFt", 1, "libSceFontFt", sceFontFtSetAliasPath);
|
||||
LIB_FUNCTION("ZcQL0iSjvFw", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportBdf);
|
||||
LIB_FUNCTION("LADHEyFTxRQ", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportCid);
|
||||
LIB_FUNCTION("+jqQjsancTs", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportFontFormats);
|
||||
LIB_FUNCTION("oakL15-mBtc", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportOpenType);
|
||||
LIB_FUNCTION("dcQeaDr8UJc", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportOpenTypeOtf);
|
||||
LIB_FUNCTION("2KXS-HkZT3c", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportOpenTypeTtf);
|
||||
LIB_FUNCTION("H0mJnhKwV-s", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportPcf);
|
||||
LIB_FUNCTION("S2mw3sYplAI", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportPfr);
|
||||
LIB_FUNCTION("+ehNXJPUyhk", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportSystemFonts);
|
||||
LIB_FUNCTION("4BAhDLdrzUI", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportTrueType);
|
||||
LIB_FUNCTION("Utlzbdf+g9o", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportTrueTypeGx);
|
||||
LIB_FUNCTION("nAfQ6qaL1fU", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportType1);
|
||||
LIB_FUNCTION("X9+pzrGtBus", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportType42);
|
||||
LIB_FUNCTION("w0hI3xsK-hc", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportWinFonts);
|
||||
LIB_FUNCTION("w5sfH9r8ZJ4", "libSceFontFt", 1, "libSceFontFt", sceFontFtTermAliases);
|
||||
LIB_FUNCTION("ojW+VKl4Ehs", "libSceFontFt", 1, "libSceFontFt", sceFontSelectGlyphsFt);
|
||||
LIB_FUNCTION("oM+XCzVG3oM", "libSceFontFt", 1, "libSceFontFt", sceFontSelectLibraryFt);
|
||||
LIB_FUNCTION("Xx974EW-QFY", "libSceFontFt", 1, "libSceFontFt", sceFontSelectRendererFt);
|
||||
};
|
||||
|
||||
} // namespace Libraries::FontFt
|
||||
37
src/core/libraries/font/fontft.h
Normal file
37
src/core/libraries/font/fontft.h
Normal file
@ -0,0 +1,37 @@
|
||||
// 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::FontFt {
|
||||
|
||||
s32 PS4_SYSV_ABI sceFontFtInitAliases();
|
||||
s32 PS4_SYSV_ABI sceFontFtSetAliasFont();
|
||||
s32 PS4_SYSV_ABI sceFontFtSetAliasPath();
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportBdf();
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportCid();
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportFontFormats();
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportOpenType();
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportOpenTypeOtf();
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportOpenTypeTtf();
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportPcf();
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportPfr();
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportSystemFonts();
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportTrueType();
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportTrueTypeGx();
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportType1();
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportType42();
|
||||
s32 PS4_SYSV_ABI sceFontFtSupportWinFonts();
|
||||
s32 PS4_SYSV_ABI sceFontFtTermAliases();
|
||||
s32 PS4_SYSV_ABI sceFontSelectGlyphsFt();
|
||||
s32 PS4_SYSV_ABI sceFontSelectLibraryFt();
|
||||
s32 PS4_SYSV_ABI sceFontSelectRendererFt();
|
||||
|
||||
void RegisterlibSceFontFt(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::FontFt
|
||||
@ -7,6 +7,7 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/config.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/elf_info.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/slot_vector.h"
|
||||
#include "core/address_space.h"
|
||||
@ -68,7 +69,7 @@ std::condition_variable cv_lock{};
|
||||
std::mutex m_submission{};
|
||||
static u64 frames_submitted{}; // frame counter
|
||||
static bool send_init_packet{true}; // initialize HW state before first game's submit in a frame
|
||||
static int sdk_version{0};
|
||||
static s32 sdk_version{0};
|
||||
|
||||
static u32 asc_next_offs_dw[Liverpool::NumComputeRings];
|
||||
|
||||
@ -2199,9 +2200,9 @@ int PS4_SYSV_ABI sceGnmSubmitCommandBuffersForWorkload(u32 workload, u32 count,
|
||||
}
|
||||
|
||||
if (send_init_packet) {
|
||||
if (sdk_version <= 0x1ffffffu) {
|
||||
if (sdk_version < Common::ElfInfo::FW_20) {
|
||||
liverpool->SubmitGfx(InitSequence, {});
|
||||
} else if (sdk_version <= 0x3ffffffu) {
|
||||
} else if (sdk_version < Common::ElfInfo::FW_40) {
|
||||
if (sceKernelIsNeoMode()) {
|
||||
if (!UseNeoCompatSequences) {
|
||||
liverpool->SubmitGfx(InitSequence200Neo, {});
|
||||
@ -2780,14 +2781,20 @@ int PS4_SYSV_ABI Func_C4C328B7CF3B4171() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceGnmDrawInitToDefaultContextStateInternalCommand() {
|
||||
LOG_ERROR(Lib_GnmDriver, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
int PS4_SYSV_ABI sceGnmDrawInitToDefaultContextStateInternalCommand(u32* cmdbuf, u32 size) {
|
||||
LOG_TRACE(Lib_GnmDriver, "called");
|
||||
if (sdk_version >= Common::ElfInfo::FW_40) {
|
||||
return sceGnmDrawInitToDefaultContextState400(cmdbuf, size);
|
||||
}
|
||||
return sceGnmDrawInitToDefaultContextState(cmdbuf, size);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceGnmDrawInitToDefaultContextStateInternalSize() {
|
||||
LOG_ERROR(Lib_GnmDriver, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
LOG_TRACE(Lib_GnmDriver, "called");
|
||||
if (sdk_version >= Common::ElfInfo::FW_40) {
|
||||
return 0x100;
|
||||
}
|
||||
return 0x20;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceGnmFindResources() {
|
||||
|
||||
@ -286,7 +286,7 @@ int PS4_SYSV_ABI Func_ECB4C6BA41FE3350();
|
||||
int PS4_SYSV_ABI sceGnmDebugModuleReset();
|
||||
int PS4_SYSV_ABI sceGnmDebugReset();
|
||||
int PS4_SYSV_ABI Func_C4C328B7CF3B4171();
|
||||
int PS4_SYSV_ABI sceGnmDrawInitToDefaultContextStateInternalCommand();
|
||||
int PS4_SYSV_ABI sceGnmDrawInitToDefaultContextStateInternalCommand(u32* cmdbuf, u32 size);
|
||||
int PS4_SYSV_ABI sceGnmDrawInitToDefaultContextStateInternalSize();
|
||||
int PS4_SYSV_ABI sceGnmFindResources();
|
||||
int PS4_SYSV_ABI sceGnmGetResourceRegistrationBuffers();
|
||||
|
||||
@ -37,6 +37,7 @@
|
||||
#endif
|
||||
|
||||
namespace D = Core::Devices;
|
||||
namespace fs = std::filesystem;
|
||||
using FactoryDevice = std::function<std::shared_ptr<D::BaseDevice>(u32, const char*, int, u16)>;
|
||||
|
||||
#define GET_DEVICE_FD(fd) \
|
||||
@ -74,6 +75,7 @@ namespace Libraries::Kernel {
|
||||
|
||||
s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
|
||||
LOG_INFO(Kernel_Fs, "path = {} flags = {:#x} mode = {:#o}", raw_path, flags, mode);
|
||||
|
||||
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
|
||||
@ -87,6 +89,11 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (strlen(raw_path) > 255) {
|
||||
*__Error() = POSIX_ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool nonblock = (flags & ORBIS_KERNEL_O_NONBLOCK) != 0;
|
||||
bool append = (flags & ORBIS_KERNEL_O_APPEND) != 0;
|
||||
// Flags fsync and sync behave the same
|
||||
@ -121,7 +128,7 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
|
||||
bool read_only = false;
|
||||
file->m_guest_name = path;
|
||||
file->m_host_name = mnt->GetHostPath(file->m_guest_name, &read_only);
|
||||
bool exists = std::filesystem::exists(file->m_host_name);
|
||||
bool exists = fs::exists(file->m_host_name);
|
||||
s32 e = 0;
|
||||
|
||||
if (create) {
|
||||
@ -140,7 +147,7 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
|
||||
return -1;
|
||||
}
|
||||
// Create a file if it doesn't exist
|
||||
Common::FS::IOFile out(file->m_host_name, Common::FS::FileAccessMode::Write);
|
||||
Common::FS::IOFile out(file->m_host_name, Common::FS::FileAccessMode::Create);
|
||||
}
|
||||
} else if (!exists) {
|
||||
// If we're not creating a file, and it doesn't exist, return ENOENT
|
||||
@ -149,14 +156,14 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (std::filesystem::is_directory(file->m_host_name) || directory) {
|
||||
if (fs::is_directory(file->m_host_name) || directory) {
|
||||
// Directories can be opened even if the directory flag isn't set.
|
||||
// In these cases, error behavior is identical to the directory code path.
|
||||
directory = true;
|
||||
}
|
||||
|
||||
if (directory) {
|
||||
if (!std::filesystem::is_directory(file->m_host_name)) {
|
||||
if (!fs::is_directory(file->m_host_name)) {
|
||||
// If the opened file is not a directory, return ENOTDIR.
|
||||
// This will trigger when create & directory is specified, this is expected.
|
||||
h->DeleteHandle(handle);
|
||||
@ -205,22 +212,30 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
|
||||
}
|
||||
|
||||
if (read) {
|
||||
// Read only
|
||||
// Open exclusively for reading
|
||||
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Read);
|
||||
} else if (read_only) {
|
||||
// Can't open files with write/read-write access in a read only directory
|
||||
h->DeleteHandle(handle);
|
||||
*__Error() = POSIX_EROFS;
|
||||
return -1;
|
||||
} else if (append) {
|
||||
// Append can be specified with rdwr or write, but we treat it as a separate mode.
|
||||
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Append);
|
||||
} else if (write) {
|
||||
// Write only
|
||||
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Write);
|
||||
if (append) {
|
||||
// Open exclusively for appending
|
||||
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Append);
|
||||
} else {
|
||||
// Open exclusively for writing
|
||||
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Write);
|
||||
}
|
||||
} else if (rdwr) {
|
||||
// Read and write
|
||||
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::ReadWrite);
|
||||
if (append) {
|
||||
// Open for reading and appending
|
||||
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::ReadAppend);
|
||||
} else {
|
||||
// Open for reading and writing
|
||||
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::ReadWrite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -303,6 +318,9 @@ s64 PS4_SYSV_ABI write(s32 fd, const void* buf, u64 nbytes) {
|
||||
} else if (file->type == Core::FileSys::FileType::Socket) {
|
||||
// Socket functions handle errnos internally.
|
||||
return file->socket->SendPacket(buf, nbytes, 0, nullptr, 0);
|
||||
} else if (file->type == Core::FileSys::FileType::Directory) {
|
||||
*__Error() = POSIX_EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return file->f.WriteRaw<u8>(buf, nbytes);
|
||||
@ -354,6 +372,12 @@ s64 PS4_SYSV_ABI readv(s32 fd, const OrbisKernelIovec* iov, s32 iovcnt) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (file->f.IsWriteOnly()) {
|
||||
*__Error() = POSIX_EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
s64 total_read = 0;
|
||||
for (s32 i = 0; i < iovcnt; i++) {
|
||||
total_read += ReadFile(file->f, iov[i].iov_base, iov[i].iov_len);
|
||||
@ -391,7 +415,11 @@ s64 PS4_SYSV_ABI writev(s32 fd, const OrbisKernelIovec* iov, s32 iovcnt) {
|
||||
return -1;
|
||||
}
|
||||
return result;
|
||||
} else if (file->type == Core::FileSys::FileType::Directory) {
|
||||
*__Error() = POSIX_EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
s64 total_written = 0;
|
||||
for (s32 i = 0; i < iovcnt; i++) {
|
||||
total_written += file->f.WriteRaw<u8>(iov[i].iov_base, iov[i].iov_len);
|
||||
@ -509,6 +537,12 @@ s64 PS4_SYSV_ABI read(s32 fd, void* buf, u64 nbytes) {
|
||||
// Socket functions handle errnos internally.
|
||||
return file->socket->ReceivePacket(buf, nbytes, 0, nullptr, 0);
|
||||
}
|
||||
|
||||
if (file->f.IsWriteOnly()) {
|
||||
*__Error() = POSIX_EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ReadFile(file->f, buf, nbytes);
|
||||
}
|
||||
|
||||
@ -527,6 +561,10 @@ s64 PS4_SYSV_ABI sceKernelRead(s32 fd, void* buf, u64 nbytes) {
|
||||
|
||||
s32 PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) {
|
||||
LOG_INFO(Kernel_Fs, "path = {} mode = {:#o}", path, mode);
|
||||
if (strlen(path) > 255) {
|
||||
*__Error() = POSIX_ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
if (path == nullptr) {
|
||||
*__Error() = POSIX_ENOTDIR;
|
||||
return -1;
|
||||
@ -536,7 +574,7 @@ s32 PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) {
|
||||
bool ro = false;
|
||||
const auto dir_name = mnt->GetHostPath(path, &ro);
|
||||
|
||||
if (std::filesystem::exists(dir_name)) {
|
||||
if (fs::exists(dir_name)) {
|
||||
*__Error() = POSIX_EEXIST;
|
||||
return -1;
|
||||
}
|
||||
@ -548,12 +586,12 @@ s32 PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) {
|
||||
|
||||
// CUSA02456: path = /aotl after sceSaveDataMount(mode = 1)
|
||||
std::error_code ec;
|
||||
if (dir_name.empty() || !std::filesystem::create_directory(dir_name, ec)) {
|
||||
if (dir_name.empty() || !fs::create_directory(dir_name, ec)) {
|
||||
*__Error() = POSIX_EIO;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(dir_name)) {
|
||||
if (!fs::exists(dir_name)) {
|
||||
*__Error() = POSIX_ENOENT;
|
||||
return -1;
|
||||
}
|
||||
@ -570,28 +608,32 @@ s32 PS4_SYSV_ABI sceKernelMkdir(const char* path, u16 mode) {
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI posix_rmdir(const char* path) {
|
||||
if (strlen(path) > 255) {
|
||||
*__Error() = POSIX_ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
bool ro = false;
|
||||
|
||||
const std::filesystem::path dir_name = mnt->GetHostPath(path, &ro);
|
||||
const fs::path dir_name = mnt->GetHostPath(path, &ro);
|
||||
|
||||
if (ro) {
|
||||
*__Error() = POSIX_EROFS;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (dir_name.empty() || !std::filesystem::is_directory(dir_name)) {
|
||||
if (dir_name.empty() || !fs::is_directory(dir_name)) {
|
||||
*__Error() = POSIX_ENOTDIR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(dir_name)) {
|
||||
if (!fs::exists(dir_name)) {
|
||||
*__Error() = POSIX_ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
s32 result = std::filesystem::remove_all(dir_name, ec);
|
||||
s32 result = fs::remove_all(dir_name, ec);
|
||||
|
||||
if (ec) {
|
||||
*__Error() = POSIX_EIO;
|
||||
@ -611,26 +653,42 @@ s32 PS4_SYSV_ABI sceKernelRmdir(const char* path) {
|
||||
|
||||
s32 PS4_SYSV_ABI posix_stat(const char* path, OrbisKernelStat* sb) {
|
||||
LOG_DEBUG(Kernel_Fs, "(PARTIAL) path = {}", path);
|
||||
if (strlen(path) > 255) {
|
||||
*__Error() = POSIX_ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
const auto path_name = mnt->GetHostPath(path);
|
||||
std::memset(sb, 0, sizeof(OrbisKernelStat));
|
||||
const bool is_dir = std::filesystem::is_directory(path_name);
|
||||
const bool is_file = std::filesystem::is_regular_file(path_name);
|
||||
const bool is_dir = fs::is_directory(path_name);
|
||||
const bool is_file = fs::is_regular_file(path_name);
|
||||
if (!is_dir && !is_file) {
|
||||
*__Error() = POSIX_ENOENT;
|
||||
return -1;
|
||||
}
|
||||
if (std::filesystem::is_directory(path_name)) {
|
||||
|
||||
// get the difference between file clock and system clock
|
||||
const auto now_sys = std::chrono::system_clock::now();
|
||||
const auto now_file = fs::file_time_type::clock::now();
|
||||
// calculate the file modified time
|
||||
const auto mtime = fs::last_write_time(path_name);
|
||||
const auto mtimestamp = now_sys + (mtime - now_file);
|
||||
|
||||
if (fs::is_directory(path_name)) {
|
||||
sb->st_mode = 0000777u | 0040000u;
|
||||
sb->st_size = 65536;
|
||||
sb->st_blksize = 65536;
|
||||
sb->st_blocks = 128;
|
||||
sb->st_mtim.tv_sec =
|
||||
std::chrono::duration_cast<std::chrono::seconds>(mtimestamp.time_since_epoch()).count();
|
||||
// TODO incomplete
|
||||
} else {
|
||||
sb->st_mode = 0000777u | 0100000u;
|
||||
sb->st_size = static_cast<s64>(std::filesystem::file_size(path_name));
|
||||
sb->st_size = static_cast<s64>(fs::file_size(path_name));
|
||||
sb->st_blksize = 512;
|
||||
sb->st_blocks = (sb->st_size + 511) / 512;
|
||||
sb->st_mtim.tv_sec =
|
||||
std::chrono::duration_cast<std::chrono::seconds>(mtimestamp.time_since_epoch()).count();
|
||||
// TODO incomplete
|
||||
}
|
||||
|
||||
@ -647,6 +705,10 @@ s32 PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) {
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelCheckReachability(const char* path) {
|
||||
if (strlen(path) > 255) {
|
||||
return ORBIS_KERNEL_ERROR_ENAMETOOLONG;
|
||||
}
|
||||
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
std::string_view guest_path{path};
|
||||
for (const auto& prefix : available_device | std::views::keys) {
|
||||
@ -655,7 +717,7 @@ s32 PS4_SYSV_ABI sceKernelCheckReachability(const char* path) {
|
||||
}
|
||||
}
|
||||
const auto path_name = mnt->GetHostPath(guest_path);
|
||||
if (!std::filesystem::exists(path_name)) {
|
||||
if (!fs::exists(path_name)) {
|
||||
return ORBIS_KERNEL_ERROR_ENOENT;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
@ -768,7 +830,15 @@ s32 PS4_SYSV_ABI posix_rename(const char* from, const char* to) {
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
bool ro = false;
|
||||
const auto src_path = mnt->GetHostPath(from, &ro);
|
||||
if (!std::filesystem::exists(src_path)) {
|
||||
if (strlen(from) > 255) {
|
||||
*__Error() = POSIX_ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
if (strlen(to) > 255) {
|
||||
*__Error() = POSIX_ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
if (!fs::exists(src_path)) {
|
||||
*__Error() = POSIX_ENOENT;
|
||||
return -1;
|
||||
}
|
||||
@ -781,36 +851,36 @@ s32 PS4_SYSV_ABI posix_rename(const char* from, const char* to) {
|
||||
*__Error() = POSIX_EROFS;
|
||||
return -1;
|
||||
}
|
||||
const bool src_is_dir = std::filesystem::is_directory(src_path);
|
||||
const bool dst_is_dir = std::filesystem::is_directory(dst_path);
|
||||
if (src_is_dir && !dst_is_dir) {
|
||||
*__Error() = POSIX_ENOTDIR;
|
||||
return -1;
|
||||
}
|
||||
if (!src_is_dir && dst_is_dir) {
|
||||
*__Error() = POSIX_EISDIR;
|
||||
return -1;
|
||||
}
|
||||
if (dst_is_dir && !std::filesystem::is_empty(dst_path)) {
|
||||
*__Error() = POSIX_ENOTEMPTY;
|
||||
return -1;
|
||||
const bool src_is_dir = fs::is_directory(src_path);
|
||||
const bool dst_is_dir = fs::is_directory(dst_path);
|
||||
|
||||
if (fs::exists(dst_path)) {
|
||||
if (src_is_dir && !dst_is_dir) {
|
||||
*__Error() = POSIX_ENOTDIR;
|
||||
return -1;
|
||||
}
|
||||
if (!src_is_dir && dst_is_dir) {
|
||||
*__Error() = POSIX_EISDIR;
|
||||
return -1;
|
||||
}
|
||||
if (dst_is_dir && !fs::is_empty(dst_path)) {
|
||||
*__Error() = POSIX_ENOTEMPTY;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// On Windows, std::filesystem::rename will error if the file has been opened before.
|
||||
std::filesystem::copy(src_path, dst_path, std::filesystem::copy_options::overwrite_existing);
|
||||
// On Windows, fs::rename will error if the file has been opened before.
|
||||
fs::copy(src_path, dst_path,
|
||||
fs::copy_options::overwrite_existing | fs::copy_options::recursive);
|
||||
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
|
||||
auto file = h->GetFile(src_path);
|
||||
if (file) {
|
||||
// We need to force ReadWrite if the file had Write access before
|
||||
// Otherwise f.Open will clear the file contents.
|
||||
auto access_mode = file->f.GetAccessMode() == Common::FS::FileAccessMode::Write
|
||||
? Common::FS::FileAccessMode::ReadWrite
|
||||
: file->f.GetAccessMode();
|
||||
auto access_mode = file->f.GetAccessMode();
|
||||
file->f.Close();
|
||||
std::filesystem::remove(src_path);
|
||||
fs::remove(src_path);
|
||||
file->f.Open(dst_path, access_mode);
|
||||
} else {
|
||||
std::filesystem::remove(src_path);
|
||||
fs::remove_all(src_path);
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
@ -855,6 +925,11 @@ s64 PS4_SYSV_ABI posix_preadv(s32 fd, OrbisKernelIovec* iov, s32 iovcnt, s64 off
|
||||
return result;
|
||||
}
|
||||
|
||||
if (file->f.IsWriteOnly()) {
|
||||
*__Error() = POSIX_EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
const s64 pos = file->f.Tell();
|
||||
SCOPE_EXIT {
|
||||
file->f.Seek(pos);
|
||||
@ -1014,7 +1089,11 @@ s64 PS4_SYSV_ABI posix_pwritev(s32 fd, const OrbisKernelIovec* iov, s32 iovcnt,
|
||||
return -1;
|
||||
}
|
||||
return result;
|
||||
} else if (file->type == Core::FileSys::FileType::Directory) {
|
||||
*__Error() = POSIX_EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
const s64 pos = file->f.Tell();
|
||||
SCOPE_EXIT {
|
||||
file->f.Seek(pos);
|
||||
@ -1054,6 +1133,10 @@ s64 PS4_SYSV_ABI sceKernelPwritev(s32 fd, const OrbisKernelIovec* iov, s32 iovcn
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI posix_unlink(const char* path) {
|
||||
if (strlen(path) > 255) {
|
||||
*__Error() = POSIX_ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
if (path == nullptr) {
|
||||
*__Error() = POSIX_EINVAL;
|
||||
return -1;
|
||||
@ -1074,7 +1157,7 @@ s32 PS4_SYSV_ABI posix_unlink(const char* path) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (std::filesystem::is_directory(host_path)) {
|
||||
if (fs::is_directory(host_path)) {
|
||||
*__Error() = POSIX_EPERM;
|
||||
return -1;
|
||||
}
|
||||
@ -1447,6 +1530,7 @@ void RegisterFileSystem(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("FCcmRZhWtOk", "libkernel", 1, "libkernel", posix_pwritev);
|
||||
LIB_FUNCTION("nKWi-N2HBV4", "libkernel", 1, "libkernel", sceKernelPwrite);
|
||||
LIB_FUNCTION("mBd4AfLP+u8", "libkernel", 1, "libkernel", sceKernelPwritev);
|
||||
LIB_FUNCTION("VAzswvTOCzI", "libkernel", 1, "libkernel", posix_unlink);
|
||||
LIB_FUNCTION("AUXVxWeJU-A", "libkernel", 1, "libkernel", sceKernelUnlink);
|
||||
LIB_FUNCTION("T8fER+tIGgk", "libScePosix", 1, "libkernel", posix_select);
|
||||
LIB_FUNCTION("T8fER+tIGgk", "libkernel", 1, "libkernel", posix_select);
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
#include "core/libraries/kernel/process.h"
|
||||
#include "core/libraries/kernel/threads.h"
|
||||
#include "core/libraries/kernel/threads/exception.h"
|
||||
#include "core/libraries/kernel/threads/pthread.h"
|
||||
#include "core/libraries/kernel/time.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/network/sys_net.h"
|
||||
@ -235,15 +236,24 @@ s32 PS4_SYSV_ABI sceKernelSetGPO() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelGetAllowedSdkVersionOnSystem(s32* ver) {
|
||||
if (ver == nullptr) {
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
// Returns the highest game SDK version this PS4 allows.
|
||||
*ver = CURRENT_FIRMWARE_VERSION | 0xfff;
|
||||
LOG_INFO(Lib_Kernel, "called, returned sw version: {}", *ver);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelGetSystemSwVersion(SwVersionStruct* ret) {
|
||||
if (ret == nullptr) {
|
||||
return ORBIS_OK; // but why?
|
||||
return ORBIS_OK;
|
||||
}
|
||||
ASSERT(ret->struct_size == 40);
|
||||
u32 fake_fw = Common::ElfInfo::Instance().RawFirmwareVer();
|
||||
u32 fake_fw = CURRENT_FIRMWARE_VERSION;
|
||||
ret->hex_representation = fake_fw;
|
||||
std::snprintf(ret->text_representation, 28, "%2x.%03x.%03x", fake_fw >> 0x18,
|
||||
fake_fw >> 0xc & 0xfff, fake_fw & 0xfff); // why %2x?
|
||||
fake_fw >> 0xc & 0xfff, fake_fw & 0xfff);
|
||||
LOG_INFO(Lib_Kernel, "called, returned sw version: {}", ret->text_representation);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@ -256,6 +266,38 @@ const char** PS4_SYSV_ABI getargv() {
|
||||
return entry_params.argv;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI get_authinfo(s32 pid, AuthInfoData* p2) {
|
||||
LOG_WARNING(Lib_Kernel, "(STUBBED) called, pid: {}", pid);
|
||||
if (p2 == nullptr) {
|
||||
*Kernel::__Error() = POSIX_EPERM;
|
||||
return -1;
|
||||
}
|
||||
if (pid != 0 && pid != GLOBAL_PID) {
|
||||
*Kernel::__Error() = POSIX_ESRCH;
|
||||
return -1;
|
||||
}
|
||||
|
||||
*p2 = {};
|
||||
p2->caps[0] = 0x2000000000000000;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelGetAppInfo(s32 pid, OrbisKernelAppInfo* app_info) {
|
||||
LOG_WARNING(Lib_Kernel, "(STUBBED) called, pid: {}", pid);
|
||||
if (pid != GLOBAL_PID) {
|
||||
return ORBIS_KERNEL_ERROR_EPERM;
|
||||
}
|
||||
if (app_info == nullptr) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
auto& game_info = Common::ElfInfo::Instance();
|
||||
*app_info = {};
|
||||
app_info->has_param_sfo = 1;
|
||||
strncpy(app_info->cusa_name, game_info.GameSerial().data(), 10);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
service_thread = std::jthread{KernelServiceThread};
|
||||
|
||||
@ -272,7 +314,10 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
|
||||
LIB_OBJ("f7uOxY9mM1U", "libkernel", 1, "libkernel", &g_stack_chk_guard);
|
||||
LIB_FUNCTION("D4yla3vx4tY", "libkernel", 1, "libkernel", sceKernelError);
|
||||
LIB_FUNCTION("YeU23Szo3BM", "libkernel", 1, "libkernel", sceKernelGetAllowedSdkVersionOnSystem);
|
||||
LIB_FUNCTION("Mv1zUObHvXI", "libkernel", 1, "libkernel", sceKernelGetSystemSwVersion);
|
||||
LIB_FUNCTION("igMefp4SAv0", "libkernel", 1, "libkernel", get_authinfo);
|
||||
LIB_FUNCTION("G-MYv5erXaU", "libkernel", 1, "libkernel", sceKernelGetAppInfo);
|
||||
LIB_FUNCTION("PfccT7qURYE", "libkernel", 1, "libkernel", kernel_ioctl);
|
||||
LIB_FUNCTION("wW+k21cmbwQ", "libkernel", 1, "libkernel", kernel_ioctl);
|
||||
LIB_FUNCTION("JGfTMBOdUJo", "libkernel", 1, "libkernel", sceKernelGetFsSandboxRandomWord);
|
||||
|
||||
@ -36,6 +36,8 @@ struct OrbisWrapperImpl<PS4_SYSV_ABI R (*)(Args...), f> {
|
||||
|
||||
#define ORBIS(func) (Libraries::Kernel::OrbisWrapperImpl<decltype(&(func)), func>::wrap)
|
||||
|
||||
#define CURRENT_FIRMWARE_VERSION 0x13020011
|
||||
|
||||
s32* PS4_SYSV_ABI __Error();
|
||||
|
||||
struct SwVersionStruct {
|
||||
@ -44,6 +46,37 @@ struct SwVersionStruct {
|
||||
u32 hex_representation;
|
||||
};
|
||||
|
||||
struct AuthInfoData {
|
||||
u64 paid;
|
||||
u64 caps[4];
|
||||
u64 attrs[4];
|
||||
u64 ucred[8];
|
||||
};
|
||||
|
||||
struct OrbisKernelTitleWorkaround {
|
||||
s32 version;
|
||||
s32 align;
|
||||
u64 ids[2];
|
||||
};
|
||||
|
||||
struct OrbisKernelAppInfo {
|
||||
s32 app_id;
|
||||
s32 mmap_flags;
|
||||
s32 attribute_exe;
|
||||
s32 attribute2;
|
||||
char cusa_name[10];
|
||||
u8 debug_level;
|
||||
u8 slv_flags;
|
||||
u8 mini_app_dmem_flags;
|
||||
u8 render_mode;
|
||||
u8 mdbg_out;
|
||||
u8 required_hdcp_type;
|
||||
u64 preload_prx_flags;
|
||||
s32 attribute1;
|
||||
s32 has_param_sfo;
|
||||
OrbisKernelTitleWorkaround title_workaround;
|
||||
};
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
|
||||
} // namespace Libraries::Kernel
|
||||
|
||||
@ -5,24 +5,35 @@
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/elf_info.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/libraries/kernel/kernel.h"
|
||||
#include "core/libraries/kernel/memory.h"
|
||||
#include "core/libraries/kernel/orbis_error.h"
|
||||
#include "core/libraries/kernel/process.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/linker.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace Libraries::Kernel {
|
||||
|
||||
static s32 g_sdk_version = -1;
|
||||
static bool g_alias_dmem = false;
|
||||
|
||||
u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize() {
|
||||
LOG_TRACE(Kernel_Vmm, "called");
|
||||
const auto* memory = Core::Memory::Instance();
|
||||
return memory->GetTotalDirectSize();
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelEnableDmemAliasing() {
|
||||
LOG_DEBUG(Kernel_Vmm, "called");
|
||||
g_alias_dmem = true;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len,
|
||||
u64 alignment, s32 memoryType, s64* physAddrOut) {
|
||||
if (searchStart < 0 || searchEnd < 0) {
|
||||
@ -197,8 +208,14 @@ s32 PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, s32 prot, s
|
||||
const VAddr in_addr = reinterpret_cast<VAddr>(*addr);
|
||||
|
||||
auto* memory = Core::Memory::Instance();
|
||||
const auto ret = memory->MapMemory(addr, in_addr, len, mem_prot, map_flags,
|
||||
Core::VMAType::Direct, name, false, phys_addr, alignment);
|
||||
bool should_check = false;
|
||||
if (g_sdk_version >= Common::ElfInfo::FW_25 && False(map_flags & Core::MemoryMapFlags::Stack)) {
|
||||
// Under these conditions, this would normally redirect to sceKernelMapDirectMemory2.
|
||||
should_check = !g_alias_dmem;
|
||||
}
|
||||
const auto ret =
|
||||
memory->MapMemory(addr, in_addr, len, mem_prot, map_flags, Core::VMAType::Direct, name,
|
||||
should_check, phys_addr, alignment);
|
||||
|
||||
LOG_INFO(Kernel_Vmm, "out_addr = {}", fmt::ptr(*addr));
|
||||
return ret;
|
||||
@ -244,8 +261,9 @@ s32 PS4_SYSV_ABI sceKernelMapDirectMemory2(void** addr, u64 len, s32 type, s32 p
|
||||
const VAddr in_addr = reinterpret_cast<VAddr>(*addr);
|
||||
|
||||
auto* memory = Core::Memory::Instance();
|
||||
const auto ret = memory->MapMemory(addr, in_addr, len, mem_prot, map_flags,
|
||||
Core::VMAType::Direct, "anon", true, phys_addr, alignment);
|
||||
const auto ret =
|
||||
memory->MapMemory(addr, in_addr, len, mem_prot, map_flags, Core::VMAType::Direct, "anon",
|
||||
!g_alias_dmem, phys_addr, alignment);
|
||||
|
||||
if (ret == 0) {
|
||||
// If the map call succeeds, set the direct memory type using the output address.
|
||||
@ -668,10 +686,21 @@ void* PS4_SYSV_ABI posix_mmap(void* addr, u64 len, s32 prot, s32 flags, s32 fd,
|
||||
}
|
||||
|
||||
s32 result = ORBIS_OK;
|
||||
if (fd == -1) {
|
||||
if (True(mem_flags & Core::MemoryMapFlags::Anon)) {
|
||||
// Maps flexible memory
|
||||
result = memory->MapMemory(&addr_out, aligned_addr, aligned_size, mem_prot, mem_flags,
|
||||
Core::VMAType::Flexible, "anon", false);
|
||||
} else if (True(mem_flags & Core::MemoryMapFlags::Stack)) {
|
||||
// Maps stack memory
|
||||
result = memory->MapMemory(&addr_out, aligned_addr, aligned_size, mem_prot, mem_flags,
|
||||
Core::VMAType::Stack, "anon", false);
|
||||
} else if (True(mem_flags & Core::MemoryMapFlags::Void)) {
|
||||
// Reserves memory
|
||||
result =
|
||||
memory->MapMemory(&addr_out, aligned_addr, aligned_size, Core::MemoryProt::NoAccess,
|
||||
mem_flags, Core::VMAType::Reserved, "anon", false);
|
||||
} else {
|
||||
// Default to file mapping
|
||||
result = memory->MapFile(&addr_out, aligned_addr, aligned_size, mem_prot, mem_flags, fd,
|
||||
phys_addr);
|
||||
}
|
||||
@ -769,6 +798,12 @@ s32 PS4_SYSV_ABI sceKernelGetPrtAperture(s32 id, VAddr* address, u64* size) {
|
||||
}
|
||||
|
||||
void RegisterMemory(Core::Loader::SymbolsResolver* sym) {
|
||||
ASSERT_MSG(sceKernelGetCompiledSdkVersion(&g_sdk_version) == ORBIS_OK,
|
||||
"Failed to get compiled SDK verision.");
|
||||
|
||||
LIB_FUNCTION("usHTMoFoBTM", "libkernel_dmem_aliasing2", 1, "libkernel",
|
||||
sceKernelEnableDmemAliasing);
|
||||
LIB_FUNCTION("usHTMoFoBTM", "libkernel", 1, "libkernel", sceKernelEnableDmemAliasing);
|
||||
LIB_FUNCTION("rTXw65xmLIA", "libkernel", 1, "libkernel", sceKernelAllocateDirectMemory);
|
||||
LIB_FUNCTION("B+vc2AO2Zrc", "libkernel", 1, "libkernel", sceKernelAllocateMainDirectMemory);
|
||||
LIB_FUNCTION("C0f7TJcbfac", "libkernel", 1, "libkernel", sceKernelAvailableDirectMemorySize);
|
||||
|
||||
@ -21,13 +21,42 @@ s32 PS4_SYSV_ABI sceKernelIsNeoMode() {
|
||||
Common::ElfInfo::Instance().GetPSFAttributes().support_neo_mode;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelHasNeoMode() {
|
||||
return Config::isNeoModeConsole();
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelGetMainSocId() {
|
||||
// These hardcoded values are based on hardware observations.
|
||||
// Different models of PS4/PS4 Pro likely return slightly different values.
|
||||
LOG_DEBUG(Lib_Kernel, "called");
|
||||
if (Config::isNeoModeConsole()) {
|
||||
return 0x740f30;
|
||||
}
|
||||
return 0x710f10;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(s32* ver) {
|
||||
s32 version = Common::ElfInfo::Instance().RawFirmwareVer();
|
||||
s32 version = Common::ElfInfo::Instance().CompiledSdkVer();
|
||||
*ver = version;
|
||||
return (version >= 0) ? ORBIS_OK : ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelGetCpumode() {
|
||||
LOG_DEBUG(Lib_Kernel, "called");
|
||||
auto& attrs = Common::ElfInfo::Instance().GetPSFAttributes();
|
||||
u32 is_cpu6 = attrs.six_cpu_mode.Value();
|
||||
u32 is_cpu7 = attrs.seven_cpu_mode.Value();
|
||||
if (is_cpu6 == 1 && is_cpu7 == 1) {
|
||||
return 2;
|
||||
}
|
||||
if (is_cpu7 == 1) {
|
||||
return 5;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelGetCurrentCpu() {
|
||||
LOG_DEBUG(Lib_Kernel, "called");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -208,7 +237,10 @@ void RegisterProcess(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("xeu-pV8wkKs", "libkernel", 1, "libkernel", sceKernelIsInSandbox);
|
||||
LIB_FUNCTION("WB66evu8bsU", "libkernel", 1, "libkernel", sceKernelGetCompiledSdkVersion);
|
||||
LIB_FUNCTION("WslcK1FQcGI", "libkernel", 1, "libkernel", sceKernelIsNeoMode);
|
||||
LIB_FUNCTION("rNRtm1uioyY", "libkernel", 1, "libkernel", sceKernelHasNeoMode);
|
||||
LIB_FUNCTION("0vTn5IDMU9A", "libkernel", 1, "libkernel", sceKernelGetMainSocId);
|
||||
LIB_FUNCTION("VOx8NGmHXTs", "libkernel", 1, "libkernel", sceKernelGetCpumode);
|
||||
LIB_FUNCTION("g0VTBxfJyu0", "libkernel", 1, "libkernel", sceKernelGetCurrentCpu);
|
||||
LIB_FUNCTION("959qrazPIrg", "libkernel", 1, "libkernel", sceKernelGetProcParam);
|
||||
LIB_FUNCTION("wzvqT4UqKX8", "libkernel", 1, "libkernel", sceKernelLoadStartModule);
|
||||
LIB_FUNCTION("LwG8g3niqwA", "libkernel", 1, "libkernel", sceKernelDlsym);
|
||||
|
||||
@ -354,6 +354,8 @@ void RegisterCond(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("Op8TBGY5KHg", "libkernel", 1, "libkernel", posix_pthread_cond_wait);
|
||||
LIB_FUNCTION("mkx2fVhNMsg", "libkernel", 1, "libkernel", posix_pthread_cond_broadcast);
|
||||
LIB_FUNCTION("2MOy+rUfuhQ", "libkernel", 1, "libkernel", posix_pthread_cond_signal);
|
||||
LIB_FUNCTION("RXXqi4CtF8w", "libkernel", 1, "libkernel", posix_pthread_cond_destroy);
|
||||
LIB_FUNCTION("27bAgiJmOh0", "libkernel", 1, "libkernel", posix_pthread_cond_timedwait);
|
||||
LIB_FUNCTION("mKoTx03HRWA", "libkernel", 1, "libkernel", posix_pthread_condattr_init);
|
||||
LIB_FUNCTION("dJcuQVn6-Iw", "libkernel", 1, "libkernel", posix_pthread_condattr_destroy);
|
||||
|
||||
|
||||
@ -173,6 +173,8 @@ void RegisterException(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("OMDRKKAZ8I4", "libkernel", 1, "libkernel", sceKernelDebugRaiseException);
|
||||
LIB_FUNCTION("zE-wXIZjLoM", "libkernel", 1, "libkernel",
|
||||
sceKernelDebugRaiseExceptionOnReleaseMode);
|
||||
LIB_FUNCTION("WkwEd3N7w0Y", "libkernel", 1, "libkernel", sceKernelInstallExceptionHandler);
|
||||
LIB_FUNCTION("Qhv5ARAoOEc", "libkernel", 1, "libkernel", sceKernelRemoveExceptionHandler);
|
||||
}
|
||||
|
||||
} // namespace Libraries::Kernel
|
||||
|
||||
@ -442,6 +442,8 @@ void RegisterMutex(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("ltCfaGr2JGE", "libkernel", 1, "libkernel", posix_pthread_mutex_destroy);
|
||||
LIB_FUNCTION("dQHWEsJtoE4", "libkernel", 1, "libkernel", posix_pthread_mutexattr_init);
|
||||
LIB_FUNCTION("mDmgMOGVUqg", "libkernel", 1, "libkernel", posix_pthread_mutexattr_settype);
|
||||
LIB_FUNCTION("HF7lK46xzjY", "libkernel", 1, "libkernel", posix_pthread_mutexattr_destroy);
|
||||
LIB_FUNCTION("K-jXhbt2gn4", "libkernel", 1, "libkernel", posix_pthread_mutex_trylock);
|
||||
|
||||
// Orbis
|
||||
LIB_FUNCTION("cmo1RIYva9o", "libkernel", 1, "libkernel", ORBIS(scePthreadMutexInit));
|
||||
|
||||
@ -337,7 +337,7 @@ void PS4_SYSV_ABI sched_yield() {
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_getpid() {
|
||||
return g_curthread->tid;
|
||||
return GLOBAL_PID;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_once(PthreadOnce* once_control,
|
||||
@ -663,6 +663,13 @@ void RegisterThread(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("Z4QosVuAsA0", "libkernel", 1, "libkernel", posix_pthread_once);
|
||||
LIB_FUNCTION("EotR8a3ASf4", "libkernel", 1, "libkernel", posix_pthread_self);
|
||||
LIB_FUNCTION("OxhIB8LB-PQ", "libkernel", 1, "libkernel", posix_pthread_create);
|
||||
LIB_FUNCTION("lZzFeSxPl08", "libkernel", 1, "libkernel", posix_pthread_setcancelstate);
|
||||
LIB_FUNCTION("CBNtXOoef-E", "libkernel", 1, "libkernel", posix_sched_get_priority_max);
|
||||
LIB_FUNCTION("m0iS6jNsXds", "libkernel", 1, "libkernel", posix_sched_get_priority_min);
|
||||
LIB_FUNCTION("Xs9hdiD7sAA", "libkernel", 1, "libkernel", posix_pthread_setschedparam);
|
||||
LIB_FUNCTION("+U1R4WtXvoc", "libkernel", 1, "libkernel", posix_pthread_detach);
|
||||
LIB_FUNCTION("7Xl257M4VNI", "libkernel", 1, "libkernel", posix_pthread_equal);
|
||||
LIB_FUNCTION("h9CcP3J0oVM", "libkernel", 1, "libkernel", posix_pthread_join);
|
||||
LIB_FUNCTION("Jb2uGFMr688", "libkernel", 1, "libkernel", posix_pthread_getaffinity_np);
|
||||
LIB_FUNCTION("5KWrg7-ZqvE", "libkernel", 1, "libkernel", posix_pthread_setaffinity_np);
|
||||
LIB_FUNCTION("3eqs37G74-s", "libkernel", 1, "libkernel", posix_pthread_getthreadid_np);
|
||||
|
||||
@ -16,6 +16,8 @@
|
||||
#include "core/thread.h"
|
||||
#include "core/tls.h"
|
||||
|
||||
#define GLOBAL_PID 0xBAD1
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <ctime>
|
||||
#include <thread>
|
||||
|
||||
#include "common/assert.h"
|
||||
@ -163,7 +164,7 @@ s32 PS4_SYSV_ABI posix_clock_gettime(u32 clock_id, OrbisKernelTimespec* ts) {
|
||||
case ORBIS_CLOCK_MONOTONIC_FAST: {
|
||||
static LARGE_INTEGER pf = [] {
|
||||
LARGE_INTEGER res{};
|
||||
QueryPerformanceFrequency(&pf);
|
||||
QueryPerformanceFrequency(&res);
|
||||
return res;
|
||||
}();
|
||||
|
||||
@ -485,25 +486,41 @@ Common::NativeClock* GetClock() {
|
||||
s32 PS4_SYSV_ABI sceKernelConvertUtcToLocaltime(time_t time, time_t* local_time,
|
||||
struct OrbisTimesec* st, u64* dst_sec) {
|
||||
LOG_TRACE(Kernel, "Called");
|
||||
#ifdef _WIN32
|
||||
TIME_ZONE_INFORMATION tz{};
|
||||
DWORD res = GetTimeZoneInformation(&tz);
|
||||
*local_time = time - tz.Bias;
|
||||
|
||||
if (st != nullptr) {
|
||||
st->t = time;
|
||||
st->west_sec = -tz.Bias * 60;
|
||||
st->dst_sec = res == TIME_ZONE_ID_DAYLIGHT ? -_dstbias : 0;
|
||||
}
|
||||
|
||||
if (dst_sec != nullptr) {
|
||||
*dst_sec = res == TIME_ZONE_ID_DAYLIGHT ? -_dstbias : 0;
|
||||
}
|
||||
#else
|
||||
#ifdef __APPLE__
|
||||
// std::chrono::current_zone() not available yet.
|
||||
const auto* time_zone = date::current_zone();
|
||||
#else
|
||||
const auto* time_zone = std::chrono::current_zone();
|
||||
#endif
|
||||
#endif // __APPLE__
|
||||
auto info = time_zone->get_info(std::chrono::system_clock::now());
|
||||
|
||||
*local_time = info.offset.count() + info.save.count() * 60 + time;
|
||||
|
||||
if (st != nullptr) {
|
||||
st->t = time;
|
||||
st->west_sec = info.offset.count() * 60;
|
||||
st->west_sec = info.offset.count();
|
||||
st->dst_sec = info.save.count() * 60;
|
||||
}
|
||||
|
||||
if (dst_sec != nullptr) {
|
||||
*dst_sec = info.save.count() * 60;
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@ -565,4 +582,4 @@ void RegisterTime(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("-o5uEDpN+oY", "libkernel", 1, "libkernel", sceKernelConvertUtcToLocaltime);
|
||||
}
|
||||
|
||||
} // namespace Libraries::Kernel
|
||||
} // namespace Libraries::Kernel
|
||||
|
||||
@ -32,6 +32,7 @@
|
||||
#include "core/libraries/network/ssl.h"
|
||||
#include "core/libraries/network/ssl2.h"
|
||||
#include "core/libraries/np/np_auth.h"
|
||||
#include "core/libraries/np/np_commerce.h"
|
||||
#include "core/libraries/np/np_common.h"
|
||||
#include "core/libraries/np/np_manager.h"
|
||||
#include "core/libraries/np/np_party.h"
|
||||
@ -93,6 +94,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
|
||||
Libraries::SysModule::RegisterLib(sym);
|
||||
Libraries::Posix::RegisterLib(sym);
|
||||
Libraries::AudioIn::RegisterLib(sym);
|
||||
Libraries::Np::NpCommerce::RegisterLib(sym);
|
||||
Libraries::Np::NpCommon::RegisterLib(sym);
|
||||
Libraries::Np::NpManager::RegisterLib(sym);
|
||||
Libraries::Np::NpScore::RegisterLib(sym);
|
||||
|
||||
@ -9,6 +9,35 @@
|
||||
|
||||
namespace Libraries::Http {
|
||||
|
||||
static bool g_isHttpInitialized = true; // TODO temp always inited
|
||||
|
||||
void NormalizeAndAppendPath(char* dest, char* src) {
|
||||
char* lastSlash;
|
||||
u64 length;
|
||||
|
||||
lastSlash = strrchr(dest, '/');
|
||||
if (lastSlash == NULL) {
|
||||
length = strlen(dest);
|
||||
dest[length] = '/';
|
||||
dest[length + 1] = '\0';
|
||||
} else {
|
||||
lastSlash[1] = '\0';
|
||||
}
|
||||
if (*src == '/') {
|
||||
dest[0] = '\0';
|
||||
}
|
||||
length = strnlen(dest, 0x3fff);
|
||||
strncat(dest, src, 0x3fff - length);
|
||||
return;
|
||||
}
|
||||
|
||||
int HttpRequestInternal_Acquire(HttpRequestInternal** outRequest, u32 requestId) {
|
||||
return 0; // TODO dummy
|
||||
}
|
||||
int HttpRequestInternal_Release(HttpRequestInternal* request) {
|
||||
return 0; // TODO dummy
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceHttpAbortRequest() {
|
||||
LOG_ERROR(Lib_Http, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
@ -34,8 +63,9 @@ int PS4_SYSV_ABI sceHttpAddQuery() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceHttpAddRequestHeader() {
|
||||
LOG_ERROR(Lib_Http, "(STUBBED) called");
|
||||
int PS4_SYSV_ABI sceHttpAddRequestHeader(int id, const char* name, const char* value, s32 mode) {
|
||||
LOG_ERROR(Lib_Http, "(STUBBED) called id= {} name = {} value = {} mode = {}", id,
|
||||
std::string(name), std::string(value), mode);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -84,8 +114,9 @@ int PS4_SYSV_ABI sceHttpCreateConnection() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceHttpCreateConnectionWithURL() {
|
||||
LOG_ERROR(Lib_Http, "(STUBBED) called");
|
||||
int PS4_SYSV_ABI sceHttpCreateConnectionWithURL(int tmplId, const char* url, bool enableKeepalive) {
|
||||
LOG_ERROR(Lib_Http, "(STUBBED) called tmpid = {} url = {} enableKeepalive = {}", tmplId,
|
||||
std::string(url), enableKeepalive ? 1 : 0);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -104,8 +135,10 @@ int PS4_SYSV_ABI sceHttpCreateRequest2() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceHttpCreateRequestWithURL() {
|
||||
LOG_ERROR(Lib_Http, "(STUBBED) called");
|
||||
int PS4_SYSV_ABI sceHttpCreateRequestWithURL(int connId, s32 method, const char* url,
|
||||
u64 contentLength) {
|
||||
LOG_ERROR(Lib_Http, "(STUBBED) called connId = {} method = {} url={} contentLength={}", connId,
|
||||
method, url, contentLength);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -184,7 +217,7 @@ int PS4_SYSV_ABI sceHttpGetAcceptEncodingGZIPEnabled() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceHttpGetAllResponseHeaders() {
|
||||
int PS4_SYSV_ABI sceHttpGetAllResponseHeaders(int reqId, char** header, u64* headerSize) {
|
||||
LOG_ERROR(Lib_Http, "(STUBBED) called");
|
||||
return ORBIS_FAIL;
|
||||
}
|
||||
@ -254,12 +287,42 @@ int PS4_SYSV_ABI sceHttpGetResponseContentLength() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceHttpGetStatusCode() {
|
||||
LOG_ERROR(Lib_Http, "(STUBBED) called");
|
||||
int PS4_SYSV_ABI sceHttpGetStatusCode(int reqId, int* statusCode) {
|
||||
LOG_ERROR(Lib_Http, "(STUBBED) called reqId = {}", reqId);
|
||||
#if 0
|
||||
if (!g_isHttpInitialized)
|
||||
return ORBIS_HTTP_ERROR_BEFORE_INIT;
|
||||
|
||||
if (statusCode == nullptr)
|
||||
return ORBIS_HTTP_ERROR_INVALID_VALUE;
|
||||
|
||||
int ret = 0;
|
||||
// Lookup HttpRequestInternal by reqId
|
||||
HttpRequestInternal* request = nullptr;
|
||||
ret = HttpRequestInternal_Acquire(&request, reqId);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
request->m_mutex.lock();
|
||||
if (request->state > 0x11) {
|
||||
if (request->state == 0x16) {
|
||||
ret = request->errorCode;
|
||||
} else {
|
||||
*statusCode = request->httpStatusCode;
|
||||
ret = 0;
|
||||
}
|
||||
} else {
|
||||
ret = ORBIS_HTTP_ERROR_BEFORE_SEND;
|
||||
}
|
||||
request->m_mutex.unlock();
|
||||
HttpRequestInternal_Release(request);
|
||||
|
||||
return ret;
|
||||
#else
|
||||
return ORBIS_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceHttpInit(int libnetMemId, int libsslCtxId, std::size_t poolSize) {
|
||||
int PS4_SYSV_ABI sceHttpInit(int libnetMemId, int libsslCtxId, u64 poolSize) {
|
||||
LOG_ERROR(Lib_Http, "(DUMMY) called libnetMemId = {} libsslCtxId = {} poolSize = {}",
|
||||
libnetMemId, libsslCtxId, poolSize);
|
||||
// return a value >1
|
||||
@ -267,14 +330,104 @@ int PS4_SYSV_ABI sceHttpInit(int libnetMemId, int libsslCtxId, std::size_t poolS
|
||||
return ++id;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceHttpParseResponseHeader() {
|
||||
int PS4_SYSV_ABI sceHttpParseResponseHeader(const char* header, u64 headerLen, const char* fieldStr,
|
||||
const char** fieldValue, u64* valueLen) {
|
||||
LOG_ERROR(Lib_Http, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceHttpParseStatusLine() {
|
||||
LOG_ERROR(Lib_Http, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
int PS4_SYSV_ABI sceHttpParseStatusLine(const char* statusLine, u64 lineLen, int32_t* httpMajorVer,
|
||||
int32_t* httpMinorVer, int32_t* responseCode,
|
||||
const char** reasonPhrase, u64* phraseLen) {
|
||||
if (!statusLine) {
|
||||
LOG_ERROR(Lib_Http, "Invalid response");
|
||||
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
|
||||
}
|
||||
if (!httpMajorVer || !httpMinorVer || !responseCode || !reasonPhrase || !phraseLen) {
|
||||
LOG_ERROR(Lib_Http, "Invalid value");
|
||||
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_VALUE;
|
||||
}
|
||||
*httpMajorVer = 0;
|
||||
*httpMinorVer = 0;
|
||||
if (lineLen < 8) {
|
||||
LOG_ERROR(Lib_Http, "Linelen is smaller than 8");
|
||||
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
|
||||
}
|
||||
if (strncmp(statusLine, "HTTP/", 5) != 0) {
|
||||
LOG_ERROR(Lib_Http, "statusLine doesn't start with HTTP/");
|
||||
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
|
||||
}
|
||||
|
||||
u64 index = 5;
|
||||
|
||||
if (!isdigit(statusLine[index])) {
|
||||
LOG_ERROR(Lib_Http, "Invalid response");
|
||||
|
||||
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
|
||||
}
|
||||
|
||||
while (isdigit(statusLine[index])) {
|
||||
*httpMajorVer = *httpMajorVer * 10 + (statusLine[index] - '0');
|
||||
index++;
|
||||
}
|
||||
|
||||
if (statusLine[index] != '.') {
|
||||
LOG_ERROR(Lib_Http, "Invalid response");
|
||||
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
|
||||
}
|
||||
index++;
|
||||
|
||||
if (!isdigit(statusLine[index])) {
|
||||
LOG_ERROR(Lib_Http, "Invalid response");
|
||||
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
|
||||
}
|
||||
|
||||
while (isdigit(statusLine[index])) {
|
||||
*httpMinorVer = *httpMinorVer * 10 + (statusLine[index] - '0');
|
||||
index++;
|
||||
}
|
||||
|
||||
if (statusLine[index] != ' ') {
|
||||
LOG_ERROR(Lib_Http, "Invalid response");
|
||||
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
|
||||
}
|
||||
index++;
|
||||
|
||||
// Validate and parse the 3-digit HTTP response code
|
||||
if (lineLen - index < 3 || !isdigit(statusLine[index]) || !isdigit(statusLine[index + 1]) ||
|
||||
!isdigit(statusLine[index + 2])) {
|
||||
LOG_ERROR(Lib_Http, "Invalid response");
|
||||
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
|
||||
}
|
||||
|
||||
*responseCode = (statusLine[index] - '0') * 100 + (statusLine[index + 1] - '0') * 10 +
|
||||
(statusLine[index + 2] - '0');
|
||||
index += 3;
|
||||
|
||||
if (statusLine[index] != ' ') {
|
||||
LOG_ERROR(Lib_Http, "Invalid response");
|
||||
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
|
||||
}
|
||||
index++;
|
||||
|
||||
// Set the reason phrase start position
|
||||
*reasonPhrase = &statusLine[index];
|
||||
u64 phraseStart = index;
|
||||
|
||||
while (index < lineLen && statusLine[index] != '\n') {
|
||||
index++;
|
||||
}
|
||||
|
||||
// Determine the length of the reason phrase, excluding trailing \r if present
|
||||
if (index == phraseStart) {
|
||||
*phraseLen = 0;
|
||||
} else {
|
||||
*phraseLen =
|
||||
(statusLine[index - 1] == '\r') ? (index - phraseStart - 1) : (index - phraseStart);
|
||||
}
|
||||
|
||||
// Return the number of bytes processed
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceHttpReadData() {
|
||||
@ -317,8 +470,8 @@ int PS4_SYSV_ABI sceHttpsEnableOptionPrivate() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceHttpSendRequest() {
|
||||
LOG_ERROR(Lib_Http, "(STUBBED) called");
|
||||
int PS4_SYSV_ABI sceHttpSendRequest(int reqId, const void* postData, u64 size) {
|
||||
LOG_ERROR(Lib_Http, "(STUBBED) called reqId = {} size = {}", reqId, size);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -548,7 +701,8 @@ int PS4_SYSV_ABI sceHttpUnsetEpoll() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceHttpUriBuild() {
|
||||
int PS4_SYSV_ABI sceHttpUriBuild(char* out, u64* require, u64 prepare,
|
||||
const OrbisHttpUriElement* srcElement, u32 option) {
|
||||
LOG_ERROR(Lib_Http, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@ -558,18 +712,155 @@ int PS4_SYSV_ABI sceHttpUriCopy() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceHttpUriEscape() {
|
||||
LOG_ERROR(Lib_Http, "(STUBBED) called");
|
||||
int PS4_SYSV_ABI sceHttpUriEscape(char* out, u64* require, u64 prepare, const char* in) {
|
||||
LOG_TRACE(Lib_Http, "called");
|
||||
|
||||
if (!in) {
|
||||
LOG_ERROR(Lib_Http, "Invalid input string");
|
||||
return ORBIS_HTTP_ERROR_INVALID_VALUE;
|
||||
}
|
||||
|
||||
auto IsUnreserved = [](unsigned char c) -> bool {
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
|
||||
c == '-' || c == '_' || c == '.' || c == '~';
|
||||
};
|
||||
|
||||
u64 needed = 0;
|
||||
const char* src = in;
|
||||
while (*src) {
|
||||
unsigned char c = static_cast<unsigned char>(*src);
|
||||
if (IsUnreserved(c)) {
|
||||
needed++;
|
||||
} else {
|
||||
needed += 3; // %XX format
|
||||
}
|
||||
src++;
|
||||
}
|
||||
needed++; // null terminator
|
||||
|
||||
if (require) {
|
||||
*require = needed;
|
||||
}
|
||||
|
||||
if (!out) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
if (prepare < needed) {
|
||||
LOG_ERROR(Lib_Http, "Buffer too small: need {} but only {} available", needed, prepare);
|
||||
return ORBIS_HTTP_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
static const char hex_chars[] = "0123456789ABCDEF";
|
||||
src = in;
|
||||
char* dst = out;
|
||||
while (*src) {
|
||||
unsigned char c = static_cast<unsigned char>(*src);
|
||||
if (IsUnreserved(c)) {
|
||||
*dst++ = *src;
|
||||
} else {
|
||||
*dst++ = '%';
|
||||
*dst++ = hex_chars[(c >> 4) & 0x0F];
|
||||
*dst++ = hex_chars[c & 0x0F];
|
||||
}
|
||||
src++;
|
||||
}
|
||||
*dst = '\0';
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceHttpUriMerge() {
|
||||
LOG_ERROR(Lib_Http, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
int PS4_SYSV_ABI sceHttpUriMerge(char* mergedUrl, char* url, char* relativeUri, u64* require,
|
||||
u64 prepare, u32 option) {
|
||||
u64 requiredLength;
|
||||
int returnValue;
|
||||
u64 baseUrlLength;
|
||||
u64 relativeUriLength;
|
||||
u64 totalLength;
|
||||
u64 combinedLength;
|
||||
int parseResult;
|
||||
u64 localSizeRelativeUri;
|
||||
u64 localSizeBaseUrl;
|
||||
OrbisHttpUriElement parsedUriElement;
|
||||
|
||||
if (option != 0 || url == NULL || relativeUri == NULL) {
|
||||
LOG_ERROR(Lib_Http, "Invalid value");
|
||||
return ORBIS_HTTP_ERROR_INVALID_VALUE;
|
||||
}
|
||||
|
||||
returnValue = sceHttpUriParse(NULL, url, NULL, &localSizeBaseUrl, 0);
|
||||
if (returnValue < 0) {
|
||||
LOG_ERROR(Lib_Http, "returning {:#x}", returnValue);
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
returnValue = sceHttpUriParse(NULL, relativeUri, NULL, &localSizeRelativeUri, 0);
|
||||
if (returnValue < 0) {
|
||||
LOG_ERROR(Lib_Http, "returning {:#x}", returnValue);
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
baseUrlLength = strnlen(url, 0x3fff);
|
||||
relativeUriLength = strnlen(relativeUri, 0x3fff);
|
||||
requiredLength = localSizeBaseUrl + 2 + (relativeUriLength + baseUrlLength) * 2;
|
||||
|
||||
if (require) {
|
||||
*require = requiredLength;
|
||||
}
|
||||
|
||||
if (mergedUrl == NULL) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
if (prepare < requiredLength) {
|
||||
LOG_ERROR(Lib_Http, "Error Out of memory");
|
||||
return ORBIS_HTTP_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
totalLength = strnlen(url, 0x3fff);
|
||||
baseUrlLength = strnlen(relativeUri, 0x3fff);
|
||||
combinedLength = totalLength + 1 + baseUrlLength;
|
||||
relativeUriLength = prepare - combinedLength;
|
||||
|
||||
returnValue =
|
||||
sceHttpUriParse(&parsedUriElement, relativeUri, mergedUrl + totalLength + baseUrlLength + 1,
|
||||
&localSizeRelativeUri, relativeUriLength);
|
||||
if (returnValue < 0) {
|
||||
LOG_ERROR(Lib_Http, "returning {:#x}", returnValue);
|
||||
return returnValue;
|
||||
}
|
||||
if (parsedUriElement.scheme == NULL) {
|
||||
strncpy(mergedUrl, relativeUri, requiredLength);
|
||||
if (require) {
|
||||
*require = strnlen(relativeUri, 0x3fff) + 1;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
returnValue =
|
||||
sceHttpUriParse(&parsedUriElement, url, mergedUrl + totalLength + baseUrlLength + 1,
|
||||
&localSizeBaseUrl, relativeUriLength);
|
||||
if (returnValue < 0) {
|
||||
LOG_ERROR(Lib_Http, "returning {:#x}", returnValue);
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
combinedLength += localSizeBaseUrl;
|
||||
strncpy(mergedUrl + combinedLength, parsedUriElement.path, prepare - combinedLength);
|
||||
NormalizeAndAppendPath(mergedUrl + combinedLength, relativeUri);
|
||||
|
||||
returnValue = sceHttpUriBuild(mergedUrl, 0, ~(baseUrlLength + totalLength) + prepare,
|
||||
&parsedUriElement, 0x3f);
|
||||
if (returnValue >= 0) {
|
||||
return ORBIS_OK;
|
||||
} else {
|
||||
LOG_ERROR(Lib_Http, "returning {:#x}", returnValue);
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, void* pool,
|
||||
size_t* require, size_t prepare) {
|
||||
u64* require, u64 prepare) {
|
||||
LOG_INFO(Lib_Http, "srcUri = {}", std::string(srcUri));
|
||||
if (!srcUri) {
|
||||
LOG_ERROR(Lib_Http, "invalid url");
|
||||
@ -586,10 +877,10 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
|
||||
}
|
||||
|
||||
// Track the total required buffer size
|
||||
size_t requiredSize = 0;
|
||||
u64 requiredSize = 0;
|
||||
|
||||
// Parse the scheme (e.g., "http:", "https:", "file:")
|
||||
size_t schemeLength = 0;
|
||||
u64 schemeLength = 0;
|
||||
while (srcUri[schemeLength] && srcUri[schemeLength] != ':') {
|
||||
if (!isalnum(srcUri[schemeLength])) {
|
||||
LOG_ERROR(Lib_Http, "invalid url");
|
||||
@ -611,7 +902,7 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
|
||||
requiredSize += schemeLength + 1;
|
||||
|
||||
// Move past the scheme and ':' character
|
||||
size_t offset = schemeLength + 1;
|
||||
u64 offset = schemeLength + 1;
|
||||
|
||||
// Check if "//" appears after the scheme
|
||||
if (strncmp(srcUri + offset, "//", 2) == 0) {
|
||||
@ -638,7 +929,7 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
|
||||
|
||||
// Parse the path (everything after the slashes)
|
||||
char* pathStart = (char*)srcUri + offset;
|
||||
size_t pathLength = 0;
|
||||
u64 pathLength = 0;
|
||||
while (pathStart[pathLength] && pathStart[pathLength] != '?' &&
|
||||
pathStart[pathLength] != '#') {
|
||||
pathLength++;
|
||||
@ -689,7 +980,7 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
|
||||
hostStart++;
|
||||
}
|
||||
|
||||
size_t hostLength = 0;
|
||||
u64 hostLength = 0;
|
||||
while (hostStart[hostLength] && hostStart[hostLength] != '/' &&
|
||||
hostStart[hostLength] != '?' && hostStart[hostLength] != ':') {
|
||||
hostLength++;
|
||||
@ -714,7 +1005,7 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
|
||||
// Parse the port (if present)
|
||||
if (hostStart[hostLength] == ':') {
|
||||
char* portStart = hostStart + hostLength + 1;
|
||||
size_t portLength = 0;
|
||||
u64 portLength = 0;
|
||||
while (portStart[portLength] && isdigit(portStart[portLength])) {
|
||||
portLength++;
|
||||
}
|
||||
@ -754,7 +1045,7 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
|
||||
// Parse the path (if present)
|
||||
if (srcUri[offset] == '/') {
|
||||
char* pathStart = (char*)srcUri + offset;
|
||||
size_t pathLength = 0;
|
||||
u64 pathLength = 0;
|
||||
while (pathStart[pathLength] && pathStart[pathLength] != '?' &&
|
||||
pathStart[pathLength] != '#') {
|
||||
pathLength++;
|
||||
@ -780,7 +1071,7 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
|
||||
// Parse the query (if present)
|
||||
if (srcUri[offset] == '?') {
|
||||
char* queryStart = (char*)srcUri + offset + 1;
|
||||
size_t queryLength = 0;
|
||||
u64 queryLength = 0;
|
||||
while (queryStart[queryLength] && queryStart[queryLength] != '#') {
|
||||
queryLength++;
|
||||
}
|
||||
@ -805,7 +1096,7 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
|
||||
// Parse the fragment (if present)
|
||||
if (srcUri[offset] == '#') {
|
||||
char* fragmentStart = (char*)srcUri + offset + 1;
|
||||
size_t fragmentLength = 0;
|
||||
u64 fragmentLength = 0;
|
||||
while (fragmentStart[fragmentLength]) {
|
||||
fragmentLength++;
|
||||
}
|
||||
@ -833,13 +1124,164 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceHttpUriSweepPath(char* dst, const char* src, size_t srcSize) {
|
||||
LOG_ERROR(Lib_Http, "(STUBBED) called");
|
||||
int PS4_SYSV_ABI sceHttpUriSweepPath(char* dst, const char* src, u64 srcSize) {
|
||||
LOG_TRACE(Lib_Http, "called");
|
||||
|
||||
if (!dst || !src) {
|
||||
LOG_ERROR(Lib_Http, "Invalid parameters");
|
||||
return ORBIS_HTTP_ERROR_INVALID_VALUE;
|
||||
}
|
||||
|
||||
if (srcSize == 0) {
|
||||
dst[0] = '\0';
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
u64 len = 0;
|
||||
while (len < srcSize && src[len] != '\0') {
|
||||
len++;
|
||||
}
|
||||
|
||||
for (u64 i = 0; i < len; i++) {
|
||||
dst[i] = src[i];
|
||||
}
|
||||
dst[len] = '\0';
|
||||
|
||||
char* read = dst;
|
||||
char* write = dst;
|
||||
|
||||
while (*read) {
|
||||
if (read[0] == '.' && read[1] == '.' && read[2] == '/') {
|
||||
read += 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (read[0] == '.' && read[1] == '/') {
|
||||
read += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (read[0] == '/' && read[1] == '.' && read[2] == '/') {
|
||||
read += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (read[0] == '/' && read[1] == '.' && read[2] == '\0') {
|
||||
if (write == dst) {
|
||||
*write++ = '/';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
bool is_dotdot_mid = (read[0] == '/' && read[1] == '.' && read[2] == '.' && read[3] == '/');
|
||||
bool is_dotdot_end =
|
||||
(read[0] == '/' && read[1] == '.' && read[2] == '.' && read[3] == '\0');
|
||||
|
||||
if (is_dotdot_mid || is_dotdot_end) {
|
||||
if (write > dst) {
|
||||
if (*(write - 1) == '/') {
|
||||
write--;
|
||||
}
|
||||
while (write > dst && *(write - 1) != '/') {
|
||||
write--;
|
||||
}
|
||||
|
||||
if (is_dotdot_mid && write > dst) {
|
||||
write--;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_dotdot_mid) {
|
||||
read += 3;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((read[0] == '.' && read[1] == '\0') ||
|
||||
(read[0] == '.' && read[1] == '.' && read[2] == '\0')) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (read[0] == '/') {
|
||||
*write++ = *read++;
|
||||
}
|
||||
while (*read && *read != '/') {
|
||||
*write++ = *read++;
|
||||
}
|
||||
}
|
||||
|
||||
*write = '\0';
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceHttpUriUnescape(char* out, size_t* require, size_t prepare, const char* in) {
|
||||
LOG_ERROR(Lib_Http, "(STUBBED) called");
|
||||
int PS4_SYSV_ABI sceHttpUriUnescape(char* out, u64* require, u64 prepare, const char* in) {
|
||||
LOG_TRACE(Lib_Http, "called");
|
||||
|
||||
if (!in) {
|
||||
LOG_ERROR(Lib_Http, "Invalid input string");
|
||||
return ORBIS_HTTP_ERROR_INVALID_VALUE;
|
||||
}
|
||||
|
||||
// Locale-independent hex digit check
|
||||
auto IsHex = [](char c) -> bool {
|
||||
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
|
||||
};
|
||||
|
||||
// Convert hex char to int value
|
||||
auto HexToInt = [](char c) -> int {
|
||||
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;
|
||||
return 0;
|
||||
};
|
||||
|
||||
// Check for valid percent-encoded sequence (%XX)
|
||||
auto IsValidPercentSequence = [&](const char* s) -> bool {
|
||||
return s[0] == '%' && s[1] != '\0' && s[2] != '\0' && IsHex(s[1]) && IsHex(s[2]);
|
||||
};
|
||||
|
||||
u64 needed = 0;
|
||||
const char* src = in;
|
||||
while (*src) {
|
||||
if (IsValidPercentSequence(src)) {
|
||||
src += 3;
|
||||
} else {
|
||||
src++;
|
||||
}
|
||||
needed++;
|
||||
}
|
||||
needed++; // null terminator
|
||||
|
||||
if (require) {
|
||||
*require = needed;
|
||||
}
|
||||
|
||||
if (!out) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
if (prepare < needed) {
|
||||
LOG_ERROR(Lib_Http, "Buffer too small: need {} but only {} available", needed, prepare);
|
||||
return ORBIS_HTTP_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
src = in;
|
||||
char* dst = out;
|
||||
while (*src) {
|
||||
if (IsValidPercentSequence(src)) {
|
||||
*dst++ = static_cast<char>((HexToInt(src[1]) << 4) | HexToInt(src[2]));
|
||||
src += 3;
|
||||
} else {
|
||||
*dst++ = *src++;
|
||||
}
|
||||
}
|
||||
*dst = '\0';
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/network/ssl.h"
|
||||
|
||||
@ -25,6 +26,12 @@ struct OrbisHttpUriElement {
|
||||
u8 reserved[10];
|
||||
};
|
||||
|
||||
struct HttpRequestInternal {
|
||||
int state; // +0x20
|
||||
int errorCode; // +0x28
|
||||
int httpStatusCode; // +0x20C
|
||||
std::mutex m_mutex;
|
||||
};
|
||||
using OrbisHttpsCaList = Libraries::Ssl::OrbisSslCaList;
|
||||
|
||||
int PS4_SYSV_ABI sceHttpAbortRequest();
|
||||
@ -32,7 +39,7 @@ int PS4_SYSV_ABI sceHttpAbortRequestForce();
|
||||
int PS4_SYSV_ABI sceHttpAbortWaitRequest();
|
||||
int PS4_SYSV_ABI sceHttpAddCookie();
|
||||
int PS4_SYSV_ABI sceHttpAddQuery();
|
||||
int PS4_SYSV_ABI sceHttpAddRequestHeader();
|
||||
int PS4_SYSV_ABI sceHttpAddRequestHeader(int id, const char* name, const char* value, s32 mode);
|
||||
int PS4_SYSV_ABI sceHttpAddRequestHeaderRaw();
|
||||
int PS4_SYSV_ABI sceHttpAuthCacheExport();
|
||||
int PS4_SYSV_ABI sceHttpAuthCacheFlush();
|
||||
@ -42,11 +49,12 @@ int PS4_SYSV_ABI sceHttpCookieExport();
|
||||
int PS4_SYSV_ABI sceHttpCookieFlush();
|
||||
int PS4_SYSV_ABI sceHttpCookieImport();
|
||||
int PS4_SYSV_ABI sceHttpCreateConnection();
|
||||
int PS4_SYSV_ABI sceHttpCreateConnectionWithURL();
|
||||
int PS4_SYSV_ABI sceHttpCreateConnectionWithURL(int tmplId, const char* url, bool enableKeepalive);
|
||||
int PS4_SYSV_ABI sceHttpCreateEpoll();
|
||||
int PS4_SYSV_ABI sceHttpCreateRequest();
|
||||
int PS4_SYSV_ABI sceHttpCreateRequest2();
|
||||
int PS4_SYSV_ABI sceHttpCreateRequestWithURL();
|
||||
int PS4_SYSV_ABI sceHttpCreateRequestWithURL(int connId, s32 method, const char* url,
|
||||
u64 contentLength);
|
||||
int PS4_SYSV_ABI sceHttpCreateRequestWithURL2();
|
||||
int PS4_SYSV_ABI sceHttpCreateTemplate();
|
||||
int PS4_SYSV_ABI sceHttpDbgEnableProfile();
|
||||
@ -62,7 +70,7 @@ int PS4_SYSV_ABI sceHttpDeleteRequest();
|
||||
int PS4_SYSV_ABI sceHttpDeleteTemplate();
|
||||
int PS4_SYSV_ABI sceHttpDestroyEpoll();
|
||||
int PS4_SYSV_ABI sceHttpGetAcceptEncodingGZIPEnabled();
|
||||
int PS4_SYSV_ABI sceHttpGetAllResponseHeaders();
|
||||
int PS4_SYSV_ABI sceHttpGetAllResponseHeaders(int reqId, char** header, u64* headerSize);
|
||||
int PS4_SYSV_ABI sceHttpGetAuthEnabled();
|
||||
int PS4_SYSV_ABI sceHttpGetAutoRedirect();
|
||||
int PS4_SYSV_ABI sceHttpGetConnectionStat();
|
||||
@ -76,10 +84,13 @@ int PS4_SYSV_ABI sceHttpGetMemoryPoolStats();
|
||||
int PS4_SYSV_ABI sceHttpGetNonblock();
|
||||
int PS4_SYSV_ABI sceHttpGetRegisteredCtxIds();
|
||||
int PS4_SYSV_ABI sceHttpGetResponseContentLength();
|
||||
int PS4_SYSV_ABI sceHttpGetStatusCode();
|
||||
int PS4_SYSV_ABI sceHttpInit(int libnetMemId, int libsslCtxId, std::size_t poolSize);
|
||||
int PS4_SYSV_ABI sceHttpParseResponseHeader();
|
||||
int PS4_SYSV_ABI sceHttpParseStatusLine();
|
||||
int PS4_SYSV_ABI sceHttpGetStatusCode(int reqId, int* statusCode);
|
||||
int PS4_SYSV_ABI sceHttpInit(int libnetMemId, int libsslCtxId, u64 poolSize);
|
||||
int PS4_SYSV_ABI sceHttpParseResponseHeader(const char* header, u64 headerLen, const char* fieldStr,
|
||||
const char** fieldValue, u64* valueLen);
|
||||
int PS4_SYSV_ABI sceHttpParseStatusLine(const char* statusLine, u64 lineLen, int32_t* httpMajorVer,
|
||||
int32_t* httpMinorVer, int32_t* responseCode,
|
||||
const char** reasonPhrase, u64* phraseLen);
|
||||
int PS4_SYSV_ABI sceHttpReadData();
|
||||
int PS4_SYSV_ABI sceHttpRedirectCacheFlush();
|
||||
int PS4_SYSV_ABI sceHttpRemoveRequestHeader();
|
||||
@ -88,7 +99,7 @@ int PS4_SYSV_ABI sceHttpsDisableOption();
|
||||
int PS4_SYSV_ABI sceHttpsDisableOptionPrivate();
|
||||
int PS4_SYSV_ABI sceHttpsEnableOption();
|
||||
int PS4_SYSV_ABI sceHttpsEnableOptionPrivate();
|
||||
int PS4_SYSV_ABI sceHttpSendRequest();
|
||||
int PS4_SYSV_ABI sceHttpSendRequest(int reqId, const void* postData, u64 size);
|
||||
int PS4_SYSV_ABI sceHttpSetAcceptEncodingGZIPEnabled();
|
||||
int PS4_SYSV_ABI sceHttpSetAuthEnabled();
|
||||
int PS4_SYSV_ABI sceHttpSetAuthInfoCallback();
|
||||
@ -134,14 +145,16 @@ int PS4_SYSV_ABI sceHttpTerm();
|
||||
int PS4_SYSV_ABI sceHttpTryGetNonblock();
|
||||
int PS4_SYSV_ABI sceHttpTrySetNonblock();
|
||||
int PS4_SYSV_ABI sceHttpUnsetEpoll();
|
||||
int PS4_SYSV_ABI sceHttpUriBuild();
|
||||
int PS4_SYSV_ABI sceHttpUriBuild(char* out, u64* require, u64 prepare,
|
||||
const OrbisHttpUriElement* srcElement, u32 option);
|
||||
int PS4_SYSV_ABI sceHttpUriCopy();
|
||||
int PS4_SYSV_ABI sceHttpUriEscape();
|
||||
int PS4_SYSV_ABI sceHttpUriMerge();
|
||||
int PS4_SYSV_ABI sceHttpUriEscape(char* out, u64* require, u64 prepare, const char* in);
|
||||
int PS4_SYSV_ABI sceHttpUriMerge(char* mergedUrl, char* url, char* relativeUri, u64* require,
|
||||
u64 prepare, u32 option);
|
||||
int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, void* pool,
|
||||
size_t* require, size_t prepare);
|
||||
int PS4_SYSV_ABI sceHttpUriSweepPath(char* dst, const char* src, size_t srcSize);
|
||||
int PS4_SYSV_ABI sceHttpUriUnescape(char* out, size_t* require, size_t prepare, const char* in);
|
||||
u64* require, u64 prepare);
|
||||
int PS4_SYSV_ABI sceHttpUriSweepPath(char* dst, const char* src, u64 srcSize);
|
||||
int PS4_SYSV_ABI sceHttpUriUnescape(char* out, u64* require, u64 prepare, const char* in);
|
||||
int PS4_SYSV_ABI sceHttpWaitRequest();
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
|
||||
@ -1285,7 +1285,8 @@ u16 PS4_SYSV_ABI sceNetNtohs(u16 net16) {
|
||||
|
||||
int PS4_SYSV_ABI sceNetPoolCreate(const char* name, int size, int flags) {
|
||||
LOG_ERROR(Lib_Net, "(DUMMY) name = {} size = {} flags = {} ", std::string(name), size, flags);
|
||||
return ORBIS_OK;
|
||||
static s32 id = 1;
|
||||
return id++;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetPoolDestroy() {
|
||||
@ -1356,7 +1357,8 @@ int PS4_SYSV_ABI sceNetResolverConnectDestroy() {
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetResolverCreate(const char* name, int poolid, int flags) {
|
||||
LOG_INFO(Lib_Net, "name = {}, poolid = {}, flags = {}", name, poolid, flags);
|
||||
const char* safe_name = name ? name : "";
|
||||
LOG_INFO(Lib_Net, "name = {}, poolid = {}, flags = {}", safe_name, poolid, flags);
|
||||
|
||||
if (flags != 0) {
|
||||
*sceNetErrnoLoc() = ORBIS_NET_EINVAL;
|
||||
@ -1367,8 +1369,8 @@ int PS4_SYSV_ABI sceNetResolverCreate(const char* name, int poolid, int flags) {
|
||||
auto* resolver = FDTable::Instance()->GetFile(fd);
|
||||
resolver->is_opened = true;
|
||||
resolver->type = Core::FileSys::FileType::Resolver;
|
||||
resolver->resolver = std::make_shared<Resolver>(name, poolid, flags);
|
||||
resolver->m_guest_name = name;
|
||||
resolver->resolver = std::make_shared<Resolver>(safe_name, poolid, flags);
|
||||
resolver->m_guest_name = safe_name;
|
||||
return fd;
|
||||
}
|
||||
|
||||
|
||||
129
src/core/libraries/np/np_commerce.cpp
Normal file
129
src/core/libraries/np/np_commerce.cpp
Normal file
@ -0,0 +1,129 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/system/commondialog.h"
|
||||
|
||||
namespace Libraries::Np::NpCommerce {
|
||||
|
||||
using CommonDialog::Error;
|
||||
using CommonDialog::Result;
|
||||
using CommonDialog::Status;
|
||||
|
||||
static Status g_dialog_status = Status::NONE;
|
||||
static Result g_dialog_result = Result::OK;
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCommerceDialogClose() {
|
||||
LOG_INFO(Lib_NpCommerce, "called");
|
||||
if (g_dialog_status == Status::NONE) {
|
||||
return static_cast<s32>(Error::NOT_INITIALIZED);
|
||||
}
|
||||
if (g_dialog_status != Status::FINISHED) {
|
||||
return static_cast<s32>(Error::NOT_FINISHED);
|
||||
}
|
||||
g_dialog_status = Status::INITIALIZED;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCommerceDialogGetResult(s32* result) {
|
||||
LOG_INFO(Lib_NpCommerce, "called");
|
||||
if (result == nullptr) {
|
||||
return static_cast<s32>(Error::ARG_NULL);
|
||||
}
|
||||
if (g_dialog_status != Status::FINISHED) {
|
||||
return static_cast<s32>(Error::NOT_FINISHED);
|
||||
}
|
||||
*result = static_cast<s32>(g_dialog_result);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s8 PS4_SYSV_ABI sceNpCommerceDialogGetStatus() {
|
||||
LOG_DEBUG(Lib_NpCommerce, "called, status = {}", static_cast<u32>(g_dialog_status));
|
||||
return static_cast<s8>(g_dialog_status);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCommerceDialogInitialize() {
|
||||
LOG_INFO(Lib_NpCommerce, "called");
|
||||
if (g_dialog_status != Status::NONE) {
|
||||
return static_cast<s32>(Error::ALREADY_INITIALIZED);
|
||||
}
|
||||
g_dialog_status = Status::INITIALIZED;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCommerceDialogInitializeInternal() {
|
||||
LOG_INFO(Lib_NpCommerce, "called");
|
||||
return sceNpCommerceDialogInitialize();
|
||||
}
|
||||
|
||||
s16 PS4_SYSV_ABI sceNpCommerceDialogOpen(s64 check) {
|
||||
LOG_INFO(Lib_NpCommerce, "called, check = {}", check);
|
||||
if (g_dialog_status != Status::INITIALIZED) {
|
||||
LOG_WARNING(Lib_NpCommerce, "Dialog not initialized");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
g_dialog_status = Status::FINISHED;
|
||||
g_dialog_result = Result::USER_CANCELED;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCommerceDialogTerminate() {
|
||||
LOG_INFO(Lib_NpCommerce, "called");
|
||||
if (g_dialog_status == Status::NONE) {
|
||||
return static_cast<s32>(Error::NOT_INITIALIZED);
|
||||
}
|
||||
if (g_dialog_status == Status::RUNNING) {
|
||||
return static_cast<s32>(Error::NOT_FINISHED);
|
||||
}
|
||||
g_dialog_status = Status::NONE;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCommerceDialogUpdateStatus() {
|
||||
LOG_DEBUG(Lib_NpCommerce, "called, status = {}", static_cast<u32>(g_dialog_status));
|
||||
return static_cast<s32>(g_dialog_status);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCommerceHidePsStoreIcon() {
|
||||
LOG_ERROR(Lib_NpCommerce, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCommerceSetPsStoreIconLayout(s32 layout) {
|
||||
LOG_ERROR(Lib_NpCommerce, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCommerceShowPsStoreIcon(s16 icon) {
|
||||
LOG_ERROR(Lib_NpCommerce, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("NU3ckGHMFXo", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||
sceNpCommerceDialogClose);
|
||||
LIB_FUNCTION("r42bWcQbtZY", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||
sceNpCommerceDialogGetResult);
|
||||
LIB_FUNCTION("CCbC+lqqvF0", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||
sceNpCommerceDialogGetStatus);
|
||||
LIB_FUNCTION("0aR2aWmQal4", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||
sceNpCommerceDialogInitialize);
|
||||
LIB_FUNCTION("9ZiLXAGG5rg", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||
sceNpCommerceDialogInitializeInternal);
|
||||
LIB_FUNCTION("DfSCDRA3EjY", "libSceNpCommerce", 1, "libSceNpCommerce", sceNpCommerceDialogOpen);
|
||||
LIB_FUNCTION("m-I92Ab50W8", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||
sceNpCommerceDialogTerminate);
|
||||
LIB_FUNCTION("LR5cwFMMCVE", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||
sceNpCommerceDialogUpdateStatus);
|
||||
LIB_FUNCTION("dsqCVsNM0Zg", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||
sceNpCommerceHidePsStoreIcon);
|
||||
LIB_FUNCTION("uKTDW8hk-ts", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||
sceNpCommerceSetPsStoreIconLayout);
|
||||
LIB_FUNCTION("DHmwsa6S8Tc", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||
sceNpCommerceShowPsStoreIcon);
|
||||
};
|
||||
|
||||
} // namespace Libraries::Np::NpCommerce
|
||||
16
src/core/libraries/np/np_commerce.h
Normal file
16
src/core/libraries/np/np_commerce.h
Normal file
@ -0,0 +1,16 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
namespace Libraries::Np::NpCommerce {
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
|
||||
} // namespace Libraries::Np::NpCommerce
|
||||
@ -5,7 +5,6 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <SDL3/SDL_audio.h>
|
||||
#include <cmrc/cmrc.hpp>
|
||||
#include <imgui.h>
|
||||
|
||||
@ -92,59 +91,45 @@ TrophyUI::TrophyUI(const std::filesystem::path& trophyIconPath, const std::strin
|
||||
|
||||
AddLayer(this);
|
||||
|
||||
bool customsoundplayed = false;
|
||||
#ifdef ENABLE_QT_GUI
|
||||
QString musicPathWav = QString::fromStdString(CustomTrophy_Dir.string() + "/trophy.wav");
|
||||
QString musicPathMp3 = QString::fromStdString(CustomTrophy_Dir.string() + "/trophy.mp3");
|
||||
if (fs::exists(musicPathWav.toStdString())) {
|
||||
BackgroundMusicPlayer::getInstance().setVolume(100);
|
||||
BackgroundMusicPlayer::getInstance().playMusic(musicPathWav, false);
|
||||
customsoundplayed = true;
|
||||
} else if (fs::exists(musicPathMp3.toStdString())) {
|
||||
BackgroundMusicPlayer::getInstance().setVolume(100);
|
||||
BackgroundMusicPlayer::getInstance().playMusic(musicPathMp3, false);
|
||||
customsoundplayed = true;
|
||||
MIX_Init();
|
||||
mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, NULL);
|
||||
if (!mixer) {
|
||||
LOG_ERROR(Lib_NpTrophy, "Could not initialize SDL Mixer, {}", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!customsoundplayed) {
|
||||
MIX_SetMasterGain(mixer, static_cast<float>(Config::getVolumeSlider() / 100.f));
|
||||
auto musicPathMp3 = CustomTrophy_Dir / "trophy.mp3";
|
||||
auto musicPathWav = CustomTrophy_Dir / "trophy.wav";
|
||||
|
||||
if (std::filesystem::exists(musicPathMp3)) {
|
||||
audio = MIX_LoadAudio(mixer, musicPathMp3.string().c_str(), false);
|
||||
} else if (std::filesystem::exists(musicPathWav)) {
|
||||
audio = MIX_LoadAudio(mixer, musicPathWav.string().c_str(), false);
|
||||
} else {
|
||||
auto soundFile = resource.open("src/images/trophy.wav");
|
||||
std::vector<u8> soundData = std::vector<u8>(soundFile.begin(), soundFile.end());
|
||||
audio =
|
||||
MIX_LoadAudio_IO(mixer, SDL_IOFromMem(soundData.data(), soundData.size()), false, true);
|
||||
// due to low volume of default sound file
|
||||
MIX_SetMasterGain(mixer, MIX_GetMasterGain(mixer) * 1.3f);
|
||||
}
|
||||
|
||||
SDL_AudioSpec spec;
|
||||
Uint8* audioBuf;
|
||||
Uint32 audioLen;
|
||||
if (!audio) {
|
||||
LOG_ERROR(Lib_NpTrophy, "Could not loud audio file, {}", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SDL_LoadWAV_IO(SDL_IOFromMem(soundData.data(), soundData.size()), true, &spec,
|
||||
&audioBuf, &audioLen)) {
|
||||
LOG_ERROR(Lib_NpTrophy, "Cannot load trophy sound: {}", SDL_GetError());
|
||||
SDL_free(audioBuf);
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_AudioStream* stream =
|
||||
SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, nullptr, nullptr);
|
||||
if (!stream) {
|
||||
LOG_ERROR(Lib_NpTrophy, "Cannot create audio stream for trophy sound: {}",
|
||||
SDL_GetError());
|
||||
SDL_free(audioBuf);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SDL_PutAudioStreamData(stream, audioBuf, audioLen)) {
|
||||
LOG_ERROR(Lib_NpTrophy, "Cannot add trophy sound data to stream: {}", SDL_GetError());
|
||||
SDL_free(audioBuf);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set audio gain 20% higher since audio file itself is soft
|
||||
SDL_SetAudioStreamGain(stream, Config::getVolumeSlider() / 100.0f * 1.2f);
|
||||
SDL_ResumeAudioStreamDevice(stream);
|
||||
SDL_free(audioBuf);
|
||||
if (!MIX_PlayAudio(mixer, audio)) {
|
||||
LOG_ERROR(Lib_NpTrophy, "Could not play audio file, {}", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
TrophyUI::~TrophyUI() {
|
||||
MIX_DestroyAudio(audio);
|
||||
MIX_DestroyMixer(mixer);
|
||||
MIX_Quit();
|
||||
|
||||
Finish();
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <SDL3_mixer/SDL_mixer.h>
|
||||
#include <queue>
|
||||
|
||||
#include "common/fixed_value.h"
|
||||
@ -30,6 +31,9 @@ private:
|
||||
std::string_view trophy_type;
|
||||
ImGui::RefCountedTexture trophy_icon;
|
||||
ImGui::RefCountedTexture trophy_type_icon;
|
||||
|
||||
MIX_Mixer* mixer;
|
||||
MIX_Audio* audio;
|
||||
};
|
||||
|
||||
struct TrophyInfo {
|
||||
|
||||
@ -180,7 +180,7 @@ void SaveInstance::SetupAndMount(bool read_only, bool copy_icon, bool ignore_cor
|
||||
}
|
||||
|
||||
if (!ignore_corrupt && !read_only) {
|
||||
Common::FS::IOFile f(corrupt_file_path, Common::FS::FileAccessMode::Write);
|
||||
Common::FS::IOFile f(corrupt_file_path, Common::FS::FileAccessMode::Create);
|
||||
f.Close();
|
||||
}
|
||||
|
||||
|
||||
@ -59,7 +59,7 @@ void PersistMemory(u32 slot_id, bool lock) {
|
||||
while (n++ < 10) {
|
||||
try {
|
||||
IOFile f;
|
||||
int r = f.Open(memoryPath, Common::FS::FileAccessMode::Write);
|
||||
int r = f.Open(memoryPath, Common::FS::FileAccessMode::Create);
|
||||
if (f.IsOpen()) {
|
||||
f.WriteRaw<u8>(data.memory_cache.data(), data.memory_cache.size());
|
||||
f.Close();
|
||||
@ -148,7 +148,7 @@ void SetIcon(u32 slot_id, void* buf, size_t buf_size) {
|
||||
fs::copy_file(src_icon, icon_path);
|
||||
}
|
||||
} else {
|
||||
IOFile file(icon_path, Common::FS::FileAccessMode::Write);
|
||||
IOFile file(icon_path, Common::FS::FileAccessMode::Create);
|
||||
file.WriteRaw<u8>(buf, buf_size);
|
||||
file.Close();
|
||||
}
|
||||
|
||||
@ -1389,7 +1389,7 @@ Error PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint
|
||||
}
|
||||
|
||||
try {
|
||||
const Common::FS::IOFile file(path, Common::FS::FileAccessMode::Write);
|
||||
const Common::FS::IOFile file(path, Common::FS::FileAccessMode::Create);
|
||||
file.WriteRaw<u8>(icon->buf, std::min(icon->bufSize, icon->dataSize));
|
||||
} catch (const fs::filesystem_error& e) {
|
||||
LOG_ERROR(Lib_SaveData, "Failed to load icon: {}", e.what());
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstdlib>
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/singleton.h"
|
||||
@ -1874,6 +1875,10 @@ int PS4_SYSV_ABI sceSystemServiceLoadExec(const char* path, const char* argv[])
|
||||
auto emu = Common::Singleton<Core::Emulator>::Instance();
|
||||
auto mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
auto hostPath = mnt->GetHostPath(std::string_view(path));
|
||||
if (hostPath.empty()) {
|
||||
LOG_INFO(Lib_SystemService, "Restart called with invalid file '{}', exiting.", path);
|
||||
std::quick_exit(0);
|
||||
}
|
||||
std::vector<std::string> args;
|
||||
if (argv != nullptr) {
|
||||
for (const char** ptr = argv; *ptr != nullptr; ptr++) {
|
||||
|
||||
651
src/core/libraries/usbd/emulated/dimensions.cpp
Normal file
651
src/core/libraries/usbd/emulated/dimensions.cpp
Normal file
@ -0,0 +1,651 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "dimensions.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
namespace Libraries::Usbd {
|
||||
|
||||
static constexpr std::array<u8, 16> COMMAND_KEY = {0x55, 0xFE, 0xF6, 0xB0, 0x62, 0xBF, 0x0B, 0x41,
|
||||
0xC9, 0xB3, 0x7C, 0xB4, 0x97, 0x3E, 0x29, 0x7B};
|
||||
|
||||
static constexpr std::array<u8, 17> CHAR_CONSTANT = {0xB7, 0xD5, 0xD7, 0xE6, 0xE7, 0xBA,
|
||||
0x3C, 0xA8, 0xD8, 0x75, 0x47, 0x68,
|
||||
0xCF, 0x23, 0xE9, 0xFE, 0xAA};
|
||||
|
||||
static constexpr std::array<u8, 25> PWD_CONSTANT = {
|
||||
0x28, 0x63, 0x29, 0x20, 0x43, 0x6F, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74,
|
||||
0x20, 0x4C, 0x45, 0x47, 0x4F, 0x20, 0x32, 0x30, 0x31, 0x34, 0xAA, 0xAA};
|
||||
|
||||
DimensionsToypad::DimensionsToypad() {}
|
||||
|
||||
void DimensionsToypad::LoadFigure(std::string file_name, u8 pad, u8 index) {
|
||||
Common::FS::IOFile file(file_name, Common::FS::FileAccessMode::ReadWrite);
|
||||
std::array<u8, 0x2D * 0x04> data;
|
||||
ASSERT(file.Read(data) == data.size());
|
||||
LoadDimensionsFigure(data, std::move(file), pad, index);
|
||||
}
|
||||
|
||||
u32 DimensionsToypad::LoadDimensionsFigure(const std::array<u8, 0x2D * 0x04>& buf,
|
||||
Common::FS::IOFile file, u8 pad, u8 index) {
|
||||
std::lock_guard lock(m_dimensions_mutex);
|
||||
|
||||
const u32 id = GetFigureId(buf);
|
||||
|
||||
DimensionsFigure& figure = GetFigureByIndex(index);
|
||||
figure.dimFile = std::move(file);
|
||||
figure.id = id;
|
||||
figure.pad = pad;
|
||||
figure.index = index + 1;
|
||||
figure.data = buf;
|
||||
// When a figure is added to the toypad, respond to the game with the pad they were added to,
|
||||
// their index, the direction (0x00 in byte 6 for added) and their UID
|
||||
std::array<u8, 32> figureChangeResponse = {0x56, 0x0b, figure.pad, 0x00, figure.index,
|
||||
0x00, buf[0], buf[1], buf[2], buf[4],
|
||||
buf[5], buf[6], buf[7]};
|
||||
figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13);
|
||||
m_figure_added_removed_responses.push(figureChangeResponse);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
void DimensionsToypad::RemoveFigure(u8 pad, u8 index, bool fullRemove) {
|
||||
std::lock_guard lock(m_dimensions_mutex);
|
||||
|
||||
DimensionsFigure& figure = GetFigureByIndex(index);
|
||||
if (figure.index == 255)
|
||||
return;
|
||||
|
||||
// When a figure is removed from the toypad, respond to the game with the pad they were removed
|
||||
// from, their index, the direction (0x01 in byte 6 for removed) and their UID
|
||||
if (fullRemove) {
|
||||
std::array<u8, 32> figureChangeResponse = {
|
||||
0x56, 0x0b, figure.pad, 0x00, figure.index,
|
||||
0x01, figure.data[0], figure.data[1], figure.data[2], figure.data[4],
|
||||
figure.data[5], figure.data[6], figure.data[7]};
|
||||
figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13);
|
||||
m_figure_added_removed_responses.push(figureChangeResponse);
|
||||
figure.Save();
|
||||
figure.dimFile.Close();
|
||||
}
|
||||
|
||||
figure.index = 255;
|
||||
figure.pad = 255;
|
||||
figure.id = 0;
|
||||
}
|
||||
|
||||
void DimensionsToypad::MoveFigure(u8 new_pad, u8 new_index, u8 old_pad, u8 old_index) {
|
||||
if (old_index == new_index) {
|
||||
// Don't bother removing and loading again, just send response to the game
|
||||
CancelRemoveFigure(new_index);
|
||||
return;
|
||||
}
|
||||
|
||||
// When moving figures between spaces on the toypad, remove any figure from the space they are
|
||||
// moving to, then remove them from their current space, then load them to the space they are
|
||||
// moving to
|
||||
RemoveFigure(new_pad, new_index, true);
|
||||
|
||||
DimensionsFigure& figure = GetFigureByIndex(old_index);
|
||||
const std::array<u8, 0x2D * 0x04> data = figure.data;
|
||||
Common::FS::IOFile inFile = std::move(figure.dimFile);
|
||||
|
||||
RemoveFigure(old_pad, old_index, false);
|
||||
|
||||
LoadDimensionsFigure(data, std::move(inFile), new_pad, new_index);
|
||||
}
|
||||
|
||||
void DimensionsToypad::TempRemoveFigure(u8 index) {
|
||||
std::lock_guard lock(m_dimensions_mutex);
|
||||
|
||||
DimensionsFigure& figure = GetFigureByIndex(index);
|
||||
if (figure.index == 255)
|
||||
return;
|
||||
|
||||
// Send a response to the game that the figure has been "Picked up" from existing slot,
|
||||
// until either the movement is cancelled, or user chooses a space to move to
|
||||
std::array<u8, 32> figureChangeResponse = {
|
||||
0x56, 0x0b, figure.pad, 0x00, figure.index,
|
||||
0x01, figure.data[0], figure.data[1], figure.data[2], figure.data[4],
|
||||
figure.data[5], figure.data[6], figure.data[7]};
|
||||
|
||||
figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13);
|
||||
m_figure_added_removed_responses.push(figureChangeResponse);
|
||||
}
|
||||
|
||||
void DimensionsToypad::CancelRemoveFigure(u8 index) {
|
||||
std::lock_guard lock(m_dimensions_mutex);
|
||||
|
||||
DimensionsFigure& figure = GetFigureByIndex(index);
|
||||
if (figure.index == 255)
|
||||
return;
|
||||
|
||||
// Cancel the previous movement of the figure
|
||||
std::array<u8, 32> figureChangeResponse = {
|
||||
0x56, 0x0b, figure.pad, 0x00, figure.index,
|
||||
0x00, figure.data[0], figure.data[1], figure.data[2], figure.data[4],
|
||||
figure.data[5], figure.data[6], figure.data[7]};
|
||||
|
||||
figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13);
|
||||
m_figure_added_removed_responses.push(figureChangeResponse);
|
||||
}
|
||||
|
||||
u8 DimensionsToypad::GenerateChecksum(const std::array<u8, 32>& data, u32 num_of_bytes) {
|
||||
int checksum = 0;
|
||||
ASSERT(num_of_bytes <= data.size());
|
||||
for (u8 i = 0; i < num_of_bytes; i++) {
|
||||
checksum += data[i];
|
||||
}
|
||||
return (checksum & 0xFF);
|
||||
}
|
||||
|
||||
void DimensionsToypad::GetBlankResponse(u8 type, u8 sequence, std::array<u8, 32>& reply_buf) {
|
||||
reply_buf[0] = 0x55;
|
||||
reply_buf[1] = type;
|
||||
reply_buf[2] = sequence;
|
||||
reply_buf[3] = GenerateChecksum(reply_buf, 3);
|
||||
}
|
||||
|
||||
void DimensionsToypad::GenerateRandomNumber(const u8* buf, u8 sequence,
|
||||
std::array<u8, 32>& reply_buf) {
|
||||
// Decrypt payload into an 8 byte array
|
||||
const std::array<u8, 8> value = Decrypt(buf, std::nullopt);
|
||||
// Seed is the first 4 bytes (little endian) of the decrypted payload
|
||||
const u32 seed = (u32&)value[0];
|
||||
// Confirmation is the second 4 bytes (big endian) of the decrypted payload
|
||||
// const u32 conf = (u32be&)value[4];
|
||||
// Initialize rng using the seed from decrypted payload
|
||||
InitializeRNG(seed);
|
||||
// Encrypt 8 bytes, first 4 bytes is the decrypted confirmation from payload, 2nd 4 bytes are
|
||||
// blank
|
||||
std::array<u8, 8> value_to_encrypt = {value[4], value[5], value[6], value[7], 0, 0, 0, 0};
|
||||
const std::array<u8, 8> encrypted = Encrypt(value_to_encrypt.data(), std::nullopt);
|
||||
reply_buf[0] = 0x55;
|
||||
reply_buf[1] = 0x09;
|
||||
reply_buf[2] = sequence;
|
||||
// Copy encrypted value to response data
|
||||
std::memcpy(&reply_buf[3], encrypted.data(), encrypted.size());
|
||||
reply_buf[11] = GenerateChecksum(reply_buf, 11);
|
||||
}
|
||||
|
||||
void DimensionsToypad::InitializeRNG(u32 seed) {
|
||||
m_random_a = 0xF1EA5EED;
|
||||
m_random_b = seed;
|
||||
m_random_c = seed;
|
||||
m_random_d = seed;
|
||||
|
||||
for (int i = 0; i < 42; i++) {
|
||||
GetNext();
|
||||
}
|
||||
}
|
||||
|
||||
u32 DimensionsToypad::GetNext() {
|
||||
const u32 e = m_random_a - std::rotl(m_random_b, 21);
|
||||
m_random_a = m_random_b ^ std::rotl(m_random_c, 19);
|
||||
m_random_b = m_random_c + std::rotl(m_random_d, 6);
|
||||
m_random_c = m_random_d + e;
|
||||
m_random_d = e + m_random_a;
|
||||
return m_random_d;
|
||||
}
|
||||
|
||||
std::array<u8, 8> DimensionsToypad::Decrypt(const u8* buf, std::optional<std::array<u8, 16>> key) {
|
||||
// Value to decrypt is separated in to two little endian 32 bit unsigned integers
|
||||
u32 data_one = u32(buf[0]) | (u32(buf[1]) << 8) | (u32(buf[2]) << 16) | (u32(buf[3]) << 24);
|
||||
u32 data_two = u32(buf[4]) | (u32(buf[5]) << 8) | (u32(buf[6]) << 16) | (u32(buf[7]) << 24);
|
||||
|
||||
// Use the key as 4 32 bit little endian unsigned integers
|
||||
u32 key_one;
|
||||
u32 key_two;
|
||||
u32 key_three;
|
||||
u32 key_four;
|
||||
|
||||
if (key) {
|
||||
key_one = u32(key.value()[0]) | (u32(key.value()[1]) << 8) | (u32(key.value()[2]) << 16) |
|
||||
(u32(key.value()[3]) << 24);
|
||||
key_two = u32(key.value()[4]) | (u32(key.value()[5]) << 8) | (u32(key.value()[6]) << 16) |
|
||||
(u32(key.value()[7]) << 24);
|
||||
key_three = u32(key.value()[8]) | (u32(key.value()[9]) << 8) |
|
||||
(u32(key.value()[10]) << 16) | (u32(key.value()[11]) << 24);
|
||||
key_four = u32(key.value()[12]) | (u32(key.value()[13]) << 8) |
|
||||
(u32(key.value()[14]) << 16) | (u32(key.value()[15]) << 24);
|
||||
} else {
|
||||
key_one = u32(COMMAND_KEY[0]) | (u32(COMMAND_KEY[1]) << 8) | (u32(COMMAND_KEY[2]) << 16) |
|
||||
(u32(COMMAND_KEY[3]) << 24);
|
||||
key_two = u32(COMMAND_KEY[4]) | (u32(COMMAND_KEY[5]) << 8) | (u32(COMMAND_KEY[6]) << 16) |
|
||||
(u32(COMMAND_KEY[7]) << 24);
|
||||
key_three = u32(COMMAND_KEY[8]) | (u32(COMMAND_KEY[9]) << 8) |
|
||||
(u32(COMMAND_KEY[10]) << 16) | (u32(COMMAND_KEY[11]) << 24);
|
||||
key_four = u32(COMMAND_KEY[12]) | (u32(COMMAND_KEY[13]) << 8) |
|
||||
(u32(COMMAND_KEY[14]) << 16) | (u32(COMMAND_KEY[15]) << 24);
|
||||
}
|
||||
|
||||
u32 sum = 0xC6EF3720;
|
||||
constexpr u32 delta = 0x9E3779B9;
|
||||
|
||||
for (int i = 0; i < 32; i++) {
|
||||
data_two -=
|
||||
(((data_one << 4) + key_three) ^ (data_one + sum) ^ ((data_one >> 5) + key_four));
|
||||
data_one -= (((data_two << 4) + key_one) ^ (data_two + sum) ^ ((data_two >> 5) + key_two));
|
||||
sum -= delta;
|
||||
}
|
||||
|
||||
ASSERT_MSG(sum == 0, "Decryption failed, sum inequal to 0");
|
||||
|
||||
std::array<u8, 8> decrypted = {u8(data_one & 0xFF), u8((data_one >> 8) & 0xFF),
|
||||
u8((data_one >> 16) & 0xFF), u8((data_one >> 24) & 0xFF),
|
||||
u8(data_two & 0xFF), u8((data_two >> 8) & 0xFF),
|
||||
u8((data_two >> 16) & 0xFF), u8((data_two >> 24) & 0xFF)};
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
std::array<u8, 8> DimensionsToypad::Encrypt(const u8* buf, std::optional<std::array<u8, 16>> key) {
|
||||
// Value to encrypt is separated in to two little endian 32 bit unsigned integers
|
||||
u32 data_one = u32(buf[0]) | (u32(buf[1]) << 8) | (u32(buf[2]) << 16) | (u32(buf[3]) << 24);
|
||||
u32 data_two = u32(buf[4]) | (u32(buf[5]) << 8) | (u32(buf[6]) << 16) | (u32(buf[7]) << 24);
|
||||
|
||||
// Use the key as 4 32 bit little endian unsigned integers
|
||||
u32 key_one;
|
||||
u32 key_two;
|
||||
u32 key_three;
|
||||
u32 key_four;
|
||||
|
||||
if (key) {
|
||||
key_one = u32(key.value()[0]) | (u32(key.value()[1]) << 8) | (u32(key.value()[2]) << 16) |
|
||||
(u32(key.value()[3]) << 24);
|
||||
key_two = u32(key.value()[4]) | (u32(key.value()[5]) << 8) | (u32(key.value()[6]) << 16) |
|
||||
(u32(key.value()[7]) << 24);
|
||||
key_three = u32(key.value()[8]) | (u32(key.value()[9]) << 8) |
|
||||
(u32(key.value()[10]) << 16) | (u32(key.value()[11]) << 24);
|
||||
key_four = u32(key.value()[12]) | (u32(key.value()[13]) << 8) |
|
||||
(u32(key.value()[14]) << 16) | (u32(key.value()[15]) << 24);
|
||||
} else {
|
||||
key_one = u32(COMMAND_KEY[0]) | (u32(COMMAND_KEY[1]) << 8) | (u32(COMMAND_KEY[2]) << 16) |
|
||||
(u32(COMMAND_KEY[3]) << 24);
|
||||
key_two = u32(COMMAND_KEY[4]) | (u32(COMMAND_KEY[5]) << 8) | (u32(COMMAND_KEY[6]) << 16) |
|
||||
(u32(COMMAND_KEY[7]) << 24);
|
||||
key_three = u32(COMMAND_KEY[8]) | (u32(COMMAND_KEY[9]) << 8) |
|
||||
(u32(COMMAND_KEY[10]) << 16) | (u32(COMMAND_KEY[11]) << 24);
|
||||
key_four = u32(COMMAND_KEY[12]) | (u32(COMMAND_KEY[13]) << 8) |
|
||||
(u32(COMMAND_KEY[14]) << 16) | (u32(COMMAND_KEY[15]) << 24);
|
||||
}
|
||||
|
||||
u32 sum = 0;
|
||||
u32 delta = 0x9E3779B9;
|
||||
|
||||
for (int i = 0; i < 32; i++) {
|
||||
sum += delta;
|
||||
data_one += (((data_two << 4) + key_one) ^ (data_two + sum) ^ ((data_two >> 5) + key_two));
|
||||
data_two +=
|
||||
(((data_one << 4) + key_three) ^ (data_one + sum) ^ ((data_one >> 5) + key_four));
|
||||
}
|
||||
|
||||
std::array<u8, 8> encrypted = {u8(data_one & 0xFF), u8((data_one >> 8) & 0xFF),
|
||||
u8((data_one >> 16) & 0xFF), u8((data_one >> 24) & 0xFF),
|
||||
u8(data_two & 0xFF), u8((data_two >> 8) & 0xFF),
|
||||
u8((data_two >> 16) & 0xFF), u8((data_two >> 24) & 0xFF)};
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
std::array<u8, 16> DimensionsToypad::GenerateFigureKey(const std::array<u8, 0x2D * 0x04>& buf) {
|
||||
std::array<u8, 7> uid = {buf[0], buf[1], buf[2], buf[4], buf[5], buf[6], buf[7]};
|
||||
|
||||
u32 scrambleA = Scramble(uid, 3);
|
||||
u32 scrambleB = Scramble(uid, 4);
|
||||
u32 scrambleC = Scramble(uid, 5);
|
||||
u32 scrambleD = Scramble(uid, 6);
|
||||
|
||||
return {
|
||||
u8((scrambleA >> 24) & 0xFF), u8((scrambleA >> 16) & 0xFF), u8((scrambleA >> 8) & 0xFF),
|
||||
u8(scrambleA & 0xFF), u8((scrambleB >> 24) & 0xFF), u8((scrambleB >> 16) & 0xFF),
|
||||
u8((scrambleB >> 8) & 0xFF), u8(scrambleB & 0xFF), u8((scrambleC >> 24) & 0xFF),
|
||||
u8((scrambleC >> 16) & 0xFF), u8((scrambleC >> 8) & 0xFF), u8(scrambleC & 0xFF),
|
||||
u8((scrambleD >> 24) & 0xFF), u8((scrambleD >> 16) & 0xFF), u8((scrambleD >> 8) & 0xFF),
|
||||
u8(scrambleD & 0xFF)};
|
||||
}
|
||||
|
||||
u32 DimensionsToypad::Scramble(const std::array<u8, 7>& uid, u8 count) {
|
||||
std::vector<u8> to_scramble;
|
||||
to_scramble.reserve(uid.size() + CHAR_CONSTANT.size());
|
||||
for (u8 x : uid) {
|
||||
to_scramble.push_back(x);
|
||||
}
|
||||
for (u8 c : CHAR_CONSTANT) {
|
||||
to_scramble.push_back(c);
|
||||
}
|
||||
to_scramble[(count * 4) - 1] = 0xaa;
|
||||
|
||||
std::array<u8, 4> randomized = DimensionsRandomize(to_scramble, count);
|
||||
|
||||
return (u32(randomized[0]) << 24) | (u32(randomized[1]) << 16) | (u32(randomized[2]) << 8) |
|
||||
u32(randomized[3]);
|
||||
}
|
||||
|
||||
std::array<u8, 4> DimensionsToypad::PWDGenerate(const std::array<u8, 7>& uid) {
|
||||
std::vector<u8> pwdCalc = {PWD_CONSTANT.begin(), PWD_CONSTANT.end() - 1};
|
||||
for (u8 i = 0; i < uid.size(); i++) {
|
||||
pwdCalc.insert(pwdCalc.begin() + i, uid[i]);
|
||||
}
|
||||
|
||||
return DimensionsRandomize(pwdCalc, 8);
|
||||
}
|
||||
|
||||
std::array<u8, 4> DimensionsToypad::DimensionsRandomize(const std::vector<u8>& key, u8 count) {
|
||||
u32 scrambled = 0;
|
||||
for (u8 i = 0; i < count; i++) {
|
||||
const u32 v4 = std::rotr(scrambled, 25);
|
||||
const u32 v5 = std::rotr(scrambled, 10);
|
||||
const u32 b = u32(key[i * 4]) | (u32(key[(i * 4) + 1]) << 8) |
|
||||
(u32(key[(i * 4) + 2]) << 16) | (u32(key[(i * 4) + 3]) << 24);
|
||||
scrambled = b + v4 + v5 - scrambled;
|
||||
}
|
||||
return {u8(scrambled & 0xFF), u8(scrambled >> 8 & 0xFF), u8(scrambled >> 16 & 0xFF),
|
||||
u8(scrambled >> 24 & 0xFF)};
|
||||
}
|
||||
|
||||
u32 DimensionsToypad::GetFigureId(const std::array<u8, 0x2D * 0x04>& buf) {
|
||||
const std::array<u8, 16> figure_key = GenerateFigureKey(buf);
|
||||
|
||||
const std::array<u8, 8> decrypted = Decrypt(&buf[36 * 4], figure_key);
|
||||
|
||||
const u32 fig_num = u32(decrypted[0]) | (u32(decrypted[1]) << 8) | (u32(decrypted[2]) << 16) |
|
||||
(u32(decrypted[3]) << 24);
|
||||
// Characters have their model number encrypted in page 36
|
||||
if (fig_num < 1000) {
|
||||
return fig_num;
|
||||
}
|
||||
// Vehicles/Gadgets have their model number written as little endian in page 36
|
||||
return u32(buf[36 * 4]) | (u32(buf[(36 * 4) + 1]) << 8) | (u32(buf[(36 * 4) + 2]) << 16) |
|
||||
(u32(buf[(36 * 4) + 3]) << 24);
|
||||
}
|
||||
|
||||
DimensionsFigure& DimensionsToypad::GetFigureByIndex(u8 index) {
|
||||
return m_figures[index];
|
||||
}
|
||||
|
||||
void DimensionsToypad::RandomUID(u8* uid_buffer) {
|
||||
uid_buffer[0] = 0x04;
|
||||
uid_buffer[7] = 0x80;
|
||||
|
||||
for (u8 i = 1; i < 7; i++) {
|
||||
u8 random = rand() % 255;
|
||||
uid_buffer[i] = random;
|
||||
}
|
||||
}
|
||||
|
||||
void DimensionsToypad::GetChallengeResponse(const u8* buf, u8 sequence,
|
||||
std::array<u8, 32>& reply_buf) {
|
||||
// Decrypt payload into an 8 byte array
|
||||
const std::array<u8, 8> value = Decrypt(buf, std::nullopt);
|
||||
// Confirmation is the first 4 bytes of the decrypted payload
|
||||
// const u32 conf = read_from_ptr<be_t<u32>>(value);
|
||||
// Generate next random number based on RNG
|
||||
const u32 next_random = GetNext();
|
||||
// Encrypt an 8 byte array, first 4 bytes are the next random number (little endian)
|
||||
// followed by the confirmation from the decrypted payload
|
||||
std::array<u8, 8> value_to_encrypt = {u8(next_random & 0xFF),
|
||||
u8((next_random >> 8) & 0xFF),
|
||||
u8((next_random >> 16) & 0xFF),
|
||||
u8((next_random >> 24) & 0xFF),
|
||||
value[0],
|
||||
value[1],
|
||||
value[2],
|
||||
value[3]};
|
||||
const std::array<u8, 8> encrypted = Encrypt(value_to_encrypt.data(), std::nullopt);
|
||||
reply_buf[0] = 0x55;
|
||||
reply_buf[1] = 0x09;
|
||||
reply_buf[2] = sequence;
|
||||
// Copy encrypted value to response data
|
||||
std::memcpy(&reply_buf[3], encrypted.data(), encrypted.size());
|
||||
reply_buf[11] = GenerateChecksum(reply_buf, 11);
|
||||
}
|
||||
|
||||
void DimensionsToypad::QueryBlock(u8 index, u8 page, std::array<u8, 32>& reply_buf, u8 sequence) {
|
||||
std::lock_guard lock(m_dimensions_mutex);
|
||||
|
||||
reply_buf[0] = 0x55;
|
||||
reply_buf[1] = 0x12;
|
||||
reply_buf[2] = sequence;
|
||||
reply_buf[3] = 0x00;
|
||||
|
||||
// Index from game begins at 1 rather than 0, so minus 1 here
|
||||
if (const u8 figure_index = index - 1; figure_index < MAX_DIMENSIONS_FIGURES) {
|
||||
const DimensionsFigure& figure = GetFigureByIndex(figure_index);
|
||||
|
||||
// Query 4 pages of 4 bytes from the figure, copy this to the response
|
||||
if (figure.index != 255 && (4 * page) < ((0x2D * 4) - 16)) {
|
||||
std::memcpy(&reply_buf[4], figure.data.data() + (4 * page), 16);
|
||||
}
|
||||
}
|
||||
reply_buf[20] = GenerateChecksum(reply_buf, 20);
|
||||
}
|
||||
|
||||
void DimensionsToypad::WriteBlock(u8 index, u8 page, const u8* to_write_buf,
|
||||
std::array<u8, 32>& reply_buf, u8 sequence) {
|
||||
std::lock_guard lock(m_dimensions_mutex);
|
||||
|
||||
reply_buf[0] = 0x55;
|
||||
reply_buf[1] = 0x02;
|
||||
reply_buf[2] = sequence;
|
||||
reply_buf[3] = 0x00;
|
||||
|
||||
// Index from game begins at 1 rather than 0, so minus 1 here
|
||||
if (const u8 figure_index = index - 1; figure_index < MAX_DIMENSIONS_FIGURES) {
|
||||
DimensionsFigure& figure = GetFigureByIndex(figure_index);
|
||||
|
||||
// Copy 4 bytes to the page on the figure requested by the game
|
||||
if (figure.index != 255 && page < 0x2D) {
|
||||
// Id is written to page 36
|
||||
if (page == 36) {
|
||||
figure.id = u32(to_write_buf[0]) | (u32(to_write_buf[1]) << 8) |
|
||||
(u32(to_write_buf[2]) << 16) | (u32(to_write_buf[3]) << 24);
|
||||
}
|
||||
std::memcpy(figure.data.data() + (page * 4), to_write_buf, 4);
|
||||
figure.Save();
|
||||
}
|
||||
}
|
||||
reply_buf[4] = GenerateChecksum(reply_buf, 4);
|
||||
}
|
||||
|
||||
void DimensionsToypad::GetModel(const u8* buf, u8 sequence, std::array<u8, 32>& reply_buf) {
|
||||
// Decrypt payload to 8 byte array, byte 1 is the index, 4-7 are the confirmation
|
||||
const std::array<u8, 8> value = Decrypt(buf, std::nullopt);
|
||||
const u8 index = value[0];
|
||||
// const u32 conf = read_from_ptr<be_t<u32>>(value, 4);
|
||||
std::array<u8, 8> value_to_encrypt = {};
|
||||
// Response is the figure's id (little endian) followed by the confirmation from payload
|
||||
// Index from game begins at 1 rather than 0, so minus 1 here
|
||||
if (const u8 figure_index = index - 1; figure_index < MAX_DIMENSIONS_FIGURES) {
|
||||
const DimensionsFigure& figure = GetFigureByIndex(figure_index);
|
||||
value_to_encrypt = {u8(figure.id & 0xFF),
|
||||
u8((figure.id >> 8) & 0xFF),
|
||||
u8((figure.id >> 16) & 0xFF),
|
||||
u8((figure.id >> 24) & 0xFF),
|
||||
value[4],
|
||||
value[5],
|
||||
value[6],
|
||||
value[7]};
|
||||
}
|
||||
const std::array<u8, 8> encrypted = Encrypt(value_to_encrypt.data(), std::nullopt);
|
||||
reply_buf[0] = 0x55;
|
||||
reply_buf[1] = 0x0a;
|
||||
reply_buf[2] = sequence;
|
||||
reply_buf[3] = 0x00;
|
||||
// Copy encrypted message to response
|
||||
std::memcpy(&reply_buf[4], encrypted.data(), encrypted.size());
|
||||
reply_buf[12] = GenerateChecksum(reply_buf, 12);
|
||||
}
|
||||
|
||||
std::optional<std::array<u8, 32>> DimensionsToypad::PopAddedRemovedResponse() {
|
||||
std::lock_guard lock(m_dimensions_mutex);
|
||||
|
||||
if (m_figure_added_removed_responses.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::array<u8, 32> response = m_figure_added_removed_responses.front();
|
||||
m_figure_added_removed_responses.pop();
|
||||
return response;
|
||||
}
|
||||
|
||||
libusb_endpoint_descriptor* DimensionsBackend::FillEndpointDescriptorPair() {
|
||||
return m_endpoint_descriptors.data();
|
||||
}
|
||||
|
||||
libusb_interface_descriptor* DimensionsBackend::FillInterfaceDescriptor(
|
||||
libusb_endpoint_descriptor* descs) {
|
||||
m_interface_descriptors[0].endpoint = descs;
|
||||
return m_interface_descriptors.data();
|
||||
}
|
||||
|
||||
libusb_config_descriptor* DimensionsBackend::FillConfigDescriptor(libusb_interface* inter) {
|
||||
m_config_descriptors[0].interface = inter;
|
||||
return m_config_descriptors.data();
|
||||
}
|
||||
|
||||
libusb_device_descriptor* DimensionsBackend::FillDeviceDescriptor() {
|
||||
return m_device_descriptors.data();
|
||||
}
|
||||
|
||||
libusb_transfer_status DimensionsBackend::HandleAsyncTransfer(libusb_transfer* transfer) {
|
||||
ASSERT(transfer->length == 32);
|
||||
|
||||
switch (transfer->endpoint) {
|
||||
case 0x81: {
|
||||
// Read Endpoint, wait to respond with either an added/removed figure response, or a queued
|
||||
// response from a previous write
|
||||
bool responded = false;
|
||||
while (!responded) {
|
||||
std::lock_guard lock(m_query_mutex);
|
||||
std::optional<std::array<u8, 32>> response =
|
||||
m_dimensions_toypad->PopAddedRemovedResponse();
|
||||
if (response) {
|
||||
std::memcpy(transfer->buffer, response.value().data(), 0x20);
|
||||
transfer->length = 32;
|
||||
responded = true;
|
||||
} else if (!m_queries.empty()) {
|
||||
std::memcpy(transfer->buffer, m_queries.front().data(), 0x20);
|
||||
transfer->length = 32;
|
||||
m_queries.pop();
|
||||
responded = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x01: {
|
||||
// Write endpoint, similar structure of request to the Infinity Base with a command for byte
|
||||
// 3, sequence for byte 4, the payload after that, then a checksum for the final byte.
|
||||
|
||||
const u8 command = transfer->buffer[2];
|
||||
const u8 sequence = transfer->buffer[3];
|
||||
|
||||
std::array<u8, 32> q_result{};
|
||||
|
||||
switch (command) {
|
||||
case 0xB0: // Wake
|
||||
{
|
||||
// Consistent device response to the wake command
|
||||
q_result = {0x55, 0x0e, 0x01, 0x28, 0x63, 0x29, 0x20, 0x4c, 0x45,
|
||||
0x47, 0x4f, 0x20, 0x32, 0x30, 0x31, 0x34, 0x46};
|
||||
break;
|
||||
}
|
||||
case 0xB1: // Seed
|
||||
{
|
||||
// Initialise a random number generator using the seed provided
|
||||
m_dimensions_toypad->GenerateRandomNumber(&transfer->buffer[4], sequence, q_result);
|
||||
break;
|
||||
}
|
||||
case 0xB3: // Challenge
|
||||
{
|
||||
// Get the next number in the sequence based on the RNG from 0xB1 command
|
||||
m_dimensions_toypad->GetChallengeResponse(&transfer->buffer[4], sequence, q_result);
|
||||
break;
|
||||
}
|
||||
case 0xC0: // Color
|
||||
case 0xC1: // Get Pad Color
|
||||
case 0xC2: // Fade
|
||||
case 0xC3: // Flash
|
||||
case 0xC4: // Fade Random
|
||||
case 0xC6: // Fade All
|
||||
case 0xC7: // Flash All
|
||||
case 0xC8: // Color All
|
||||
{
|
||||
// Send a blank response to acknowledge color has been sent to toypad
|
||||
m_dimensions_toypad->GetBlankResponse(0x01, sequence, q_result);
|
||||
break;
|
||||
}
|
||||
case 0xD2: // Read
|
||||
{
|
||||
// Read 4 pages from the figure at index (buf[4]), starting with page buf[5]
|
||||
m_dimensions_toypad->QueryBlock(transfer->buffer[4], transfer->buffer[5], q_result,
|
||||
sequence);
|
||||
break;
|
||||
}
|
||||
case 0xD3: // Write
|
||||
{
|
||||
// Write 4 bytes to page buf[5] to the figure at index buf[4]
|
||||
m_dimensions_toypad->WriteBlock(transfer->buffer[4], transfer->buffer[5],
|
||||
&transfer->buffer[6], q_result, sequence);
|
||||
break;
|
||||
}
|
||||
case 0xD4: // Model
|
||||
{
|
||||
// Get the model id of the figure at index buf[4]
|
||||
m_dimensions_toypad->GetModel(&transfer->buffer[4], sequence, q_result);
|
||||
break;
|
||||
}
|
||||
case 0xD0: // Tag List
|
||||
case 0xE1: // PWD
|
||||
case 0xE5: // Active
|
||||
case 0xFF: // LEDS Query
|
||||
{
|
||||
// Further investigation required
|
||||
LOG_ERROR(Lib_Usbd, "Unimplemented LD Function: {:x}", command);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
LOG_ERROR(Lib_Usbd, "Unknown LD Function: {:x}", command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::lock_guard lock(m_query_mutex);
|
||||
m_queries.push(q_result);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return LIBUSB_TRANSFER_COMPLETED;
|
||||
}
|
||||
|
||||
s32 DimensionsBackend::SubmitTransfer(libusb_transfer* transfer) {
|
||||
if (transfer->endpoint == 0x01) {
|
||||
std::thread write_thread([this, transfer] {
|
||||
HandleAsyncTransfer(transfer);
|
||||
|
||||
const u8 flags = transfer->flags;
|
||||
transfer->status = LIBUSB_TRANSFER_COMPLETED;
|
||||
transfer->actual_length = transfer->length;
|
||||
if (transfer->callback) {
|
||||
transfer->callback(transfer);
|
||||
}
|
||||
if (flags & LIBUSB_TRANSFER_FREE_TRANSFER) {
|
||||
libusb_free_transfer(transfer);
|
||||
}
|
||||
});
|
||||
write_thread.detach();
|
||||
return LIBUSB_SUCCESS;
|
||||
}
|
||||
|
||||
return UsbEmulatedBackend::SubmitTransfer(transfer);
|
||||
}
|
||||
|
||||
void DimensionsFigure::Save() {
|
||||
if (!dimFile.IsOpen())
|
||||
return;
|
||||
|
||||
dimFile.Seek(0);
|
||||
dimFile.Write(data);
|
||||
}
|
||||
} // namespace Libraries::Usbd
|
||||
118
src/core/libraries/usbd/emulated/dimensions.h
Normal file
118
src/core/libraries/usbd/emulated/dimensions.h
Normal file
@ -0,0 +1,118 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
|
||||
#include "common/io_file.h"
|
||||
#include "core/libraries/usbd/usb_backend.h"
|
||||
|
||||
namespace Libraries::Usbd {
|
||||
|
||||
constexpr u8 DIMEMSIONS_BLOCK_COUNT = 0x2D;
|
||||
constexpr u8 DIMENSIONS_BLOCK_SIZE = 0x04;
|
||||
constexpr u8 DIMENSIONS_FIGURE_SIZE = DIMEMSIONS_BLOCK_COUNT * DIMENSIONS_BLOCK_SIZE;
|
||||
constexpr u8 MAX_DIMENSIONS_FIGURES = 7;
|
||||
|
||||
struct DimensionsFigure final {
|
||||
Common::FS::IOFile dimFile;
|
||||
std::array<u8, DIMENSIONS_FIGURE_SIZE> data{};
|
||||
u8 index = 255;
|
||||
u8 pad = 255;
|
||||
u32 id = 0;
|
||||
void Save();
|
||||
};
|
||||
|
||||
class DimensionsToypad final : public UsbEmulatedImpl {
|
||||
public:
|
||||
DimensionsToypad();
|
||||
~DimensionsToypad() override = default;
|
||||
|
||||
static void GetBlankResponse(u8 type, u8 sequence, std::array<u8, 32>& reply_buf);
|
||||
void GenerateRandomNumber(const u8* buf, u8 sequence, std::array<u8, 32>& reply_buf);
|
||||
void InitializeRNG(u32 seed);
|
||||
void GetChallengeResponse(const u8* buf, u8 sequence, std::array<u8, 32>& reply_buf);
|
||||
void QueryBlock(u8 index, u8 page, std::array<u8, 32>& reply_buf, u8 sequence);
|
||||
void WriteBlock(u8 index, u8 page, const u8* to_write_buf, std::array<u8, 32>& reply_buf,
|
||||
u8 sequence);
|
||||
void GetModel(const u8* buf, u8 sequence, std::array<u8, 32>& reply_buf);
|
||||
std::optional<std::array<u8, 32>> PopAddedRemovedResponse();
|
||||
|
||||
void LoadFigure(std::string file_name, u8 pad, u8 slot) override;
|
||||
void RemoveFigure(u8 pad, u8 slot, bool full_remove) override;
|
||||
void MoveFigure(u8 new_pad, u8 new_index, u8 old_pad, u8 old_index) override;
|
||||
void TempRemoveFigure(u8 index) override;
|
||||
void CancelRemoveFigure(u8 index) override;
|
||||
|
||||
u32 LoadDimensionsFigure(const std::array<u8, 0x2D * 0x04>& buf, Common::FS::IOFile file,
|
||||
u8 pad, u8 index);
|
||||
|
||||
protected:
|
||||
std::mutex m_dimensions_mutex;
|
||||
std::array<DimensionsFigure, MAX_DIMENSIONS_FIGURES> m_figures{};
|
||||
|
||||
private:
|
||||
static void RandomUID(u8* uid_buffer);
|
||||
static u8 GenerateChecksum(const std::array<u8, 32>& data, u32 num_of_bytes);
|
||||
static std::array<u8, 8> Decrypt(const u8* buf, std::optional<std::array<u8, 16>> key);
|
||||
static std::array<u8, 8> Encrypt(const u8* buf, std::optional<std::array<u8, 16>> key);
|
||||
static std::array<u8, 16> GenerateFigureKey(const std::array<u8, 0x2D * 0x04>& buf);
|
||||
static u32 Scramble(const std::array<u8, 7>& uid, u8 count);
|
||||
static std::array<u8, 4> PWDGenerate(const std::array<u8, 7>& uid);
|
||||
static std::array<u8, 4> DimensionsRandomize(const std::vector<u8>& key, u8 count);
|
||||
static u32 GetFigureId(const std::array<u8, 0x2D * 0x04>& buf);
|
||||
u32 GetNext();
|
||||
DimensionsFigure& GetFigureByIndex(u8 index);
|
||||
|
||||
u32 m_random_a{};
|
||||
u32 m_random_b{};
|
||||
u32 m_random_c{};
|
||||
u32 m_random_d{};
|
||||
|
||||
u8 m_figure_order = 0;
|
||||
std::queue<std::array<u8, 32>> m_figure_added_removed_responses;
|
||||
};
|
||||
|
||||
class DimensionsBackend final : public UsbEmulatedBackend {
|
||||
protected:
|
||||
libusb_endpoint_descriptor* FillEndpointDescriptorPair() override;
|
||||
libusb_interface_descriptor* FillInterfaceDescriptor(
|
||||
libusb_endpoint_descriptor* descs) override;
|
||||
libusb_config_descriptor* FillConfigDescriptor(libusb_interface* inter) override;
|
||||
libusb_device_descriptor* FillDeviceDescriptor() override;
|
||||
|
||||
s32 GetMaxPacketSize(libusb_device* dev, u8 endpoint) override {
|
||||
return 32;
|
||||
}
|
||||
|
||||
s32 SubmitTransfer(libusb_transfer* transfer) override;
|
||||
|
||||
libusb_transfer_status HandleAsyncTransfer(libusb_transfer* transfer) override;
|
||||
|
||||
std::shared_ptr<UsbEmulatedImpl> GetImplRef() override {
|
||||
return m_dimensions_toypad;
|
||||
}
|
||||
|
||||
std::mutex m_query_mutex;
|
||||
std::queue<std::array<u8, 32>> m_queries;
|
||||
|
||||
private:
|
||||
std::shared_ptr<DimensionsToypad> m_dimensions_toypad = std::make_shared<DimensionsToypad>();
|
||||
|
||||
std::array<u8, 9> m_endpoint_out_extra = {0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, 0x1d, 0x00};
|
||||
std::vector<libusb_endpoint_descriptor> m_endpoint_descriptors = {
|
||||
{0x7, 0x5, 0x81, 0x3, 0x20, 0x1, 0x0, 0x0}, {0x7, 0x5, 0x1, 0x3, 0x20, 0x1, 0x0, 0x0}};
|
||||
std::vector<libusb_interface_descriptor> m_interface_descriptors = {
|
||||
{0x9, 0x4, 0x0, 0x0, 0x2, 0x3, 0x0, 0x0, 0x0}};
|
||||
std::vector<libusb_config_descriptor> m_config_descriptors = {
|
||||
{0x9, 0x2, 0x29, 0x1, 0x1, 0x0, 0x80, 0xFA}};
|
||||
std::vector<libusb_device_descriptor> m_device_descriptors = {
|
||||
{0x12, 0x1, 0x200, 0x0, 0x0, 0x0, 0x20, 0x0E6F, 0x0241, 0x200, 0x1, 0x2, 0x0, 0x1}};
|
||||
};
|
||||
} // namespace Libraries::Usbd
|
||||
392
src/core/libraries/usbd/emulated/infinity.cpp
Normal file
392
src/core/libraries/usbd/emulated/infinity.cpp
Normal file
@ -0,0 +1,392 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "infinity.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace Libraries::Usbd {
|
||||
|
||||
InfinityBase::InfinityBase() {}
|
||||
|
||||
void InfinityBase::LoadFigure(std::string file_name, u8 pad, u8 slot) {
|
||||
Common::FS::IOFile file(file_name, Common::FS::FileAccessMode::ReadWrite);
|
||||
std::array<u8, INFINITY_FIGURE_SIZE> data;
|
||||
ASSERT(file.Read(data) == data.size());
|
||||
LoadInfinityFigure(data, std::move(file), slot);
|
||||
}
|
||||
|
||||
void InfinityBase::RemoveFigure(u8 pad, u8 slot, bool full_remove) {
|
||||
std::lock_guard lock(infinity_mutex);
|
||||
InfinityFigure& figure = infinity_figures[slot];
|
||||
|
||||
if (!figure.present) {
|
||||
return;
|
||||
}
|
||||
|
||||
slot = DeriveFigurePosition(slot);
|
||||
if (slot == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
figure.present = false;
|
||||
|
||||
std::array<u8, 32> figure_change_response = {0xab, 0x04, slot, 0x09, figure.order_added, 0x01};
|
||||
figure_change_response[6] = GenerateChecksum(figure_change_response, 6);
|
||||
m_figure_added_removed_responses.push(figure_change_response);
|
||||
|
||||
figure.Save();
|
||||
figure.infFile.Close();
|
||||
}
|
||||
|
||||
void InfinityBase::LoadInfinityFigure(const std::array<u8, INFINITY_FIGURE_SIZE>& buf,
|
||||
Common::FS::IOFile file, u8 position) {
|
||||
std::lock_guard lock(infinity_mutex);
|
||||
u8 order_added;
|
||||
|
||||
InfinityFigure& figure = infinity_figures[position];
|
||||
|
||||
figure.infFile = std::move(file);
|
||||
memcpy(figure.data.data(), buf.data(), figure.data.size());
|
||||
figure.present = true;
|
||||
if (figure.order_added == 255) {
|
||||
figure.order_added = m_figure_order;
|
||||
m_figure_order++;
|
||||
}
|
||||
order_added = figure.order_added;
|
||||
|
||||
position = DeriveFigurePosition(position);
|
||||
if (position == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::array<u8, 32> figure_change_response = {0xab, 0x04, position, 0x09, order_added, 0x00};
|
||||
figure_change_response[6] = GenerateChecksum(figure_change_response, 6);
|
||||
m_figure_added_removed_responses.push(figure_change_response);
|
||||
}
|
||||
|
||||
void InfinityBase::GetBlankResponse(u8 sequence, std::array<u8, 32>& reply_buf) {
|
||||
reply_buf[0] = 0xaa;
|
||||
reply_buf[1] = 0x01;
|
||||
reply_buf[2] = sequence;
|
||||
reply_buf[3] = GenerateChecksum(reply_buf, 3);
|
||||
}
|
||||
|
||||
void InfinityBase::DescrambleAndSeed(u8* buf, u8 sequence, std::array<u8, 32>& reply_buf) {
|
||||
u64 value = u64(buf[4]) << 56 | u64(buf[5]) << 48 | u64(buf[6]) << 40 | u64(buf[7]) << 32 |
|
||||
u64(buf[8]) << 24 | u64(buf[9]) << 16 | u64(buf[10]) << 8 | u64(buf[11]);
|
||||
u32 seed = Descramble(value);
|
||||
GenerateSeed(seed);
|
||||
GetBlankResponse(sequence, reply_buf);
|
||||
}
|
||||
|
||||
void InfinityBase::GetNextAndScramble(u8 sequence, std::array<u8, 32>& reply_buf) {
|
||||
const u32 next_random = GetNext();
|
||||
const u64 scrambled_next_random = Scramble(next_random, 0);
|
||||
reply_buf = {0xAA, 0x09, sequence};
|
||||
reply_buf[3] = u8((scrambled_next_random >> 56) & 0xFF);
|
||||
reply_buf[4] = u8((scrambled_next_random >> 48) & 0xFF);
|
||||
reply_buf[5] = u8((scrambled_next_random >> 40) & 0xFF);
|
||||
reply_buf[6] = u8((scrambled_next_random >> 32) & 0xFF);
|
||||
reply_buf[7] = u8((scrambled_next_random >> 24) & 0xFF);
|
||||
reply_buf[8] = u8((scrambled_next_random >> 16) & 0xFF);
|
||||
reply_buf[9] = u8((scrambled_next_random >> 8) & 0xFF);
|
||||
reply_buf[10] = u8(scrambled_next_random & 0xFF);
|
||||
reply_buf[11] = GenerateChecksum(reply_buf, 11);
|
||||
}
|
||||
|
||||
void InfinityBase::GetPresentFigures(u8 sequence, std::array<u8, 32>& reply_buf) {
|
||||
int x = 3;
|
||||
for (u8 i = 0; i < infinity_figures.size(); i++) {
|
||||
u8 slot = i == 0 ? 0x10 : (i < 4) ? 0x20 : 0x30;
|
||||
if (infinity_figures[i].present) {
|
||||
reply_buf[x] = slot + infinity_figures[i].order_added;
|
||||
reply_buf[x + 1] = 0x09;
|
||||
x += 2;
|
||||
}
|
||||
}
|
||||
reply_buf[0] = 0xaa;
|
||||
reply_buf[1] = x - 2;
|
||||
reply_buf[2] = sequence;
|
||||
reply_buf[x] = GenerateChecksum(reply_buf, x);
|
||||
}
|
||||
|
||||
void InfinityBase::QueryBlock(u8 fig_num, u8 block, std::array<u8, 32>& reply_buf, u8 sequence) {
|
||||
std::lock_guard lock(infinity_mutex);
|
||||
|
||||
InfinityFigure& figure = GetFigureByOrder(fig_num);
|
||||
|
||||
reply_buf[0] = 0xaa;
|
||||
reply_buf[1] = 0x12;
|
||||
reply_buf[2] = sequence;
|
||||
reply_buf[3] = 0x00;
|
||||
const u8 file_block = (block == 0) ? 1 : (block * 4);
|
||||
if (figure.present && file_block < 20) {
|
||||
memcpy(&reply_buf[4], figure.data.data() + (16 * file_block), 16);
|
||||
}
|
||||
reply_buf[20] = GenerateChecksum(reply_buf, 20);
|
||||
}
|
||||
|
||||
void InfinityBase::WriteBlock(u8 fig_num, u8 block, const u8* to_write_buf,
|
||||
std::array<u8, 32>& reply_buf, u8 sequence) {
|
||||
std::lock_guard lock(infinity_mutex);
|
||||
|
||||
InfinityFigure& figure = GetFigureByOrder(fig_num);
|
||||
|
||||
reply_buf[0] = 0xaa;
|
||||
reply_buf[1] = 0x02;
|
||||
reply_buf[2] = sequence;
|
||||
reply_buf[3] = 0x00;
|
||||
const u8 file_block = (block == 0) ? 1 : (block * 4);
|
||||
if (figure.present && file_block < 20) {
|
||||
memcpy(figure.data.data() + (file_block * 16), to_write_buf, 16);
|
||||
figure.Save();
|
||||
}
|
||||
reply_buf[4] = GenerateChecksum(reply_buf, 4);
|
||||
}
|
||||
|
||||
void InfinityBase::GetFigureIdentifier(u8 fig_num, u8 sequence, std::array<u8, 32>& reply_buf) {
|
||||
std::lock_guard lock(infinity_mutex);
|
||||
|
||||
InfinityFigure& figure = GetFigureByOrder(fig_num);
|
||||
|
||||
reply_buf[0] = 0xaa;
|
||||
reply_buf[1] = 0x09;
|
||||
reply_buf[2] = sequence;
|
||||
reply_buf[3] = 0x00;
|
||||
|
||||
if (figure.present) {
|
||||
memcpy(&reply_buf[4], figure.data.data(), 7);
|
||||
}
|
||||
reply_buf[11] = GenerateChecksum(reply_buf, 11);
|
||||
}
|
||||
|
||||
std::optional<std::array<u8, 32>> InfinityBase::PopAddedRemovedResponse() {
|
||||
if (m_figure_added_removed_responses.empty())
|
||||
return std::nullopt;
|
||||
|
||||
std::array<u8, 32> response = m_figure_added_removed_responses.front();
|
||||
m_figure_added_removed_responses.pop();
|
||||
return response;
|
||||
}
|
||||
|
||||
u8 InfinityBase::GenerateChecksum(const std::array<u8, 32>& data, int num_of_bytes) const {
|
||||
int checksum = 0;
|
||||
for (int i = 0; i < num_of_bytes; i++) {
|
||||
checksum += data[i];
|
||||
}
|
||||
return (checksum & 0xFF);
|
||||
}
|
||||
|
||||
u32 InfinityBase::Descramble(u64 num_to_descramble) {
|
||||
u64 mask = 0x8E55AA1B3999E8AA;
|
||||
u32 ret = 0;
|
||||
|
||||
for (int i = 0; i < 64; i++) {
|
||||
if (mask & 0x8000000000000000) {
|
||||
ret = (ret << 1) | (num_to_descramble & 0x01);
|
||||
}
|
||||
|
||||
num_to_descramble >>= 1;
|
||||
mask <<= 1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
u64 InfinityBase::Scramble(u32 num_to_scramble, u32 garbage) {
|
||||
u64 mask = 0x8E55AA1B3999E8AA;
|
||||
u64 ret = 0;
|
||||
|
||||
for (int i = 0; i < 64; i++) {
|
||||
ret <<= 1;
|
||||
|
||||
if ((mask & 1) != 0) {
|
||||
ret |= (num_to_scramble & 1);
|
||||
num_to_scramble >>= 1;
|
||||
} else {
|
||||
ret |= (garbage & 1);
|
||||
garbage >>= 1;
|
||||
}
|
||||
|
||||
mask >>= 1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void InfinityBase::GenerateSeed(u32 seed) {
|
||||
random_a = 0xF1EA5EED;
|
||||
random_b = seed;
|
||||
random_c = seed;
|
||||
random_d = seed;
|
||||
|
||||
for (int i = 0; i < 23; i++) {
|
||||
GetNext();
|
||||
}
|
||||
}
|
||||
|
||||
u32 InfinityBase::GetNext() {
|
||||
u32 a = random_a;
|
||||
u32 b = random_b;
|
||||
u32 c = random_c;
|
||||
u32 ret = std::rotl(random_b, 27);
|
||||
|
||||
const u32 temp = (a + ((ret ^ 0xFFFFFFFF) + 1));
|
||||
b ^= std::rotl(c, 17);
|
||||
a = random_d;
|
||||
c += a;
|
||||
ret = b + temp;
|
||||
a += temp;
|
||||
|
||||
random_c = a;
|
||||
random_a = b;
|
||||
random_b = c;
|
||||
random_d = ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
InfinityFigure& InfinityBase::GetFigureByOrder(u8 order_added) {
|
||||
for (u8 i = 0; i < infinity_figures.size(); i++) {
|
||||
if (infinity_figures[i].order_added == order_added) {
|
||||
return infinity_figures[i];
|
||||
}
|
||||
}
|
||||
return infinity_figures[0];
|
||||
}
|
||||
|
||||
u8 InfinityBase::DeriveFigurePosition(u8 position) {
|
||||
switch (position) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
return 1;
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
return 2;
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
return 3;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
libusb_endpoint_descriptor* InfinityBackend::FillEndpointDescriptorPair() {
|
||||
return m_endpoint_descriptors.data();
|
||||
}
|
||||
|
||||
libusb_interface_descriptor* InfinityBackend::FillInterfaceDescriptor(
|
||||
libusb_endpoint_descriptor* descs) {
|
||||
m_interface_descriptors[0].endpoint = descs;
|
||||
return m_interface_descriptors.data();
|
||||
}
|
||||
|
||||
libusb_config_descriptor* InfinityBackend::FillConfigDescriptor(libusb_interface* inter) {
|
||||
m_config_descriptors[0].interface = inter;
|
||||
return m_config_descriptors.data();
|
||||
}
|
||||
|
||||
libusb_device_descriptor* InfinityBackend::FillDeviceDescriptor() {
|
||||
return m_device_descriptors.data();
|
||||
}
|
||||
|
||||
libusb_transfer_status InfinityBackend::HandleAsyncTransfer(libusb_transfer* transfer) {
|
||||
switch (transfer->endpoint) {
|
||||
case 0x81: {
|
||||
// Respond after FF command
|
||||
std::optional<std::array<u8, 32>> response = m_infinity_base->PopAddedRemovedResponse();
|
||||
if (response) {
|
||||
memcpy(transfer->buffer, response.value().data(), 0x20);
|
||||
} else if (!m_queries.empty()) {
|
||||
memcpy(transfer->buffer, m_queries.front().data(), 0x20);
|
||||
m_queries.pop();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x01: {
|
||||
const u8 command = transfer->buffer[2];
|
||||
const u8 sequence = transfer->buffer[3];
|
||||
LOG_INFO(Lib_Usbd, "Infinity Backend Transfer command: {:x}", command);
|
||||
|
||||
std::array<u8, 32> q_result{};
|
||||
|
||||
switch (command) {
|
||||
case 0x80: {
|
||||
q_result = {0xaa, 0x15, 0x00, 0x00, 0x0f, 0x01, 0x00, 0x03, 0x02, 0x09, 0x09, 0x43,
|
||||
0x20, 0x32, 0x62, 0x36, 0x36, 0x4b, 0x34, 0x99, 0x67, 0x31, 0x93, 0x8c};
|
||||
break;
|
||||
}
|
||||
case 0x81: {
|
||||
// Initiate Challenge
|
||||
m_infinity_base->DescrambleAndSeed(transfer->buffer, sequence, q_result);
|
||||
break;
|
||||
}
|
||||
case 0x83: {
|
||||
// Challenge Response
|
||||
m_infinity_base->GetNextAndScramble(sequence, q_result);
|
||||
break;
|
||||
}
|
||||
case 0x90:
|
||||
case 0x92:
|
||||
case 0x93:
|
||||
case 0x95:
|
||||
case 0x96: {
|
||||
// Color commands
|
||||
m_infinity_base->GetBlankResponse(sequence, q_result);
|
||||
break;
|
||||
}
|
||||
case 0xA1: {
|
||||
// Get Present Figures
|
||||
m_infinity_base->GetPresentFigures(sequence, q_result);
|
||||
break;
|
||||
}
|
||||
case 0xA2: {
|
||||
// Read Block from Figure
|
||||
m_infinity_base->QueryBlock(transfer->buffer[4], transfer->buffer[5], q_result,
|
||||
sequence);
|
||||
break;
|
||||
}
|
||||
case 0xA3: {
|
||||
// Write block to figure
|
||||
m_infinity_base->WriteBlock(transfer->buffer[4], transfer->buffer[5],
|
||||
&transfer->buffer[7], q_result, sequence);
|
||||
break;
|
||||
}
|
||||
case 0xB4: {
|
||||
// Get figure ID
|
||||
m_infinity_base->GetFigureIdentifier(transfer->buffer[4], sequence, q_result);
|
||||
break;
|
||||
}
|
||||
case 0xB5: {
|
||||
// Get status?
|
||||
m_infinity_base->GetBlankResponse(sequence, q_result);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_ERROR(Lib_Usbd, "Unhandled Infinity Query: {}", command);
|
||||
break;
|
||||
}
|
||||
|
||||
m_queries.push(q_result);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_ERROR(Lib_Usbd, "Unhandled Infinity Endpoint: {}", transfer->endpoint);
|
||||
break;
|
||||
}
|
||||
return LIBUSB_TRANSFER_COMPLETED;
|
||||
}
|
||||
|
||||
void InfinityFigure::Save() {
|
||||
if (!infFile.IsOpen())
|
||||
return;
|
||||
|
||||
infFile.Seek(0);
|
||||
infFile.Write(data);
|
||||
}
|
||||
} // namespace Libraries::Usbd
|
||||
112
src/core/libraries/usbd/emulated/infinity.h
Normal file
112
src/core/libraries/usbd/emulated/infinity.h
Normal file
@ -0,0 +1,112 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
|
||||
#include "common/io_file.h"
|
||||
#include "core/libraries/usbd/usb_backend.h"
|
||||
|
||||
namespace Libraries::Usbd {
|
||||
|
||||
constexpr u16 INFINITY_BLOCK_COUNT = 0x14;
|
||||
constexpr u16 INFINITY_BLOCK_SIZE = 0x10;
|
||||
constexpr u16 INFINITY_FIGURE_SIZE = INFINITY_BLOCK_COUNT * INFINITY_BLOCK_SIZE;
|
||||
constexpr u8 MAX_INFINITY_FIGURES = 9;
|
||||
|
||||
struct InfinityFigure final {
|
||||
Common::FS::IOFile infFile;
|
||||
std::array<u8, INFINITY_FIGURE_SIZE> data{};
|
||||
bool present = false;
|
||||
u8 order_added = 255;
|
||||
void Save();
|
||||
};
|
||||
|
||||
class InfinityBase final : public UsbEmulatedImpl {
|
||||
public:
|
||||
InfinityBase();
|
||||
~InfinityBase() override = default;
|
||||
|
||||
void GetBlankResponse(u8 sequence, std::array<u8, 32>& reply_buf);
|
||||
void DescrambleAndSeed(u8* buf, u8 sequence, std::array<u8, 32>& reply_buf);
|
||||
void GetNextAndScramble(u8 sequence, std::array<u8, 32>& reply_buf);
|
||||
void GetPresentFigures(u8 sequence, std::array<u8, 32>& reply_buf);
|
||||
void QueryBlock(u8 fig_num, u8 block, std::array<u8, 32>& reply_buf, u8 sequence);
|
||||
void WriteBlock(u8 fig_num, u8 block, const u8* to_write_buf, std::array<u8, 32>& reply_buf,
|
||||
u8 sequence);
|
||||
void GetFigureIdentifier(u8 fig_num, u8 sequence, std::array<u8, 32>& reply_buf);
|
||||
std::optional<std::array<u8, 32>> PopAddedRemovedResponse();
|
||||
|
||||
void LoadFigure(std::string file_name, u8 pad, u8 slot) override;
|
||||
void RemoveFigure(u8 pad, u8 slot, bool full_remove) override;
|
||||
void MoveFigure(u8 new_pad, u8 new_index, u8 old_pad, u8 old_index) override {}
|
||||
void TempRemoveFigure(u8 index) override {}
|
||||
void CancelRemoveFigure(u8 index) override {}
|
||||
|
||||
void LoadInfinityFigure(const std::array<u8, 0x14 * 0x10>& buf, Common::FS::IOFile file,
|
||||
u8 position);
|
||||
|
||||
protected:
|
||||
std::mutex infinity_mutex;
|
||||
std::array<InfinityFigure, MAX_INFINITY_FIGURES> infinity_figures;
|
||||
|
||||
private:
|
||||
u8 GenerateChecksum(const std::array<u8, 32>& data, int num_of_bytes) const;
|
||||
u32 Descramble(u64 num_to_descramble);
|
||||
u64 Scramble(u32 num_to_scramble, u32 garbage);
|
||||
void GenerateSeed(u32 seed);
|
||||
u32 GetNext();
|
||||
InfinityFigure& GetFigureByOrder(u8 order_added);
|
||||
u8 DeriveFigurePosition(u8 position);
|
||||
|
||||
u32 random_a = 0;
|
||||
u32 random_b = 0;
|
||||
u32 random_c = 0;
|
||||
u32 random_d = 0;
|
||||
|
||||
u8 m_figure_order = 0;
|
||||
std::queue<std::array<u8, 32>> m_figure_added_removed_responses;
|
||||
};
|
||||
|
||||
class InfinityBackend final : public UsbEmulatedBackend {
|
||||
protected:
|
||||
libusb_endpoint_descriptor* FillEndpointDescriptorPair() override;
|
||||
libusb_interface_descriptor* FillInterfaceDescriptor(
|
||||
libusb_endpoint_descriptor* descs) override;
|
||||
libusb_config_descriptor* FillConfigDescriptor(libusb_interface* inter) override;
|
||||
libusb_device_descriptor* FillDeviceDescriptor() override;
|
||||
|
||||
s32 ControlTransfer(libusb_device_handle* dev_handle, u8 bmRequestType, u8 bRequest, u16 wValue,
|
||||
u16 wIndex, u8* data, u16 wLength, u32 timeout) override {
|
||||
return LIBUSB_SUCCESS;
|
||||
}
|
||||
|
||||
libusb_transfer_status HandleAsyncTransfer(libusb_transfer* transfer) override;
|
||||
|
||||
std::shared_ptr<UsbEmulatedImpl> GetImplRef() override {
|
||||
return m_infinity_base;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<InfinityBase> m_infinity_base = std::make_shared<InfinityBase>();
|
||||
|
||||
std::array<u8, 9> m_endpoint_out_extra = {0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, 0x1d, 0x00};
|
||||
std::vector<libusb_endpoint_descriptor> m_endpoint_descriptors = {
|
||||
{0x7, 0x5, 0x81, 0x3, 0x20, 0x1, 0x0, 0x0},
|
||||
{0x7, 0x5, 0x1, 0x3, 0x20, 0x1, 0x0, 0x0, m_endpoint_out_extra.data(), 9}};
|
||||
std::vector<libusb_interface_descriptor> m_interface_descriptors = {
|
||||
{0x9, 0x4, 0x0, 0x0, 0x2, 0x3, 0x0, 0x0, 0x0}};
|
||||
std::vector<libusb_config_descriptor> m_config_descriptors = {
|
||||
{0x9, 0x2, 0x29, 0x1, 0x1, 0x0, 0x80, 0xFA}};
|
||||
std::vector<libusb_device_descriptor> m_device_descriptors = {
|
||||
{0x12, 0x1, 0x200, 0x0, 0x0, 0x0, 0x20, 0x0E6F, 0x0129, 0x200, 0x1, 0x2, 0x3, 0x1}};
|
||||
|
||||
std::queue<std::array<u8, 32>> m_queries;
|
||||
};
|
||||
} // namespace Libraries::Usbd
|
||||
503
src/core/libraries/usbd/emulated/skylander.cpp
Normal file
503
src/core/libraries/usbd/emulated/skylander.cpp
Normal file
@ -0,0 +1,503 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "skylander.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace Libraries::Usbd {
|
||||
|
||||
SkylanderPortal::SkylanderPortal() {}
|
||||
|
||||
void SkylanderPortal::LoadFigure(std::string file_name, u8 pad, u8 slot) {
|
||||
Common::FS::IOFile file(file_name, Common::FS::FileAccessMode::ReadWrite);
|
||||
std::array<u8, 0x40 * 0x10> data;
|
||||
ASSERT(file.Read(data) == data.size());
|
||||
ui_skylanders[slot] = LoadSkylander(data.data(), std::move(file));
|
||||
}
|
||||
|
||||
void SkylanderPortal::RemoveFigure(u8 pad, u8 slot, bool full_remove) {
|
||||
std::lock_guard lock(sky_mutex);
|
||||
auto& thesky = skylanders[ui_skylanders[slot]];
|
||||
|
||||
if (thesky.status & 1) {
|
||||
thesky.status = 2;
|
||||
thesky.queued_status.push(Skylander::REMOVING);
|
||||
thesky.queued_status.push(Skylander::REMOVED);
|
||||
thesky.Save();
|
||||
thesky.skyFile.Close();
|
||||
}
|
||||
}
|
||||
|
||||
u8 SkylanderPortal::LoadSkylander(u8* buf, Common::FS::IOFile file) {
|
||||
std::lock_guard lock(sky_mutex);
|
||||
|
||||
u32 skySerial = 0;
|
||||
for (int i = 3; i > -1; i--) {
|
||||
skySerial <<= 8;
|
||||
skySerial |= buf[i];
|
||||
}
|
||||
u8 foundSlot = 0xFF;
|
||||
|
||||
// mimics spot retaining on the portal
|
||||
for (auto i = 0; i < MAX_SKYLANDERS; i++) {
|
||||
if ((skylanders[i].status & 1) == 0) {
|
||||
if (skylanders[i].last_id == skySerial) {
|
||||
foundSlot = i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (i < foundSlot) {
|
||||
foundSlot = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundSlot != 0xFF) {
|
||||
auto& skylander = skylanders[foundSlot];
|
||||
memcpy(skylander.data.data(), buf, skylander.data.size());
|
||||
skylander.skyFile = std::move(file);
|
||||
skylander.status = Skylander::ADDED;
|
||||
skylander.queued_status.push(Skylander::ADDED);
|
||||
skylander.queued_status.push(Skylander::READY);
|
||||
skylander.last_id = skySerial;
|
||||
}
|
||||
return foundSlot;
|
||||
}
|
||||
|
||||
void SkylanderPortal::Activate() {
|
||||
const std::lock_guard lock(sky_mutex);
|
||||
if (m_activated) {
|
||||
// If the portal was already active no change is needed
|
||||
return;
|
||||
}
|
||||
|
||||
// If not we need to advertise change to all the figures present on the portal
|
||||
for (auto& s : skylanders) {
|
||||
if (s.status & 1) {
|
||||
s.queued_status.push(Skylander::ADDED);
|
||||
s.queued_status.push(Skylander::READY);
|
||||
}
|
||||
}
|
||||
|
||||
m_activated = true;
|
||||
}
|
||||
|
||||
void SkylanderPortal::Deactivate() {
|
||||
const std::lock_guard lock(sky_mutex);
|
||||
|
||||
for (auto& s : skylanders) {
|
||||
// check if at the end of the updates there would be a figure on the portal
|
||||
if (!s.queued_status.empty()) {
|
||||
s.status = s.queued_status.back();
|
||||
s.queued_status = std::queue<u8>();
|
||||
}
|
||||
|
||||
s.status &= 1;
|
||||
}
|
||||
|
||||
m_activated = false;
|
||||
}
|
||||
|
||||
// Side:
|
||||
// 0x00 = right
|
||||
// 0x01 = left and right
|
||||
// 0x02 = left
|
||||
// 0x03 = trap
|
||||
void SkylanderPortal::SetLEDs(u8 side, u8 red, u8 green, u8 blue) {
|
||||
const std::lock_guard lock(sky_mutex);
|
||||
if (side == 0x00) {
|
||||
m_color_right.red = red;
|
||||
m_color_right.green = green;
|
||||
m_color_right.blue = blue;
|
||||
} else if (side == 0x01) {
|
||||
m_color_right.red = red;
|
||||
m_color_right.green = green;
|
||||
m_color_right.blue = blue;
|
||||
|
||||
m_color_left.red = red;
|
||||
m_color_left.green = green;
|
||||
m_color_left.blue = blue;
|
||||
} else if (side == 0x02) {
|
||||
m_color_left.red = red;
|
||||
m_color_left.green = green;
|
||||
m_color_left.blue = blue;
|
||||
} else if (side == 0x03) {
|
||||
m_color_trap.red = red;
|
||||
m_color_trap.green = green;
|
||||
m_color_trap.blue = blue;
|
||||
}
|
||||
}
|
||||
|
||||
std::array<u8, 64> SkylanderPortal::GetStatus() {
|
||||
const std::lock_guard lock(sky_mutex);
|
||||
|
||||
u32 status = 0;
|
||||
u8 active = 0x00;
|
||||
|
||||
if (m_activated) {
|
||||
active = 0x01;
|
||||
}
|
||||
|
||||
for (int i = MAX_SKYLANDERS - 1; i >= 0; i--) {
|
||||
auto& s = skylanders[i];
|
||||
|
||||
if (!s.queued_status.empty()) {
|
||||
s.status = s.queued_status.front();
|
||||
s.queued_status.pop();
|
||||
}
|
||||
status <<= 2;
|
||||
status |= s.status;
|
||||
}
|
||||
|
||||
std::array<u8, 64> interrupt_response = {0x53, 0x00, 0x00, 0x00, 0x00, m_interrupt_counter++,
|
||||
active, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00};
|
||||
memcpy(&interrupt_response[1], &status, sizeof(status));
|
||||
return interrupt_response;
|
||||
}
|
||||
|
||||
void SkylanderPortal::QueryBlock(u8 sky_num, u8 block, u8* reply_buf) {
|
||||
if (!IsSkylanderNumberValid(sky_num) || !IsBlockNumberValid(block))
|
||||
return;
|
||||
|
||||
std::lock_guard lock(sky_mutex);
|
||||
|
||||
const auto& skylander = skylanders[sky_num];
|
||||
|
||||
reply_buf[0] = 'Q';
|
||||
reply_buf[2] = block;
|
||||
if (skylander.status & Skylander::READY) {
|
||||
reply_buf[1] = (0x10 | sky_num);
|
||||
memcpy(reply_buf + 3, skylander.data.data() + (16 * block), 16);
|
||||
} else {
|
||||
reply_buf[1] = 0x01;
|
||||
}
|
||||
}
|
||||
|
||||
void SkylanderPortal::WriteBlock(u8 sky_num, u8 block, const u8* to_write_buf, u8* reply_buf) {
|
||||
if (!IsSkylanderNumberValid(sky_num) || !IsBlockNumberValid(block))
|
||||
return;
|
||||
|
||||
std::lock_guard lock(sky_mutex);
|
||||
|
||||
auto& skylander = skylanders[sky_num];
|
||||
|
||||
reply_buf[0] = 'W';
|
||||
reply_buf[2] = block;
|
||||
|
||||
if (skylander.status & 1) {
|
||||
reply_buf[1] = (0x10 | sky_num);
|
||||
memcpy(skylander.data.data() + (block * 16), to_write_buf, 16);
|
||||
skylander.Save();
|
||||
} else {
|
||||
reply_buf[1] = 0x01;
|
||||
}
|
||||
}
|
||||
|
||||
bool SkylanderPortal::IsSkylanderNumberValid(u8 sky_num) {
|
||||
return sky_num < MAX_SKYLANDERS;
|
||||
}
|
||||
|
||||
bool SkylanderPortal::IsBlockNumberValid(u8 block) {
|
||||
return block < 64;
|
||||
}
|
||||
|
||||
libusb_endpoint_descriptor* SkylanderBackend::FillEndpointDescriptorPair() {
|
||||
return m_endpoint_descriptors.data();
|
||||
}
|
||||
|
||||
libusb_interface_descriptor* SkylanderBackend::FillInterfaceDescriptor(
|
||||
libusb_endpoint_descriptor* descs) {
|
||||
m_interface_descriptors[0].endpoint = descs;
|
||||
return m_interface_descriptors.data();
|
||||
}
|
||||
|
||||
libusb_config_descriptor* SkylanderBackend::FillConfigDescriptor(libusb_interface* inter) {
|
||||
m_config_descriptors[0].interface = inter;
|
||||
return m_config_descriptors.data();
|
||||
}
|
||||
|
||||
libusb_device_descriptor* SkylanderBackend::FillDeviceDescriptor() {
|
||||
return m_device_descriptors.data();
|
||||
}
|
||||
|
||||
s32 SkylanderBackend::ControlTransfer(libusb_device_handle* dev_handle, u8 bmRequestType,
|
||||
u8 bRequest, u16 wValue, u16 wIndex, u8* data, u16 wLength,
|
||||
u32 timeout) {
|
||||
if (bmRequestType != 0x21) {
|
||||
return LIBUSB_ERROR_PIPE;
|
||||
}
|
||||
if (bRequest != 0x09) {
|
||||
return 8;
|
||||
}
|
||||
// Data to be sent back via the control transfer immediately
|
||||
std::array<u8, 64> control_response = {};
|
||||
s32 expected_count = 0;
|
||||
// Data to be queued to be sent back via the Interrupt Transfer (if needed)
|
||||
std::array<u8, 64> interrupt_response = {};
|
||||
switch (data[0]) {
|
||||
case 'A': {
|
||||
// Activation
|
||||
// Command { 'A', (00 | 01), 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
|
||||
// 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 }
|
||||
// Response { 'A', (00 | 01),
|
||||
// ff, 77, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
|
||||
// 00, 00, 00, 00, 00, 00, 00, 00 }
|
||||
// The 2nd byte of the command is whether to activate (0x01) or deactivate (0x00) the
|
||||
// portal. The response echos back the activation byte as the 2nd byte of the response. The
|
||||
// 3rd and 4th bytes of the response appear to vary from wired to wireless. On wired
|
||||
// portals, the bytes appear to always be ff 77. On wireless portals, during activation the
|
||||
// 3rd byte appears to count down from ff (possibly a battery power indication) and during
|
||||
// deactivation ed and eb responses have been observed. The 4th byte appears to always be 00
|
||||
// for wireless portals.
|
||||
|
||||
// Wii U Wireless: 41 01 f4 00 41 00 ed 00 41 01 f4 00 41 00 eb 00 41 01 f3 00 41 00 ed 00
|
||||
if (wLength == 2) {
|
||||
control_response = {data[0], data[1]};
|
||||
interrupt_response = {0x41, data[1], 0xFF, 0x77, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
m_queries.push(interrupt_response);
|
||||
expected_count = 10;
|
||||
m_skylander_portal->Activate();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'C': {
|
||||
// Color
|
||||
// Command { 'C', 12, 34, 56, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
|
||||
// 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 }
|
||||
// Response { 'C', 12, 34, 56, 00, 00,
|
||||
// 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
|
||||
// 00, 00, 00, 00 }
|
||||
// The 3 bytes {12, 34, 56} are RGB values.
|
||||
|
||||
// This command should set the color of the LED in the portal, however this appears
|
||||
// deprecated in most of the recent portals. On portals that do not have LEDs, this command
|
||||
// is silently ignored and do not require a response.
|
||||
if (wLength == 4) {
|
||||
m_skylander_portal->SetLEDs(0x01, data[1], data[2], data[3]);
|
||||
control_response = {0x43, data[1], data[2], data[3]};
|
||||
expected_count = 12;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'J': {
|
||||
// Sided color
|
||||
// The 2nd byte is the side
|
||||
// 0x00: right
|
||||
// 0x02: left
|
||||
|
||||
// The 3rd, 4th and 5th bytes are red, green and blue
|
||||
|
||||
// The 6th and 7th bytes form a little-endian short for how long the fade duration should be
|
||||
// in milliseconds.
|
||||
// For example, 500 milliseconds becomes 0xF4, 0x01
|
||||
if (wLength == 7) {
|
||||
control_response = {data[0], data[1], data[2], data[3], data[4], data[5], data[6]};
|
||||
expected_count = 15;
|
||||
interrupt_response = {data[0]};
|
||||
m_queries.push(interrupt_response);
|
||||
m_skylander_portal->SetLEDs(data[1], data[2], data[3], data[4]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'L': {
|
||||
// Light
|
||||
// This command is used while playing audio through the portal
|
||||
|
||||
// The 2nd bytes is the position
|
||||
// 0x00: right
|
||||
// 0x01: trap led
|
||||
// 0x02: left
|
||||
|
||||
// The 3rd, 4th and 5th bytes are red, green and blue
|
||||
// the trap led is white-only
|
||||
// increasing or decreasing the values results in a brighter or dimmer light
|
||||
if (wLength == 5) {
|
||||
control_response = {data[0], data[1], data[2], data[3], data[4]};
|
||||
expected_count = 13;
|
||||
|
||||
u8 side = data[1];
|
||||
if (side == 0x02) {
|
||||
side = 0x04;
|
||||
}
|
||||
m_skylander_portal->SetLEDs(side, data[2], data[3], data[4]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'M': {
|
||||
// Audio Firmware version
|
||||
// Respond with version obtained from Trap Team wired portal
|
||||
if (wLength == 2) {
|
||||
control_response = {data[0], data[1]};
|
||||
expected_count = 10;
|
||||
interrupt_response = {data[0], data[1], 0x00, 0x19};
|
||||
m_queries.push(interrupt_response);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Query
|
||||
// Command { 'Q', 10, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
|
||||
// 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 }
|
||||
// Response { 'Q', 10, 00, 00, 00, 00,
|
||||
// 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
|
||||
// 00, 00, 00, 00 }
|
||||
// In the command the 2nd byte indicates which Skylander to query data
|
||||
// from. Index starts at 0x10 for the 1st Skylander (as reported in the Status command.) The
|
||||
// 16th Skylander indexed would be 0x20. The 3rd byte indicate which block to read from.
|
||||
|
||||
// A response with the 2nd byte of 0x01 indicates an error in the read. Otherwise, the
|
||||
// response indicates the Skylander's index in the 2nd byte, the block read in the 3rd byte,
|
||||
// data (16 bytes) is contained in bytes 4-19.
|
||||
|
||||
// A Skylander has 64 blocks of data indexed from 0x00 to 0x3f. SwapForce characters have 2
|
||||
// character indexes, these may not be sequential.
|
||||
case 'Q': {
|
||||
if (wLength == 3) {
|
||||
const u8 sky_num = data[1] & 0xF;
|
||||
const u8 block = data[2];
|
||||
m_skylander_portal->QueryBlock(sky_num, block, interrupt_response.data());
|
||||
m_queries.push(interrupt_response);
|
||||
control_response = {data[0], data[1], data[2]};
|
||||
expected_count = 11;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'R': {
|
||||
// Ready
|
||||
// Command { 'R', 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
|
||||
// 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 }
|
||||
// Response { 'R', 02, 0a, 03, 02, 00,
|
||||
// 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
|
||||
// 00, 00, 00, 00 }
|
||||
// The 4 byte sequence after the R (0x52) is unknown, but appears consistent based on device
|
||||
// type.
|
||||
if (wLength == 2) {
|
||||
control_response = {0x52, 0x00};
|
||||
interrupt_response = {0x52, 0x02, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
m_queries.push(interrupt_response);
|
||||
expected_count = 10;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Status
|
||||
// Command { 'S', 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
|
||||
// 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 }
|
||||
// Response { 'S', 55, 00, 00, 55, 3e,
|
||||
// (00|01), 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
|
||||
// 00, 00, 00, 00, 00 }
|
||||
// Status is the default command. If you open the HID device and
|
||||
// activate the portal, you will get status outputs.
|
||||
|
||||
// The 4 bytes {55, 00, 00, 55} are the status of characters on the portal. The 4 bytes are
|
||||
// treated as a 32-bit binary array. Each unique Skylander placed on a board is represented
|
||||
// by 2 bits starting with the first Skylander in the least significant bit. This bit is
|
||||
// present whenever the Skylander is added or present on the portal. When the Skylander is
|
||||
// added to the board, both bits are set in the next status message as a one-time signal.
|
||||
// When a Skylander is removed from the board, only the most significant bit of the 2 bits
|
||||
// is set.
|
||||
|
||||
// Different portals can track a different number of RFID tags. The Wii Wireless portal
|
||||
// tracks 4, the Wired portal can track 8. The maximum number of unique Skylanders tracked
|
||||
// at any time is 16, after which new Skylanders appear to cycle unused bits.
|
||||
|
||||
// Certain Skylanders, e.g. SwapForce Skylanders, are represented as 2 ID in the bit array.
|
||||
// This may be due to the presence of 2 RFIDs, one for each half of the Skylander.
|
||||
|
||||
// The 6th byte {3e} is a counter and increments by one. It will roll over when reaching
|
||||
// {ff}.
|
||||
|
||||
// The purpose of the (00\|01) byte at the 7th position appear to indicate if the portal has
|
||||
// been activated: {01} when active and {00} when deactivated.
|
||||
case 'S': {
|
||||
if (wLength == 1) {
|
||||
// The Status interrupt responses are automatically handled via the GetStatus method
|
||||
control_response = {data[0]};
|
||||
expected_count = 9;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'V': {
|
||||
if (wLength == 4) {
|
||||
control_response = {data[0], data[1], data[2], data[3]};
|
||||
expected_count = 12;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Write
|
||||
// Command { 'W', 10, 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 00,
|
||||
// 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 }
|
||||
// Response { 'W', 00, 00, 00, 00, 00,
|
||||
// 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
|
||||
// 00, 00, 00, 00 }
|
||||
// In the command the 2nd byte indicates which Skylander to query data from. Index starts at
|
||||
// 0x10 for the 1st Skylander (as reported in the Status command.) The 16th Skylander
|
||||
// indexed would be 0x20.
|
||||
|
||||
// 3rd byte is the block to write to.
|
||||
|
||||
// Bytes 4 - 19 ({ 01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f }) are the
|
||||
// data to write.
|
||||
|
||||
// The response does not appear to return the id of the Skylander being written, the 2nd
|
||||
// byte is 0x00; however, the 3rd byte echos the block that was written (0x00 in example
|
||||
// above.)
|
||||
|
||||
case 'W': {
|
||||
if (wLength == 19) {
|
||||
const u8 sky_num = data[1] & 0xF;
|
||||
const u8 block = data[2];
|
||||
m_skylander_portal->WriteBlock(sky_num, block, &data[3], interrupt_response.data());
|
||||
m_queries.push(interrupt_response);
|
||||
control_response = {data[0], data[1], data[2], data[3], data[4],
|
||||
data[5], data[6], data[7], data[8], data[9],
|
||||
data[10], data[11], data[12], data[13], data[14],
|
||||
data[15], data[16], data[17], data[18]};
|
||||
expected_count = 27;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_ERROR(Lib_Usbd, "Unhandled Skylander Portal Query: {}", data[0]);
|
||||
break;
|
||||
}
|
||||
return expected_count;
|
||||
}
|
||||
|
||||
libusb_transfer_status SkylanderBackend::HandleAsyncTransfer(libusb_transfer* transfer) {
|
||||
switch (transfer->endpoint) {
|
||||
case 0x81:
|
||||
if (m_queries.empty()) {
|
||||
memcpy(transfer->buffer, m_skylander_portal->GetStatus().data(), 32);
|
||||
} else {
|
||||
memcpy(transfer->buffer, m_queries.front().data(), 32);
|
||||
m_queries.pop();
|
||||
}
|
||||
transfer->length = 32;
|
||||
break;
|
||||
case 0x02:
|
||||
LOG_INFO(Lib_Usbd, "OUT ENDPOINT");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return LIBUSB_TRANSFER_COMPLETED;
|
||||
}
|
||||
|
||||
void Skylander::Save() {
|
||||
if (!skyFile.IsOpen())
|
||||
return;
|
||||
|
||||
skyFile.Seek(0);
|
||||
skyFile.Write(data);
|
||||
}
|
||||
} // namespace Libraries::Usbd
|
||||
111
src/core/libraries/usbd/emulated/skylander.h
Normal file
111
src/core/libraries/usbd/emulated/skylander.h
Normal file
@ -0,0 +1,111 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
||||
#include "common/io_file.h"
|
||||
#include "core/libraries/usbd/usb_backend.h"
|
||||
|
||||
namespace Libraries::Usbd {
|
||||
|
||||
constexpr u16 SKY_BLOCK_COUNT = 0x40;
|
||||
constexpr u16 SKY_BLOCK_SIZE = 0x10;
|
||||
constexpr u16 SKY_FIGURE_SIZE = SKY_BLOCK_COUNT * SKY_BLOCK_SIZE;
|
||||
constexpr u8 MAX_SKYLANDERS = 16;
|
||||
|
||||
struct Skylander final {
|
||||
Common::FS::IOFile skyFile;
|
||||
u8 status = 0;
|
||||
std::queue<u8> queued_status;
|
||||
std::array<u8, SKY_FIGURE_SIZE> data{};
|
||||
u32 last_id = 0;
|
||||
void Save();
|
||||
|
||||
enum : u8 { REMOVED = 0, READY = 1, REMOVING = 2, ADDED = 3 };
|
||||
};
|
||||
|
||||
struct SkylanderLEDColor final {
|
||||
u8 red = 0;
|
||||
u8 green = 0;
|
||||
u8 blue = 0;
|
||||
};
|
||||
|
||||
class SkylanderPortal final : public UsbEmulatedImpl {
|
||||
public:
|
||||
SkylanderPortal();
|
||||
~SkylanderPortal() override = default;
|
||||
|
||||
void Activate();
|
||||
void Deactivate();
|
||||
void SetLEDs(u8 side, u8 r, u8 g, u8 b);
|
||||
|
||||
std::array<u8, 64> GetStatus();
|
||||
void QueryBlock(u8 sky_num, u8 block, u8* reply_buf);
|
||||
void WriteBlock(u8 sky_num, u8 block, const u8* to_write_buf, u8* reply_buf);
|
||||
|
||||
void LoadFigure(std::string file_name, u8 pad, u8 slot) override;
|
||||
void RemoveFigure(u8 pad, u8 slot, bool full_remove) override;
|
||||
void MoveFigure(u8 new_pad, u8 new_index, u8 old_pad, u8 old_index) override {}
|
||||
void TempRemoveFigure(u8 index) override {}
|
||||
void CancelRemoveFigure(u8 index) override {}
|
||||
|
||||
u8 LoadSkylander(u8* buf, Common::FS::IOFile file);
|
||||
|
||||
protected:
|
||||
std::mutex sky_mutex;
|
||||
|
||||
private:
|
||||
static bool IsSkylanderNumberValid(u8 sky_num);
|
||||
static bool IsBlockNumberValid(u8 block);
|
||||
|
||||
bool m_activated = true;
|
||||
bool m_status_updated = false;
|
||||
u8 m_interrupt_counter = 0;
|
||||
SkylanderLEDColor m_color_right = {};
|
||||
SkylanderLEDColor m_color_left = {};
|
||||
SkylanderLEDColor m_color_trap = {};
|
||||
|
||||
std::array<Skylander, MAX_SKYLANDERS> skylanders;
|
||||
std::array<u8, MAX_SKYLANDERS> ui_skylanders;
|
||||
};
|
||||
|
||||
class SkylanderBackend final : public UsbEmulatedBackend {
|
||||
protected:
|
||||
libusb_endpoint_descriptor* FillEndpointDescriptorPair() override;
|
||||
libusb_interface_descriptor* FillInterfaceDescriptor(
|
||||
libusb_endpoint_descriptor* descs) override;
|
||||
libusb_config_descriptor* FillConfigDescriptor(libusb_interface* inter) override;
|
||||
libusb_device_descriptor* FillDeviceDescriptor() override;
|
||||
|
||||
s32 ControlTransfer(libusb_device_handle* dev_handle, u8 bmRequestType, u8 bRequest, u16 wValue,
|
||||
u16 wIndex, u8* data, u16 wLength, u32 timeout) override;
|
||||
|
||||
libusb_transfer_status HandleAsyncTransfer(libusb_transfer* transfer) override;
|
||||
|
||||
std::shared_ptr<UsbEmulatedImpl> GetImplRef() override {
|
||||
return m_skylander_portal;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<SkylanderPortal> m_skylander_portal = std::make_shared<SkylanderPortal>();
|
||||
|
||||
std::array<u8, 9> m_endpoint_out_extra = {0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, 0x1d, 0x00};
|
||||
std::vector<libusb_endpoint_descriptor> m_endpoint_descriptors = {
|
||||
{0x7, 0x5, 0x81, 0x3, 0x40, 0x1, 0x0, 0x0},
|
||||
{0x7, 0x5, 0x2, 0x3, 0x40, 0x1, 0x0, 0x0, m_endpoint_out_extra.data(), 9}};
|
||||
std::vector<libusb_interface_descriptor> m_interface_descriptors = {
|
||||
{0x9, 0x4, 0x0, 0x0, 0x2, 0x3, 0x0, 0x0, 0x0}};
|
||||
std::vector<libusb_config_descriptor> m_config_descriptors = {
|
||||
{0x9, 0x2, 0x29, 0x1, 0x1, 0x0, 0x80, 0xFA}};
|
||||
std::vector<libusb_device_descriptor> m_device_descriptors = {
|
||||
{0x12, 0x1, 0x200, 0x0, 0x0, 0x0, 0x40, 0x1430, 0x150, 0x100, 0x1, 0x2, 0x0, 0x1}};
|
||||
|
||||
std::queue<std::array<u8, 64>> m_queries;
|
||||
};
|
||||
} // namespace Libraries::Usbd
|
||||
477
src/core/libraries/usbd/usb_backend.h
Normal file
477
src/core/libraries/usbd/usb_backend.h
Normal file
@ -0,0 +1,477 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
|
||||
#include <libusb.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Libraries::Usbd {
|
||||
|
||||
#if defined(_WIN32)
|
||||
typedef CRITICAL_SECTION usbi_mutex_t;
|
||||
#else
|
||||
typedef pthread_mutex_t usbi_mutex_t;
|
||||
#endif
|
||||
|
||||
struct list_head {
|
||||
struct list_head *prev, *next;
|
||||
};
|
||||
|
||||
// Forward declared libusb structs
|
||||
struct UsbDeviceHandle {
|
||||
usbi_mutex_t lock;
|
||||
unsigned long claimed_interfaces;
|
||||
struct list_head list;
|
||||
struct libusb_device* dev;
|
||||
int auto_detach_kernel_driver;
|
||||
};
|
||||
|
||||
struct UsbDevice {
|
||||
volatile long refcnt;
|
||||
|
||||
struct libusb_context* ctx;
|
||||
struct libusb_device* parent_dev;
|
||||
|
||||
u8 bus_number;
|
||||
u8 port_number;
|
||||
u8 device_address;
|
||||
enum libusb_speed speed;
|
||||
|
||||
struct list_head list;
|
||||
unsigned long session_data;
|
||||
|
||||
struct libusb_device_descriptor device_descriptor;
|
||||
volatile long attached;
|
||||
};
|
||||
|
||||
class UsbEmulatedImpl {
|
||||
public:
|
||||
UsbEmulatedImpl() = default;
|
||||
|
||||
virtual void LoadFigure(std::string file_name, u8 pad, u8 slot) = 0;
|
||||
virtual void RemoveFigure(u8 pad, u8 slot, bool full_remove) = 0;
|
||||
virtual void MoveFigure(u8 new_pad, u8 new_index, u8 old_pad, u8 old_index) = 0;
|
||||
virtual void TempRemoveFigure(u8 index) = 0;
|
||||
virtual void CancelRemoveFigure(u8 index) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~UsbEmulatedImpl() = default;
|
||||
};
|
||||
|
||||
class UsbBackend {
|
||||
public:
|
||||
UsbBackend() = default;
|
||||
|
||||
virtual s32 Init() = 0;
|
||||
virtual void Exit() = 0;
|
||||
|
||||
virtual s64 GetDeviceList(libusb_device*** list) = 0;
|
||||
virtual void FreeDeviceList(libusb_device** list, s32 unref_devices) = 0;
|
||||
|
||||
virtual s32 GetConfiguration(libusb_device_handle* dev, s32* config) = 0;
|
||||
virtual s32 GetDeviceDescriptor(libusb_device* dev, libusb_device_descriptor* desc) = 0;
|
||||
virtual s32 GetActiveConfigDescriptor(libusb_device* dev,
|
||||
libusb_config_descriptor** config) = 0;
|
||||
virtual s32 GetConfigDescriptor(libusb_device* dev, u8 config_index,
|
||||
libusb_config_descriptor** config) = 0;
|
||||
virtual void FreeConfigDescriptor(libusb_config_descriptor* config) = 0;
|
||||
|
||||
virtual u8 GetBusNumber(libusb_device* dev) = 0;
|
||||
virtual u8 GetDeviceAddress(libusb_device* dev) = 0;
|
||||
virtual s32 GetMaxPacketSize(libusb_device* dev, u8 endpoint) = 0;
|
||||
|
||||
virtual s32 OpenDevice(libusb_device* dev, libusb_device_handle** dev_handle) = 0;
|
||||
virtual void CloseDevice(libusb_device_handle* dev_handle) = 0;
|
||||
virtual libusb_device* GetDevice(libusb_device_handle* dev_handle) = 0;
|
||||
|
||||
virtual s32 SetConfiguration(libusb_device_handle* dev_handle, s32 configuration) = 0;
|
||||
virtual s32 ClaimInterface(libusb_device_handle* dev_handle, s32 interface_number) = 0;
|
||||
virtual libusb_device_handle* OpenDeviceWithVidPid(u16 vendor_id, u16 product_id) = 0;
|
||||
virtual s32 ResetDevice(libusb_device_handle* dev_handle) = 0;
|
||||
|
||||
virtual s32 KernelDriverActive(libusb_device_handle* dev_handle, s32 interface_number) = 0;
|
||||
virtual s32 DetachKernelDriver(libusb_device_handle* dev_handle, s32 interface_number) = 0;
|
||||
virtual s32 AttachKernelDriver(libusb_device_handle* dev_handle, s32 interface_number) = 0;
|
||||
|
||||
virtual s32 ControlTransfer(libusb_device_handle* dev_handle, u8 bmRequestType, u8 bRequest,
|
||||
u16 wValue, u16 wIndex, u8* data, u16 wLength, u32 timeout) = 0;
|
||||
virtual s32 SubmitTransfer(libusb_transfer* transfer) = 0;
|
||||
|
||||
virtual s32 TryLockEvents() = 0;
|
||||
virtual void LockEvents() = 0;
|
||||
virtual void UnlockEvents() = 0;
|
||||
virtual s32 EventHandlingOk() = 0;
|
||||
virtual s32 EventHandlerActive() = 0;
|
||||
virtual void LockEventWaiters() = 0;
|
||||
virtual void UnlockEventWaiters() = 0;
|
||||
virtual s32 WaitForEvent(timeval* tv) = 0;
|
||||
|
||||
virtual s32 HandleEventsTimeout(timeval* tv) = 0;
|
||||
virtual s32 HandleEvents() = 0;
|
||||
virtual s32 HandleEventsLocked(timeval* tv) = 0;
|
||||
|
||||
virtual s32 CheckConnected(libusb_device_handle* dev) = 0;
|
||||
virtual std::shared_ptr<UsbEmulatedImpl> GetImplRef() = 0;
|
||||
|
||||
protected:
|
||||
virtual ~UsbBackend() = default;
|
||||
};
|
||||
|
||||
class UsbRealBackend : public UsbBackend {
|
||||
public:
|
||||
s32 Init() override {
|
||||
return libusb_init(&g_libusb_context);
|
||||
}
|
||||
void Exit() override {
|
||||
libusb_exit(g_libusb_context);
|
||||
}
|
||||
|
||||
s64 GetDeviceList(libusb_device*** list) override {
|
||||
return libusb_get_device_list(g_libusb_context, list);
|
||||
}
|
||||
void FreeDeviceList(libusb_device** list, s32 unref_devices) override {
|
||||
libusb_free_device_list(list, unref_devices);
|
||||
}
|
||||
|
||||
s32 GetConfiguration(libusb_device_handle* dev, s32* config) override {
|
||||
return libusb_get_configuration(dev, config);
|
||||
}
|
||||
s32 GetDeviceDescriptor(libusb_device* dev, libusb_device_descriptor* desc) override {
|
||||
return libusb_get_device_descriptor(dev, desc);
|
||||
}
|
||||
s32 GetActiveConfigDescriptor(libusb_device* dev, libusb_config_descriptor** config) override {
|
||||
return libusb_get_active_config_descriptor(dev, config);
|
||||
}
|
||||
s32 GetConfigDescriptor(libusb_device* dev, u8 config_index,
|
||||
libusb_config_descriptor** config) override {
|
||||
return libusb_get_config_descriptor(dev, config_index, config);
|
||||
}
|
||||
void FreeConfigDescriptor(libusb_config_descriptor* config) override {
|
||||
libusb_free_config_descriptor(config);
|
||||
}
|
||||
|
||||
u8 GetBusNumber(libusb_device* dev) override {
|
||||
return libusb_get_bus_number(dev);
|
||||
}
|
||||
u8 GetDeviceAddress(libusb_device* dev) override {
|
||||
return libusb_get_device_address(dev);
|
||||
}
|
||||
s32 GetMaxPacketSize(libusb_device* dev, u8 endpoint) override {
|
||||
return libusb_get_max_packet_size(dev, endpoint);
|
||||
}
|
||||
|
||||
s32 OpenDevice(libusb_device* dev, libusb_device_handle** dev_handle) override {
|
||||
return libusb_open(dev, dev_handle);
|
||||
}
|
||||
void CloseDevice(libusb_device_handle* dev_handle) override {
|
||||
libusb_close(dev_handle);
|
||||
}
|
||||
libusb_device* GetDevice(libusb_device_handle* dev_handle) override {
|
||||
return libusb_get_device(dev_handle);
|
||||
}
|
||||
|
||||
s32 SetConfiguration(libusb_device_handle* dev_handle, s32 configuration) override {
|
||||
return libusb_set_configuration(dev_handle, configuration);
|
||||
}
|
||||
s32 ClaimInterface(libusb_device_handle* dev_handle, s32 interface_number) override {
|
||||
return libusb_claim_interface(dev_handle, interface_number);
|
||||
}
|
||||
libusb_device_handle* OpenDeviceWithVidPid(u16 vendor_id, u16 product_id) override {
|
||||
return libusb_open_device_with_vid_pid(g_libusb_context, vendor_id, product_id);
|
||||
}
|
||||
s32 ResetDevice(libusb_device_handle* dev_handle) override {
|
||||
return libusb_reset_device(dev_handle);
|
||||
}
|
||||
|
||||
s32 KernelDriverActive(libusb_device_handle* dev_handle, s32 interface_number) override {
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
return s_has_removed_driver ? 0 : 1;
|
||||
#else
|
||||
return libusb_kernel_driver_active(dev_handle, interface_number);
|
||||
#endif
|
||||
}
|
||||
s32 DetachKernelDriver(libusb_device_handle* dev_handle, s32 interface_number) override {
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
s_has_removed_driver = true;
|
||||
return LIBUSB_SUCCESS;
|
||||
#else
|
||||
return libusb_detach_kernel_driver(dev_handle, interface_number);
|
||||
#endif
|
||||
}
|
||||
s32 AttachKernelDriver(libusb_device_handle* dev_handle, s32 interface_number) override {
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
s_has_removed_driver = false;
|
||||
return LIBUSB_SUCCESS;
|
||||
#else
|
||||
return libusb_attach_kernel_driver(dev_handle, interface_number);
|
||||
#endif
|
||||
}
|
||||
|
||||
s32 ControlTransfer(libusb_device_handle* dev_handle, u8 bmRequestType, u8 bRequest, u16 wValue,
|
||||
u16 wIndex, u8* data, u16 wLength, u32 timeout) override {
|
||||
return libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, data,
|
||||
wLength, timeout);
|
||||
}
|
||||
s32 SubmitTransfer(libusb_transfer* transfer) override {
|
||||
return libusb_submit_transfer(transfer);
|
||||
}
|
||||
|
||||
s32 TryLockEvents() override {
|
||||
return libusb_try_lock_events(g_libusb_context);
|
||||
}
|
||||
void LockEvents() override {
|
||||
return libusb_lock_events(g_libusb_context);
|
||||
}
|
||||
void UnlockEvents() override {
|
||||
return libusb_unlock_events(g_libusb_context);
|
||||
}
|
||||
s32 EventHandlingOk() override {
|
||||
return libusb_event_handling_ok(g_libusb_context);
|
||||
}
|
||||
s32 EventHandlerActive() override {
|
||||
return libusb_event_handler_active(g_libusb_context);
|
||||
}
|
||||
void LockEventWaiters() override {
|
||||
return libusb_lock_event_waiters(g_libusb_context);
|
||||
}
|
||||
void UnlockEventWaiters() override {
|
||||
return libusb_unlock_event_waiters(g_libusb_context);
|
||||
}
|
||||
s32 WaitForEvent(timeval* tv) override {
|
||||
return libusb_wait_for_event(g_libusb_context, tv);
|
||||
}
|
||||
|
||||
s32 HandleEventsTimeout(timeval* tv) override {
|
||||
return libusb_handle_events_timeout(g_libusb_context, tv);
|
||||
}
|
||||
s32 HandleEvents() override {
|
||||
return libusb_handle_events(g_libusb_context);
|
||||
}
|
||||
s32 HandleEventsLocked(timeval* tv) override {
|
||||
return libusb_handle_events_locked(g_libusb_context, tv);
|
||||
}
|
||||
|
||||
s32 CheckConnected(libusb_device_handle* dev) override {
|
||||
// There's no libusb version of this function.
|
||||
// Simulate by querying data.
|
||||
|
||||
int config;
|
||||
return libusb_get_configuration(dev, &config);
|
||||
}
|
||||
|
||||
std::shared_ptr<UsbEmulatedImpl> GetImplRef() override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
libusb_context* g_libusb_context = nullptr;
|
||||
bool s_has_removed_driver = false;
|
||||
};
|
||||
|
||||
class UsbEmulatedBackend : public UsbRealBackend {
|
||||
public:
|
||||
s64 GetDeviceList(libusb_device*** list) override {
|
||||
auto** fake = static_cast<libusb_device**>(calloc(2, sizeof(libusb_device*)));
|
||||
fake[0] = GetDevice(nullptr);
|
||||
fake[1] = nullptr;
|
||||
*list = fake;
|
||||
|
||||
return 1;
|
||||
}
|
||||
void FreeDeviceList(libusb_device** list, s32 unref_devices) override {
|
||||
if (!list) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (unref_devices) {
|
||||
int i = 0;
|
||||
libusb_device* dev;
|
||||
|
||||
while ((dev = list[i++]) != nullptr) {
|
||||
free(dev);
|
||||
}
|
||||
}
|
||||
free(list);
|
||||
}
|
||||
|
||||
s32 GetConfiguration(libusb_device_handle* dev, s32* config) override {
|
||||
config = nullptr;
|
||||
return LIBUSB_SUCCESS;
|
||||
}
|
||||
|
||||
s32 GetDeviceDescriptor(libusb_device* dev, libusb_device_descriptor* desc) override {
|
||||
std::memcpy(desc, FillDeviceDescriptor(), sizeof(libusb_device_descriptor));
|
||||
return LIBUSB_SUCCESS;
|
||||
}
|
||||
s32 GetActiveConfigDescriptor(libusb_device* dev, libusb_config_descriptor** config) override {
|
||||
const auto endpoint_descs = FillEndpointDescriptorPair();
|
||||
const auto interface_desc = FillInterfaceDescriptor(endpoint_descs);
|
||||
|
||||
const auto interface = static_cast<libusb_interface*>(calloc(1, sizeof(libusb_interface)));
|
||||
interface->altsetting = interface_desc;
|
||||
interface->num_altsetting = 1;
|
||||
|
||||
const auto new_config = FillConfigDescriptor(interface);
|
||||
|
||||
ASSERT(endpoint_descs && interface_desc && new_config);
|
||||
*config = new_config;
|
||||
return LIBUSB_SUCCESS;
|
||||
}
|
||||
s32 GetConfigDescriptor(libusb_device* dev, u8 config_index,
|
||||
libusb_config_descriptor** config) override {
|
||||
if (config_index >= 1) {
|
||||
return LIBUSB_ERROR_NOT_FOUND;
|
||||
}
|
||||
return GetActiveConfigDescriptor(dev, config);
|
||||
}
|
||||
void FreeConfigDescriptor(libusb_config_descriptor* config) override {
|
||||
// Member variable reference, don't free
|
||||
}
|
||||
|
||||
u8 GetBusNumber(libusb_device* dev) override {
|
||||
return 0;
|
||||
}
|
||||
u8 GetDeviceAddress(libusb_device* dev) override {
|
||||
return 0;
|
||||
}
|
||||
s32 GetMaxPacketSize(libusb_device* dev, u8 endpoint) override {
|
||||
libusb_device_descriptor* desc = nullptr;
|
||||
|
||||
int r = GetDeviceDescriptor(dev, desc);
|
||||
if (r < LIBUSB_SUCCESS) {
|
||||
return LIBUSB_ERROR_OTHER;
|
||||
}
|
||||
return desc->bMaxPacketSize0;
|
||||
}
|
||||
|
||||
s32 OpenDevice(libusb_device* dev, libusb_device_handle** dev_handle) override {
|
||||
auto* _dev_handle = static_cast<UsbDeviceHandle*>(calloc(1, sizeof(libusb_device_handle*)));
|
||||
if (!_dev_handle) {
|
||||
return LIBUSB_ERROR_NO_MEM;
|
||||
}
|
||||
_dev_handle->dev = dev;
|
||||
*dev_handle = reinterpret_cast<libusb_device_handle*>(_dev_handle);
|
||||
return LIBUSB_SUCCESS;
|
||||
}
|
||||
void CloseDevice(libusb_device_handle* dev_handle) override {
|
||||
LOG_WARNING(Lib_Usbd, "Guest decided to close device, might be an implementation issue");
|
||||
free(dev_handle);
|
||||
}
|
||||
libusb_device* GetDevice(libusb_device_handle* dev_handle) override {
|
||||
const auto desc = FillDeviceDescriptor();
|
||||
ASSERT(desc);
|
||||
|
||||
const auto fake = static_cast<UsbDevice*>(calloc(1, sizeof(UsbDevice)));
|
||||
fake->bus_number = 0;
|
||||
fake->port_number = 0;
|
||||
fake->device_address = 0;
|
||||
fake->device_descriptor = *desc;
|
||||
fake->ctx = g_libusb_context;
|
||||
return reinterpret_cast<libusb_device*>(fake);
|
||||
}
|
||||
|
||||
s32 SetConfiguration(libusb_device_handle* dev_handle, s32 configuration) override {
|
||||
return LIBUSB_SUCCESS;
|
||||
}
|
||||
s32 ClaimInterface(libusb_device_handle* dev_handle, s32 interface_number) override {
|
||||
return LIBUSB_SUCCESS;
|
||||
}
|
||||
libusb_device_handle* OpenDeviceWithVidPid(u16 vendor_id, u16 product_id) override {
|
||||
libusb_device_handle* dev_handle = nullptr;
|
||||
OpenDevice(GetDevice(nullptr), &dev_handle);
|
||||
return dev_handle;
|
||||
}
|
||||
s32 ResetDevice(libusb_device_handle* dev_handle) override {
|
||||
LOG_WARNING(Lib_Usbd, "Guest decided to reset device, might be an implementation issue");
|
||||
return LIBUSB_SUCCESS;
|
||||
}
|
||||
|
||||
s32 KernelDriverActive(libusb_device_handle* dev_handle, s32 interface_number) override {
|
||||
return s_has_removed_driver ? 0 : 1;
|
||||
}
|
||||
s32 DetachKernelDriver(libusb_device_handle* dev_handle, s32 interface_number) override {
|
||||
s_has_removed_driver = true;
|
||||
return LIBUSB_SUCCESS;
|
||||
}
|
||||
s32 AttachKernelDriver(libusb_device_handle* dev_handle, s32 interface_number) override {
|
||||
s_has_removed_driver = false;
|
||||
return LIBUSB_SUCCESS;
|
||||
}
|
||||
|
||||
s32 ControlTransfer(libusb_device_handle* dev_handle, u8 bmRequestType, u8 bRequest, u16 wValue,
|
||||
u16 wIndex, u8* data, u16 wLength, u32 timeout) override {
|
||||
LOG_WARNING(Lib_Usbd, "Backend has not handled control transfers");
|
||||
return LIBUSB_ERROR_PIPE;
|
||||
}
|
||||
s32 SubmitTransfer(libusb_transfer* transfer) override {
|
||||
ASSERT(transfer->type == LIBUSB_TRANSFER_TYPE_INTERRUPT);
|
||||
|
||||
// if we have no other flying transfers, start the list with this one
|
||||
if (flight_list.empty()) {
|
||||
flight_list.push_front(transfer);
|
||||
return LIBUSB_SUCCESS;
|
||||
}
|
||||
|
||||
// if we have infinite timeout, append to end of list
|
||||
if (transfer->timeout == 0) {
|
||||
flight_list.push_back(transfer);
|
||||
return LIBUSB_SUCCESS;
|
||||
}
|
||||
|
||||
// otherwise, find appropriate place in list
|
||||
for (auto it = flight_list.begin(); it != flight_list.end(); ++it) {
|
||||
if ((*it)->timeout > transfer->timeout) {
|
||||
flight_list.insert(it, transfer);
|
||||
return LIBUSB_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise we need to be inserted at the end
|
||||
flight_list.push_back(transfer);
|
||||
return LIBUSB_SUCCESS;
|
||||
}
|
||||
|
||||
s32 HandleEventsTimeout(timeval* tv) override {
|
||||
return HandleEvents();
|
||||
}
|
||||
s32 HandleEvents() override {
|
||||
if (!flight_list.empty()) {
|
||||
const auto transfer = flight_list.front();
|
||||
|
||||
const u8 flags = transfer->flags;
|
||||
transfer->status = HandleAsyncTransfer(transfer);
|
||||
transfer->actual_length = transfer->length;
|
||||
if (transfer->callback) {
|
||||
transfer->callback(transfer);
|
||||
}
|
||||
if (flags & LIBUSB_TRANSFER_FREE_TRANSFER) {
|
||||
libusb_free_transfer(transfer);
|
||||
}
|
||||
flight_list.pop_front();
|
||||
}
|
||||
|
||||
return LIBUSB_SUCCESS;
|
||||
}
|
||||
|
||||
s32 CheckConnected(libusb_device_handle* dev) override {
|
||||
return LIBUSB_SUCCESS;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual libusb_endpoint_descriptor* FillEndpointDescriptorPair() = 0;
|
||||
virtual libusb_interface_descriptor* FillInterfaceDescriptor(
|
||||
libusb_endpoint_descriptor* descs) = 0;
|
||||
virtual libusb_config_descriptor* FillConfigDescriptor(libusb_interface* inter) = 0;
|
||||
virtual libusb_device_descriptor* FillDeviceDescriptor() = 0;
|
||||
|
||||
virtual libusb_transfer_status HandleAsyncTransfer(libusb_transfer* transfer) = 0;
|
||||
|
||||
std::list<libusb_transfer*> flight_list;
|
||||
};
|
||||
|
||||
} // namespace Libraries::Usbd
|
||||
@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "usbd.h"
|
||||
@ -10,6 +9,8 @@
|
||||
#include <fmt/format.h>
|
||||
#include <libusb.h>
|
||||
|
||||
#include "common/config.h"
|
||||
|
||||
namespace Libraries::Usbd {
|
||||
|
||||
s32 libusb_to_orbis_error(int retVal) {
|
||||
@ -23,7 +24,7 @@ s32 libusb_to_orbis_error(int retVal) {
|
||||
return retVal;
|
||||
}
|
||||
|
||||
libusb_context* g_libusb_context;
|
||||
std::shared_ptr<UsbBackend> usb_backend;
|
||||
|
||||
#if defined(_WIN32)
|
||||
bool s_has_removed_driver = false;
|
||||
@ -32,25 +33,25 @@ bool s_has_removed_driver = false;
|
||||
s32 PS4_SYSV_ABI sceUsbdInit() {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
return libusb_to_orbis_error(libusb_init(&g_libusb_context));
|
||||
return libusb_to_orbis_error(usb_backend->Init());
|
||||
}
|
||||
|
||||
void PS4_SYSV_ABI sceUsbdExit() {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
libusb_exit(g_libusb_context);
|
||||
usb_backend->Exit();
|
||||
}
|
||||
|
||||
s64 PS4_SYSV_ABI sceUsbdGetDeviceList(SceUsbdDevice*** list) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
return libusb_to_orbis_error(libusb_get_device_list(g_libusb_context, list));
|
||||
return libusb_to_orbis_error(usb_backend->GetDeviceList(list));
|
||||
}
|
||||
|
||||
void PS4_SYSV_ABI sceUsbdFreeDeviceList(SceUsbdDevice** list, s32 unref_devices) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
libusb_free_device_list(list, unref_devices);
|
||||
usb_backend->FreeDeviceList(list, unref_devices);
|
||||
}
|
||||
|
||||
SceUsbdDevice* PS4_SYSV_ABI sceUsbdRefDevice(SceUsbdDevice* device) {
|
||||
@ -68,27 +69,27 @@ void PS4_SYSV_ABI sceUsbdUnrefDevice(SceUsbdDevice* device) {
|
||||
s32 PS4_SYSV_ABI sceUsbdGetConfiguration(SceUsbdDeviceHandle* dev_handle, s32* config) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
return libusb_to_orbis_error(libusb_get_configuration(dev_handle, config));
|
||||
return libusb_to_orbis_error(usb_backend->GetConfiguration(dev_handle, config));
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUsbdGetDeviceDescriptor(SceUsbdDevice* device, SceUsbdDeviceDescriptor* desc) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
return libusb_to_orbis_error(libusb_get_device_descriptor(device, desc));
|
||||
return libusb_to_orbis_error(usb_backend->GetDeviceDescriptor(device, desc));
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUsbdGetActiveConfigDescriptor(SceUsbdDevice* device,
|
||||
SceUsbdConfigDescriptor** config) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
return libusb_to_orbis_error(libusb_get_active_config_descriptor(device, config));
|
||||
return libusb_to_orbis_error(usb_backend->GetActiveConfigDescriptor(device, config));
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUsbdGetConfigDescriptor(SceUsbdDevice* device, u8 config_index,
|
||||
SceUsbdConfigDescriptor** config) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
return libusb_to_orbis_error(libusb_get_config_descriptor(device, config_index, config));
|
||||
return libusb_to_orbis_error(usb_backend->GetConfigDescriptor(device, config_index, config));
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUsbdGetConfigDescriptorByValue(SceUsbdDevice* device, u8 bConfigurationValue,
|
||||
@ -102,19 +103,19 @@ s32 PS4_SYSV_ABI sceUsbdGetConfigDescriptorByValue(SceUsbdDevice* device, u8 bCo
|
||||
void PS4_SYSV_ABI sceUsbdFreeConfigDescriptor(SceUsbdConfigDescriptor* config) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
libusb_free_config_descriptor(config);
|
||||
usb_backend->FreeConfigDescriptor(config);
|
||||
}
|
||||
|
||||
u8 PS4_SYSV_ABI sceUsbdGetBusNumber(SceUsbdDevice* device) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
return libusb_get_bus_number(device);
|
||||
return usb_backend->GetBusNumber(device);
|
||||
}
|
||||
|
||||
u8 PS4_SYSV_ABI sceUsbdGetDeviceAddress(SceUsbdDevice* device) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
return libusb_get_device_address(device);
|
||||
return usb_backend->GetDeviceAddress(device);
|
||||
}
|
||||
|
||||
SceUsbdSpeed PS4_SYSV_ABI sceUsbdGetDeviceSpeed(SceUsbdDevice* device) {
|
||||
@ -126,7 +127,7 @@ SceUsbdSpeed PS4_SYSV_ABI sceUsbdGetDeviceSpeed(SceUsbdDevice* device) {
|
||||
s32 PS4_SYSV_ABI sceUsbdGetMaxPacketSize(SceUsbdDevice* device, u8 endpoint) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
return libusb_to_orbis_error(libusb_get_max_packet_size(device, endpoint));
|
||||
return libusb_to_orbis_error(usb_backend->GetMaxPacketSize(device, endpoint));
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUsbdGetMaxIsoPacketSize(SceUsbdDevice* device, u8 endpoint) {
|
||||
@ -138,25 +139,25 @@ s32 PS4_SYSV_ABI sceUsbdGetMaxIsoPacketSize(SceUsbdDevice* device, u8 endpoint)
|
||||
s32 PS4_SYSV_ABI sceUsbdOpen(SceUsbdDevice* device, SceUsbdDeviceHandle** dev_handle) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
return libusb_to_orbis_error(libusb_open(device, dev_handle));
|
||||
return libusb_to_orbis_error(usb_backend->OpenDevice(device, dev_handle));
|
||||
}
|
||||
|
||||
void PS4_SYSV_ABI sceUsbdClose(SceUsbdDeviceHandle* dev_handle) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
libusb_close(dev_handle);
|
||||
usb_backend->CloseDevice(dev_handle);
|
||||
}
|
||||
|
||||
SceUsbdDevice* PS4_SYSV_ABI sceUsbdGetDevice(SceUsbdDeviceHandle* dev_handle) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
return libusb_get_device(dev_handle);
|
||||
return usb_backend->GetDevice(dev_handle);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUsbdSetConfiguration(SceUsbdDeviceHandle* dev_handle, s32 config) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
return libusb_to_orbis_error(libusb_set_configuration(dev_handle, config));
|
||||
return libusb_to_orbis_error(usb_backend->SetConfiguration(dev_handle, config));
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUsbdClaimInterface(SceUsbdDeviceHandle* dev_handle, s32 interface_number) {
|
||||
@ -166,7 +167,7 @@ s32 PS4_SYSV_ABI sceUsbdClaimInterface(SceUsbdDeviceHandle* dev_handle, s32 inte
|
||||
sceUsbdDetachKernelDriver(dev_handle, interface_number);
|
||||
}
|
||||
|
||||
return libusb_to_orbis_error(libusb_claim_interface(dev_handle, interface_number));
|
||||
return libusb_to_orbis_error(usb_backend->ClaimInterface(dev_handle, interface_number));
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUsbdReleaseInterface(SceUsbdDeviceHandle* dev_handle, s32 interface_number) {
|
||||
@ -178,7 +179,7 @@ s32 PS4_SYSV_ABI sceUsbdReleaseInterface(SceUsbdDeviceHandle* dev_handle, s32 in
|
||||
SceUsbdDeviceHandle* PS4_SYSV_ABI sceUsbdOpenDeviceWithVidPid(u16 vendor_id, u16 product_id) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
return libusb_open_device_with_vid_pid(g_libusb_context, vendor_id, product_id);
|
||||
return usb_backend->OpenDeviceWithVidPid(vendor_id, product_id);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUsbdSetInterfaceAltSetting(SceUsbdDeviceHandle* dev_handle,
|
||||
@ -198,42 +199,25 @@ s32 PS4_SYSV_ABI sceUsbdClearHalt(SceUsbdDeviceHandle* dev_handle, uint8_t endpo
|
||||
s32 PS4_SYSV_ABI sceUsbdResetDevice(SceUsbdDeviceHandle* dev_handle) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
return libusb_to_orbis_error(libusb_reset_device(dev_handle));
|
||||
return libusb_to_orbis_error(usb_backend->ResetDevice(dev_handle));
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUsbdKernelDriverActive(SceUsbdDeviceHandle* dev_handle, int interface_number) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
#if defined(_WIN32)
|
||||
if (!s_has_removed_driver)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
return libusb_to_orbis_error(libusb_kernel_driver_active(dev_handle, interface_number));
|
||||
return libusb_to_orbis_error(usb_backend->KernelDriverActive(dev_handle, interface_number));
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUsbdDetachKernelDriver(SceUsbdDeviceHandle* dev_handle, int interface_number) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
#if defined(_WIN32)
|
||||
s_has_removed_driver = true;
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
return libusb_to_orbis_error(libusb_detach_kernel_driver(dev_handle, interface_number));
|
||||
return libusb_to_orbis_error(usb_backend->DetachKernelDriver(dev_handle, interface_number));
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUsbdAttachKernelDriver(SceUsbdDeviceHandle* dev_handle, int interface_number) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
#if defined(_WIN32)
|
||||
s_has_removed_driver = false;
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
return libusb_to_orbis_error(libusb_attach_kernel_driver(dev_handle, interface_number));
|
||||
return libusb_to_orbis_error(usb_backend->AttachKernelDriver(dev_handle, interface_number));
|
||||
}
|
||||
|
||||
u8* PS4_SYSV_ABI sceUsbdControlTransferGetData(SceUsbdTransfer* transfer) {
|
||||
@ -264,7 +248,7 @@ SceUsbdTransfer* PS4_SYSV_ABI sceUsbdAllocTransfer(int iso_packets) {
|
||||
s32 PS4_SYSV_ABI sceUsbdSubmitTransfer(SceUsbdTransfer* transfer) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
return libusb_to_orbis_error(libusb_submit_transfer(transfer));
|
||||
return libusb_to_orbis_error(usb_backend->SubmitTransfer(transfer));
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUsbdCancelTransfer(SceUsbdTransfer* transfer) {
|
||||
@ -336,8 +320,8 @@ s32 PS4_SYSV_ABI sceUsbdControlTransfer(SceUsbdDeviceHandle* dev_handle, u8 requ
|
||||
u32 timeout) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
return libusb_to_orbis_error(libusb_control_transfer(dev_handle, request_type, bRequest, wValue,
|
||||
wIndex, data, wLength, timeout));
|
||||
return libusb_to_orbis_error(usb_backend->ControlTransfer(
|
||||
dev_handle, request_type, bRequest, wValue, wIndex, data, wLength, timeout));
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUsbdBulkTransfer(SceUsbdDeviceHandle* dev_handle, u8 endpoint, u8* data,
|
||||
@ -383,79 +367,73 @@ s32 PS4_SYSV_ABI sceUsbdGetStringDescriptorAscii(SceUsbdDeviceHandle* dev_handle
|
||||
s32 PS4_SYSV_ABI sceUsbdTryLockEvents() {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
return libusb_try_lock_events(g_libusb_context);
|
||||
return usb_backend->TryLockEvents();
|
||||
}
|
||||
|
||||
void PS4_SYSV_ABI sceUsbdLockEvents() {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
libusb_lock_events(g_libusb_context);
|
||||
usb_backend->LockEvents();
|
||||
}
|
||||
|
||||
void PS4_SYSV_ABI sceUsbdUnlockEvents() {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
libusb_unlock_events(g_libusb_context);
|
||||
usb_backend->UnlockEvents();
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUsbdEventHandlingOk() {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
return libusb_event_handling_ok(g_libusb_context);
|
||||
return usb_backend->EventHandlingOk();
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUsbdEventHandlerActive() {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
return libusb_event_handler_active(g_libusb_context);
|
||||
return usb_backend->EventHandlerActive();
|
||||
}
|
||||
|
||||
void PS4_SYSV_ABI sceUsbdLockEventWaiters() {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
libusb_lock_event_waiters(g_libusb_context);
|
||||
usb_backend->LockEventWaiters();
|
||||
}
|
||||
|
||||
void PS4_SYSV_ABI sceUsbdUnlockEventWaiters() {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
libusb_unlock_event_waiters(g_libusb_context);
|
||||
usb_backend->UnlockEventWaiters();
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUsbdWaitForEvent(timeval* tv) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
return libusb_to_orbis_error(libusb_wait_for_event(g_libusb_context, tv));
|
||||
return libusb_to_orbis_error(usb_backend->WaitForEvent(tv));
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUsbdHandleEventsTimeout(timeval* tv) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
return libusb_to_orbis_error(libusb_handle_events_timeout(g_libusb_context, tv));
|
||||
return libusb_to_orbis_error(usb_backend->HandleEventsTimeout(tv));
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUsbdHandleEvents() {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
return libusb_to_orbis_error(libusb_handle_events(g_libusb_context));
|
||||
return libusb_to_orbis_error(usb_backend->HandleEvents());
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUsbdHandleEventsLocked(timeval* tv) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
return libusb_to_orbis_error(libusb_handle_events_locked(g_libusb_context, tv));
|
||||
return libusb_to_orbis_error(usb_backend->HandleEventsTimeout(tv));
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUsbdCheckConnected(SceUsbdDeviceHandle* dev_handle) {
|
||||
LOG_DEBUG(Lib_Usbd, "called");
|
||||
|
||||
// There's no libusb version of this function.
|
||||
// Simulate by querying data.
|
||||
|
||||
int config;
|
||||
int r = libusb_get_configuration(dev_handle, &config);
|
||||
|
||||
return libusb_to_orbis_error(r);
|
||||
return libusb_to_orbis_error(usb_backend->CheckConnected(dev_handle));
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI Func_65F6EF33E38FFF50() {
|
||||
@ -479,6 +457,21 @@ int PS4_SYSV_ABI Func_D56B43060720B1E0() {
|
||||
}
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
switch (Config::getUsbDeviceBackend()) {
|
||||
case Config::SkylandersPortal:
|
||||
usb_backend = std::make_shared<SkylandersPortalBackend>();
|
||||
break;
|
||||
case Config::InfinityBase:
|
||||
usb_backend = std::make_shared<InfinityBaseBackend>();
|
||||
break;
|
||||
case Config::DimensionsToypad:
|
||||
usb_backend = std::make_shared<DimensionsToypadBackend>();
|
||||
break;
|
||||
default:
|
||||
usb_backend = std::make_shared<UsbRealBackend>();
|
||||
break;
|
||||
}
|
||||
|
||||
LIB_FUNCTION("0ktE1PhzGFU", "libSceUsbd", 1, "libSceUsbd", sceUsbdAllocTransfer);
|
||||
LIB_FUNCTION("BKMEGvfCPyU", "libSceUsbd", 1, "libSceUsbd", sceUsbdAttachKernelDriver);
|
||||
LIB_FUNCTION("fotb7DzeHYw", "libSceUsbd", 1, "libSceUsbd", sceUsbdBulkTransfer);
|
||||
|
||||
@ -4,6 +4,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
#include "emulated/dimensions.h"
|
||||
#include "emulated/infinity.h"
|
||||
#include "emulated/skylander.h"
|
||||
#include "usb_backend.h"
|
||||
|
||||
extern "C" {
|
||||
struct libusb_device;
|
||||
@ -21,6 +25,8 @@ class SymbolsResolver;
|
||||
|
||||
namespace Libraries::Usbd {
|
||||
|
||||
extern std::shared_ptr<UsbBackend> usb_backend;
|
||||
|
||||
using SceUsbdDevice = libusb_device;
|
||||
using SceUsbdDeviceHandle = libusb_device_handle;
|
||||
using SceUsbdDeviceDescriptor = libusb_device_descriptor;
|
||||
@ -29,6 +35,10 @@ using SceUsbdTransfer = libusb_transfer;
|
||||
using SceUsbdControlSetup = libusb_control_setup;
|
||||
using SceUsbdTransferCallback = void PS4_SYSV_ABI (*)(SceUsbdTransfer* transfer);
|
||||
|
||||
using SkylandersPortalBackend = SkylanderBackend;
|
||||
using InfinityBaseBackend = InfinityBackend;
|
||||
using DimensionsToypadBackend = DimensionsBackend;
|
||||
|
||||
enum class SceUsbdSpeed : u32 {
|
||||
UNKNOWN = 0,
|
||||
LOW = 1,
|
||||
|
||||
@ -104,17 +104,6 @@ void Linker::Execute(const std::vector<std::string>& args) {
|
||||
|
||||
memory->SetupMemoryRegions(fmem_size, use_extended_mem1, use_extended_mem2);
|
||||
|
||||
// Simulate sceKernelInternalMemory mapping, a mapping usually performed during libkernel init.
|
||||
// Due to the large size of this mapping, failing to emulate it causes issues in some titles.
|
||||
// This mapping belongs in the system reserved area, which starts at address 0x880000000.
|
||||
static constexpr VAddr KernelAllocBase = 0x880000000ULL;
|
||||
static constexpr s64 InternalMemorySize = 0x1000000;
|
||||
void* addr_out{reinterpret_cast<void*>(KernelAllocBase)};
|
||||
|
||||
s32 ret = Libraries::Kernel::sceKernelMapNamedFlexibleMemory(&addr_out, InternalMemorySize, 3,
|
||||
0, "SceKernelInternalMemory");
|
||||
ASSERT_MSG(ret == 0, "Unable to perform sceKernelInternalMemory mapping");
|
||||
|
||||
main_thread.Run([this, module, &args](std::stop_token) {
|
||||
Common::SetCurrentThreadName("GAME_MainThread");
|
||||
if (auto& ipc = IPC::Instance()) {
|
||||
|
||||
@ -502,19 +502,19 @@ bool Elf::IsSharedLib() {
|
||||
}
|
||||
|
||||
void Elf::ElfHeaderDebugDump(const std::filesystem::path& file_name) {
|
||||
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Write,
|
||||
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Create,
|
||||
Common::FS::FileType::TextFile};
|
||||
f.WriteString(ElfHeaderStr());
|
||||
}
|
||||
|
||||
void Elf::SelfHeaderDebugDump(const std::filesystem::path& file_name) {
|
||||
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Write,
|
||||
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Create,
|
||||
Common::FS::FileType::TextFile};
|
||||
f.WriteString(SElfHeaderStr());
|
||||
}
|
||||
|
||||
void Elf::SelfSegHeaderDebugDump(const std::filesystem::path& file_name) {
|
||||
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Write,
|
||||
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Create,
|
||||
Common::FS::FileType::TextFile};
|
||||
for (u16 i = 0; i < m_self.segment_count; i++) {
|
||||
f.WriteString(SELFSegHeader(i));
|
||||
@ -522,7 +522,7 @@ void Elf::SelfSegHeaderDebugDump(const std::filesystem::path& file_name) {
|
||||
}
|
||||
|
||||
void Elf::PHeaderDebugDump(const std::filesystem::path& file_name) {
|
||||
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Write,
|
||||
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Create,
|
||||
Common::FS::FileType::TextFile};
|
||||
if (m_elf_header.e_phentsize > 0) {
|
||||
for (u16 i = 0; i < m_elf_header.e_phnum; i++) {
|
||||
|
||||
@ -32,7 +32,7 @@ const SymbolRecord* SymbolsResolver::FindSymbol(const SymbolResolver& s) const {
|
||||
}
|
||||
|
||||
void SymbolsResolver::DebugDump(const std::filesystem::path& file_name) {
|
||||
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Write,
|
||||
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Create,
|
||||
Common::FS::FileType::TextFile};
|
||||
for (const auto& symbol : m_symbols) {
|
||||
const auto ids = Common::SplitString(symbol.name, '#');
|
||||
|
||||
@ -587,6 +587,10 @@ s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, Memory
|
||||
// On real hardware, GPU file mmaps cause a full system crash due to an internal error.
|
||||
ASSERT_MSG(false, "Files cannot be mapped to GPU memory");
|
||||
}
|
||||
if (True(prot & MemoryProt::CpuExec)) {
|
||||
// On real hardware, execute permissions are silently removed.
|
||||
prot &= ~MemoryProt::CpuExec;
|
||||
}
|
||||
|
||||
// Add virtual memory area
|
||||
auto& new_vma = CarveVMA(mapped_addr, size)->second;
|
||||
@ -793,10 +797,9 @@ s32 MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* pr
|
||||
s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea& vma_base, u64 size,
|
||||
MemoryProt prot) {
|
||||
const auto start_in_vma = addr - vma_base.base;
|
||||
const auto adjusted_size =
|
||||
vma_base.size - start_in_vma < size ? vma_base.size - start_in_vma : size;
|
||||
const auto adjusted_size = std::min<u64>(vma_base.size - start_in_vma, size);
|
||||
|
||||
if (vma_base.type == VMAType::Free) {
|
||||
if (vma_base.type == VMAType::Free || vma_base.type == VMAType::PoolReserved) {
|
||||
// On PS4, protecting freed memory does nothing.
|
||||
return adjusted_size;
|
||||
}
|
||||
@ -828,8 +831,9 @@ s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea& vma_base, u64 siz
|
||||
perms |= Core::MemoryPermission::ReadWrite;
|
||||
}
|
||||
|
||||
if (vma_base.type == VMAType::Direct || vma_base.type == VMAType::Pooled) {
|
||||
// On PS4, execute permissions are hidden from direct memory mappings.
|
||||
if (vma_base.type == VMAType::Direct || vma_base.type == VMAType::Pooled ||
|
||||
vma_base.type == VMAType::File) {
|
||||
// On PS4, execute permissions are hidden from direct memory and file mappings.
|
||||
// Tests show that execute permissions still apply, so handle this after reading perms.
|
||||
prot &= ~MemoryProt::CpuExec;
|
||||
}
|
||||
@ -837,6 +841,12 @@ s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea& vma_base, u64 siz
|
||||
// Change protection
|
||||
vma_base.prot = prot;
|
||||
|
||||
if (vma_base.type == VMAType::Reserved) {
|
||||
// On PS4, protections change vma_map, but don't apply.
|
||||
// Return early to avoid protecting memory that isn't mapped in address space.
|
||||
return adjusted_size;
|
||||
}
|
||||
|
||||
impl.Protect(addr, size, perms);
|
||||
|
||||
return adjusted_size;
|
||||
@ -853,22 +863,20 @@ s32 MemoryManager::Protect(VAddr addr, u64 size, MemoryProt prot) {
|
||||
// Ensure the range to modify is valid
|
||||
ASSERT_MSG(IsValidMapping(addr, size), "Attempted to access invalid address {:#x}", addr);
|
||||
|
||||
// Validate protection flags
|
||||
constexpr static MemoryProt valid_flags =
|
||||
MemoryProt::NoAccess | MemoryProt::CpuRead | MemoryProt::CpuWrite | MemoryProt::CpuExec |
|
||||
MemoryProt::GpuRead | MemoryProt::GpuWrite | MemoryProt::GpuReadWrite;
|
||||
|
||||
MemoryProt invalid_flags = prot & ~valid_flags;
|
||||
if (invalid_flags != MemoryProt::NoAccess) {
|
||||
LOG_ERROR(Kernel_Vmm, "Invalid protection flags");
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
// Appropriately restrict flags.
|
||||
constexpr static MemoryProt flag_mask =
|
||||
MemoryProt::CpuReadWrite | MemoryProt::CpuExec | MemoryProt::GpuReadWrite;
|
||||
MemoryProt valid_flags = prot & flag_mask;
|
||||
|
||||
// Protect all VMAs between addr and addr + size.
|
||||
s64 protected_bytes = 0;
|
||||
while (protected_bytes < size) {
|
||||
auto it = FindVMA(addr + protected_bytes);
|
||||
auto& vma_base = it->second;
|
||||
if (vma_base.base > addr + protected_bytes) {
|
||||
// Account for potential gaps in memory map.
|
||||
protected_bytes += vma_base.base - (addr + protected_bytes);
|
||||
}
|
||||
auto result = ProtectBytes(addr + protected_bytes, vma_base, size - protected_bytes, prot);
|
||||
if (result < 0) {
|
||||
// ProtectBytes returned an error, return it
|
||||
@ -904,13 +912,21 @@ s32 MemoryManager::VirtualQuery(VAddr addr, s32 flags,
|
||||
const auto& vma = it->second;
|
||||
info->start = vma.base;
|
||||
info->end = vma.base + vma.size;
|
||||
info->offset = vma.type == VMAType::Flexible ? 0 : vma.phys_base;
|
||||
info->offset = 0;
|
||||
info->protection = static_cast<s32>(vma.prot);
|
||||
info->is_flexible = vma.type == VMAType::Flexible ? 1 : 0;
|
||||
info->is_direct = vma.type == VMAType::Direct ? 1 : 0;
|
||||
info->is_stack = vma.type == VMAType::Stack ? 1 : 0;
|
||||
info->is_pooled = vma.type == VMAType::PoolReserved || vma.type == VMAType::Pooled ? 1 : 0;
|
||||
info->is_committed = vma.IsMapped() ? 1 : 0;
|
||||
if (vma.type == VMAType::Direct || vma.type == VMAType::Pooled) {
|
||||
// Offset is only assigned for direct and pooled mappings.
|
||||
info->offset = vma.phys_base;
|
||||
}
|
||||
if (vma.type == VMAType::Reserved || vma.type == VMAType::PoolReserved) {
|
||||
// Protection is hidden from reserved mappings.
|
||||
info->protection = 0;
|
||||
}
|
||||
|
||||
strncpy(info->name, vma.name.data(), ::Libraries::Kernel::ORBIS_KERNEL_MAXIMUM_NAME_LENGTH);
|
||||
|
||||
|
||||
@ -45,7 +45,10 @@ enum class MemoryMapFlags : u32 {
|
||||
Private = 2,
|
||||
Fixed = 0x10,
|
||||
NoOverwrite = 0x80,
|
||||
Void = 0x100,
|
||||
Stack = 0x400,
|
||||
NoSync = 0x800,
|
||||
Anon = 0x1000,
|
||||
NoCore = 0x20000,
|
||||
NoCoalesce = 0x400000,
|
||||
};
|
||||
|
||||
@ -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 <mutex>
|
||||
@ -165,10 +165,7 @@ void SetTcbBase(void* image_address) {
|
||||
}
|
||||
|
||||
Tcb* GetTcbBase() {
|
||||
void* tcb = nullptr;
|
||||
const int ret = syscall(SYS_arch_prctl, ARCH_GET_GS, &tcb);
|
||||
ASSERT_MSG(ret == 0, "Failed to get GS base: errno {}", errno);
|
||||
return static_cast<Tcb*>(tcb);
|
||||
return Libraries::Kernel::g_curthread->tcb;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include "common/types.h"
|
||||
#ifdef _WIN32
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
namespace Xbyak {
|
||||
class CodeGenerator;
|
||||
|
||||
@ -15,10 +15,6 @@
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/ipc/ipc.h"
|
||||
#ifdef ENABLE_QT_GUI
|
||||
#include <QtCore>
|
||||
#endif
|
||||
#include "common/assert.h"
|
||||
#ifdef ENABLE_DISCORD_RPC
|
||||
#include "common/discord_rpc_handler.h"
|
||||
#endif
|
||||
@ -35,6 +31,8 @@
|
||||
#include "core/file_format/trp.h"
|
||||
#include "core/file_sys/fs.h"
|
||||
#include "core/libraries/disc_map/disc_map.h"
|
||||
#include "core/libraries/font/font.h"
|
||||
#include "core/libraries/font/fontft.h"
|
||||
#include "core/libraries/libc_internal/libc_internal.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/ngs2/ngs2.h"
|
||||
@ -44,6 +42,7 @@
|
||||
#include "core/linker.h"
|
||||
#include "core/memory.h"
|
||||
#include "emulator.h"
|
||||
#include "video_core/cache_storage.h"
|
||||
#include "video_core/renderdoc.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
@ -60,10 +59,11 @@ Frontend::WindowSDL* g_window = nullptr;
|
||||
namespace Core {
|
||||
|
||||
Emulator::Emulator() {
|
||||
// Initialize NT API functions and set high priority
|
||||
// Initialize NT API functions, set high priority and disable WER
|
||||
#ifdef _WIN32
|
||||
Common::NtApi::Initialize();
|
||||
SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS);
|
||||
SetErrorMode(SetErrorMode(0) | SEM_NOGPFAULTERRORBOX);
|
||||
// need to init this in order for winsock2 to work
|
||||
WORD versionWanted = MAKEWORD(2, 2);
|
||||
WSADATA wsaData;
|
||||
@ -115,6 +115,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
||||
std::string id;
|
||||
std::string title;
|
||||
std::string app_version;
|
||||
u32 sdk_version;
|
||||
u32 fw_version;
|
||||
Common::PSFAttributes psf_attributes{};
|
||||
if (param_sfo_exists) {
|
||||
@ -134,8 +135,48 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
||||
if (const auto raw_attributes = param_sfo->GetInteger("ATTRIBUTE")) {
|
||||
psf_attributes.raw = *raw_attributes;
|
||||
}
|
||||
|
||||
// Extract sdk version from pubtool info.
|
||||
std::string_view pubtool_info =
|
||||
param_sfo->GetString("PUBTOOLINFO").value_or("Unknown value");
|
||||
u64 sdk_ver_offset = pubtool_info.find("sdk_ver");
|
||||
|
||||
if (sdk_ver_offset == pubtool_info.npos) {
|
||||
// Default to using firmware version if SDK version is not found.
|
||||
sdk_version = fw_version;
|
||||
} else {
|
||||
// Increment offset to account for sdk_ver= part of string.
|
||||
sdk_ver_offset += 8;
|
||||
u64 sdk_ver_len = pubtool_info.find(",", sdk_ver_offset);
|
||||
if (sdk_ver_len == pubtool_info.npos) {
|
||||
// If there's no more commas, this is likely the last entry of pubtool info.
|
||||
// Use string length instead.
|
||||
sdk_ver_len = pubtool_info.size();
|
||||
}
|
||||
sdk_ver_len -= sdk_ver_offset;
|
||||
std::string sdk_ver_string = pubtool_info.substr(sdk_ver_offset, sdk_ver_len).data();
|
||||
// Number is stored in base 16.
|
||||
sdk_version = std::stoi(sdk_ver_string, nullptr, 16);
|
||||
}
|
||||
}
|
||||
|
||||
auto& game_info = Common::ElfInfo::Instance();
|
||||
game_info.initialized = true;
|
||||
game_info.game_serial = id;
|
||||
game_info.title = title;
|
||||
game_info.app_ver = app_version;
|
||||
game_info.firmware_ver = fw_version & 0xFFF00000;
|
||||
game_info.raw_firmware_ver = fw_version;
|
||||
game_info.sdk_ver = sdk_version;
|
||||
game_info.psf_attributes = psf_attributes;
|
||||
|
||||
const auto pic1_path = mnt->GetHostPath("/app0/sce_sys/pic1.png");
|
||||
if (std::filesystem::exists(pic1_path)) {
|
||||
game_info.splash_path = pic1_path;
|
||||
}
|
||||
|
||||
game_info.game_folder = game_folder;
|
||||
|
||||
Config::load(Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (id + ".toml"),
|
||||
true);
|
||||
|
||||
@ -198,6 +239,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
||||
if (param_sfo_exists) {
|
||||
LOG_INFO(Loader, "Game id: {} Title: {}", id, title);
|
||||
LOG_INFO(Loader, "Fw: {:#x} App Version: {}", fw_version, app_version);
|
||||
LOG_INFO(Loader, "Compiled SDK version: {:#x}", sdk_version);
|
||||
LOG_INFO(Loader, "PSVR Supported: {}", (bool)psf_attributes.support_ps_vr.Value());
|
||||
LOG_INFO(Loader, "PSVR Required: {}", (bool)psf_attributes.require_ps_vr.Value());
|
||||
}
|
||||
@ -237,22 +279,6 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
||||
}
|
||||
}
|
||||
|
||||
auto& game_info = Common::ElfInfo::Instance();
|
||||
game_info.initialized = true;
|
||||
game_info.game_serial = id;
|
||||
game_info.title = title;
|
||||
game_info.app_ver = app_version;
|
||||
game_info.firmware_ver = fw_version & 0xFFF00000;
|
||||
game_info.raw_firmware_ver = fw_version;
|
||||
game_info.psf_attributes = psf_attributes;
|
||||
|
||||
const auto pic1_path = mnt->GetHostPath("/app0/sce_sys/pic1.png");
|
||||
if (std::filesystem::exists(pic1_path)) {
|
||||
game_info.splash_path = pic1_path;
|
||||
}
|
||||
|
||||
game_info.game_folder = game_folder;
|
||||
|
||||
std::string game_title = fmt::format("{} - {} <{}>", id, title, app_version);
|
||||
std::string window_title = "";
|
||||
std::string remote_url(Common::g_scm_remote_url);
|
||||
@ -341,8 +367,6 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
||||
}
|
||||
#endif
|
||||
|
||||
// Start the timer (Play Time)
|
||||
#ifdef ENABLE_QT_GUI
|
||||
if (!id.empty()) {
|
||||
start_time = std::chrono::steady_clock::now();
|
||||
|
||||
@ -354,7 +378,6 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
#endif
|
||||
|
||||
args.insert(args.begin(), eboot_name.generic_string());
|
||||
linker->Execute(args);
|
||||
@ -364,9 +387,8 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
||||
window->WaitEvent();
|
||||
}
|
||||
|
||||
#ifdef ENABLE_QT_GUI
|
||||
UpdatePlayTime(id);
|
||||
#endif
|
||||
Storage::DataBase::Instance().Close();
|
||||
|
||||
std::quick_exit(0);
|
||||
}
|
||||
@ -389,9 +411,9 @@ void Emulator::Restart(std::filesystem::path eboot_path,
|
||||
args.push_back("--ignore-game-patch");
|
||||
}
|
||||
|
||||
if (!MemoryPatcher::patchFile.empty()) {
|
||||
if (!MemoryPatcher::patch_file.empty()) {
|
||||
args.push_back("--patch");
|
||||
args.push_back(MemoryPatcher::patchFile);
|
||||
args.push_back(MemoryPatcher::patch_file);
|
||||
}
|
||||
|
||||
args.push_back("--wait-for-pid");
|
||||
@ -482,8 +504,8 @@ void Emulator::LoadSystemModules(const std::string& game_serial) {
|
||||
{"libSceJson2.sprx", nullptr},
|
||||
{"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterLib},
|
||||
{"libSceCesCs.sprx", nullptr},
|
||||
{"libSceFont.sprx", nullptr},
|
||||
{"libSceFontFt.sprx", nullptr},
|
||||
{"libSceFont.sprx", &Libraries::Font::RegisterlibSceFont},
|
||||
{"libSceFontFt.sprx", &Libraries::FontFt::RegisterlibSceFontFt},
|
||||
{"libSceFreeTypeOt.sprx", nullptr}});
|
||||
|
||||
std::vector<std::filesystem::path> found_modules;
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <imgui.h>
|
||||
|
||||
@ -12,6 +14,7 @@
|
||||
|
||||
#define IMGUI_FONT_TEXT 0
|
||||
#define IMGUI_FONT_MONO 1
|
||||
#define IMGUI_FONT_TEXT_BIG 2
|
||||
|
||||
namespace ImGui {
|
||||
|
||||
|
||||
@ -76,10 +76,15 @@ void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& w
|
||||
font_cfg.OversampleV = 1;
|
||||
io.FontDefault = io.Fonts->AddFontFromMemoryCompressedTTF(
|
||||
imgui_font_notosansjp_regular_compressed_data,
|
||||
imgui_font_notosansjp_regular_compressed_size, 16.0f, &font_cfg, ranges.Data);
|
||||
imgui_font_notosansjp_regular_compressed_size, 32.0f, &font_cfg, ranges.Data);
|
||||
io.Fonts->AddFontFromMemoryCompressedTTF(imgui_font_proggyvector_regular_compressed_data,
|
||||
imgui_font_proggyvector_regular_compressed_size,
|
||||
16.0f);
|
||||
32.0f);
|
||||
io.Fonts->AddFontFromMemoryCompressedTTF(imgui_font_notosansjp_regular_compressed_data,
|
||||
imgui_font_notosansjp_regular_compressed_size, 128.0f,
|
||||
&font_cfg, ranges.Data);
|
||||
|
||||
io.FontGlobalScale = 0.5f;
|
||||
|
||||
StyleColorsDark();
|
||||
|
||||
@ -110,7 +115,7 @@ void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& w
|
||||
dock_id = ImHashStr(label);
|
||||
|
||||
if (const auto dpi = SDL_GetWindowDisplayScale(window.GetSDLWindow()); dpi > 0.0f) {
|
||||
GetIO().FontGlobalScale = dpi;
|
||||
GetIO().FontGlobalScale *= dpi;
|
||||
}
|
||||
|
||||
std::at_quick_exit([] { SaveIniSettingsToDisk(GetIO().IniFilename); });
|
||||
@ -245,7 +250,7 @@ void Render(const vk::CommandBuffer& cmdbuf, const vk::ImageView& image_view,
|
||||
}
|
||||
|
||||
bool MustKeepDrawing() {
|
||||
return layers.size() > 1 || DebugState.IsShowingDebugMenuBar();
|
||||
return layers.size() > 1 || change_layers.size() > 1 || DebugState.IsShowingDebugMenuBar();
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@ -114,69 +114,6 @@ State GameController::GetLastState() const {
|
||||
}
|
||||
|
||||
void GameController::AddState(const State& state) {
|
||||
// Don't add duplicate states - compare input values (ignoring timestamp)
|
||||
if (m_states_num > 0) {
|
||||
const State& last = GetLastState();
|
||||
|
||||
// Compare button states
|
||||
bool buttons_match = (last.buttonsState == state.buttonsState);
|
||||
|
||||
// Compare axes with a LARGE threshold to maintain continuous state during spinning
|
||||
// Use ±64 (~25% of 0-255 range) - only create new states for significant direction changes
|
||||
// This treats analog movement as continuous rather than discrete state changes
|
||||
bool axes_match = true;
|
||||
for (int i = 0; i < static_cast<int>(Axis::AxisMax); i++) {
|
||||
int diff = std::abs(last.axes[i] - state.axes[i]);
|
||||
if (diff > 64) {
|
||||
axes_match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Compare touchpad states
|
||||
bool touchpad_match = true;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (last.touchpad[i].state != state.touchpad[i].state ||
|
||||
last.touchpad[i].x != state.touchpad[i].x ||
|
||||
last.touchpad[i].y != state.touchpad[i].y) {
|
||||
touchpad_match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip motion sensor comparison for duplicate detection
|
||||
// Motion sensors (gyro/accelerometer) constantly fluctuate due to sensor noise
|
||||
// and shouldn't affect whether we consider a state as "duplicate" for gameplay purposes
|
||||
|
||||
// If all gameplay-relevant input values are similar enough, just update the values
|
||||
// in the most recent buffered state to track gradual movement without creating new entries
|
||||
if (buttons_match && axes_match && touchpad_match) {
|
||||
m_last_state.time = state.time;
|
||||
// Update the axes to track continuous movement
|
||||
for (int i = 0; i < static_cast<int>(Axis::AxisMax); i++) {
|
||||
m_last_state.axes[i] = state.axes[i];
|
||||
}
|
||||
// Update motion sensor data
|
||||
m_last_state.acceleration = state.acceleration;
|
||||
m_last_state.angularVelocity = state.angularVelocity;
|
||||
m_last_state.orientation = state.orientation;
|
||||
|
||||
// IMPORTANT: Also update the last buffered state (if any) with current axis values
|
||||
// This ensures the game reads the current stick position, not old values
|
||||
if (m_states_num > 0) {
|
||||
const u32 last_index = (m_first_state + m_states_num - 1) % MAX_STATES;
|
||||
m_states[last_index].time = state.time;
|
||||
for (int i = 0; i < static_cast<int>(Axis::AxisMax); i++) {
|
||||
m_states[last_index].axes[i] = state.axes[i];
|
||||
}
|
||||
m_states[last_index].acceleration = state.acceleration;
|
||||
m_states[last_index].angularVelocity = state.angularVelocity;
|
||||
m_states[last_index].orientation = state.orientation;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_states_num >= MAX_STATES) {
|
||||
m_states_num = MAX_STATES - 1;
|
||||
m_first_state = (m_first_state + 1) % MAX_STATES;
|
||||
|
||||
@ -106,6 +106,7 @@ auto output_array = std::array{
|
||||
ControllerOutput(HOTKEY_RELOAD_INPUTS),
|
||||
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK),
|
||||
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_GYRO),
|
||||
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD),
|
||||
ControllerOutput(HOTKEY_RENDERDOC),
|
||||
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID),
|
||||
@ -579,6 +580,9 @@ void ControllerOutput::FinalizeUpdate() {
|
||||
case HOTKEY_TOGGLE_MOUSE_TO_GYRO:
|
||||
PushSDLEvent(SDL_EVENT_MOUSE_TO_GYRO);
|
||||
break;
|
||||
case HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD:
|
||||
PushSDLEvent(SDL_EVENT_MOUSE_TO_TOUCHPAD);
|
||||
break;
|
||||
case HOTKEY_RENDERDOC:
|
||||
PushSDLEvent(SDL_EVENT_RDOC_CAPTURE);
|
||||
break;
|
||||
@ -773,6 +777,9 @@ void ActivateOutputsFromInputs() {
|
||||
it.ResetUpdate();
|
||||
}
|
||||
|
||||
// Check for input blockers
|
||||
ApplyMouseInputBlockers();
|
||||
|
||||
// Iterate over all inputs, and update their respecive outputs accordingly
|
||||
for (auto& it : connections) {
|
||||
it.output->AddUpdate(it.ProcessBinding());
|
||||
|
||||
@ -34,9 +34,10 @@
|
||||
#define SDL_EVENT_RELOAD_INPUTS SDL_EVENT_USER + 5
|
||||
#define SDL_EVENT_MOUSE_TO_JOYSTICK SDL_EVENT_USER + 6
|
||||
#define SDL_EVENT_MOUSE_TO_GYRO SDL_EVENT_USER + 7
|
||||
#define SDL_EVENT_RDOC_CAPTURE SDL_EVENT_USER + 8
|
||||
#define SDL_EVENT_QUIT_DIALOG SDL_EVENT_USER + 9
|
||||
#define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 10
|
||||
#define SDL_EVENT_MOUSE_TO_TOUCHPAD SDL_EVENT_USER + 8
|
||||
#define SDL_EVENT_RDOC_CAPTURE SDL_EVENT_USER + 9
|
||||
#define SDL_EVENT_QUIT_DIALOG SDL_EVENT_USER + 10
|
||||
#define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 11
|
||||
|
||||
#define LEFTJOYSTICK_HALFMODE 0x00010000
|
||||
#define RIGHTJOYSTICK_HALFMODE 0x00020000
|
||||
@ -52,7 +53,8 @@
|
||||
#define HOTKEY_RELOAD_INPUTS 0xf0000005
|
||||
#define HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK 0xf0000006
|
||||
#define HOTKEY_TOGGLE_MOUSE_TO_GYRO 0xf0000007
|
||||
#define HOTKEY_RENDERDOC 0xf0000008
|
||||
#define HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD 0xf0000008
|
||||
#define HOTKEY_RENDERDOC 0xf0000009
|
||||
|
||||
#define SDL_UNMAPPED UINT32_MAX - 1
|
||||
|
||||
@ -141,6 +143,7 @@ const std::map<std::string, u32> string_to_cbutton_map = {
|
||||
{"hotkey_reload_inputs", HOTKEY_RELOAD_INPUTS},
|
||||
{"hotkey_toggle_mouse_to_joystick", HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK},
|
||||
{"hotkey_toggle_mouse_to_gyro", HOTKEY_TOGGLE_MOUSE_TO_GYRO},
|
||||
{"hotkey_toggle_mouse_to_touchpad", HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD},
|
||||
{"hotkey_renderdoc_capture", HOTKEY_RENDERDOC},
|
||||
};
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user