Merge branch 'main' into trophy3
1
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
ko_fi: shadps4
|
481
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,481 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
name: Build and Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "*" ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
BUILD_TYPE: Release
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
reuse:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
continue-on-error: true
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: fsfe/reuse-action@v4
|
||||||
|
|
||||||
|
clang-format:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
continue-on-error: true
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Install
|
||||||
|
run: |
|
||||||
|
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
||||||
|
sudo add-apt-repository 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main'
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install clang-format-17
|
||||||
|
- name: Build
|
||||||
|
env:
|
||||||
|
COMMIT_RANGE: ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}
|
||||||
|
run: ./.ci/clang-format.sh
|
||||||
|
|
||||||
|
get-info:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
date: ${{ steps.vars.outputs.date }}
|
||||||
|
shorthash: ${{ steps.vars.outputs.shorthash }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Get date and git hash
|
||||||
|
id: vars
|
||||||
|
run: |
|
||||||
|
echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_ENV
|
||||||
|
echo "shorthash=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||||
|
echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||||
|
echo "shorthash=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
windows-sdl:
|
||||||
|
runs-on: windows-latest
|
||||||
|
needs: get-info
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Cache CMake Configuration
|
||||||
|
uses: actions/cache@v4
|
||||||
|
env:
|
||||||
|
cache-name: ${{ runner.os }}-sdl-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.14
|
||||||
|
env:
|
||||||
|
cache-name: ${{ runner.os }}-sdl-cache-cmake-build
|
||||||
|
with:
|
||||||
|
append-timestamp: false
|
||||||
|
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
|
||||||
|
|
||||||
|
- name: Setup VS Environment
|
||||||
|
uses: ilammy/msvc-dev-cmd@v1.13.0
|
||||||
|
with:
|
||||||
|
arch: amd64
|
||||||
|
|
||||||
|
- name: Configure CMake
|
||||||
|
run: cmake --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -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
|
||||||
|
|
||||||
|
- name: Upload Windows SDL artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
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-latest
|
||||||
|
needs: get-info
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Setup Qt
|
||||||
|
uses: jurplel/install-qt-action@v4
|
||||||
|
with:
|
||||||
|
version: 6.7.3
|
||||||
|
host: windows
|
||||||
|
target: desktop
|
||||||
|
arch: win64_msvc2019_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.14
|
||||||
|
env:
|
||||||
|
cache-name: ${{ runner.os }}-qt-cache-cmake-build
|
||||||
|
with:
|
||||||
|
append-timestamp: false
|
||||||
|
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
|
||||||
|
|
||||||
|
- name: Setup VS Environment
|
||||||
|
uses: ilammy/msvc-dev-cmd@v1.13.0
|
||||||
|
with:
|
||||||
|
arch: amd64
|
||||||
|
|
||||||
|
- name: Configure CMake
|
||||||
|
run: cmake --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_QT_GUI=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
|
||||||
|
|
||||||
|
- name: Deploy and Package
|
||||||
|
run: |
|
||||||
|
mkdir upload
|
||||||
|
move build/shadPS4.exe upload
|
||||||
|
windeployqt --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-latest
|
||||||
|
needs: get-info
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Setup latest Xcode
|
||||||
|
uses: maxim-lobanov/setup-xcode@v1
|
||||||
|
with:
|
||||||
|
xcode-version: latest
|
||||||
|
|
||||||
|
- name: Install MoltenVK
|
||||||
|
run: |
|
||||||
|
arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||||
|
arch -x86_64 /usr/local/bin/brew install molten-vk
|
||||||
|
|
||||||
|
- name: Cache CMake Configuration
|
||||||
|
uses: actions/cache@v4
|
||||||
|
env:
|
||||||
|
cache-name: ${{ runner.os }}-sdl-cache-cmake-configuration
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
${{github.workspace}}/build
|
||||||
|
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ env.cache-name }}-
|
||||||
|
|
||||||
|
- name: Cache CMake Build
|
||||||
|
uses: hendrikmuhs/ccache-action@v1.2.14
|
||||||
|
env:
|
||||||
|
cache-name: ${{runner.os}}-sdl-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 -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 SDL artifact
|
||||||
|
run: |
|
||||||
|
mkdir upload
|
||||||
|
mv ${{github.workspace}}/build/shadps4 upload
|
||||||
|
cp $(arch -x86_64 /usr/local/bin/brew --prefix)/opt/molten-vk/lib/libMoltenVK.dylib upload
|
||||||
|
tar cf shadps4-macos-sdl.tar.gz -C upload .
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: shadps4-macos-sdl-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
|
||||||
|
path: shadps4-macos-sdl.tar.gz
|
||||||
|
|
||||||
|
macos-qt:
|
||||||
|
runs-on: macos-latest
|
||||||
|
needs: get-info
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Setup latest Xcode
|
||||||
|
uses: maxim-lobanov/setup-xcode@v1
|
||||||
|
with:
|
||||||
|
xcode-version: latest
|
||||||
|
|
||||||
|
- name: Install MoltenVK and Setup Qt
|
||||||
|
run: |
|
||||||
|
arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||||
|
arch -x86_64 /usr/local/bin/brew install molten-vk
|
||||||
|
- uses: jurplel/install-qt-action@v4
|
||||||
|
with:
|
||||||
|
version: 6.7.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.14
|
||||||
|
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 -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
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential
|
||||||
|
|
||||||
|
- name: Cache CMake Configuration
|
||||||
|
uses: actions/cache@v4
|
||||||
|
env:
|
||||||
|
cache-name: ${{ runner.os }}-sdl-cache-cmake-configuration
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
${{github.workspace}}/build
|
||||||
|
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ env.cache-name }}-
|
||||||
|
|
||||||
|
- name: Cache CMake Build
|
||||||
|
uses: hendrikmuhs/ccache-action@v1.2.14
|
||||||
|
env:
|
||||||
|
cache-name: ${{ runner.os }}-sdl-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_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel
|
||||||
|
|
||||||
|
- name: Package and Upload Linux(ubuntu64) SDL artifact
|
||||||
|
run: |
|
||||||
|
ls -la ${{ github.workspace }}/build/shadps4
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: shadps4-ubuntu64-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
|
||||||
|
path: ${{ github.workspace }}/build/shadps4
|
||||||
|
|
||||||
|
- name: Run AppImage packaging script
|
||||||
|
run: ./.github/linux-appimage-sdl.sh
|
||||||
|
|
||||||
|
- name: Package and Upload Linux SDL artifact
|
||||||
|
run: |
|
||||||
|
tar cf shadps4-linux-sdl.tar.gz -C ${{github.workspace}}/build shadps4
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
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@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-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.14
|
||||||
|
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_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
pre-release:
|
||||||
|
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||||
|
needs: [get-info, windows-sdl, windows-qt, macos-sdl, macos-qt, linux-sdl, linux-qt]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Download all artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: ./artifacts
|
||||||
|
|
||||||
|
- name: Compress individual directories (without parent directory)
|
||||||
|
run: |
|
||||||
|
cd ./artifacts
|
||||||
|
for dir in */; do
|
||||||
|
if [ -d "$dir" ]; then
|
||||||
|
dir_name=${dir%/}
|
||||||
|
echo "Creating zip for $dir_name"
|
||||||
|
(cd "$dir_name" && zip -r "../${dir_name}.zip" .)
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Get latest release information
|
||||||
|
id: get_latest_release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.SHADPS4_TOKEN_REPO }}
|
||||||
|
run: |
|
||||||
|
api_url="https://api.github.com/repos/${{ github.repository }}"
|
||||||
|
latest_release_info=$(curl -H "Authorization: token $GITHUB_TOKEN" "$api_url/releases/latest")
|
||||||
|
echo "last_release_tag=$(echo "$latest_release_info" | jq -r '.tag_name')" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Create Pre-Release on GitHub
|
||||||
|
id: create_release
|
||||||
|
uses: ncipollo/release-action@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.SHADPS4_TOKEN_REPO }}
|
||||||
|
name: "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}"
|
||||||
|
tag: "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}"
|
||||||
|
draft: false
|
||||||
|
prerelease: true
|
||||||
|
body: "Full Changelog: [${{ env.last_release_tag }}...${{ needs.get-info.outputs.shorthash }}](https://github.com/shadps4-emu/shadPS4/compare/${{ env.last_release_tag }}...${{ needs.get-info.outputs.shorthash }})"
|
||||||
|
artifacts: ./artifacts/*.zip
|
||||||
|
|
||||||
|
- name: Get current pre-release information
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.SHADPS4_TOKEN_REPO }}
|
||||||
|
run: |
|
||||||
|
api_url="https://api.github.com/repos/${{ github.repository }}/releases"
|
||||||
|
|
||||||
|
# Get all releases (sorted by date)
|
||||||
|
releases=$(curl -H "Authorization: token $GITHUB_TOKEN" "$api_url")
|
||||||
|
|
||||||
|
# Capture the most recent pre-release (assuming the first one is the latest)
|
||||||
|
current_release=$(echo "$releases" | jq -c '.[] | select(.prerelease == true) | .published_at' | sort -r | head -n 1)
|
||||||
|
|
||||||
|
# Remove extra quotes from captured date
|
||||||
|
current_release=$(echo $current_release | tr -d '"')
|
||||||
|
|
||||||
|
# Export the current published_at to be available for the next step
|
||||||
|
echo "CURRENT_PUBLISHED_AT=$current_release" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Delete old pre-releases and tags
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.SHADPS4_TOKEN_REPO }}
|
||||||
|
run: |
|
||||||
|
api_url="https://api.github.com/repos/${{ github.repository }}/releases"
|
||||||
|
|
||||||
|
# Get current pre-releases
|
||||||
|
releases=$(curl -H "Authorization: token $GITHUB_TOKEN" "$api_url")
|
||||||
|
|
||||||
|
# Remove extra quotes from captured date
|
||||||
|
CURRENT_PUBLISHED_AT=$(echo $CURRENT_PUBLISHED_AT | tr -d '"')
|
||||||
|
|
||||||
|
# Convert CURRENT_PUBLISHED_AT para timestamp Unix
|
||||||
|
current_published_ts=$(date -d "$CURRENT_PUBLISHED_AT" +%s)
|
||||||
|
|
||||||
|
# Identify pre-releases
|
||||||
|
echo "$releases" | jq -c '.[] | select(.prerelease == true)' | while read -r release; do
|
||||||
|
release_date=$(echo "$release" | jq -r '.published_at')
|
||||||
|
release_id=$(echo "$release" | jq -r '.id')
|
||||||
|
release_tag=$(echo "$release" | jq -r '.tag_name')
|
||||||
|
|
||||||
|
# Remove extra quotes from captured date
|
||||||
|
release_date=$(echo $release_date | tr -d '"')
|
||||||
|
|
||||||
|
# Convert release_date para timestamp Unix
|
||||||
|
release_date_ts=$(date -d "$release_date" +%s)
|
||||||
|
|
||||||
|
# Compare timestamps and delete old pre-releases
|
||||||
|
if [[ "$release_date_ts" -lt "$current_published_ts" ]]; then
|
||||||
|
echo "Deleting old pre-release: $release_id from $release_date with tag: $release_tag"
|
||||||
|
# Delete the pre-release
|
||||||
|
curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" "$api_url/$release_id"
|
||||||
|
# Delete the tag
|
||||||
|
curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" "https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$release_tag"
|
||||||
|
else
|
||||||
|
echo "Skipping pre-release: $release_id (newer or same date)"
|
||||||
|
fi
|
||||||
|
done
|
17
.github/workflows/ci.yml
vendored
@ -1,17 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
name: Reuse
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ main ]
|
|
||||||
tags: [ "*" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ main ]
|
|
||||||
jobs:
|
|
||||||
reuse:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: fsfe/reuse-action@v4
|
|
28
.github/workflows/format.yml
vendored
@ -1,28 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
name: Clang Format
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "*" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ main ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
clang-format:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Install
|
|
||||||
run: |
|
|
||||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
|
||||||
sudo add-apt-repository 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main'
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install clang-format-17
|
|
||||||
- name: Build
|
|
||||||
env:
|
|
||||||
COMMIT_RANGE: ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}
|
|
||||||
run: ./.ci/clang-format.sh
|
|
66
.github/workflows/linux-qt.yml
vendored
@ -1,66 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
name: Linux-Qt
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "main" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ "main" ]
|
|
||||||
|
|
||||||
env:
|
|
||||||
BUILD_TYPE: Release
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
|
|
||||||
- name: Install misc packages
|
|
||||||
run: >
|
|
||||||
sudo apt-get update && sudo apt install libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential qt6-base-dev qt6-tools-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.14
|
|
||||||
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_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel
|
|
||||||
|
|
||||||
- name: Run AppImage packaging script
|
|
||||||
run: ./.github/linux-appimage-qt.sh
|
|
||||||
|
|
||||||
- name: Get date and git hash
|
|
||||||
id: vars
|
|
||||||
run: |
|
|
||||||
echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
|
||||||
echo "shorthash=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Upload executable
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: shadps4-linux-qt-${{ steps.vars.outputs.date }}-${{ steps.vars.outputs.shorthash }}
|
|
||||||
path: Shadps4-qt.AppImage
|
|
73
.github/workflows/linux.yml
vendored
@ -1,73 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
name: Linux
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "main" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ "main" ]
|
|
||||||
|
|
||||||
env:
|
|
||||||
BUILD_TYPE: Release
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
|
|
||||||
- name: Install misc packages
|
|
||||||
run: >
|
|
||||||
sudo apt-get update && sudo apt install libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential
|
|
||||||
|
|
||||||
- name: Cache CMake Configuration
|
|
||||||
uses: actions/cache@v4
|
|
||||||
env:
|
|
||||||
cache-name: ${{ runner.os }}-sdl-cache-cmake-configuration
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
${{github.workspace}}/build
|
|
||||||
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ env.cache-name }}-
|
|
||||||
|
|
||||||
- name: Cache CMake Build
|
|
||||||
uses: hendrikmuhs/ccache-action@v1.2.14
|
|
||||||
env:
|
|
||||||
cache-name: ${{ runner.os }}-sdl-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_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel
|
|
||||||
|
|
||||||
- name: Get date and git hash
|
|
||||||
id: vars
|
|
||||||
run: |
|
|
||||||
echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
|
||||||
echo "shorthash=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Upload executable
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: shadps4-ubuntu64-${{ steps.vars.outputs.date }}-${{ steps.vars.outputs.shorthash }}
|
|
||||||
path: |
|
|
||||||
${{github.workspace}}/build/shadps4
|
|
||||||
|
|
||||||
- name: Run AppImage packaging script
|
|
||||||
run: ./.github/linux-appimage-sdl.sh
|
|
||||||
|
|
||||||
- name: Upload executable
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: shadps4-sdl-appimage-${{ steps.vars.outputs.date }}-${{ steps.vars.outputs.shorthash }}
|
|
||||||
path: Shadps4-sdl.AppImage
|
|
88
.github/workflows/macos-qt.yml
vendored
@ -1,88 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
name: macOS-Qt
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "main" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ "main" ]
|
|
||||||
|
|
||||||
env:
|
|
||||||
BUILD_TYPE: Release
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: macos-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
|
|
||||||
- name: Setup latest Xcode
|
|
||||||
uses: maxim-lobanov/setup-xcode@v1
|
|
||||||
with:
|
|
||||||
xcode-version: latest
|
|
||||||
|
|
||||||
- name: Install MoltenVK
|
|
||||||
run: |
|
|
||||||
arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
|
||||||
arch -x86_64 /usr/local/bin/brew install molten-vk
|
|
||||||
|
|
||||||
- name: Setup Qt
|
|
||||||
uses: jurplel/install-qt-action@v4
|
|
||||||
with:
|
|
||||||
version: 6.7.2
|
|
||||||
host: mac
|
|
||||||
target: desktop
|
|
||||||
arch: clang_64
|
|
||||||
archives: qtbase qttools
|
|
||||||
|
|
||||||
- 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.14
|
|
||||||
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 -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
|
|
||||||
run: |
|
|
||||||
mkdir upload
|
|
||||||
mv ${{github.workspace}}/build/shadps4.app upload
|
|
||||||
macdeployqt upload/shadps4.app
|
|
||||||
tar cf shadps4-macos-qt.tar.gz -C upload .
|
|
||||||
|
|
||||||
- name: Get date and git hash
|
|
||||||
id: vars
|
|
||||||
run: |
|
|
||||||
echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
|
||||||
echo "shorthash=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
|
|
||||||
- name: Upload executable
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: shadps4-macos-qt-${{ steps.vars.outputs.date }}-${{ steps.vars.outputs.shorthash }}
|
|
||||||
path: shadps4-macos-qt.tar.gz
|
|
79
.github/workflows/macos.yml
vendored
@ -1,79 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
name: macOS
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "main" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ "main" ]
|
|
||||||
|
|
||||||
env:
|
|
||||||
BUILD_TYPE: Release
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: macos-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
|
|
||||||
- name: Setup latest Xcode
|
|
||||||
uses: maxim-lobanov/setup-xcode@v1
|
|
||||||
with:
|
|
||||||
xcode-version: latest
|
|
||||||
|
|
||||||
- name: Install MoltenVK
|
|
||||||
run: |
|
|
||||||
arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
|
||||||
arch -x86_64 /usr/local/bin/brew install molten-vk
|
|
||||||
|
|
||||||
- name: Cache CMake Configuration
|
|
||||||
uses: actions/cache@v4
|
|
||||||
env:
|
|
||||||
cache-name: ${{ runner.os }}-sdl-cache-cmake-configuration
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
${{github.workspace}}/build
|
|
||||||
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ env.cache-name }}-
|
|
||||||
|
|
||||||
- name: Cache CMake Build
|
|
||||||
uses: hendrikmuhs/ccache-action@v1.2.14
|
|
||||||
env:
|
|
||||||
cache-name: ${{runner.os}}-sdl-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 -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
|
|
||||||
run: |
|
|
||||||
mkdir upload
|
|
||||||
mv ${{github.workspace}}/build/shadps4 upload
|
|
||||||
cp $(arch -x86_64 /usr/local/bin/brew --prefix)/opt/molten-vk/lib/libMoltenVK.dylib upload
|
|
||||||
install_name_tool -add_rpath "@loader_path" upload/shadps4
|
|
||||||
tar cf shadps4-macos-sdl.tar.gz -C upload .
|
|
||||||
|
|
||||||
- name: Get date and git hash
|
|
||||||
id: vars
|
|
||||||
run: |
|
|
||||||
echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
|
||||||
echo "shorthash=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Upload executable
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: shadps4-macos-sdl-${{ steps.vars.outputs.date }}-${{ steps.vars.outputs.shorthash }}
|
|
||||||
path: shadps4-macos-sdl.tar.gz
|
|
80
.github/workflows/windows-qt.yml
vendored
@ -1,80 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
name: Windows-Qt
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "main" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ "main" ]
|
|
||||||
|
|
||||||
env:
|
|
||||||
BUILD_TYPE: Release
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: windows-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
|
|
||||||
- name: Setup Qt
|
|
||||||
uses: jurplel/install-qt-action@v4
|
|
||||||
with:
|
|
||||||
version: 6.7.2
|
|
||||||
host: windows
|
|
||||||
target: desktop
|
|
||||||
arch: win64_msvc2019_64
|
|
||||||
archives: qtbase qttools
|
|
||||||
|
|
||||||
- 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.14
|
|
||||||
env:
|
|
||||||
cache-name: ${{ runner.os }}-qt-cache-cmake-build
|
|
||||||
with:
|
|
||||||
append-timestamp: false
|
|
||||||
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
|
|
||||||
|
|
||||||
- name: Setup VS Environment
|
|
||||||
uses: ilammy/msvc-dev-cmd@v1.13.0
|
|
||||||
with:
|
|
||||||
arch: amd64
|
|
||||||
|
|
||||||
- name: Configure CMake
|
|
||||||
run: cmake --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_QT_GUI=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
|
|
||||||
|
|
||||||
- name: Deploy
|
|
||||||
run: |
|
|
||||||
mkdir upload
|
|
||||||
move build/shadPS4.exe upload
|
|
||||||
windeployqt --no-compiler-runtime --no-system-d3d-compiler --no-system-dxc-compiler --dir upload upload/shadPS4.exe
|
|
||||||
|
|
||||||
- name: Get date and git hash
|
|
||||||
id: vars
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
echo "date=$(Get-Date -Format 'yyyy-MM-dd')" >> $env:GITHUB_OUTPUT
|
|
||||||
echo "shorthash=$(git rev-parse --short HEAD)" >> $env:GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Upload executable
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: shadps4-win64-qt-${{ steps.vars.outputs.date }}-${{ steps.vars.outputs.shorthash }}
|
|
||||||
path: upload
|
|
65
.github/workflows/windows.yml
vendored
@ -1,65 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
name: Windows
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "main" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ "main" ]
|
|
||||||
|
|
||||||
env:
|
|
||||||
BUILD_TYPE: Release
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: windows-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
|
|
||||||
- name: Cache CMake Configuration
|
|
||||||
uses: actions/cache@v4
|
|
||||||
env:
|
|
||||||
cache-name: ${{ runner.os }}-sdl-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.14
|
|
||||||
env:
|
|
||||||
cache-name: ${{ runner.os }}-sdl-cache-cmake-build
|
|
||||||
with:
|
|
||||||
append-timestamp: false
|
|
||||||
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
|
|
||||||
|
|
||||||
- name: Setup VS Environment
|
|
||||||
uses: ilammy/msvc-dev-cmd@v1.13.0
|
|
||||||
with:
|
|
||||||
arch: amd64
|
|
||||||
|
|
||||||
- name: Configure CMake
|
|
||||||
run: cmake --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -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
|
|
||||||
|
|
||||||
- name: Get date and git hash
|
|
||||||
id: vars
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
echo "date=$(Get-Date -Format 'yyyy-MM-dd')" >> $env:GITHUB_OUTPUT
|
|
||||||
echo "shorthash=$(git rev-parse --short HEAD)" >> $env:GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Upload executable
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: shadps4-win64-sdl-${{ steps.vars.outputs.date }}-${{ steps.vars.outputs.shorthash }}
|
|
||||||
path: |
|
|
||||||
${{github.workspace}}/build/shadPS4.exe
|
|
6
.gitignore
vendored
@ -387,6 +387,8 @@ FodyWeavers.xsd
|
|||||||
!.vscode/launch.json
|
!.vscode/launch.json
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
*.code-workspace
|
*.code-workspace
|
||||||
|
/CMakeUserPresets.json
|
||||||
|
/compile_commands.json
|
||||||
|
|
||||||
# Local History for Visual Studio Code
|
# Local History for Visual Studio Code
|
||||||
.history/
|
.history/
|
||||||
@ -409,6 +411,6 @@ FodyWeavers.xsd
|
|||||||
/out/*
|
/out/*
|
||||||
/third-party/out/*
|
/third-party/out/*
|
||||||
/src/common/scm_rev.cpp
|
/src/common/scm_rev.cpp
|
||||||
|
|
||||||
# for macOS
|
# for macOS
|
||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
|
2
.gitmodules
vendored
@ -85,6 +85,7 @@
|
|||||||
[submodule "externals/half"]
|
[submodule "externals/half"]
|
||||||
path = externals/half
|
path = externals/half
|
||||||
url = https://github.com/ROCm/half.git
|
url = https://github.com/ROCm/half.git
|
||||||
|
shallow = true
|
||||||
[submodule "externals/dear_imgui"]
|
[submodule "externals/dear_imgui"]
|
||||||
path = externals/dear_imgui
|
path = externals/dear_imgui
|
||||||
url = https://github.com/shadps4-emu/ext-imgui.git
|
url = https://github.com/shadps4-emu/ext-imgui.git
|
||||||
@ -93,3 +94,4 @@
|
|||||||
[submodule "externals/pugixml"]
|
[submodule "externals/pugixml"]
|
||||||
path = externals/pugixml
|
path = externals/pugixml
|
||||||
url = https://github.com/zeux/pugixml.git
|
url = https://github.com/zeux/pugixml.git
|
||||||
|
shallow = true
|
60
.reuse/dep5
@ -1,60 +0,0 @@
|
|||||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
|
||||||
Comment: It is best to use this file to record copyright information about
|
|
||||||
generated, binary and third party files
|
|
||||||
|
|
||||||
Files: CMakeSettings.json
|
|
||||||
.github/shadps4.desktop
|
|
||||||
.github/shadps4.png
|
|
||||||
.gitmodules
|
|
||||||
documents/changelog.txt
|
|
||||||
documents/readme.txt
|
|
||||||
documents/Quickstart/1.png
|
|
||||||
documents/Quickstart/2.png
|
|
||||||
documents/Screenshots/Bloodborne.png
|
|
||||||
documents/Screenshots/Sonic Mania.png
|
|
||||||
documents/Screenshots/Undertale.png
|
|
||||||
documents/Screenshots/We are DOOMED.png
|
|
||||||
scripts/ps4_names.txt
|
|
||||||
src/images/about_icon.png
|
|
||||||
src/images/controller_icon.png
|
|
||||||
src/images/exit_icon.png
|
|
||||||
src/images/file_icon.png
|
|
||||||
src/images/flag_china.png
|
|
||||||
src/images/flag_eu.png
|
|
||||||
src/images/flag_jp.png
|
|
||||||
src/images/flag_unk.png
|
|
||||||
src/images/flag_us.png
|
|
||||||
src/images/flag_world.png
|
|
||||||
src/images/folder_icon.png
|
|
||||||
src/images/grid_icon.png
|
|
||||||
src/images/iconsize_icon.png
|
|
||||||
src/images/list_icon.png
|
|
||||||
src/images/list_mode_icon.png
|
|
||||||
src/images/pause_icon.png
|
|
||||||
src/images/play_icon.png
|
|
||||||
src/images/refresh_icon.png
|
|
||||||
src/images/settings_icon.png
|
|
||||||
src/images/stop_icon.png
|
|
||||||
src/images/shadPS4.icns
|
|
||||||
src/images/shadps4.ico
|
|
||||||
src/images/themes_icon.png
|
|
||||||
src/shadps4.qrc
|
|
||||||
src/shadps4.rc
|
|
||||||
Copyright: shadPS4 Emulator Project
|
|
||||||
License: GPL-2.0-or-later
|
|
||||||
|
|
||||||
Files: externals/cmake-modules/*
|
|
||||||
Copyright: 2009-2010 Iowa State University
|
|
||||||
License: BSL-1.0
|
|
||||||
|
|
||||||
Files: externals/renderdoc/*
|
|
||||||
Copyright: 2019-2024 Baldur Karlsson
|
|
||||||
License: MIT
|
|
||||||
|
|
||||||
Files: externals/stb_image.h
|
|
||||||
Copyright: 2017 Sean Barrett
|
|
||||||
License: MIT
|
|
||||||
|
|
||||||
Files: externals/tracy/*
|
|
||||||
Copyright: 2017-2024 Bartosz Taudul <wolf@nereid.pl>
|
|
||||||
License: BSD-3-Clause
|
|
@ -95,6 +95,7 @@ include(GetGitRevisionDescription)
|
|||||||
get_git_head_revision(GIT_REF_SPEC GIT_REV)
|
get_git_head_revision(GIT_REF_SPEC GIT_REV)
|
||||||
git_describe(GIT_DESC --always --long --dirty)
|
git_describe(GIT_DESC --always --long --dirty)
|
||||||
git_branch_name(GIT_BRANCH)
|
git_branch_name(GIT_BRANCH)
|
||||||
|
string(TIMESTAMP BUILD_DATE "%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp" @ONLY)
|
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp" @ONLY)
|
||||||
|
|
||||||
@ -143,7 +144,7 @@ add_subdirectory(externals)
|
|||||||
include_directories(src)
|
include_directories(src)
|
||||||
|
|
||||||
if(ENABLE_QT_GUI)
|
if(ENABLE_QT_GUI)
|
||||||
find_package(Qt6 REQUIRED COMPONENTS Widgets Concurrent LinguistTools Network)
|
find_package(Qt6 REQUIRED COMPONENTS Widgets Concurrent LinguistTools Network Multimedia)
|
||||||
qt_standard_project_setup()
|
qt_standard_project_setup()
|
||||||
set(CMAKE_AUTORCC ON)
|
set(CMAKE_AUTORCC ON)
|
||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
@ -358,8 +359,9 @@ set(COMMON src/common/logging/backend.cpp
|
|||||||
src/common/config.h
|
src/common/config.h
|
||||||
src/common/cstring.h
|
src/common/cstring.h
|
||||||
src/common/debug.h
|
src/common/debug.h
|
||||||
src/common/disassembler.cpp
|
src/common/decoder.cpp
|
||||||
src/common/disassembler.h
|
src/common/decoder.h
|
||||||
|
src/common/elf_info.h
|
||||||
src/common/endian.h
|
src/common/endian.h
|
||||||
src/common/enum.h
|
src/common/enum.h
|
||||||
src/common/io_file.cpp
|
src/common/io_file.cpp
|
||||||
@ -377,6 +379,8 @@ set(COMMON src/common/logging/backend.cpp
|
|||||||
src/common/polyfill_thread.h
|
src/common/polyfill_thread.h
|
||||||
src/common/rdtsc.cpp
|
src/common/rdtsc.cpp
|
||||||
src/common/rdtsc.h
|
src/common/rdtsc.h
|
||||||
|
src/common/signal_context.h
|
||||||
|
src/common/signal_context.cpp
|
||||||
src/common/singleton.h
|
src/common/singleton.h
|
||||||
src/common/slot_vector.h
|
src/common/slot_vector.h
|
||||||
src/common/string_util.cpp
|
src/common/string_util.cpp
|
||||||
@ -474,6 +478,7 @@ set(SHADER_RECOMPILER src/shader_recompiler/exception.h
|
|||||||
src/shader_recompiler/params.h
|
src/shader_recompiler/params.h
|
||||||
src/shader_recompiler/runtime_info.h
|
src/shader_recompiler/runtime_info.h
|
||||||
src/shader_recompiler/specialization.h
|
src/shader_recompiler/specialization.h
|
||||||
|
src/shader_recompiler/backend/bindings.h
|
||||||
src/shader_recompiler/backend/spirv/emit_spirv.cpp
|
src/shader_recompiler/backend/spirv/emit_spirv.cpp
|
||||||
src/shader_recompiler/backend/spirv/emit_spirv.h
|
src/shader_recompiler/backend/spirv/emit_spirv.h
|
||||||
src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp
|
src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp
|
||||||
@ -579,6 +584,8 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp
|
|||||||
src/video_core/renderer_vulkan/vk_master_semaphore.h
|
src/video_core/renderer_vulkan/vk_master_semaphore.h
|
||||||
src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
|
src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
|
||||||
src/video_core/renderer_vulkan/vk_pipeline_cache.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_platform.cpp
|
src/video_core/renderer_vulkan/vk_platform.cpp
|
||||||
src/video_core/renderer_vulkan/vk_platform.h
|
src/video_core/renderer_vulkan/vk_platform.h
|
||||||
src/video_core/renderer_vulkan/vk_rasterizer.cpp
|
src/video_core/renderer_vulkan/vk_rasterizer.cpp
|
||||||
@ -646,8 +653,12 @@ qt_add_resources(RESOURCE_FILES src/shadps4.qrc)
|
|||||||
set(QT_GUI src/qt_gui/about_dialog.cpp
|
set(QT_GUI src/qt_gui/about_dialog.cpp
|
||||||
src/qt_gui/about_dialog.h
|
src/qt_gui/about_dialog.h
|
||||||
src/qt_gui/about_dialog.ui
|
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.cpp
|
||||||
src/qt_gui/cheats_patches.h
|
src/qt_gui/cheats_patches.h
|
||||||
|
src/qt_gui/check_update.cpp
|
||||||
|
src/qt_gui/check_update.h
|
||||||
src/qt_gui/main_window_ui.h
|
src/qt_gui/main_window_ui.h
|
||||||
src/qt_gui/main_window.cpp
|
src/qt_gui/main_window.cpp
|
||||||
src/qt_gui/main_window.h
|
src/qt_gui/main_window.h
|
||||||
@ -751,7 +762,7 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (ENABLE_QT_GUI)
|
if (ENABLE_QT_GUI)
|
||||||
target_link_libraries(shadps4 PRIVATE Qt6::Widgets Qt6::Concurrent Qt6::Network)
|
target_link_libraries(shadps4 PRIVATE Qt6::Widgets Qt6::Concurrent Qt6::Network Qt6::Multimedia)
|
||||||
add_definitions(-DENABLE_QT_GUI)
|
add_definitions(-DENABLE_QT_GUI)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@ -807,6 +818,11 @@ add_subdirectory(${HOST_SHADERS_INCLUDE})
|
|||||||
add_dependencies(shadps4 host_shaders)
|
add_dependencies(shadps4 host_shaders)
|
||||||
target_include_directories(shadps4 PRIVATE ${HOST_SHADERS_INCLUDE})
|
target_include_directories(shadps4 PRIVATE ${HOST_SHADERS_INCLUDE})
|
||||||
|
|
||||||
|
# ImGui resources
|
||||||
|
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)
|
if (ENABLE_QT_GUI)
|
||||||
set_target_properties(shadps4 PROPERTIES
|
set_target_properties(shadps4 PROPERTIES
|
||||||
# WIN32_EXECUTABLE ON
|
# WIN32_EXECUTABLE ON
|
||||||
@ -822,4 +838,4 @@ if (UNIX AND NOT APPLE)
|
|||||||
find_package(OpenSSL REQUIRED)
|
find_package(OpenSSL REQUIRED)
|
||||||
target_link_libraries(shadps4 PRIVATE ${OPENSSL_LIBRARIES})
|
target_link_libraries(shadps4 PRIVATE ${OPENSSL_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
@ -9,7 +9,8 @@
|
|||||||
"cmakeCommandArgs": "",
|
"cmakeCommandArgs": "",
|
||||||
"buildCommandArgs": "",
|
"buildCommandArgs": "",
|
||||||
"ctestCommandArgs": "",
|
"ctestCommandArgs": "",
|
||||||
"inheritEnvironments": [ "clang_cl_x64_x64" ]
|
"inheritEnvironments": [ "clang_cl_x64_x64" ],
|
||||||
|
"intelliSenseMode": "windows-clang-x64"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "x64-Clang-Debug",
|
"name": "x64-Clang-Debug",
|
||||||
@ -20,7 +21,8 @@
|
|||||||
"cmakeCommandArgs": "",
|
"cmakeCommandArgs": "",
|
||||||
"buildCommandArgs": "",
|
"buildCommandArgs": "",
|
||||||
"ctestCommandArgs": "",
|
"ctestCommandArgs": "",
|
||||||
"inheritEnvironments": [ "clang_cl_x64_x64" ]
|
"inheritEnvironments": [ "clang_cl_x64_x64" ],
|
||||||
|
"intelliSenseMode": "windows-clang-x64"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "x64-Clang-RelWithDebInfo",
|
"name": "x64-Clang-RelWithDebInfo",
|
||||||
@ -31,7 +33,8 @@
|
|||||||
"cmakeCommandArgs": "",
|
"cmakeCommandArgs": "",
|
||||||
"buildCommandArgs": "",
|
"buildCommandArgs": "",
|
||||||
"ctestCommandArgs": "",
|
"ctestCommandArgs": "",
|
||||||
"inheritEnvironments": [ "clang_cl_x64_x64" ]
|
"inheritEnvironments": [ "clang_cl_x64_x64" ],
|
||||||
|
"intelliSenseMode": "windows-clang-x64"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
43
LICENSES/OFL-1.1.txt
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
SIL OPEN FONT LICENSE
|
||||||
|
|
||||||
|
Version 1.1 - 26 February 2007
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
|
||||||
|
"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting, or substituting — in part or in whole — any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
|
||||||
|
This license becomes null and void if any of the above conditions are not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
54
README.md
@ -26,15 +26,15 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://shadps4.net/">
|
<a href="https://shadps4.net/">
|
||||||
<img src="https://github.com/shadps4-emu/shadPS4/blob/main/documents/Screenshots/Sonic Mania.png" width="400">
|
<img src="https://github.com/shadps4-emu/shadPS4/blob/main/documents/Screenshots/1.png" width="400">
|
||||||
<img src="https://github.com/shadps4-emu/shadPS4/blob/main/documents/Screenshots/Bloodborne.png" width="400">
|
<img src="https://github.com/shadps4-emu/shadPS4/blob/main/documents/Screenshots/2.png" width="400">
|
||||||
<img src="https://github.com/shadps4-emu/shadPS4/blob/main/documents/Screenshots/Undertale.png" width="400">
|
<img src="https://github.com/shadps4-emu/shadPS4/blob/main/documents/Screenshots/3.png" width="400">
|
||||||
<img src="https://github.com/shadps4-emu/shadPS4/blob/main/documents/Screenshots/We are DOOMED.png" width="400">
|
<img src="https://github.com/shadps4-emu/shadPS4/blob/main/documents/Screenshots/4.png" width="400">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
# General information
|
# General information
|
||||||
|
|
||||||
shadPS4 is an early **PlayStation 4** emulator for **Windows**, **Linux** and **macOS** written in C++.
|
**shadPS4** is an early **PlayStation 4** emulator for **Windows**, **Linux** and **macOS** written in C++.
|
||||||
|
|
||||||
If you encounter problems or have doubts, do not hesitate to look at the [**Quickstart**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Quickstart/Quickstart.md).
|
If you encounter problems or have doubts, do not hesitate to look at the [**Quickstart**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Quickstart/Quickstart.md).
|
||||||
|
|
||||||
@ -44,12 +44,14 @@ To discuss shadPS4 development, suggest ideas or to ask for help, join our [**Di
|
|||||||
|
|
||||||
To get the latest news, go to our [**X (Twitter)**](https://x.com/shadps4) or our [**website**](https://shadps4.net/).
|
To get the latest news, go to our [**X (Twitter)**](https://x.com/shadps4) or our [**website**](https://shadps4.net/).
|
||||||
|
|
||||||
|
For those who'd like to donate to the project, we now have a [**Kofi page**](https://ko-fi.com/shadps4)!
|
||||||
|
|
||||||
# Status
|
# Status
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> shadPS4 is early in development, don't expect a flawless experience.
|
> shadPS4 is early in development, don't expect a flawless experience.
|
||||||
|
|
||||||
Currently, the emulator successfully runs small games like [**Sonic Mania**](https://www.youtube.com/watch?v=AAHoNzhHyCU), [**Undertale**](https://youtu.be/5zIvdy65Ro4) and it can even *somewhat* run [**Bloodborne**](https://www.youtube.com/watch?v=wC6s0avpQRE).
|
Currently, the emulator successfully runs small games like [**Sonic Mania**](https://www.youtube.com/watch?v=AAHoNzhHyCU), [**Undertale**](https://youtu.be/5zIvdy65Ro4) and it can even run [**Bloodborne**](https://www.youtube.com/watch?v=wC6s0avpQRE).
|
||||||
|
|
||||||
# Why
|
# Why
|
||||||
|
|
||||||
@ -69,40 +71,12 @@ Check the build instructions for [**Linux**](https://github.com/shadps4-emu/shad
|
|||||||
|
|
||||||
Check the build instructions for [**macOS**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-macos.md).
|
Check the build instructions for [**macOS**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-macos.md).
|
||||||
|
|
||||||
Note that macOS users need at least macOS 15 on an Apple Silicon Mac, or at least macOS 11 on an Intel Mac.
|
> [!IMPORTANT]
|
||||||
|
> macOS users need at least macOS 15 on Apple Silicon-based Mac devices and at least macOS 11 on Intel-based Mac devices.
|
||||||
## Building status
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><b>Windows</b></summary>
|
|
||||||
|
|
||||||
| Windows | Build status |
|
|
||||||
|--------|--------|
|
|
||||||
|Windows SDL Build|[](https://github.com/shadps4-emu/shadPS4/actions/workflows/windows.yml)
|
|
||||||
|Windows Qt Build|[](https://github.com/shadps4-emu/shadPS4/actions/workflows/windows-qt.yml)
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><b>Linux</b></summary>
|
|
||||||
|
|
||||||
| Linux | Build status |
|
|
||||||
|--------|--------|
|
|
||||||
|Linux SDL Build|[](https://github.com/shadps4-emu/shadPS4/actions/workflows/linux.yml)
|
|
||||||
|Linux Qt Build|[](https://github.com/shadps4-emu/shadPS4/actions/workflows/linux-qt.yml)
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><b>macOS</b></summary>
|
|
||||||
|
|
||||||
| macOS | Build status |
|
|
||||||
|--------|--------|
|
|
||||||
|macOS SDL Build|[](https://github.com/shadps4-emu/shadPS4/actions/workflows/macos.yml)
|
|
||||||
|macOS Qt Build|[](https://github.com/shadps4-emu/shadPS4/actions/workflows/macos-qt.yml)
|
|
||||||
</details>
|
|
||||||
|
|
||||||
# Debugging and reporting issues
|
# 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).
|
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).
|
||||||
|
|
||||||
# Keyboard mapping
|
# Keyboard mapping
|
||||||
|
|
||||||
@ -172,12 +146,6 @@ A few noteworthy teams/projects who've helped us along the way are:
|
|||||||
|
|
||||||
- [**hydra**](https://github.com/hydra-emu/hydra): A multisystem, multiplatform emulator (chip-8, GB, NES, N64) from Paris.
|
- [**hydra**](https://github.com/hydra-emu/hydra): A multisystem, multiplatform emulator (chip-8, GB, NES, N64) from Paris.
|
||||||
|
|
||||||
|
|
||||||
# Sister Projects
|
|
||||||
|
|
||||||
- [**Panda3DS**](https://github.com/wheremyfoodat/Panda3DS): A multiplatform 3DS emulator from our co-author wheremyfoodat.
|
|
||||||
- [**hydra**](https://github.com/hydra-emu/hydra): A multisystem, multiplatform emulator (chip-8, GB, NES, N64) from Paris.
|
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
||||||
- [**GPL-2.0 license**](https://github.com/shadps4-emu/shadPS4/blob/main/LICENSE)
|
- [**GPL-2.0 license**](https://github.com/shadps4-emu/shadPS4/blob/main/LICENSE)
|
||||||
|
75
REUSE.toml
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
version = 1
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = [
|
||||||
|
"REUSE.toml",
|
||||||
|
"CMakeSettings.json",
|
||||||
|
".github/FUNDING.yml",
|
||||||
|
".github/shadps4.desktop",
|
||||||
|
".github/shadps4.png",
|
||||||
|
".gitmodules",
|
||||||
|
"documents/changelog.txt",
|
||||||
|
"documents/Quickstart/2.png",
|
||||||
|
"documents/Screenshots/*",
|
||||||
|
"scripts/ps4_names.txt",
|
||||||
|
"src/images/about_icon.png",
|
||||||
|
"src/images/controller_icon.png",
|
||||||
|
"src/images/dump_icon.png",
|
||||||
|
"src/images/exit_icon.png",
|
||||||
|
"src/images/file_icon.png",
|
||||||
|
"src/images/flag_china.png",
|
||||||
|
"src/images/flag_eu.png",
|
||||||
|
"src/images/flag_jp.png",
|
||||||
|
"src/images/flag_unk.png",
|
||||||
|
"src/images/flag_us.png",
|
||||||
|
"src/images/flag_world.png",
|
||||||
|
"src/images/folder_icon.png",
|
||||||
|
"src/images/grid_icon.png",
|
||||||
|
"src/images/iconsize_icon.png",
|
||||||
|
"src/images/list_icon.png",
|
||||||
|
"src/images/list_mode_icon.png",
|
||||||
|
"src/images/pause_icon.png",
|
||||||
|
"src/images/play_icon.png",
|
||||||
|
"src/images/refresh_icon.png",
|
||||||
|
"src/images/settings_icon.png",
|
||||||
|
"src/images/stop_icon.png",
|
||||||
|
"src/images/shadPS4.icns",
|
||||||
|
"src/images/shadps4.ico",
|
||||||
|
"src/images/themes_icon.png",
|
||||||
|
"src/images/update_icon.png",
|
||||||
|
"src/shadps4.qrc",
|
||||||
|
"src/shadps4.rc",
|
||||||
|
]
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "shadPS4 Emulator Project"
|
||||||
|
SPDX-License-Identifier = "GPL-2.0-or-later"
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = "externals/cmake-modules/**"
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "2009-2010 Iowa State University"
|
||||||
|
SPDX-License-Identifier = "BSL-1.0"
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = "externals/renderdoc/**"
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "2019-2024 Baldur Karlsson"
|
||||||
|
SPDX-License-Identifier = "MIT"
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = "externals/stb_image.h"
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "2017 Sean Barrett"
|
||||||
|
SPDX-License-Identifier = "MIT"
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = "externals/tracy/**"
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "2017-2024 Bartosz Taudul <wolf@nereid.pl>"
|
||||||
|
SPDX-License-Identifier = "BSD-3-Clause"
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = "src/imgui/renderer/fonts/NotoSansJP-Regular.ttf"
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "2012 Google Inc. All Rights Reserved."
|
||||||
|
SPDX-License-Identifier = "OFL-1.1"
|
@ -39,16 +39,12 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
|
|
||||||
## How to run the latest Work-in-Progress builds of ShadPS4
|
## How to run the latest Work-in-Progress builds of ShadPS4
|
||||||
|
|
||||||
1. Go to <https://github.com/shadps4-emu/shadPS4/actions> and make sure you are logged into your GitHub account (important!)
|
1. Go to <https://github.com/shadps4-emu/shadPS4/releases> In the release identified as 'pre-release' click on the down arrow(Assets), select your operating system of choice (the "**qt**" versions have a user interface, which is probably the one you want. The others are SDL versions, which can only be run via command line).
|
||||||
2. On the left side of the page, select your operating system of choice (the "**qt**" versions have a user interface, which is probably the one you want. The others are SDL versions, which can only be run via command line). 
|

|
||||||
|
|
||||||
3. In the workflow list, select the latest entry with a green :white_check_mark: icon in front of it. (or the latest entry for whatever pull request you wish to test). 
|
2. Once downloaded, extract to its own folder, and run ShadPS4's executable from the extracted folder.
|
||||||
|
|
||||||
4. On the bottom of this page, select the name of the file, and it should start downloading. (If there is no file here, double check that you are indeed logged into a GitHub account, and that there is a green :white_check_mark: icon. 
|
3. Upon first launch, ShadPS4 will prompt you to select a folder to store your installed games in. Select "Browse" and then select a folder that ShadPS4 can use to install your PKG files to.
|
||||||
|
|
||||||
5. Once downloaded, extract to its own folder, and run ShadPS4's executable from the extracted folder.
|
|
||||||
|
|
||||||
6. Upon first launch, ShadPS4 will prompt you to select a folder to store your installed games in. Select "Browse" and then select a folder that ShadPS4 can use to install your PKG files to.
|
|
||||||
|
|
||||||
## Install PKG files
|
## Install PKG files
|
||||||
|
|
||||||
@ -78,4 +74,4 @@ Here's a list of configuration entries that are worth changing:
|
|||||||
- If you'd like to mute everything, but still want to receive messages from Vulkan rendering: `*:Error Render.Vulkan:Info`
|
- If you'd like to mute everything, but still want to receive messages from Vulkan rendering: `*:Error Render.Vulkan:Info`
|
||||||
|
|
||||||
- `[GPU]`
|
- `[GPU]`
|
||||||
- `screenWidth` and `screenHeight`: Configures the game window width and height.
|
- `screenWidth` and `screenHeight`: Configures the game window width and height.
|
||||||
|
BIN
documents/Screenshots/1.png
Normal file
After Width: | Height: | Size: 1.3 MiB |
BIN
documents/Screenshots/2.png
Normal file
After Width: | Height: | Size: 2.3 MiB |
BIN
documents/Screenshots/3.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
documents/Screenshots/4.png
Normal file
After Width: | Height: | Size: 1.8 MiB |
Before Width: | Height: | Size: 350 KiB |
Before Width: | Height: | Size: 850 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 175 KiB |
@ -80,7 +80,7 @@ Normal x86-based computers, follow:
|
|||||||
1. Open "MSYS2 MINGW64" from your new applications
|
1. Open "MSYS2 MINGW64" from your new applications
|
||||||
2. Run `pacman -Syu`, let it complete;
|
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-ninja mingw-w64-x86_64-ffmpeg`
|
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-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`
|
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`
|
4. Run `git clone --depth 1 --recursive https://github.com/shadps4-emu/shadPS4`
|
||||||
5. Run `cd 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"`
|
6. Run `cmake -S . -B build -DCMAKE_C_COMPILER="clang.exe" -DCMAKE_CXX_COMPILER="clang++.exe" -DCMAKE_CXX_FLAGS="-O2 -march=native"`
|
||||||
@ -94,7 +94,7 @@ ARM64-based computers, follow:
|
|||||||
1. Open "MSYS2 CLANGARM64" from your new applications
|
1. Open "MSYS2 CLANGARM64" from your new applications
|
||||||
2. Run `pacman -Syu`, let it complete;
|
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-cmake mingw-w64-clang-aarch64-ninja mingw-w64-clang-aarch64-ffmpeg`
|
3. Run `pacman -S --needed git mingw-w64-clang-aarch64-binutils mingw-w64-clang-aarch64-clang 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`
|
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`
|
4. Run `git clone --depth 1 --recursive https://github.com/shadps4-emu/shadPS4`
|
||||||
5. Run `cd 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"`
|
6. Run `cmake -S . -B build -DCMAKE_C_COMPILER="clang.exe" -DCMAKE_CXX_COMPILER="clang++.exe" -DCMAKE_CXX_FLAGS="-O2 -march=native"`
|
||||||
|
@ -1,3 +1,14 @@
|
|||||||
|
v0.3.0 23/09/2024 - codename broamic
|
||||||
|
=================
|
||||||
|
|
||||||
|
- Cheat/Patching support
|
||||||
|
- DLC support
|
||||||
|
- New translations support (26 languages)
|
||||||
|
- Support for unlocking trophies
|
||||||
|
- Support for more controllers (Dualshock and Xbox)
|
||||||
|
- Many GUI imporovements
|
||||||
|
- AVplayer
|
||||||
|
|
||||||
v0.2.0 15/08/2024 - codename validptr
|
v0.2.0 15/08/2024 - codename validptr
|
||||||
=================
|
=================
|
||||||
- Adding macOS support
|
- Adding macOS support
|
||||||
|
2
externals/date
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 1ead6715dec030d340a316c927c877a3c4e5a00c
|
Subproject commit 51ce7e131079c061533d741be5fe7cca57f2faac
|
2
externals/fmt
vendored
@ -1 +1 @@
|
|||||||
Subproject commit c98518351efd5a46f5d448e947e0b7242d197d07
|
Subproject commit 8ee89546ffcf046309d1f0d38c0393f02fde56c8
|
2
externals/glslang
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 12cbda959b6df2af119a76a73ff906c2bed36884
|
Subproject commit 46ef757e048e760b46601e6e77ae0cb72c97bd2f
|
2
externals/magic_enum
vendored
@ -1 +1 @@
|
|||||||
Subproject commit dae6bbf16c363e9ead4e628a47fdb02956a634f3
|
Subproject commit 126539e13cccdc2e75ce770e94f3c26403099fa5
|
2
externals/pugixml
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 30cc354fe37114ec7a0a4ed2192951690357c2ed
|
Subproject commit 3b17184379fcaaeb7f1fbe08018b7fedf2640b3b
|
2
externals/robin-map
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 2c48a1a50203bbaf1e3d0d64c5d726d56f8d3bb3
|
Subproject commit fe845fd7852ef541c5479ae23b3d36b57f8608ee
|
2
externals/sdl3
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 4cc3410dce50cefce98d3cf3cf1bc8eca83b862a
|
Subproject commit 0548050fc5a4edf1f47c3633c2cd06d8762b5532
|
2
externals/toml11
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 4b740127230472779c4a4d71e1a75aaa3a367a2d
|
Subproject commit d050c6b137199666cae75c2628a75d63b49b1c22
|
2
externals/vma
vendored
@ -1 +1 @@
|
|||||||
Subproject commit e1bdbca9baf4d682fb6066b380f4aa4a7bdbb58a
|
Subproject commit 1c35ba99ce775f8342d87a83a3f0f696f99c2a39
|
2
externals/vulkan-headers
vendored
@ -1 +1 @@
|
|||||||
Subproject commit d205aff40b4e15d4c568523ee6a26f85138126d9
|
Subproject commit 29f979ee5aa58b7b005f805ea8df7a855c39ff37
|
2
externals/xxhash
vendored
@ -1 +1 @@
|
|||||||
Subproject commit dbea33e47e7c0fe0b7c8592cd931c7430c1f130d
|
Subproject commit 3e321b4407318ac1348c0b80fb6fbae8c81ad5fa
|
@ -28,4 +28,16 @@ template <typename T>
|
|||||||
return (value & 0x3FFF) == 0;
|
return (value & 0x3FFF) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
requires std::is_integral_v<T>
|
||||||
|
[[nodiscard]] constexpr bool Is64KBAligned(T value) {
|
||||||
|
return (value & 0xFFFF) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
requires std::is_integral_v<T>
|
||||||
|
[[nodiscard]] constexpr bool Is2MBAligned(T value) {
|
||||||
|
return (value & 0x1FFFFF) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
@ -3,24 +3,48 @@
|
|||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <common/version.h>
|
||||||
#include <fmt/core.h>
|
#include <fmt/core.h>
|
||||||
|
#include <fmt/xchar.h> // for wstring support
|
||||||
#include <toml.hpp>
|
#include <toml.hpp>
|
||||||
|
#include "common/logging/formatter.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
namespace toml {
|
||||||
|
template <typename TC, typename K>
|
||||||
|
std::filesystem::path find_fs_path_or(const basic_value<TC>& v, const K& ky,
|
||||||
|
std::filesystem::path opt) {
|
||||||
|
try {
|
||||||
|
auto str = find<std::string>(v, ky);
|
||||||
|
if (str.empty()) {
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
std::u8string u8str{(char8_t*)&str.front(), (char8_t*)&str.back() + 1};
|
||||||
|
return std::filesystem::path{u8str};
|
||||||
|
} catch (...) {
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace toml
|
||||||
|
|
||||||
namespace Config {
|
namespace Config {
|
||||||
|
|
||||||
static bool isNeo = false;
|
static bool isNeo = false;
|
||||||
static bool isFullscreen = false;
|
static bool isFullscreen = false;
|
||||||
|
static bool playBGM = false;
|
||||||
|
static int BGMvolume = 50;
|
||||||
static u32 screenWidth = 1280;
|
static u32 screenWidth = 1280;
|
||||||
static u32 screenHeight = 720;
|
static u32 screenHeight = 720;
|
||||||
static s32 gpuId = -1; // Vulkan physical device index. Set to negative for auto select
|
static s32 gpuId = -1; // Vulkan physical device index. Set to negative for auto select
|
||||||
static std::string logFilter;
|
static std::string logFilter;
|
||||||
static std::string logType = "async";
|
static std::string logType = "async";
|
||||||
static std::string userName = "shadPS4";
|
static std::string userName = "shadPS4";
|
||||||
|
static std::string updateChannel;
|
||||||
static bool useSpecialPad = false;
|
static bool useSpecialPad = false;
|
||||||
static int specialPadClass = 1;
|
static int specialPadClass = 1;
|
||||||
static bool isDebugDump = false;
|
static bool isDebugDump = false;
|
||||||
static bool isShowSplash = false;
|
static bool isShowSplash = false;
|
||||||
|
static bool isAutoUpdate = false;
|
||||||
static bool isNullGpu = false;
|
static bool isNullGpu = false;
|
||||||
static bool shouldCopyGPUBuffers = false;
|
static bool shouldCopyGPUBuffers = false;
|
||||||
static bool shouldDumpShaders = false;
|
static bool shouldDumpShaders = false;
|
||||||
@ -34,7 +58,7 @@ static bool vkMarkers = false;
|
|||||||
static bool vkCrashDiagnostic = false;
|
static bool vkCrashDiagnostic = false;
|
||||||
|
|
||||||
// Gui
|
// Gui
|
||||||
std::string settings_install_dir = "";
|
std::filesystem::path settings_install_dir = {};
|
||||||
u32 main_window_geometry_x = 400;
|
u32 main_window_geometry_x = 400;
|
||||||
u32 main_window_geometry_y = 400;
|
u32 main_window_geometry_y = 400;
|
||||||
u32 main_window_geometry_w = 1280;
|
u32 main_window_geometry_w = 1280;
|
||||||
@ -62,6 +86,14 @@ bool isFullscreenMode() {
|
|||||||
return isFullscreen;
|
return isFullscreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool getPlayBGM() {
|
||||||
|
return playBGM;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getBGMvolume() {
|
||||||
|
return BGMvolume;
|
||||||
|
}
|
||||||
|
|
||||||
u32 getScreenWidth() {
|
u32 getScreenWidth() {
|
||||||
return screenWidth;
|
return screenWidth;
|
||||||
}
|
}
|
||||||
@ -86,6 +118,10 @@ std::string getUserName() {
|
|||||||
return userName;
|
return userName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string getUpdateChannel() {
|
||||||
|
return updateChannel;
|
||||||
|
}
|
||||||
|
|
||||||
bool getUseSpecialPad() {
|
bool getUseSpecialPad() {
|
||||||
return useSpecialPad;
|
return useSpecialPad;
|
||||||
}
|
}
|
||||||
@ -102,6 +138,10 @@ bool showSplash() {
|
|||||||
return isShowSplash;
|
return isShowSplash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool autoUpdate() {
|
||||||
|
return isAutoUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
bool nullGpu() {
|
bool nullGpu() {
|
||||||
return isNullGpu;
|
return isNullGpu;
|
||||||
}
|
}
|
||||||
@ -170,6 +210,10 @@ void setShowSplash(bool enable) {
|
|||||||
isShowSplash = enable;
|
isShowSplash = enable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setAutoUpdate(bool enable) {
|
||||||
|
isAutoUpdate = enable;
|
||||||
|
}
|
||||||
|
|
||||||
void setNullGpu(bool enable) {
|
void setNullGpu(bool enable) {
|
||||||
isNullGpu = enable;
|
isNullGpu = enable;
|
||||||
}
|
}
|
||||||
@ -206,6 +250,14 @@ void setFullscreenMode(bool enable) {
|
|||||||
isFullscreen = enable;
|
isFullscreen = enable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setPlayBGM(bool enable) {
|
||||||
|
playBGM = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setBGMvolume(int volume) {
|
||||||
|
BGMvolume = volume;
|
||||||
|
}
|
||||||
|
|
||||||
void setLanguage(u32 language) {
|
void setLanguage(u32 language) {
|
||||||
m_language = language;
|
m_language = language;
|
||||||
}
|
}
|
||||||
@ -226,6 +278,10 @@ void setUserName(const std::string& type) {
|
|||||||
userName = type;
|
userName = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setUpdateChannel(const std::string& type) {
|
||||||
|
updateChannel = type;
|
||||||
|
}
|
||||||
|
|
||||||
void setUseSpecialPad(bool use) {
|
void setUseSpecialPad(bool use) {
|
||||||
useSpecialPad = use;
|
useSpecialPad = use;
|
||||||
}
|
}
|
||||||
@ -240,7 +296,7 @@ void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) {
|
|||||||
main_window_geometry_w = w;
|
main_window_geometry_w = w;
|
||||||
main_window_geometry_h = h;
|
main_window_geometry_h = h;
|
||||||
}
|
}
|
||||||
void setGameInstallDir(const std::string& dir) {
|
void setGameInstallDir(const std::filesystem::path& dir) {
|
||||||
settings_install_dir = dir;
|
settings_install_dir = dir;
|
||||||
}
|
}
|
||||||
void setMainWindowTheme(u32 theme) {
|
void setMainWindowTheme(u32 theme) {
|
||||||
@ -296,7 +352,7 @@ u32 getMainWindowGeometryW() {
|
|||||||
u32 getMainWindowGeometryH() {
|
u32 getMainWindowGeometryH() {
|
||||||
return main_window_geometry_h;
|
return main_window_geometry_h;
|
||||||
}
|
}
|
||||||
std::string getGameInstallDir() {
|
std::filesystem::path getGameInstallDir() {
|
||||||
return settings_install_dir;
|
return settings_install_dir;
|
||||||
}
|
}
|
||||||
u32 getMainWindowTheme() {
|
u32 getMainWindowTheme() {
|
||||||
@ -351,7 +407,10 @@ void load(const std::filesystem::path& path) {
|
|||||||
toml::value data;
|
toml::value data;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
data = toml::parse(path);
|
std::ifstream ifs;
|
||||||
|
ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit);
|
||||||
|
ifs.open(path, std::ios_base::binary);
|
||||||
|
data = toml::parse(ifs, std::string{fmt::UTF(path.filename().u8string()).data});
|
||||||
} catch (std::exception& ex) {
|
} catch (std::exception& ex) {
|
||||||
fmt::print("Got exception trying to load config file. Exception: {}\n", ex.what());
|
fmt::print("Got exception trying to load config file. Exception: {}\n", ex.what());
|
||||||
return;
|
return;
|
||||||
@ -361,10 +420,18 @@ void load(const std::filesystem::path& path) {
|
|||||||
|
|
||||||
isNeo = toml::find_or<bool>(general, "isPS4Pro", false);
|
isNeo = toml::find_or<bool>(general, "isPS4Pro", false);
|
||||||
isFullscreen = toml::find_or<bool>(general, "Fullscreen", false);
|
isFullscreen = toml::find_or<bool>(general, "Fullscreen", false);
|
||||||
|
playBGM = toml::find_or<bool>(general, "playBGM", false);
|
||||||
|
BGMvolume = toml::find_or<int>(general, "BGMvolume", 50);
|
||||||
logFilter = toml::find_or<std::string>(general, "logFilter", "");
|
logFilter = toml::find_or<std::string>(general, "logFilter", "");
|
||||||
logType = toml::find_or<std::string>(general, "logType", "sync");
|
logType = toml::find_or<std::string>(general, "logType", "sync");
|
||||||
userName = toml::find_or<std::string>(general, "userName", "shadPS4");
|
userName = toml::find_or<std::string>(general, "userName", "shadPS4");
|
||||||
|
if (Common::isRelease) {
|
||||||
|
updateChannel = toml::find_or<std::string>(general, "updateChannel", "Release");
|
||||||
|
} else {
|
||||||
|
updateChannel = toml::find_or<std::string>(general, "updateChannel", "Nightly");
|
||||||
|
}
|
||||||
isShowSplash = toml::find_or<bool>(general, "showSplash", true);
|
isShowSplash = toml::find_or<bool>(general, "showSplash", true);
|
||||||
|
isAutoUpdate = toml::find_or<bool>(general, "autoUpdate", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.contains("Input")) {
|
if (data.contains("Input")) {
|
||||||
@ -414,7 +481,7 @@ void load(const std::filesystem::path& path) {
|
|||||||
mw_themes = toml::find_or<int>(gui, "theme", 0);
|
mw_themes = toml::find_or<int>(gui, "theme", 0);
|
||||||
m_window_size_W = toml::find_or<int>(gui, "mw_width", 0);
|
m_window_size_W = toml::find_or<int>(gui, "mw_width", 0);
|
||||||
m_window_size_H = toml::find_or<int>(gui, "mw_height", 0);
|
m_window_size_H = toml::find_or<int>(gui, "mw_height", 0);
|
||||||
settings_install_dir = toml::find_or<std::string>(gui, "installDir", "");
|
settings_install_dir = toml::find_fs_path_or(gui, "installDir", {});
|
||||||
main_window_geometry_x = toml::find_or<int>(gui, "geometry_x", 0);
|
main_window_geometry_x = toml::find_or<int>(gui, "geometry_x", 0);
|
||||||
main_window_geometry_y = toml::find_or<int>(gui, "geometry_y", 0);
|
main_window_geometry_y = toml::find_or<int>(gui, "geometry_y", 0);
|
||||||
main_window_geometry_w = toml::find_or<int>(gui, "geometry_w", 0);
|
main_window_geometry_w = toml::find_or<int>(gui, "geometry_w", 0);
|
||||||
@ -438,25 +505,31 @@ void save(const std::filesystem::path& path) {
|
|||||||
std::error_code error;
|
std::error_code error;
|
||||||
if (std::filesystem::exists(path, error)) {
|
if (std::filesystem::exists(path, error)) {
|
||||||
try {
|
try {
|
||||||
data = toml::parse(path);
|
std::ifstream ifs;
|
||||||
|
ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit);
|
||||||
|
ifs.open(path, std::ios_base::binary);
|
||||||
|
data = toml::parse(ifs, std::string{fmt::UTF(path.filename().u8string()).data});
|
||||||
} catch (const std::exception& ex) {
|
} catch (const std::exception& ex) {
|
||||||
fmt::print("Exception trying to parse config file. Exception: {}\n", ex.what());
|
fmt::print("Exception trying to parse config file. Exception: {}\n", ex.what());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (error) {
|
if (error) {
|
||||||
fmt::print("Filesystem error accessing {} (error: {})\n", path.string(),
|
fmt::print("Filesystem error: {}\n", error.message());
|
||||||
error.message().c_str());
|
|
||||||
}
|
}
|
||||||
fmt::print("Saving new configuration file {}\n", path.string());
|
fmt::print("Saving new configuration file {}\n", fmt::UTF(path.u8string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
data["General"]["isPS4Pro"] = isNeo;
|
data["General"]["isPS4Pro"] = isNeo;
|
||||||
data["General"]["Fullscreen"] = isFullscreen;
|
data["General"]["Fullscreen"] = isFullscreen;
|
||||||
|
data["General"]["playBGM"] = playBGM;
|
||||||
|
data["General"]["BGMvolume"] = BGMvolume;
|
||||||
data["General"]["logFilter"] = logFilter;
|
data["General"]["logFilter"] = logFilter;
|
||||||
data["General"]["logType"] = logType;
|
data["General"]["logType"] = logType;
|
||||||
data["General"]["userName"] = userName;
|
data["General"]["userName"] = userName;
|
||||||
|
data["General"]["updateChannel"] = updateChannel;
|
||||||
data["General"]["showSplash"] = isShowSplash;
|
data["General"]["showSplash"] = isShowSplash;
|
||||||
|
data["General"]["autoUpdate"] = isAutoUpdate;
|
||||||
data["Input"]["useSpecialPad"] = useSpecialPad;
|
data["Input"]["useSpecialPad"] = useSpecialPad;
|
||||||
data["Input"]["specialPadClass"] = specialPadClass;
|
data["Input"]["specialPadClass"] = specialPadClass;
|
||||||
data["GPU"]["screenWidth"] = screenWidth;
|
data["GPU"]["screenWidth"] = screenWidth;
|
||||||
@ -482,7 +555,7 @@ void save(const std::filesystem::path& path) {
|
|||||||
data["GUI"]["gameTableMode"] = m_table_mode;
|
data["GUI"]["gameTableMode"] = m_table_mode;
|
||||||
data["GUI"]["mw_width"] = m_window_size_W;
|
data["GUI"]["mw_width"] = m_window_size_W;
|
||||||
data["GUI"]["mw_height"] = m_window_size_H;
|
data["GUI"]["mw_height"] = m_window_size_H;
|
||||||
data["GUI"]["installDir"] = settings_install_dir;
|
data["GUI"]["installDir"] = std::string{fmt::UTF(settings_install_dir.u8string()).data};
|
||||||
data["GUI"]["geometry_x"] = main_window_geometry_x;
|
data["GUI"]["geometry_x"] = main_window_geometry_x;
|
||||||
data["GUI"]["geometry_y"] = main_window_geometry_y;
|
data["GUI"]["geometry_y"] = main_window_geometry_y;
|
||||||
data["GUI"]["geometry_w"] = main_window_geometry_w;
|
data["GUI"]["geometry_w"] = main_window_geometry_w;
|
||||||
@ -502,15 +575,23 @@ void save(const std::filesystem::path& path) {
|
|||||||
void setDefaultValues() {
|
void setDefaultValues() {
|
||||||
isNeo = false;
|
isNeo = false;
|
||||||
isFullscreen = false;
|
isFullscreen = false;
|
||||||
|
playBGM = false;
|
||||||
|
BGMvolume = 50;
|
||||||
screenWidth = 1280;
|
screenWidth = 1280;
|
||||||
screenHeight = 720;
|
screenHeight = 720;
|
||||||
logFilter = "";
|
logFilter = "";
|
||||||
logType = "async";
|
logType = "async";
|
||||||
userName = "shadPS4";
|
userName = "shadPS4";
|
||||||
|
if (Common::isRelease) {
|
||||||
|
updateChannel = "Release";
|
||||||
|
} else {
|
||||||
|
updateChannel = "Nightly";
|
||||||
|
}
|
||||||
useSpecialPad = false;
|
useSpecialPad = false;
|
||||||
specialPadClass = 1;
|
specialPadClass = 1;
|
||||||
isDebugDump = false;
|
isDebugDump = false;
|
||||||
isShowSplash = false;
|
isShowSplash = false;
|
||||||
|
isAutoUpdate = false;
|
||||||
isNullGpu = false;
|
isNullGpu = false;
|
||||||
shouldDumpShaders = false;
|
shouldDumpShaders = false;
|
||||||
shouldDumpPM4 = false;
|
shouldDumpPM4 = false;
|
||||||
|
@ -13,9 +13,13 @@ void save(const std::filesystem::path& path);
|
|||||||
|
|
||||||
bool isNeoMode();
|
bool isNeoMode();
|
||||||
bool isFullscreenMode();
|
bool isFullscreenMode();
|
||||||
|
bool getPlayBGM();
|
||||||
|
int getBGMvolume();
|
||||||
|
|
||||||
std::string getLogFilter();
|
std::string getLogFilter();
|
||||||
std::string getLogType();
|
std::string getLogType();
|
||||||
std::string getUserName();
|
std::string getUserName();
|
||||||
|
std::string getUpdateChannel();
|
||||||
|
|
||||||
bool getUseSpecialPad();
|
bool getUseSpecialPad();
|
||||||
int getSpecialPadClass();
|
int getSpecialPadClass();
|
||||||
@ -26,6 +30,7 @@ s32 getGpuId();
|
|||||||
|
|
||||||
bool debugDump();
|
bool debugDump();
|
||||||
bool showSplash();
|
bool showSplash();
|
||||||
|
bool autoUpdate();
|
||||||
bool nullGpu();
|
bool nullGpu();
|
||||||
bool copyGPUCmdBuffers();
|
bool copyGPUCmdBuffers();
|
||||||
bool dumpShaders();
|
bool dumpShaders();
|
||||||
@ -35,6 +40,7 @@ u32 vblankDiv();
|
|||||||
|
|
||||||
void setDebugDump(bool enable);
|
void setDebugDump(bool enable);
|
||||||
void setShowSplash(bool enable);
|
void setShowSplash(bool enable);
|
||||||
|
void setAutoUpdate(bool enable);
|
||||||
void setNullGpu(bool enable);
|
void setNullGpu(bool enable);
|
||||||
void setCopyGPUCmdBuffers(bool enable);
|
void setCopyGPUCmdBuffers(bool enable);
|
||||||
void setDumpShaders(bool enable);
|
void setDumpShaders(bool enable);
|
||||||
@ -44,9 +50,12 @@ void setGpuId(s32 selectedGpuId);
|
|||||||
void setScreenWidth(u32 width);
|
void setScreenWidth(u32 width);
|
||||||
void setScreenHeight(u32 height);
|
void setScreenHeight(u32 height);
|
||||||
void setFullscreenMode(bool enable);
|
void setFullscreenMode(bool enable);
|
||||||
|
void setPlayBGM(bool enable);
|
||||||
|
void setBGMvolume(int volume);
|
||||||
void setLanguage(u32 language);
|
void setLanguage(u32 language);
|
||||||
void setNeoMode(bool enable);
|
void setNeoMode(bool enable);
|
||||||
void setUserName(const std::string& type);
|
void setUserName(const std::string& type);
|
||||||
|
void setUpdateChannel(const std::string& type);
|
||||||
|
|
||||||
void setUseSpecialPad(bool use);
|
void setUseSpecialPad(bool use);
|
||||||
void setSpecialPadClass(int type);
|
void setSpecialPadClass(int type);
|
||||||
@ -66,7 +75,7 @@ bool vkCrashDiagnosticEnabled();
|
|||||||
|
|
||||||
// Gui
|
// Gui
|
||||||
void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h);
|
void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h);
|
||||||
void setGameInstallDir(const std::string& dir);
|
void setGameInstallDir(const std::filesystem::path& dir);
|
||||||
void setMainWindowTheme(u32 theme);
|
void setMainWindowTheme(u32 theme);
|
||||||
void setIconSize(u32 size);
|
void setIconSize(u32 size);
|
||||||
void setIconSizeGrid(u32 size);
|
void setIconSizeGrid(u32 size);
|
||||||
@ -84,7 +93,7 @@ u32 getMainWindowGeometryX();
|
|||||||
u32 getMainWindowGeometryY();
|
u32 getMainWindowGeometryY();
|
||||||
u32 getMainWindowGeometryW();
|
u32 getMainWindowGeometryW();
|
||||||
u32 getMainWindowGeometryH();
|
u32 getMainWindowGeometryH();
|
||||||
std::string getGameInstallDir();
|
std::filesystem::path getGameInstallDir();
|
||||||
u32 getMainWindowTheme();
|
u32 getMainWindowTheme();
|
||||||
u32 getIconSize();
|
u32 getIconSize();
|
||||||
u32 getIconSizeGrid();
|
u32 getIconSizeGrid();
|
||||||
|
@ -9,6 +9,9 @@
|
|||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wtautological-undefined-compare"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief A null-terminated string with a fixed maximum length
|
* @brief A null-terminated string with a fixed maximum length
|
||||||
* This class is not meant to be used as a general-purpose string class
|
* This class is not meant to be used as a general-purpose string class
|
||||||
@ -29,20 +32,27 @@ public:
|
|||||||
explicit CString(const CString<M>& other)
|
explicit CString(const CString<M>& other)
|
||||||
requires(M <= N)
|
requires(M <= N)
|
||||||
{
|
{
|
||||||
|
if (this == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
std::ranges::copy(other.begin(), other.end(), data);
|
std::ranges::copy(other.begin(), other.end(), data);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FromString(const std::basic_string_view<T>& str) {
|
void FromString(const std::basic_string_view<T>& str) {
|
||||||
|
if (this == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
size_t p = str.copy(data, N - 1);
|
size_t p = str.copy(data, N - 1);
|
||||||
data[p] = '\0';
|
data[p] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
void Zero() {
|
void Zero() {
|
||||||
|
if (this == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
std::ranges::fill(data, 0);
|
std::ranges::fill(data, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma clang diagnostic push
|
|
||||||
#pragma clang diagnostic ignored "-Wtautological-undefined-compare"
|
|
||||||
explicit(false) operator std::basic_string_view<T>() const {
|
explicit(false) operator std::basic_string_view<T>() const {
|
||||||
if (this == nullptr) {
|
if (this == nullptr) {
|
||||||
return {};
|
return {};
|
||||||
@ -70,21 +80,32 @@ public:
|
|||||||
}
|
}
|
||||||
return std::basic_string_view<T>{data};
|
return std::basic_string_view<T>{data};
|
||||||
}
|
}
|
||||||
#pragma clang diagnostic pop
|
|
||||||
|
|
||||||
char* begin() {
|
char* begin() {
|
||||||
|
if (this == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* begin() const {
|
const char* begin() const {
|
||||||
|
if (this == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
char* end() {
|
char* end() {
|
||||||
|
if (this == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
return data + N;
|
return data + N;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* end() const {
|
const char* end() const {
|
||||||
|
if (this == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
return data + N;
|
return data + N;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,7 +148,10 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(CString<13>) == sizeof(char[13])); // Ensure size still matches a simple array
|
static_assert(sizeof(CString<13>) == sizeof(char[13])); // Ensure size still matches a simple array
|
||||||
static_assert(std::weakly_incrementable<CString<13>::Iterator>);
|
static_assert(std::weakly_incrementable<CString<13>::Iterator>);
|
||||||
|
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
@ -2,18 +2,18 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
#include "common/disassembler.h"
|
#include "common/decoder.h"
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
Disassembler::Disassembler() {
|
DecoderImpl::DecoderImpl() {
|
||||||
ZydisDecoderInit(&m_decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64);
|
ZydisDecoderInit(&m_decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64);
|
||||||
ZydisFormatterInit(&m_formatter, ZYDIS_FORMATTER_STYLE_INTEL);
|
ZydisFormatterInit(&m_formatter, ZYDIS_FORMATTER_STYLE_INTEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
Disassembler::~Disassembler() = default;
|
DecoderImpl::~DecoderImpl() = default;
|
||||||
|
|
||||||
void Disassembler::printInstruction(void* code, u64 address) {
|
void DecoderImpl::printInstruction(void* code, u64 address) {
|
||||||
ZydisDecodedInstruction instruction;
|
ZydisDecodedInstruction instruction;
|
||||||
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT_VISIBLE];
|
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT_VISIBLE];
|
||||||
ZyanStatus status =
|
ZyanStatus status =
|
||||||
@ -25,8 +25,8 @@ void Disassembler::printInstruction(void* code, u64 address) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Disassembler::printInst(ZydisDecodedInstruction& inst, ZydisDecodedOperand* operands,
|
void DecoderImpl::printInst(ZydisDecodedInstruction& inst, ZydisDecodedOperand* operands,
|
||||||
u64 address) {
|
u64 address) {
|
||||||
const int bufLen = 256;
|
const int bufLen = 256;
|
||||||
char szBuffer[bufLen];
|
char szBuffer[bufLen];
|
||||||
ZydisFormatterFormatInstruction(&m_formatter, &inst, operands, inst.operand_count_visible,
|
ZydisFormatterFormatInstruction(&m_formatter, &inst, operands, inst.operand_count_visible,
|
||||||
@ -34,4 +34,9 @@ void Disassembler::printInst(ZydisDecodedInstruction& inst, ZydisDecodedOperand*
|
|||||||
fmt::print("instruction: {}\n", szBuffer);
|
fmt::print("instruction: {}\n", szBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ZyanStatus DecoderImpl::decodeInstruction(ZydisDecodedInstruction& inst,
|
||||||
|
ZydisDecodedOperand* operands, void* data, u64 size) {
|
||||||
|
return ZydisDecoderDecodeFull(&m_decoder, data, size, &inst, operands);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
@ -4,21 +4,26 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Zydis/Zydis.h>
|
#include <Zydis/Zydis.h>
|
||||||
|
#include "common/singleton.h"
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
class Disassembler {
|
class DecoderImpl {
|
||||||
public:
|
public:
|
||||||
Disassembler();
|
DecoderImpl();
|
||||||
~Disassembler();
|
~DecoderImpl();
|
||||||
|
|
||||||
void printInst(ZydisDecodedInstruction& inst, ZydisDecodedOperand* operands, u64 address);
|
void printInst(ZydisDecodedInstruction& inst, ZydisDecodedOperand* operands, u64 address);
|
||||||
void printInstruction(void* code, u64 address);
|
void printInstruction(void* code, u64 address);
|
||||||
|
ZyanStatus decodeInstruction(ZydisDecodedInstruction& inst, ZydisDecodedOperand* operands,
|
||||||
|
void* data, u64 size = 15);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ZydisDecoder m_decoder;
|
ZydisDecoder m_decoder;
|
||||||
ZydisFormatter m_formatter;
|
ZydisFormatter m_formatter;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using Decoder = Common::Singleton<DecoderImpl>;
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
72
src/common/elf_info.h
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#include "assert.h"
|
||||||
|
#include "singleton.h"
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
class Emulator;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
class ElfInfo {
|
||||||
|
friend class Core::Emulator;
|
||||||
|
|
||||||
|
bool initialized = false;
|
||||||
|
|
||||||
|
std::string game_serial{};
|
||||||
|
std::string title{};
|
||||||
|
std::string app_ver{};
|
||||||
|
u32 firmware_ver = 0;
|
||||||
|
u32 raw_firmware_ver = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr u32 FW_15 = 0x1500000;
|
||||||
|
static constexpr u32 FW_16 = 0x1600000;
|
||||||
|
static constexpr u32 FW_17 = 0x1700000;
|
||||||
|
static constexpr u32 FW_20 = 0x2000000;
|
||||||
|
static constexpr u32 FW_25 = 0x2500000;
|
||||||
|
static constexpr u32 FW_30 = 0x3000000;
|
||||||
|
static constexpr u32 FW_40 = 0x4000000;
|
||||||
|
static constexpr u32 FW_45 = 0x4500000;
|
||||||
|
static constexpr u32 FW_50 = 0x5000000;
|
||||||
|
static constexpr u32 FW_80 = 0x8000000;
|
||||||
|
|
||||||
|
static ElfInfo& Instance() {
|
||||||
|
return *Singleton<ElfInfo>::Instance();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::string_view GameSerial() const {
|
||||||
|
ASSERT(initialized);
|
||||||
|
return Instance().game_serial;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::string_view Title() const {
|
||||||
|
ASSERT(initialized);
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::string_view AppVer() const {
|
||||||
|
ASSERT(initialized);
|
||||||
|
return app_ver;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] u32 FirmwareVer() const {
|
||||||
|
ASSERT(initialized);
|
||||||
|
return firmware_ver;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] u32 RawFirmwareVer() const {
|
||||||
|
ASSERT(initialized);
|
||||||
|
return raw_firmware_ver;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Common
|
@ -19,3 +19,24 @@ struct fmt::formatter<T, std::enable_if_t<std::is_enum_v<T>, char>>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
namespace fmt {
|
||||||
|
template <typename T = std::string_view>
|
||||||
|
struct UTF {
|
||||||
|
T data;
|
||||||
|
|
||||||
|
explicit UTF(const std::u8string_view view) {
|
||||||
|
data = view.empty() ? T{} : T{(const char*)&view.front(), (const char*)&view.back() + 1};
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit UTF(const std::u8string& str) : UTF(std::u8string_view{str}) {}
|
||||||
|
};
|
||||||
|
} // namespace fmt
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct fmt::formatter<fmt::UTF<std::string_view>, char> : formatter<std::string_view> {
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(const UTF<std::string_view>& wrapper, FormatContext& ctx) const {
|
||||||
|
return formatter<std::string_view>::format(wrapper.data, ctx);
|
||||||
|
}
|
||||||
|
};
|
@ -119,9 +119,9 @@ std::string convertValueToHex(const std::string type, const std::string valueStr
|
|||||||
void OnGameLoaded() {
|
void OnGameLoaded() {
|
||||||
|
|
||||||
if (!patchFile.empty()) {
|
if (!patchFile.empty()) {
|
||||||
std::string patchDir = Common::FS::GetUserPath(Common::FS::PathType::PatchesDir).string();
|
std::filesystem::path patchDir = Common::FS::GetUserPath(Common::FS::PathType::PatchesDir);
|
||||||
|
|
||||||
std::string filePath = patchDir + "/" + patchFile;
|
auto filePath = (patchDir / patchFile).native();
|
||||||
|
|
||||||
pugi::xml_document doc;
|
pugi::xml_document doc;
|
||||||
pugi::xml_parse_result result = doc.load_file(filePath.c_str());
|
pugi::xml_parse_result result = doc.load_file(filePath.c_str());
|
||||||
@ -187,8 +187,8 @@ void OnGameLoaded() {
|
|||||||
|
|
||||||
#ifdef ENABLE_QT_GUI
|
#ifdef ENABLE_QT_GUI
|
||||||
// We use the QT headers for the xml and json parsing, this define is only true on QT builds
|
// We use the QT headers for the xml and json parsing, this define is only true on QT builds
|
||||||
QString patchDir =
|
QString patchDir;
|
||||||
QString::fromStdString(Common::FS::GetUserPath(Common::FS::PathType::PatchesDir).string());
|
Common::FS::PathToQString(patchDir, Common::FS::GetUserPath(Common::FS::PathType::PatchesDir));
|
||||||
QString repositories[] = {"GoldHEN", "shadPS4"};
|
QString repositories[] = {"GoldHEN", "shadPS4"};
|
||||||
|
|
||||||
for (const QString& repository : repositories) {
|
for (const QString& repository : repositories) {
|
||||||
|
@ -22,6 +22,10 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef ENABLE_QT_GUI
|
||||||
|
#include <QString>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Common::FS {
|
namespace Common::FS {
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
@ -165,4 +169,22 @@ void SetUserPath(PathType shad_path, const fs::path& new_path) {
|
|||||||
UserPaths.insert_or_assign(shad_path, new_path);
|
UserPaths.insert_or_assign(shad_path, new_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#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
|
} // namespace Common::FS
|
@ -6,6 +6,10 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#ifdef ENABLE_QT_GUI
|
||||||
|
class QString; // to avoid including <QString> in this header
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Common::FS {
|
namespace Common::FS {
|
||||||
|
|
||||||
enum class PathType {
|
enum class PathType {
|
||||||
@ -96,4 +100,23 @@ constexpr auto LOG_FILE = "shad_log.txt";
|
|||||||
*/
|
*/
|
||||||
void SetUserPath(PathType user_path, const std::filesystem::path& new_path);
|
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
|
||||||
|
|
||||||
} // namespace Common::FS
|
} // namespace Common::FS
|
||||||
|
@ -6,12 +6,14 @@
|
|||||||
#define GIT_REV "@GIT_REV@"
|
#define GIT_REV "@GIT_REV@"
|
||||||
#define GIT_BRANCH "@GIT_BRANCH@"
|
#define GIT_BRANCH "@GIT_BRANCH@"
|
||||||
#define GIT_DESC "@GIT_DESC@"
|
#define GIT_DESC "@GIT_DESC@"
|
||||||
|
#define BUILD_DATE "@BUILD_DATE@"
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
const char g_scm_rev[] = GIT_REV;
|
const char g_scm_rev[] = GIT_REV;
|
||||||
const char g_scm_branch[] = GIT_BRANCH;
|
const char g_scm_branch[] = GIT_BRANCH;
|
||||||
const char g_scm_desc[] = GIT_DESC;
|
const char g_scm_desc[] = GIT_DESC;
|
||||||
|
const char g_scm_date[] = BUILD_DATE;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -8,5 +8,6 @@ namespace Common {
|
|||||||
extern const char g_scm_rev[];
|
extern const char g_scm_rev[];
|
||||||
extern const char g_scm_branch[];
|
extern const char g_scm_branch[];
|
||||||
extern const char g_scm_desc[];
|
extern const char g_scm_desc[];
|
||||||
|
extern const char g_scm_date[];
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
92
src/common/signal_context.cpp
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/arch.h"
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/signal_context.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <sys/ucontext.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
void* GetXmmPointer(void* ctx, u8 index) {
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#define CASE(index) \
|
||||||
|
case index: \
|
||||||
|
return (void*)(&((EXCEPTION_POINTERS*)ctx)->ContextRecord->Xmm##index.Low)
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
#define CASE(index) \
|
||||||
|
case index: \
|
||||||
|
return (void*)(&((ucontext_t*)ctx)->uc_mcontext->__fs.__fpu_xmm##index);
|
||||||
|
#else
|
||||||
|
#define CASE(index) \
|
||||||
|
case index: \
|
||||||
|
return (void*)(&((ucontext_t*)ctx)->uc_mcontext.fpregs->_xmm[index].element[0])
|
||||||
|
#endif
|
||||||
|
switch (index) {
|
||||||
|
CASE(0);
|
||||||
|
CASE(1);
|
||||||
|
CASE(2);
|
||||||
|
CASE(3);
|
||||||
|
CASE(4);
|
||||||
|
CASE(5);
|
||||||
|
CASE(6);
|
||||||
|
CASE(7);
|
||||||
|
CASE(8);
|
||||||
|
CASE(9);
|
||||||
|
CASE(10);
|
||||||
|
CASE(11);
|
||||||
|
CASE(12);
|
||||||
|
CASE(13);
|
||||||
|
CASE(14);
|
||||||
|
CASE(15);
|
||||||
|
default: {
|
||||||
|
UNREACHABLE_MSG("Invalid XMM register index: {}", index);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#undef CASE
|
||||||
|
}
|
||||||
|
|
||||||
|
void* GetRip(void* ctx) {
|
||||||
|
#if defined(_WIN32)
|
||||||
|
return (void*)((EXCEPTION_POINTERS*)ctx)->ContextRecord->Rip;
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
return (void*)((ucontext_t*)ctx)->uc_mcontext->__ss.__rip;
|
||||||
|
#else
|
||||||
|
return (void*)((ucontext_t*)ctx)->uc_mcontext.gregs[REG_RIP];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void IncrementRip(void* ctx, u64 length) {
|
||||||
|
#if defined(_WIN32)
|
||||||
|
((EXCEPTION_POINTERS*)ctx)->ContextRecord->Rip += length;
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
((ucontext_t*)ctx)->uc_mcontext->__ss.__rip += length;
|
||||||
|
#else
|
||||||
|
((ucontext_t*)ctx)->uc_mcontext.gregs[REG_RIP] += length;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsWriteError(void* ctx) {
|
||||||
|
#if defined(_WIN32)
|
||||||
|
return ((EXCEPTION_POINTERS*)ctx)->ExceptionRecord->ExceptionInformation[0] == 1;
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
#if defined(ARCH_X86_64)
|
||||||
|
return ((ucontext_t*)ctx)->uc_mcontext->__es.__err & 0x2;
|
||||||
|
#elif defined(ARCH_ARM64)
|
||||||
|
return ((ucontext_t*)ctx)->uc_mcontext->__es.__esr & 0x40;
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#if defined(ARCH_X86_64)
|
||||||
|
return ((ucontext_t*)ctx)->uc_mcontext.gregs[REG_ERR] & 0x2;
|
||||||
|
#else
|
||||||
|
#error "Unsupported architecture"
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
} // namespace Common
|
18
src/common/signal_context.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/types.h"
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
void* GetXmmPointer(void* ctx, u8 index);
|
||||||
|
|
||||||
|
void* GetRip(void* ctx);
|
||||||
|
|
||||||
|
void IncrementRip(void* ctx, u64 length);
|
||||||
|
|
||||||
|
bool IsWriteError(void* ctx);
|
||||||
|
|
||||||
|
} // namespace Common
|
@ -3,10 +3,12 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include "common/error.h"
|
#include "common/error.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
|
#include "ntapi.h"
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
#include <mach/mach.h>
|
#include <mach/mach.h>
|
||||||
#include <mach/mach_time.h>
|
#include <mach/mach_time.h>
|
||||||
@ -102,6 +104,16 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
|||||||
SetThreadPriority(handle, windows_priority);
|
SetThreadPriority(handle, windows_priority);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void AccurateSleep(std::chrono::nanoseconds duration) {
|
||||||
|
LARGE_INTEGER interval{
|
||||||
|
.QuadPart = -1 * (duration.count() / 100u),
|
||||||
|
};
|
||||||
|
HANDLE timer = ::CreateWaitableTimer(NULL, TRUE, NULL);
|
||||||
|
SetWaitableTimer(timer, &interval, 0, NULL, NULL, 0);
|
||||||
|
WaitForSingleObject(timer, INFINITE);
|
||||||
|
::CloseHandle(timer);
|
||||||
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
||||||
@ -122,6 +134,10 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
|||||||
pthread_setschedparam(this_thread, scheduling_type, ¶ms);
|
pthread_setschedparam(this_thread, scheduling_type, ¶ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void AccurateSleep(std::chrono::nanoseconds duration) {
|
||||||
|
std::this_thread::sleep_for(duration);
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
@ -164,4 +180,22 @@ void SetCurrentThreadName(const char*) {
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
AccurateTimer::AccurateTimer(std::chrono::nanoseconds target_interval)
|
||||||
|
: target_interval(target_interval) {}
|
||||||
|
|
||||||
|
void AccurateTimer::Start() {
|
||||||
|
auto begin_sleep = std::chrono::high_resolution_clock::now();
|
||||||
|
if (total_wait.count() > 0) {
|
||||||
|
AccurateSleep(total_wait);
|
||||||
|
}
|
||||||
|
start_time = std::chrono::high_resolution_clock::now();
|
||||||
|
total_wait -= std::chrono::duration_cast<std::chrono::nanoseconds>(start_time - begin_sleep);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccurateTimer::End() {
|
||||||
|
auto now = std::chrono::high_resolution_clock::now();
|
||||||
|
total_wait +=
|
||||||
|
target_interval - std::chrono::duration_cast<std::chrono::nanoseconds>(now - start_time);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
@ -23,4 +23,18 @@ void SetCurrentThreadPriority(ThreadPriority new_priority);
|
|||||||
|
|
||||||
void SetCurrentThreadName(const char* name);
|
void SetCurrentThreadName(const char* name);
|
||||||
|
|
||||||
|
class AccurateTimer {
|
||||||
|
std::chrono::nanoseconds target_interval{};
|
||||||
|
std::chrono::nanoseconds total_wait{};
|
||||||
|
|
||||||
|
std::chrono::high_resolution_clock::time_point start_time;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AccurateTimer(std::chrono::nanoseconds target_interval);
|
||||||
|
|
||||||
|
void Start();
|
||||||
|
|
||||||
|
void End();
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
constexpr char VERSION[] = "0.2.1 WIP";
|
constexpr char VERSION[] = "0.3.1 WIP";
|
||||||
constexpr bool isRelease = false;
|
constexpr bool isRelease = false;
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
@ -72,7 +72,8 @@ struct AddressSpace::Impl {
|
|||||||
}
|
}
|
||||||
reduction += ReductionOnFail;
|
reduction += ReductionOnFail;
|
||||||
}
|
}
|
||||||
ASSERT_MSG(virtual_base, "Unable to reserve virtual address space!");
|
ASSERT_MSG(virtual_base, "Unable to reserve virtual address space: {}",
|
||||||
|
Common::GetLastErrorMsg());
|
||||||
|
|
||||||
// Take the reduction off of the system managed area, and leave the others unchanged.
|
// Take the reduction off of the system managed area, and leave the others unchanged.
|
||||||
system_managed_base = virtual_base;
|
system_managed_base = virtual_base;
|
||||||
|
@ -7,8 +7,12 @@
|
|||||||
#include <set>
|
#include <set>
|
||||||
#include <Zydis/Zydis.h>
|
#include <Zydis/Zydis.h>
|
||||||
#include <xbyak/xbyak.h>
|
#include <xbyak/xbyak.h>
|
||||||
|
#include <xbyak/xbyak_util.h>
|
||||||
#include "common/alignment.h"
|
#include "common/alignment.h"
|
||||||
|
#include "common/arch.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
#include "common/decoder.h"
|
||||||
|
#include "common/signal_context.h"
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
#include "core/signals.h"
|
#include "core/signals.h"
|
||||||
#include "core/tls.h"
|
#include "core/tls.h"
|
||||||
@ -26,6 +30,16 @@
|
|||||||
|
|
||||||
using namespace Xbyak::util;
|
using namespace Xbyak::util;
|
||||||
|
|
||||||
|
#define MAYBE_AVX(OPCODE, ...) \
|
||||||
|
[&] { \
|
||||||
|
Cpu cpu; \
|
||||||
|
if (cpu.has(Cpu::tAVX)) { \
|
||||||
|
c.v##OPCODE(__VA_ARGS__); \
|
||||||
|
} else { \
|
||||||
|
c.OPCODE(__VA_ARGS__); \
|
||||||
|
} \
|
||||||
|
}()
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
static Xbyak::Reg ZydisToXbyakRegister(const ZydisRegister reg) {
|
static Xbyak::Reg ZydisToXbyakRegister(const ZydisRegister reg) {
|
||||||
@ -586,6 +600,273 @@ static void GenerateTcbAccess(const ZydisDecodedOperand* operands, Xbyak::CodeGe
|
|||||||
|
|
||||||
#endif // __APPLE__
|
#endif // __APPLE__
|
||||||
|
|
||||||
|
static bool FilterNoSSE4a(const ZydisDecodedOperand*) {
|
||||||
|
Cpu cpu;
|
||||||
|
return !cpu.has(Cpu::tSSE4a);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GenerateEXTRQ(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) {
|
||||||
|
bool immediateForm = operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE &&
|
||||||
|
operands[2].type == ZYDIS_OPERAND_TYPE_IMMEDIATE;
|
||||||
|
|
||||||
|
ASSERT_MSG(operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER, "operand 0 must be a register");
|
||||||
|
|
||||||
|
const auto dst = ZydisToXbyakRegisterOperand(operands[0]);
|
||||||
|
|
||||||
|
ASSERT_MSG(dst.isXMM(), "operand 0 must be an XMM register");
|
||||||
|
|
||||||
|
Xbyak::Xmm xmm_dst = *reinterpret_cast<const Xbyak::Xmm*>(&dst);
|
||||||
|
|
||||||
|
if (immediateForm) {
|
||||||
|
u8 length = operands[1].imm.value.u & 0x3F;
|
||||||
|
u8 index = operands[2].imm.value.u & 0x3F;
|
||||||
|
|
||||||
|
LOG_DEBUG(Core, "Patching immediate form EXTRQ, length: {}, index: {}", length, index);
|
||||||
|
|
||||||
|
const Xbyak::Reg64 scratch1 = rax;
|
||||||
|
const Xbyak::Reg64 scratch2 = rcx;
|
||||||
|
|
||||||
|
// Set rsp to before red zone and save scratch registers
|
||||||
|
c.lea(rsp, ptr[rsp - 128]);
|
||||||
|
c.pushfq();
|
||||||
|
c.push(scratch1);
|
||||||
|
c.push(scratch2);
|
||||||
|
|
||||||
|
u64 mask;
|
||||||
|
if (length == 0) {
|
||||||
|
length = 64; // for the check below
|
||||||
|
mask = 0xFFFF'FFFF'FFFF'FFFF;
|
||||||
|
} else {
|
||||||
|
mask = (1ULL << length) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_MSG(length + index <= 64, "length + index must be less than or equal to 64.");
|
||||||
|
|
||||||
|
// Get lower qword from xmm register
|
||||||
|
MAYBE_AVX(movq, scratch1, xmm_dst);
|
||||||
|
|
||||||
|
if (index != 0) {
|
||||||
|
c.shr(scratch1, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to move mask to a register because we can't use all the possible
|
||||||
|
// immediate values with `and reg, imm32`
|
||||||
|
c.mov(scratch2, mask);
|
||||||
|
c.and_(scratch1, scratch2);
|
||||||
|
|
||||||
|
// Writeback to xmm register, extrq instruction says top 64-bits are undefined so we don't
|
||||||
|
// care to preserve them
|
||||||
|
MAYBE_AVX(movq, xmm_dst, scratch1);
|
||||||
|
|
||||||
|
c.pop(scratch2);
|
||||||
|
c.pop(scratch1);
|
||||||
|
c.popfq();
|
||||||
|
c.lea(rsp, ptr[rsp + 128]);
|
||||||
|
} else {
|
||||||
|
ASSERT_MSG(operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
|
||||||
|
operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER &&
|
||||||
|
operands[0].reg.value >= ZYDIS_REGISTER_XMM0 &&
|
||||||
|
operands[0].reg.value <= ZYDIS_REGISTER_XMM15 &&
|
||||||
|
operands[1].reg.value >= ZYDIS_REGISTER_XMM0 &&
|
||||||
|
operands[1].reg.value <= ZYDIS_REGISTER_XMM15,
|
||||||
|
"Unexpected operand types for EXTRQ instruction");
|
||||||
|
|
||||||
|
const auto src = ZydisToXbyakRegisterOperand(operands[1]);
|
||||||
|
|
||||||
|
ASSERT_MSG(src.isXMM(), "operand 1 must be an XMM register");
|
||||||
|
|
||||||
|
Xbyak::Xmm xmm_src = *reinterpret_cast<const Xbyak::Xmm*>(&src);
|
||||||
|
|
||||||
|
const Xbyak::Reg64 scratch1 = rax;
|
||||||
|
const Xbyak::Reg64 scratch2 = rcx;
|
||||||
|
const Xbyak::Reg64 mask = rdx;
|
||||||
|
|
||||||
|
Xbyak::Label length_zero, end;
|
||||||
|
|
||||||
|
c.lea(rsp, ptr[rsp - 128]);
|
||||||
|
c.pushfq();
|
||||||
|
c.push(scratch1);
|
||||||
|
c.push(scratch2);
|
||||||
|
c.push(mask);
|
||||||
|
|
||||||
|
// Construct the mask out of the length that resides in bottom 6 bits of source xmm
|
||||||
|
MAYBE_AVX(movq, scratch1, xmm_src);
|
||||||
|
c.mov(scratch2, scratch1);
|
||||||
|
c.and_(scratch2, 0x3F);
|
||||||
|
c.jz(length_zero);
|
||||||
|
|
||||||
|
// mask = (1ULL << length) - 1
|
||||||
|
c.mov(mask, 1);
|
||||||
|
c.shl(mask, cl);
|
||||||
|
c.dec(mask);
|
||||||
|
c.jmp(end);
|
||||||
|
|
||||||
|
c.L(length_zero);
|
||||||
|
c.mov(mask, 0xFFFF'FFFF'FFFF'FFFF);
|
||||||
|
|
||||||
|
c.L(end);
|
||||||
|
|
||||||
|
// Get the shift amount and store it in scratch2
|
||||||
|
c.shr(scratch1, 8);
|
||||||
|
c.and_(scratch1, 0x3F);
|
||||||
|
c.mov(scratch2, scratch1); // cl now contains the shift amount
|
||||||
|
|
||||||
|
MAYBE_AVX(movq, scratch1, xmm_dst);
|
||||||
|
c.shr(scratch1, cl);
|
||||||
|
c.and_(scratch1, mask);
|
||||||
|
MAYBE_AVX(movq, xmm_dst, scratch1);
|
||||||
|
|
||||||
|
c.pop(mask);
|
||||||
|
c.pop(scratch2);
|
||||||
|
c.pop(scratch1);
|
||||||
|
c.popfq();
|
||||||
|
c.lea(rsp, ptr[rsp + 128]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GenerateINSERTQ(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) {
|
||||||
|
bool immediateForm = operands[2].type == ZYDIS_OPERAND_TYPE_IMMEDIATE &&
|
||||||
|
operands[3].type == ZYDIS_OPERAND_TYPE_IMMEDIATE;
|
||||||
|
|
||||||
|
ASSERT_MSG(operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
|
||||||
|
operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER,
|
||||||
|
"operands 0 and 1 must be registers.");
|
||||||
|
|
||||||
|
const auto dst = ZydisToXbyakRegisterOperand(operands[0]);
|
||||||
|
const auto src = ZydisToXbyakRegisterOperand(operands[1]);
|
||||||
|
|
||||||
|
ASSERT_MSG(dst.isXMM() && src.isXMM(), "operands 0 and 1 must be xmm registers.");
|
||||||
|
|
||||||
|
Xbyak::Xmm xmm_dst = *reinterpret_cast<const Xbyak::Xmm*>(&dst);
|
||||||
|
Xbyak::Xmm xmm_src = *reinterpret_cast<const Xbyak::Xmm*>(&src);
|
||||||
|
|
||||||
|
if (immediateForm) {
|
||||||
|
u8 length = operands[2].imm.value.u & 0x3F;
|
||||||
|
u8 index = operands[3].imm.value.u & 0x3F;
|
||||||
|
|
||||||
|
const Xbyak::Reg64 scratch1 = rax;
|
||||||
|
const Xbyak::Reg64 scratch2 = rcx;
|
||||||
|
const Xbyak::Reg64 mask = rdx;
|
||||||
|
|
||||||
|
// Set rsp to before red zone and save scratch registers
|
||||||
|
c.lea(rsp, ptr[rsp - 128]);
|
||||||
|
c.pushfq();
|
||||||
|
c.push(scratch1);
|
||||||
|
c.push(scratch2);
|
||||||
|
c.push(mask);
|
||||||
|
|
||||||
|
u64 mask_value;
|
||||||
|
if (length == 0) {
|
||||||
|
length = 64; // for the check below
|
||||||
|
mask_value = 0xFFFF'FFFF'FFFF'FFFF;
|
||||||
|
} else {
|
||||||
|
mask_value = (1ULL << length) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_MSG(length + index <= 64, "length + index must be less than or equal to 64.");
|
||||||
|
|
||||||
|
MAYBE_AVX(movq, scratch1, xmm_src);
|
||||||
|
MAYBE_AVX(movq, scratch2, xmm_dst);
|
||||||
|
c.mov(mask, mask_value);
|
||||||
|
|
||||||
|
// src &= mask
|
||||||
|
c.and_(scratch1, mask);
|
||||||
|
|
||||||
|
// src <<= index
|
||||||
|
c.shl(scratch1, index);
|
||||||
|
|
||||||
|
// dst &= ~(mask << index)
|
||||||
|
mask_value = ~(mask_value << index);
|
||||||
|
c.mov(mask, mask_value);
|
||||||
|
c.and_(scratch2, mask);
|
||||||
|
|
||||||
|
// dst |= src
|
||||||
|
c.or_(scratch2, scratch1);
|
||||||
|
|
||||||
|
// Insert scratch2 into low 64 bits of dst, upper 64 bits are unaffected
|
||||||
|
Cpu cpu;
|
||||||
|
if (cpu.has(Cpu::tAVX)) {
|
||||||
|
c.vpinsrq(xmm_dst, xmm_dst, scratch2, 0);
|
||||||
|
} else {
|
||||||
|
c.pinsrq(xmm_dst, scratch2, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
c.pop(mask);
|
||||||
|
c.pop(scratch2);
|
||||||
|
c.pop(scratch1);
|
||||||
|
c.popfq();
|
||||||
|
c.lea(rsp, ptr[rsp + 128]);
|
||||||
|
} else {
|
||||||
|
ASSERT_MSG(operands[2].type == ZYDIS_OPERAND_TYPE_UNUSED &&
|
||||||
|
operands[3].type == ZYDIS_OPERAND_TYPE_UNUSED,
|
||||||
|
"operands 2 and 3 must be unused for register form.");
|
||||||
|
|
||||||
|
const Xbyak::Reg64 scratch1 = rax;
|
||||||
|
const Xbyak::Reg64 scratch2 = rcx;
|
||||||
|
const Xbyak::Reg64 index = rdx;
|
||||||
|
const Xbyak::Reg64 mask = rbx;
|
||||||
|
|
||||||
|
Xbyak::Label length_zero, end;
|
||||||
|
|
||||||
|
c.lea(rsp, ptr[rsp - 128]);
|
||||||
|
c.pushfq();
|
||||||
|
c.push(scratch1);
|
||||||
|
c.push(scratch2);
|
||||||
|
c.push(index);
|
||||||
|
c.push(mask);
|
||||||
|
|
||||||
|
// Get upper 64 bits of src and copy it to mask and index
|
||||||
|
MAYBE_AVX(pextrq, index, xmm_src, 1);
|
||||||
|
c.mov(mask, index);
|
||||||
|
|
||||||
|
// When length is 0, set it to 64
|
||||||
|
c.and_(mask, 0x3F); // mask now holds the length
|
||||||
|
c.jz(length_zero); // Check if length is 0 and set mask to all 1s if it is
|
||||||
|
|
||||||
|
// Create a mask out of the length
|
||||||
|
c.mov(cl, mask.cvt8());
|
||||||
|
c.mov(mask, 1);
|
||||||
|
c.shl(mask, cl);
|
||||||
|
c.dec(mask);
|
||||||
|
c.jmp(end);
|
||||||
|
|
||||||
|
c.L(length_zero);
|
||||||
|
c.mov(mask, 0xFFFF'FFFF'FFFF'FFFF);
|
||||||
|
|
||||||
|
c.L(end);
|
||||||
|
// Get index to insert at
|
||||||
|
c.shr(index, 8);
|
||||||
|
c.and_(index, 0x3F);
|
||||||
|
|
||||||
|
// src &= mask
|
||||||
|
MAYBE_AVX(movq, scratch1, xmm_src);
|
||||||
|
c.and_(scratch1, mask);
|
||||||
|
|
||||||
|
// mask = ~(mask << index)
|
||||||
|
c.mov(cl, index.cvt8());
|
||||||
|
c.shl(mask, cl);
|
||||||
|
c.not_(mask);
|
||||||
|
|
||||||
|
// src <<= index
|
||||||
|
c.shl(scratch1, cl);
|
||||||
|
|
||||||
|
// dst = (dst & mask) | src
|
||||||
|
MAYBE_AVX(movq, scratch2, xmm_dst);
|
||||||
|
c.and_(scratch2, mask);
|
||||||
|
c.or_(scratch2, scratch1);
|
||||||
|
|
||||||
|
// Upper 64 bits are undefined in insertq
|
||||||
|
MAYBE_AVX(movq, xmm_dst, scratch2);
|
||||||
|
|
||||||
|
c.pop(mask);
|
||||||
|
c.pop(index);
|
||||||
|
c.pop(scratch2);
|
||||||
|
c.pop(scratch1);
|
||||||
|
c.popfq();
|
||||||
|
c.lea(rsp, ptr[rsp + 128]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
using PatchFilter = bool (*)(const ZydisDecodedOperand*);
|
using PatchFilter = bool (*)(const ZydisDecodedOperand*);
|
||||||
using InstructionGenerator = void (*)(const ZydisDecodedOperand*, Xbyak::CodeGenerator&);
|
using InstructionGenerator = void (*)(const ZydisDecodedOperand*, Xbyak::CodeGenerator&);
|
||||||
struct PatchInfo {
|
struct PatchInfo {
|
||||||
@ -607,6 +888,9 @@ static const std::unordered_map<ZydisMnemonic, PatchInfo> Patches = {
|
|||||||
{ZYDIS_MNEMONIC_MOV, {FilterTcbAccess, GenerateTcbAccess, false}},
|
{ZYDIS_MNEMONIC_MOV, {FilterTcbAccess, GenerateTcbAccess, false}},
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
{ZYDIS_MNEMONIC_EXTRQ, {FilterNoSSE4a, GenerateEXTRQ, true}},
|
||||||
|
{ZYDIS_MNEMONIC_INSERTQ, {FilterNoSSE4a, GenerateINSERTQ, true}},
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
// Patches for instruction sets not supported by Rosetta 2.
|
// Patches for instruction sets not supported by Rosetta 2.
|
||||||
// BMI1
|
// BMI1
|
||||||
@ -622,7 +906,6 @@ static const std::unordered_map<ZydisMnemonic, PatchInfo> Patches = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static std::once_flag init_flag;
|
static std::once_flag init_flag;
|
||||||
static ZydisDecoder instr_decoder;
|
|
||||||
|
|
||||||
struct PatchModule {
|
struct PatchModule {
|
||||||
/// Mutex controlling access to module code regions.
|
/// Mutex controlling access to module code regions.
|
||||||
@ -663,22 +946,31 @@ static PatchModule* GetModule(const void* ptr) {
|
|||||||
static std::pair<bool, u64> TryPatch(u8* code, PatchModule* module) {
|
static std::pair<bool, u64> TryPatch(u8* code, PatchModule* module) {
|
||||||
ZydisDecodedInstruction instruction;
|
ZydisDecodedInstruction instruction;
|
||||||
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
|
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
|
||||||
const auto status =
|
const auto status = Common::Decoder::Instance()->decodeInstruction(instruction, operands, code,
|
||||||
ZydisDecoderDecodeFull(&instr_decoder, code, module->end - code, &instruction, operands);
|
module->end - code);
|
||||||
if (!ZYAN_SUCCESS(status)) {
|
if (!ZYAN_SUCCESS(status)) {
|
||||||
return std::make_pair(false, 1);
|
return std::make_pair(false, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Patches.contains(instruction.mnemonic)) {
|
if (Patches.contains(instruction.mnemonic)) {
|
||||||
const auto& patch_info = Patches.at(instruction.mnemonic);
|
const auto& patch_info = Patches.at(instruction.mnemonic);
|
||||||
|
bool needs_trampoline = patch_info.trampoline;
|
||||||
if (patch_info.filter(operands)) {
|
if (patch_info.filter(operands)) {
|
||||||
auto& patch_gen = module->patch_gen;
|
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 fall back to the illegal instruction handler,
|
||||||
|
// or 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.
|
// Reset state and move to current code position.
|
||||||
patch_gen.reset();
|
patch_gen.reset();
|
||||||
patch_gen.setSize(code - patch_gen.getCode());
|
patch_gen.setSize(code - patch_gen.getCode());
|
||||||
|
|
||||||
if (patch_info.trampoline) {
|
if (needs_trampoline) {
|
||||||
auto& trampoline_gen = module->trampoline_gen;
|
auto& trampoline_gen = module->trampoline_gen;
|
||||||
const auto trampoline_ptr = trampoline_gen.getCurr();
|
const auto trampoline_ptr = trampoline_gen.getCurr();
|
||||||
|
|
||||||
@ -714,6 +1006,153 @@ static std::pair<bool, u64> TryPatch(u8* code, PatchModule* module) {
|
|||||||
return std::make_pair(false, instruction.length);
|
return std::make_pair(false, instruction.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(ARCH_X86_64)
|
||||||
|
|
||||||
|
static bool TryExecuteIllegalInstruction(void* ctx, void* code_address) {
|
||||||
|
ZydisDecodedInstruction instruction;
|
||||||
|
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
|
||||||
|
const auto status =
|
||||||
|
Common::Decoder::Instance()->decodeInstruction(instruction, operands, code_address);
|
||||||
|
|
||||||
|
switch (instruction.mnemonic) {
|
||||||
|
case ZYDIS_MNEMONIC_EXTRQ: {
|
||||||
|
bool immediateForm = operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE &&
|
||||||
|
operands[2].type == ZYDIS_OPERAND_TYPE_IMMEDIATE;
|
||||||
|
if (immediateForm) {
|
||||||
|
LOG_CRITICAL(Core, "EXTRQ immediate form should have been patched at code address: {}",
|
||||||
|
fmt::ptr(code_address));
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
ASSERT_MSG(operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
|
||||||
|
operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER &&
|
||||||
|
operands[0].reg.value >= ZYDIS_REGISTER_XMM0 &&
|
||||||
|
operands[0].reg.value <= ZYDIS_REGISTER_XMM15 &&
|
||||||
|
operands[1].reg.value >= ZYDIS_REGISTER_XMM0 &&
|
||||||
|
operands[1].reg.value <= ZYDIS_REGISTER_XMM15,
|
||||||
|
"Unexpected operand types for EXTRQ instruction");
|
||||||
|
|
||||||
|
const auto dstIndex = operands[0].reg.value - ZYDIS_REGISTER_XMM0;
|
||||||
|
const auto srcIndex = operands[1].reg.value - ZYDIS_REGISTER_XMM0;
|
||||||
|
|
||||||
|
const auto dst = Common::GetXmmPointer(ctx, dstIndex);
|
||||||
|
const auto src = Common::GetXmmPointer(ctx, srcIndex);
|
||||||
|
|
||||||
|
u64 lowQWordSrc;
|
||||||
|
memcpy(&lowQWordSrc, src, sizeof(lowQWordSrc));
|
||||||
|
|
||||||
|
u64 lowQWordDst;
|
||||||
|
memcpy(&lowQWordDst, dst, sizeof(lowQWordDst));
|
||||||
|
|
||||||
|
u64 length = lowQWordSrc & 0x3F;
|
||||||
|
u64 mask;
|
||||||
|
if (length == 0) {
|
||||||
|
length = 64; // for the check below
|
||||||
|
mask = 0xFFFF'FFFF'FFFF'FFFF;
|
||||||
|
} else {
|
||||||
|
mask = (1ULL << length) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 index = (lowQWordSrc >> 8) & 0x3F;
|
||||||
|
if (length + index > 64) {
|
||||||
|
// Undefined behavior if length + index is bigger than 64 according to the spec,
|
||||||
|
// we'll warn and continue execution.
|
||||||
|
LOG_WARNING(Core,
|
||||||
|
"extrq at {} with length {} and index {} is bigger than 64, "
|
||||||
|
"undefined behavior",
|
||||||
|
fmt::ptr(code_address), length, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
lowQWordDst >>= index;
|
||||||
|
lowQWordDst &= mask;
|
||||||
|
|
||||||
|
memcpy(dst, &lowQWordDst, sizeof(lowQWordDst));
|
||||||
|
|
||||||
|
Common::IncrementRip(ctx, instruction.length);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ZYDIS_MNEMONIC_INSERTQ: {
|
||||||
|
bool immediateForm = operands[2].type == ZYDIS_OPERAND_TYPE_IMMEDIATE &&
|
||||||
|
operands[3].type == ZYDIS_OPERAND_TYPE_IMMEDIATE;
|
||||||
|
if (immediateForm) {
|
||||||
|
LOG_CRITICAL(Core,
|
||||||
|
"INSERTQ immediate form should have been patched at code address: {}",
|
||||||
|
fmt::ptr(code_address));
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
ASSERT_MSG(operands[2].type == ZYDIS_OPERAND_TYPE_UNUSED &&
|
||||||
|
operands[3].type == ZYDIS_OPERAND_TYPE_UNUSED,
|
||||||
|
"operands 2 and 3 must be unused for register form.");
|
||||||
|
|
||||||
|
ASSERT_MSG(operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
|
||||||
|
operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER,
|
||||||
|
"operands 0 and 1 must be registers.");
|
||||||
|
|
||||||
|
const auto dstIndex = operands[0].reg.value - ZYDIS_REGISTER_XMM0;
|
||||||
|
const auto srcIndex = operands[1].reg.value - ZYDIS_REGISTER_XMM0;
|
||||||
|
|
||||||
|
const auto dst = Common::GetXmmPointer(ctx, dstIndex);
|
||||||
|
const auto src = Common::GetXmmPointer(ctx, srcIndex);
|
||||||
|
|
||||||
|
u64 lowQWordSrc, highQWordSrc;
|
||||||
|
memcpy(&lowQWordSrc, src, sizeof(lowQWordSrc));
|
||||||
|
memcpy(&highQWordSrc, (u8*)src + 8, sizeof(highQWordSrc));
|
||||||
|
|
||||||
|
u64 lowQWordDst;
|
||||||
|
memcpy(&lowQWordDst, dst, sizeof(lowQWordDst));
|
||||||
|
|
||||||
|
u64 length = highQWordSrc & 0x3F;
|
||||||
|
u64 mask;
|
||||||
|
if (length == 0) {
|
||||||
|
length = 64; // for the check below
|
||||||
|
mask = 0xFFFF'FFFF'FFFF'FFFF;
|
||||||
|
} else {
|
||||||
|
mask = (1ULL << length) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 index = (highQWordSrc >> 8) & 0x3F;
|
||||||
|
if (length + index > 64) {
|
||||||
|
// Undefined behavior if length + index is bigger than 64 according to the spec,
|
||||||
|
// we'll warn and continue execution.
|
||||||
|
LOG_WARNING(Core,
|
||||||
|
"insertq at {} with length {} and index {} is bigger than 64, "
|
||||||
|
"undefined behavior",
|
||||||
|
fmt::ptr(code_address), length, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
lowQWordSrc &= mask;
|
||||||
|
lowQWordDst &= ~(mask << index);
|
||||||
|
lowQWordDst |= lowQWordSrc << index;
|
||||||
|
|
||||||
|
memcpy(dst, &lowQWordDst, sizeof(lowQWordDst));
|
||||||
|
|
||||||
|
Common::IncrementRip(ctx, instruction.length);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
LOG_ERROR(Core, "Unhandled illegal instruction at code address {}: {}",
|
||||||
|
fmt::ptr(code_address), ZydisMnemonicGetString(instruction.mnemonic));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
#elif defined(ARCH_ARM64)
|
||||||
|
// These functions shouldn't be needed for ARM as it will use a JIT so there's no need to patch
|
||||||
|
// instructions.
|
||||||
|
static bool TryExecuteIllegalInstruction(void*, void*) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#error "Unsupported architecture"
|
||||||
|
#endif
|
||||||
|
|
||||||
static bool TryPatchJit(void* code_address) {
|
static bool TryPatchJit(void* code_address) {
|
||||||
auto* code = static_cast<u8*>(code_address);
|
auto* code = static_cast<u8*>(code_address);
|
||||||
auto* module = GetModule(code);
|
auto* module = GetModule(code);
|
||||||
@ -746,17 +1185,19 @@ static void TryPatchAot(void* code_address, u64 code_size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool PatchesAccessViolationHandler(void* code_address, void* fault_address, bool is_write) {
|
static bool PatchesAccessViolationHandler(void* context, void* /* fault_address */) {
|
||||||
return TryPatchJit(code_address);
|
return TryPatchJit(Common::GetRip(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool PatchesIllegalInstructionHandler(void* code_address) {
|
static bool PatchesIllegalInstructionHandler(void* context) {
|
||||||
return TryPatchJit(code_address);
|
void* code_address = Common::GetRip(context);
|
||||||
|
if (!TryPatchJit(code_address)) {
|
||||||
|
return TryExecuteIllegalInstruction(context, code_address);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void PatchesInit() {
|
static void PatchesInit() {
|
||||||
ZydisDecoderInit(&instr_decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64);
|
|
||||||
|
|
||||||
if (!Patches.empty()) {
|
if (!Patches.empty()) {
|
||||||
auto* signals = Signals::Instance();
|
auto* signals = Signals::Instance();
|
||||||
// Should be called last.
|
// Should be called last.
|
||||||
|
@ -371,8 +371,7 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
|
|||||||
if (table.type == PFS_CURRENT_DIR) {
|
if (table.type == PFS_CURRENT_DIR) {
|
||||||
current_dir = extractPaths[table.inode];
|
current_dir = extractPaths[table.inode];
|
||||||
}
|
}
|
||||||
extractPaths[table.inode] =
|
extractPaths[table.inode] = current_dir / std::filesystem::path(table.name);
|
||||||
current_dir.string() / std::filesystem::path(table.name);
|
|
||||||
|
|
||||||
if (table.type == PFS_FILE || table.type == PFS_DIR) {
|
if (table.type == PFS_FILE || table.type == PFS_DIR) {
|
||||||
if (table.type == PFS_DIR) { // Create dirs.
|
if (table.type == PFS_DIR) { // Create dirs.
|
||||||
@ -402,7 +401,7 @@ void PKG::ExtractFiles(const int index) {
|
|||||||
int bsize = iNodeBuf[inode_number].Size;
|
int bsize = iNodeBuf[inode_number].Size;
|
||||||
|
|
||||||
Common::FS::IOFile inflated;
|
Common::FS::IOFile inflated;
|
||||||
inflated.Open(extractPaths[inode_number].string(), Common::FS::FileAccessMode::Write);
|
inflated.Open(extractPaths[inode_number], Common::FS::FileAccessMode::Write);
|
||||||
|
|
||||||
Common::FS::IOFile pkgFile; // Open the file for each iteration to avoid conflict.
|
Common::FS::IOFile pkgFile; // Open the file for each iteration to avoid conflict.
|
||||||
pkgFile.Open(pkgpath, Common::FS::FileAccessMode::Read);
|
pkgFile.Open(pkgpath, Common::FS::FileAccessMode::Read);
|
||||||
|
@ -102,7 +102,12 @@ bool PSF::Encode(const std::filesystem::path& filepath) const {
|
|||||||
last_write = std::filesystem::file_time_type::clock::now();
|
last_write = std::filesystem::file_time_type::clock::now();
|
||||||
|
|
||||||
const auto psf_buffer = Encode();
|
const auto psf_buffer = Encode();
|
||||||
return file.Write(psf_buffer) == psf_buffer.size();
|
const size_t written = file.Write(psf_buffer);
|
||||||
|
if (written != psf_buffer.size()) {
|
||||||
|
LOG_ERROR(Core, "Failed to write PSF file. Written {} Expected {}", written,
|
||||||
|
psf_buffer.size());
|
||||||
|
}
|
||||||
|
return written == psf_buffer.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<u8> PSF::Encode() const {
|
std::vector<u8> PSF::Encode() const {
|
||||||
|
@ -12,8 +12,8 @@
|
|||||||
#define STBI_NO_STDIO
|
#define STBI_NO_STDIO
|
||||||
#include "externals/stb_image.h"
|
#include "externals/stb_image.h"
|
||||||
|
|
||||||
bool Splash::Open(const std::string& filepath) {
|
bool Splash::Open(const std::filesystem::path& filepath) {
|
||||||
ASSERT_MSG(filepath.ends_with(".png"), "Unexpected file format passed");
|
ASSERT_MSG(filepath.stem().string() != "png", "Unexpected file format passed");
|
||||||
|
|
||||||
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
|
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
|
||||||
if (!file.IsOpen()) {
|
if (!file.IsOpen()) {
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
@ -22,7 +23,7 @@ public:
|
|||||||
Splash() = default;
|
Splash() = default;
|
||||||
~Splash() = default;
|
~Splash() = default;
|
||||||
|
|
||||||
bool Open(const std::string& filepath);
|
bool Open(const std::filesystem::path& filepath);
|
||||||
[[nodiscard]] bool IsLoaded() const {
|
[[nodiscard]] bool IsLoaded() const {
|
||||||
return img_data.size();
|
return img_data.size();
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ static void removePadding(std::vector<u8>& vec) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool TRP::Extract(const std::filesystem::path& trophyPath) {
|
bool TRP::Extract(const std::filesystem::path& trophyPath) {
|
||||||
std::string title = trophyPath.filename().string();
|
std::filesystem::path title = trophyPath.filename();
|
||||||
std::filesystem::path gameSysDir = trophyPath / "sce_sys/trophy/";
|
std::filesystem::path gameSysDir = trophyPath / "sce_sys/trophy/";
|
||||||
if (!std::filesystem::exists(gameSysDir)) {
|
if (!std::filesystem::exists(gameSysDir)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
#include "app_content.h"
|
#include "app_content.h"
|
||||||
|
#include "common/assert.h"
|
||||||
#include "common/io_file.h"
|
#include "common/io_file.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/path_util.h"
|
#include "common/path_util.h"
|
||||||
@ -246,7 +247,11 @@ int PS4_SYSV_ABI sceAppContentInitialize(const OrbisAppContentInitParam* initPar
|
|||||||
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
||||||
|
|
||||||
const auto addons_dir = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir);
|
const auto addons_dir = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir);
|
||||||
title_id = *param_sfo->GetString("TITLE_ID");
|
if (const auto value = param_sfo->GetString("TITLE_ID"); value.has_value()) {
|
||||||
|
title_id = *value;
|
||||||
|
} else {
|
||||||
|
UNREACHABLE_MSG("Failed to get TITLE_ID");
|
||||||
|
}
|
||||||
auto addon_path = addons_dir / title_id;
|
auto addon_path = addons_dir / title_id;
|
||||||
if (std::filesystem::exists(addon_path)) {
|
if (std::filesystem::exists(addon_path)) {
|
||||||
for (const auto& entry : std::filesystem::directory_iterator(addon_path)) {
|
for (const auto& entry : std::filesystem::directory_iterator(addon_path)) {
|
||||||
|
@ -161,7 +161,20 @@ struct SceAvPlayerFileReplacement {
|
|||||||
SceAvPlayerSizeFile size;
|
SceAvPlayerSizeFile size;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef void PS4_SYSV_ABI (*SceAvPlayerEventCallback)(void* p, s32 event, s32 src_id, void* data);
|
enum SceAvPlayerEvents {
|
||||||
|
SCE_AVPLAYER_STATE_STOP = 0x01,
|
||||||
|
SCE_AVPLAYER_STATE_READY = 0x02,
|
||||||
|
SCE_AVPLAYER_STATE_PLAY = 0x03,
|
||||||
|
SCE_AVPLAYER_STATE_PAUSE = 0x04,
|
||||||
|
SCE_AVPLAYER_STATE_BUFFERING = 0x05,
|
||||||
|
SCE_AVPLAYER_TIMED_TEXT_DELIVERY = 0x10,
|
||||||
|
SCE_AVPLAYER_WARNING_ID = 0x20,
|
||||||
|
SCE_AVPLAYER_ENCRYPTION = 0x30,
|
||||||
|
SCE_AVPLAYER_DRM_ERROR = 0x40
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef void PS4_SYSV_ABI (*SceAvPlayerEventCallback)(void* p, SceAvPlayerEvents event, s32 src_id,
|
||||||
|
void* data);
|
||||||
|
|
||||||
struct SceAvPlayerEventReplacement {
|
struct SceAvPlayerEventReplacement {
|
||||||
void* object_ptr;
|
void* object_ptr;
|
||||||
@ -275,18 +288,6 @@ enum SceAvPlayerAvSyncMode {
|
|||||||
|
|
||||||
typedef int PS4_SYSV_ABI (*SceAvPlayerLogCallback)(void* p, const char* format, va_list args);
|
typedef int PS4_SYSV_ABI (*SceAvPlayerLogCallback)(void* p, const char* format, va_list args);
|
||||||
|
|
||||||
enum SceAvPlayerEvents {
|
|
||||||
SCE_AVPLAYER_STATE_STOP = 0x01,
|
|
||||||
SCE_AVPLAYER_STATE_READY = 0x02,
|
|
||||||
SCE_AVPLAYER_STATE_PLAY = 0x03,
|
|
||||||
SCE_AVPLAYER_STATE_PAUSE = 0x04,
|
|
||||||
SCE_AVPLAYER_STATE_BUFFERING = 0x05,
|
|
||||||
SCE_AVPLAYER_TIMED_TEXT_DELIVERY = 0x10,
|
|
||||||
SCE_AVPLAYER_WARNING_ID = 0x20,
|
|
||||||
SCE_AVPLAYER_ENCRYPTION = 0x30,
|
|
||||||
SCE_AVPLAYER_DRM_ERROR = 0x40
|
|
||||||
};
|
|
||||||
|
|
||||||
void RegisterlibSceAvPlayer(Core::Loader::SymbolsResolver* sym);
|
void RegisterlibSceAvPlayer(Core::Loader::SymbolsResolver* sym);
|
||||||
|
|
||||||
} // namespace Libraries::AvPlayer
|
} // namespace Libraries::AvPlayer
|
||||||
|
@ -5,10 +5,11 @@
|
|||||||
#include "avplayer_source.h"
|
#include "avplayer_source.h"
|
||||||
#include "avplayer_state.h"
|
#include "avplayer_state.h"
|
||||||
|
|
||||||
|
#include "common/singleton.h"
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
|
|
||||||
#include "core/libraries/error_codes.h"
|
#include "core/libraries/error_codes.h"
|
||||||
#include "core/libraries/kernel/time_management.h"
|
#include "core/libraries/kernel/time_management.h"
|
||||||
|
#include "core/linker.h"
|
||||||
|
|
||||||
#include <magic_enum.hpp>
|
#include <magic_enum.hpp>
|
||||||
|
|
||||||
@ -16,8 +17,8 @@ namespace Libraries::AvPlayer {
|
|||||||
|
|
||||||
using namespace Kernel;
|
using namespace Kernel;
|
||||||
|
|
||||||
void PS4_SYSV_ABI AvPlayerState::AutoPlayEventCallback(void* opaque, s32 event_id, s32 source_id,
|
void PS4_SYSV_ABI AvPlayerState::AutoPlayEventCallback(void* opaque, SceAvPlayerEvents event_id,
|
||||||
void* event_data) {
|
s32 source_id, void* event_data) {
|
||||||
auto const self = reinterpret_cast<AvPlayerState*>(opaque);
|
auto const self = reinterpret_cast<AvPlayerState*>(opaque);
|
||||||
|
|
||||||
if (event_id == SCE_AVPLAYER_STATE_READY) {
|
if (event_id == SCE_AVPLAYER_STATE_READY) {
|
||||||
@ -90,7 +91,8 @@ void PS4_SYSV_ABI AvPlayerState::AutoPlayEventCallback(void* opaque, s32 event_i
|
|||||||
const auto callback = self->m_event_replacement.event_callback;
|
const auto callback = self->m_event_replacement.event_callback;
|
||||||
const auto ptr = self->m_event_replacement.object_ptr;
|
const auto ptr = self->m_event_replacement.object_ptr;
|
||||||
if (callback != nullptr) {
|
if (callback != nullptr) {
|
||||||
callback(ptr, event_id, 0, event_data);
|
auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||||
|
linker->ExecuteGuest(callback, ptr, event_id, 0, event_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,7 +367,8 @@ void AvPlayerState::EmitEvent(SceAvPlayerEvents event_id, void* event_data) {
|
|||||||
const auto callback = m_init_data.event_replacement.event_callback;
|
const auto callback = m_init_data.event_replacement.event_callback;
|
||||||
if (callback) {
|
if (callback) {
|
||||||
const auto ptr = m_init_data.event_replacement.object_ptr;
|
const auto ptr = m_init_data.event_replacement.object_ptr;
|
||||||
callback(ptr, event_id, 0, event_data);
|
auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||||
|
linker->ExecuteGuest(callback, ptr, event_id, 0, event_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,8 +39,8 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
// Event Replacement
|
// Event Replacement
|
||||||
static void PS4_SYSV_ABI AutoPlayEventCallback(void* handle, s32 event_id, s32 source_id,
|
static void PS4_SYSV_ABI AutoPlayEventCallback(void* handle, SceAvPlayerEvents event_id,
|
||||||
void* event_data);
|
s32 source_id, void* event_data);
|
||||||
|
|
||||||
void OnWarning(u32 id) override;
|
void OnWarning(u32 id) override;
|
||||||
void OnError() override;
|
void OnError() override;
|
||||||
|
@ -1,40 +1,166 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
#include <imgui.h>
|
||||||
|
#include <magic_enum.hpp>
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/libraries/error_codes.h"
|
#include "core/libraries/error_codes.h"
|
||||||
#include "core/libraries/libs.h"
|
#include "core/libraries/libs.h"
|
||||||
#include "error_codes.h"
|
#include "core/libraries/system/commondialog.h"
|
||||||
#include "error_dialog.h"
|
#include "error_dialog.h"
|
||||||
|
#include "imgui/imgui_layer.h"
|
||||||
|
#include "imgui/imgui_std.h"
|
||||||
|
|
||||||
|
static constexpr ImVec2 BUTTON_SIZE{100.0f, 30.0f};
|
||||||
|
|
||||||
namespace Libraries::ErrorDialog {
|
namespace Libraries::ErrorDialog {
|
||||||
|
|
||||||
static OrbisErrorDialogStatus g_error_dlg_status =
|
using CommonDialog::Error;
|
||||||
OrbisErrorDialogStatus::ORBIS_ERROR_DIALOG_STATUS_NONE;
|
using CommonDialog::Result;
|
||||||
|
using CommonDialog::Status;
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceErrorDialogClose() {
|
class ErrorDialogUi final : public ImGui::Layer {
|
||||||
g_error_dlg_status = OrbisErrorDialogStatus::ORBIS_ERROR_DIALOG_STATUS_FINISHED;
|
bool first_render{false};
|
||||||
return ORBIS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
OrbisErrorDialogStatus PS4_SYSV_ABI sceErrorDialogGetStatus() {
|
Status* status{nullptr};
|
||||||
return g_error_dlg_status;
|
std::string err_message{};
|
||||||
}
|
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceErrorDialogInitialize(OrbisErrorDialogParam* param) {
|
public:
|
||||||
if (g_error_dlg_status == OrbisErrorDialogStatus::ORBIS_ERROR_DIALOG_STATUS_INITIALIZED) {
|
explicit ErrorDialogUi(Status* status = nullptr, std::string err_message = "")
|
||||||
LOG_ERROR(Lib_ErrorDialog, "Error dialog is already at init mode");
|
: status(status), err_message(std::move(err_message)) {
|
||||||
return ORBIS_ERROR_DIALOG_ERROR_ALREADY_INITIALIZED;
|
if (status && *status == Status::RUNNING) {
|
||||||
|
first_render = true;
|
||||||
|
AddLayer(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
g_error_dlg_status = OrbisErrorDialogStatus::ORBIS_ERROR_DIALOG_STATUS_INITIALIZED;
|
~ErrorDialogUi() override {
|
||||||
return ORBIS_OK;
|
Finish();
|
||||||
|
}
|
||||||
|
ErrorDialogUi(const ErrorDialogUi& other) = delete;
|
||||||
|
ErrorDialogUi(ErrorDialogUi&& other) noexcept
|
||||||
|
: Layer(other), status(other.status), err_message(std::move(other.err_message)) {
|
||||||
|
other.status = nullptr;
|
||||||
|
}
|
||||||
|
ErrorDialogUi& operator=(ErrorDialogUi other) {
|
||||||
|
using std::swap;
|
||||||
|
swap(status, other.status);
|
||||||
|
swap(err_message, other.err_message);
|
||||||
|
if (status && *status == Status::RUNNING) {
|
||||||
|
first_render = true;
|
||||||
|
AddLayer(this);
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Finish() {
|
||||||
|
if (status) {
|
||||||
|
*status = Status::FINISHED;
|
||||||
|
}
|
||||||
|
status = nullptr;
|
||||||
|
RemoveLayer(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Draw() override {
|
||||||
|
using namespace ImGui;
|
||||||
|
if (status == nullptr || *status != Status::RUNNING) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto& io = GetIO();
|
||||||
|
|
||||||
|
const ImVec2 window_size{
|
||||||
|
std::min(io.DisplaySize.x, 500.0f),
|
||||||
|
std::min(io.DisplaySize.y, 300.0f),
|
||||||
|
};
|
||||||
|
|
||||||
|
CentralizeWindow();
|
||||||
|
SetNextWindowSize(window_size);
|
||||||
|
SetNextWindowCollapsed(false);
|
||||||
|
if (first_render || !io.NavActive) {
|
||||||
|
SetNextWindowFocus();
|
||||||
|
}
|
||||||
|
KeepNavHighlight();
|
||||||
|
if (Begin("Error Dialog##ErrorDialog", nullptr,
|
||||||
|
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) {
|
||||||
|
const auto ws = GetWindowSize();
|
||||||
|
|
||||||
|
DrawPrettyBackground();
|
||||||
|
const char* begin = &err_message.front();
|
||||||
|
const char* end = &err_message.back() + 1;
|
||||||
|
SetWindowFontScale(1.3f);
|
||||||
|
DrawCenteredText(begin, end,
|
||||||
|
GetContentRegionAvail() - ImVec2{0.0f, 15.0f + BUTTON_SIZE.y});
|
||||||
|
SetWindowFontScale(1.0f);
|
||||||
|
|
||||||
|
SetCursorPos({
|
||||||
|
ws.x / 2.0f - BUTTON_SIZE.x / 2.0f,
|
||||||
|
ws.y - 10.0f - BUTTON_SIZE.y,
|
||||||
|
});
|
||||||
|
if (Button("OK", BUTTON_SIZE)) {
|
||||||
|
Finish();
|
||||||
|
}
|
||||||
|
if (first_render) {
|
||||||
|
SetItemCurrentNavFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
End();
|
||||||
|
|
||||||
|
first_render = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static auto g_status = Status::NONE;
|
||||||
|
static ErrorDialogUi g_dialog_ui;
|
||||||
|
|
||||||
|
struct Param {
|
||||||
|
s32 size;
|
||||||
|
s32 errorCode;
|
||||||
|
OrbisUserServiceUserId userId;
|
||||||
|
s32 _reserved;
|
||||||
|
};
|
||||||
|
|
||||||
|
Error PS4_SYSV_ABI sceErrorDialogClose() {
|
||||||
|
LOG_DEBUG(Lib_ErrorDialog, "called");
|
||||||
|
if (g_status != Status::RUNNING) {
|
||||||
|
return Error::NOT_RUNNING;
|
||||||
|
}
|
||||||
|
g_dialog_ui.Finish();
|
||||||
|
return Error::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceErrorDialogOpen(OrbisErrorDialogParam* param) {
|
Status PS4_SYSV_ABI sceErrorDialogGetStatus() {
|
||||||
LOG_ERROR(Lib_ErrorDialog, "size = {} errorcode = {:#x} userid = {}", param->size,
|
LOG_TRACE(Lib_ErrorDialog, "called status={}", magic_enum::enum_name(g_status));
|
||||||
param->errorCode, param->userId);
|
return g_status;
|
||||||
g_error_dlg_status = OrbisErrorDialogStatus::ORBIS_ERROR_DIALOG_STATUS_RUNNING;
|
}
|
||||||
return ORBIS_OK;
|
|
||||||
|
Error PS4_SYSV_ABI sceErrorDialogInitialize() {
|
||||||
|
LOG_DEBUG(Lib_ErrorDialog, "called");
|
||||||
|
if (g_status != Status::NONE) {
|
||||||
|
return Error::ALREADY_INITIALIZED;
|
||||||
|
}
|
||||||
|
g_status = Status::INITIALIZED;
|
||||||
|
return Error::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
Error PS4_SYSV_ABI sceErrorDialogOpen(const Param* param) {
|
||||||
|
if (g_status != Status::INITIALIZED && g_status != Status::FINISHED) {
|
||||||
|
LOG_INFO(Lib_ErrorDialog, "called without initialize");
|
||||||
|
return Error::INVALID_STATE;
|
||||||
|
}
|
||||||
|
if (param == nullptr) {
|
||||||
|
LOG_DEBUG(Lib_ErrorDialog, "called param:(NULL)");
|
||||||
|
return Error::ARG_NULL;
|
||||||
|
}
|
||||||
|
const auto err = static_cast<u32>(param->errorCode);
|
||||||
|
LOG_DEBUG(Lib_ErrorDialog, "called param->errorCode = {:#x}", err);
|
||||||
|
ASSERT(param->size == sizeof(Param));
|
||||||
|
|
||||||
|
const std::string err_message = fmt::format("An error has occurred. \nCode: {:#X}", err);
|
||||||
|
g_status = Status::RUNNING;
|
||||||
|
g_dialog_ui = ErrorDialogUi{&g_status, err_message};
|
||||||
|
return Error::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceErrorDialogOpenDetail() {
|
int PS4_SYSV_ABI sceErrorDialogOpenDetail() {
|
||||||
@ -47,20 +173,21 @@ int PS4_SYSV_ABI sceErrorDialogOpenWithReport() {
|
|||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceErrorDialogTerminate() {
|
Error PS4_SYSV_ABI sceErrorDialogTerminate() {
|
||||||
if (g_error_dlg_status == OrbisErrorDialogStatus::ORBIS_ERROR_DIALOG_STATUS_NONE) {
|
LOG_DEBUG(Lib_ErrorDialog, "called");
|
||||||
LOG_ERROR(Lib_ErrorDialog, "Error dialog hasn't initialized");
|
if (g_status == Status::RUNNING) {
|
||||||
return ORBIS_ERROR_DIALOG_ERROR_NOT_INITIALIZED;
|
sceErrorDialogClose();
|
||||||
}
|
}
|
||||||
g_error_dlg_status = OrbisErrorDialogStatus::ORBIS_ERROR_DIALOG_STATUS_NONE;
|
if (g_status == Status::NONE) {
|
||||||
return ORBIS_OK;
|
return Error::NOT_INITIALIZED;
|
||||||
|
}
|
||||||
|
g_status = Status::NONE;
|
||||||
|
return Error::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
OrbisErrorDialogStatus PS4_SYSV_ABI sceErrorDialogUpdateStatus() {
|
Status PS4_SYSV_ABI sceErrorDialogUpdateStatus() {
|
||||||
// TODO when imgui dialog is done this will loop until ORBIS_ERROR_DIALOG_STATUS_FINISHED
|
LOG_TRACE(Lib_ErrorDialog, "called status={}", magic_enum::enum_name(g_status));
|
||||||
// This should be done calling sceErrorDialogClose but since we don't have a dialog we finish it
|
return g_status;
|
||||||
// here
|
|
||||||
return OrbisErrorDialogStatus::ORBIS_ERROR_DIALOG_STATUS_FINISHED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RegisterlibSceErrorDialog(Core::Loader::SymbolsResolver* sym) {
|
void RegisterlibSceErrorDialog(Core::Loader::SymbolsResolver* sym) {
|
||||||
|
@ -4,34 +4,25 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
|
#include "core/libraries/system/commondialog.h"
|
||||||
|
|
||||||
namespace Core::Loader {
|
namespace Core::Loader {
|
||||||
class SymbolsResolver;
|
class SymbolsResolver;
|
||||||
}
|
}
|
||||||
namespace Libraries::ErrorDialog {
|
namespace Libraries::ErrorDialog {
|
||||||
|
|
||||||
enum OrbisErrorDialogStatus {
|
using OrbisUserServiceUserId = s32;
|
||||||
ORBIS_ERROR_DIALOG_STATUS_NONE = 0,
|
|
||||||
ORBIS_ERROR_DIALOG_STATUS_INITIALIZED = 1,
|
|
||||||
ORBIS_ERROR_DIALOG_STATUS_RUNNING = 2,
|
|
||||||
ORBIS_ERROR_DIALOG_STATUS_FINISHED = 3
|
|
||||||
};
|
|
||||||
|
|
||||||
struct OrbisErrorDialogParam {
|
struct Param;
|
||||||
s32 size;
|
|
||||||
u32 errorCode;
|
|
||||||
s32 userId;
|
|
||||||
s32 reserved;
|
|
||||||
};
|
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceErrorDialogClose();
|
CommonDialog::Error PS4_SYSV_ABI sceErrorDialogClose();
|
||||||
OrbisErrorDialogStatus PS4_SYSV_ABI sceErrorDialogGetStatus();
|
CommonDialog::Status PS4_SYSV_ABI sceErrorDialogGetStatus();
|
||||||
int PS4_SYSV_ABI sceErrorDialogInitialize(OrbisErrorDialogParam* param);
|
CommonDialog::Error PS4_SYSV_ABI sceErrorDialogInitialize();
|
||||||
int PS4_SYSV_ABI sceErrorDialogOpen(OrbisErrorDialogParam* param);
|
CommonDialog::Error PS4_SYSV_ABI sceErrorDialogOpen(const Param* param);
|
||||||
int PS4_SYSV_ABI sceErrorDialogOpenDetail();
|
int PS4_SYSV_ABI sceErrorDialogOpenDetail();
|
||||||
int PS4_SYSV_ABI sceErrorDialogOpenWithReport();
|
int PS4_SYSV_ABI sceErrorDialogOpenWithReport();
|
||||||
int PS4_SYSV_ABI sceErrorDialogTerminate();
|
CommonDialog::Error PS4_SYSV_ABI sceErrorDialogTerminate();
|
||||||
OrbisErrorDialogStatus PS4_SYSV_ABI sceErrorDialogUpdateStatus();
|
CommonDialog::Status PS4_SYSV_ABI sceErrorDialogUpdateStatus();
|
||||||
|
|
||||||
void RegisterlibSceErrorDialog(Core::Loader::SymbolsResolver* sym);
|
void RegisterlibSceErrorDialog(Core::Loader::SymbolsResolver* sym);
|
||||||
} // namespace Libraries::ErrorDialog
|
} // namespace Libraries::ErrorDialog
|
@ -137,7 +137,7 @@ int PS4_SYSV_ABI sceKernelPollEventFlag(OrbisKernelEventFlag ef, u64 bitPattern,
|
|||||||
|
|
||||||
auto result = ef->Poll(bitPattern, wait, clear, pResultPat);
|
auto result = ef->Poll(bitPattern, wait, clear, pResultPat);
|
||||||
|
|
||||||
if (result != ORBIS_OK) {
|
if (result != ORBIS_OK && result != ORBIS_KERNEL_ERROR_EBUSY) {
|
||||||
LOG_ERROR(Kernel_Event, "returned {}", result);
|
LOG_ERROR(Kernel_Event, "returned {}", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,7 +184,7 @@ int PS4_SYSV_ABI sceKernelWaitEventFlag(OrbisKernelEventFlag ef, u64 bitPattern,
|
|||||||
|
|
||||||
u32 result = ef->Wait(bitPattern, wait, clear, pResultPat, pTimeout);
|
u32 result = ef->Wait(bitPattern, wait, clear, pResultPat, pTimeout);
|
||||||
|
|
||||||
if (result != ORBIS_OK) {
|
if (result != ORBIS_OK && result != ORBIS_KERNEL_ERROR_ETIMEDOUT) {
|
||||||
LOG_ERROR(Kernel_Event, "returned {:#x}", result);
|
LOG_ERROR(Kernel_Event, "returned {:#x}", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,6 +89,8 @@ int PS4_SYSV_ABI sceKernelOpen(const char* path, int flags, u16 mode) {
|
|||||||
}
|
}
|
||||||
// RW, then scekernelWrite is called and savedata is written just fine now.
|
// RW, then scekernelWrite is called and savedata is written just fine now.
|
||||||
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::ReadWrite);
|
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::ReadWrite);
|
||||||
|
} else if (write) {
|
||||||
|
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Write);
|
||||||
} else {
|
} else {
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
@ -288,7 +290,8 @@ int PS4_SYSV_ABI sceKernelMkdir(const char* path, u16 mode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CUSA02456: path = /aotl after sceSaveDataMount(mode = 1)
|
// CUSA02456: path = /aotl after sceSaveDataMount(mode = 1)
|
||||||
if (dir_name.empty() || !std::filesystem::create_directory(dir_name)) {
|
std::error_code ec;
|
||||||
|
if (dir_name.empty() || !std::filesystem::create_directory(dir_name, ec)) {
|
||||||
return SCE_KERNEL_ERROR_EIO;
|
return SCE_KERNEL_ERROR_EIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/debug.h"
|
#include "common/debug.h"
|
||||||
|
#include "common/elf_info.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/polyfill_thread.h"
|
#include "common/polyfill_thread.h"
|
||||||
#include "common/singleton.h"
|
#include "common/singleton.h"
|
||||||
@ -55,7 +56,7 @@ void KernelSignalRequest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void KernelServiceThread(std::stop_token stoken) {
|
static void KernelServiceThread(std::stop_token stoken) {
|
||||||
Common::SetCurrentThreadName("Kernel_ServiceThread");
|
Common::SetCurrentThreadName("shadPS4:Kernel_ServiceThread");
|
||||||
|
|
||||||
while (!stoken.stop_requested()) {
|
while (!stoken.stop_requested()) {
|
||||||
HLE_TRACE;
|
HLE_TRACE;
|
||||||
@ -185,6 +186,16 @@ void* PS4_SYSV_ABI posix_mmap(void* addr, u64 len, int prot, int flags, int fd,
|
|||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s32 PS4_SYSV_ABI sceKernelConfiguredFlexibleMemorySize(u64* sizeOut) {
|
||||||
|
if (sizeOut == nullptr) {
|
||||||
|
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* memory = Core::Memory::Instance();
|
||||||
|
*sizeOut = memory->GetTotalFlexibleSize();
|
||||||
|
return ORBIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
static uint64_t g_mspace_atomic_id_mask = 0;
|
static uint64_t g_mspace_atomic_id_mask = 0;
|
||||||
static uint64_t g_mstate_table[64] = {0};
|
static uint64_t g_mstate_table[64] = {0};
|
||||||
|
|
||||||
@ -243,8 +254,7 @@ int PS4_SYSV_ABI sceKernelConvertUtcToLocaltime(time_t time, time_t* local_time,
|
|||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(int* ver) {
|
int PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(int* ver) {
|
||||||
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
int version = Common::ElfInfo::Instance().RawFirmwareVer();
|
||||||
int version = param_sfo->GetInteger("SYSTEM_VER").value_or(0x4700000);
|
|
||||||
LOG_INFO(Kernel, "returned system version = {:#x}", version);
|
LOG_INFO(Kernel, "returned system version = {:#x}", version);
|
||||||
*ver = version;
|
*ver = version;
|
||||||
return (version > 0) ? ORBIS_OK : ORBIS_KERNEL_ERROR_EINVAL;
|
return (version > 0) ? ORBIS_OK : ORBIS_KERNEL_ERROR_EINVAL;
|
||||||
@ -403,10 +413,12 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) {
|
|||||||
|
|
||||||
// obj
|
// obj
|
||||||
LIB_OBJ("f7uOxY9mM1U", "libkernel", 1, "libkernel", 1, 1, &g_stack_chk_guard);
|
LIB_OBJ("f7uOxY9mM1U", "libkernel", 1, "libkernel", 1, 1, &g_stack_chk_guard);
|
||||||
|
|
||||||
// misc
|
// misc
|
||||||
LIB_FUNCTION("JGfTMBOdUJo", "libkernel", 1, "libkernel", 1, 1, sceKernelGetFsSandboxRandomWord);
|
LIB_FUNCTION("JGfTMBOdUJo", "libkernel", 1, "libkernel", 1, 1, sceKernelGetFsSandboxRandomWord);
|
||||||
LIB_FUNCTION("XVL8So3QJUk", "libkernel", 1, "libkernel", 1, 1, posix_connect);
|
LIB_FUNCTION("XVL8So3QJUk", "libkernel", 1, "libkernel", 1, 1, posix_connect);
|
||||||
LIB_FUNCTION("6xVpy0Fdq+I", "libkernel", 1, "libkernel", 1, 1, _sigprocmask);
|
LIB_FUNCTION("6xVpy0Fdq+I", "libkernel", 1, "libkernel", 1, 1, _sigprocmask);
|
||||||
|
|
||||||
// memory
|
// memory
|
||||||
LIB_FUNCTION("OMDRKKAZ8I4", "libkernel", 1, "libkernel", 1, 1, sceKernelDebugRaiseException);
|
LIB_FUNCTION("OMDRKKAZ8I4", "libkernel", 1, "libkernel", 1, 1, sceKernelDebugRaiseException);
|
||||||
LIB_FUNCTION("rTXw65xmLIA", "libkernel", 1, "libkernel", 1, 1, sceKernelAllocateDirectMemory);
|
LIB_FUNCTION("rTXw65xmLIA", "libkernel", 1, "libkernel", 1, 1, sceKernelAllocateDirectMemory);
|
||||||
@ -425,6 +437,7 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) {
|
|||||||
LIB_FUNCTION("WFcfL2lzido", "libkernel", 1, "libkernel", 1, 1, sceKernelQueryMemoryProtection);
|
LIB_FUNCTION("WFcfL2lzido", "libkernel", 1, "libkernel", 1, 1, sceKernelQueryMemoryProtection);
|
||||||
LIB_FUNCTION("BHouLQzh0X0", "libkernel", 1, "libkernel", 1, 1, sceKernelDirectMemoryQuery);
|
LIB_FUNCTION("BHouLQzh0X0", "libkernel", 1, "libkernel", 1, 1, sceKernelDirectMemoryQuery);
|
||||||
LIB_FUNCTION("MBuItvba6z8", "libkernel", 1, "libkernel", 1, 1, sceKernelReleaseDirectMemory);
|
LIB_FUNCTION("MBuItvba6z8", "libkernel", 1, "libkernel", 1, 1, sceKernelReleaseDirectMemory);
|
||||||
|
LIB_FUNCTION("PGhQHd-dzv8", "libkernel", 1, "libkernel", 1, 1, sceKernelMmap);
|
||||||
LIB_FUNCTION("cQke9UuBQOk", "libkernel", 1, "libkernel", 1, 1, sceKernelMunmap);
|
LIB_FUNCTION("cQke9UuBQOk", "libkernel", 1, "libkernel", 1, 1, sceKernelMunmap);
|
||||||
LIB_FUNCTION("mL8NDH86iQI", "libkernel", 1, "libkernel", 1, 1, sceKernelMapNamedFlexibleMemory);
|
LIB_FUNCTION("mL8NDH86iQI", "libkernel", 1, "libkernel", 1, 1, sceKernelMapNamedFlexibleMemory);
|
||||||
LIB_FUNCTION("aNz11fnnzi4", "libkernel", 1, "libkernel", 1, 1,
|
LIB_FUNCTION("aNz11fnnzi4", "libkernel", 1, "libkernel", 1, 1,
|
||||||
@ -442,6 +455,14 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) {
|
|||||||
LIB_FUNCTION("2SKEx6bSq-4", "libkernel", 1, "libkernel", 1, 1, sceKernelBatchMap);
|
LIB_FUNCTION("2SKEx6bSq-4", "libkernel", 1, "libkernel", 1, 1, sceKernelBatchMap);
|
||||||
LIB_FUNCTION("kBJzF8x4SyE", "libkernel", 1, "libkernel", 1, 1, sceKernelBatchMap2);
|
LIB_FUNCTION("kBJzF8x4SyE", "libkernel", 1, "libkernel", 1, 1, sceKernelBatchMap2);
|
||||||
LIB_FUNCTION("DGMG3JshrZU", "libkernel", 1, "libkernel", 1, 1, sceKernelSetVirtualRangeName);
|
LIB_FUNCTION("DGMG3JshrZU", "libkernel", 1, "libkernel", 1, 1, sceKernelSetVirtualRangeName);
|
||||||
|
LIB_FUNCTION("n1-v6FgU7MQ", "libkernel", 1, "libkernel", 1, 1,
|
||||||
|
sceKernelConfiguredFlexibleMemorySize);
|
||||||
|
|
||||||
|
// Memory pool
|
||||||
|
LIB_FUNCTION("qCSfqDILlns", "libkernel", 1, "libkernel", 1, 1, sceKernelMemoryPoolExpand);
|
||||||
|
LIB_FUNCTION("pU-QydtGcGY", "libkernel", 1, "libkernel", 1, 1, sceKernelMemoryPoolReserve);
|
||||||
|
LIB_FUNCTION("Vzl66WmfLvk", "libkernel", 1, "libkernel", 1, 1, sceKernelMemoryPoolCommit);
|
||||||
|
LIB_FUNCTION("LXo1tpFqJGs", "libkernel", 1, "libkernel", 1, 1, sceKernelMemoryPoolDecommit);
|
||||||
|
|
||||||
// equeue
|
// equeue
|
||||||
LIB_FUNCTION("D0OdFMjp46I", "libkernel", 1, "libkernel", 1, 1, sceKernelCreateEqueue);
|
LIB_FUNCTION("D0OdFMjp46I", "libkernel", 1, "libkernel", 1, 1, sceKernelCreateEqueue);
|
||||||
|
@ -228,8 +228,7 @@ int PS4_SYSV_ABI sceKernelMProtect(const void* addr, size_t size, int prot) {
|
|||||||
int PS4_SYSV_ABI sceKernelMTypeProtect(const void* addr, size_t size, int mtype, int prot) {
|
int PS4_SYSV_ABI sceKernelMTypeProtect(const void* addr, size_t size, int mtype, int prot) {
|
||||||
Core::MemoryManager* memory_manager = Core::Memory::Instance();
|
Core::MemoryManager* memory_manager = Core::Memory::Instance();
|
||||||
Core::MemoryProt protection_flags = static_cast<Core::MemoryProt>(prot);
|
Core::MemoryProt protection_flags = static_cast<Core::MemoryProt>(prot);
|
||||||
return memory_manager->MTypeProtect(std::bit_cast<VAddr>(addr), size,
|
return memory_manager->Protect(std::bit_cast<VAddr>(addr), size, protection_flags);
|
||||||
static_cast<Core::VMAType>(mtype), protection_flags);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, int flags, OrbisQueryInfo* query_info,
|
int PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, int flags, OrbisQueryInfo* query_info,
|
||||||
@ -348,4 +347,102 @@ s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, size_t len, cons
|
|||||||
memory->NameVirtualRange(std::bit_cast<VAddr>(addr), len, name);
|
memory->NameVirtualRange(std::bit_cast<VAddr>(addr), len, name);
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s32 PS4_SYSV_ABI sceKernelMemoryPoolExpand(u64 searchStart, u64 searchEnd, size_t len,
|
||||||
|
size_t alignment, u64* physAddrOut) {
|
||||||
|
if (searchStart < 0 || searchEnd <= searchStart) {
|
||||||
|
LOG_ERROR(Kernel_Vmm, "Provided address range is invalid!");
|
||||||
|
return SCE_KERNEL_ERROR_EINVAL;
|
||||||
|
}
|
||||||
|
const bool is_in_range = searchEnd - searchStart >= len;
|
||||||
|
if (len <= 0 || !Common::Is64KBAligned(len) || !is_in_range) {
|
||||||
|
LOG_ERROR(Kernel_Vmm, "Provided address range is invalid!");
|
||||||
|
return SCE_KERNEL_ERROR_EINVAL;
|
||||||
|
}
|
||||||
|
if (alignment != 0 && !Common::Is64KBAligned(alignment)) {
|
||||||
|
LOG_ERROR(Kernel_Vmm, "Alignment value is invalid!");
|
||||||
|
return SCE_KERNEL_ERROR_EINVAL;
|
||||||
|
}
|
||||||
|
if (physAddrOut == nullptr) {
|
||||||
|
LOG_ERROR(Kernel_Vmm, "Result physical address pointer is null!");
|
||||||
|
return SCE_KERNEL_ERROR_EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* memory = Core::Memory::Instance();
|
||||||
|
PAddr phys_addr = memory->PoolExpand(searchStart, searchEnd, len, alignment);
|
||||||
|
*physAddrOut = static_cast<s64>(phys_addr);
|
||||||
|
|
||||||
|
LOG_INFO(Kernel_Vmm,
|
||||||
|
"searchStart = {:#x}, searchEnd = {:#x}, len = {:#x}, alignment = {:#x}, physAddrOut "
|
||||||
|
"= {:#x}",
|
||||||
|
searchStart, searchEnd, len, alignment, phys_addr);
|
||||||
|
return ORBIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 PS4_SYSV_ABI sceKernelMemoryPoolReserve(void* addrIn, size_t len, size_t alignment, int flags,
|
||||||
|
void** addrOut) {
|
||||||
|
LOG_INFO(Kernel_Vmm, "addrIn = {}, len = {:#x}, alignment = {:#x}, flags = {:#x}",
|
||||||
|
fmt::ptr(addrIn), len, alignment, flags);
|
||||||
|
|
||||||
|
if (addrIn == nullptr) {
|
||||||
|
LOG_ERROR(Kernel_Vmm, "Address is invalid!");
|
||||||
|
return SCE_KERNEL_ERROR_EINVAL;
|
||||||
|
}
|
||||||
|
if (len == 0 || !Common::Is2MBAligned(len)) {
|
||||||
|
LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 2MB aligned!");
|
||||||
|
return SCE_KERNEL_ERROR_EINVAL;
|
||||||
|
}
|
||||||
|
if (alignment != 0) {
|
||||||
|
if ((!std::has_single_bit(alignment) && !Common::Is2MBAligned(alignment))) {
|
||||||
|
LOG_ERROR(Kernel_Vmm, "Alignment value is invalid!");
|
||||||
|
return SCE_KERNEL_ERROR_EINVAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* memory = Core::Memory::Instance();
|
||||||
|
const VAddr in_addr = reinterpret_cast<VAddr>(addrIn);
|
||||||
|
const auto map_flags = static_cast<Core::MemoryMapFlags>(flags);
|
||||||
|
memory->PoolReserve(addrOut, in_addr, len, map_flags, alignment);
|
||||||
|
|
||||||
|
return ORBIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 PS4_SYSV_ABI sceKernelMemoryPoolCommit(void* addr, size_t len, int type, int prot, int flags) {
|
||||||
|
if (addr == nullptr) {
|
||||||
|
LOG_ERROR(Kernel_Vmm, "Address is invalid!");
|
||||||
|
return SCE_KERNEL_ERROR_EINVAL;
|
||||||
|
}
|
||||||
|
if (len == 0 || !Common::Is64KBAligned(len)) {
|
||||||
|
LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 64KB aligned!");
|
||||||
|
return SCE_KERNEL_ERROR_EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO(Kernel_Vmm, "addr = {}, len = {:#x}, type = {:#x}, prot = {:#x}, flags = {:#x}",
|
||||||
|
fmt::ptr(addr), len, type, prot, flags);
|
||||||
|
|
||||||
|
const VAddr in_addr = reinterpret_cast<VAddr>(addr);
|
||||||
|
const auto mem_prot = static_cast<Core::MemoryProt>(prot);
|
||||||
|
auto* memory = Core::Memory::Instance();
|
||||||
|
return memory->PoolCommit(in_addr, len, mem_prot);
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 PS4_SYSV_ABI sceKernelMemoryPoolDecommit(void* addr, size_t len, int flags) {
|
||||||
|
if (addr == nullptr) {
|
||||||
|
LOG_ERROR(Kernel_Vmm, "Address is invalid!");
|
||||||
|
return SCE_KERNEL_ERROR_EINVAL;
|
||||||
|
}
|
||||||
|
if (len == 0 || !Common::Is64KBAligned(len)) {
|
||||||
|
LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 64KB aligned!");
|
||||||
|
return SCE_KERNEL_ERROR_EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO(Kernel_Vmm, "addr = {}, len = {:#x}, flags = {:#x}", fmt::ptr(addr), len, flags);
|
||||||
|
|
||||||
|
const VAddr pool_addr = reinterpret_cast<VAddr>(addr);
|
||||||
|
auto* memory = Core::Memory::Instance();
|
||||||
|
memory->PoolDecommit(pool_addr, len);
|
||||||
|
|
||||||
|
return ORBIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Libraries::Kernel
|
} // namespace Libraries::Kernel
|
||||||
|
@ -114,4 +114,11 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn
|
|||||||
|
|
||||||
s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, size_t len, const char* name);
|
s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, size_t len, const char* name);
|
||||||
|
|
||||||
|
s32 PS4_SYSV_ABI sceKernelMemoryPoolExpand(u64 searchStart, u64 searchEnd, size_t len,
|
||||||
|
size_t alignment, u64* physAddrOut);
|
||||||
|
s32 PS4_SYSV_ABI sceKernelMemoryPoolReserve(void* addrIn, size_t len, size_t alignment, int flags,
|
||||||
|
void** addrOut);
|
||||||
|
s32 PS4_SYSV_ABI sceKernelMemoryPoolCommit(void* addr, size_t len, int type, int prot, int flags);
|
||||||
|
s32 PS4_SYSV_ABI sceKernelMemoryPoolDecommit(void* addr, size_t len, int flags);
|
||||||
|
|
||||||
} // namespace Libraries::Kernel
|
} // namespace Libraries::Kernel
|
||||||
|
@ -6,13 +6,11 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include "common/alignment.h"
|
#include "common/alignment.h"
|
||||||
#include "common/arch.h"
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/error.h"
|
#include "common/error.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/singleton.h"
|
#include "common/singleton.h"
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
#include "core/cpu_patches.h"
|
|
||||||
#include "core/libraries/error_codes.h"
|
#include "core/libraries/error_codes.h"
|
||||||
#include "core/libraries/kernel/libkernel.h"
|
#include "core/libraries/kernel/libkernel.h"
|
||||||
#include "core/libraries/kernel/thread_management.h"
|
#include "core/libraries/kernel/thread_management.h"
|
||||||
@ -416,6 +414,7 @@ ScePthreadMutex* createMutex(ScePthreadMutex* addr) {
|
|||||||
if (addr == nullptr || *addr != nullptr) {
|
if (addr == nullptr || *addr != nullptr) {
|
||||||
return addr;
|
return addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const VAddr vaddr = reinterpret_cast<VAddr>(addr);
|
const VAddr vaddr = reinterpret_cast<VAddr>(addr);
|
||||||
std::string name = fmt::format("mutex{:#x}", vaddr);
|
std::string name = fmt::format("mutex{:#x}", vaddr);
|
||||||
scePthreadMutexInit(addr, nullptr, name.c_str());
|
scePthreadMutexInit(addr, nullptr, name.c_str());
|
||||||
@ -517,9 +516,12 @@ int PS4_SYSV_ABI scePthreadMutexattrSettype(ScePthreadMutexattr* attr, int type)
|
|||||||
ptype = PTHREAD_MUTEX_RECURSIVE;
|
ptype = PTHREAD_MUTEX_RECURSIVE;
|
||||||
break;
|
break;
|
||||||
case ORBIS_PTHREAD_MUTEX_NORMAL:
|
case ORBIS_PTHREAD_MUTEX_NORMAL:
|
||||||
case ORBIS_PTHREAD_MUTEX_ADAPTIVE:
|
|
||||||
ptype = PTHREAD_MUTEX_NORMAL;
|
ptype = PTHREAD_MUTEX_NORMAL;
|
||||||
break;
|
break;
|
||||||
|
case ORBIS_PTHREAD_MUTEX_ADAPTIVE:
|
||||||
|
LOG_ERROR(Kernel_Pthread, "Unimplemented adaptive mutex");
|
||||||
|
ptype = PTHREAD_MUTEX_ERRORCHECK;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return SCE_KERNEL_ERROR_EINVAL;
|
return SCE_KERNEL_ERROR_EINVAL;
|
||||||
}
|
}
|
||||||
@ -991,16 +993,12 @@ static void cleanup_thread(void* arg) {
|
|||||||
static void* run_thread(void* arg) {
|
static void* run_thread(void* arg) {
|
||||||
auto* thread = static_cast<ScePthread>(arg);
|
auto* thread = static_cast<ScePthread>(arg);
|
||||||
Common::SetCurrentThreadName(thread->name.c_str());
|
Common::SetCurrentThreadName(thread->name.c_str());
|
||||||
#ifdef ARCH_X86_64
|
|
||||||
Core::InitializeThreadPatchStack();
|
|
||||||
#endif
|
|
||||||
auto* linker = Common::Singleton<Core::Linker>::Instance();
|
auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||||
linker->InitTlsForThread(false);
|
|
||||||
void* ret = nullptr;
|
void* ret = nullptr;
|
||||||
g_pthread_self = thread;
|
g_pthread_self = thread;
|
||||||
pthread_cleanup_push(cleanup_thread, thread);
|
pthread_cleanup_push(cleanup_thread, thread);
|
||||||
thread->is_started = true;
|
thread->is_started = true;
|
||||||
ret = thread->entry(thread->arg);
|
ret = linker->ExecuteGuest(thread->entry, thread->arg);
|
||||||
pthread_cleanup_pop(1);
|
pthread_cleanup_pop(1);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -1512,6 +1510,10 @@ int PS4_SYSV_ABI scePthreadGetprio(ScePthread thread, int* prio) {
|
|||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
int PS4_SYSV_ABI scePthreadSetprio(ScePthread thread, int prio) {
|
int PS4_SYSV_ABI scePthreadSetprio(ScePthread thread, int prio) {
|
||||||
|
if (thread == nullptr) {
|
||||||
|
LOG_ERROR(Kernel_Pthread, "scePthreadSetprio: thread is nullptr");
|
||||||
|
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||||
|
}
|
||||||
thread->prio = prio;
|
thread->prio = prio;
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
@ -1622,6 +1624,10 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
|
|||||||
LIB_FUNCTION("upoVrzMHFeE", "libkernel", 1, "libkernel", 1, 1, scePthreadMutexTrylock);
|
LIB_FUNCTION("upoVrzMHFeE", "libkernel", 1, "libkernel", 1, 1, scePthreadMutexTrylock);
|
||||||
LIB_FUNCTION("IafI2PxcPnQ", "libkernel", 1, "libkernel", 1, 1, scePthreadMutexTimedlock);
|
LIB_FUNCTION("IafI2PxcPnQ", "libkernel", 1, "libkernel", 1, 1, scePthreadMutexTimedlock);
|
||||||
|
|
||||||
|
// scePthreadMutexInitForInternalLibc, scePthreadMutexattrInitForInternalLibc
|
||||||
|
LIB_FUNCTION("qH1gXoq71RY", "libkernel", 1, "libkernel", 1, 1, scePthreadMutexInit);
|
||||||
|
LIB_FUNCTION("n2MMpvU8igI", "libkernel", 1, "libkernel", 1, 1, scePthreadMutexattrInit);
|
||||||
|
|
||||||
// cond calls
|
// cond calls
|
||||||
LIB_FUNCTION("2Tb92quprl0", "libkernel", 1, "libkernel", 1, 1, scePthreadCondInit);
|
LIB_FUNCTION("2Tb92quprl0", "libkernel", 1, "libkernel", 1, 1, scePthreadCondInit);
|
||||||
LIB_FUNCTION("m5-2bsNfv7s", "libkernel", 1, "libkernel", 1, 1, scePthreadCondattrInit);
|
LIB_FUNCTION("m5-2bsNfv7s", "libkernel", 1, "libkernel", 1, 1, scePthreadCondattrInit);
|
||||||
|
@ -147,13 +147,20 @@ int PS4_SYSV_ABI sceKernelGettimeofday(OrbisKernelTimeval* tp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN64
|
#ifdef _WIN64
|
||||||
auto now = std::chrono::system_clock::now();
|
FILETIME filetime;
|
||||||
auto duration = now.time_since_epoch();
|
GetSystemTimeAsFileTime(&filetime);
|
||||||
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(duration);
|
|
||||||
auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(duration - seconds);
|
|
||||||
|
|
||||||
tp->tv_sec = seconds.count();
|
constexpr u64 UNIX_TIME_START = 0x295E9648864000;
|
||||||
tp->tv_usec = microsecs.count();
|
constexpr u64 TICKS_PER_SECOND = 1000000;
|
||||||
|
|
||||||
|
u64 ticks = filetime.dwHighDateTime;
|
||||||
|
ticks <<= 32;
|
||||||
|
ticks |= filetime.dwLowDateTime;
|
||||||
|
ticks /= 10;
|
||||||
|
ticks -= UNIX_TIME_START;
|
||||||
|
|
||||||
|
tp->tv_sec = ticks / TICKS_PER_SECOND;
|
||||||
|
tp->tv_usec = ticks % TICKS_PER_SECOND;
|
||||||
#else
|
#else
|
||||||
timeval tv;
|
timeval tv;
|
||||||
gettimeofday(&tv, nullptr);
|
gettimeofday(&tv, nullptr);
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/singleton.h"
|
||||||
|
#include "core/linker.h"
|
||||||
#include "net_ctl_codes.h"
|
#include "net_ctl_codes.h"
|
||||||
#include "net_ctl_obj.h"
|
#include "net_ctl_obj.h"
|
||||||
|
|
||||||
@ -57,18 +59,22 @@ s32 Libraries::NetCtl::NetCtlInternal::registerNpToolkitCallback(
|
|||||||
|
|
||||||
void Libraries::NetCtl::NetCtlInternal::checkCallback() {
|
void Libraries::NetCtl::NetCtlInternal::checkCallback() {
|
||||||
std::unique_lock lock{m_mutex};
|
std::unique_lock lock{m_mutex};
|
||||||
|
auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||||
for (auto& callback : callbacks) {
|
for (auto& callback : callbacks) {
|
||||||
if (callback.func != nullptr) {
|
if (callback.func != nullptr) {
|
||||||
callback.func(ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED, callback.arg);
|
linker->ExecuteGuest(callback.func, ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED,
|
||||||
|
callback.arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Libraries::NetCtl::NetCtlInternal::checkNpToolkitCallback() {
|
void Libraries::NetCtl::NetCtlInternal::checkNpToolkitCallback() {
|
||||||
std::unique_lock lock{m_mutex};
|
std::unique_lock lock{m_mutex};
|
||||||
|
auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||||
for (auto& callback : nptoolCallbacks) {
|
for (auto& callback : nptoolCallbacks) {
|
||||||
if (callback.func != nullptr) {
|
if (callback.func != nullptr) {
|
||||||
callback.func(ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED, callback.arg);
|
linker->ExecuteGuest(callback.func, ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED,
|
||||||
|
callback.arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -506,10 +506,10 @@ s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context,
|
|||||||
|
|
||||||
const auto trophy_dir =
|
const auto trophy_dir =
|
||||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
|
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
|
||||||
|
auto trophy_file = trophy_dir / "trophy00" / "Xml" / "TROP.XML";
|
||||||
|
|
||||||
pugi::xml_document doc;
|
pugi::xml_document doc;
|
||||||
pugi::xml_parse_result result =
|
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
|
||||||
doc.load_file((trophy_dir / "trophy00" / "Xml" / "TROP.XML").c_str());
|
|
||||||
|
|
||||||
ASSERT_MSG(result, "Couldnt parse trophy XML : {}", result.description());
|
ASSERT_MSG(result, "Couldnt parse trophy XML : {}", result.description());
|
||||||
|
|
||||||
|
@ -91,4 +91,4 @@ void TrophyUI::Draw() {
|
|||||||
End();
|
End();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/elf_info.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/libraries/libs.h"
|
#include "core/libraries/libs.h"
|
||||||
#include "core/libraries/system/commondialog.h"
|
#include "core/libraries/system/commondialog.h"
|
||||||
|
@ -5,7 +5,9 @@
|
|||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <magic_enum.hpp>
|
#include <magic_enum.hpp>
|
||||||
|
|
||||||
|
#include "common/elf_info.h"
|
||||||
#include "common/singleton.h"
|
#include "common/singleton.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
#include "core/file_sys/fs.h"
|
#include "core/file_sys/fs.h"
|
||||||
#include "core/libraries/save_data/save_instance.h"
|
#include "core/libraries/save_data/save_instance.h"
|
||||||
#include "imgui/imgui_std.h"
|
#include "imgui/imgui_std.h"
|
||||||
@ -13,6 +15,7 @@
|
|||||||
|
|
||||||
using namespace ImGui;
|
using namespace ImGui;
|
||||||
using namespace Libraries::CommonDialog;
|
using namespace Libraries::CommonDialog;
|
||||||
|
using Common::ElfInfo;
|
||||||
|
|
||||||
constexpr u32 OrbisSaveDataBlockSize = 32768; // 32 KiB
|
constexpr u32 OrbisSaveDataBlockSize = 32768; // 32 KiB
|
||||||
|
|
||||||
@ -46,11 +49,13 @@ void SaveDialogResult::CopyTo(OrbisSaveDataDialogResult& result) const {
|
|||||||
result.mode = this->mode;
|
result.mode = this->mode;
|
||||||
result.result = this->result;
|
result.result = this->result;
|
||||||
result.buttonId = this->button_id;
|
result.buttonId = this->button_id;
|
||||||
if (result.dirName != nullptr) {
|
if (mode == SaveDataDialogMode::LIST || ElfInfo::Instance().FirmwareVer() >= ElfInfo::FW_45) {
|
||||||
result.dirName->data.FromString(this->dir_name);
|
if (result.dirName != nullptr) {
|
||||||
}
|
result.dirName->data.FromString(this->dir_name);
|
||||||
if (result.param != nullptr && this->param.GetString(SaveParams::MAINTITLE).has_value()) {
|
}
|
||||||
result.param->FromSFO(this->param);
|
if (result.param != nullptr && this->param.GetString(SaveParams::MAINTITLE).has_value()) {
|
||||||
|
result.param->FromSFO(this->param);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
result.userData = this->user_data;
|
result.userData = this->user_data;
|
||||||
}
|
}
|
||||||
@ -63,8 +68,7 @@ SaveDialogState::SaveDialogState(const OrbisSaveDataDialogParam& param) {
|
|||||||
this->enable_back = {param.optionParam->back == OptionBack::ENABLE};
|
this->enable_back = {param.optionParam->back == OptionBack::ENABLE};
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string game_serial{*Common::Singleton<PSF>::Instance()->GetString("CONTENT_ID"), 7,
|
const auto& game_serial = Common::ElfInfo::Instance().GameSerial();
|
||||||
9};
|
|
||||||
|
|
||||||
const auto item = param.items;
|
const auto item = param.items;
|
||||||
this->user_id = item->userId;
|
this->user_id = item->userId;
|
||||||
@ -75,63 +79,66 @@ SaveDialogState::SaveDialogState(const OrbisSaveDataDialogParam& param) {
|
|||||||
this->title_id = item->titleId->data.to_string();
|
this->title_id = item->titleId->data.to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (u32 i = 0; i < item->dirNameNum; i++) {
|
if (item->dirName != nullptr) {
|
||||||
const auto dir_name = item->dirName[i].data.to_view();
|
for (u32 i = 0; i < item->dirNameNum; i++) {
|
||||||
|
const auto dir_name = item->dirName[i].data.to_view();
|
||||||
|
|
||||||
if (dir_name.empty()) {
|
if (dir_name.empty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto dir_path = SaveInstance::MakeDirSavePath(user_id, title_id, dir_name);
|
auto dir_path = SaveInstance::MakeDirSavePath(user_id, title_id, dir_name);
|
||||||
|
|
||||||
auto param_sfo_path = dir_path / "sce_sys" / "param.sfo";
|
auto param_sfo_path = dir_path / "sce_sys" / "param.sfo";
|
||||||
if (!std::filesystem::exists(param_sfo_path)) {
|
if (!std::filesystem::exists(param_sfo_path)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
PSF param_sfo;
|
PSF param_sfo;
|
||||||
param_sfo.Open(param_sfo_path);
|
param_sfo.Open(param_sfo_path);
|
||||||
|
|
||||||
auto last_write = param_sfo.GetLastWrite();
|
auto last_write = param_sfo.GetLastWrite();
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
auto utc_time = std::chrono::file_clock::to_utc(last_write);
|
auto utc_time = std::chrono::file_clock::to_utc(last_write);
|
||||||
#else
|
#else
|
||||||
auto utc_time = std::chrono::file_clock::to_sys(last_write);
|
auto utc_time = std::chrono::file_clock::to_sys(last_write);
|
||||||
#endif
|
#endif
|
||||||
std::string date_str = fmt::format("{:%d %b, %Y %R}", utc_time);
|
std::string date_str = fmt::format("{:%d %b, %Y %R}", utc_time);
|
||||||
|
|
||||||
size_t size = Common::FS::GetDirectorySize(dir_path);
|
size_t size = Common::FS::GetDirectorySize(dir_path);
|
||||||
std::string size_str = SpaceSizeToString(size);
|
std::string size_str = SpaceSizeToString(size);
|
||||||
|
|
||||||
auto icon_path = dir_path / "sce_sys" / "icon0.png";
|
auto icon_path = dir_path / "sce_sys" / "icon0.png";
|
||||||
RefCountedTexture icon;
|
RefCountedTexture icon;
|
||||||
if (std::filesystem::exists(icon_path)) {
|
if (std::filesystem::exists(icon_path)) {
|
||||||
icon = RefCountedTexture::DecodePngFile(icon_path);
|
icon = RefCountedTexture::DecodePngFile(icon_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_corrupted = std::filesystem::exists(dir_path / "sce_sys" / "corrupted");
|
||||||
|
|
||||||
|
this->save_list.emplace_back(Item{
|
||||||
|
.dir_name = std::string{dir_name},
|
||||||
|
.icon = icon,
|
||||||
|
|
||||||
|
.title =
|
||||||
|
std::string{param_sfo.GetString(SaveParams::MAINTITLE).value_or("Unknown")},
|
||||||
|
.subtitle = std::string{param_sfo.GetString(SaveParams::SUBTITLE).value_or("")},
|
||||||
|
.details = std::string{param_sfo.GetString(SaveParams::DETAIL).value_or("")},
|
||||||
|
.date = date_str,
|
||||||
|
.size = size_str,
|
||||||
|
.last_write = param_sfo.GetLastWrite(),
|
||||||
|
.pfo = param_sfo,
|
||||||
|
.is_corrupted = is_corrupted,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_corrupted = std::filesystem::exists(dir_path / "sce_sys" / "corrupted");
|
|
||||||
|
|
||||||
this->save_list.emplace_back(Item{
|
|
||||||
.dir_name = std::string{dir_name},
|
|
||||||
.icon = icon,
|
|
||||||
|
|
||||||
.title = std::string{*param_sfo.GetString(SaveParams::MAINTITLE)},
|
|
||||||
.subtitle = std::string{*param_sfo.GetString(SaveParams::SUBTITLE)},
|
|
||||||
.details = std::string{*param_sfo.GetString(SaveParams::DETAIL)},
|
|
||||||
.date = date_str,
|
|
||||||
.size = size_str,
|
|
||||||
.last_write = param_sfo.GetLastWrite(),
|
|
||||||
.pfo = param_sfo,
|
|
||||||
.is_corrupted = is_corrupted,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == DialogType::SAVE) {
|
if (type == DialogType::SAVE && item->newItem != nullptr) {
|
||||||
RefCountedTexture icon;
|
RefCountedTexture icon;
|
||||||
std::string title{"New Save"};
|
std::string title{"New Save"};
|
||||||
|
|
||||||
const auto new_item = item->newItem;
|
const auto new_item = item->newItem;
|
||||||
if (new_item != nullptr && new_item->iconBuf && new_item->iconSize) {
|
if (new_item->iconBuf && new_item->iconSize) {
|
||||||
auto buf = (u8*)new_item->iconBuf;
|
auto buf = (u8*)new_item->iconBuf;
|
||||||
icon = RefCountedTexture::DecodePngTexture({buf, buf + new_item->iconSize});
|
icon = RefCountedTexture::DecodePngTexture({buf, buf + new_item->iconSize});
|
||||||
} else {
|
} else {
|
||||||
@ -140,7 +147,7 @@ SaveDialogState::SaveDialogState(const OrbisSaveDataDialogParam& param) {
|
|||||||
icon = RefCountedTexture::DecodePngFile(src_icon);
|
icon = RefCountedTexture::DecodePngFile(src_icon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (new_item != nullptr && new_item->title != nullptr) {
|
if (new_item->title != nullptr) {
|
||||||
title = std::string{new_item->title};
|
title = std::string{new_item->title};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,6 +206,7 @@ SaveDialogState::SystemState::SystemState(const SaveDialogState& state,
|
|||||||
auto& sys = *param.sysMsgParam;
|
auto& sys = *param.sysMsgParam;
|
||||||
switch (sys.msgType) {
|
switch (sys.msgType) {
|
||||||
case SystemMessageType::NODATA: {
|
case SystemMessageType::NODATA: {
|
||||||
|
return_cancel = true;
|
||||||
this->msg = "There is no saved data";
|
this->msg = "There is no saved data";
|
||||||
} break;
|
} break;
|
||||||
case SystemMessageType::CONFIRM:
|
case SystemMessageType::CONFIRM:
|
||||||
@ -211,6 +219,7 @@ SaveDialogState::SystemState::SystemState(const SaveDialogState& state,
|
|||||||
M("Do you want to overwrite the existing saved data?", "##UNKNOWN##", "##UNKNOWN##");
|
M("Do you want to overwrite the existing saved data?", "##UNKNOWN##", "##UNKNOWN##");
|
||||||
break;
|
break;
|
||||||
case SystemMessageType::NOSPACE:
|
case SystemMessageType::NOSPACE:
|
||||||
|
return_cancel = true;
|
||||||
M(fmt::format(
|
M(fmt::format(
|
||||||
"There is not enough space to save the data. To continue {} free space is required.",
|
"There is not enough space to save the data. To continue {} free space is required.",
|
||||||
SpaceSizeToString(sys.value * OrbisSaveDataBlockSize)),
|
SpaceSizeToString(sys.value * OrbisSaveDataBlockSize)),
|
||||||
@ -222,12 +231,15 @@ SaveDialogState::SystemState::SystemState(const SaveDialogState& state,
|
|||||||
M("Saving...", "Loading...", "Deleting...");
|
M("Saving...", "Loading...", "Deleting...");
|
||||||
break;
|
break;
|
||||||
case SystemMessageType::FILE_CORRUPTED:
|
case SystemMessageType::FILE_CORRUPTED:
|
||||||
|
return_cancel = true;
|
||||||
this->msg = "The saved data is corrupted.";
|
this->msg = "The saved data is corrupted.";
|
||||||
break;
|
break;
|
||||||
case SystemMessageType::FINISHED:
|
case SystemMessageType::FINISHED:
|
||||||
|
return_cancel = true;
|
||||||
M("Saved successfully.", "Loading complete.", "Deletion complete.");
|
M("Saved successfully.", "Loading complete.", "Deletion complete.");
|
||||||
break;
|
break;
|
||||||
case SystemMessageType::NOSPACE_CONTINUABLE:
|
case SystemMessageType::NOSPACE_CONTINUABLE:
|
||||||
|
return_cancel = true;
|
||||||
M(fmt::format("There is not enough space to save the data. {} free space is required.",
|
M(fmt::format("There is not enough space to save the data. {} free space is required.",
|
||||||
SpaceSizeToString(sys.value * OrbisSaveDataBlockSize)),
|
SpaceSizeToString(sys.value * OrbisSaveDataBlockSize)),
|
||||||
"##UNKNOWN##", "##UNKNOWN##");
|
"##UNKNOWN##", "##UNKNOWN##");
|
||||||
@ -279,29 +291,36 @@ SaveDialogState::ErrorCodeState::ErrorCodeState(const OrbisSaveDataDialogParam&
|
|||||||
}
|
}
|
||||||
SaveDialogState::ProgressBarState::ProgressBarState(const SaveDialogState& state,
|
SaveDialogState::ProgressBarState::ProgressBarState(const SaveDialogState& state,
|
||||||
const OrbisSaveDataDialogParam& param) {
|
const OrbisSaveDataDialogParam& param) {
|
||||||
|
static auto fw_ver = ElfInfo::Instance().FirmwareVer();
|
||||||
|
|
||||||
this->progress = 0;
|
this->progress = 0;
|
||||||
|
|
||||||
auto& bar = *param.progressBarParam;
|
auto& bar = *param.progressBarParam;
|
||||||
switch (bar.sysMsgType) {
|
|
||||||
case ProgressSystemMessageType::INVALID:
|
if (bar.msg != nullptr) {
|
||||||
this->msg = bar.msg != nullptr ? std::string{bar.msg} : std::string{};
|
this->msg = std::string{bar.msg};
|
||||||
break;
|
} else {
|
||||||
case ProgressSystemMessageType::PROGRESS:
|
switch (bar.sysMsgType) {
|
||||||
switch (state.type) {
|
case ProgressSystemMessageType::INVALID:
|
||||||
case DialogType::SAVE:
|
this->msg = "";
|
||||||
this->msg = "Saving...";
|
|
||||||
break;
|
break;
|
||||||
case DialogType::LOAD:
|
case ProgressSystemMessageType::PROGRESS:
|
||||||
this->msg = "Loading...";
|
switch (state.type) {
|
||||||
|
case DialogType::SAVE:
|
||||||
|
this->msg = "Saving...";
|
||||||
|
break;
|
||||||
|
case DialogType::LOAD:
|
||||||
|
this->msg = "Loading...";
|
||||||
|
break;
|
||||||
|
case DialogType::DELETE:
|
||||||
|
this->msg = "Deleting...";
|
||||||
|
break;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case DialogType::DELETE:
|
case ProgressSystemMessageType::RESTORE:
|
||||||
this->msg = "Deleting...";
|
this->msg = "Restoring saved data...";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case ProgressSystemMessageType::RESTORE:
|
|
||||||
this->msg = "Restoring saved data...";
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,7 +397,7 @@ void SaveDialogUi::Draw() {
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
window_size = ImVec2{
|
window_size = ImVec2{
|
||||||
std::min(io.DisplaySize.x, 500.0f),
|
std::min(io.DisplaySize.x, 600.0f),
|
||||||
std::min(io.DisplaySize.y, 300.0f),
|
std::min(io.DisplaySize.y, 300.0f),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -446,7 +465,7 @@ void SaveDialogUi::Draw() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SaveDialogUi::DrawItem(int _id, const SaveDialogState::Item& item, bool clickable) {
|
void SaveDialogUi::DrawItem(int _id, const SaveDialogState::Item& item, bool clickable) {
|
||||||
constexpr auto text_spacing = 1.2f;
|
constexpr auto text_spacing = 0.95f;
|
||||||
|
|
||||||
auto& ctx = *GetCurrentContext();
|
auto& ctx = *GetCurrentContext();
|
||||||
auto& window = *ctx.CurrentWindow;
|
auto& window = *ctx.CurrentWindow;
|
||||||
@ -495,18 +514,20 @@ void SaveDialogUi::DrawItem(int _id, const SaveDialogState::Item& item, bool cli
|
|||||||
if (!item.title.empty()) {
|
if (!item.title.empty()) {
|
||||||
const char* begin = &item.title.front();
|
const char* begin = &item.title.front();
|
||||||
const char* end = &item.title.back() + 1;
|
const char* end = &item.title.back() + 1;
|
||||||
SetWindowFontScale(2.0f);
|
SetWindowFontScale(1.5f);
|
||||||
RenderText(pos + ImVec2{pos_x, pos_y}, begin, end, false);
|
RenderText(pos + ImVec2{pos_x, pos_y}, begin, end, false);
|
||||||
if (item.is_corrupted) {
|
|
||||||
float width = CalcTextSize(begin, end).x + 10.0f;
|
|
||||||
PushStyleColor(ImGuiCol_Text, 0xFF0000FF);
|
|
||||||
RenderText(pos + ImVec2{pos_x + width, pos_y}, "- Corrupted", nullptr, false);
|
|
||||||
PopStyleColor();
|
|
||||||
}
|
|
||||||
pos_y += ctx.FontSize * text_spacing;
|
pos_y += ctx.FontSize * text_spacing;
|
||||||
}
|
}
|
||||||
|
SetWindowFontScale(1.1f);
|
||||||
|
|
||||||
SetWindowFontScale(1.3f);
|
if (item.is_corrupted) {
|
||||||
|
pos_y -= ctx.FontSize * text_spacing * 0.3f;
|
||||||
|
const auto bright = (int)std::abs(std::sin(ctx.Time) * 0.15f * 255.0f);
|
||||||
|
PushStyleColor(ImGuiCol_Text, IM_COL32(bright + 216, bright, bright, 0xFF));
|
||||||
|
RenderText(pos + ImVec2{pos_x, pos_y}, "Corrupted", nullptr, false);
|
||||||
|
PopStyleColor();
|
||||||
|
pos_y += ctx.FontSize * text_spacing * 0.8f;
|
||||||
|
}
|
||||||
|
|
||||||
if (state->style == ItemStyle::TITLE_SUBTITLE_DATESIZE) {
|
if (state->style == ItemStyle::TITLE_SUBTITLE_DATESIZE) {
|
||||||
if (!item.subtitle.empty()) {
|
if (!item.subtitle.empty()) {
|
||||||
@ -620,7 +641,7 @@ void SaveDialogUi::DrawList() {
|
|||||||
SetCursorPosX(GetContentRegionAvail().x - button_size.x);
|
SetCursorPosX(GetContentRegionAvail().x - button_size.x);
|
||||||
if (Button(back, button_size)) {
|
if (Button(back, button_size)) {
|
||||||
result->dir_name.clear();
|
result->dir_name.clear();
|
||||||
Finish(ButtonId::INVALID);
|
Finish(ButtonId::INVALID, Result::USER_CANCELED);
|
||||||
}
|
}
|
||||||
if (IsKeyPressed(ImGuiKey_GamepadFaceRight)) {
|
if (IsKeyPressed(ImGuiKey_GamepadFaceRight)) {
|
||||||
SetItemCurrentNavFocus();
|
SetItemCurrentNavFocus();
|
||||||
@ -636,6 +657,8 @@ void SaveDialogUi::DrawUser() {
|
|||||||
|
|
||||||
if (!state->save_list.empty()) {
|
if (!state->save_list.empty()) {
|
||||||
DrawItem(0, state->save_list.front(), false);
|
DrawItem(0, state->save_list.front(), false);
|
||||||
|
} else if (state->new_item) {
|
||||||
|
DrawItem(0, *state->new_item, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto has_btn = btn_type != ButtonType::NONE;
|
auto has_btn = btn_type != ButtonType::NONE;
|
||||||
@ -660,7 +683,7 @@ void SaveDialogUi::DrawUser() {
|
|||||||
|
|
||||||
if (has_btn) {
|
if (has_btn) {
|
||||||
int count = 1;
|
int count = 1;
|
||||||
if (btn_type == ButtonType::YESNO || btn_type == ButtonType::ONCANCEL) {
|
if (btn_type == ButtonType::YESNO || btn_type == ButtonType::OKCANCEL) {
|
||||||
++count;
|
++count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -676,19 +699,28 @@ void SaveDialogUi::DrawUser() {
|
|||||||
}
|
}
|
||||||
SameLine();
|
SameLine();
|
||||||
if (Button("No", BUTTON_SIZE)) {
|
if (Button("No", BUTTON_SIZE)) {
|
||||||
Finish(ButtonId::NO);
|
if (ElfInfo::Instance().FirmwareVer() < ElfInfo::FW_45) {
|
||||||
|
Finish(ButtonId::INVALID, Result::USER_CANCELED);
|
||||||
|
} else {
|
||||||
|
Finish(ButtonId::NO);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) {
|
if (first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) {
|
||||||
SetItemCurrentNavFocus();
|
SetItemCurrentNavFocus();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (Button("OK", BUTTON_SIZE)) {
|
if (Button("OK", BUTTON_SIZE)) {
|
||||||
Finish(ButtonId::OK);
|
if (btn_type == ButtonType::OK &&
|
||||||
|
ElfInfo::Instance().FirmwareVer() < ElfInfo::FW_45) {
|
||||||
|
Finish(ButtonId::INVALID, Result::USER_CANCELED);
|
||||||
|
} else {
|
||||||
|
Finish(ButtonId::OK);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (first_render) {
|
if (first_render) {
|
||||||
SetItemCurrentNavFocus();
|
SetItemCurrentNavFocus();
|
||||||
}
|
}
|
||||||
if (btn_type == ButtonType::ONCANCEL) {
|
if (btn_type == ButtonType::OKCANCEL) {
|
||||||
SameLine();
|
SameLine();
|
||||||
if (Button("Cancel", BUTTON_SIZE)) {
|
if (Button("Cancel", BUTTON_SIZE)) {
|
||||||
Finish(ButtonId::INVALID, Result::USER_CANCELED);
|
Finish(ButtonId::INVALID, Result::USER_CANCELED);
|
||||||
@ -707,6 +739,8 @@ void SaveDialogUi::DrawSystemMessage() {
|
|||||||
|
|
||||||
if (!state->save_list.empty()) {
|
if (!state->save_list.empty()) {
|
||||||
DrawItem(0, state->save_list.front(), false);
|
DrawItem(0, state->save_list.front(), false);
|
||||||
|
} else if (state->new_item) {
|
||||||
|
DrawItem(0, *state->new_item, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto ws = GetWindowSize();
|
const auto ws = GetWindowSize();
|
||||||
@ -730,12 +764,20 @@ void SaveDialogUi::DrawSystemMessage() {
|
|||||||
});
|
});
|
||||||
BeginGroup();
|
BeginGroup();
|
||||||
if (Button(sys_state.show_no ? "Yes" : "OK", BUTTON_SIZE)) {
|
if (Button(sys_state.show_no ? "Yes" : "OK", BUTTON_SIZE)) {
|
||||||
Finish(ButtonId::YES);
|
if (sys_state.return_cancel && ElfInfo::Instance().FirmwareVer() < ElfInfo::FW_45) {
|
||||||
|
Finish(ButtonId::INVALID, Result::USER_CANCELED);
|
||||||
|
} else {
|
||||||
|
Finish(ButtonId::YES);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SameLine();
|
SameLine();
|
||||||
if (sys_state.show_no) {
|
if (sys_state.show_no) {
|
||||||
if (Button("No", BUTTON_SIZE)) {
|
if (Button("No", BUTTON_SIZE)) {
|
||||||
Finish(ButtonId::NO);
|
if (ElfInfo::Instance().FirmwareVer() < ElfInfo::FW_45) {
|
||||||
|
Finish(ButtonId::INVALID, Result::USER_CANCELED);
|
||||||
|
} else {
|
||||||
|
Finish(ButtonId::NO);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (sys_state.show_cancel) {
|
} else if (sys_state.show_cancel) {
|
||||||
if (Button("Cancel", BUTTON_SIZE)) {
|
if (Button("Cancel", BUTTON_SIZE)) {
|
||||||
@ -753,6 +795,8 @@ void SaveDialogUi::DrawErrorCode() {
|
|||||||
|
|
||||||
if (!state->save_list.empty()) {
|
if (!state->save_list.empty()) {
|
||||||
DrawItem(0, state->save_list.front(), false);
|
DrawItem(0, state->save_list.front(), false);
|
||||||
|
} else if (state->new_item) {
|
||||||
|
DrawItem(0, *state->new_item, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto ws = GetWindowSize();
|
const auto ws = GetWindowSize();
|
||||||
@ -768,7 +812,11 @@ void SaveDialogUi::DrawErrorCode() {
|
|||||||
ws.y - FOOTER_HEIGHT + 5.0f,
|
ws.y - FOOTER_HEIGHT + 5.0f,
|
||||||
});
|
});
|
||||||
if (Button("OK", BUTTON_SIZE)) {
|
if (Button("OK", BUTTON_SIZE)) {
|
||||||
Finish(ButtonId::OK);
|
if (ElfInfo::Instance().FirmwareVer() < ElfInfo::FW_45) {
|
||||||
|
Finish(ButtonId::INVALID, Result::USER_CANCELED);
|
||||||
|
} else {
|
||||||
|
Finish(ButtonId::OK);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (first_render) {
|
if (first_render) {
|
||||||
SetItemCurrentNavFocus();
|
SetItemCurrentNavFocus();
|
||||||
@ -782,6 +830,8 @@ void SaveDialogUi::DrawProgressBar() {
|
|||||||
|
|
||||||
if (!state->save_list.empty()) {
|
if (!state->save_list.empty()) {
|
||||||
DrawItem(0, state->save_list.front(), false);
|
DrawItem(0, state->save_list.front(), false);
|
||||||
|
} else if (state->new_item) {
|
||||||
|
DrawItem(0, *state->new_item, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& msg = bar_state.msg;
|
const auto& msg = bar_state.msg;
|
||||||
|
@ -48,7 +48,7 @@ enum class ButtonType : u32 {
|
|||||||
OK = 0,
|
OK = 0,
|
||||||
YESNO = 1,
|
YESNO = 1,
|
||||||
NONE = 2,
|
NONE = 2,
|
||||||
ONCANCEL = 3,
|
OKCANCEL = 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class UserMessageType : u32 {
|
enum class UserMessageType : u32 {
|
||||||
@ -222,6 +222,8 @@ public:
|
|||||||
bool show_no{}; // Yes instead of OK
|
bool show_no{}; // Yes instead of OK
|
||||||
bool show_cancel{};
|
bool show_cancel{};
|
||||||
|
|
||||||
|
bool return_cancel{};
|
||||||
|
|
||||||
SystemState(const SaveDialogState& state, const OrbisSaveDataDialogParam& param);
|
SystemState(const SaveDialogState& state, const OrbisSaveDataDialogParam& param);
|
||||||
};
|
};
|
||||||
struct ErrorCodeState {
|
struct ErrorCodeState {
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save
|
constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save
|
||||||
constexpr std::string_view backup_dir = "sce_backup"; // backup folder
|
constexpr std::string_view backup_dir = "sce_backup"; // backup folder
|
||||||
constexpr std::string_view backup_dir_tmp = "sce_backup_tmp"; // in-progress backup folder
|
constexpr std::string_view backup_dir_tmp = "sce_backup_tmp"; // in-progress backup folder
|
||||||
|
constexpr std::string_view backup_dir_old = "sce_backup_old"; // previous backup folder
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
@ -26,6 +27,8 @@ namespace Libraries::SaveData::Backup {
|
|||||||
static std::jthread g_backup_thread;
|
static std::jthread g_backup_thread;
|
||||||
static std::counting_semaphore g_backup_thread_semaphore{0};
|
static std::counting_semaphore g_backup_thread_semaphore{0};
|
||||||
|
|
||||||
|
static std::mutex g_backup_running_mutex;
|
||||||
|
|
||||||
static std::mutex g_backup_queue_mutex;
|
static std::mutex g_backup_queue_mutex;
|
||||||
static std::deque<BackupRequest> g_backup_queue;
|
static std::deque<BackupRequest> g_backup_queue;
|
||||||
static std::deque<BackupRequest> g_result_queue;
|
static std::deque<BackupRequest> g_result_queue;
|
||||||
@ -34,59 +37,91 @@ static std::atomic_int g_backup_progress = 0;
|
|||||||
static std::atomic g_backup_status = WorkerStatus::NotStarted;
|
static std::atomic g_backup_status = WorkerStatus::NotStarted;
|
||||||
|
|
||||||
static void backup(const std::filesystem::path& dir_name) {
|
static void backup(const std::filesystem::path& dir_name) {
|
||||||
|
std::unique_lock lk{g_backup_running_mutex};
|
||||||
if (!fs::exists(dir_name)) {
|
if (!fs::exists(dir_name)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto backup_dir = dir_name / ::backup_dir;
|
||||||
|
const auto backup_dir_tmp = dir_name / ::backup_dir_tmp;
|
||||||
|
const auto backup_dir_old = dir_name / ::backup_dir_old;
|
||||||
|
|
||||||
|
fs::remove_all(backup_dir_tmp);
|
||||||
|
fs::remove_all(backup_dir_old);
|
||||||
|
|
||||||
std::vector<std::filesystem::path> backup_files;
|
std::vector<std::filesystem::path> backup_files;
|
||||||
for (const auto& entry : fs::directory_iterator(dir_name)) {
|
for (const auto& entry : fs::directory_iterator(dir_name)) {
|
||||||
const auto filename = entry.path().filename();
|
const auto filename = entry.path().filename();
|
||||||
if (filename != backup_dir && filename != backup_dir_tmp) {
|
if (filename != ::backup_dir) {
|
||||||
backup_files.push_back(entry.path());
|
backup_files.push_back(entry.path());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const auto backup_dir = dir_name / ::backup_dir;
|
|
||||||
const auto backup_dir_tmp = dir_name / ::backup_dir_tmp;
|
|
||||||
|
|
||||||
g_backup_progress = 0;
|
g_backup_progress = 0;
|
||||||
|
|
||||||
int total_count = static_cast<int>(backup_files.size());
|
int total_count = static_cast<int>(backup_files.size());
|
||||||
int current_count = 0;
|
int current_count = 0;
|
||||||
|
|
||||||
fs::remove_all(backup_dir_tmp);
|
|
||||||
fs::create_directory(backup_dir_tmp);
|
fs::create_directory(backup_dir_tmp);
|
||||||
for (const auto& file : backup_files) {
|
for (const auto& file : backup_files) {
|
||||||
fs::copy(file, backup_dir_tmp / file.filename(), fs::copy_options::recursive);
|
fs::copy(file, backup_dir_tmp / file.filename(), fs::copy_options::recursive);
|
||||||
current_count++;
|
current_count++;
|
||||||
g_backup_progress = current_count * 100 / total_count;
|
g_backup_progress = current_count * 100 / total_count;
|
||||||
}
|
}
|
||||||
bool has_existing = fs::exists(backup_dir);
|
bool has_existing_backup = fs::exists(backup_dir);
|
||||||
if (has_existing) {
|
if (has_existing_backup) {
|
||||||
fs::rename(backup_dir, dir_name / "sce_backup_old");
|
fs::rename(backup_dir, backup_dir_old);
|
||||||
}
|
}
|
||||||
fs::rename(backup_dir_tmp, backup_dir);
|
fs::rename(backup_dir_tmp, backup_dir);
|
||||||
if (has_existing) {
|
if (has_existing_backup) {
|
||||||
fs::remove_all(dir_name / "sce_backup_old");
|
fs::remove_all(backup_dir_old);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void BackupThreadBody() {
|
static void BackupThreadBody() {
|
||||||
Common::SetCurrentThreadName("SaveData_BackupThread");
|
Common::SetCurrentThreadName("shadPS4:SaveData_BackupThread");
|
||||||
while (true) {
|
while (g_backup_status != WorkerStatus::Stopping) {
|
||||||
g_backup_status = WorkerStatus::Waiting;
|
g_backup_status = WorkerStatus::Waiting;
|
||||||
g_backup_thread_semaphore.acquire();
|
|
||||||
|
bool wait;
|
||||||
BackupRequest req;
|
BackupRequest req;
|
||||||
{
|
{
|
||||||
std::scoped_lock lk{g_backup_queue_mutex};
|
std::scoped_lock lk{g_backup_queue_mutex};
|
||||||
req = g_backup_queue.front();
|
wait = g_backup_queue.empty();
|
||||||
|
if (!wait) {
|
||||||
|
req = g_backup_queue.front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (wait) {
|
||||||
|
g_backup_thread_semaphore.acquire();
|
||||||
|
{
|
||||||
|
std::scoped_lock lk{g_backup_queue_mutex};
|
||||||
|
if (g_backup_queue.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
req = g_backup_queue.front();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (req.save_path.empty()) {
|
if (req.save_path.empty()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
g_backup_status = WorkerStatus::Running;
|
g_backup_status = WorkerStatus::Running;
|
||||||
LOG_INFO(Lib_SaveData, "Backing up the following directory: {}", req.save_path.string());
|
|
||||||
backup(req.save_path);
|
LOG_INFO(Lib_SaveData, "Backing up the following directory: {}",
|
||||||
|
fmt::UTF(req.save_path.u8string()));
|
||||||
|
try {
|
||||||
|
backup(req.save_path);
|
||||||
|
} catch (const std::filesystem::filesystem_error& err) {
|
||||||
|
LOG_ERROR(Lib_SaveData, "Failed to backup {}: {}", fmt::UTF(req.save_path.u8string()),
|
||||||
|
err.what());
|
||||||
|
}
|
||||||
LOG_DEBUG(Lib_SaveData, "Backing up the following directory: {} finished",
|
LOG_DEBUG(Lib_SaveData, "Backing up the following directory: {} finished",
|
||||||
req.save_path.string());
|
fmt::UTF(req.save_path.u8string()));
|
||||||
|
{
|
||||||
|
std::scoped_lock lk{g_backup_queue_mutex};
|
||||||
|
g_backup_queue.front().done = true;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(5)); // Don't backup too often
|
||||||
{
|
{
|
||||||
std::scoped_lock lk{g_backup_queue_mutex};
|
std::scoped_lock lk{g_backup_queue_mutex};
|
||||||
g_backup_queue.pop_front();
|
g_backup_queue.pop_front();
|
||||||
@ -104,8 +139,8 @@ void StartThread() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LOG_DEBUG(Lib_SaveData, "Starting backup thread");
|
LOG_DEBUG(Lib_SaveData, "Starting backup thread");
|
||||||
g_backup_thread = std::jthread{BackupThreadBody};
|
|
||||||
g_backup_status = WorkerStatus::Waiting;
|
g_backup_status = WorkerStatus::Waiting;
|
||||||
|
g_backup_thread = std::jthread{BackupThreadBody};
|
||||||
}
|
}
|
||||||
|
|
||||||
void StopThread() {
|
void StopThread() {
|
||||||
@ -127,11 +162,17 @@ bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id,
|
|||||||
|
|
||||||
if (g_backup_status != WorkerStatus::Waiting && g_backup_status != WorkerStatus::Running) {
|
if (g_backup_status != WorkerStatus::Waiting && g_backup_status != WorkerStatus::Running) {
|
||||||
LOG_ERROR(Lib_SaveData, "Called backup while status is {}. Backup request to {} ignored",
|
LOG_ERROR(Lib_SaveData, "Called backup while status is {}. Backup request to {} ignored",
|
||||||
magic_enum::enum_name(g_backup_status.load()), save_path.string());
|
magic_enum::enum_name(g_backup_status.load()), fmt::UTF(save_path.u8string()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::scoped_lock lk{g_backup_queue_mutex};
|
std::scoped_lock lk{g_backup_queue_mutex};
|
||||||
|
for (const auto& it : g_backup_queue) {
|
||||||
|
if (it.dir_name == dir_name) {
|
||||||
|
LOG_TRACE(Lib_SaveData, "Backup request to {} ignored. Already queued", dir_name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
g_backup_queue.push_back(BackupRequest{
|
g_backup_queue.push_back(BackupRequest{
|
||||||
.user_id = user_id,
|
.user_id = user_id,
|
||||||
.title_id = std::string{title_id},
|
.title_id = std::string{title_id},
|
||||||
@ -145,7 +186,8 @@ bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id,
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Restore(const std::filesystem::path& save_path) {
|
bool Restore(const std::filesystem::path& save_path) {
|
||||||
LOG_INFO(Lib_SaveData, "Restoring backup for {}", save_path.string());
|
LOG_INFO(Lib_SaveData, "Restoring backup for {}", fmt::UTF(save_path.u8string()));
|
||||||
|
std::unique_lock lk{g_backup_running_mutex};
|
||||||
if (!fs::exists(save_path) || !fs::exists(save_path / backup_dir)) {
|
if (!fs::exists(save_path) || !fs::exists(save_path / backup_dir)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -170,8 +212,9 @@ WorkerStatus GetWorkerStatus() {
|
|||||||
|
|
||||||
bool IsBackupExecutingFor(const std::filesystem::path& save_path) {
|
bool IsBackupExecutingFor(const std::filesystem::path& save_path) {
|
||||||
std::scoped_lock lk{g_backup_queue_mutex};
|
std::scoped_lock lk{g_backup_queue_mutex};
|
||||||
return std::ranges::find(g_backup_queue, save_path,
|
const auto& it =
|
||||||
[](const auto& v) { return v.save_path; }) != g_backup_queue.end();
|
std::ranges::find(g_backup_queue, save_path, [](const auto& v) { return v.save_path; });
|
||||||
|
return it != g_backup_queue.end() && !it->done;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::filesystem::path MakeBackupPath(const std::filesystem::path& save_path) {
|
std::filesystem::path MakeBackupPath(const std::filesystem::path& save_path) {
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
|
|
||||||
@ -27,6 +28,8 @@ enum class OrbisSaveDataEventType : u32 {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct BackupRequest {
|
struct BackupRequest {
|
||||||
|
bool done{};
|
||||||
|
|
||||||
OrbisUserServiceUserId user_id{};
|
OrbisUserServiceUserId user_id{};
|
||||||
std::string title_id{};
|
std::string title_id{};
|
||||||
std::string dir_name{};
|
std::string dir_name{};
|
||||||
|
@ -66,7 +66,7 @@ static void SaveFileSafe(void* buf, size_t count, const std::filesystem::path& p
|
|||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] void SaveThreadLoop() {
|
[[noreturn]] void SaveThreadLoop() {
|
||||||
Common::SetCurrentThreadName("SaveData_SaveDataMemoryThread");
|
Common::SetCurrentThreadName("shadPS4:SaveData_SaveDataMemoryThread");
|
||||||
std::mutex mtx;
|
std::mutex mtx;
|
||||||
while (true) {
|
while (true) {
|
||||||
{
|
{
|
||||||
@ -77,7 +77,7 @@ static void SaveFileSafe(void* buf, size_t count, const std::filesystem::path& p
|
|||||||
g_saving_memory = true;
|
g_saving_memory = true;
|
||||||
std::scoped_lock lk{g_saving_memory_mutex};
|
std::scoped_lock lk{g_saving_memory_mutex};
|
||||||
try {
|
try {
|
||||||
LOG_DEBUG(Lib_SaveData, "Saving save data memory {}", g_save_path.string());
|
LOG_DEBUG(Lib_SaveData, "Saving save data memory {}", fmt::UTF(g_save_path.u8string()));
|
||||||
|
|
||||||
if (g_memory_dirty) {
|
if (g_memory_dirty) {
|
||||||
g_memory_dirty = false;
|
g_memory_dirty = false;
|
||||||
@ -163,7 +163,8 @@ size_t CreateSaveMemory(size_t memory_size) {
|
|||||||
|
|
||||||
bool ok = g_param_sfo.Open(g_param_sfo_path);
|
bool ok = g_param_sfo.Open(g_param_sfo_path);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOG_ERROR(Lib_SaveData, "Failed to open SFO at {}", g_param_sfo_path.string());
|
LOG_ERROR(Lib_SaveData, "Failed to open SFO at {}",
|
||||||
|
fmt::UTF(g_param_sfo_path.u8string()));
|
||||||
throw std::filesystem::filesystem_error(
|
throw std::filesystem::filesystem_error(
|
||||||
"failed to open SFO", g_param_sfo_path,
|
"failed to open SFO", g_param_sfo_path,
|
||||||
std::make_error_code(std::errc::illegal_byte_sequence));
|
std::make_error_code(std::errc::illegal_byte_sequence));
|
||||||
@ -190,14 +191,19 @@ void SetIcon(void* buf, size_t buf_size) {
|
|||||||
if (buf == nullptr) {
|
if (buf == nullptr) {
|
||||||
const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png");
|
const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png");
|
||||||
if (fs::exists(src_icon)) {
|
if (fs::exists(src_icon)) {
|
||||||
|
if (fs::exists(g_icon_path)) {
|
||||||
|
fs::remove(g_icon_path);
|
||||||
|
}
|
||||||
fs::copy_file(src_icon, g_icon_path);
|
fs::copy_file(src_icon, g_icon_path);
|
||||||
}
|
}
|
||||||
IOFile file(g_icon_path, Common::FS::FileAccessMode::Read);
|
if (fs::exists(g_icon_path)) {
|
||||||
size_t size = file.GetSize();
|
IOFile file(g_icon_path, Common::FS::FileAccessMode::Read);
|
||||||
file.Seek(0);
|
size_t size = file.GetSize();
|
||||||
g_icon_memory.resize(size);
|
file.Seek(0);
|
||||||
file.ReadRaw<u8>(g_icon_memory.data(), size);
|
g_icon_memory.resize(size);
|
||||||
file.Close();
|
file.ReadRaw<u8>(g_icon_memory.data(), size);
|
||||||
|
file.Close();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
g_icon_memory.resize(buf_size);
|
g_icon_memory.resize(buf_size);
|
||||||
std::memcpy(g_icon_memory.data(), buf, buf_size);
|
std::memcpy(g_icon_memory.data(), buf, buf_size);
|
||||||
@ -263,9 +269,6 @@ bool TriggerSave() {
|
|||||||
|
|
||||||
void ReadMemory(void* buf, size_t buf_size, int64_t offset) {
|
void ReadMemory(void* buf, size_t buf_size, int64_t offset) {
|
||||||
std::scoped_lock lk{g_saving_memory_mutex};
|
std::scoped_lock lk{g_saving_memory_mutex};
|
||||||
if (offset > g_save_memory.size()) {
|
|
||||||
UNREACHABLE_MSG("ReadMemory out of bounds");
|
|
||||||
}
|
|
||||||
if (offset + buf_size > g_save_memory.size()) {
|
if (offset + buf_size > g_save_memory.size()) {
|
||||||
UNREACHABLE_MSG("ReadMemory out of bounds");
|
UNREACHABLE_MSG("ReadMemory out of bounds");
|
||||||
}
|
}
|
||||||
@ -274,11 +277,8 @@ void ReadMemory(void* buf, size_t buf_size, int64_t offset) {
|
|||||||
|
|
||||||
void WriteMemory(void* buf, size_t buf_size, int64_t offset) {
|
void WriteMemory(void* buf, size_t buf_size, int64_t offset) {
|
||||||
std::scoped_lock lk{g_saving_memory_mutex};
|
std::scoped_lock lk{g_saving_memory_mutex};
|
||||||
if (offset > g_save_memory.size()) {
|
|
||||||
UNREACHABLE_MSG("WriteMemory out of bounds");
|
|
||||||
}
|
|
||||||
if (offset + buf_size > g_save_memory.size()) {
|
if (offset + buf_size > g_save_memory.size()) {
|
||||||
UNREACHABLE_MSG("WriteMemory out of bounds");
|
g_save_memory.resize(offset + buf_size);
|
||||||
}
|
}
|
||||||
std::memcpy(g_save_memory.data() + offset, buf, buf_size);
|
std::memcpy(g_save_memory.data() + offset, buf, buf_size);
|
||||||
g_memory_dirty = true;
|
g_memory_dirty = true;
|
||||||
|
@ -9,10 +9,10 @@
|
|||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/cstring.h"
|
#include "common/cstring.h"
|
||||||
|
#include "common/elf_info.h"
|
||||||
#include "common/enum.h"
|
#include "common/enum.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/path_util.h"
|
#include "common/path_util.h"
|
||||||
#include "common/singleton.h"
|
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
#include "core/file_format/psf.h"
|
#include "core/file_format/psf.h"
|
||||||
#include "core/file_sys/fs.h"
|
#include "core/file_sys/fs.h"
|
||||||
@ -28,11 +28,13 @@ namespace fs = std::filesystem;
|
|||||||
namespace chrono = std::chrono;
|
namespace chrono = std::chrono;
|
||||||
|
|
||||||
using Common::CString;
|
using Common::CString;
|
||||||
|
using Common::ElfInfo;
|
||||||
|
|
||||||
namespace Libraries::SaveData {
|
namespace Libraries::SaveData {
|
||||||
|
|
||||||
enum class Error : u32 {
|
enum class Error : u32 {
|
||||||
OK = 0,
|
OK = 0,
|
||||||
|
USER_SERVICE_NOT_INITIALIZED = 0x80960002,
|
||||||
PARAMETER = 0x809F0000,
|
PARAMETER = 0x809F0000,
|
||||||
NOT_INITIALIZED = 0x809F0001,
|
NOT_INITIALIZED = 0x809F0001,
|
||||||
OUT_OF_MEMORY = 0x809F0002,
|
OUT_OF_MEMORY = 0x809F0002,
|
||||||
@ -191,7 +193,9 @@ struct OrbisSaveDataMemorySetup2 {
|
|||||||
OrbisUserServiceUserId userId;
|
OrbisUserServiceUserId userId;
|
||||||
size_t memorySize;
|
size_t memorySize;
|
||||||
size_t iconMemorySize;
|
size_t iconMemorySize;
|
||||||
|
// +4.5
|
||||||
const OrbisSaveDataParam* initParam;
|
const OrbisSaveDataParam* initParam;
|
||||||
|
// +4.5
|
||||||
const OrbisSaveDataIcon* initIcon;
|
const OrbisSaveDataIcon* initIcon;
|
||||||
std::array<u8, 24> _reserved;
|
std::array<u8, 24> _reserved;
|
||||||
};
|
};
|
||||||
@ -241,6 +245,7 @@ struct OrbisSaveDataMountResult {
|
|||||||
OrbisSaveDataMountPoint mount_point;
|
OrbisSaveDataMountPoint mount_point;
|
||||||
OrbisSaveDataBlocks required_blocks;
|
OrbisSaveDataBlocks required_blocks;
|
||||||
u32 _unused;
|
u32 _unused;
|
||||||
|
// +4.5
|
||||||
OrbisSaveDataMountStatus mount_status;
|
OrbisSaveDataMountStatus mount_status;
|
||||||
std::array<u8, 28> _reserved;
|
std::array<u8, 28> _reserved;
|
||||||
s32 : 32;
|
s32 : 32;
|
||||||
@ -278,8 +283,11 @@ struct OrbisSaveDataDirNameSearchResult {
|
|||||||
int : 32;
|
int : 32;
|
||||||
OrbisSaveDataDirName* dirNames;
|
OrbisSaveDataDirName* dirNames;
|
||||||
u32 dirNamesNum;
|
u32 dirNamesNum;
|
||||||
|
// +1.7
|
||||||
u32 setNum;
|
u32 setNum;
|
||||||
|
// +1.7
|
||||||
OrbisSaveDataParam* params;
|
OrbisSaveDataParam* params;
|
||||||
|
// +2.5
|
||||||
OrbisSaveDataSearchInfo* infos;
|
OrbisSaveDataSearchInfo* infos;
|
||||||
std::array<u8, 12> _reserved;
|
std::array<u8, 12> _reserved;
|
||||||
int : 32;
|
int : 32;
|
||||||
@ -303,12 +311,13 @@ struct OrbisSaveDataEvent {
|
|||||||
|
|
||||||
static bool g_initialized = false;
|
static bool g_initialized = false;
|
||||||
static std::string g_game_serial;
|
static std::string g_game_serial;
|
||||||
|
static u32 g_fw_ver;
|
||||||
static std::array<std::optional<SaveInstance>, 16> g_mount_slots;
|
static std::array<std::optional<SaveInstance>, 16> g_mount_slots;
|
||||||
|
|
||||||
static void initialize() {
|
static void initialize() {
|
||||||
g_initialized = true;
|
g_initialized = true;
|
||||||
static auto* param_sfo = Common::Singleton<PSF>::Instance();
|
g_game_serial = ElfInfo::Instance().GameSerial();
|
||||||
g_game_serial = std::string(*param_sfo->GetString("CONTENT_ID"), 7, 9);
|
g_fw_ver = ElfInfo::Instance().FirmwareVer();
|
||||||
}
|
}
|
||||||
|
|
||||||
// game_00other | game*other
|
// game_00other | game*other
|
||||||
@ -337,6 +346,16 @@ static bool match(std::string_view str, std::string_view pattern) {
|
|||||||
return str_it == str.end() && pat_it == pattern.end();
|
return str_it == str.end() && pat_it == pattern.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Error setNotInitializedError() {
|
||||||
|
if (g_fw_ver < ElfInfo::FW_20) {
|
||||||
|
return Error::INTERNAL;
|
||||||
|
}
|
||||||
|
if (g_fw_ver < ElfInfo::FW_25) {
|
||||||
|
return Error::USER_SERVICE_NOT_INITIALIZED;
|
||||||
|
}
|
||||||
|
return Error::NOT_INITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
|
static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
|
||||||
OrbisSaveDataMountResult* mount_result) {
|
OrbisSaveDataMountResult* mount_result) {
|
||||||
|
|
||||||
@ -352,7 +371,7 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
|
|||||||
{
|
{
|
||||||
const auto save_path = SaveInstance::MakeDirSavePath(mount_info->userId, g_game_serial,
|
const auto save_path = SaveInstance::MakeDirSavePath(mount_info->userId, g_game_serial,
|
||||||
mount_info->dirName->data);
|
mount_info->dirName->data);
|
||||||
if (Backup::IsBackupExecutingFor(save_path)) {
|
if (Backup::IsBackupExecutingFor(save_path) && g_fw_ver) {
|
||||||
return Error::BACKUP_BUSY;
|
return Error::BACKUP_BUSY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -361,11 +380,14 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
|
|||||||
const bool is_ro = True(mount_mode & OrbisSaveDataMountMode::RDONLY);
|
const bool is_ro = True(mount_mode & OrbisSaveDataMountMode::RDONLY);
|
||||||
|
|
||||||
const bool create = True(mount_mode & OrbisSaveDataMountMode::CREATE);
|
const bool create = True(mount_mode & OrbisSaveDataMountMode::CREATE);
|
||||||
const bool create_if_not_exist = True(mount_mode & OrbisSaveDataMountMode::CREATE2);
|
const bool create_if_not_exist =
|
||||||
|
True(mount_mode & OrbisSaveDataMountMode::CREATE2) && g_fw_ver >= ElfInfo::FW_45;
|
||||||
ASSERT(!create || !create_if_not_exist); // Can't have both
|
ASSERT(!create || !create_if_not_exist); // Can't have both
|
||||||
|
|
||||||
const bool copy_icon = True(mount_mode & OrbisSaveDataMountMode::COPY_ICON);
|
const bool copy_icon = True(mount_mode & OrbisSaveDataMountMode::COPY_ICON);
|
||||||
const bool ignore_corrupt = True(mount_mode & OrbisSaveDataMountMode::DESTRUCT_OFF);
|
|
||||||
|
const bool ignore_corrupt =
|
||||||
|
True(mount_mode & OrbisSaveDataMountMode::DESTRUCT_OFF) || g_fw_ver < ElfInfo::FW_16;
|
||||||
|
|
||||||
const std::string_view dir_name{mount_info->dirName->data};
|
const std::string_view dir_name{mount_info->dirName->data};
|
||||||
|
|
||||||
@ -437,9 +459,11 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
|
|||||||
|
|
||||||
mount_result->mount_point.data.FromString(save_instance.GetMountPoint());
|
mount_result->mount_point.data.FromString(save_instance.GetMountPoint());
|
||||||
|
|
||||||
mount_result->mount_status = create_if_not_exist && to_be_created
|
if (g_fw_ver >= ElfInfo::FW_45) {
|
||||||
? OrbisSaveDataMountStatus::CREATED
|
mount_result->mount_status = create_if_not_exist && to_be_created
|
||||||
: OrbisSaveDataMountStatus::NOTHING;
|
? OrbisSaveDataMountStatus::CREATED
|
||||||
|
: OrbisSaveDataMountStatus::NOTHING;
|
||||||
|
}
|
||||||
|
|
||||||
g_mount_slots[slot_num].emplace(std::move(save_instance));
|
g_mount_slots[slot_num].emplace(std::move(save_instance));
|
||||||
|
|
||||||
@ -449,7 +473,7 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
|
|||||||
static Error Umount(const OrbisSaveDataMountPoint* mountPoint, bool call_backup = false) {
|
static Error Umount(const OrbisSaveDataMountPoint* mountPoint, bool call_backup = false) {
|
||||||
if (!g_initialized) {
|
if (!g_initialized) {
|
||||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||||
return Error::NOT_INITIALIZED;
|
return setNotInitializedError();
|
||||||
}
|
}
|
||||||
if (mountPoint == nullptr) {
|
if (mountPoint == nullptr) {
|
||||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||||
@ -461,14 +485,14 @@ static Error Umount(const OrbisSaveDataMountPoint* mountPoint, bool call_backup
|
|||||||
if (instance.has_value()) {
|
if (instance.has_value()) {
|
||||||
const auto& slot_name = instance->GetMountPoint();
|
const auto& slot_name = instance->GetMountPoint();
|
||||||
if (slot_name == mount_point_str) {
|
if (slot_name == mount_point_str) {
|
||||||
|
// TODO: check if is busy
|
||||||
|
instance->Umount();
|
||||||
if (call_backup) {
|
if (call_backup) {
|
||||||
Backup::StartThread();
|
Backup::StartThread();
|
||||||
Backup::NewRequest(instance->GetUserId(), instance->GetTitleId(),
|
Backup::NewRequest(instance->GetUserId(), instance->GetTitleId(),
|
||||||
instance->GetDirName(),
|
instance->GetDirName(),
|
||||||
OrbisSaveDataEventType::UMOUNT_BACKUP);
|
OrbisSaveDataEventType::UMOUNT_BACKUP);
|
||||||
}
|
}
|
||||||
// TODO: check if is busy
|
|
||||||
instance->Umount();
|
|
||||||
instance.reset();
|
instance.reset();
|
||||||
return Error::OK;
|
return Error::OK;
|
||||||
}
|
}
|
||||||
@ -479,9 +503,9 @@ static Error Umount(const OrbisSaveDataMountPoint* mountPoint, bool call_backup
|
|||||||
|
|
||||||
void OrbisSaveDataParam::FromSFO(const PSF& sfo) {
|
void OrbisSaveDataParam::FromSFO(const PSF& sfo) {
|
||||||
memset(this, 0, sizeof(OrbisSaveDataParam));
|
memset(this, 0, sizeof(OrbisSaveDataParam));
|
||||||
title.FromString(*sfo.GetString(SaveParams::MAINTITLE));
|
title.FromString(sfo.GetString(SaveParams::MAINTITLE).value_or("Unknown"));
|
||||||
subTitle.FromString(*sfo.GetString(SaveParams::SUBTITLE));
|
subTitle.FromString(sfo.GetString(SaveParams::SUBTITLE).value_or(""));
|
||||||
detail.FromString(*sfo.GetString(SaveParams::DETAIL));
|
detail.FromString(sfo.GetString(SaveParams::DETAIL).value_or(""));
|
||||||
userParam = sfo.GetInteger(SaveParams::SAVEDATA_LIST_PARAM).value_or(0);
|
userParam = sfo.GetInteger(SaveParams::SAVEDATA_LIST_PARAM).value_or(0);
|
||||||
const auto time_since_epoch = sfo.GetLastWrite().time_since_epoch();
|
const auto time_since_epoch = sfo.GetLastWrite().time_since_epoch();
|
||||||
mtime = chrono::duration_cast<chrono::seconds>(time_since_epoch).count();
|
mtime = chrono::duration_cast<chrono::seconds>(time_since_epoch).count();
|
||||||
@ -502,7 +526,7 @@ int PS4_SYSV_ABI sceSaveDataAbort() {
|
|||||||
Error PS4_SYSV_ABI sceSaveDataBackup(const OrbisSaveDataBackup* backup) {
|
Error PS4_SYSV_ABI sceSaveDataBackup(const OrbisSaveDataBackup* backup) {
|
||||||
if (!g_initialized) {
|
if (!g_initialized) {
|
||||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||||
return Error::NOT_INITIALIZED;
|
return setNotInitializedError();
|
||||||
}
|
}
|
||||||
if (backup == nullptr || backup->dirName == nullptr) {
|
if (backup == nullptr || backup->dirName == nullptr) {
|
||||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||||
@ -551,7 +575,7 @@ int PS4_SYSV_ABI sceSaveDataChangeInternal() {
|
|||||||
Error PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData* check) {
|
Error PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData* check) {
|
||||||
if (!g_initialized) {
|
if (!g_initialized) {
|
||||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||||
return Error::NOT_INITIALIZED;
|
return setNotInitializedError();
|
||||||
}
|
}
|
||||||
if (check == nullptr || check->dirName == nullptr) {
|
if (check == nullptr || check->dirName == nullptr) {
|
||||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||||
@ -560,6 +584,7 @@ Error PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData
|
|||||||
|
|
||||||
const std::string_view title{check->titleId != nullptr ? std::string_view{check->titleId->data}
|
const std::string_view title{check->titleId != nullptr ? std::string_view{check->titleId->data}
|
||||||
: std::string_view{g_game_serial}};
|
: std::string_view{g_game_serial}};
|
||||||
|
LOG_DEBUG(Lib_SaveData, "called with titleId={}", title);
|
||||||
|
|
||||||
const auto save_path =
|
const auto save_path =
|
||||||
SaveInstance::MakeDirSavePath(check->userId, title, check->dirName->data);
|
SaveInstance::MakeDirSavePath(check->userId, title, check->dirName->data);
|
||||||
@ -582,7 +607,7 @@ Error PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData
|
|||||||
if (check->param != nullptr) {
|
if (check->param != nullptr) {
|
||||||
PSF sfo;
|
PSF sfo;
|
||||||
if (!sfo.Open(backup_path / "sce_sys" / "param.sfo")) {
|
if (!sfo.Open(backup_path / "sce_sys" / "param.sfo")) {
|
||||||
LOG_ERROR(Lib_SaveData, "Failed to read SFO at {}", backup_path.string());
|
LOG_ERROR(Lib_SaveData, "Failed to read SFO at {}", fmt::UTF(backup_path.u8string()));
|
||||||
return Error::INTERNAL;
|
return Error::INTERNAL;
|
||||||
}
|
}
|
||||||
check->param->FromSFO(sfo);
|
check->param->FromSFO(sfo);
|
||||||
@ -636,7 +661,7 @@ int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersionLatest() {
|
|||||||
Error PS4_SYSV_ABI sceSaveDataClearProgress() {
|
Error PS4_SYSV_ABI sceSaveDataClearProgress() {
|
||||||
if (!g_initialized) {
|
if (!g_initialized) {
|
||||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||||
return Error::NOT_INITIALIZED;
|
return setNotInitializedError();
|
||||||
}
|
}
|
||||||
LOG_DEBUG(Lib_SaveData, "called");
|
LOG_DEBUG(Lib_SaveData, "called");
|
||||||
Backup::ClearProgress();
|
Backup::ClearProgress();
|
||||||
@ -691,7 +716,7 @@ int PS4_SYSV_ABI sceSaveDataDebugTarget() {
|
|||||||
Error PS4_SYSV_ABI sceSaveDataDelete(const OrbisSaveDataDelete* del) {
|
Error PS4_SYSV_ABI sceSaveDataDelete(const OrbisSaveDataDelete* del) {
|
||||||
if (!g_initialized) {
|
if (!g_initialized) {
|
||||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||||
return Error::NOT_INITIALIZED;
|
return setNotInitializedError();
|
||||||
}
|
}
|
||||||
if (del == nullptr) {
|
if (del == nullptr) {
|
||||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||||
@ -743,7 +768,7 @@ Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond
|
|||||||
OrbisSaveDataDirNameSearchResult* result) {
|
OrbisSaveDataDirNameSearchResult* result) {
|
||||||
if (!g_initialized) {
|
if (!g_initialized) {
|
||||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||||
return Error::NOT_INITIALIZED;
|
return setNotInitializedError();
|
||||||
}
|
}
|
||||||
if (cond == nullptr || result == nullptr || cond->key > OrbisSaveDataSortKey::FREE_BLOCKS ||
|
if (cond == nullptr || result == nullptr || cond->key > OrbisSaveDataSortKey::FREE_BLOCKS ||
|
||||||
cond->order > OrbisSaveDataSortOrder::DESCENT) {
|
cond->order > OrbisSaveDataSortOrder::DESCENT) {
|
||||||
@ -758,7 +783,9 @@ Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond
|
|||||||
|
|
||||||
if (!fs::exists(save_path)) {
|
if (!fs::exists(save_path)) {
|
||||||
result->hitNum = 0;
|
result->hitNum = 0;
|
||||||
result->setNum = 0;
|
if (g_fw_ver >= ElfInfo::FW_17) {
|
||||||
|
result->setNum = 0;
|
||||||
|
}
|
||||||
return Error::OK;
|
return Error::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -775,9 +802,11 @@ Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond
|
|||||||
if (cond->dirName != nullptr) {
|
if (cond->dirName != nullptr) {
|
||||||
// Filter names
|
// Filter names
|
||||||
const auto pat = Common::ToLower(std::string_view{cond->dirName->data});
|
const auto pat = Common::ToLower(std::string_view{cond->dirName->data});
|
||||||
std::erase_if(dir_list, [&](const std::string& dir_name) {
|
if (!pat.empty()) {
|
||||||
return !match(Common::ToLower(dir_name), pat);
|
std::erase_if(dir_list, [&](const std::string& dir_name) {
|
||||||
});
|
return !match(Common::ToLower(dir_name), pat);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unordered_map<std::string, PSF> map_dir_sfo;
|
std::unordered_map<std::string, PSF> map_dir_sfo;
|
||||||
@ -789,7 +818,7 @@ Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond
|
|||||||
const auto sfo_path = SaveInstance::GetParamSFOPath(dir_path);
|
const auto sfo_path = SaveInstance::GetParamSFOPath(dir_path);
|
||||||
PSF sfo;
|
PSF sfo;
|
||||||
if (!sfo.Open(sfo_path)) {
|
if (!sfo.Open(sfo_path)) {
|
||||||
LOG_ERROR(Lib_SaveData, "Failed to read SFO: {}", sfo_path.string());
|
LOG_ERROR(Lib_SaveData, "Failed to read SFO: {}", fmt::UTF(sfo_path.u8string()));
|
||||||
ASSERT_MSG(false, "Failed to read SFO");
|
ASSERT_MSG(false, "Failed to read SFO");
|
||||||
}
|
}
|
||||||
map_dir_sfo.emplace(dir_name, std::move(sfo));
|
map_dir_sfo.emplace(dir_name, std::move(sfo));
|
||||||
@ -826,21 +855,25 @@ Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond
|
|||||||
std::ranges::reverse(dir_list);
|
std::ranges::reverse(dir_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
result->hitNum = dir_list.size();
|
|
||||||
size_t max_count = std::min(static_cast<size_t>(result->dirNamesNum), dir_list.size());
|
size_t max_count = std::min(static_cast<size_t>(result->dirNamesNum), dir_list.size());
|
||||||
result->setNum = max_count;
|
if (g_fw_ver >= ElfInfo::FW_17) {
|
||||||
|
result->hitNum = dir_list.size();
|
||||||
|
result->setNum = max_count;
|
||||||
|
} else {
|
||||||
|
result->hitNum = max_count;
|
||||||
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < max_count; i++) {
|
for (size_t i = 0; i < max_count; i++) {
|
||||||
auto& name_data = result->dirNames[i].data;
|
auto& name_data = result->dirNames[i].data;
|
||||||
name_data.FromString(dir_list[i]);
|
name_data.FromString(dir_list[i]);
|
||||||
|
|
||||||
if (result->params != nullptr) {
|
if (g_fw_ver >= ElfInfo::FW_17 && result->params != nullptr) {
|
||||||
auto& sfo = map_dir_sfo.at(dir_list[i]);
|
auto& sfo = map_dir_sfo.at(dir_list[i]);
|
||||||
auto& param_data = result->params[i];
|
auto& param_data = result->params[i];
|
||||||
param_data.FromSFO(sfo);
|
param_data.FromSFO(sfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result->infos != nullptr) {
|
if (g_fw_ver >= ElfInfo::FW_25 && result->infos != nullptr) {
|
||||||
auto& info = result->infos[i];
|
auto& info = result->infos[i];
|
||||||
info.blocks = map_max_blocks.at(dir_list[i]);
|
info.blocks = map_max_blocks.at(dir_list[i]);
|
||||||
info.freeBlocks = map_free_size.at(dir_list[i]);
|
info.freeBlocks = map_free_size.at(dir_list[i]);
|
||||||
@ -914,7 +947,7 @@ Error PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam*,
|
|||||||
OrbisSaveDataEvent* event) {
|
OrbisSaveDataEvent* event) {
|
||||||
if (!g_initialized) {
|
if (!g_initialized) {
|
||||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||||
return Error::NOT_INITIALIZED;
|
return setNotInitializedError();
|
||||||
}
|
}
|
||||||
if (event == nullptr) {
|
if (event == nullptr) {
|
||||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||||
@ -950,7 +983,7 @@ Error PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountP
|
|||||||
OrbisSaveDataMountInfo* info) {
|
OrbisSaveDataMountInfo* info) {
|
||||||
if (!g_initialized) {
|
if (!g_initialized) {
|
||||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||||
return Error::NOT_INITIALIZED;
|
return setNotInitializedError();
|
||||||
}
|
}
|
||||||
if (mountPoint == nullptr || info == nullptr) {
|
if (mountPoint == nullptr || info == nullptr) {
|
||||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||||
@ -975,7 +1008,7 @@ Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint
|
|||||||
size_t paramBufSize, size_t* gotSize) {
|
size_t paramBufSize, size_t* gotSize) {
|
||||||
if (!g_initialized) {
|
if (!g_initialized) {
|
||||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||||
return Error::NOT_INITIALIZED;
|
return setNotInitializedError();
|
||||||
}
|
}
|
||||||
if (paramType > OrbisSaveDataParamType::MTIME || paramBuf == nullptr) {
|
if (paramType > OrbisSaveDataParamType::MTIME || paramBuf == nullptr) {
|
||||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||||
@ -1019,7 +1052,7 @@ Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint
|
|||||||
} else {
|
} else {
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
const size_t s = param_sfo->GetString(key)->copy(param, paramBufSize - 1);
|
const size_t s = param_sfo->GetString(key).value_or("").copy(param, paramBufSize - 1);
|
||||||
param[s] = '\0'; // null terminate
|
param[s] = '\0'; // null terminate
|
||||||
if (gotSize != nullptr) {
|
if (gotSize != nullptr) {
|
||||||
*gotSize = s + 1;
|
*gotSize = s + 1;
|
||||||
@ -1050,7 +1083,7 @@ Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint
|
|||||||
Error PS4_SYSV_ABI sceSaveDataGetProgress(float* progress) {
|
Error PS4_SYSV_ABI sceSaveDataGetProgress(float* progress) {
|
||||||
if (!g_initialized) {
|
if (!g_initialized) {
|
||||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||||
return Error::NOT_INITIALIZED;
|
return setNotInitializedError();
|
||||||
}
|
}
|
||||||
if (progress == nullptr) {
|
if (progress == nullptr) {
|
||||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||||
@ -1084,7 +1117,7 @@ Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const OrbisUserServiceUserId use
|
|||||||
Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam) {
|
Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam) {
|
||||||
if (!g_initialized) {
|
if (!g_initialized) {
|
||||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||||
return Error::NOT_INITIALIZED;
|
return setNotInitializedError();
|
||||||
}
|
}
|
||||||
if (getParam == nullptr) {
|
if (getParam == nullptr) {
|
||||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||||
@ -1180,7 +1213,7 @@ Error PS4_SYSV_ABI sceSaveDataLoadIcon(const OrbisSaveDataMountPoint* mountPoint
|
|||||||
OrbisSaveDataIcon* icon) {
|
OrbisSaveDataIcon* icon) {
|
||||||
if (!g_initialized) {
|
if (!g_initialized) {
|
||||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||||
return Error::NOT_INITIALIZED;
|
return setNotInitializedError();
|
||||||
}
|
}
|
||||||
if (mountPoint == nullptr || icon == nullptr || icon->buf == nullptr) {
|
if (mountPoint == nullptr || icon == nullptr || icon->buf == nullptr) {
|
||||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||||
@ -1209,7 +1242,7 @@ Error PS4_SYSV_ABI sceSaveDataMount(const OrbisSaveDataMount* mount,
|
|||||||
OrbisSaveDataMountResult* mount_result) {
|
OrbisSaveDataMountResult* mount_result) {
|
||||||
if (!g_initialized) {
|
if (!g_initialized) {
|
||||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||||
return Error::NOT_INITIALIZED;
|
return setNotInitializedError();
|
||||||
}
|
}
|
||||||
if (mount == nullptr && mount->dirName != nullptr) {
|
if (mount == nullptr && mount->dirName != nullptr) {
|
||||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||||
@ -1230,7 +1263,7 @@ Error PS4_SYSV_ABI sceSaveDataMount2(const OrbisSaveDataMount2* mount,
|
|||||||
OrbisSaveDataMountResult* mount_result) {
|
OrbisSaveDataMountResult* mount_result) {
|
||||||
if (!g_initialized) {
|
if (!g_initialized) {
|
||||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||||
return Error::NOT_INITIALIZED;
|
return setNotInitializedError();
|
||||||
}
|
}
|
||||||
if (mount == nullptr && mount->dirName != nullptr) {
|
if (mount == nullptr && mount->dirName != nullptr) {
|
||||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||||
@ -1274,7 +1307,7 @@ int PS4_SYSV_ABI sceSaveDataRegisterEventCallback() {
|
|||||||
Error PS4_SYSV_ABI sceSaveDataRestoreBackupData(const OrbisSaveDataRestoreBackupData* restore) {
|
Error PS4_SYSV_ABI sceSaveDataRestoreBackupData(const OrbisSaveDataRestoreBackupData* restore) {
|
||||||
if (!g_initialized) {
|
if (!g_initialized) {
|
||||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||||
return Error::NOT_INITIALIZED;
|
return setNotInitializedError();
|
||||||
}
|
}
|
||||||
if (restore == nullptr || restore->dirName == nullptr) {
|
if (restore == nullptr || restore->dirName == nullptr) {
|
||||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||||
@ -1325,7 +1358,7 @@ Error PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint
|
|||||||
const OrbisSaveDataIcon* icon) {
|
const OrbisSaveDataIcon* icon) {
|
||||||
if (!g_initialized) {
|
if (!g_initialized) {
|
||||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||||
return Error::NOT_INITIALIZED;
|
return setNotInitializedError();
|
||||||
}
|
}
|
||||||
if (mountPoint == nullptr || icon == nullptr || icon->buf == nullptr) {
|
if (mountPoint == nullptr || icon == nullptr || icon->buf == nullptr) {
|
||||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||||
@ -1373,7 +1406,7 @@ Error PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint
|
|||||||
size_t paramBufSize) {
|
size_t paramBufSize) {
|
||||||
if (!g_initialized) {
|
if (!g_initialized) {
|
||||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||||
return Error::NOT_INITIALIZED;
|
return setNotInitializedError();
|
||||||
}
|
}
|
||||||
if (paramType > OrbisSaveDataParamType::USER_PARAM || mountPoint == nullptr ||
|
if (paramType > OrbisSaveDataParamType::USER_PARAM || mountPoint == nullptr ||
|
||||||
paramBuf == nullptr) {
|
paramBuf == nullptr) {
|
||||||
@ -1438,13 +1471,15 @@ Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, v
|
|||||||
OrbisSaveDataMemorySet2 setParam{};
|
OrbisSaveDataMemorySet2 setParam{};
|
||||||
setParam.userId = userId;
|
setParam.userId = userId;
|
||||||
setParam.data = &data;
|
setParam.data = &data;
|
||||||
|
setParam.param = nullptr;
|
||||||
|
setParam.icon = nullptr;
|
||||||
return sceSaveDataSetSaveDataMemory2(&setParam);
|
return sceSaveDataSetSaveDataMemory2(&setParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam) {
|
Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam) {
|
||||||
if (!g_initialized) {
|
if (!g_initialized) {
|
||||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||||
return Error::NOT_INITIALIZED;
|
return setNotInitializedError();
|
||||||
}
|
}
|
||||||
if (setParam == nullptr) {
|
if (setParam == nullptr) {
|
||||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||||
@ -1477,17 +1512,35 @@ Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2*
|
|||||||
return Error::OK;
|
return Error::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(/*u32 userId, size_t memorySize,
|
Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(OrbisUserServiceUserId userId, size_t memorySize,
|
||||||
OrbisSaveDataParam* param*/) {
|
OrbisSaveDataParam* param) {
|
||||||
LOG_ERROR(Lib_SaveData, "(STUBBED) called");
|
LOG_DEBUG(Lib_SaveData, "called: userId = {}, memorySize = {}", userId, memorySize);
|
||||||
return ORBIS_OK;
|
OrbisSaveDataMemorySetup2 setupParam{};
|
||||||
|
setupParam.userId = userId;
|
||||||
|
setupParam.memorySize = memorySize;
|
||||||
|
setupParam.initParam = nullptr;
|
||||||
|
setupParam.initIcon = nullptr;
|
||||||
|
OrbisSaveDataMemorySetupResult result{};
|
||||||
|
const auto res = sceSaveDataSetupSaveDataMemory2(&setupParam, &result);
|
||||||
|
if (res != Error::OK) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
if (param != nullptr) {
|
||||||
|
OrbisSaveDataMemorySet2 setParam{};
|
||||||
|
setParam.userId = userId;
|
||||||
|
setParam.data = nullptr;
|
||||||
|
setParam.param = param;
|
||||||
|
setParam.icon = nullptr;
|
||||||
|
sceSaveDataSetSaveDataMemory2(&setParam);
|
||||||
|
}
|
||||||
|
return Error::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam,
|
Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam,
|
||||||
OrbisSaveDataMemorySetupResult* result) {
|
OrbisSaveDataMemorySetupResult* result) {
|
||||||
if (!g_initialized) {
|
if (!g_initialized) {
|
||||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||||
return Error::NOT_INITIALIZED;
|
return setNotInitializedError();
|
||||||
}
|
}
|
||||||
if (setupParam == nullptr) {
|
if (setupParam == nullptr) {
|
||||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||||
@ -1507,20 +1560,20 @@ Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetu
|
|||||||
try {
|
try {
|
||||||
size_t existed_size = SaveMemory::CreateSaveMemory(setupParam->memorySize);
|
size_t existed_size = SaveMemory::CreateSaveMemory(setupParam->memorySize);
|
||||||
if (existed_size == 0) { // Just created
|
if (existed_size == 0) { // Just created
|
||||||
if (setupParam->initParam != nullptr) {
|
if (g_fw_ver >= ElfInfo::FW_45 && setupParam->initParam != nullptr) {
|
||||||
auto& sfo = SaveMemory::GetParamSFO();
|
auto& sfo = SaveMemory::GetParamSFO();
|
||||||
setupParam->initParam->ToSFO(sfo);
|
setupParam->initParam->ToSFO(sfo);
|
||||||
}
|
}
|
||||||
SaveMemory::SaveSFO();
|
SaveMemory::SaveSFO();
|
||||||
|
|
||||||
auto init_icon = setupParam->initIcon;
|
auto init_icon = setupParam->initIcon;
|
||||||
if (init_icon != nullptr) {
|
if (g_fw_ver >= ElfInfo::FW_45 && init_icon != nullptr) {
|
||||||
SaveMemory::SetIcon(init_icon->buf, init_icon->bufSize);
|
SaveMemory::SetIcon(init_icon->buf, init_icon->bufSize);
|
||||||
} else {
|
} else {
|
||||||
SaveMemory::SetIcon(nullptr, 0);
|
SaveMemory::SetIcon(nullptr, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (result != nullptr) {
|
if (g_fw_ver >= ElfInfo::FW_45 && result != nullptr) {
|
||||||
result->existedMemorySize = existed_size;
|
result->existedMemorySize = existed_size;
|
||||||
}
|
}
|
||||||
} catch (const fs::filesystem_error& e) {
|
} catch (const fs::filesystem_error& e) {
|
||||||
@ -1556,7 +1609,7 @@ int PS4_SYSV_ABI sceSaveDataSyncCloudList() {
|
|||||||
Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam) {
|
Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam) {
|
||||||
if (!g_initialized) {
|
if (!g_initialized) {
|
||||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||||
return Error::NOT_INITIALIZED;
|
return setNotInitializedError();
|
||||||
}
|
}
|
||||||
if (syncParam == nullptr) {
|
if (syncParam == nullptr) {
|
||||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||||
@ -1577,11 +1630,15 @@ Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncPa
|
|||||||
Error PS4_SYSV_ABI sceSaveDataTerminate() {
|
Error PS4_SYSV_ABI sceSaveDataTerminate() {
|
||||||
LOG_DEBUG(Lib_SaveData, "called");
|
LOG_DEBUG(Lib_SaveData, "called");
|
||||||
if (!g_initialized) {
|
if (!g_initialized) {
|
||||||
return Error::NOT_INITIALIZED;
|
return setNotInitializedError();
|
||||||
}
|
}
|
||||||
for (const auto& instance : g_mount_slots) {
|
for (auto& instance : g_mount_slots) {
|
||||||
if (instance.has_value()) {
|
if (instance.has_value()) {
|
||||||
return Error::BUSY;
|
if (g_fw_ver >= ElfInfo::FW_40) {
|
||||||
|
return Error::BUSY;
|
||||||
|
}
|
||||||
|
instance->Umount();
|
||||||
|
instance.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g_initialized = false;
|
g_initialized = false;
|
||||||
|
@ -165,8 +165,8 @@ int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser();
|
|||||||
Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, void* buf,
|
Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, void* buf,
|
||||||
size_t bufSize, int64_t offset);
|
size_t bufSize, int64_t offset);
|
||||||
Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam);
|
Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam);
|
||||||
int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(/*u32 userId, size_t memorySize,
|
Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(OrbisUserServiceUserId userId, size_t memorySize,
|
||||||
OrbisSaveDataParam* param*/);
|
OrbisSaveDataParam* param);
|
||||||
Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam,
|
Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam,
|
||||||
OrbisSaveDataMemorySetupResult* result);
|
OrbisSaveDataMemorySetupResult* result);
|
||||||
int PS4_SYSV_ABI sceSaveDataShutdownStart();
|
int PS4_SYSV_ABI sceSaveDataShutdownStart();
|
||||||
|
@ -1717,7 +1717,7 @@ int PS4_SYSV_ABI sceSystemServiceGetAppType() {
|
|||||||
|
|
||||||
s32 PS4_SYSV_ABI
|
s32 PS4_SYSV_ABI
|
||||||
sceSystemServiceGetDisplaySafeAreaInfo(OrbisSystemServiceDisplaySafeAreaInfo* info) {
|
sceSystemServiceGetDisplaySafeAreaInfo(OrbisSystemServiceDisplaySafeAreaInfo* info) {
|
||||||
LOG_INFO(Lib_SystemService, "called");
|
LOG_DEBUG(Lib_SystemService, "called");
|
||||||
if (info == nullptr) {
|
if (info == nullptr) {
|
||||||
LOG_ERROR(Lib_SystemService, "OrbisSystemServiceDisplaySafeAreaInfo is null");
|
LOG_ERROR(Lib_SystemService, "OrbisSystemServiceDisplaySafeAreaInfo is null");
|
||||||
return ORBIS_SYSTEM_SERVICE_ERROR_PARAMETER;
|
return ORBIS_SYSTEM_SERVICE_ERROR_PARAMETER;
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/config.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
|
||||||
#include "core/libraries/error_codes.h"
|
#include "core/libraries/error_codes.h"
|
||||||
#include "core/libraries/libs.h"
|
#include "core/libraries/libs.h"
|
||||||
#include "core/libraries/system/userservice.h"
|
#include "core/libraries/system/userservice.h"
|
||||||
@ -1071,7 +1073,7 @@ s32 PS4_SYSV_ABI sceUserServiceGetUserName(int user_id, char* user_name, std::si
|
|||||||
LOG_ERROR(Lib_UserService, "user_name is null");
|
LOG_ERROR(Lib_UserService, "user_name is null");
|
||||||
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
std::string name = "shadps4"; // TODO onfigurable username
|
std::string name = Config::getUserName();
|
||||||
if (size < name.length()) {
|
if (size < name.length()) {
|
||||||
LOG_ERROR(Lib_UserService, "buffer is too short");
|
LOG_ERROR(Lib_UserService, "buffer is too short");
|
||||||
return ORBIS_USER_SERVICE_ERROR_BUFFER_TOO_SHORT;
|
return ORBIS_USER_SERVICE_ERROR_BUFFER_TOO_SHORT;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
@ -160,9 +161,7 @@ int VideoOutDriver::UnregisterBuffers(VideoOutPort* port, s32 attributeIndex) {
|
|||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::chrono::microseconds VideoOutDriver::Flip(const Request& req) {
|
void VideoOutDriver::Flip(const Request& req) {
|
||||||
const auto start = std::chrono::high_resolution_clock::now();
|
|
||||||
|
|
||||||
// Whatever the game is rendering show splash if it is active
|
// Whatever the game is rendering show splash if it is active
|
||||||
if (!renderer->ShowSplash(req.frame)) {
|
if (!renderer->ShowSplash(req.frame)) {
|
||||||
// Present the frame.
|
// Present the frame.
|
||||||
@ -198,9 +197,6 @@ std::chrono::microseconds VideoOutDriver::Flip(const Request& req) {
|
|||||||
port->buffer_labels[req.index] = 0;
|
port->buffer_labels[req.index] = 0;
|
||||||
port->SignalVoLabel();
|
port->SignalVoLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto end = std::chrono::high_resolution_clock::now();
|
|
||||||
return std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoOutDriver::DrawBlankFrame() {
|
void VideoOutDriver::DrawBlankFrame() {
|
||||||
@ -264,9 +260,11 @@ void VideoOutDriver::PresentThread(std::stop_token token) {
|
|||||||
static constexpr std::chrono::nanoseconds VblankPeriod{16666667};
|
static constexpr std::chrono::nanoseconds VblankPeriod{16666667};
|
||||||
const auto vblank_period = VblankPeriod / Config::vblankDiv();
|
const auto vblank_period = VblankPeriod / Config::vblankDiv();
|
||||||
|
|
||||||
Common::SetCurrentThreadName("PresentThread");
|
Common::SetCurrentThreadName("shadPS4:PresentThread");
|
||||||
Common::SetCurrentThreadRealtime(vblank_period);
|
Common::SetCurrentThreadRealtime(vblank_period);
|
||||||
|
|
||||||
|
Common::AccurateTimer timer{vblank_period};
|
||||||
|
|
||||||
const auto receive_request = [this] -> Request {
|
const auto receive_request = [this] -> Request {
|
||||||
std::scoped_lock lk{mutex};
|
std::scoped_lock lk{mutex};
|
||||||
if (!requests.empty()) {
|
if (!requests.empty()) {
|
||||||
@ -279,20 +277,18 @@ void VideoOutDriver::PresentThread(std::stop_token token) {
|
|||||||
|
|
||||||
auto delay = std::chrono::microseconds{0};
|
auto delay = std::chrono::microseconds{0};
|
||||||
while (!token.stop_requested()) {
|
while (!token.stop_requested()) {
|
||||||
// Sleep for most of the vblank duration.
|
timer.Start();
|
||||||
std::this_thread::sleep_for(vblank_period - delay);
|
|
||||||
|
|
||||||
// Check if it's time to take a request.
|
// Check if it's time to take a request.
|
||||||
auto& vblank_status = main_port.vblank_status;
|
auto& vblank_status = main_port.vblank_status;
|
||||||
if (vblank_status.count % (main_port.flip_rate + 1) == 0) {
|
if (vblank_status.count % (main_port.flip_rate + 1) == 0) {
|
||||||
const auto request = receive_request();
|
const auto request = receive_request();
|
||||||
if (!request) {
|
if (!request) {
|
||||||
delay = std::chrono::microseconds{0};
|
|
||||||
if (!main_port.is_open) {
|
if (!main_port.is_open) {
|
||||||
DrawBlankFrame();
|
DrawBlankFrame();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
delay = Flip(request);
|
Flip(request);
|
||||||
FRAME_END;
|
FRAME_END;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -313,6 +309,8 @@ void VideoOutDriver::PresentThread(std::stop_token token) {
|
|||||||
Kernel::SceKernelEvent::Filter::VideoOut, nullptr);
|
Kernel::SceKernelEvent::Filter::VideoOut, nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
timer.End();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ private:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::chrono::microseconds Flip(const Request& req);
|
void Flip(const Request& req);
|
||||||
void DrawBlankFrame(); // Used when there is no flip request to keep ImGui up to date
|
void DrawBlankFrame(); // Used when there is no flip request to keep ImGui up to date
|
||||||
void SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop = false);
|
void SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop = false);
|
||||||
void PresentThread(std::stop_token token);
|
void PresentThread(std::stop_token token);
|
||||||
|
@ -90,11 +90,8 @@ void Linker::Execute() {
|
|||||||
|
|
||||||
// Init primary thread.
|
// Init primary thread.
|
||||||
Common::SetCurrentThreadName("GAME_MainThread");
|
Common::SetCurrentThreadName("GAME_MainThread");
|
||||||
#ifdef ARCH_X86_64
|
|
||||||
InitializeThreadPatchStack();
|
|
||||||
#endif
|
|
||||||
Libraries::Kernel::pthreadInitSelfMainThread();
|
Libraries::Kernel::pthreadInitSelfMainThread();
|
||||||
InitTlsForThread(true);
|
EnsureThreadInitialized(true);
|
||||||
|
|
||||||
// Start shared library modules
|
// Start shared library modules
|
||||||
for (auto& m : m_modules) {
|
for (auto& m : m_modules) {
|
||||||
@ -335,6 +332,17 @@ void* Linker::TlsGetAddr(u64 module_index, u64 offset) {
|
|||||||
return addr + offset;
|
return addr + offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
thread_local std::once_flag init_tls_flag;
|
||||||
|
|
||||||
|
void Linker::EnsureThreadInitialized(bool is_primary) {
|
||||||
|
std::call_once(init_tls_flag, [this, is_primary] {
|
||||||
|
#ifdef ARCH_X86_64
|
||||||
|
InitializeThreadPatchStack();
|
||||||
|
#endif
|
||||||
|
InitTlsForThread(is_primary);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void Linker::InitTlsForThread(bool is_primary) {
|
void Linker::InitTlsForThread(bool is_primary) {
|
||||||
static constexpr size_t TcbSize = 0x40;
|
static constexpr size_t TcbSize = 0x40;
|
||||||
static constexpr size_t TlsAllocAlign = 0x20;
|
static constexpr size_t TlsAllocAlign = 0x20;
|
||||||
|
@ -98,7 +98,6 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void* TlsGetAddr(u64 module_index, u64 offset);
|
void* TlsGetAddr(u64 module_index, u64 offset);
|
||||||
void InitTlsForThread(bool is_primary = false);
|
|
||||||
|
|
||||||
s32 LoadModule(const std::filesystem::path& elf_name, bool is_dynamic = false);
|
s32 LoadModule(const std::filesystem::path& elf_name, bool is_dynamic = false);
|
||||||
Module* FindByAddress(VAddr address);
|
Module* FindByAddress(VAddr address);
|
||||||
@ -109,8 +108,17 @@ public:
|
|||||||
void Execute();
|
void Execute();
|
||||||
void DebugDump();
|
void DebugDump();
|
||||||
|
|
||||||
|
template <class ReturnType, class... FuncArgs, class... CallArgs>
|
||||||
|
ReturnType ExecuteGuest(PS4_SYSV_ABI ReturnType (*func)(FuncArgs...), CallArgs&&... args) {
|
||||||
|
// Make sure TLS is initialized for the thread before entering guest.
|
||||||
|
EnsureThreadInitialized();
|
||||||
|
return func(std::forward<CallArgs>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const Module* FindExportedModule(const ModuleInfo& m, const LibraryInfo& l);
|
const Module* FindExportedModule(const ModuleInfo& m, const LibraryInfo& l);
|
||||||
|
void EnsureThreadInitialized(bool is_primary = false);
|
||||||
|
void InitTlsForThread(bool is_primary);
|
||||||
|
|
||||||
MemoryManager* memory;
|
MemoryManager* memory;
|
||||||
std::mutex mutex;
|
std::mutex mutex;
|
||||||
|
@ -51,6 +51,35 @@ void MemoryManager::SetupMemoryRegions(u64 flexible_size) {
|
|||||||
total_flexible_size, total_direct_size);
|
total_flexible_size, total_direct_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PAddr MemoryManager::PoolExpand(PAddr search_start, PAddr search_end, size_t size, u64 alignment) {
|
||||||
|
std::scoped_lock lk{mutex};
|
||||||
|
|
||||||
|
auto dmem_area = FindDmemArea(search_start);
|
||||||
|
|
||||||
|
const auto is_suitable = [&] {
|
||||||
|
const auto aligned_base = alignment > 0 ? Common::AlignUp(dmem_area->second.base, alignment)
|
||||||
|
: dmem_area->second.base;
|
||||||
|
const auto alignment_size = aligned_base - dmem_area->second.base;
|
||||||
|
const auto remaining_size =
|
||||||
|
dmem_area->second.size >= alignment_size ? dmem_area->second.size - alignment_size : 0;
|
||||||
|
return dmem_area->second.is_free && remaining_size >= size;
|
||||||
|
};
|
||||||
|
while (!is_suitable() && dmem_area->second.GetEnd() <= search_end) {
|
||||||
|
dmem_area++;
|
||||||
|
}
|
||||||
|
ASSERT_MSG(is_suitable(), "Unable to find free direct memory area: size = {:#x}", size);
|
||||||
|
|
||||||
|
// Align free position
|
||||||
|
PAddr free_addr = dmem_area->second.base;
|
||||||
|
free_addr = alignment > 0 ? Common::AlignUp(free_addr, alignment) : free_addr;
|
||||||
|
|
||||||
|
// Add the allocated region to the list and commit its pages.
|
||||||
|
auto& area = CarveDmemArea(free_addr, size)->second;
|
||||||
|
area.is_free = false;
|
||||||
|
area.is_pooled = true;
|
||||||
|
return free_addr;
|
||||||
|
}
|
||||||
|
|
||||||
PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, u64 alignment,
|
PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, u64 alignment,
|
||||||
int memory_type) {
|
int memory_type) {
|
||||||
std::scoped_lock lk{mutex};
|
std::scoped_lock lk{mutex};
|
||||||
@ -112,6 +141,43 @@ void MemoryManager::Free(PAddr phys_addr, size_t size) {
|
|||||||
MergeAdjacent(dmem_map, dmem_area);
|
MergeAdjacent(dmem_map, dmem_area);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int MemoryManager::PoolReserve(void** out_addr, VAddr virtual_addr, size_t size,
|
||||||
|
MemoryMapFlags flags, u64 alignment) {
|
||||||
|
std::scoped_lock lk{mutex};
|
||||||
|
|
||||||
|
virtual_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr;
|
||||||
|
alignment = alignment > 0 ? alignment : 2_MB;
|
||||||
|
VAddr mapped_addr = alignment > 0 ? Common::AlignUp(virtual_addr, alignment) : virtual_addr;
|
||||||
|
|
||||||
|
// Fixed mapping means the virtual address must exactly match the provided one.
|
||||||
|
if (True(flags & MemoryMapFlags::Fixed)) {
|
||||||
|
const auto& vma = FindVMA(mapped_addr)->second;
|
||||||
|
// If the VMA is mapped, unmap the region first.
|
||||||
|
if (vma.IsMapped()) {
|
||||||
|
UnmapMemoryImpl(mapped_addr, size);
|
||||||
|
}
|
||||||
|
const size_t remaining_size = vma.base + vma.size - mapped_addr;
|
||||||
|
ASSERT_MSG(vma.type == VMAType::Free && remaining_size >= size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the first free area starting with provided virtual address.
|
||||||
|
if (False(flags & MemoryMapFlags::Fixed)) {
|
||||||
|
mapped_addr = SearchFree(mapped_addr, size, alignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add virtual memory area
|
||||||
|
const auto new_vma_handle = CarveVMA(mapped_addr, size);
|
||||||
|
auto& new_vma = new_vma_handle->second;
|
||||||
|
new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce);
|
||||||
|
new_vma.prot = MemoryProt::NoAccess;
|
||||||
|
new_vma.name = "";
|
||||||
|
new_vma.type = VMAType::PoolReserved;
|
||||||
|
MergeAdjacent(vma_map, new_vma_handle);
|
||||||
|
|
||||||
|
*out_addr = std::bit_cast<void*>(mapped_addr);
|
||||||
|
return ORBIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
int MemoryManager::Reserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags,
|
int MemoryManager::Reserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags,
|
||||||
u64 alignment) {
|
u64 alignment) {
|
||||||
std::scoped_lock lk{mutex};
|
std::scoped_lock lk{mutex};
|
||||||
@ -149,6 +215,36 @@ int MemoryManager::Reserve(void** out_addr, VAddr virtual_addr, size_t size, Mem
|
|||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int MemoryManager::PoolCommit(VAddr virtual_addr, size_t size, MemoryProt prot) {
|
||||||
|
std::scoped_lock lk{mutex};
|
||||||
|
|
||||||
|
const u64 alignment = 64_KB;
|
||||||
|
|
||||||
|
// When virtual addr is zero, force it to virtual_base. The guest cannot pass Fixed
|
||||||
|
// flag so we will take the branch that searches for free (or reserved) mappings.
|
||||||
|
virtual_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr;
|
||||||
|
VAddr mapped_addr = Common::AlignUp(virtual_addr, alignment);
|
||||||
|
|
||||||
|
// This should return SCE_KERNEL_ERROR_ENOMEM but shouldn't normally happen.
|
||||||
|
const auto& vma = FindVMA(mapped_addr)->second;
|
||||||
|
const size_t remaining_size = vma.base + vma.size - mapped_addr;
|
||||||
|
ASSERT_MSG(!vma.IsMapped() && remaining_size >= size);
|
||||||
|
|
||||||
|
// Perform the mapping.
|
||||||
|
void* out_addr = impl.Map(mapped_addr, size, alignment, -1, false);
|
||||||
|
TRACK_ALLOC(out_addr, size, "VMEM");
|
||||||
|
|
||||||
|
auto& new_vma = CarveVMA(mapped_addr, size)->second;
|
||||||
|
new_vma.disallow_merge = false;
|
||||||
|
new_vma.prot = prot;
|
||||||
|
new_vma.name = "";
|
||||||
|
new_vma.type = Core::VMAType::Pooled;
|
||||||
|
new_vma.is_exec = false;
|
||||||
|
new_vma.phys_base = 0;
|
||||||
|
|
||||||
|
return ORBIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
|
int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
|
||||||
MemoryMapFlags flags, VMAType type, std::string_view name,
|
MemoryMapFlags flags, VMAType type, std::string_view name,
|
||||||
bool is_exec, PAddr phys_addr, u64 alignment) {
|
bool is_exec, PAddr phys_addr, u64 alignment) {
|
||||||
@ -232,6 +328,39 @@ int MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, size_t size, Mem
|
|||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MemoryManager::PoolDecommit(VAddr virtual_addr, size_t size) {
|
||||||
|
std::scoped_lock lk{mutex};
|
||||||
|
|
||||||
|
const auto it = FindVMA(virtual_addr);
|
||||||
|
const auto& vma_base = it->second;
|
||||||
|
ASSERT_MSG(vma_base.Contains(virtual_addr, size),
|
||||||
|
"Existing mapping does not contain requested unmap range");
|
||||||
|
|
||||||
|
const auto vma_base_addr = vma_base.base;
|
||||||
|
const auto vma_base_size = vma_base.size;
|
||||||
|
const auto phys_base = vma_base.phys_base;
|
||||||
|
const bool is_exec = vma_base.is_exec;
|
||||||
|
const auto start_in_vma = virtual_addr - vma_base_addr;
|
||||||
|
const auto type = vma_base.type;
|
||||||
|
|
||||||
|
rasterizer->UnmapMemory(virtual_addr, size);
|
||||||
|
|
||||||
|
// Mark region as free and attempt to coalesce it with neighbours.
|
||||||
|
const auto new_it = CarveVMA(virtual_addr, size);
|
||||||
|
auto& vma = new_it->second;
|
||||||
|
vma.type = VMAType::PoolReserved;
|
||||||
|
vma.prot = MemoryProt::NoAccess;
|
||||||
|
vma.phys_base = 0;
|
||||||
|
vma.disallow_merge = false;
|
||||||
|
vma.name = "";
|
||||||
|
MergeAdjacent(vma_map, new_it);
|
||||||
|
|
||||||
|
// Unmap the memory region.
|
||||||
|
impl.Unmap(vma_base_addr, vma_base_size, start_in_vma, start_in_vma + size, phys_base, is_exec,
|
||||||
|
false, false);
|
||||||
|
TRACK_FREE(virtual_addr, "VMEM");
|
||||||
|
}
|
||||||
|
|
||||||
void MemoryManager::UnmapMemory(VAddr virtual_addr, size_t size) {
|
void MemoryManager::UnmapMemory(VAddr virtual_addr, size_t size) {
|
||||||
std::scoped_lock lk{mutex};
|
std::scoped_lock lk{mutex};
|
||||||
UnmapMemoryImpl(virtual_addr, size);
|
UnmapMemoryImpl(virtual_addr, size);
|
||||||
@ -348,63 +477,6 @@ int MemoryManager::Protect(VAddr addr, size_t size, MemoryProt prot) {
|
|||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int MemoryManager::MTypeProtect(VAddr addr, size_t size, VMAType mtype, MemoryProt prot) {
|
|
||||||
std::scoped_lock lk{mutex};
|
|
||||||
|
|
||||||
// Find the virtual memory area that contains the specified address range.
|
|
||||||
auto it = FindVMA(addr);
|
|
||||||
if (it == vma_map.end() || !it->second.Contains(addr, size)) {
|
|
||||||
LOG_ERROR(Core, "Address range not mapped");
|
|
||||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
VirtualMemoryArea& vma = it->second;
|
|
||||||
|
|
||||||
if (vma.type == VMAType::Free) {
|
|
||||||
LOG_ERROR(Core, "Cannot change protection on free memory region");
|
|
||||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate protection flags
|
|
||||||
constexpr static MemoryProt valid_flags = MemoryProt::NoAccess | MemoryProt::CpuRead |
|
|
||||||
MemoryProt::CpuReadWrite | MemoryProt::GpuRead |
|
|
||||||
MemoryProt::GpuWrite | MemoryProt::GpuReadWrite;
|
|
||||||
|
|
||||||
MemoryProt invalid_flags = prot & ~valid_flags;
|
|
||||||
if (u32(invalid_flags) != 0 && u32(invalid_flags) != u32(MemoryProt::NoAccess)) {
|
|
||||||
LOG_ERROR(Core, "Invalid protection flags: prot = {:#x}, invalid flags = {:#x}", u32(prot),
|
|
||||||
u32(invalid_flags));
|
|
||||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change type and protection
|
|
||||||
vma.type = mtype;
|
|
||||||
vma.prot = prot;
|
|
||||||
|
|
||||||
// Set permissions
|
|
||||||
Core::MemoryPermission perms{};
|
|
||||||
|
|
||||||
if (True(prot & MemoryProt::CpuRead)) {
|
|
||||||
perms |= Core::MemoryPermission::Read;
|
|
||||||
}
|
|
||||||
if (True(prot & MemoryProt::CpuReadWrite)) {
|
|
||||||
perms |= Core::MemoryPermission::ReadWrite;
|
|
||||||
}
|
|
||||||
if (True(prot & MemoryProt::GpuRead)) {
|
|
||||||
perms |= Core::MemoryPermission::Read;
|
|
||||||
}
|
|
||||||
if (True(prot & MemoryProt::GpuWrite)) {
|
|
||||||
perms |= Core::MemoryPermission::Write;
|
|
||||||
}
|
|
||||||
if (True(prot & MemoryProt::GpuReadWrite)) {
|
|
||||||
perms |= Core::MemoryPermission::ReadWrite;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl.Protect(addr, size, perms);
|
|
||||||
|
|
||||||
return ORBIS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
int MemoryManager::VirtualQuery(VAddr addr, int flags,
|
int MemoryManager::VirtualQuery(VAddr addr, int flags,
|
||||||
::Libraries::Kernel::OrbisVirtualQueryInfo* info) {
|
::Libraries::Kernel::OrbisVirtualQueryInfo* info) {
|
||||||
std::scoped_lock lk{mutex};
|
std::scoped_lock lk{mutex};
|
||||||
|
@ -50,15 +50,17 @@ enum class VMAType : u32 {
|
|||||||
Direct = 2,
|
Direct = 2,
|
||||||
Flexible = 3,
|
Flexible = 3,
|
||||||
Pooled = 4,
|
Pooled = 4,
|
||||||
Stack = 5,
|
PoolReserved = 5,
|
||||||
Code = 6,
|
Stack = 6,
|
||||||
File = 7,
|
Code = 7,
|
||||||
|
File = 8,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DirectMemoryArea {
|
struct DirectMemoryArea {
|
||||||
PAddr base = 0;
|
PAddr base = 0;
|
||||||
size_t size = 0;
|
size_t size = 0;
|
||||||
int memory_type = 0;
|
int memory_type = 0;
|
||||||
|
bool is_pooled = false;
|
||||||
bool is_free = true;
|
bool is_free = true;
|
||||||
|
|
||||||
PAddr GetEnd() const {
|
PAddr GetEnd() const {
|
||||||
@ -96,7 +98,7 @@ struct VirtualMemoryArea {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool IsMapped() const noexcept {
|
bool IsMapped() const noexcept {
|
||||||
return type != VMAType::Free && type != VMAType::Reserved;
|
return type != VMAType::Free && type != VMAType::Reserved && type != VMAType::PoolReserved;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CanMergeWith(const VirtualMemoryArea& next) const {
|
bool CanMergeWith(const VirtualMemoryArea& next) const {
|
||||||
@ -135,6 +137,10 @@ public:
|
|||||||
return total_direct_size;
|
return total_direct_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u64 GetTotalFlexibleSize() const {
|
||||||
|
return total_flexible_size;
|
||||||
|
}
|
||||||
|
|
||||||
u64 GetAvailableFlexibleSize() const {
|
u64 GetAvailableFlexibleSize() const {
|
||||||
return total_flexible_size - flexible_usage;
|
return total_flexible_size - flexible_usage;
|
||||||
}
|
}
|
||||||
@ -145,14 +151,21 @@ public:
|
|||||||
|
|
||||||
void SetupMemoryRegions(u64 flexible_size);
|
void SetupMemoryRegions(u64 flexible_size);
|
||||||
|
|
||||||
|
PAddr PoolExpand(PAddr search_start, PAddr search_end, size_t size, u64 alignment);
|
||||||
|
|
||||||
PAddr Allocate(PAddr search_start, PAddr search_end, size_t size, u64 alignment,
|
PAddr Allocate(PAddr search_start, PAddr search_end, size_t size, u64 alignment,
|
||||||
int memory_type);
|
int memory_type);
|
||||||
|
|
||||||
void Free(PAddr phys_addr, size_t size);
|
void Free(PAddr phys_addr, size_t size);
|
||||||
|
|
||||||
|
int PoolReserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags,
|
||||||
|
u64 alignment = 0);
|
||||||
|
|
||||||
int Reserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags,
|
int Reserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags,
|
||||||
u64 alignment = 0);
|
u64 alignment = 0);
|
||||||
|
|
||||||
|
int PoolCommit(VAddr virtual_addr, size_t size, MemoryProt prot);
|
||||||
|
|
||||||
int MapMemory(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
|
int MapMemory(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
|
||||||
MemoryMapFlags flags, VMAType type, std::string_view name = "",
|
MemoryMapFlags flags, VMAType type, std::string_view name = "",
|
||||||
bool is_exec = false, PAddr phys_addr = -1, u64 alignment = 0);
|
bool is_exec = false, PAddr phys_addr = -1, u64 alignment = 0);
|
||||||
@ -160,14 +173,14 @@ public:
|
|||||||
int MapFile(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
|
int MapFile(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
|
||||||
MemoryMapFlags flags, uintptr_t fd, size_t offset);
|
MemoryMapFlags flags, uintptr_t fd, size_t offset);
|
||||||
|
|
||||||
|
void PoolDecommit(VAddr virtual_addr, size_t size);
|
||||||
|
|
||||||
void UnmapMemory(VAddr virtual_addr, size_t size);
|
void UnmapMemory(VAddr virtual_addr, size_t size);
|
||||||
|
|
||||||
int QueryProtection(VAddr addr, void** start, void** end, u32* prot);
|
int QueryProtection(VAddr addr, void** start, void** end, u32* prot);
|
||||||
|
|
||||||
int Protect(VAddr addr, size_t size, MemoryProt prot);
|
int Protect(VAddr addr, size_t size, MemoryProt prot);
|
||||||
|
|
||||||
int MTypeProtect(VAddr addr, size_t size, VMAType mtype, MemoryProt prot);
|
|
||||||
|
|
||||||
int VirtualQuery(VAddr addr, int flags, ::Libraries::Kernel::OrbisVirtualQueryInfo* info);
|
int VirtualQuery(VAddr addr, int flags, ::Libraries::Kernel::OrbisVirtualQueryInfo* info);
|
||||||
|
|
||||||
int DirectMemoryQuery(PAddr addr, bool find_next,
|
int DirectMemoryQuery(PAddr addr, bool find_next,
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
#include "core/aerolib/aerolib.h"
|
#include "core/aerolib/aerolib.h"
|
||||||
#include "core/cpu_patches.h"
|
#include "core/cpu_patches.h"
|
||||||
|
#include "core/linker.h"
|
||||||
#include "core/loader/dwarf.h"
|
#include "core/loader/dwarf.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
#include "core/module.h"
|
#include "core/module.h"
|
||||||
@ -69,8 +70,9 @@ Module::~Module() = default;
|
|||||||
|
|
||||||
s32 Module::Start(size_t args, const void* argp, void* param) {
|
s32 Module::Start(size_t args, const void* argp, void* param) {
|
||||||
LOG_INFO(Core_Linker, "Module started : {}", name);
|
LOG_INFO(Core_Linker, "Module started : {}", name);
|
||||||
|
auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||||
const VAddr addr = dynamic_info.init_virtual_addr + GetBaseAddress();
|
const VAddr addr = dynamic_info.init_virtual_addr + GetBaseAddress();
|
||||||
return reinterpret_cast<EntryFunc>(addr)(args, argp, param);
|
return linker->ExecuteGuest(reinterpret_cast<EntryFunc>(addr), args, argp, param);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Module::LoadModuleToMemory(u32& max_tls_index) {
|
void Module::LoadModuleToMemory(u32& max_tls_index) {
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
#include "common/arch.h"
|
#include "common/arch.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
#include "common/decoder.h"
|
||||||
|
#include "common/signal_context.h"
|
||||||
#include "core/signals.h"
|
#include "core/signals.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@ -10,7 +12,6 @@
|
|||||||
#else
|
#else
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
#ifdef ARCH_X86_64
|
#ifdef ARCH_X86_64
|
||||||
#include <Zydis/Decoder.h>
|
|
||||||
#include <Zydis/Formatter.h>
|
#include <Zydis/Formatter.h>
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
@ -22,17 +23,14 @@ namespace Core {
|
|||||||
static LONG WINAPI SignalHandler(EXCEPTION_POINTERS* pExp) noexcept {
|
static LONG WINAPI SignalHandler(EXCEPTION_POINTERS* pExp) noexcept {
|
||||||
const auto* signals = Signals::Instance();
|
const auto* signals = Signals::Instance();
|
||||||
|
|
||||||
auto* code_address = reinterpret_cast<void*>(pExp->ContextRecord->Rip);
|
|
||||||
|
|
||||||
bool handled = false;
|
bool handled = false;
|
||||||
switch (pExp->ExceptionRecord->ExceptionCode) {
|
switch (pExp->ExceptionRecord->ExceptionCode) {
|
||||||
case EXCEPTION_ACCESS_VIOLATION:
|
case EXCEPTION_ACCESS_VIOLATION:
|
||||||
handled = signals->DispatchAccessViolation(
|
handled = signals->DispatchAccessViolation(
|
||||||
code_address, reinterpret_cast<void*>(pExp->ExceptionRecord->ExceptionInformation[1]),
|
pExp, reinterpret_cast<void*>(pExp->ExceptionRecord->ExceptionInformation[1]));
|
||||||
pExp->ExceptionRecord->ExceptionInformation[0] == 1);
|
|
||||||
break;
|
break;
|
||||||
case EXCEPTION_ILLEGAL_INSTRUCTION:
|
case EXCEPTION_ILLEGAL_INSTRUCTION:
|
||||||
handled = signals->DispatchIllegalInstruction(code_address);
|
handled = signals->DispatchIllegalInstruction(pExp);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@ -43,37 +41,14 @@ static LONG WINAPI SignalHandler(EXCEPTION_POINTERS* pExp) noexcept {
|
|||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
#ifdef __APPLE__
|
|
||||||
#if defined(ARCH_X86_64)
|
|
||||||
#define CODE_ADDRESS(ctx) reinterpret_cast<void*>((ctx)->uc_mcontext->__ss.__rip)
|
|
||||||
#define IS_WRITE_ERROR(ctx) ((ctx)->uc_mcontext->__es.__err & 0x2)
|
|
||||||
#elif defined(ARCH_ARM64)
|
|
||||||
#define CODE_ADDRESS(ctx) reinterpret_cast<void*>((ctx)->uc_mcontext->__ss.__pc)
|
|
||||||
#define IS_WRITE_ERROR(ctx) ((ctx)->uc_mcontext->__es.__esr & 0x40)
|
|
||||||
#endif
|
|
||||||
#else
|
|
||||||
#if defined(ARCH_X86_64)
|
|
||||||
#define CODE_ADDRESS(ctx) reinterpret_cast<void*>((ctx)->uc_mcontext.gregs[REG_RIP])
|
|
||||||
#define IS_WRITE_ERROR(ctx) ((ctx)->uc_mcontext.gregs[REG_ERR] & 0x2)
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef IS_WRITE_ERROR
|
|
||||||
#error "Missing IS_WRITE_ERROR() implementation for target OS and CPU architecture."
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static std::string DisassembleInstruction(void* code_address) {
|
static std::string DisassembleInstruction(void* code_address) {
|
||||||
char buffer[256] = "<unable to decode>";
|
char buffer[256] = "<unable to decode>";
|
||||||
|
|
||||||
#ifdef ARCH_X86_64
|
#ifdef ARCH_X86_64
|
||||||
ZydisDecoder decoder;
|
|
||||||
ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64);
|
|
||||||
|
|
||||||
ZydisDecodedInstruction instruction;
|
ZydisDecodedInstruction instruction;
|
||||||
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
|
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
|
||||||
static constexpr u64 max_length = 0x20;
|
|
||||||
const auto status =
|
const auto status =
|
||||||
ZydisDecoderDecodeFull(&decoder, code_address, max_length, &instruction, operands);
|
Common::Decoder::Instance()->decodeInstruction(instruction, operands, code_address);
|
||||||
if (ZYAN_SUCCESS(status)) {
|
if (ZYAN_SUCCESS(status)) {
|
||||||
ZydisFormatter formatter;
|
ZydisFormatter formatter;
|
||||||
ZydisFormatterInit(&formatter, ZYDIS_FORMATTER_STYLE_INTEL);
|
ZydisFormatterInit(&formatter, ZYDIS_FORMATTER_STYLE_INTEL);
|
||||||
@ -87,23 +62,23 @@ static std::string DisassembleInstruction(void* code_address) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void SignalHandler(int sig, siginfo_t* info, void* raw_context) {
|
static void SignalHandler(int sig, siginfo_t* info, void* raw_context) {
|
||||||
const auto* ctx = static_cast<ucontext_t*>(raw_context);
|
|
||||||
const auto* signals = Signals::Instance();
|
const auto* signals = Signals::Instance();
|
||||||
|
|
||||||
auto* code_address = CODE_ADDRESS(ctx);
|
auto* code_address = Common::GetRip(raw_context);
|
||||||
|
|
||||||
switch (sig) {
|
switch (sig) {
|
||||||
case SIGSEGV:
|
case SIGSEGV:
|
||||||
case SIGBUS:
|
case SIGBUS: {
|
||||||
if (const bool is_write = IS_WRITE_ERROR(ctx);
|
const bool is_write = Common::IsWriteError(raw_context);
|
||||||
!signals->DispatchAccessViolation(code_address, info->si_addr, is_write)) {
|
if (!signals->DispatchAccessViolation(raw_context, info->si_addr)) {
|
||||||
UNREACHABLE_MSG("Unhandled access violation at code address {}: {} address {}",
|
UNREACHABLE_MSG("Unhandled access violation at code address {}: {} address {}",
|
||||||
fmt::ptr(code_address), is_write ? "Write to" : "Read from",
|
fmt::ptr(code_address), is_write ? "Write to" : "Read from",
|
||||||
fmt::ptr(info->si_addr));
|
fmt::ptr(info->si_addr));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case SIGILL:
|
case SIGILL:
|
||||||
if (!signals->DispatchIllegalInstruction(code_address)) {
|
if (!signals->DispatchIllegalInstruction(raw_context)) {
|
||||||
UNREACHABLE_MSG("Unhandled illegal instruction at code address {}: {}",
|
UNREACHABLE_MSG("Unhandled illegal instruction at code address {}: {}",
|
||||||
fmt::ptr(code_address), DisassembleInstruction(code_address));
|
fmt::ptr(code_address), DisassembleInstruction(code_address));
|
||||||
}
|
}
|
||||||
@ -150,19 +125,18 @@ SignalDispatch::~SignalDispatch() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SignalDispatch::DispatchAccessViolation(void* code_address, void* fault_address,
|
bool SignalDispatch::DispatchAccessViolation(void* context, void* fault_address) const {
|
||||||
bool is_write) const {
|
|
||||||
for (const auto& [handler, _] : access_violation_handlers) {
|
for (const auto& [handler, _] : access_violation_handlers) {
|
||||||
if (handler(code_address, fault_address, is_write)) {
|
if (handler(context, fault_address)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SignalDispatch::DispatchIllegalInstruction(void* code_address) const {
|
bool SignalDispatch::DispatchIllegalInstruction(void* context) const {
|
||||||
for (const auto& [handler, _] : illegal_instruction_handlers) {
|
for (const auto& [handler, _] : illegal_instruction_handlers) {
|
||||||
if (handler(code_address)) {
|
if (handler(context)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
using AccessViolationHandler = bool (*)(void* code_address, void* fault_address, bool is_write);
|
using AccessViolationHandler = bool (*)(void* context, void* fault_address);
|
||||||
using IllegalInstructionHandler = bool (*)(void* code_address);
|
using IllegalInstructionHandler = bool (*)(void* context);
|
||||||
|
|
||||||
/// Receives OS signals and dispatches to the appropriate handlers.
|
/// Receives OS signals and dispatches to the appropriate handlers.
|
||||||
class SignalDispatch {
|
class SignalDispatch {
|
||||||
@ -28,10 +28,10 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Dispatches an access violation signal, returning whether it was successfully handled.
|
/// Dispatches an access violation signal, returning whether it was successfully handled.
|
||||||
bool DispatchAccessViolation(void* code_address, void* fault_address, bool is_write) const;
|
bool DispatchAccessViolation(void* context, void* fault_address) const;
|
||||||
|
|
||||||
/// Dispatches an illegal instruction signal, returning whether it was successfully handled.
|
/// Dispatches an illegal instruction signal, returning whether it was successfully handled.
|
||||||
bool DispatchIllegalInstruction(void* code_address) const;
|
bool DispatchIllegalInstruction(void* context) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|