Merge branch 'shadps4-emu:main' into disable-heap-malloc
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/extensions.json
|
||||
*.code-workspace
|
||||
/CMakeUserPresets.json
|
||||
/compile_commands.json
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
@ -409,6 +411,6 @@ FodyWeavers.xsd
|
|||
/out/*
|
||||
/third-party/out/*
|
||||
/src/common/scm_rev.cpp
|
||||
|
||||
|
||||
# for macOS
|
||||
**/.DS_Store
|
||||
**/.DS_Store
|
||||
|
|
2
.gitmodules
vendored
|
@ -85,6 +85,7 @@
|
|||
[submodule "externals/half"]
|
||||
path = externals/half
|
||||
url = https://github.com/ROCm/half.git
|
||||
shallow = true
|
||||
[submodule "externals/dear_imgui"]
|
||||
path = externals/dear_imgui
|
||||
url = https://github.com/shadps4-emu/ext-imgui.git
|
||||
|
@ -93,3 +94,4 @@
|
|||
[submodule "externals/pugixml"]
|
||||
path = externals/pugixml
|
||||
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)
|
||||
git_describe(GIT_DESC --always --long --dirty)
|
||||
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)
|
||||
|
||||
|
@ -143,7 +144,7 @@ add_subdirectory(externals)
|
|||
include_directories(src)
|
||||
|
||||
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()
|
||||
set(CMAKE_AUTORCC ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
@ -358,8 +359,9 @@ set(COMMON src/common/logging/backend.cpp
|
|||
src/common/config.h
|
||||
src/common/cstring.h
|
||||
src/common/debug.h
|
||||
src/common/disassembler.cpp
|
||||
src/common/disassembler.h
|
||||
src/common/decoder.cpp
|
||||
src/common/decoder.h
|
||||
src/common/elf_info.h
|
||||
src/common/endian.h
|
||||
src/common/enum.h
|
||||
src/common/io_file.cpp
|
||||
|
@ -377,6 +379,8 @@ set(COMMON src/common/logging/backend.cpp
|
|||
src/common/polyfill_thread.h
|
||||
src/common/rdtsc.cpp
|
||||
src/common/rdtsc.h
|
||||
src/common/signal_context.h
|
||||
src/common/signal_context.cpp
|
||||
src/common/singleton.h
|
||||
src/common/slot_vector.h
|
||||
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/runtime_info.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.h
|
||||
src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp
|
||||
|
@ -648,8 +653,12 @@ qt_add_resources(RESOURCE_FILES src/shadps4.qrc)
|
|||
set(QT_GUI src/qt_gui/about_dialog.cpp
|
||||
src/qt_gui/about_dialog.h
|
||||
src/qt_gui/about_dialog.ui
|
||||
src/qt_gui/background_music_player.cpp
|
||||
src/qt_gui/background_music_player.h
|
||||
src/qt_gui/cheats_patches.cpp
|
||||
src/qt_gui/cheats_patches.h
|
||||
src/qt_gui/check_update.cpp
|
||||
src/qt_gui/check_update.h
|
||||
src/qt_gui/main_window_ui.h
|
||||
src/qt_gui/main_window.cpp
|
||||
src/qt_gui/main_window.h
|
||||
|
@ -753,7 +762,7 @@ else()
|
|||
endif()
|
||||
|
||||
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)
|
||||
endif()
|
||||
|
||||
|
@ -809,6 +818,11 @@ add_subdirectory(${HOST_SHADERS_INCLUDE})
|
|||
add_dependencies(shadps4 host_shaders)
|
||||
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)
|
||||
set_target_properties(shadps4 PROPERTIES
|
||||
# WIN32_EXECUTABLE ON
|
||||
|
@ -824,4 +838,4 @@ if (UNIX AND NOT APPLE)
|
|||
find_package(OpenSSL REQUIRED)
|
||||
target_link_libraries(shadps4 PRIVATE ${OPENSSL_LIBRARIES})
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
|
@ -9,7 +9,8 @@
|
|||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "",
|
||||
"ctestCommandArgs": "",
|
||||
"inheritEnvironments": [ "clang_cl_x64_x64" ]
|
||||
"inheritEnvironments": [ "clang_cl_x64_x64" ],
|
||||
"intelliSenseMode": "windows-clang-x64"
|
||||
},
|
||||
{
|
||||
"name": "x64-Clang-Debug",
|
||||
|
@ -20,7 +21,8 @@
|
|||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "",
|
||||
"ctestCommandArgs": "",
|
||||
"inheritEnvironments": [ "clang_cl_x64_x64" ]
|
||||
"inheritEnvironments": [ "clang_cl_x64_x64" ],
|
||||
"intelliSenseMode": "windows-clang-x64"
|
||||
},
|
||||
{
|
||||
"name": "x64-Clang-RelWithDebInfo",
|
||||
|
@ -31,7 +33,8 @@
|
|||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "",
|
||||
"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.
|
21
README.md
|
@ -26,10 +26,10 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||
|
||||
<p align="center">
|
||||
<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/Bloodborne.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/We are DOOMED.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/2.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/4.png" width="400">
|
||||
</p>
|
||||
|
||||
# General information
|
||||
|
@ -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/).
|
||||
|
||||
For those who'd like to donate to the project, we now have a [Kofi page!](https://ko-fi.com/shadps4)
|
||||
|
||||
# Status
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 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
|
||||
|
||||
|
@ -69,7 +71,8 @@ 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).
|
||||
|
||||
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
|
||||
|
||||
|
@ -172,12 +175,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.
|
||||
|
||||
|
||||
# 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
|
||||
|
||||
- [**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
|
||||
|
||||
1. Go to <https://github.com/shadps4-emu/shadPS4/actions> and make sure you are logged into your GitHub account (important!)
|
||||
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). 
|
||||
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).
|
||||

