Merge branch 'main' into trophy3

This commit is contained in:
CrazyBloo 2024-09-29 08:31:46 -04:00 committed by GitHub
commit 54e458413a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
237 changed files with 35419 additions and 24872 deletions

1
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1 @@
ko_fi: shadps4

481
.github/workflows/build.yml vendored Normal file
View 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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
View file

@ -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
View file

@ -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

View file

@ -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

View file

@ -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
@ -579,6 +584,8 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp
src/video_core/renderer_vulkan/vk_master_semaphore.h
src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
src/video_core/renderer_vulkan/vk_pipeline_cache.h
src/video_core/renderer_vulkan/vk_pipeline_common.cpp
src/video_core/renderer_vulkan/vk_pipeline_common.h
src/video_core/renderer_vulkan/vk_platform.cpp
src/video_core/renderer_vulkan/vk_platform.h
src/video_core/renderer_vulkan/vk_rasterizer.cpp
@ -646,8 +653,12 @@ qt_add_resources(RESOURCE_FILES src/shadps4.qrc)
set(QT_GUI src/qt_gui/about_dialog.cpp
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
@ -751,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()
@ -807,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
@ -822,4 +838,4 @@ if (UNIX AND NOT APPLE)
find_package(OpenSSL REQUIRED)
target_link_libraries(shadps4 PRIVATE ${OPENSSL_LIBRARIES})
endif()
endif()
endif()

View file

@ -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
View 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.

View file

@ -26,15 +26,15 @@ 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
shadPS4 is an early **PlayStation 4** emulator for **Windows**, **Linux** and **macOS** written in C++.
**shadPS4** is an early **PlayStation 4** emulator for **Windows**, **Linux** and **macOS** written in C++.
If you encounter problems or have doubts, do not hesitate to look at the [**Quickstart**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Quickstart/Quickstart.md).
@ -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,40 +71,12 @@ Check the build instructions for [**Linux**](https://github.com/shadps4-emu/shad
Check the build instructions for [**macOS**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-macos.md).
Note that macOS users need at least macOS 15 on an Apple Silicon Mac, or at least macOS 11 on an Intel Mac.
## Building status
<details>
<summary><b>Windows</b></summary>
| Windows | Build status |
|--------|--------|
|Windows SDL Build|[![Windows-sdl](https://github.com/shadps4-emu/shadPS4/actions/workflows/windows.yml/badge.svg)](https://github.com/shadps4-emu/shadPS4/actions/workflows/windows.yml)
|Windows Qt Build|[![Windows-qt](https://github.com/shadps4-emu/shadPS4/actions/workflows/windows-qt.yml/badge.svg)](https://github.com/shadps4-emu/shadPS4/actions/workflows/windows-qt.yml)
</details>
<details>
<summary><b>Linux</b></summary>
| Linux | Build status |
|--------|--------|
|Linux SDL Build|[![Linux-sdl](https://github.com/shadps4-emu/shadPS4/actions/workflows/linux.yml/badge.svg)](https://github.com/shadps4-emu/shadPS4/actions/workflows/linux.yml)
|Linux Qt Build|[![Linux-qt](https://github.com/shadps4-emu/shadPS4/actions/workflows/linux-qt.yml/badge.svg)](https://github.com/shadps4-emu/shadPS4/actions/workflows/linux-qt.yml)
</details>
<details>
<summary><b>macOS</b></summary>
| macOS | Build status |
|--------|--------|
|macOS SDL Build|[![macOS-sdl](https://github.com/shadps4-emu/shadPS4/actions/workflows/macos.yml/badge.svg)](https://github.com/shadps4-emu/shadPS4/actions/workflows/macos.yml)
|macOS Qt Build|[![macOS-qt](https://github.com/shadps4-emu/shadPS4/actions/workflows/macos-qt.yml/badge.svg)](https://github.com/shadps4-emu/shadPS4/actions/workflows/macos-qt.yml)
</details>
> [!IMPORTANT]
> macOS users need at least macOS 15 on Apple Silicon-based Mac devices and at least macOS 11 on Intel-based Mac devices.
# Debugging and reporting issues
For more information on how to test, debug and report issues with the emulator or games, read the [Debugging documentation](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Debugging/Debugging.md).
For more information on how to test, debug and report issues with the emulator or games, read the [**Debugging documentation**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Debugging/Debugging.md).
# Keyboard mapping
@ -172,12 +146,6 @@ A few noteworthy teams/projects who've helped us along the way are:
- [**hydra**](https://github.com/hydra-emu/hydra): A multisystem, multiplatform emulator (chip-8, GB, NES, N64) from Paris.
# 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
View 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"

View file

@ -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). ![image](https://github.com/user-attachments/assets/43f01bbf-236c-4d6d-98ac-f5a5badd4ce8)
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).
![image](https://github.com/user-attachments/assets/af520c77-797c-41a0-8f67-d87f5de3e3df)
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). ![image](https://github.com/user-attachments/assets/6365f407-867c-44ae-bf00-944f8d84a349)
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. ![image](https://github.com/user-attachments/assets/97924500-3911-4f90-ab63-ffae7e52700b)
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
documents/Screenshots/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

BIN
documents/Screenshots/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
documents/Screenshots/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 850 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 KiB

View file

@ -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"`

View file

@ -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

@ -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

@ -1 +1 @@
Subproject commit d205aff40b4e15d4c568523ee6a26f85138126d9
Subproject commit 29f979ee5aa58b7b005f805ea8df7a855c39ff37

2
externals/xxhash vendored

@ -1 +1 @@
Subproject commit dbea33e47e7c0fe0b7c8592cd931c7430c1f130d
Subproject commit 3e321b4407318ac1348c0b80fb6fbae8c81ad5fa

View file

@ -28,4 +28,16 @@ template <typename T>
return (value & 0x3FFF) == 0;
}
template <typename T>
requires std::is_integral_v<T>
[[nodiscard]] constexpr bool Is64KBAligned(T value) {
return (value & 0xFFFF) == 0;
}
template <typename T>
requires std::is_integral_v<T>
[[nodiscard]] constexpr bool Is2MBAligned(T value) {
return (value & 0x1FFFFF) == 0;
}
} // namespace Common

View file

@ -3,24 +3,48 @@
#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 int BGMvolume = 50;
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 +58,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 +86,14 @@ bool isFullscreenMode() {
return isFullscreen;
}
bool getPlayBGM() {
return playBGM;
}
int getBGMvolume() {
return BGMvolume;
}
u32 getScreenWidth() {
return screenWidth;
}
@ -86,6 +118,10 @@ std::string getUserName() {
return userName;
}
std::string getUpdateChannel() {
return updateChannel;
}
bool getUseSpecialPad() {
return useSpecialPad;
}
@ -102,6 +138,10 @@ bool showSplash() {
return isShowSplash;
}
bool autoUpdate() {
return isAutoUpdate;
}
bool nullGpu() {
return isNullGpu;
}
@ -170,6 +210,10 @@ void setShowSplash(bool enable) {
isShowSplash = enable;
}
void setAutoUpdate(bool enable) {
isAutoUpdate = enable;
}
void setNullGpu(bool enable) {
isNullGpu = enable;
}
@ -206,6 +250,14 @@ void setFullscreenMode(bool enable) {
isFullscreen = enable;
}
void setPlayBGM(bool enable) {
playBGM = enable;
}
void setBGMvolume(int volume) {
BGMvolume = volume;
}
void setLanguage(u32 language) {
m_language = language;
}
@ -226,6 +278,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 +296,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 +352,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 +407,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 +420,18 @@ 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);
BGMvolume = toml::find_or<int>(general, "BGMvolume", 50);
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 +481,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 +505,31 @@ 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"]["BGMvolume"] = BGMvolume;
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 +555,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 +575,23 @@ void save(const std::filesystem::path& path) {
void setDefaultValues() {
isNeo = false;
isFullscreen = false;
playBGM = false;
BGMvolume = 50;
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;

View file

@ -13,9 +13,13 @@ void save(const std::filesystem::path& path);
bool isNeoMode();
bool isFullscreenMode();
bool getPlayBGM();
int getBGMvolume();
std::string getLogFilter();
std::string getLogType();
std::string getUserName();
std::string getUpdateChannel();
bool getUseSpecialPad();
int getSpecialPadClass();
@ -26,6 +30,7 @@ s32 getGpuId();
bool debugDump();
bool showSplash();
bool autoUpdate();
bool nullGpu();
bool copyGPUCmdBuffers();
bool dumpShaders();
@ -35,6 +40,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 +50,12 @@ void setGpuId(s32 selectedGpuId);
void setScreenWidth(u32 width);
void setScreenHeight(u32 height);
void setFullscreenMode(bool enable);
void setPlayBGM(bool enable);
void setBGMvolume(int volume);
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 +75,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 +93,7 @@ u32 getMainWindowGeometryX();
u32 getMainWindowGeometryY();
u32 getMainWindowGeometryW();
u32 getMainWindowGeometryH();
std::string getGameInstallDir();
std::filesystem::path getGameInstallDir();
u32 getMainWindowTheme();
u32 getIconSize();
u32 getIconSizeGrid();

View file

@ -9,6 +9,9 @@
namespace Common {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wtautological-undefined-compare"
/**
* @brief A null-terminated string with a fixed maximum length
* This class is not meant to be used as a general-purpose string class
@ -29,20 +32,27 @@ public:
explicit CString(const CString<M>& other)
requires(M <= N)
{
if (this == nullptr) {
return;
}
std::ranges::copy(other.begin(), other.end(), data);
}
void FromString(const std::basic_string_view<T>& str) {
if (this == nullptr) {
return;
}
size_t p = str.copy(data, N - 1);
data[p] = '\0';
}
void Zero() {
if (this == nullptr) {
return;
}
std::ranges::fill(data, 0);
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wtautological-undefined-compare"
explicit(false) operator std::basic_string_view<T>() const {
if (this == nullptr) {
return {};
@ -70,21 +80,32 @@ public:
}
return std::basic_string_view<T>{data};
}
#pragma clang diagnostic pop
char* begin() {
if (this == nullptr) {
return nullptr;
}
return data;
}
const char* begin() const {
if (this == nullptr) {
return nullptr;
}
return data;
}
char* end() {
if (this == nullptr) {
return nullptr;
}
return data + N;
}
const char* end() const {
if (this == nullptr) {
return nullptr;
}
return data + N;
}
@ -127,7 +148,10 @@ public:
}
};
};
static_assert(sizeof(CString<13>) == sizeof(char[13])); // Ensure size still matches a simple array
static_assert(std::weakly_incrementable<CString<13>::Iterator>);
#pragma clang diagnostic pop
} // namespace Common

View file

@ -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

View file

@ -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
View 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

View file

@ -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);
}
};

View file

@ -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) {

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View 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

View 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

View file

@ -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, &params);
}
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

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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 {} with length {} and index {} is bigger than 64, "
"undefined behavior",
fmt::ptr(code_address), length, index);
}
lowQWordDst >>= index;
lowQWordDst &= mask;
memcpy(dst, &lowQWordDst, sizeof(lowQWordDst));
Common::IncrementRip(ctx, instruction.length);
return true;
}
break;
}
case ZYDIS_MNEMONIC_INSERTQ: {
bool immediateForm = operands[2].type == ZYDIS_OPERAND_TYPE_IMMEDIATE &&
operands[3].type == ZYDIS_OPERAND_TYPE_IMMEDIATE;
if (immediateForm) {
LOG_CRITICAL(Core,
"INSERTQ immediate form should have been patched at code address: {}",
fmt::ptr(code_address));
return false;
} else {
ASSERT_MSG(operands[2].type == ZYDIS_OPERAND_TYPE_UNUSED &&
operands[3].type == ZYDIS_OPERAND_TYPE_UNUSED,
"operands 2 and 3 must be unused for register form.");
ASSERT_MSG(operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER,
"operands 0 and 1 must be registers.");
const auto dstIndex = operands[0].reg.value - ZYDIS_REGISTER_XMM0;
const auto srcIndex = operands[1].reg.value - ZYDIS_REGISTER_XMM0;
const auto dst = Common::GetXmmPointer(ctx, dstIndex);
const auto src = Common::GetXmmPointer(ctx, srcIndex);
u64 lowQWordSrc, highQWordSrc;
memcpy(&lowQWordSrc, src, sizeof(lowQWordSrc));
memcpy(&highQWordSrc, (u8*)src + 8, sizeof(highQWordSrc));
u64 lowQWordDst;
memcpy(&lowQWordDst, dst, sizeof(lowQWordDst));
u64 length = highQWordSrc & 0x3F;
u64 mask;
if (length == 0) {
length = 64; // for the check below
mask = 0xFFFF'FFFF'FFFF'FFFF;
} else {
mask = (1ULL << length) - 1;
}
u64 index = (highQWordSrc >> 8) & 0x3F;
if (length + index > 64) {
// Undefined behavior if length + index is bigger than 64 according to the spec,
// we'll warn and continue execution.
LOG_WARNING(Core,
"insertq at {} with length {} and index {} is bigger than 64, "
"undefined behavior",
fmt::ptr(code_address), length, index);
}
lowQWordSrc &= mask;
lowQWordDst &= ~(mask << index);
lowQWordDst |= lowQWordSrc << index;
memcpy(dst, &lowQWordDst, sizeof(lowQWordDst));
Common::IncrementRip(ctx, instruction.length);
return true;
}
break;
}
default: {
LOG_ERROR(Core, "Unhandled illegal instruction at code address {}: {}",
fmt::ptr(code_address), ZydisMnemonicGetString(instruction.mnemonic));
return false;
}
}
UNREACHABLE();
}
#elif defined(ARCH_ARM64)
// These functions shouldn't be needed for ARM as it will use a JIT so there's no need to patch
// instructions.
static bool TryExecuteIllegalInstruction(void*, void*) {
return false;
}
#else
#error "Unsupported architecture"
#endif
static bool TryPatchJit(void* code_address) {
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.

View file

@ -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);

View file

@ -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 {

View file

@ -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()) {

View file

@ -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();
}

View file

@ -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;

View file

@ -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)) {

View file

@ -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

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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) {

View file

@ -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

View file

@ -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);
}

View file

@ -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();
}
@ -288,7 +290,8 @@ int PS4_SYSV_ABI sceKernelMkdir(const char* path, u16 mode) {
}
// CUSA02456: path = /aotl after sceSaveDataMount(mode = 1)
if (dir_name.empty() || !std::filesystem::create_directory(dir_name)) {
std::error_code ec;
if (dir_name.empty() || !std::filesystem::create_directory(dir_name, ec)) {
return SCE_KERNEL_ERROR_EIO;
}

View file

@ -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"
@ -55,7 +56,7 @@ void KernelSignalRequest() {
}
static void KernelServiceThread(std::stop_token stoken) {
Common::SetCurrentThreadName("Kernel_ServiceThread");
Common::SetCurrentThreadName("shadPS4:Kernel_ServiceThread");
while (!stoken.stop_requested()) {
HLE_TRACE;
@ -185,6 +186,16 @@ void* PS4_SYSV_ABI posix_mmap(void* addr, u64 len, int prot, int flags, int fd,
return ptr;
}
s32 PS4_SYSV_ABI sceKernelConfiguredFlexibleMemorySize(u64* sizeOut) {
if (sizeOut == nullptr) {
return ORBIS_KERNEL_ERROR_EINVAL;
}
auto* memory = Core::Memory::Instance();
*sizeOut = memory->GetTotalFlexibleSize();
return ORBIS_OK;
}
static uint64_t g_mspace_atomic_id_mask = 0;
static uint64_t g_mstate_table[64] = {0};
@ -243,8 +254,7 @@ int PS4_SYSV_ABI sceKernelConvertUtcToLocaltime(time_t time, time_t* local_time,
}
int PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(int* ver) {
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;
@ -403,10 +413,12 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) {
// obj
LIB_OBJ("f7uOxY9mM1U", "libkernel", 1, "libkernel", 1, 1, &g_stack_chk_guard);
// misc
LIB_FUNCTION("JGfTMBOdUJo", "libkernel", 1, "libkernel", 1, 1, sceKernelGetFsSandboxRandomWord);
LIB_FUNCTION("XVL8So3QJUk", "libkernel", 1, "libkernel", 1, 1, posix_connect);
LIB_FUNCTION("6xVpy0Fdq+I", "libkernel", 1, "libkernel", 1, 1, _sigprocmask);
// memory
LIB_FUNCTION("OMDRKKAZ8I4", "libkernel", 1, "libkernel", 1, 1, sceKernelDebugRaiseException);
LIB_FUNCTION("rTXw65xmLIA", "libkernel", 1, "libkernel", 1, 1, sceKernelAllocateDirectMemory);
@ -425,6 +437,7 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("WFcfL2lzido", "libkernel", 1, "libkernel", 1, 1, sceKernelQueryMemoryProtection);
LIB_FUNCTION("BHouLQzh0X0", "libkernel", 1, "libkernel", 1, 1, sceKernelDirectMemoryQuery);
LIB_FUNCTION("MBuItvba6z8", "libkernel", 1, "libkernel", 1, 1, sceKernelReleaseDirectMemory);
LIB_FUNCTION("PGhQHd-dzv8", "libkernel", 1, "libkernel", 1, 1, sceKernelMmap);
LIB_FUNCTION("cQke9UuBQOk", "libkernel", 1, "libkernel", 1, 1, sceKernelMunmap);
LIB_FUNCTION("mL8NDH86iQI", "libkernel", 1, "libkernel", 1, 1, sceKernelMapNamedFlexibleMemory);
LIB_FUNCTION("aNz11fnnzi4", "libkernel", 1, "libkernel", 1, 1,
@ -442,6 +455,14 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("2SKEx6bSq-4", "libkernel", 1, "libkernel", 1, 1, sceKernelBatchMap);
LIB_FUNCTION("kBJzF8x4SyE", "libkernel", 1, "libkernel", 1, 1, sceKernelBatchMap2);
LIB_FUNCTION("DGMG3JshrZU", "libkernel", 1, "libkernel", 1, 1, sceKernelSetVirtualRangeName);
LIB_FUNCTION("n1-v6FgU7MQ", "libkernel", 1, "libkernel", 1, 1,
sceKernelConfiguredFlexibleMemorySize);
// Memory pool
LIB_FUNCTION("qCSfqDILlns", "libkernel", 1, "libkernel", 1, 1, sceKernelMemoryPoolExpand);
LIB_FUNCTION("pU-QydtGcGY", "libkernel", 1, "libkernel", 1, 1, sceKernelMemoryPoolReserve);
LIB_FUNCTION("Vzl66WmfLvk", "libkernel", 1, "libkernel", 1, 1, sceKernelMemoryPoolCommit);
LIB_FUNCTION("LXo1tpFqJGs", "libkernel", 1, "libkernel", 1, 1, sceKernelMemoryPoolDecommit);
// equeue
LIB_FUNCTION("D0OdFMjp46I", "libkernel", 1, "libkernel", 1, 1, sceKernelCreateEqueue);

View file

@ -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,
@ -348,4 +347,102 @@ s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, size_t len, cons
memory->NameVirtualRange(std::bit_cast<VAddr>(addr), len, name);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelMemoryPoolExpand(u64 searchStart, u64 searchEnd, size_t len,
size_t alignment, u64* physAddrOut) {
if (searchStart < 0 || searchEnd <= searchStart) {
LOG_ERROR(Kernel_Vmm, "Provided address range is invalid!");
return SCE_KERNEL_ERROR_EINVAL;
}
const bool is_in_range = searchEnd - searchStart >= len;
if (len <= 0 || !Common::Is64KBAligned(len) || !is_in_range) {
LOG_ERROR(Kernel_Vmm, "Provided address range is invalid!");
return SCE_KERNEL_ERROR_EINVAL;
}
if (alignment != 0 && !Common::Is64KBAligned(alignment)) {
LOG_ERROR(Kernel_Vmm, "Alignment value is invalid!");
return SCE_KERNEL_ERROR_EINVAL;
}
if (physAddrOut == nullptr) {
LOG_ERROR(Kernel_Vmm, "Result physical address pointer is null!");
return SCE_KERNEL_ERROR_EINVAL;
}
auto* memory = Core::Memory::Instance();
PAddr phys_addr = memory->PoolExpand(searchStart, searchEnd, len, alignment);
*physAddrOut = static_cast<s64>(phys_addr);
LOG_INFO(Kernel_Vmm,
"searchStart = {:#x}, searchEnd = {:#x}, len = {:#x}, alignment = {:#x}, physAddrOut "
"= {:#x}",
searchStart, searchEnd, len, alignment, phys_addr);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelMemoryPoolReserve(void* addrIn, size_t len, size_t alignment, int flags,
void** addrOut) {
LOG_INFO(Kernel_Vmm, "addrIn = {}, len = {:#x}, alignment = {:#x}, flags = {:#x}",
fmt::ptr(addrIn), len, alignment, flags);
if (addrIn == nullptr) {
LOG_ERROR(Kernel_Vmm, "Address is invalid!");
return SCE_KERNEL_ERROR_EINVAL;
}
if (len == 0 || !Common::Is2MBAligned(len)) {
LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 2MB aligned!");
return SCE_KERNEL_ERROR_EINVAL;
}
if (alignment != 0) {
if ((!std::has_single_bit(alignment) && !Common::Is2MBAligned(alignment))) {
LOG_ERROR(Kernel_Vmm, "Alignment value is invalid!");
return SCE_KERNEL_ERROR_EINVAL;
}
}
auto* memory = Core::Memory::Instance();
const VAddr in_addr = reinterpret_cast<VAddr>(addrIn);
const auto map_flags = static_cast<Core::MemoryMapFlags>(flags);
memory->PoolReserve(addrOut, in_addr, len, map_flags, alignment);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelMemoryPoolCommit(void* addr, size_t len, int type, int prot, int flags) {
if (addr == nullptr) {
LOG_ERROR(Kernel_Vmm, "Address is invalid!");
return SCE_KERNEL_ERROR_EINVAL;
}
if (len == 0 || !Common::Is64KBAligned(len)) {
LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 64KB aligned!");
return SCE_KERNEL_ERROR_EINVAL;
}
LOG_INFO(Kernel_Vmm, "addr = {}, len = {:#x}, type = {:#x}, prot = {:#x}, flags = {:#x}",
fmt::ptr(addr), len, type, prot, flags);
const VAddr in_addr = reinterpret_cast<VAddr>(addr);
const auto mem_prot = static_cast<Core::MemoryProt>(prot);
auto* memory = Core::Memory::Instance();
return memory->PoolCommit(in_addr, len, mem_prot);
}
s32 PS4_SYSV_ABI sceKernelMemoryPoolDecommit(void* addr, size_t len, int flags) {
if (addr == nullptr) {
LOG_ERROR(Kernel_Vmm, "Address is invalid!");
return SCE_KERNEL_ERROR_EINVAL;
}
if (len == 0 || !Common::Is64KBAligned(len)) {
LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 64KB aligned!");
return SCE_KERNEL_ERROR_EINVAL;
}
LOG_INFO(Kernel_Vmm, "addr = {}, len = {:#x}, flags = {:#x}", fmt::ptr(addr), len, flags);
const VAddr pool_addr = reinterpret_cast<VAddr>(addr);
auto* memory = Core::Memory::Instance();
memory->PoolDecommit(pool_addr, len);
return ORBIS_OK;
}
} // namespace Libraries::Kernel

View file

@ -114,4 +114,11 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn
s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, size_t len, const char* name);
s32 PS4_SYSV_ABI sceKernelMemoryPoolExpand(u64 searchStart, u64 searchEnd, size_t len,
size_t alignment, u64* physAddrOut);
s32 PS4_SYSV_ABI sceKernelMemoryPoolReserve(void* addrIn, size_t len, size_t alignment, int flags,
void** addrOut);
s32 PS4_SYSV_ABI sceKernelMemoryPoolCommit(void* addr, size_t len, int type, int prot, int flags);
s32 PS4_SYSV_ABI sceKernelMemoryPoolDecommit(void* addr, size_t len, int flags);
} // namespace Libraries::Kernel

View file

@ -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"
@ -416,6 +414,7 @@ ScePthreadMutex* createMutex(ScePthreadMutex* addr) {
if (addr == nullptr || *addr != nullptr) {
return addr;
}
const VAddr vaddr = reinterpret_cast<VAddr>(addr);
std::string name = fmt::format("mutex{:#x}", vaddr);
scePthreadMutexInit(addr, nullptr, name.c_str());
@ -517,9 +516,12 @@ int PS4_SYSV_ABI scePthreadMutexattrSettype(ScePthreadMutexattr* attr, int type)
ptype = PTHREAD_MUTEX_RECURSIVE;
break;
case ORBIS_PTHREAD_MUTEX_NORMAL:
case ORBIS_PTHREAD_MUTEX_ADAPTIVE:
ptype = PTHREAD_MUTEX_NORMAL;
break;
case ORBIS_PTHREAD_MUTEX_ADAPTIVE:
LOG_ERROR(Kernel_Pthread, "Unimplemented adaptive mutex");
ptype = PTHREAD_MUTEX_ERRORCHECK;
break;
default:
return SCE_KERNEL_ERROR_EINVAL;
}
@ -991,16 +993,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 +1510,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;
}
@ -1622,6 +1624,10 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("upoVrzMHFeE", "libkernel", 1, "libkernel", 1, 1, scePthreadMutexTrylock);
LIB_FUNCTION("IafI2PxcPnQ", "libkernel", 1, "libkernel", 1, 1, scePthreadMutexTimedlock);
// scePthreadMutexInitForInternalLibc, scePthreadMutexattrInitForInternalLibc
LIB_FUNCTION("qH1gXoq71RY", "libkernel", 1, "libkernel", 1, 1, scePthreadMutexInit);
LIB_FUNCTION("n2MMpvU8igI", "libkernel", 1, "libkernel", 1, 1, scePthreadMutexattrInit);
// cond calls
LIB_FUNCTION("2Tb92quprl0", "libkernel", 1, "libkernel", 1, 1, scePthreadCondInit);
LIB_FUNCTION("m5-2bsNfv7s", "libkernel", 1, "libkernel", 1, 1, scePthreadCondattrInit);

View file

@ -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);

View file

@ -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);
}
}
}

View file

@ -506,10 +506,10 @@ s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context,
const auto trophy_dir =
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
auto trophy_file = trophy_dir / "trophy00" / "Xml" / "TROP.XML";
pugi::xml_document doc;
pugi::xml_parse_result result =
doc.load_file((trophy_dir / "trophy00" / "Xml" / "TROP.XML").c_str());
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
ASSERT_MSG(result, "Couldnt parse trophy XML : {}", result.description());

View file

@ -91,4 +91,4 @@ void TrophyUI::Draw() {
End();
}
}
}
}

View file

@ -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"

View file

@ -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()) {
@ -620,7 +641,7 @@ void SaveDialogUi::DrawList() {
SetCursorPosX(GetContentRegionAvail().x - button_size.x);
if (Button(back, button_size)) {
result->dir_name.clear();
Finish(ButtonId::INVALID);
Finish(ButtonId::INVALID, Result::USER_CANCELED);
}
if (IsKeyPressed(ImGuiKey_GamepadFaceRight)) {
SetItemCurrentNavFocus();
@ -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;

View file

@ -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 {

View file

@ -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) {
Common::SetCurrentThreadName("shadPS4:SaveData_BackupThread");
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) {

View file

@ -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{};

View file

@ -66,7 +66,7 @@ static void SaveFileSafe(void* buf, size_t count, const std::filesystem::path& p
}
[[noreturn]] void SaveThreadLoop() {
Common::SetCurrentThreadName("SaveData_SaveDataMemoryThread");
Common::SetCurrentThreadName("shadPS4:SaveData_SaveDataMemoryThread");
std::mutex mtx;
while (true) {
{
@ -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;

View file

@ -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;

View file

@ -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();

View file

@ -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;

View file

@ -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;

View file

@ -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() {
@ -264,9 +260,11 @@ void VideoOutDriver::PresentThread(std::stop_token token) {
static constexpr std::chrono::nanoseconds VblankPeriod{16666667};
const auto vblank_period = VblankPeriod / Config::vblankDiv();
Common::SetCurrentThreadName("PresentThread");
Common::SetCurrentThreadName("shadPS4: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();
}
}

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -51,6 +51,35 @@ void MemoryManager::SetupMemoryRegions(u64 flexible_size) {
total_flexible_size, total_direct_size);
}
PAddr MemoryManager::PoolExpand(PAddr search_start, PAddr search_end, size_t size, u64 alignment) {
std::scoped_lock lk{mutex};
auto dmem_area = FindDmemArea(search_start);
const auto is_suitable = [&] {
const auto aligned_base = alignment > 0 ? Common::AlignUp(dmem_area->second.base, alignment)
: dmem_area->second.base;
const auto alignment_size = aligned_base - dmem_area->second.base;
const auto remaining_size =
dmem_area->second.size >= alignment_size ? dmem_area->second.size - alignment_size : 0;
return dmem_area->second.is_free && remaining_size >= size;
};
while (!is_suitable() && dmem_area->second.GetEnd() <= search_end) {
dmem_area++;
}
ASSERT_MSG(is_suitable(), "Unable to find free direct memory area: size = {:#x}", size);
// Align free position
PAddr free_addr = dmem_area->second.base;
free_addr = alignment > 0 ? Common::AlignUp(free_addr, alignment) : free_addr;
// Add the allocated region to the list and commit its pages.
auto& area = CarveDmemArea(free_addr, size)->second;
area.is_free = false;
area.is_pooled = true;
return free_addr;
}
PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, u64 alignment,
int memory_type) {
std::scoped_lock lk{mutex};
@ -112,6 +141,43 @@ void MemoryManager::Free(PAddr phys_addr, size_t size) {
MergeAdjacent(dmem_map, dmem_area);
}
int MemoryManager::PoolReserve(void** out_addr, VAddr virtual_addr, size_t size,
MemoryMapFlags flags, u64 alignment) {
std::scoped_lock lk{mutex};
virtual_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr;
alignment = alignment > 0 ? alignment : 2_MB;
VAddr mapped_addr = alignment > 0 ? Common::AlignUp(virtual_addr, alignment) : virtual_addr;
// Fixed mapping means the virtual address must exactly match the provided one.
if (True(flags & MemoryMapFlags::Fixed)) {
const auto& vma = FindVMA(mapped_addr)->second;
// If the VMA is mapped, unmap the region first.
if (vma.IsMapped()) {
UnmapMemoryImpl(mapped_addr, size);
}
const size_t remaining_size = vma.base + vma.size - mapped_addr;
ASSERT_MSG(vma.type == VMAType::Free && remaining_size >= size);
}
// Find the first free area starting with provided virtual address.
if (False(flags & MemoryMapFlags::Fixed)) {
mapped_addr = SearchFree(mapped_addr, size, alignment);
}
// Add virtual memory area
const auto new_vma_handle = CarveVMA(mapped_addr, size);
auto& new_vma = new_vma_handle->second;
new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce);
new_vma.prot = MemoryProt::NoAccess;
new_vma.name = "";
new_vma.type = VMAType::PoolReserved;
MergeAdjacent(vma_map, new_vma_handle);
*out_addr = std::bit_cast<void*>(mapped_addr);
return ORBIS_OK;
}
int MemoryManager::Reserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags,
u64 alignment) {
std::scoped_lock lk{mutex};
@ -149,6 +215,36 @@ int MemoryManager::Reserve(void** out_addr, VAddr virtual_addr, size_t size, Mem
return ORBIS_OK;
}
int MemoryManager::PoolCommit(VAddr virtual_addr, size_t size, MemoryProt prot) {
std::scoped_lock lk{mutex};
const u64 alignment = 64_KB;
// When virtual addr is zero, force it to virtual_base. The guest cannot pass Fixed
// flag so we will take the branch that searches for free (or reserved) mappings.
virtual_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr;
VAddr mapped_addr = Common::AlignUp(virtual_addr, alignment);
// This should return SCE_KERNEL_ERROR_ENOMEM but shouldn't normally happen.
const auto& vma = FindVMA(mapped_addr)->second;
const size_t remaining_size = vma.base + vma.size - mapped_addr;
ASSERT_MSG(!vma.IsMapped() && remaining_size >= size);
// Perform the mapping.
void* out_addr = impl.Map(mapped_addr, size, alignment, -1, false);
TRACK_ALLOC(out_addr, size, "VMEM");
auto& new_vma = CarveVMA(mapped_addr, size)->second;
new_vma.disallow_merge = false;
new_vma.prot = prot;
new_vma.name = "";
new_vma.type = Core::VMAType::Pooled;
new_vma.is_exec = false;
new_vma.phys_base = 0;
return ORBIS_OK;
}
int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
MemoryMapFlags flags, VMAType type, std::string_view name,
bool is_exec, PAddr phys_addr, u64 alignment) {
@ -232,6 +328,39 @@ int MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, size_t size, Mem
return ORBIS_OK;
}
void MemoryManager::PoolDecommit(VAddr virtual_addr, size_t size) {
std::scoped_lock lk{mutex};
const auto it = FindVMA(virtual_addr);
const auto& vma_base = it->second;
ASSERT_MSG(vma_base.Contains(virtual_addr, size),
"Existing mapping does not contain requested unmap range");
const auto vma_base_addr = vma_base.base;
const auto vma_base_size = vma_base.size;
const auto phys_base = vma_base.phys_base;
const bool is_exec = vma_base.is_exec;
const auto start_in_vma = virtual_addr - vma_base_addr;
const auto type = vma_base.type;
rasterizer->UnmapMemory(virtual_addr, size);
// Mark region as free and attempt to coalesce it with neighbours.
const auto new_it = CarveVMA(virtual_addr, size);
auto& vma = new_it->second;
vma.type = VMAType::PoolReserved;
vma.prot = MemoryProt::NoAccess;
vma.phys_base = 0;
vma.disallow_merge = false;
vma.name = "";
MergeAdjacent(vma_map, new_it);
// Unmap the memory region.
impl.Unmap(vma_base_addr, vma_base_size, start_in_vma, start_in_vma + size, phys_base, is_exec,
false, false);
TRACK_FREE(virtual_addr, "VMEM");
}
void MemoryManager::UnmapMemory(VAddr virtual_addr, size_t size) {
std::scoped_lock lk{mutex};
UnmapMemoryImpl(virtual_addr, size);
@ -348,63 +477,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};

View file

@ -50,15 +50,17 @@ enum class VMAType : u32 {
Direct = 2,
Flexible = 3,
Pooled = 4,
Stack = 5,
Code = 6,
File = 7,
PoolReserved = 5,
Stack = 6,
Code = 7,
File = 8,
};
struct DirectMemoryArea {
PAddr base = 0;
size_t size = 0;
int memory_type = 0;
bool is_pooled = false;
bool is_free = true;
PAddr GetEnd() const {
@ -96,7 +98,7 @@ struct VirtualMemoryArea {
}
bool IsMapped() const noexcept {
return type != VMAType::Free && type != VMAType::Reserved;
return type != VMAType::Free && type != VMAType::Reserved && type != VMAType::PoolReserved;
}
bool CanMergeWith(const VirtualMemoryArea& next) const {
@ -135,6 +137,10 @@ public:
return total_direct_size;
}
u64 GetTotalFlexibleSize() const {
return total_flexible_size;
}
u64 GetAvailableFlexibleSize() const {
return total_flexible_size - flexible_usage;
}
@ -145,14 +151,21 @@ public:
void SetupMemoryRegions(u64 flexible_size);
PAddr PoolExpand(PAddr search_start, PAddr search_end, size_t size, u64 alignment);
PAddr Allocate(PAddr search_start, PAddr search_end, size_t size, u64 alignment,
int memory_type);
void Free(PAddr phys_addr, size_t size);
int PoolReserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags,
u64 alignment = 0);
int Reserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags,
u64 alignment = 0);
int PoolCommit(VAddr virtual_addr, size_t size, MemoryProt prot);
int MapMemory(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
MemoryMapFlags flags, VMAType type, std::string_view name = "",
bool is_exec = false, PAddr phys_addr = -1, u64 alignment = 0);
@ -160,14 +173,14 @@ public:
int MapFile(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
MemoryMapFlags flags, uintptr_t fd, size_t offset);
void PoolDecommit(VAddr virtual_addr, size_t size);
void UnmapMemory(VAddr virtual_addr, size_t size);
int QueryProtection(VAddr addr, void** start, void** end, u32* prot);
int 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,

View file

@ -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) {

View file

@ -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;
}
}

View file

@ -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>

Some files were not shown because too many files have changed in this diff Show more