|
||||
|
||||
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. 
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## 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`
|
||||
|
||||
- `[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
|
||||
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`
|
||||
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`
|
||||
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"`
|
||||
|
@ -94,7 +94,7 @@ ARM64-based computers, follow:
|
|||
1. Open "MSYS2 CLANGARM64" from your new applications
|
||||
2. Run `pacman -Syu`, let it complete;
|
||||
3. Run `pacman -S --needed git mingw-w64-clang-aarch64-binutils mingw-w64-clang-aarch64-clang mingw-w64-clang-aarch64-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`
|
||||
5. Run `cd shadPS4`
|
||||
6. Run `cmake -S . -B build -DCMAKE_C_COMPILER="clang.exe" -DCMAKE_CXX_COMPILER="clang++.exe" -DCMAKE_CXX_FLAGS="-O2 -march=native"`
|
||||
|
|
|
@ -1,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
|
||||
=================
|
||||
- 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
|
|
@ -3,24 +3,47 @@
|
|||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <common/version.h>
|
||||
#include <fmt/core.h>
|
||||
#include <fmt/xchar.h> // for wstring support
|
||||
#include <toml.hpp>
|
||||
#include "common/logging/formatter.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 {
|
||||
|
||||
static bool isNeo = false;
|
||||
static bool isFullscreen = false;
|
||||
static bool playBGM = false;
|
||||
static u32 screenWidth = 1280;
|
||||
static u32 screenHeight = 720;
|
||||
static s32 gpuId = -1; // Vulkan physical device index. Set to negative for auto select
|
||||
static std::string logFilter;
|
||||
static std::string logType = "async";
|
||||
static std::string userName = "shadPS4";
|
||||
static std::string updateChannel;
|
||||
static bool useSpecialPad = false;
|
||||
static int specialPadClass = 1;
|
||||
static bool isDebugDump = false;
|
||||
static bool isShowSplash = false;
|
||||
static bool isAutoUpdate = false;
|
||||
static bool isNullGpu = false;
|
||||
static bool shouldCopyGPUBuffers = false;
|
||||
static bool shouldDumpShaders = false;
|
||||
|
@ -34,7 +57,7 @@ static bool vkMarkers = false;
|
|||
static bool vkCrashDiagnostic = false;
|
||||
|
||||
// Gui
|
||||
std::string settings_install_dir = "";
|
||||
std::filesystem::path settings_install_dir = {};
|
||||
u32 main_window_geometry_x = 400;
|
||||
u32 main_window_geometry_y = 400;
|
||||
u32 main_window_geometry_w = 1280;
|
||||
|
@ -62,6 +85,10 @@ bool isFullscreenMode() {
|
|||
return isFullscreen;
|
||||
}
|
||||
|
||||
bool getPlayBGM() {
|
||||
return playBGM;
|
||||
}
|
||||
|
||||
u32 getScreenWidth() {
|
||||
return screenWidth;
|
||||
}
|
||||
|
@ -86,6 +113,10 @@ std::string getUserName() {
|
|||
return userName;
|
||||
}
|
||||
|
||||
std::string getUpdateChannel() {
|
||||
return updateChannel;
|
||||
}
|
||||
|
||||
bool getUseSpecialPad() {
|
||||
return useSpecialPad;
|
||||
}
|
||||
|
@ -102,6 +133,10 @@ bool showSplash() {
|
|||
return isShowSplash;
|
||||
}
|
||||
|
||||
bool autoUpdate() {
|
||||
return isAutoUpdate;
|
||||
}
|
||||
|
||||
bool nullGpu() {
|
||||
return isNullGpu;
|
||||
}
|
||||
|
@ -170,6 +205,10 @@ void setShowSplash(bool enable) {
|
|||
isShowSplash = enable;
|
||||
}
|
||||
|
||||
void setAutoUpdate(bool enable) {
|
||||
isAutoUpdate = enable;
|
||||
}
|
||||
|
||||
void setNullGpu(bool enable) {
|
||||
isNullGpu = enable;
|
||||
}
|
||||
|
@ -206,6 +245,10 @@ void setFullscreenMode(bool enable) {
|
|||
isFullscreen = enable;
|
||||
}
|
||||
|
||||
void setPlayBGM(bool enable) {
|
||||
playBGM = enable;
|
||||
}
|
||||
|
||||
void setLanguage(u32 language) {
|
||||
m_language = language;
|
||||
}
|
||||
|
@ -226,6 +269,10 @@ void setUserName(const std::string& type) {
|
|||
userName = type;
|
||||
}
|
||||
|
||||
void setUpdateChannel(const std::string& type) {
|
||||
updateChannel = type;
|
||||
}
|
||||
|
||||
void setUseSpecialPad(bool use) {
|
||||
useSpecialPad = use;
|
||||
}
|
||||
|
@ -240,7 +287,7 @@ void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) {
|
|||
main_window_geometry_w = w;
|
||||
main_window_geometry_h = h;
|
||||
}
|
||||
void setGameInstallDir(const std::string& dir) {
|
||||
void setGameInstallDir(const std::filesystem::path& dir) {
|
||||
settings_install_dir = dir;
|
||||
}
|
||||
void setMainWindowTheme(u32 theme) {
|
||||
|
@ -296,7 +343,7 @@ u32 getMainWindowGeometryW() {
|
|||
u32 getMainWindowGeometryH() {
|
||||
return main_window_geometry_h;
|
||||
}
|
||||
std::string getGameInstallDir() {
|
||||
std::filesystem::path getGameInstallDir() {
|
||||
return settings_install_dir;
|
||||
}
|
||||
u32 getMainWindowTheme() {
|
||||
|
@ -351,7 +398,10 @@ void load(const std::filesystem::path& path) {
|
|||
toml::value data;
|
||||
|
||||
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) {
|
||||
fmt::print("Got exception trying to load config file. Exception: {}\n", ex.what());
|
||||
return;
|
||||
|
@ -361,10 +411,17 @@ void load(const std::filesystem::path& path) {
|
|||
|
||||
isNeo = toml::find_or<bool>(general, "isPS4Pro", false);
|
||||
isFullscreen = toml::find_or<bool>(general, "Fullscreen", false);
|
||||
playBGM = toml::find_or<bool>(general, "playBGM", false);
|
||||
logFilter = toml::find_or<std::string>(general, "logFilter", "");
|
||||
logType = toml::find_or<std::string>(general, "logType", "sync");
|
||||
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);
|
||||
isAutoUpdate = toml::find_or<bool>(general, "autoUpdate", false);
|
||||
}
|
||||
|
||||
if (data.contains("Input")) {
|
||||
|
@ -414,7 +471,7 @@ void load(const std::filesystem::path& path) {
|
|||
mw_themes = toml::find_or<int>(gui, "theme", 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);
|
||||
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_y = toml::find_or<int>(gui, "geometry_y", 0);
|
||||
main_window_geometry_w = toml::find_or<int>(gui, "geometry_w", 0);
|
||||
|
@ -438,25 +495,30 @@ void save(const std::filesystem::path& path) {
|
|||
std::error_code error;
|
||||
if (std::filesystem::exists(path, error)) {
|
||||
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) {
|
||||
fmt::print("Exception trying to parse config file. Exception: {}\n", ex.what());
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (error) {
|
||||
fmt::print("Filesystem error accessing {} (error: {})\n", path.string(),
|
||||
error.message().c_str());
|
||||
fmt::print("Filesystem error: {}\n", error.message());
|
||||
}
|
||||
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"]["Fullscreen"] = isFullscreen;
|
||||
data["General"]["playBGM"] = playBGM;
|
||||
data["General"]["logFilter"] = logFilter;
|
||||
data["General"]["logType"] = logType;
|
||||
data["General"]["userName"] = userName;
|
||||
data["General"]["updateChannel"] = updateChannel;
|
||||
data["General"]["showSplash"] = isShowSplash;
|
||||
data["General"]["autoUpdate"] = isAutoUpdate;
|
||||
data["Input"]["useSpecialPad"] = useSpecialPad;
|
||||
data["Input"]["specialPadClass"] = specialPadClass;
|
||||
data["GPU"]["screenWidth"] = screenWidth;
|
||||
|
@ -482,7 +544,7 @@ void save(const std::filesystem::path& path) {
|
|||
data["GUI"]["gameTableMode"] = m_table_mode;
|
||||
data["GUI"]["mw_width"] = m_window_size_W;
|
||||
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_y"] = main_window_geometry_y;
|
||||
data["GUI"]["geometry_w"] = main_window_geometry_w;
|
||||
|
@ -502,15 +564,22 @@ void save(const std::filesystem::path& path) {
|
|||
void setDefaultValues() {
|
||||
isNeo = false;
|
||||
isFullscreen = false;
|
||||
playBGM = false;
|
||||
screenWidth = 1280;
|
||||
screenHeight = 720;
|
||||
logFilter = "";
|
||||
logType = "async";
|
||||
userName = "shadPS4";
|
||||
if (Common::isRelease) {
|
||||
updateChannel = "Release";
|
||||
} else {
|
||||
updateChannel = "Nightly";
|
||||
}
|
||||
useSpecialPad = false;
|
||||
specialPadClass = 1;
|
||||
isDebugDump = false;
|
||||
isShowSplash = false;
|
||||
isAutoUpdate = false;
|
||||
isNullGpu = false;
|
||||
shouldDumpShaders = false;
|
||||
shouldDumpPM4 = false;
|
||||
|
|
|
@ -13,9 +13,11 @@ void save(const std::filesystem::path& path);
|
|||
|
||||
bool isNeoMode();
|
||||
bool isFullscreenMode();
|
||||
bool getPlayBGM();
|
||||
std::string getLogFilter();
|
||||
std::string getLogType();
|
||||
std::string getUserName();
|
||||
std::string getUpdateChannel();
|
||||
|
||||
bool getUseSpecialPad();
|
||||
int getSpecialPadClass();
|
||||
|
@ -26,6 +28,7 @@ s32 getGpuId();
|
|||
|
||||
bool debugDump();
|
||||
bool showSplash();
|
||||
bool autoUpdate();
|
||||
bool nullGpu();
|
||||
bool copyGPUCmdBuffers();
|
||||
bool dumpShaders();
|
||||
|
@ -35,6 +38,7 @@ u32 vblankDiv();
|
|||
|
||||
void setDebugDump(bool enable);
|
||||
void setShowSplash(bool enable);
|
||||
void setAutoUpdate(bool enable);
|
||||
void setNullGpu(bool enable);
|
||||
void setCopyGPUCmdBuffers(bool enable);
|
||||
void setDumpShaders(bool enable);
|
||||
|
@ -44,9 +48,11 @@ void setGpuId(s32 selectedGpuId);
|
|||
void setScreenWidth(u32 width);
|
||||
void setScreenHeight(u32 height);
|
||||
void setFullscreenMode(bool enable);
|
||||
void setPlayBGM(bool enable);
|
||||
void setLanguage(u32 language);
|
||||
void setNeoMode(bool enable);
|
||||
void setUserName(const std::string& type);
|
||||
void setUpdateChannel(const std::string& type);
|
||||
|
||||
void setUseSpecialPad(bool use);
|
||||
void setSpecialPadClass(int type);
|
||||
|
@ -66,7 +72,7 @@ bool vkCrashDiagnosticEnabled();
|
|||
|
||||
// Gui
|
||||
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 setIconSize(u32 size);
|
||||
void setIconSizeGrid(u32 size);
|
||||
|
@ -84,7 +90,7 @@ u32 getMainWindowGeometryX();
|
|||
u32 getMainWindowGeometryY();
|
||||
u32 getMainWindowGeometryW();
|
||||
u32 getMainWindowGeometryH();
|
||||
std::string getGameInstallDir();
|
||||
std::filesystem::path getGameInstallDir();
|
||||
u32 getMainWindowTheme();
|
||||
u32 getIconSize();
|
||||
u32 getIconSizeGrid();
|
||||
|
|
|
@ -2,18 +2,18 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include "common/disassembler.h"
|
||||
#include "common/decoder.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
Disassembler::Disassembler() {
|
||||
DecoderImpl::DecoderImpl() {
|
||||
ZydisDecoderInit(&m_decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64);
|
||||
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;
|
||||
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT_VISIBLE];
|
||||
ZyanStatus status =
|
||||
|
@ -25,8 +25,8 @@ void Disassembler::printInstruction(void* code, u64 address) {
|
|||
}
|
||||
}
|
||||
|
||||
void Disassembler::printInst(ZydisDecodedInstruction& inst, ZydisDecodedOperand* operands,
|
||||
u64 address) {
|
||||
void DecoderImpl::printInst(ZydisDecodedInstruction& inst, ZydisDecodedOperand* operands,
|
||||
u64 address) {
|
||||
const int bufLen = 256;
|
||||
char szBuffer[bufLen];
|
||||
ZydisFormatterFormatInstruction(&m_formatter, &inst, operands, inst.operand_count_visible,
|
||||
|
@ -34,4 +34,9 @@ void Disassembler::printInst(ZydisDecodedInstruction& inst, ZydisDecodedOperand*
|
|||
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
|
|
@ -4,21 +4,26 @@
|
|||
#pragma once
|
||||
|
||||
#include <Zydis/Zydis.h>
|
||||
#include "common/singleton.h"
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
class Disassembler {
|
||||
class DecoderImpl {
|
||||
public:
|
||||
Disassembler();
|
||||
~Disassembler();
|
||||
DecoderImpl();
|
||||
~DecoderImpl();
|
||||
|
||||
void printInst(ZydisDecodedInstruction& inst, ZydisDecodedOperand* operands, u64 address);
|
||||
void printInstruction(void* code, u64 address);
|
||||
ZyanStatus decodeInstruction(ZydisDecodedInstruction& inst, ZydisDecodedOperand* operands,
|
||||
void* data, u64 size = 15);
|
||||
|
||||
private:
|
||||
ZydisDecoder m_decoder;
|
||||
ZydisFormatter m_formatter;
|
||||
};
|
||||
|
||||
using Decoder = Common::Singleton<DecoderImpl>;
|
||||
|
||||
} // 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
|
||||
|
||||
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() {
|
||||
|
||||
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_parse_result result = doc.load_file(filePath.c_str());
|
||||
|
@ -187,8 +187,8 @@ void OnGameLoaded() {
|
|||
|
||||
#ifdef ENABLE_QT_GUI
|
||||
// We use the QT headers for the xml and json parsing, this define is only true on QT builds
|
||||
QString patchDir =
|
||||
QString::fromStdString(Common::FS::GetUserPath(Common::FS::PathType::PatchesDir).string());
|
||||
QString patchDir;
|
||||
Common::FS::PathToQString(patchDir, Common::FS::GetUserPath(Common::FS::PathType::PatchesDir));
|
||||
QString repositories[] = {"GoldHEN", "shadPS4"};
|
||||
|
||||
for (const QString& repository : repositories) {
|
||||
|
|
|
@ -22,6 +22,10 @@
|
|||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_QT_GUI
|
||||
#include <QString>
|
||||
#endif
|
||||
|
||||
namespace Common::FS {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
#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
|
|
@ -6,6 +6,10 @@
|
|||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
#ifdef ENABLE_QT_GUI
|
||||
class QString; // to avoid including <QString> in this header
|
||||
#endif
|
||||
|
||||
namespace Common::FS {
|
||||
|
||||
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);
|
||||
|
||||
#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
|
||||
|
|
|
@ -6,12 +6,14 @@
|
|||
#define GIT_REV "@GIT_REV@"
|
||||
#define GIT_BRANCH "@GIT_BRANCH@"
|
||||
#define GIT_DESC "@GIT_DESC@"
|
||||
#define BUILD_DATE "@BUILD_DATE@"
|
||||
|
||||
namespace Common {
|
||||
|
||||
const char g_scm_rev[] = GIT_REV;
|
||||
const char g_scm_branch[] = GIT_BRANCH;
|
||||
const char g_scm_desc[] = GIT_DESC;
|
||||
const char g_scm_date[] = BUILD_DATE;
|
||||
|
||||
} // namespace
|
||||
|
||||
|
|
|
@ -8,5 +8,6 @@ namespace Common {
|
|||
extern const char g_scm_rev[];
|
||||
extern const char g_scm_branch[];
|
||||
extern const char g_scm_desc[];
|
||||
extern const char g_scm_date[];
|
||||
|
||||
} // 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
|
||||
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/thread.h"
|
||||
#include "ntapi.h"
|
||||
#ifdef __APPLE__
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_time.h>
|
||||
|
@ -102,6 +104,16 @@ void SetCurrentThreadPriority(ThreadPriority new_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
|
||||
|
||||
void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
||||
|
@ -122,6 +134,10 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
|||
pthread_setschedparam(this_thread, scheduling_type, ¶ms);
|
||||
}
|
||||
|
||||
static void AccurateSleep(std::chrono::nanoseconds duration) {
|
||||
std::this_thread::sleep_for(duration);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
@ -164,4 +180,22 @@ void SetCurrentThreadName(const char*) {
|
|||
|
||||
#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
|
||||
|
|
|
@ -23,4 +23,18 @@ void SetCurrentThreadPriority(ThreadPriority new_priority);
|
|||
|
||||
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
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
namespace Common {
|
||||
|
||||
constexpr char VERSION[] = "0.2.1 WIP";
|
||||
constexpr char VERSION[] = "0.3.1 WIP";
|
||||
constexpr bool isRelease = false;
|
||||
|
||||
} // namespace Common
|
||||
|
|
|
@ -72,7 +72,8 @@ struct AddressSpace::Impl {
|
|||
}
|
||||
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.
|
||||
system_managed_base = virtual_base;
|
||||
|
|
|
@ -7,8 +7,12 @@
|
|||
#include <set>
|
||||
#include <Zydis/Zydis.h>
|
||||
#include <xbyak/xbyak.h>
|
||||
#include <xbyak/xbyak_util.h>
|
||||
#include "common/alignment.h"
|
||||
#include "common/arch.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/decoder.h"
|
||||
#include "common/signal_context.h"
|
||||
#include "common/types.h"
|
||||
#include "core/signals.h"
|
||||
#include "core/tls.h"
|
||||
|
@ -26,6 +30,16 @@
|
|||
|
||||
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 {
|
||||
|
||||
static Xbyak::Reg ZydisToXbyakRegister(const ZydisRegister reg) {
|
||||
|
@ -586,6 +600,273 @@ static void GenerateTcbAccess(const ZydisDecodedOperand* operands, Xbyak::CodeGe
|
|||
|
||||
#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 InstructionGenerator = void (*)(const ZydisDecodedOperand*, Xbyak::CodeGenerator&);
|
||||
struct PatchInfo {
|
||||
|
@ -607,6 +888,9 @@ static const std::unordered_map<ZydisMnemonic, PatchInfo> Patches = {
|
|||
{ZYDIS_MNEMONIC_MOV, {FilterTcbAccess, GenerateTcbAccess, false}},
|
||||
#endif
|
||||
|
||||
{ZYDIS_MNEMONIC_EXTRQ, {FilterNoSSE4a, GenerateEXTRQ, true}},
|
||||
{ZYDIS_MNEMONIC_INSERTQ, {FilterNoSSE4a, GenerateINSERTQ, true}},
|
||||
|
||||
#ifdef __APPLE__
|
||||
// Patches for instruction sets not supported by Rosetta 2.
|
||||
// BMI1
|
||||
|
@ -622,7 +906,6 @@ static const std::unordered_map<ZydisMnemonic, PatchInfo> Patches = {
|
|||
};
|
||||
|
||||
static std::once_flag init_flag;
|
||||
static ZydisDecoder instr_decoder;
|
||||
|
||||
struct PatchModule {
|
||||
/// 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) {
|
||||
ZydisDecodedInstruction instruction;
|
||||
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
|
||||
const auto status =
|
||||
ZydisDecoderDecodeFull(&instr_decoder, code, module->end - code, &instruction, operands);
|
||||
const auto status = Common::Decoder::Instance()->decodeInstruction(instruction, operands, code,
|
||||
module->end - code);
|
||||
if (!ZYAN_SUCCESS(status)) {
|
||||
return std::make_pair(false, 1);
|
||||
}
|
||||
|
||||
if (Patches.contains(instruction.mnemonic)) {
|
||||
const auto& patch_info = Patches.at(instruction.mnemonic);
|
||||
bool needs_trampoline = patch_info.trampoline;
|
||||
if (patch_info.filter(operands)) {
|
||||
auto& patch_gen = module->patch_gen;
|
||||
|
||||
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.
|
||||
patch_gen.reset();
|
||||
patch_gen.setSize(code - patch_gen.getCode());
|
||||
|
||||
if (patch_info.trampoline) {
|
||||
if (needs_trampoline) {
|
||||
auto& trampoline_gen = module->trampoline_gen;
|
||||
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);
|
||||
}
|
||||
|
||||
#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 {:x} 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 {:x} 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) {
|
||||
auto* code = static_cast<u8*>(code_address);
|
||||
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) {
|
||||
return TryPatchJit(code_address);
|
||||
static bool PatchesAccessViolationHandler(void* context, void* /* fault_address */) {
|
||||
return TryPatchJit(Common::GetRip(context));
|
||||
}
|
||||
|
||||
static bool PatchesIllegalInstructionHandler(void* code_address) {
|
||||
return TryPatchJit(code_address);
|
||||
static bool PatchesIllegalInstructionHandler(void* context) {
|
||||
void* code_address = Common::GetRip(context);
|
||||
if (!TryPatchJit(code_address)) {
|
||||
return TryExecuteIllegalInstruction(context, code_address);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void PatchesInit() {
|
||||
ZydisDecoderInit(&instr_decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64);
|
||||
|
||||
if (!Patches.empty()) {
|
||||
auto* signals = Signals::Instance();
|
||||
// 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) {
|
||||
current_dir = extractPaths[table.inode];
|
||||
}
|
||||
extractPaths[table.inode] =
|
||||
current_dir.string() / std::filesystem::path(table.name);
|
||||
extractPaths[table.inode] = current_dir / std::filesystem::path(table.name);
|
||||
|
||||
if (table.type == PFS_FILE || table.type == PFS_DIR) {
|
||||
if (table.type == PFS_DIR) { // Create dirs.
|
||||
|
@ -402,7 +401,7 @@ void PKG::ExtractFiles(const int index) {
|
|||
int bsize = iNodeBuf[inode_number].Size;
|
||||
|
||||
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.
|
||||
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();
|
||||
|
||||
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 {
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
#define STBI_NO_STDIO
|
||||
#include "externals/stb_image.h"
|
||||
|
||||
bool Splash::Open(const std::string& filepath) {
|
||||
ASSERT_MSG(filepath.ends_with(".png"), "Unexpected file format passed");
|
||||
bool Splash::Open(const std::filesystem::path& filepath) {
|
||||
ASSERT_MSG(filepath.stem().string() != "png", "Unexpected file format passed");
|
||||
|
||||
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
|
||||
if (!file.IsOpen()) {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "common/types.h"
|
||||
|
@ -22,7 +23,7 @@ public:
|
|||
Splash() = default;
|
||||
~Splash() = default;
|
||||
|
||||
bool Open(const std::string& filepath);
|
||||
bool Open(const std::filesystem::path& filepath);
|
||||
[[nodiscard]] bool IsLoaded() const {
|
||||
return img_data.size();
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ static void removePadding(std::vector<u8>& vec) {
|
|||
}
|
||||
|
||||
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/";
|
||||
if (!std::filesystem::exists(gameSysDir)) {
|
||||
return false;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <cmath>
|
||||
|
||||
#include "app_content.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/io_file.h"
|
||||
#include "common/logging/log.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();
|
||||
|
||||
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;
|
||||
if (std::filesystem::exists(addon_path)) {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(addon_path)) {
|
||||
|
|
|
@ -161,7 +161,20 @@ struct SceAvPlayerFileReplacement {
|
|||
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 {
|
||||
void* object_ptr;
|
||||
|
@ -275,18 +288,6 @@ enum SceAvPlayerAvSyncMode {
|
|||
|
||||
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);
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
#include "avplayer_source.h"
|
||||
#include "avplayer_state.h"
|
||||
|
||||
#include "common/singleton.h"
|
||||
#include "common/thread.h"
|
||||
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/kernel/time_management.h"
|
||||
#include "core/linker.h"
|
||||
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
|
@ -16,8 +17,8 @@ namespace Libraries::AvPlayer {
|
|||
|
||||
using namespace Kernel;
|
||||
|
||||
void PS4_SYSV_ABI AvPlayerState::AutoPlayEventCallback(void* opaque, s32 event_id, s32 source_id,
|
||||
void* event_data) {
|
||||
void PS4_SYSV_ABI AvPlayerState::AutoPlayEventCallback(void* opaque, SceAvPlayerEvents event_id,
|
||||
s32 source_id, void* event_data) {
|
||||
auto const self = reinterpret_cast<AvPlayerState*>(opaque);
|
||||
|
||||
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 ptr = self->m_event_replacement.object_ptr;
|
||||
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;
|
||||
if (callback) {
|
||||
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:
|
||||
// Event Replacement
|
||||
static void PS4_SYSV_ABI AutoPlayEventCallback(void* handle, s32 event_id, s32 source_id,
|
||||
void* event_data);
|
||||
static void PS4_SYSV_ABI AutoPlayEventCallback(void* handle, SceAvPlayerEvents event_id,
|
||||
s32 source_id, void* event_data);
|
||||
|
||||
void OnWarning(u32 id) override;
|
||||
void OnError() override;
|
||||
|
|
|
@ -1,40 +1,166 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// 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 "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "error_codes.h"
|
||||
#include "core/libraries/system/commondialog.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 {
|
||||
|
||||
static OrbisErrorDialogStatus g_error_dlg_status =
|
||||
OrbisErrorDialogStatus::ORBIS_ERROR_DIALOG_STATUS_NONE;
|
||||
using CommonDialog::Error;
|
||||
using CommonDialog::Result;
|
||||
using CommonDialog::Status;
|
||||
|
||||
int PS4_SYSV_ABI sceErrorDialogClose() {
|
||||
g_error_dlg_status = OrbisErrorDialogStatus::ORBIS_ERROR_DIALOG_STATUS_FINISHED;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
class ErrorDialogUi final : public ImGui::Layer {
|
||||
bool first_render{false};
|
||||
|
||||
OrbisErrorDialogStatus PS4_SYSV_ABI sceErrorDialogGetStatus() {
|
||||
return g_error_dlg_status;
|
||||
}
|
||||
Status* status{nullptr};
|
||||
std::string err_message{};
|
||||
|
||||
int PS4_SYSV_ABI sceErrorDialogInitialize(OrbisErrorDialogParam* param) {
|
||||
if (g_error_dlg_status == OrbisErrorDialogStatus::ORBIS_ERROR_DIALOG_STATUS_INITIALIZED) {
|
||||
LOG_ERROR(Lib_ErrorDialog, "Error dialog is already at init mode");
|
||||
return ORBIS_ERROR_DIALOG_ERROR_ALREADY_INITIALIZED;
|
||||
public:
|
||||
explicit ErrorDialogUi(Status* status = nullptr, std::string err_message = "")
|
||||
: status(status), err_message(std::move(err_message)) {
|
||||
if (status && *status == Status::RUNNING) {
|
||||
first_render = true;
|
||||
AddLayer(this);
|
||||
}
|
||||
}
|
||||
g_error_dlg_status = OrbisErrorDialogStatus::ORBIS_ERROR_DIALOG_STATUS_INITIALIZED;
|
||||
return ORBIS_OK;
|
||||
~ErrorDialogUi() override {
|
||||
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) {
|
||||
LOG_ERROR(Lib_ErrorDialog, "size = {} errorcode = {:#x} userid = {}", param->size,
|
||||
param->errorCode, param->userId);
|
||||
g_error_dlg_status = OrbisErrorDialogStatus::ORBIS_ERROR_DIALOG_STATUS_RUNNING;
|
||||
return ORBIS_OK;
|
||||
Status PS4_SYSV_ABI sceErrorDialogGetStatus() {
|
||||
LOG_TRACE(Lib_ErrorDialog, "called status={}", magic_enum::enum_name(g_status));
|
||||
return g_status;
|
||||
}
|
||||
|
||||
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() {
|
||||
|
@ -47,20 +173,21 @@ int PS4_SYSV_ABI sceErrorDialogOpenWithReport() {
|
|||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceErrorDialogTerminate() {
|
||||
if (g_error_dlg_status == OrbisErrorDialogStatus::ORBIS_ERROR_DIALOG_STATUS_NONE) {
|
||||
LOG_ERROR(Lib_ErrorDialog, "Error dialog hasn't initialized");
|
||||
return ORBIS_ERROR_DIALOG_ERROR_NOT_INITIALIZED;
|
||||
Error PS4_SYSV_ABI sceErrorDialogTerminate() {
|
||||
LOG_DEBUG(Lib_ErrorDialog, "called");
|
||||
if (g_status == Status::RUNNING) {
|
||||
sceErrorDialogClose();
|
||||
}
|
||||
g_error_dlg_status = OrbisErrorDialogStatus::ORBIS_ERROR_DIALOG_STATUS_NONE;
|
||||
return ORBIS_OK;
|
||||
if (g_status == Status::NONE) {
|
||||
return Error::NOT_INITIALIZED;
|
||||
}
|
||||
g_status = Status::NONE;
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
OrbisErrorDialogStatus PS4_SYSV_ABI sceErrorDialogUpdateStatus() {
|
||||
// TODO when imgui dialog is done this will loop until ORBIS_ERROR_DIALOG_STATUS_FINISHED
|
||||
// This should be done calling sceErrorDialogClose but since we don't have a dialog we finish it
|
||||
// here
|
||||
return OrbisErrorDialogStatus::ORBIS_ERROR_DIALOG_STATUS_FINISHED;
|
||||
Status PS4_SYSV_ABI sceErrorDialogUpdateStatus() {
|
||||
LOG_TRACE(Lib_ErrorDialog, "called status={}", magic_enum::enum_name(g_status));
|
||||
return g_status;
|
||||
}
|
||||
|
||||
void RegisterlibSceErrorDialog(Core::Loader::SymbolsResolver* sym) {
|
||||
|
|
|
@ -4,34 +4,25 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/system/commondialog.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
namespace Libraries::ErrorDialog {
|
||||
|
||||
enum OrbisErrorDialogStatus {
|
||||
ORBIS_ERROR_DIALOG_STATUS_NONE = 0,
|
||||
ORBIS_ERROR_DIALOG_STATUS_INITIALIZED = 1,
|
||||
ORBIS_ERROR_DIALOG_STATUS_RUNNING = 2,
|
||||
ORBIS_ERROR_DIALOG_STATUS_FINISHED = 3
|
||||
};
|
||||
using OrbisUserServiceUserId = s32;
|
||||
|
||||
struct OrbisErrorDialogParam {
|
||||
s32 size;
|
||||
u32 errorCode;
|
||||
s32 userId;
|
||||
s32 reserved;
|
||||
};
|
||||
struct Param;
|
||||
|
||||
int PS4_SYSV_ABI sceErrorDialogClose();
|
||||
OrbisErrorDialogStatus PS4_SYSV_ABI sceErrorDialogGetStatus();
|
||||
int PS4_SYSV_ABI sceErrorDialogInitialize(OrbisErrorDialogParam* param);
|
||||
int PS4_SYSV_ABI sceErrorDialogOpen(OrbisErrorDialogParam* param);
|
||||
CommonDialog::Error PS4_SYSV_ABI sceErrorDialogClose();
|
||||
CommonDialog::Status PS4_SYSV_ABI sceErrorDialogGetStatus();
|
||||
CommonDialog::Error PS4_SYSV_ABI sceErrorDialogInitialize();
|
||||
CommonDialog::Error PS4_SYSV_ABI sceErrorDialogOpen(const Param* param);
|
||||
int PS4_SYSV_ABI sceErrorDialogOpenDetail();
|
||||
int PS4_SYSV_ABI sceErrorDialogOpenWithReport();
|
||||
int PS4_SYSV_ABI sceErrorDialogTerminate();
|
||||
OrbisErrorDialogStatus PS4_SYSV_ABI sceErrorDialogUpdateStatus();
|
||||
CommonDialog::Error PS4_SYSV_ABI sceErrorDialogTerminate();
|
||||
CommonDialog::Status PS4_SYSV_ABI sceErrorDialogUpdateStatus();
|
||||
|
||||
void RegisterlibSceErrorDialog(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::ErrorDialog
|
|
@ -137,7 +137,7 @@ int PS4_SYSV_ABI sceKernelPollEventFlag(OrbisKernelEventFlag ef, u64 bitPattern,
|
|||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -184,7 +184,7 @@ int PS4_SYSV_ABI sceKernelWaitEventFlag(OrbisKernelEventFlag ef, u64 bitPattern,
|
|||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
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 {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "common/assert.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/elf_info.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/singleton.h"
|
||||
|
@ -243,8 +244,7 @@ int PS4_SYSV_ABI sceKernelConvertUtcToLocaltime(time_t time, time_t* local_time,
|
|||
}
|
||||
|
||||
int PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(int* ver) {
|
||||
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
||||
int version = param_sfo->GetInteger("SYSTEM_VER").value_or(0x4700000);
|
||||
int version = Common::ElfInfo::Instance().RawFirmwareVer();
|
||||
LOG_INFO(Kernel, "returned system version = {:#x}", version);
|
||||
*ver = version;
|
||||
return (version > 0) ? ORBIS_OK : ORBIS_KERNEL_ERROR_EINVAL;
|
||||
|
|
|
@ -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) {
|
||||
Core::MemoryManager* memory_manager = Core::Memory::Instance();
|
||||
Core::MemoryProt protection_flags = static_cast<Core::MemoryProt>(prot);
|
||||
return memory_manager->MTypeProtect(std::bit_cast<VAddr>(addr), size,
|
||||
static_cast<Core::VMAType>(mtype), protection_flags);
|
||||
return memory_manager->Protect(std::bit_cast<VAddr>(addr), size, protection_flags);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, int flags, OrbisQueryInfo* query_info,
|
||||
|
|
|
@ -6,13 +6,11 @@
|
|||
#include <thread>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/arch.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/error.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/singleton.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/cpu_patches.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/kernel/libkernel.h"
|
||||
#include "core/libraries/kernel/thread_management.h"
|
||||
|
@ -991,16 +989,12 @@ static void cleanup_thread(void* arg) {
|
|||
static void* run_thread(void* arg) {
|
||||
auto* thread = static_cast<ScePthread>(arg);
|
||||
Common::SetCurrentThreadName(thread->name.c_str());
|
||||
#ifdef ARCH_X86_64
|
||||
Core::InitializeThreadPatchStack();
|
||||
#endif
|
||||
auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
linker->InitTlsForThread(false);
|
||||
void* ret = nullptr;
|
||||
g_pthread_self = thread;
|
||||
pthread_cleanup_push(cleanup_thread, thread);
|
||||
thread->is_started = true;
|
||||
ret = thread->entry(thread->arg);
|
||||
ret = linker->ExecuteGuest(thread->entry, thread->arg);
|
||||
pthread_cleanup_pop(1);
|
||||
return ret;
|
||||
}
|
||||
|
@ -1512,6 +1506,10 @@ int PS4_SYSV_ABI scePthreadGetprio(ScePthread thread, int* prio) {
|
|||
return ORBIS_OK;
|
||||
}
|
||||
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;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
|
|
@ -147,13 +147,20 @@ int PS4_SYSV_ABI sceKernelGettimeofday(OrbisKernelTimeval* tp) {
|
|||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto duration = now.time_since_epoch();
|
||||
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(duration);
|
||||
auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(duration - seconds);
|
||||
FILETIME filetime;
|
||||
GetSystemTimeAsFileTime(&filetime);
|
||||
|
||||
tp->tv_sec = seconds.count();
|
||||
tp->tv_usec = microsecs.count();
|
||||
constexpr u64 UNIX_TIME_START = 0x295E9648864000;
|
||||
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
|
||||
timeval tv;
|
||||
gettimeofday(&tv, nullptr);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/singleton.h"
|
||||
#include "core/linker.h"
|
||||
#include "net_ctl_codes.h"
|
||||
#include "net_ctl_obj.h"
|
||||
|
||||
|
@ -57,18 +59,22 @@ s32 Libraries::NetCtl::NetCtlInternal::registerNpToolkitCallback(
|
|||
|
||||
void Libraries::NetCtl::NetCtlInternal::checkCallback() {
|
||||
std::unique_lock lock{m_mutex};
|
||||
auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
for (auto& callback : callbacks) {
|
||||
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() {
|
||||
std::unique_lock lock{m_mutex};
|
||||
auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
for (auto& callback : nptoolCallbacks) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -557,10 +557,10 @@ s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context,
|
|||
|
||||
const auto trophyDir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
|
||||
auto trophy_file = trophyDir / "trophy00" / "Xml" / "TROP.XML";
|
||||
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result =
|
||||
doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
|
||||
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
|
||||
|
||||
int numTrophies = 0;
|
||||
|
||||
|
|
|
@ -71,4 +71,4 @@ void TrophyUI::Draw() {
|
|||
End();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/elf_info.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/system/commondialog.h"
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
#include <imgui.h>
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
#include "common/elf_info.h"
|
||||
#include "common/singleton.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/file_sys/fs.h"
|
||||
#include "core/libraries/save_data/save_instance.h"
|
||||
#include "imgui/imgui_std.h"
|
||||
|
@ -13,6 +15,7 @@
|
|||
|
||||
using namespace ImGui;
|
||||
using namespace Libraries::CommonDialog;
|
||||
using Common::ElfInfo;
|
||||
|
||||
constexpr u32 OrbisSaveDataBlockSize = 32768; // 32 KiB
|
||||
|
||||
|
@ -46,11 +49,13 @@ void SaveDialogResult::CopyTo(OrbisSaveDataDialogResult& result) const {
|
|||
result.mode = this->mode;
|
||||
result.result = this->result;
|
||||
result.buttonId = this->button_id;
|
||||
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 (mode == SaveDataDialogMode::LIST || ElfInfo::Instance().FirmwareVer() >= ElfInfo::FW_45) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
result.userData = this->user_data;
|
||||
}
|
||||
|
@ -63,8 +68,7 @@ SaveDialogState::SaveDialogState(const OrbisSaveDataDialogParam& param) {
|
|||
this->enable_back = {param.optionParam->back == OptionBack::ENABLE};
|
||||
}
|
||||
|
||||
static std::string game_serial{*Common::Singleton<PSF>::Instance()->GetString("CONTENT_ID"), 7,
|
||||
9};
|
||||
const auto& game_serial = Common::ElfInfo::Instance().GameSerial();
|
||||
|
||||
const auto item = param.items;
|
||||
this->user_id = item->userId;
|
||||
|
@ -75,63 +79,66 @@ SaveDialogState::SaveDialogState(const OrbisSaveDataDialogParam& param) {
|
|||
this->title_id = item->titleId->data.to_string();
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < item->dirNameNum; i++) {
|
||||
const auto dir_name = item->dirName[i].data.to_view();
|
||||
if (item->dirName != nullptr) {
|
||||
for (u32 i = 0; i < item->dirNameNum; i++) {
|
||||
const auto dir_name = item->dirName[i].data.to_view();
|
||||
|
||||
if (dir_name.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (dir_name.empty()) {
|
||||
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";
|
||||
if (!std::filesystem::exists(param_sfo_path)) {
|
||||
continue;
|
||||
}
|
||||
auto param_sfo_path = dir_path / "sce_sys" / "param.sfo";
|
||||
if (!std::filesystem::exists(param_sfo_path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PSF param_sfo;
|
||||
param_sfo.Open(param_sfo_path);
|
||||
PSF param_sfo;
|
||||
param_sfo.Open(param_sfo_path);
|
||||
|
||||
auto last_write = param_sfo.GetLastWrite();
|
||||
auto last_write = param_sfo.GetLastWrite();
|
||||
#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
|
||||
auto utc_time = std::chrono::file_clock::to_sys(last_write);
|
||||
auto utc_time = std::chrono::file_clock::to_sys(last_write);
|
||||
#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);
|
||||
std::string size_str = SpaceSizeToString(size);
|
||||
size_t size = Common::FS::GetDirectorySize(dir_path);
|
||||
std::string size_str = SpaceSizeToString(size);
|
||||
|
||||
auto icon_path = dir_path / "sce_sys" / "icon0.png";
|
||||
RefCountedTexture icon;
|
||||
if (std::filesystem::exists(icon_path)) {
|
||||
icon = RefCountedTexture::DecodePngFile(icon_path);
|
||||
auto icon_path = dir_path / "sce_sys" / "icon0.png";
|
||||
RefCountedTexture icon;
|
||||
if (std::filesystem::exists(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;
|
||||
std::string title{"New Save"};
|
||||
|
||||
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;
|
||||
icon = RefCountedTexture::DecodePngTexture({buf, buf + new_item->iconSize});
|
||||
} else {
|
||||
|
@ -140,7 +147,7 @@ SaveDialogState::SaveDialogState(const OrbisSaveDataDialogParam& param) {
|
|||
icon = RefCountedTexture::DecodePngFile(src_icon);
|
||||
}
|
||||
}
|
||||
if (new_item != nullptr && new_item->title != nullptr) {
|
||||
if (new_item->title != nullptr) {
|
||||
title = std::string{new_item->title};
|
||||
}
|
||||
|
||||
|
@ -199,6 +206,7 @@ SaveDialogState::SystemState::SystemState(const SaveDialogState& state,
|
|||
auto& sys = *param.sysMsgParam;
|
||||
switch (sys.msgType) {
|
||||
case SystemMessageType::NODATA: {
|
||||
return_cancel = true;
|
||||
this->msg = "There is no saved data";
|
||||
} break;
|
||||
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##");
|
||||
break;
|
||||
case SystemMessageType::NOSPACE:
|
||||
return_cancel = true;
|
||||
M(fmt::format(
|
||||
"There is not enough space to save the data. To continue {} free space is required.",
|
||||
SpaceSizeToString(sys.value * OrbisSaveDataBlockSize)),
|
||||
|
@ -222,12 +231,15 @@ SaveDialogState::SystemState::SystemState(const SaveDialogState& state,
|
|||
M("Saving...", "Loading...", "Deleting...");
|
||||
break;
|
||||
case SystemMessageType::FILE_CORRUPTED:
|
||||
return_cancel = true;
|
||||
this->msg = "The saved data is corrupted.";
|
||||
break;
|
||||
case SystemMessageType::FINISHED:
|
||||
return_cancel = true;
|
||||
M("Saved successfully.", "Loading complete.", "Deletion complete.");
|
||||
break;
|
||||
case SystemMessageType::NOSPACE_CONTINUABLE:
|
||||
return_cancel = true;
|
||||
M(fmt::format("There is not enough space to save the data. {} free space is required.",
|
||||
SpaceSizeToString(sys.value * OrbisSaveDataBlockSize)),
|
||||
"##UNKNOWN##", "##UNKNOWN##");
|
||||
|
@ -279,29 +291,36 @@ SaveDialogState::ErrorCodeState::ErrorCodeState(const OrbisSaveDataDialogParam&
|
|||
}
|
||||
SaveDialogState::ProgressBarState::ProgressBarState(const SaveDialogState& state,
|
||||
const OrbisSaveDataDialogParam& param) {
|
||||
static auto fw_ver = ElfInfo::Instance().FirmwareVer();
|
||||
|
||||
this->progress = 0;
|
||||
|
||||
auto& bar = *param.progressBarParam;
|
||||
switch (bar.sysMsgType) {
|
||||
case ProgressSystemMessageType::INVALID:
|
||||
this->msg = bar.msg != nullptr ? std::string{bar.msg} : std::string{};
|
||||
break;
|
||||
case ProgressSystemMessageType::PROGRESS:
|
||||
switch (state.type) {
|
||||
case DialogType::SAVE:
|
||||
this->msg = "Saving...";
|
||||
|
||||
if (bar.msg != nullptr) {
|
||||
this->msg = std::string{bar.msg};
|
||||
} else {
|
||||
switch (bar.sysMsgType) {
|
||||
case ProgressSystemMessageType::INVALID:
|
||||
this->msg = "";
|
||||
break;
|
||||
case DialogType::LOAD:
|
||||
this->msg = "Loading...";
|
||||
case ProgressSystemMessageType::PROGRESS:
|
||||
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;
|
||||
case DialogType::DELETE:
|
||||
this->msg = "Deleting...";
|
||||
case ProgressSystemMessageType::RESTORE:
|
||||
this->msg = "Restoring saved data...";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ProgressSystemMessageType::RESTORE:
|
||||
this->msg = "Restoring saved data...";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -378,7 +397,7 @@ void SaveDialogUi::Draw() {
|
|||
};
|
||||
} else {
|
||||
window_size = ImVec2{
|
||||
std::min(io.DisplaySize.x, 500.0f),
|
||||
std::min(io.DisplaySize.x, 600.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) {
|
||||
constexpr auto text_spacing = 1.2f;
|
||||
constexpr auto text_spacing = 0.95f;
|
||||
|
||||
auto& ctx = *GetCurrentContext();
|
||||
auto& window = *ctx.CurrentWindow;
|
||||
|
@ -495,18 +514,20 @@ void SaveDialogUi::DrawItem(int _id, const SaveDialogState::Item& item, bool cli
|
|||
if (!item.title.empty()) {
|
||||
const char* begin = &item.title.front();
|
||||
const char* end = &item.title.back() + 1;
|
||||
SetWindowFontScale(2.0f);
|
||||
SetWindowFontScale(1.5f);
|
||||
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;
|
||||
}
|
||||
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 (!item.subtitle.empty()) {
|
||||
|
@ -636,6 +657,8 @@ void SaveDialogUi::DrawUser() {
|
|||
|
||||
if (!state->save_list.empty()) {
|
||||
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;
|
||||
|
@ -660,7 +683,7 @@ void SaveDialogUi::DrawUser() {
|
|||
|
||||
if (has_btn) {
|
||||
int count = 1;
|
||||
if (btn_type == ButtonType::YESNO || btn_type == ButtonType::ONCANCEL) {
|
||||
if (btn_type == ButtonType::YESNO || btn_type == ButtonType::OKCANCEL) {
|
||||
++count;
|
||||
}
|
||||
|
||||
|
@ -676,19 +699,28 @@ void SaveDialogUi::DrawUser() {
|
|||
}
|
||||
SameLine();
|
||||
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)) {
|
||||
SetItemCurrentNavFocus();
|
||||
}
|
||||
} else {
|
||||
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) {
|
||||
SetItemCurrentNavFocus();
|
||||
}
|
||||
if (btn_type == ButtonType::ONCANCEL) {
|
||||
if (btn_type == ButtonType::OKCANCEL) {
|
||||
SameLine();
|
||||
if (Button("Cancel", BUTTON_SIZE)) {
|
||||
Finish(ButtonId::INVALID, Result::USER_CANCELED);
|
||||
|
@ -707,6 +739,8 @@ void SaveDialogUi::DrawSystemMessage() {
|
|||
|
||||
if (!state->save_list.empty()) {
|
||||
DrawItem(0, state->save_list.front(), false);
|
||||
} else if (state->new_item) {
|
||||
DrawItem(0, *state->new_item, false);
|
||||
}
|
||||
|
||||
const auto ws = GetWindowSize();
|
||||
|
@ -730,12 +764,20 @@ void SaveDialogUi::DrawSystemMessage() {
|
|||
});
|
||||
BeginGroup();
|
||||
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();
|
||||
if (sys_state.show_no) {
|
||||
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) {
|
||||
if (Button("Cancel", BUTTON_SIZE)) {
|
||||
|
@ -753,6 +795,8 @@ void SaveDialogUi::DrawErrorCode() {
|
|||
|
||||
if (!state->save_list.empty()) {
|
||||
DrawItem(0, state->save_list.front(), false);
|
||||
} else if (state->new_item) {
|
||||
DrawItem(0, *state->new_item, false);
|
||||
}
|
||||
|
||||
const auto ws = GetWindowSize();
|
||||
|
@ -768,7 +812,11 @@ void SaveDialogUi::DrawErrorCode() {
|
|||
ws.y - FOOTER_HEIGHT + 5.0f,
|
||||
});
|
||||
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) {
|
||||
SetItemCurrentNavFocus();
|
||||
|
@ -782,6 +830,8 @@ void SaveDialogUi::DrawProgressBar() {
|
|||
|
||||
if (!state->save_list.empty()) {
|
||||
DrawItem(0, state->save_list.front(), false);
|
||||
} else if (state->new_item) {
|
||||
DrawItem(0, *state->new_item, false);
|
||||
}
|
||||
|
||||
const auto& msg = bar_state.msg;
|
||||
|
|
|
@ -48,7 +48,7 @@ enum class ButtonType : u32 {
|
|||
OK = 0,
|
||||
YESNO = 1,
|
||||
NONE = 2,
|
||||
ONCANCEL = 3,
|
||||
OKCANCEL = 3,
|
||||
};
|
||||
|
||||
enum class UserMessageType : u32 {
|
||||
|
@ -222,6 +222,8 @@ public:
|
|||
bool show_no{}; // Yes instead of OK
|
||||
bool show_cancel{};
|
||||
|
||||
bool return_cancel{};
|
||||
|
||||
SystemState(const SaveDialogState& state, const OrbisSaveDataDialogParam& param);
|
||||
};
|
||||
struct ErrorCodeState {
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
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_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;
|
||||
|
||||
|
@ -26,6 +27,8 @@ namespace Libraries::SaveData::Backup {
|
|||
static std::jthread g_backup_thread;
|
||||
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::deque<BackupRequest> g_backup_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 void backup(const std::filesystem::path& dir_name) {
|
||||
std::unique_lock lk{g_backup_running_mutex};
|
||||
if (!fs::exists(dir_name)) {
|
||||
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;
|
||||
for (const auto& entry : fs::directory_iterator(dir_name)) {
|
||||
const auto filename = entry.path().filename();
|
||||
if (filename != backup_dir && filename != backup_dir_tmp) {
|
||||
if (filename != ::backup_dir) {
|
||||
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;
|
||||
|
||||
int total_count = static_cast<int>(backup_files.size());
|
||||
int current_count = 0;
|
||||
|
||||
fs::remove_all(backup_dir_tmp);
|
||||
fs::create_directory(backup_dir_tmp);
|
||||
for (const auto& file : backup_files) {
|
||||
fs::copy(file, backup_dir_tmp / file.filename(), fs::copy_options::recursive);
|
||||
current_count++;
|
||||
g_backup_progress = current_count * 100 / total_count;
|
||||
}
|
||||
bool has_existing = fs::exists(backup_dir);
|
||||
if (has_existing) {
|
||||
fs::rename(backup_dir, dir_name / "sce_backup_old");
|
||||
bool has_existing_backup = fs::exists(backup_dir);
|
||||
if (has_existing_backup) {
|
||||
fs::rename(backup_dir, backup_dir_old);
|
||||
}
|
||||
fs::rename(backup_dir_tmp, backup_dir);
|
||||
if (has_existing) {
|
||||
fs::remove_all(dir_name / "sce_backup_old");
|
||||
if (has_existing_backup) {
|
||||
fs::remove_all(backup_dir_old);
|
||||
}
|
||||
}
|
||||
|
||||
static void BackupThreadBody() {
|
||||
Common::SetCurrentThreadName("SaveData_BackupThread");
|
||||
while (true) {
|
||||
while (g_backup_status != WorkerStatus::Stopping) {
|
||||
g_backup_status = WorkerStatus::Waiting;
|
||||
g_backup_thread_semaphore.acquire();
|
||||
|
||||
bool wait;
|
||||
BackupRequest req;
|
||||
{
|
||||
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()) {
|
||||
break;
|
||||
}
|
||||
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",
|
||||
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};
|
||||
g_backup_queue.pop_front();
|
||||
|
@ -104,8 +139,8 @@ void StartThread() {
|
|||
return;
|
||||
}
|
||||
LOG_DEBUG(Lib_SaveData, "Starting backup thread");
|
||||
g_backup_thread = std::jthread{BackupThreadBody};
|
||||
g_backup_status = WorkerStatus::Waiting;
|
||||
g_backup_thread = std::jthread{BackupThreadBody};
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
{
|
||||
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{
|
||||
.user_id = user_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) {
|
||||
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)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -170,8 +212,9 @@ WorkerStatus GetWorkerStatus() {
|
|||
|
||||
bool IsBackupExecutingFor(const std::filesystem::path& save_path) {
|
||||
std::scoped_lock lk{g_backup_queue_mutex};
|
||||
return std::ranges::find(g_backup_queue, save_path,
|
||||
[](const auto& v) { return v.save_path; }) != g_backup_queue.end();
|
||||
const auto& it =
|
||||
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) {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
|
@ -27,6 +28,8 @@ enum class OrbisSaveDataEventType : u32 {
|
|||
};
|
||||
|
||||
struct BackupRequest {
|
||||
bool done{};
|
||||
|
||||
OrbisUserServiceUserId user_id{};
|
||||
std::string title_id{};
|
||||
std::string dir_name{};
|
||||
|
|
|
@ -77,7 +77,7 @@ static void SaveFileSafe(void* buf, size_t count, const std::filesystem::path& p
|
|||
g_saving_memory = true;
|
||||
std::scoped_lock lk{g_saving_memory_mutex};
|
||||
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) {
|
||||
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);
|
||||
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(
|
||||
"failed to open SFO", g_param_sfo_path,
|
||||
std::make_error_code(std::errc::illegal_byte_sequence));
|
||||
|
@ -190,14 +191,19 @@ void SetIcon(void* buf, size_t buf_size) {
|
|||
if (buf == nullptr) {
|
||||
const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png");
|
||||
if (fs::exists(src_icon)) {
|
||||
if (fs::exists(g_icon_path)) {
|
||||
fs::remove(g_icon_path);
|
||||
}
|
||||
fs::copy_file(src_icon, g_icon_path);
|
||||
}
|
||||
IOFile file(g_icon_path, Common::FS::FileAccessMode::Read);
|
||||
size_t size = file.GetSize();
|
||||
file.Seek(0);
|
||||
g_icon_memory.resize(size);
|
||||
file.ReadRaw<u8>(g_icon_memory.data(), size);
|
||||
file.Close();
|
||||
if (fs::exists(g_icon_path)) {
|
||||
IOFile file(g_icon_path, Common::FS::FileAccessMode::Read);
|
||||
size_t size = file.GetSize();
|
||||
file.Seek(0);
|
||||
g_icon_memory.resize(size);
|
||||
file.ReadRaw<u8>(g_icon_memory.data(), size);
|
||||
file.Close();
|
||||
}
|
||||
} else {
|
||||
g_icon_memory.resize(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) {
|
||||
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()) {
|
||||
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) {
|
||||
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()) {
|
||||
UNREACHABLE_MSG("WriteMemory out of bounds");
|
||||
g_save_memory.resize(offset + buf_size);
|
||||
}
|
||||
std::memcpy(g_save_memory.data() + offset, buf, buf_size);
|
||||
g_memory_dirty = true;
|
||||
|
|
|
@ -9,10 +9,10 @@
|
|||
|
||||
#include "common/assert.h"
|
||||
#include "common/cstring.h"
|
||||
#include "common/elf_info.h"
|
||||
#include "common/enum.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/path_util.h"
|
||||
#include "common/singleton.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/file_format/psf.h"
|
||||
#include "core/file_sys/fs.h"
|
||||
|
@ -28,11 +28,13 @@ namespace fs = std::filesystem;
|
|||
namespace chrono = std::chrono;
|
||||
|
||||
using Common::CString;
|
||||
using Common::ElfInfo;
|
||||
|
||||
namespace Libraries::SaveData {
|
||||
|
||||
enum class Error : u32 {
|
||||
OK = 0,
|
||||
USER_SERVICE_NOT_INITIALIZED = 0x80960002,
|
||||
PARAMETER = 0x809F0000,
|
||||
NOT_INITIALIZED = 0x809F0001,
|
||||
OUT_OF_MEMORY = 0x809F0002,
|
||||
|
@ -191,7 +193,9 @@ struct OrbisSaveDataMemorySetup2 {
|
|||
OrbisUserServiceUserId userId;
|
||||
size_t memorySize;
|
||||
size_t iconMemorySize;
|
||||
// +4.5
|
||||
const OrbisSaveDataParam* initParam;
|
||||
// +4.5
|
||||
const OrbisSaveDataIcon* initIcon;
|
||||
std::array<u8, 24> _reserved;
|
||||
};
|
||||
|
@ -241,6 +245,7 @@ struct OrbisSaveDataMountResult {
|
|||
OrbisSaveDataMountPoint mount_point;
|
||||
OrbisSaveDataBlocks required_blocks;
|
||||
u32 _unused;
|
||||
// +4.5
|
||||
OrbisSaveDataMountStatus mount_status;
|
||||
std::array<u8, 28> _reserved;
|
||||
s32 : 32;
|
||||
|
@ -278,8 +283,11 @@ struct OrbisSaveDataDirNameSearchResult {
|
|||
int : 32;
|
||||
OrbisSaveDataDirName* dirNames;
|
||||
u32 dirNamesNum;
|
||||
// +1.7
|
||||
u32 setNum;
|
||||
// +1.7
|
||||
OrbisSaveDataParam* params;
|
||||
// +2.5
|
||||
OrbisSaveDataSearchInfo* infos;
|
||||
std::array<u8, 12> _reserved;
|
||||
int : 32;
|
||||
|
@ -303,12 +311,13 @@ struct OrbisSaveDataEvent {
|
|||
|
||||
static bool g_initialized = false;
|
||||
static std::string g_game_serial;
|
||||
static u32 g_fw_ver;
|
||||
static std::array<std::optional<SaveInstance>, 16> g_mount_slots;
|
||||
|
||||
static void initialize() {
|
||||
g_initialized = true;
|
||||
static auto* param_sfo = Common::Singleton<PSF>::Instance();
|
||||
g_game_serial = std::string(*param_sfo->GetString("CONTENT_ID"), 7, 9);
|
||||
g_game_serial = ElfInfo::Instance().GameSerial();
|
||||
g_fw_ver = ElfInfo::Instance().FirmwareVer();
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
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,
|
||||
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,
|
||||
mount_info->dirName->data);
|
||||
if (Backup::IsBackupExecutingFor(save_path)) {
|
||||
if (Backup::IsBackupExecutingFor(save_path) && g_fw_ver) {
|
||||
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 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
|
||||
|
||||
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};
|
||||
|
||||
|
@ -437,9 +459,11 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
|
|||
|
||||
mount_result->mount_point.data.FromString(save_instance.GetMountPoint());
|
||||
|
||||
mount_result->mount_status = create_if_not_exist && to_be_created
|
||||
? OrbisSaveDataMountStatus::CREATED
|
||||
: OrbisSaveDataMountStatus::NOTHING;
|
||||
if (g_fw_ver >= ElfInfo::FW_45) {
|
||||
mount_result->mount_status = create_if_not_exist && to_be_created
|
||||
? OrbisSaveDataMountStatus::CREATED
|
||||
: OrbisSaveDataMountStatus::NOTHING;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!g_initialized) {
|
||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||
return Error::NOT_INITIALIZED;
|
||||
return setNotInitializedError();
|
||||
}
|
||||
if (mountPoint == nullptr) {
|
||||
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()) {
|
||||
const auto& slot_name = instance->GetMountPoint();
|
||||
if (slot_name == mount_point_str) {
|
||||
// TODO: check if is busy
|
||||
instance->Umount();
|
||||
if (call_backup) {
|
||||
Backup::StartThread();
|
||||
Backup::NewRequest(instance->GetUserId(), instance->GetTitleId(),
|
||||
instance->GetDirName(),
|
||||
OrbisSaveDataEventType::UMOUNT_BACKUP);
|
||||
}
|
||||
// TODO: check if is busy
|
||||
instance->Umount();
|
||||
instance.reset();
|
||||
return Error::OK;
|
||||
}
|
||||
|
@ -479,9 +503,9 @@ static Error Umount(const OrbisSaveDataMountPoint* mountPoint, bool call_backup
|
|||
|
||||
void OrbisSaveDataParam::FromSFO(const PSF& sfo) {
|
||||
memset(this, 0, sizeof(OrbisSaveDataParam));
|
||||
title.FromString(*sfo.GetString(SaveParams::MAINTITLE));
|
||||
subTitle.FromString(*sfo.GetString(SaveParams::SUBTITLE));
|
||||
detail.FromString(*sfo.GetString(SaveParams::DETAIL));
|
||||
title.FromString(sfo.GetString(SaveParams::MAINTITLE).value_or("Unknown"));
|
||||
subTitle.FromString(sfo.GetString(SaveParams::SUBTITLE).value_or(""));
|
||||
detail.FromString(sfo.GetString(SaveParams::DETAIL).value_or(""));
|
||||
userParam = sfo.GetInteger(SaveParams::SAVEDATA_LIST_PARAM).value_or(0);
|
||||
const auto time_since_epoch = sfo.GetLastWrite().time_since_epoch();
|
||||
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) {
|
||||
if (!g_initialized) {
|
||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||
return Error::NOT_INITIALIZED;
|
||||
return setNotInitializedError();
|
||||
}
|
||||
if (backup == nullptr || backup->dirName == nullptr) {
|
||||
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) {
|
||||
if (!g_initialized) {
|
||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||
return Error::NOT_INITIALIZED;
|
||||
return setNotInitializedError();
|
||||
}
|
||||
if (check == nullptr || check->dirName == nullptr) {
|
||||
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}
|
||||
: std::string_view{g_game_serial}};
|
||||
LOG_DEBUG(Lib_SaveData, "called with titleId={}", title);
|
||||
|
||||
const auto save_path =
|
||||
SaveInstance::MakeDirSavePath(check->userId, title, check->dirName->data);
|
||||
|
@ -582,7 +607,7 @@ Error PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData
|
|||
if (check->param != nullptr) {
|
||||
PSF 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;
|
||||
}
|
||||
check->param->FromSFO(sfo);
|
||||
|
@ -636,7 +661,7 @@ int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersionLatest() {
|
|||
Error PS4_SYSV_ABI sceSaveDataClearProgress() {
|
||||
if (!g_initialized) {
|
||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||
return Error::NOT_INITIALIZED;
|
||||
return setNotInitializedError();
|
||||
}
|
||||
LOG_DEBUG(Lib_SaveData, "called");
|
||||
Backup::ClearProgress();
|
||||
|
@ -691,7 +716,7 @@ int PS4_SYSV_ABI sceSaveDataDebugTarget() {
|
|||
Error PS4_SYSV_ABI sceSaveDataDelete(const OrbisSaveDataDelete* del) {
|
||||
if (!g_initialized) {
|
||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||
return Error::NOT_INITIALIZED;
|
||||
return setNotInitializedError();
|
||||
}
|
||||
if (del == nullptr) {
|
||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||
|
@ -743,7 +768,7 @@ Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond
|
|||
OrbisSaveDataDirNameSearchResult* result) {
|
||||
if (!g_initialized) {
|
||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||
return Error::NOT_INITIALIZED;
|
||||
return setNotInitializedError();
|
||||
}
|
||||
if (cond == nullptr || result == nullptr || cond->key > OrbisSaveDataSortKey::FREE_BLOCKS ||
|
||||
cond->order > OrbisSaveDataSortOrder::DESCENT) {
|
||||
|
@ -758,7 +783,9 @@ Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond
|
|||
|
||||
if (!fs::exists(save_path)) {
|
||||
result->hitNum = 0;
|
||||
result->setNum = 0;
|
||||
if (g_fw_ver >= ElfInfo::FW_17) {
|
||||
result->setNum = 0;
|
||||
}
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
|
@ -775,9 +802,11 @@ Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond
|
|||
if (cond->dirName != nullptr) {
|
||||
// Filter names
|
||||
const auto pat = Common::ToLower(std::string_view{cond->dirName->data});
|
||||
std::erase_if(dir_list, [&](const std::string& dir_name) {
|
||||
return !match(Common::ToLower(dir_name), pat);
|
||||
});
|
||||
if (!pat.empty()) {
|
||||
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;
|
||||
|
@ -789,7 +818,7 @@ Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond
|
|||
const auto sfo_path = SaveInstance::GetParamSFOPath(dir_path);
|
||||
PSF sfo;
|
||||
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");
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
result->hitNum = 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++) {
|
||||
auto& name_data = result->dirNames[i].data;
|
||||
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& param_data = result->params[i];
|
||||
param_data.FromSFO(sfo);
|
||||
}
|
||||
|
||||
if (result->infos != nullptr) {
|
||||
if (g_fw_ver >= ElfInfo::FW_25 && result->infos != nullptr) {
|
||||
auto& info = result->infos[i];
|
||||
info.blocks = map_max_blocks.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) {
|
||||
if (!g_initialized) {
|
||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||
return Error::NOT_INITIALIZED;
|
||||
return setNotInitializedError();
|
||||
}
|
||||
if (event == nullptr) {
|
||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||
|
@ -950,7 +983,7 @@ Error PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountP
|
|||
OrbisSaveDataMountInfo* info) {
|
||||
if (!g_initialized) {
|
||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||
return Error::NOT_INITIALIZED;
|
||||
return setNotInitializedError();
|
||||
}
|
||||
if (mountPoint == nullptr || info == nullptr) {
|
||||
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) {
|
||||
if (!g_initialized) {
|
||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||
return Error::NOT_INITIALIZED;
|
||||
return setNotInitializedError();
|
||||
}
|
||||
if (paramType > OrbisSaveDataParamType::MTIME || paramBuf == nullptr) {
|
||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||
|
@ -1019,7 +1052,7 @@ Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint
|
|||
} else {
|
||||
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
|
||||
if (gotSize != nullptr) {
|
||||
*gotSize = s + 1;
|
||||
|
@ -1050,7 +1083,7 @@ Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint
|
|||
Error PS4_SYSV_ABI sceSaveDataGetProgress(float* progress) {
|
||||
if (!g_initialized) {
|
||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||
return Error::NOT_INITIALIZED;
|
||||
return setNotInitializedError();
|
||||
}
|
||||
if (progress == nullptr) {
|
||||
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) {
|
||||
if (!g_initialized) {
|
||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||
return Error::NOT_INITIALIZED;
|
||||
return setNotInitializedError();
|
||||
}
|
||||
if (getParam == nullptr) {
|
||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||
|
@ -1180,7 +1213,7 @@ Error PS4_SYSV_ABI sceSaveDataLoadIcon(const OrbisSaveDataMountPoint* mountPoint
|
|||
OrbisSaveDataIcon* icon) {
|
||||
if (!g_initialized) {
|
||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||
return Error::NOT_INITIALIZED;
|
||||
return setNotInitializedError();
|
||||
}
|
||||
if (mountPoint == nullptr || icon == nullptr || icon->buf == nullptr) {
|
||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||
|
@ -1209,7 +1242,7 @@ Error PS4_SYSV_ABI sceSaveDataMount(const OrbisSaveDataMount* mount,
|
|||
OrbisSaveDataMountResult* mount_result) {
|
||||
if (!g_initialized) {
|
||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||
return Error::NOT_INITIALIZED;
|
||||
return setNotInitializedError();
|
||||
}
|
||||
if (mount == nullptr && mount->dirName != nullptr) {
|
||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||
|
@ -1230,7 +1263,7 @@ Error PS4_SYSV_ABI sceSaveDataMount2(const OrbisSaveDataMount2* mount,
|
|||
OrbisSaveDataMountResult* mount_result) {
|
||||
if (!g_initialized) {
|
||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||
return Error::NOT_INITIALIZED;
|
||||
return setNotInitializedError();
|
||||
}
|
||||
if (mount == nullptr && mount->dirName != nullptr) {
|
||||
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) {
|
||||
if (!g_initialized) {
|
||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||
return Error::NOT_INITIALIZED;
|
||||
return setNotInitializedError();
|
||||
}
|
||||
if (restore == nullptr || restore->dirName == nullptr) {
|
||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||
|
@ -1325,7 +1358,7 @@ Error PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint
|
|||
const OrbisSaveDataIcon* icon) {
|
||||
if (!g_initialized) {
|
||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||
return Error::NOT_INITIALIZED;
|
||||
return setNotInitializedError();
|
||||
}
|
||||
if (mountPoint == nullptr || icon == nullptr || icon->buf == nullptr) {
|
||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||
|
@ -1373,7 +1406,7 @@ Error PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint
|
|||
size_t paramBufSize) {
|
||||
if (!g_initialized) {
|
||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||
return Error::NOT_INITIALIZED;
|
||||
return setNotInitializedError();
|
||||
}
|
||||
if (paramType > OrbisSaveDataParamType::USER_PARAM || mountPoint == nullptr ||
|
||||
paramBuf == nullptr) {
|
||||
|
@ -1438,13 +1471,15 @@ Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, v
|
|||
OrbisSaveDataMemorySet2 setParam{};
|
||||
setParam.userId = userId;
|
||||
setParam.data = &data;
|
||||
setParam.param = nullptr;
|
||||
setParam.icon = nullptr;
|
||||
return sceSaveDataSetSaveDataMemory2(&setParam);
|
||||
}
|
||||
|
||||
Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam) {
|
||||
if (!g_initialized) {
|
||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||
return Error::NOT_INITIALIZED;
|
||||
return setNotInitializedError();
|
||||
}
|
||||
if (setParam == nullptr) {
|
||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||
|
@ -1477,17 +1512,35 @@ Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2*
|
|||
return Error::OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(/*u32 userId, size_t memorySize,
|
||||
OrbisSaveDataParam* param*/) {
|
||||
LOG_ERROR(Lib_SaveData, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(OrbisUserServiceUserId userId, size_t memorySize,
|
||||
OrbisSaveDataParam* param) {
|
||||
LOG_DEBUG(Lib_SaveData, "called: userId = {}, memorySize = {}", userId, memorySize);
|
||||
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,
|
||||
OrbisSaveDataMemorySetupResult* result) {
|
||||
if (!g_initialized) {
|
||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||
return Error::NOT_INITIALIZED;
|
||||
return setNotInitializedError();
|
||||
}
|
||||
if (setupParam == nullptr) {
|
||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||
|
@ -1507,20 +1560,20 @@ Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetu
|
|||
try {
|
||||
size_t existed_size = SaveMemory::CreateSaveMemory(setupParam->memorySize);
|
||||
if (existed_size == 0) { // Just created
|
||||
if (setupParam->initParam != nullptr) {
|
||||
if (g_fw_ver >= ElfInfo::FW_45 && setupParam->initParam != nullptr) {
|
||||
auto& sfo = SaveMemory::GetParamSFO();
|
||||
setupParam->initParam->ToSFO(sfo);
|
||||
}
|
||||
SaveMemory::SaveSFO();
|
||||
|
||||
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);
|
||||
} else {
|
||||
SaveMemory::SetIcon(nullptr, 0);
|
||||
}
|
||||
}
|
||||
if (result != nullptr) {
|
||||
if (g_fw_ver >= ElfInfo::FW_45 && result != nullptr) {
|
||||
result->existedMemorySize = existed_size;
|
||||
}
|
||||
} catch (const fs::filesystem_error& e) {
|
||||
|
@ -1556,7 +1609,7 @@ int PS4_SYSV_ABI sceSaveDataSyncCloudList() {
|
|||
Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam) {
|
||||
if (!g_initialized) {
|
||||
LOG_INFO(Lib_SaveData, "called without initialize");
|
||||
return Error::NOT_INITIALIZED;
|
||||
return setNotInitializedError();
|
||||
}
|
||||
if (syncParam == nullptr) {
|
||||
LOG_INFO(Lib_SaveData, "called with invalid parameter");
|
||||
|
@ -1577,11 +1630,15 @@ Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncPa
|
|||
Error PS4_SYSV_ABI sceSaveDataTerminate() {
|
||||
LOG_DEBUG(Lib_SaveData, "called");
|
||||
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()) {
|
||||
return Error::BUSY;
|
||||
if (g_fw_ver >= ElfInfo::FW_40) {
|
||||
return Error::BUSY;
|
||||
}
|
||||
instance->Umount();
|
||||
instance.reset();
|
||||
}
|
||||
}
|
||||
g_initialized = false;
|
||||
|
|
|
@ -165,8 +165,8 @@ int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser();
|
|||
Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, void* buf,
|
||||
size_t bufSize, int64_t offset);
|
||||
Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam);
|
||||
int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(/*u32 userId, size_t memorySize,
|
||||
OrbisSaveDataParam* param*/);
|
||||
Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(OrbisUserServiceUserId userId, size_t memorySize,
|
||||
OrbisSaveDataParam* param);
|
||||
Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam,
|
||||
OrbisSaveDataMemorySetupResult* result);
|
||||
int PS4_SYSV_ABI sceSaveDataShutdownStart();
|
||||
|
|
|
@ -1717,7 +1717,7 @@ int PS4_SYSV_ABI sceSystemServiceGetAppType() {
|
|||
|
||||
s32 PS4_SYSV_ABI
|
||||
sceSystemServiceGetDisplaySafeAreaInfo(OrbisSystemServiceDisplaySafeAreaInfo* info) {
|
||||
LOG_INFO(Lib_SystemService, "called");
|
||||
LOG_DEBUG(Lib_SystemService, "called");
|
||||
if (info == nullptr) {
|
||||
LOG_ERROR(Lib_SystemService, "OrbisSystemServiceDisplaySafeAreaInfo is null");
|
||||
return ORBIS_SYSTEM_SERVICE_ERROR_PARAMETER;
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.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");
|
||||
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
std::string name = "shadps4"; // TODO onfigurable username
|
||||
std::string name = Config::getUserName();
|
||||
if (size < name.length()) {
|
||||
LOG_ERROR(Lib_UserService, "buffer is too short");
|
||||
return ORBIS_USER_SERVICE_ERROR_BUFFER_TOO_SHORT;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <imgui.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
|
@ -160,9 +161,7 @@ int VideoOutDriver::UnregisterBuffers(VideoOutPort* port, s32 attributeIndex) {
|
|||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
std::chrono::microseconds VideoOutDriver::Flip(const Request& req) {
|
||||
const auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
void VideoOutDriver::Flip(const Request& req) {
|
||||
// Whatever the game is rendering show splash if it is active
|
||||
if (!renderer->ShowSplash(req.frame)) {
|
||||
// Present the frame.
|
||||
|
@ -198,9 +197,6 @@ std::chrono::microseconds VideoOutDriver::Flip(const Request& req) {
|
|||
port->buffer_labels[req.index] = 0;
|
||||
port->SignalVoLabel();
|
||||
}
|
||||
|
||||
const auto end = std::chrono::high_resolution_clock::now();
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
}
|
||||
|
||||
void VideoOutDriver::DrawBlankFrame() {
|
||||
|
@ -267,6 +263,8 @@ void VideoOutDriver::PresentThread(std::stop_token token) {
|
|||
Common::SetCurrentThreadName("PresentThread");
|
||||
Common::SetCurrentThreadRealtime(vblank_period);
|
||||
|
||||
Common::AccurateTimer timer{vblank_period};
|
||||
|
||||
const auto receive_request = [this] -> Request {
|
||||
std::scoped_lock lk{mutex};
|
||||
if (!requests.empty()) {
|
||||
|
@ -279,20 +277,18 @@ void VideoOutDriver::PresentThread(std::stop_token token) {
|
|||
|
||||
auto delay = std::chrono::microseconds{0};
|
||||
while (!token.stop_requested()) {
|
||||
// Sleep for most of the vblank duration.
|
||||
std::this_thread::sleep_for(vblank_period - delay);
|
||||
timer.Start();
|
||||
|
||||
// Check if it's time to take a request.
|
||||
auto& vblank_status = main_port.vblank_status;
|
||||
if (vblank_status.count % (main_port.flip_rate + 1) == 0) {
|
||||
const auto request = receive_request();
|
||||
if (!request) {
|
||||
delay = std::chrono::microseconds{0};
|
||||
if (!main_port.is_open) {
|
||||
DrawBlankFrame();
|
||||
}
|
||||
} else {
|
||||
delay = Flip(request);
|
||||
Flip(request);
|
||||
FRAME_END;
|
||||
}
|
||||
}
|
||||
|
@ -313,6 +309,8 @@ void VideoOutDriver::PresentThread(std::stop_token token) {
|
|||
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 SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop = false);
|
||||
void PresentThread(std::stop_token token);
|
||||
|
|
|
@ -90,11 +90,8 @@ void Linker::Execute() {
|
|||
|
||||
// Init primary thread.
|
||||
Common::SetCurrentThreadName("GAME_MainThread");
|
||||
#ifdef ARCH_X86_64
|
||||
InitializeThreadPatchStack();
|
||||
#endif
|
||||
Libraries::Kernel::pthreadInitSelfMainThread();
|
||||
InitTlsForThread(true);
|
||||
EnsureThreadInitialized(true);
|
||||
|
||||
// Start shared library modules
|
||||
for (auto& m : m_modules) {
|
||||
|
@ -335,6 +332,17 @@ void* Linker::TlsGetAddr(u64 module_index, u64 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) {
|
||||
static constexpr size_t TcbSize = 0x40;
|
||||
static constexpr size_t TlsAllocAlign = 0x20;
|
||||
|
|
|
@ -98,7 +98,6 @@ public:
|
|||
}
|
||||
|
||||
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);
|
||||
Module* FindByAddress(VAddr address);
|
||||
|
@ -109,8 +108,17 @@ public:
|
|||
void Execute();
|
||||
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:
|
||||
const Module* FindExportedModule(const ModuleInfo& m, const LibraryInfo& l);
|
||||
void EnsureThreadInitialized(bool is_primary = false);
|
||||
void InitTlsForThread(bool is_primary);
|
||||
|
||||
MemoryManager* memory;
|
||||
std::mutex mutex;
|
||||
|
|
|
@ -348,63 +348,6 @@ int MemoryManager::Protect(VAddr addr, size_t size, MemoryProt prot) {
|
|||
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,
|
||||
::Libraries::Kernel::OrbisVirtualQueryInfo* info) {
|
||||
std::scoped_lock lk{mutex};
|
||||
|
|
|
@ -166,8 +166,6 @@ public:
|
|||
|
||||
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 DirectMemoryQuery(PAddr addr, bool find_next,
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "common/string_util.h"
|
||||
#include "core/aerolib/aerolib.h"
|
||||
#include "core/cpu_patches.h"
|
||||
#include "core/linker.h"
|
||||
#include "core/loader/dwarf.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/module.h"
|
||||
|
@ -69,8 +70,9 @@ Module::~Module() = default;
|
|||
|
||||
s32 Module::Start(size_t args, const void* argp, void* param) {
|
||||
LOG_INFO(Core_Linker, "Module started : {}", name);
|
||||
auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
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) {
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include "common/arch.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/decoder.h"
|
||||
#include "common/signal_context.h"
|
||||
#include "core/signals.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
|
@ -10,7 +12,6 @@
|
|||
#else
|
||||
#include <csignal>
|
||||
#ifdef ARCH_X86_64
|
||||
#include <Zydis/Decoder.h>
|
||||
#include <Zydis/Formatter.h>
|
||||
#endif
|
||||
#endif
|
||||
|
@ -22,17 +23,14 @@ namespace Core {
|
|||
static LONG WINAPI SignalHandler(EXCEPTION_POINTERS* pExp) noexcept {
|
||||
const auto* signals = Signals::Instance();
|
||||
|
||||
auto* code_address = reinterpret_cast<void*>(pExp->ContextRecord->Rip);
|
||||
|
||||
bool handled = false;
|
||||
switch (pExp->ExceptionRecord->ExceptionCode) {
|
||||
case EXCEPTION_ACCESS_VIOLATION:
|
||||
handled = signals->DispatchAccessViolation(
|
||||
code_address, reinterpret_cast<void*>(pExp->ExceptionRecord->ExceptionInformation[1]),
|
||||
pExp->ExceptionRecord->ExceptionInformation[0] == 1);
|
||||
pExp, reinterpret_cast<void*>(pExp->ExceptionRecord->ExceptionInformation[1]));
|
||||
break;
|
||||
case EXCEPTION_ILLEGAL_INSTRUCTION:
|
||||
handled = signals->DispatchIllegalInstruction(code_address);
|
||||
handled = signals->DispatchIllegalInstruction(pExp);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -43,37 +41,14 @@ static LONG WINAPI SignalHandler(EXCEPTION_POINTERS* pExp) noexcept {
|
|||
|
||||
#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) {
|
||||
char buffer[256] = "<unable to decode>";
|
||||
|
||||
#ifdef ARCH_X86_64
|
||||
ZydisDecoder decoder;
|
||||
ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64);
|
||||
|
||||
ZydisDecodedInstruction instruction;
|
||||
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
|
||||
static constexpr u64 max_length = 0x20;
|
||||
const auto status =
|
||||
ZydisDecoderDecodeFull(&decoder, code_address, max_length, &instruction, operands);
|
||||
Common::Decoder::Instance()->decodeInstruction(instruction, operands, code_address);
|
||||
if (ZYAN_SUCCESS(status)) {
|
||||
ZydisFormatter formatter;
|
||||
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) {
|
||||
const auto* ctx = static_cast<ucontext_t*>(raw_context);
|
||||
const auto* signals = Signals::Instance();
|
||||
|
||||
auto* code_address = CODE_ADDRESS(ctx);
|
||||
auto* code_address = Common::GetRip(raw_context);
|
||||
|
||||
switch (sig) {
|
||||
case SIGSEGV:
|
||||
case SIGBUS:
|
||||
if (const bool is_write = IS_WRITE_ERROR(ctx);
|
||||
!signals->DispatchAccessViolation(code_address, info->si_addr, is_write)) {
|
||||
case SIGBUS: {
|
||||
const bool is_write = Common::IsWriteError(raw_context);
|
||||
if (!signals->DispatchAccessViolation(raw_context, info->si_addr)) {
|
||||
UNREACHABLE_MSG("Unhandled access violation at code address {}: {} address {}",
|
||||
fmt::ptr(code_address), is_write ? "Write to" : "Read from",
|
||||
fmt::ptr(info->si_addr));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SIGILL:
|
||||
if (!signals->DispatchIllegalInstruction(code_address)) {
|
||||
if (!signals->DispatchIllegalInstruction(raw_context)) {
|
||||
UNREACHABLE_MSG("Unhandled illegal instruction at code address {}: {}",
|
||||
fmt::ptr(code_address), DisassembleInstruction(code_address));
|
||||
}
|
||||
|
@ -150,19 +125,18 @@ SignalDispatch::~SignalDispatch() {
|
|||
#endif
|
||||
}
|
||||
|
||||
bool SignalDispatch::DispatchAccessViolation(void* code_address, void* fault_address,
|
||||
bool is_write) const {
|
||||
bool SignalDispatch::DispatchAccessViolation(void* context, void* fault_address) const {
|
||||
for (const auto& [handler, _] : access_violation_handlers) {
|
||||
if (handler(code_address, fault_address, is_write)) {
|
||||
if (handler(context, fault_address)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SignalDispatch::DispatchIllegalInstruction(void* code_address) const {
|
||||
bool SignalDispatch::DispatchIllegalInstruction(void* context) const {
|
||||
for (const auto& [handler, _] : illegal_instruction_handlers) {
|
||||
if (handler(code_address)) {
|
||||
if (handler(context)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
namespace Core {
|
||||
|
||||
using AccessViolationHandler = bool (*)(void* code_address, void* fault_address, bool is_write);
|
||||
using IllegalInstructionHandler = bool (*)(void* code_address);
|
||||
using AccessViolationHandler = bool (*)(void* context, void* fault_address);
|
||||
using IllegalInstructionHandler = bool (*)(void* context);
|
||||
|
||||
/// Receives OS signals and dispatches to the appropriate handlers.
|
||||
class SignalDispatch {
|
||||
|
@ -28,10 +28,10 @@ public:
|
|||
}
|
||||
|
||||
/// 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.
|
||||
bool DispatchIllegalInstruction(void* code_address) const;
|
||||
bool DispatchIllegalInstruction(void* context) const;
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "common/memory_patcher.h"
|
||||
#endif
|
||||
#include "common/assert.h"
|
||||
#include "common/elf_info.h"
|
||||
#include "common/ntapi.h"
|
||||
#include "common/path_util.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
|
@ -91,10 +92,14 @@ void Emulator::Run(const std::filesystem::path& file) {
|
|||
// Certain games may use /hostapp as well such as CUSA001100
|
||||
mnt->Mount(file.parent_path(), "/hostapp");
|
||||
|
||||
auto& game_info = Common::ElfInfo::Instance();
|
||||
|
||||
// Loading param.sfo file if exists
|
||||
std::string id;
|
||||
std::string title;
|
||||
std::string app_version;
|
||||
u32 fw_version;
|
||||
|
||||
std::filesystem::path sce_sys_folder = file.parent_path() / "sce_sys";
|
||||
if (std::filesystem::is_directory(sce_sys_folder)) {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(sce_sys_folder)) {
|
||||
|
@ -102,7 +107,9 @@ void Emulator::Run(const std::filesystem::path& file) {
|
|||
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
||||
const bool success = param_sfo->Open(sce_sys_folder / "param.sfo");
|
||||
ASSERT_MSG(success, "Failed to open param.sfo");
|
||||
id = std::string(*param_sfo->GetString("CONTENT_ID"), 7, 9);
|
||||
const auto content_id = param_sfo->GetString("CONTENT_ID");
|
||||
ASSERT_MSG(content_id.has_value(), "Failed to get CONTENT_ID");
|
||||
id = std::string(*content_id, 7, 9);
|
||||
Libraries::NpTrophy::game_serial = id;
|
||||
const auto trophyDir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / id / "TrophyFiles";
|
||||
|
@ -115,10 +122,10 @@ void Emulator::Run(const std::filesystem::path& file) {
|
|||
#ifdef ENABLE_QT_GUI
|
||||
MemoryPatcher::g_game_serial = id;
|
||||
#endif
|
||||
title = *param_sfo->GetString("TITLE");
|
||||
title = param_sfo->GetString("TITLE").value_or("Unknown title");
|
||||
LOG_INFO(Loader, "Game id: {} Title: {}", id, title);
|
||||
u32 fw_version = param_sfo->GetInteger("SYSTEM_VER").value_or(0x4700000);
|
||||
app_version = *param_sfo->GetString("APP_VER");
|
||||
fw_version = param_sfo->GetInteger("SYSTEM_VER").value_or(0x4700000);
|
||||
app_version = param_sfo->GetString("APP_VER").value_or("Unknown version");
|
||||
LOG_INFO(Loader, "Fw: {:#x} App Version: {}", fw_version, app_version);
|
||||
} else if (entry.path().filename() == "playgo-chunk.dat") {
|
||||
auto* playgo = Common::Singleton<PlaygoFile>::Instance();
|
||||
|
@ -132,13 +139,20 @@ void Emulator::Run(const std::filesystem::path& file) {
|
|||
if (splash->IsLoaded()) {
|
||||
continue;
|
||||
}
|
||||
if (!splash->Open(entry.path().string())) {
|
||||
if (!splash->Open(entry.path())) {
|
||||
LOG_ERROR(Loader, "Game splash: unable to open file");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
game_info.initialized = true;
|
||||
game_info.game_serial = id;
|
||||
game_info.title = title;
|
||||
game_info.app_ver = app_version;
|
||||
game_info.firmware_ver = fw_version & 0xFFF00000;
|
||||
game_info.raw_firmware_ver = fw_version;
|
||||
|
||||
std::string game_title = fmt::format("{} - {} <{}>", id, title, app_version);
|
||||
std::string window_title = "";
|
||||
if (Common::isRelease) {
|
||||
|
@ -175,7 +189,7 @@ void Emulator::Run(const std::filesystem::path& file) {
|
|||
if (!std::filesystem::exists(mount_captures_dir)) {
|
||||
std::filesystem::create_directory(mount_captures_dir);
|
||||
}
|
||||
VideoCore::SetOutputDir(mount_captures_dir.generic_string(), id);
|
||||
VideoCore::SetOutputDir(mount_captures_dir, id);
|
||||
|
||||
// Initialize kernel and library facilities.
|
||||
Libraries::Kernel::init_pthreads();
|
||||
|
@ -191,7 +205,7 @@ void Emulator::Run(const std::filesystem::path& file) {
|
|||
std::filesystem::path sce_module_folder = file.parent_path() / "sce_module";
|
||||
if (std::filesystem::is_directory(sce_module_folder)) {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(sce_module_folder)) {
|
||||
LOG_INFO(Loader, "Loading {}", entry.path().string().c_str());
|
||||
LOG_INFO(Loader, "Loading {}", fmt::UTF(entry.path().u8string()));
|
||||
linker->LoadModule(entry.path());
|
||||
}
|
||||
}
|
||||
|
|
BIN
src/images/dump_icon.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/images/update_icon.png
Normal file
After Width: | Height: | Size: 780 B |