mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-02-19 23:31:07 +00:00
Merge branch 'implement-IsSixAxisSensorAtRest' of https://git.ryujinx.app/sh0inx/ryujinx into implement-IsSixAxisSensorAtRest
This commit is contained in:
99
.github/workflows/canary.yml
vendored
99
.github/workflows/canary.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Canary release job
|
||||
name: Canary CI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -19,7 +19,6 @@ concurrency: release
|
||||
env:
|
||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
RYUJINX_BASE_VERSION: "1.3"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "canary"
|
||||
RELEASE: 1
|
||||
|
||||
@@ -30,8 +29,8 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
||||
#- { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 }
|
||||
- { name: win-x64, os: ubuntu-latest, zip_os_name: win_x64 }
|
||||
#- { name: win-arm64, os: ubuntu-latest, zip_os_name: win_arm64 }
|
||||
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
||||
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
||||
steps:
|
||||
@@ -44,11 +43,25 @@ jobs:
|
||||
- name: Overwrite csc problem matcher
|
||||
run: echo "::add-matcher::.github/csc.json"
|
||||
|
||||
- name: Install 7zip
|
||||
run: |
|
||||
sudo apt install -y 7zip
|
||||
|
||||
- name: Install gli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
||||
echo "build_version=$(gli get-next-version -c Canary -R)" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
@@ -69,33 +82,20 @@ jobs:
|
||||
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained
|
||||
|
||||
- name: Packing Windows builds
|
||||
if: matrix.platform.os == 'windows-latest'
|
||||
if: contains(matrix.platform.name, 'win')
|
||||
run: |
|
||||
pushd publish
|
||||
rm libarmeilleure-jitsupport.dylib
|
||||
7z a ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
||||
popd
|
||||
|
||||
gh release download -R GreemDev/GLI -O gli.exe -p 'GitLabCli-win_x64.exe'
|
||||
|
||||
./gli.exe --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip"
|
||||
|
||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install GitLabCli
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Packing Linux builds
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
if: contains(matrix.platform.name, 'linux')
|
||||
run: |
|
||||
pushd publish
|
||||
rm libarmeilleure-jitsupport.dylib
|
||||
@@ -103,11 +103,11 @@ jobs:
|
||||
tar -czvf ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
||||
popd
|
||||
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz"
|
||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz
|
||||
shell: bash
|
||||
|
||||
- name: Build AppImage (Linux)
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
if: contains(matrix.platform.name, 'linux')
|
||||
run: |
|
||||
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
|
||||
PLATFORM_NAME="${{ matrix.platform.name }}"
|
||||
@@ -139,8 +139,8 @@ jobs:
|
||||
pushd publish_appimage
|
||||
mv Ryujinx.AppImage ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||
popd
|
||||
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage"
|
||||
|
||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||
shell: bash
|
||||
|
||||
macos_release:
|
||||
@@ -159,10 +159,10 @@ jobs:
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 17
|
||||
|
||||
- name: Install GitLabCli
|
||||
- name: Install gli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
@@ -183,9 +183,10 @@ jobs:
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
||||
echo "build_version=$(gli get-next-version -c Canary -R)" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Configure for release
|
||||
run: |
|
||||
@@ -200,7 +201,7 @@ jobs:
|
||||
- name: Publish macOS Ryujinx
|
||||
run: |
|
||||
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 1
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|publish_ava/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz"
|
||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r 5 -p publish_ava/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz
|
||||
|
||||
create_gitlab_release:
|
||||
name: Create GitLab Release
|
||||
@@ -210,37 +211,41 @@ jobs:
|
||||
- release
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Install GitLabCli
|
||||
|
||||
- name: Install gli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=$(gli get-next-version -c Canary -R)" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Create tag
|
||||
run: |
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=CreateTag "Canary-${{ steps.version_info.outputs.build_version }}|${{ steps.version_info.outputs.git_short_hash }}"
|
||||
gli create-tag -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Canary-${{ steps.version_info.outputs.build_version }} -r ${{ steps.version_info.outputs.git_short_hash }}
|
||||
|
||||
- name: Create release
|
||||
run: |
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=CreateReleaseFromGenericPackageFiles "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|main|Canary ${{ steps.version_info.outputs.build_version }}|**Full Changelog:** [${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}](https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }})"
|
||||
gli create-release-from-generic-package-files -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r main -t "Canary ${{ steps.version_info.outputs.build_version }}" -b "**Full Changelog:** [${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}](https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }})"
|
||||
|
||||
- name: Send notification webhook
|
||||
run: |
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=SendUpdateMessage "${{ steps.version_info.outputs.build_version }}|FF4500|${{ secrets.CANARY_DISCORD_WEBHOOK }}|https://avatars.githubusercontent.com/u/192939710?s=200&v=4|false"
|
||||
gli send-update-message -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -t ${{ steps.version_info.outputs.build_version }} -c FF4500 -w ${{ secrets.CANARY_DISCORD_WEBHOOK }} -i https://avatars.githubusercontent.com/u/192939710?s=200&v=4
|
||||
|
||||
- name: Notify update server of new builds
|
||||
run: |
|
||||
curl 'https://update.ryujinx.app/api/v1/admin/refresh_cache?rc=canary' -X PATCH -H 'accept: */*' -H 'Authorization: ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }}'
|
||||
gli refresh-version-cache -T ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }} -c Canary
|
||||
|
||||
- name: Advance to the next version
|
||||
run: |
|
||||
gli increment-version -T ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }} -c Canary
|
||||
|
||||
224
.github/workflows/debug_release.yml
vendored
224
.github/workflows/debug_release.yml
vendored
@@ -1,224 +0,0 @@
|
||||
name: Release job (Debug)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs: {}
|
||||
|
||||
concurrency: release
|
||||
|
||||
env:
|
||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
RYUJINX_BASE_VERSION: "1.3"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "release"
|
||||
RELEASE: 1
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release for ${{ matrix.platform.name }}
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
||||
#- { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 }
|
||||
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
||||
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Overwrite csc problem matcher
|
||||
run: echo "::add-matcher::.github/csc.json"
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} + 10))" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Configure for release
|
||||
run: |
|
||||
sed -r --in-place 's/\%\%RYUJINX_BUILD_VERSION\%\%/${{ steps.version_info.outputs.build_version }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
|
||||
- name: Create output dir
|
||||
run: "mkdir release_output"
|
||||
|
||||
- name: Publish
|
||||
run: |
|
||||
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained
|
||||
|
||||
- name: Packing Windows builds
|
||||
if: matrix.platform.os == 'windows-latest'
|
||||
run: |
|
||||
pushd publish
|
||||
rm libarmeilleure-jitsupport.dylib
|
||||
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
||||
popd
|
||||
|
||||
gh release download -R GreemDev/GLI -O gli.exe -p 'GitLabCli-win_x64.exe'
|
||||
|
||||
./gli.exe --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip"
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install GitLabCli
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Packing Linux builds
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
run: |
|
||||
pushd publish
|
||||
rm libarmeilleure-jitsupport.dylib
|
||||
chmod +x Ryujinx.sh Ryujinx
|
||||
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
||||
popd
|
||||
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz"
|
||||
shell: bash
|
||||
|
||||
- name: Build AppImage (Linux)
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
run: |
|
||||
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
|
||||
PLATFORM_NAME="${{ matrix.platform.name }}"
|
||||
|
||||
sudo apt install -y zsync desktop-file-utils appstream
|
||||
|
||||
mkdir -p tools
|
||||
export PATH="$PATH:$(readlink -f tools)"
|
||||
|
||||
# Setup appimagetool
|
||||
wget -q -O tools/appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
chmod +x tools/appimagetool
|
||||
chmod +x distribution/linux/appimage/build-appimage.sh
|
||||
|
||||
# Explicitly set $ARCH for appimagetool ($ARCH_NAME is for the file name)
|
||||
if [ "$PLATFORM_NAME" = "linux-x64" ]; then
|
||||
ARCH_NAME=x64
|
||||
export ARCH=x86_64
|
||||
elif [ "$PLATFORM_NAME" = "linux-arm64" ]; then
|
||||
ARCH_NAME=arm64
|
||||
export ARCH=aarch64
|
||||
else
|
||||
echo "Unexpected PLATFORM_NAME "$PLATFORM_NAME""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export UFLAG="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|*-$ARCH_NAME.AppImage.zsync"
|
||||
BUILDDIR=publish OUTDIR=publish_appimage distribution/linux/appimage/build-appimage.sh
|
||||
|
||||
pushd publish_appimage
|
||||
mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||
mv Ryujinx.AppImage.zsync ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
|
||||
popd
|
||||
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage"
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync"
|
||||
shell: bash
|
||||
|
||||
macos_release:
|
||||
name: Release MacOS universal
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Setup LLVM 17
|
||||
run: |
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 17
|
||||
|
||||
- name: Install GitLabCli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install rcodesign
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R indygreg/apple-platform-rs -O apple-codesign.tar.gz -p 'apple-codesign-*-x86_64-unknown-linux-musl.tar.gz'
|
||||
tar -xzvf apple-codesign.tar.gz --wildcards '*/rcodesign' --strip-components=1
|
||||
rm apple-codesign.tar.gz
|
||||
mv rcodesign $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} + 10))" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Configure for release
|
||||
run: |
|
||||
sed -r --in-place 's/\%\%RYUJINX_BUILD_VERSION\%\%/${{ steps.version_info.outputs.build_version }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
|
||||
- name: Publish macOS Ryujinx
|
||||
run: |
|
||||
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|publish/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz"
|
||||
|
||||
create_gitlab_release:
|
||||
name: Create GitLab Release
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- macos_release
|
||||
- release
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} + 10))" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Install GitLabCli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create release
|
||||
run: |
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=CreateReleaseFromGenericPackageFiles "Ryubing|${{ steps.version_info.outputs.build_version }}|${{ steps.version_info.outputs.git_short_hash }}|test|THIS IS NOT INTENDED FOR END USER USAGE"
|
||||
123
.github/workflows/release.yml
vendored
123
.github/workflows/release.yml
vendored
@@ -1,15 +1,18 @@
|
||||
name: Release job
|
||||
name: Stable CI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs: {}
|
||||
inputs:
|
||||
is_bugfix_release:
|
||||
description: "Bug fix release: If checked, this will increment the third number for only Stable, and leave the Major version alone for both Stable and Canary."
|
||||
required: true
|
||||
type: boolean
|
||||
|
||||
concurrency: release
|
||||
|
||||
env:
|
||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
RYUJINX_BASE_VERSION: "1.3"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "release"
|
||||
RELEASE: 1
|
||||
|
||||
@@ -20,8 +23,8 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
||||
#- { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 }
|
||||
- { name: win-x64, os: ubuntu-latest, zip_os_name: win_x64 }
|
||||
#- { name: win-arm64, os: ubuntu-latest, zip_os_name: win_arm64 }
|
||||
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
||||
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
||||
steps:
|
||||
@@ -33,12 +36,30 @@ jobs:
|
||||
|
||||
- name: Overwrite csc problem matcher
|
||||
run: echo "::add-matcher::.github/csc.json"
|
||||
|
||||
- name: Install 7zip
|
||||
run: |
|
||||
sudo apt install -y 7zip
|
||||
|
||||
- name: Install gli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
||||
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
|
||||
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "build_version=$(gli get-next-version -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
@@ -58,47 +79,34 @@ jobs:
|
||||
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained
|
||||
|
||||
- name: Packing Windows builds
|
||||
if: matrix.platform.os == 'windows-latest'
|
||||
if: contains(matrix.platform.name, 'win')
|
||||
run: |
|
||||
pushd publish
|
||||
rm libarmeilleure-jitsupport.dylib
|
||||
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
||||
popd
|
||||
|
||||
gh release download -R GreemDev/GLI -O gli.exe -p 'GitLabCli-win_x64.exe'
|
||||
|
||||
./gli.exe --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip"
|
||||
|
||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install GitLabCli
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Packing Linux builds
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
if: contains(matrix.platform.name, 'linux')
|
||||
run: |
|
||||
pushd publish
|
||||
rm libarmeilleure-jitsupport.dylib
|
||||
chmod +x Ryujinx.sh Ryujinx
|
||||
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
||||
popd
|
||||
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz"
|
||||
|
||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build AppImage (Linux)
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
if: contains(matrix.platform.name, 'linux')
|
||||
run: |
|
||||
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
|
||||
PLATFORM_NAME="${{ matrix.platform.name }}"
|
||||
@@ -131,7 +139,7 @@ jobs:
|
||||
mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||
popd
|
||||
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage"
|
||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||
shell: bash
|
||||
|
||||
macos_release:
|
||||
@@ -150,10 +158,10 @@ jobs:
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 17
|
||||
|
||||
- name: Install GitLabCli
|
||||
- name: Install gli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
@@ -174,9 +182,14 @@ jobs:
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
||||
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
|
||||
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "build_version=$(gli get-next-version -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Configure for release
|
||||
run: |
|
||||
@@ -189,7 +202,8 @@ jobs:
|
||||
- name: Publish macOS Ryujinx
|
||||
run: |
|
||||
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|publish/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz"
|
||||
|
||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r 5 -p publish/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz
|
||||
|
||||
create_gitlab_release:
|
||||
name: Create GitLab Release
|
||||
@@ -200,32 +214,45 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Install GitLabCli
|
||||
- name: Install gli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
|
||||
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "build_version=$(gli get-next-version -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
echo "commit_message=$(git log -1 --pretty=%B)" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Create release
|
||||
run: |
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=CreateReleaseFromGenericPackageFiles "Ryubing|${{ steps.version_info.outputs.build_version }}|${{ steps.version_info.outputs.git_short_hash }}|${{ steps.version_info.outputs.build_version }}|msd:${{ steps.version_info.outputs.build_version }}"
|
||||
|
||||
gli create-release-from-generic-package-files -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r ${{ steps.version_info.outputs.git_short_hash }} -t "${{ steps.version_info.outputs.build_version }}" -b "msd:${{ steps.version_info.outputs.build_version }}"
|
||||
|
||||
- name: Send notification webhook
|
||||
run: |
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=SendUpdateMessage "${{ steps.version_info.outputs.build_version }}|32cd32|${{ secrets.STABLE_DISCORD_WEBHOOK }}|https://avatars.githubusercontent.com/u/192939710?s=200&v=4|false"
|
||||
|
||||
gli send-update-message -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -t ${{ steps.version_info.outputs.build_version }} -c 32cd32 -w ${{ secrets.STABLE_DISCORD_WEBHOOK }} -i https://avatars.githubusercontent.com/u/192939710?s=200&v=4
|
||||
|
||||
- name: Notify update server of new builds
|
||||
run: |
|
||||
curl 'https://update.ryujinx.app/api/v1/admin/refresh_cache?rc=stable' -X PATCH -H 'accept: */*' -H 'Authorization: ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }}'
|
||||
gli refresh-version-cache -T ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }} -c Stable
|
||||
|
||||
- name: Advance to the next version
|
||||
run: |
|
||||
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
|
||||
gli advance-version -T ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }}
|
||||
else
|
||||
gli increment-version -T ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }} -c Stable
|
||||
fi
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -100,6 +100,7 @@ DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
RyubingMaintainerTools/
|
||||
|
||||
# Publish Web Output
|
||||
*.Publish.xml
|
||||
|
||||
24
assets/Languages.json
Normal file
24
assets/Languages.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"Languages": {
|
||||
"ar_SA": "اَلْعَرَبِيَّةُ",
|
||||
"de_DE": "Deutsch",
|
||||
"el_GR": "Ελληνικά",
|
||||
"en_US": "English (US)",
|
||||
"es_ES": "Español (ES)",
|
||||
"fr_FR": "Français (FR)",
|
||||
"he_IL": "עִברִית",
|
||||
"it_IT": "Italiano",
|
||||
"ja_JP": "日本語",
|
||||
"ko_KR": "한국어",
|
||||
"no_NO": "Norsk",
|
||||
"pl_PL": "Polski",
|
||||
"pt_BR": "Português (BR)",
|
||||
"ru_RU": "Русский",
|
||||
"sv_SE": "Svenska",
|
||||
"th_TH": "ภาษาไทย",
|
||||
"tr_TR": "Türkçe",
|
||||
"uk_UA": "Українська",
|
||||
"zh_CN": "简体中文",
|
||||
"zh_TW": "繁體中文 (台灣)"
|
||||
}
|
||||
}
|
||||
60
assets/Locales.md
Normal file
60
assets/Locales.md
Normal file
@@ -0,0 +1,60 @@
|
||||
|
||||
# Ryubing Locales
|
||||
|
||||
Ryubing Locales uses a custom format, which uses a file for defining the supported languages and a folder of json files for the locales themselves.
|
||||
Each json file holds the locales for a specific part of the emulator, e.g. the Setup Wizard locales are in `SetupWizard.json`, and each locale entry in the file includes all the supported languages in the same place.
|
||||
|
||||
## Languages
|
||||
in the `/assets/` folder you will find the `Languages.json` file, which defines all the languages supported by the emulator.
|
||||
The file includes a table of the langauge codes and their langauge names.
|
||||
|
||||
#Example of the format for Languages.json
|
||||
{
|
||||
"Languages": {
|
||||
"ar_SA": "اَلْعَرَبِيَّةُ",
|
||||
"en_US": "English (US)",
|
||||
...
|
||||
"zh_TW": "繁體中文 (台灣)"
|
||||
}
|
||||
}
|
||||
|
||||
## Locales
|
||||
in the `/assets/Locales/` folder you will find the json files, which define all the locales supported by the emulator.
|
||||
Each json file holds locales for a specific part of the emulator in a large array of locale objects.
|
||||
Each locale is made up an ID used for lookup and a list of the languages and their matching translations.
|
||||
Any empty string or null value will automatically use the English translation instead in the emulator.
|
||||
|
||||
### Format
|
||||
When adding a new locale, you just need to add the ID and the en_US language translation, then the validation system will add default values for the rest of languages automatically, when rebuilding the project.
|
||||
If you want to signal that a translation is supposed to match the English translation, you just have to replace the empty string with `null`.
|
||||
When you want to check what translations are missing for a language just search for `"<lang_code>": ""`, e.g: `"en_US": ""` (but with any other language, as English will never be missing translations).
|
||||
|
||||
### Legacy file (Root.json)
|
||||
Currently all older locales are stored in `Root.json`, but they are slowly being moved into newer, more descriptive json files, to make the locale system more accessible.
|
||||
Do **not** add new locales to `Root.json`.
|
||||
If no json file exists for the specific part of the emulator you're working on, you should instead add a new json file for that part.
|
||||
|
||||
#Example of the format for Root.json
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "MenuBarActionsOpenMiiEditor",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"en_US": "Mii Editor",
|
||||
...
|
||||
"zh_TW": "Mii 編輯器"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "KeyNumber9",
|
||||
"Translations": {
|
||||
"ar_SA": "٩",
|
||||
"en_US": "9",
|
||||
...
|
||||
"zh_TW": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,72 +1,5 @@
|
||||
{
|
||||
"Info": {
|
||||
"Format1": "The Locale file uses a custom Unified format.",
|
||||
"Format2": "The file starts with a list of all the supported languages.",
|
||||
"Format3": "Each locale is made up an ID used for lookup and a list",
|
||||
"Format4": "of the languages and their matching translations.",
|
||||
"Format5": "When adding a new locale you just need to add the ID and",
|
||||
"Format6": "the en_US language translation, then the validation system",
|
||||
"Format7": "will add the rest of the languages automatically on rebuild.",
|
||||
"Format8": "By default the languages will be added with an empty string.",
|
||||
"Format9": "Any empty string or null value will automatically match the",
|
||||
"Format10": "English translation.",
|
||||
"Format11": "If you want to signal that a translation is supposed to",
|
||||
"Format12": "match the English translation, you just have to replace the",
|
||||
"Format13": "empty string with null.",
|
||||
"Format14": "Translators who want to check what translations are missing",
|
||||
"Format15": "for their language just need to search for:",
|
||||
"Format16": "{'lang_code': ''} with double quotes instead of single",
|
||||
"Format17": "e.g: {'en_US': ''} (but with any other language as English",
|
||||
"Format18": "will never be missing translations)."
|
||||
},
|
||||
"Languages": [
|
||||
"ar_SA",
|
||||
"de_DE",
|
||||
"el_GR",
|
||||
"en_US",
|
||||
"es_ES",
|
||||
"fr_FR",
|
||||
"he_IL",
|
||||
"it_IT",
|
||||
"ja_JP",
|
||||
"ko_KR",
|
||||
"no_NO",
|
||||
"pl_PL",
|
||||
"pt_BR",
|
||||
"ru_RU",
|
||||
"sv_SE",
|
||||
"th_TH",
|
||||
"tr_TR",
|
||||
"uk_UA",
|
||||
"zh_CN",
|
||||
"zh_TW"
|
||||
],
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "Language",
|
||||
"Translations": {
|
||||
"ar_SA": "اَلْعَرَبِيَّةُ",
|
||||
"de_DE": "Deutsch",
|
||||
"el_GR": "Ελληνικά",
|
||||
"en_US": "English (US)",
|
||||
"es_ES": "Español (ES)",
|
||||
"fr_FR": "Français (FR)",
|
||||
"he_IL": "עִברִית",
|
||||
"it_IT": "Italiano",
|
||||
"ja_JP": "日本語",
|
||||
"ko_KR": "한국어",
|
||||
"no_NO": "Norsk",
|
||||
"pl_PL": "Polski",
|
||||
"pt_BR": "Português (BR)",
|
||||
"ru_RU": "Русский",
|
||||
"sv_SE": "Svenska",
|
||||
"th_TH": "ภาษาไทย",
|
||||
"tr_TR": "Türkçe",
|
||||
"uk_UA": "Українська",
|
||||
"zh_CN": "简体中文",
|
||||
"zh_TW": "繁體中文 (台灣)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "MenuBarActionsOpenMiiEditor",
|
||||
"Translations": {
|
||||
@@ -19,6 +19,11 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
|
||||
/// The output channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
|
||||
/// </summary>
|
||||
public Array6<byte> Output;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved/unused.
|
||||
/// </summary>
|
||||
private readonly uint _padding;
|
||||
|
||||
/// <summary>
|
||||
/// Biquad filter numerator (b0, b1, b2).
|
||||
|
||||
@@ -11,9 +11,7 @@ namespace Ryujinx.BuildValidationTasks
|
||||
{
|
||||
static readonly JsonSerializerOptions _jsonOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
NewLine = "\n",
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
WriteIndented = true, NewLine = "\n", Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
public LocalesValidationTask() { }
|
||||
@@ -22,77 +20,116 @@ namespace Ryujinx.BuildValidationTasks
|
||||
{
|
||||
Console.WriteLine("Running Locale Validation Task...");
|
||||
|
||||
string path = projectPath + "assets/locales.json";
|
||||
bool encounteredIssue = false;
|
||||
string langPath = projectPath + "assets/Languages.json";
|
||||
string data;
|
||||
|
||||
using (StreamReader sr = new(path))
|
||||
using (StreamReader sr = new(langPath))
|
||||
{
|
||||
data = sr.ReadToEnd();
|
||||
}
|
||||
|
||||
LocalesJson json;
|
||||
|
||||
if (isGitRunner && data.Contains("\r\n"))
|
||||
throw new FormatException("locales.json is using CRLF line endings! It should be using LF line endings, rebuild locally to fix...");
|
||||
throw new FormatException("Languages.json is using CRLF line endings! It should be using LF line endings, rebuild locally to fix...");
|
||||
|
||||
LanguagesJson langJson;
|
||||
|
||||
try
|
||||
{
|
||||
json = JsonSerializer.Deserialize<LocalesJson>(data);
|
||||
|
||||
langJson = JsonSerializer.Deserialize<LanguagesJson>(data);
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
throw new JsonException(e.Message); //shorter and easier stacktrace
|
||||
}
|
||||
|
||||
bool encounteredIssue = false;
|
||||
|
||||
for (int i = 0; i < json.Locales.Count; i++)
|
||||
foreach ((string code, string lang) in langJson.Languages)
|
||||
{
|
||||
LocalesEntry locale = json.Locales[i];
|
||||
|
||||
foreach (string langCode in json.Languages.Where(lang => !locale.Translations.ContainsKey(lang)))
|
||||
if (string.IsNullOrEmpty(lang))
|
||||
{
|
||||
encounteredIssue = true;
|
||||
|
||||
if (!isGitRunner)
|
||||
{
|
||||
locale.Translations.Add(langCode, string.Empty);
|
||||
Console.WriteLine($"Added '{langCode}' to Locale '{locale.ID}'");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Missing '{langCode}' in Locale '{locale.ID}'!");
|
||||
}
|
||||
throw new JsonException($"{code} language name missing!");
|
||||
}
|
||||
|
||||
foreach (string langCode in json.Languages.Where(lang => locale.Translations.ContainsKey(lang) && lang != "en_US" && locale.Translations[lang] == locale.Translations["en_US"]))
|
||||
{
|
||||
encounteredIssue = true;
|
||||
|
||||
if (!isGitRunner)
|
||||
{
|
||||
locale.Translations[langCode] = string.Empty;
|
||||
Console.WriteLine($"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'! Resetting it...");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'!");
|
||||
}
|
||||
}
|
||||
|
||||
locale.Translations = locale.Translations.OrderBy(pair => pair.Key).ToDictionary(pair => pair.Key, pair => pair.Value);
|
||||
json.Locales[i] = locale;
|
||||
}
|
||||
|
||||
if (isGitRunner && encounteredIssue)
|
||||
throw new JsonException("1 or more locales are invalid! Rebuild locally to fix...");
|
||||
string folderPath = projectPath + "assets/Locales/";
|
||||
|
||||
string jsonString = JsonSerializer.Serialize(json, _jsonOptions);
|
||||
string[] paths = Directory.GetFiles(folderPath, "*.json", SearchOption.AllDirectories);
|
||||
|
||||
using (StreamWriter sw = new(path))
|
||||
foreach (string path in paths)
|
||||
{
|
||||
sw.Write(jsonString);
|
||||
using (StreamReader sr = new(path))
|
||||
{
|
||||
data = sr.ReadToEnd();
|
||||
}
|
||||
|
||||
if (isGitRunner && data.Contains("\r\n"))
|
||||
throw new FormatException($"{Path.GetFileName(path)} is using CRLF line endings! It should be using LF line endings, rebuild locally to fix...");
|
||||
|
||||
LocalesJson json;
|
||||
|
||||
try
|
||||
{
|
||||
json = JsonSerializer.Deserialize<LocalesJson>(data);
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
throw new JsonException(e.Message); //shorter and easier stacktrace
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < json.Locales.Count; i++)
|
||||
{
|
||||
LocalesEntry locale = json.Locales[i];
|
||||
|
||||
foreach (string langCode in
|
||||
langJson.Languages.Keys.Where(lang => !locale.Translations.ContainsKey(lang)))
|
||||
{
|
||||
encounteredIssue = true;
|
||||
|
||||
if (!isGitRunner)
|
||||
{
|
||||
locale.Translations.Add(langCode, string.Empty);
|
||||
Console.WriteLine($"Added '{langCode}' to Locale '{locale.ID}'");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Missing '{langCode}' in Locale '{locale.ID}'!");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (string langCode in langJson.Languages.Keys.Where(lang =>
|
||||
locale.Translations.ContainsKey(lang) && lang != "en_US" &&
|
||||
locale.Translations[lang] == locale.Translations["en_US"]))
|
||||
{
|
||||
encounteredIssue = true;
|
||||
|
||||
if (!isGitRunner)
|
||||
{
|
||||
locale.Translations[langCode] = string.Empty;
|
||||
Console.WriteLine(
|
||||
$"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'! Resetting it...");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'!");
|
||||
}
|
||||
}
|
||||
|
||||
locale.Translations = locale.Translations.OrderBy(pair => pair.Key)
|
||||
.ToDictionary(pair => pair.Key, pair => pair.Value);
|
||||
json.Locales[i] = locale;
|
||||
}
|
||||
|
||||
if (isGitRunner && encounteredIssue)
|
||||
throw new JsonException("1 or more locales are invalid! Rebuild locally to fix...");
|
||||
|
||||
string jsonString = JsonSerializer.Serialize(json, _jsonOptions);
|
||||
|
||||
using (StreamWriter sw = new(path))
|
||||
{
|
||||
sw.Write(jsonString);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("Finished Locale Validation Task!");
|
||||
@@ -100,10 +137,13 @@ namespace Ryujinx.BuildValidationTasks
|
||||
return true;
|
||||
}
|
||||
|
||||
struct LanguagesJson
|
||||
{
|
||||
public Dictionary<string, string> Languages { get; set; }
|
||||
}
|
||||
|
||||
struct LocalesJson
|
||||
{
|
||||
public Dictionary<string, string> Info { get; set; }
|
||||
public List<string> Languages { get; set; }
|
||||
public List<LocalesEntry> Locales { get; set; }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<AntiAliasing>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<AntiAliasing>))]
|
||||
public enum AntiAliasing
|
||||
{
|
||||
None,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<AspectRatio>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<AspectRatio>))]
|
||||
public enum AspectRatio
|
||||
{
|
||||
Fixed4x3,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<BackendThreading>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<BackendThreading>))]
|
||||
public enum BackendThreading
|
||||
{
|
||||
Auto,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsBackend>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<GraphicsBackend>))]
|
||||
public enum GraphicsBackend
|
||||
{
|
||||
Vulkan,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsDebugLevel>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<GraphicsDebugLevel>))]
|
||||
public enum GraphicsDebugLevel
|
||||
{
|
||||
None,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid.Controller
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<GamepadInputId>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<GamepadInputId>))]
|
||||
public enum GamepadInputId : byte
|
||||
{
|
||||
Unbound,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<MotionInputBackendType>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<MotionInputBackendType>))]
|
||||
public enum MotionInputBackendType : byte
|
||||
{
|
||||
Invalid,
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid.Controller
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<StickInputId>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<StickInputId>))]
|
||||
public enum StickInputId : byte
|
||||
{
|
||||
Unbound,
|
||||
Left,
|
||||
Right,
|
||||
|
||||
|
||||
Count,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
@@ -6,7 +5,7 @@ namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
// This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical
|
||||
[Flags]
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<ControllerType>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<ControllerType>))]
|
||||
public enum ControllerType
|
||||
{
|
||||
None,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<InputBackendType>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<InputBackendType>))]
|
||||
public enum InputBackendType
|
||||
{
|
||||
Invalid,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<Key>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<Key>))]
|
||||
public enum Key
|
||||
{
|
||||
Unknown,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
// This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<PlayerIndex>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<PlayerIndex>))]
|
||||
public enum PlayerIndex
|
||||
{
|
||||
Player1 = 0,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<MemoryManagerMode>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<MemoryManagerMode>))]
|
||||
public enum MemoryManagerMode : byte
|
||||
{
|
||||
SoftwarePageTable,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<ScalingFilter>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<ScalingFilter>))]
|
||||
public enum ScalingFilter
|
||||
{
|
||||
Bilinear,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<LogClass>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<LogClass>))]
|
||||
public enum LogClass
|
||||
{
|
||||
Application,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<LogLevel>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<LogLevel>))]
|
||||
public enum LogLevel
|
||||
{
|
||||
Debug,
|
||||
|
||||
@@ -7,6 +7,9 @@ namespace Ryujinx.Common.Memory
|
||||
{
|
||||
private static readonly RecyclableMemoryStreamManager _shared = new();
|
||||
|
||||
private static readonly ObjectPool<RecyclableMemoryStream> _streamPool =
|
||||
new(() => new RecyclableMemoryStream(_shared, Guid.NewGuid(), null, 0));
|
||||
|
||||
/// <summary>
|
||||
/// We don't expose the <c>RecyclableMemoryStreamManager</c> directly because version 2.x
|
||||
/// returns them as <c>MemoryStream</c>. This Shared class is here to a) offer only the GetStream() versions we use
|
||||
@@ -19,7 +22,12 @@ namespace Ryujinx.Common.Memory
|
||||
/// </summary>
|
||||
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||
public static RecyclableMemoryStream GetStream()
|
||||
=> new(_shared);
|
||||
{
|
||||
RecyclableMemoryStream stream = _streamPool.Allocate();
|
||||
stream.SetLength(0);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a new <c>MemoryStream</c> object with the contents copied from the provided
|
||||
@@ -55,7 +63,8 @@ namespace Ryujinx.Common.Memory
|
||||
RecyclableMemoryStream stream = null;
|
||||
try
|
||||
{
|
||||
stream = new RecyclableMemoryStream(_shared, id, tag, buffer.Length);
|
||||
stream = _streamPool.Allocate();
|
||||
stream.SetLength(0);
|
||||
stream.Write(buffer);
|
||||
stream.Position = 0;
|
||||
return stream;
|
||||
@@ -83,7 +92,8 @@ namespace Ryujinx.Common.Memory
|
||||
RecyclableMemoryStream stream = null;
|
||||
try
|
||||
{
|
||||
stream = new RecyclableMemoryStream(_shared, id, tag, count);
|
||||
stream = _streamPool.Allocate();
|
||||
stream.SetLength(0);
|
||||
stream.Write(buffer, offset, count);
|
||||
stream.Position = 0;
|
||||
return stream;
|
||||
@@ -94,6 +104,11 @@ namespace Ryujinx.Common.Memory
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReleaseStream(RecyclableMemoryStream stream)
|
||||
{
|
||||
_streamPool.Release(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ namespace Ryujinx.Common
|
||||
public static string[] GetAllAvailableResources(string path, string ext = "")
|
||||
{
|
||||
return ResolveManifestPath(path).Item1.GetManifestResourceNames()
|
||||
.Where(r => r.EndsWith(ext))
|
||||
.Where(r => r.StartsWith(path.Replace('/', '.')) && r.EndsWith(ext))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,5 +20,21 @@ namespace Ryujinx.Common.Utilities
|
||||
Debug.Assert(res != -1);
|
||||
}
|
||||
}
|
||||
|
||||
// "dumpable" attribute of the calling process
|
||||
private const int PR_SET_DUMPABLE = 4;
|
||||
|
||||
[DllImport("libc", SetLastError = true)]
|
||||
private static extern int prctl(int option, int arg2);
|
||||
|
||||
public static void SetCoreDumpable(bool dumpable)
|
||||
{
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
int dumpableInt = dumpable ? 1 : 0;
|
||||
int result = prctl(PR_SET_DUMPABLE, dumpableInt);
|
||||
Debug.Assert(result == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
#nullable enable
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies that value of <see cref="TEnum"/> will be serialized as string in JSONs
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Trimming friendly alternative to <see cref="JsonStringEnumConverter"/>.
|
||||
/// Get rid of this converter if dotnet supports similar functionality out of the box.
|
||||
/// </remarks>
|
||||
/// <typeparam name="TEnum">Type of enum to serialize</typeparam>
|
||||
public sealed class TypedStringEnumConverter<TEnum> : JsonConverter<TEnum> where TEnum : struct, Enum
|
||||
{
|
||||
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
string? enumValue = reader.GetString();
|
||||
|
||||
if (Enum.TryParse(enumValue, out TEnum value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
Logger.Warning?.Print(LogClass.Configuration, $"Failed to parse enum value \"{enumValue}\" for {typeof(TEnum)}, using default \"{default(TEnum)}\"");
|
||||
return default;
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
void SetRasterizerDiscard(bool discard);
|
||||
|
||||
void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask);
|
||||
void SetRenderTargets(ITexture[] colors, ITexture depthStencil);
|
||||
void SetRenderTargets(Span<ITexture> colors, ITexture depthStencil);
|
||||
|
||||
void SetScissors(ReadOnlySpan<Rectangle<int>> regions);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
@@ -8,11 +9,13 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
{
|
||||
public static readonly ArrayPool<ITexture> ArrayPool = ArrayPool<ITexture>.Create(512, 50);
|
||||
public readonly CommandType CommandType => CommandType.SetRenderTargets;
|
||||
private int _colorsCount;
|
||||
private TableRef<ITexture[]> _colors;
|
||||
private TableRef<ITexture> _depthStencil;
|
||||
|
||||
public void Set(TableRef<ITexture[]> colors, TableRef<ITexture> depthStencil)
|
||||
public void Set(int colorsCount, TableRef<ITexture[]> colors, TableRef<ITexture> depthStencil)
|
||||
{
|
||||
_colorsCount = colorsCount;
|
||||
_colors = colors;
|
||||
_depthStencil = depthStencil;
|
||||
}
|
||||
@@ -20,16 +23,15 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
public static void Run(ref SetRenderTargetsCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
ITexture[] colors = command._colors.Get(threaded);
|
||||
ITexture[] colorsCopy = ArrayPool.Rent(colors.Length);
|
||||
Span<ITexture> colorsSpan = colors.AsSpan(0, command._colorsCount);
|
||||
|
||||
for (int i = 0; i < colors.Length; i++)
|
||||
for (int i = 0; i < colorsSpan.Length; i++)
|
||||
{
|
||||
colorsCopy[i] = ((ThreadedTexture)colors[i])?.Base;
|
||||
colorsSpan[i] = ((ThreadedTexture)colorsSpan[i])?.Base;
|
||||
}
|
||||
|
||||
renderer.Pipeline.SetRenderTargets(colorsCopy, command._depthStencil.GetAs<ThreadedTexture>(threaded)?.Base);
|
||||
renderer.Pipeline.SetRenderTargets(colorsSpan, command._depthStencil.GetAs<ThreadedTexture>(threaded)?.Base);
|
||||
|
||||
ArrayPool.Return(colorsCopy);
|
||||
ArrayPool.Return(colors);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,12 +267,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public unsafe void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
|
||||
public unsafe void SetRenderTargets(Span<ITexture> colors, ITexture depthStencil)
|
||||
{
|
||||
ITexture[] colorsCopy = SetRenderTargetsCommand.ArrayPool.Rent(colors.Length);
|
||||
colors.CopyTo(colorsCopy, 0);
|
||||
colors.CopyTo(colorsCopy.AsSpan());
|
||||
|
||||
_renderer.New<SetRenderTargetsCommand>()->Set(Ref(colorsCopy), Ref(depthStencil));
|
||||
_renderer.New<SetRenderTargetsCommand>()->Set(colors.Length, Ref(colorsCopy), Ref(depthStencil));
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
/// </summary>
|
||||
public class ThreadedRenderer : IRenderer
|
||||
{
|
||||
private const int SpanPoolBytes = 4 * 1024 * 1024;
|
||||
private const int SpanPoolBytes = 8 * 1024 * 1024;
|
||||
private const int MaxRefsPerCommand = 2;
|
||||
private const int QueueCount = 10000;
|
||||
|
||||
|
||||
@@ -404,9 +404,12 @@ namespace Ryujinx.Graphics.Gpu
|
||||
|
||||
if (force || _pendingSync || (syncPoint && SyncpointActions.Count > 0))
|
||||
{
|
||||
foreach (ISyncActionHandler action in SyncActions)
|
||||
for (int i = 0; i < SyncActions.Count; i++)
|
||||
{
|
||||
action.SyncPreAction(syncPoint);
|
||||
if (SyncActions[i].SyncPreAction(syncPoint))
|
||||
{
|
||||
SyncActions.RemoveAt(i--);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (ISyncActionHandler action in SyncpointActions)
|
||||
|
||||
@@ -411,7 +411,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// flushes often enough, which is determined by the flush balance.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public void SyncPreAction(bool syncpoint)
|
||||
public bool SyncPreAction(bool syncpoint)
|
||||
{
|
||||
if (syncpoint || NextSyncCopies())
|
||||
{
|
||||
@@ -421,6 +421,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
_registeredBufferSync = _modifiedSync;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <summary>
|
||||
/// Buffer, used to store vertex and index data, uniform and storage buffers, and others.
|
||||
/// </summary>
|
||||
class Buffer : INonOverlappingRange, ISyncActionHandler, IDisposable
|
||||
class Buffer : INonOverlappingRange<Buffer>, ISyncActionHandler, IDisposable
|
||||
{
|
||||
private const ulong GranularBufferThreshold = 4096;
|
||||
|
||||
@@ -41,6 +41,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// End address of the buffer in guest memory.
|
||||
/// </summary>
|
||||
public ulong EndAddress => Address + Size;
|
||||
|
||||
public Buffer Next { get; set; }
|
||||
public Buffer Previous { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Increments when the buffer is (partially) unmapped or disposed.
|
||||
@@ -87,6 +90,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
private readonly bool _useGranular;
|
||||
private bool _syncActionRegistered;
|
||||
private bool _bufferInherited;
|
||||
|
||||
private int _referenceCount = 1;
|
||||
|
||||
@@ -113,7 +117,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
ulong size,
|
||||
BufferStage stage,
|
||||
bool sparseCompatible,
|
||||
RangeItem<Buffer>[] baseBuffers)
|
||||
Buffer[] baseBuffers)
|
||||
{
|
||||
_context = context;
|
||||
_physicalMemory = physicalMemory;
|
||||
@@ -134,15 +138,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
if (baseBuffers.Length != 0)
|
||||
{
|
||||
baseHandles = new List<IRegionHandle>();
|
||||
foreach (RangeItem<Buffer> item in baseBuffers)
|
||||
foreach (Buffer item in baseBuffers)
|
||||
{
|
||||
if (item.Value._useGranular)
|
||||
if (item._useGranular)
|
||||
{
|
||||
baseHandles.AddRange(item.Value._memoryTrackingGranular.Handles);
|
||||
baseHandles.AddRange(item._memoryTrackingGranular.Handles);
|
||||
}
|
||||
else
|
||||
{
|
||||
baseHandles.Add(item.Value._memoryTracking);
|
||||
baseHandles.Add(item._memoryTracking);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -247,14 +251,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// Checks if a given range overlaps with the buffer.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="size">Size in bytes of the range</param>
|
||||
/// <param name="endAddress">End address of the range</param>
|
||||
/// <returns>True if the range overlaps, false otherwise</returns>
|
||||
public bool OverlapsWith(ulong address, ulong size)
|
||||
public bool OverlapsWith(ulong address, ulong endAddress)
|
||||
{
|
||||
return Address < address + size && address < EndAddress;
|
||||
return Address < endAddress && address < EndAddress;
|
||||
}
|
||||
|
||||
public INonOverlappingRange Split(ulong splitAddress)
|
||||
public INonOverlappingRange<Buffer> Split(ulong splitAddress)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -389,11 +393,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// This will copy any buffer ranges designated for pre-flushing.
|
||||
/// </summary>
|
||||
/// <param name="syncpoint">True if the action is a guest syncpoint</param>
|
||||
public void SyncPreAction(bool syncpoint)
|
||||
public bool SyncPreAction(bool syncpoint)
|
||||
{
|
||||
if (_bufferInherited)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_referenceCount == 0)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (BackingState.ShouldChangeBacking())
|
||||
@@ -410,6 +419,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
_modifiedRanges?.GetRangesAtSync(Address, Size, _context.SyncNumber, _syncPreRangeAction);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SyncPreRangeAction(ulong address, ulong size)
|
||||
@@ -426,10 +437,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
_syncActionRegistered = false;
|
||||
|
||||
if (_bufferInherited)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_useGranular)
|
||||
{
|
||||
|
||||
|
||||
_modifiedRanges?.GetRanges(Address, Size, _syncRangeAction);
|
||||
}
|
||||
else
|
||||
@@ -453,6 +467,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="from">The buffer to inherit from</param>
|
||||
public void InheritModifiedRanges(Buffer from)
|
||||
{
|
||||
from._bufferInherited = true;
|
||||
|
||||
if (from._modifiedRanges is { HasRanges: true })
|
||||
{
|
||||
if (from._syncActionRegistered && !_syncActionRegistered)
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="parent">Parent buffer</param>
|
||||
/// <param name="stage">Initial buffer stage</param>
|
||||
/// <param name="baseBuffers">Buffers to inherit state from</param>
|
||||
public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, RangeItem<Buffer>[] baseBuffers)
|
||||
public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, Buffer[] baseBuffers)
|
||||
{
|
||||
_size = (int)parent.Size;
|
||||
_systemMemoryType = context.Capabilities.MemoryType;
|
||||
@@ -102,9 +102,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
if (baseBuffers.Length != 0)
|
||||
{
|
||||
foreach (RangeItem<Buffer> item in baseBuffers)
|
||||
foreach (Buffer item in baseBuffers)
|
||||
{
|
||||
CombineState(item.Value.BackingState);
|
||||
CombineState(item.BackingState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,16 +79,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
for (int index = 0; index < range.Count; index++)
|
||||
{
|
||||
MemoryRange subRange = range.GetSubRange(index);
|
||||
|
||||
_buffers.Lock.EnterReadLock();
|
||||
Span<RangeItem<Buffer>> overlaps = _buffers.FindOverlapsAsSpan(subRange.Address, subRange.Size);
|
||||
|
||||
ReadOnlySpan<Buffer> overlaps = _buffers.FindOverlapsAsSpan(subRange.Address, subRange.Size);
|
||||
|
||||
for (int i = 0; i < overlaps.Length; i++)
|
||||
{
|
||||
overlaps[i].Value.Unmapped(subRange.Address, subRange.Size);
|
||||
overlaps[i].Unmapped(subRange.Address, subRange.Size);
|
||||
}
|
||||
|
||||
_buffers.Lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,7 +325,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
ulong alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask;
|
||||
ulong alignedSize = alignedEndAddress - alignedAddress;
|
||||
|
||||
Buffer buffer = _buffers.FindOverlap(alignedAddress, alignedSize).Value;
|
||||
Buffer buffer = _buffers.FindOverlap(alignedAddress, alignedSize);
|
||||
BufferRange bufferRange = buffer.GetRange(alignedAddress, alignedSize, false);
|
||||
|
||||
alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize);
|
||||
@@ -395,7 +392,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
if (subRange.Address != MemoryManager.PteUnmapped)
|
||||
{
|
||||
Buffer buffer = _buffers.FindOverlap(subRange.Address, subRange.Size).Value;
|
||||
Buffer buffer = _buffers.FindOverlap(subRange.Address, subRange.Size);
|
||||
|
||||
virtualBuffer.AddPhysicalDependency(buffer, subRange.Address, dstOffset, subRange.Size);
|
||||
physicalBuffers.Add(buffer);
|
||||
@@ -487,10 +484,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="stage">The type of usage that created the buffer</param>
|
||||
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage)
|
||||
{
|
||||
Buffer newBuffer = null;
|
||||
|
||||
_buffers.Lock.EnterWriteLock();
|
||||
Span<RangeItem<Buffer>> overlaps = _buffers.FindOverlapsAsSpan(address, size);
|
||||
ReadOnlySpan<Buffer> overlaps = _buffers.FindOverlapsAsSpan(address, size);
|
||||
|
||||
if (overlaps.Length != 0)
|
||||
{
|
||||
@@ -521,7 +515,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
// Try to grow the buffer by 1.5x of its current size.
|
||||
// This improves performance in the cases where the buffer is resized often by small amounts.
|
||||
ulong existingSize = overlaps[0].Value.Size;
|
||||
ulong existingSize = overlaps[0].Size;
|
||||
ulong growthSize = (existingSize + Math.Min(existingSize >> 1, MaxDynamicGrowthSize)) & ~BufferAlignmentMask;
|
||||
|
||||
size = Math.Max(size, growthSize);
|
||||
@@ -535,39 +529,22 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
for (int i = 0; i < overlaps.Length; i++)
|
||||
{
|
||||
anySparseCompatible |= overlaps[i].Value.SparseCompatible;
|
||||
anySparseCompatible |= overlaps[i].SparseCompatible;
|
||||
}
|
||||
|
||||
RangeItem<Buffer>[] overlapsArray = overlaps.ToArray();
|
||||
Buffer[] overlapsArray = overlaps.ToArray();
|
||||
|
||||
_buffers.RemoveRange(overlaps[0], overlaps[^1]);
|
||||
|
||||
_buffers.Lock.ExitWriteLock();
|
||||
|
||||
ulong newSize = endAddress - address;
|
||||
|
||||
newBuffer = CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlapsArray);
|
||||
}
|
||||
else
|
||||
{
|
||||
_buffers.Lock.ExitWriteLock();
|
||||
_buffers.Add(CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlapsArray));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_buffers.Lock.ExitWriteLock();
|
||||
|
||||
// No overlap, just create a new buffer.
|
||||
newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, []);
|
||||
}
|
||||
|
||||
if (newBuffer is not null)
|
||||
{
|
||||
_buffers.Lock.EnterWriteLock();
|
||||
|
||||
_buffers.Add(newBuffer);
|
||||
|
||||
_buffers.Lock.ExitWriteLock();
|
||||
_buffers.Add(new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, []));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -583,10 +560,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, ulong alignment)
|
||||
{
|
||||
bool sparseAligned = alignment >= SparseBufferAlignmentSize;
|
||||
Buffer newBuffer = null;
|
||||
|
||||
_buffers.Lock.EnterWriteLock();
|
||||
Span<RangeItem<Buffer>> overlaps = _buffers.FindOverlapsAsSpan(address, size);
|
||||
ReadOnlySpan<Buffer> overlaps = _buffers.FindOverlapsAsSpan(address, size);
|
||||
|
||||
if (overlaps.Length != 0)
|
||||
{
|
||||
@@ -598,7 +573,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
if (overlaps[0].Address > address ||
|
||||
overlaps[0].EndAddress < endAddress ||
|
||||
(overlaps[0].Address & (alignment - 1)) != 0 ||
|
||||
(!overlaps[0].Value.SparseCompatible && sparseAligned))
|
||||
(!overlaps[0].SparseCompatible && sparseAligned))
|
||||
{
|
||||
// We need to make sure the new buffer is properly aligned.
|
||||
// However, after the range is aligned, it is possible that it
|
||||
@@ -622,35 +597,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
ulong newSize = endAddress - address;
|
||||
|
||||
RangeItem<Buffer>[] overlapsArray = overlaps.ToArray();
|
||||
Buffer[] overlapsArray = overlaps.ToArray();
|
||||
|
||||
_buffers.RemoveRange(overlaps[0], overlaps[^1]);
|
||||
|
||||
_buffers.Lock.ExitWriteLock();
|
||||
|
||||
newBuffer = CreateBufferAligned(address, newSize, stage, sparseAligned, overlapsArray);
|
||||
}
|
||||
else
|
||||
{
|
||||
_buffers.Lock.ExitWriteLock();
|
||||
_buffers.Add(CreateBufferAligned(address, newSize, stage, sparseAligned, overlapsArray));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_buffers.Lock.ExitWriteLock();
|
||||
|
||||
// No overlap, just create a new buffer.
|
||||
newBuffer = new(_context, _physicalMemory, address, size, stage, sparseAligned, []);
|
||||
}
|
||||
|
||||
if (newBuffer is not null)
|
||||
{
|
||||
_buffers.Lock.EnterWriteLock();
|
||||
|
||||
_buffers.Add(newBuffer);
|
||||
|
||||
_buffers.Lock.ExitWriteLock();
|
||||
}
|
||||
_buffers.Add(new(_context, _physicalMemory, address, size, stage, sparseAligned, []));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -663,13 +621,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="stage">The type of usage that created the buffer</param>
|
||||
/// <param name="sparseCompatible">Indicates if the buffer can be used in a sparse buffer mapping</param>
|
||||
/// <param name="overlaps">Buffers overlapping the range</param>
|
||||
private Buffer CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, RangeItem<Buffer>[] overlaps)
|
||||
private Buffer CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, Buffer[] overlaps)
|
||||
{
|
||||
Buffer newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible, overlaps);
|
||||
|
||||
for (int index = 0; index < overlaps.Length; index++)
|
||||
{
|
||||
Buffer buffer = overlaps[index].Value;
|
||||
Buffer buffer = overlaps[index];
|
||||
|
||||
int dstOffset = (int)(buffer.Address - newBuffer.Address);
|
||||
|
||||
@@ -897,7 +855,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
MemoryRange subRange = range.GetSubRange(i);
|
||||
|
||||
Buffer subBuffer = _buffers.FindOverlap(subRange.Address, subRange.Size).Value;
|
||||
Buffer subBuffer = _buffers.FindOverlap(subRange.Address, subRange.Size);
|
||||
|
||||
subBuffer.SynchronizeMemory(subRange.Address, subRange.Size);
|
||||
|
||||
@@ -945,7 +903,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
if (size != 0)
|
||||
{
|
||||
buffer = _buffers.FindOverlap(address, size).Value;
|
||||
buffer = _buffers.FindOverlap(address, size);
|
||||
|
||||
buffer.CopyFromDependantVirtualBuffers();
|
||||
buffer.SynchronizeMemory(address, size);
|
||||
@@ -957,7 +915,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer = _buffers.FindOverlapFast(address, 1).Value;
|
||||
buffer = _buffers.FindOverlapFast(address, 1);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
@@ -995,7 +953,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
if (size != 0)
|
||||
{
|
||||
Buffer buffer = _buffers.FindOverlap(address, size).Value;
|
||||
Buffer buffer = _buffers.FindOverlap(address, size);
|
||||
|
||||
if (copyBackVirtual)
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <summary>
|
||||
/// A range within a buffer that has been modified by the GPU.
|
||||
/// </summary>
|
||||
class BufferModifiedRange : INonOverlappingRange
|
||||
class BufferModifiedRange : INonOverlappingRange<BufferModifiedRange>
|
||||
{
|
||||
/// <summary>
|
||||
/// Start address of the range in guest memory.
|
||||
@@ -24,6 +24,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// End address of the range in guest memory.
|
||||
/// </summary>
|
||||
public ulong EndAddress => Address + Size;
|
||||
|
||||
public BufferModifiedRange Next { get; set; }
|
||||
public BufferModifiedRange Previous { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The GPU sync number at the time of the last modification.
|
||||
@@ -54,14 +57,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// Checks if a given range overlaps with the modified range.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="size">Size in bytes of the range</param>
|
||||
/// <param name="endAddress">End address of the range</param>
|
||||
/// <returns>True if the range overlaps, false otherwise</returns>
|
||||
public bool OverlapsWith(ulong address, ulong size)
|
||||
public bool OverlapsWith(ulong address, ulong endAddress)
|
||||
{
|
||||
return Address < address + size && address < EndAddress;
|
||||
return Address < endAddress && address < EndAddress;
|
||||
}
|
||||
|
||||
public INonOverlappingRange Split(ulong splitAddress)
|
||||
public INonOverlappingRange<BufferModifiedRange> Split(ulong splitAddress)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -119,11 +122,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
// Slices a given region using the modified regions in the list. Calls the action for the new slices.
|
||||
Lock.EnterReadLock();
|
||||
|
||||
Span<RangeItem<BufferModifiedRange>> overlaps = FindOverlapsAsSpan(address, size);
|
||||
ReadOnlySpan<BufferModifiedRange> overlaps = FindOverlapsAsSpan(address, size);
|
||||
|
||||
for (int i = 0; i < overlaps.Length; i++)
|
||||
{
|
||||
BufferModifiedRange overlap = overlaps[i].Value;
|
||||
BufferModifiedRange overlap = overlaps[i];
|
||||
|
||||
if (overlap.Address > address)
|
||||
{
|
||||
@@ -157,7 +160,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
ulong syncNumber = _context.SyncNumber;
|
||||
// We may overlap with some existing modified regions. They must be cut into by the new entry.
|
||||
Lock.EnterWriteLock();
|
||||
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlapsAsNodes(address, size);
|
||||
(BufferModifiedRange first, BufferModifiedRange last) = FindOverlapsAsNodes(address, size);
|
||||
|
||||
if (first is null)
|
||||
{
|
||||
@@ -170,34 +173,39 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
if (first.Address == address && first.EndAddress == endAddress)
|
||||
{
|
||||
first.Value.SyncNumber = syncNumber;
|
||||
first.Value.Parent = this;
|
||||
first.SyncNumber = syncNumber;
|
||||
first.Parent = this;
|
||||
Lock.ExitWriteLock();
|
||||
return;
|
||||
}
|
||||
|
||||
if (first.Address < address)
|
||||
{
|
||||
first.Value.Size = address - first.Address;
|
||||
Update(first);
|
||||
|
||||
if (first.EndAddress > endAddress)
|
||||
{
|
||||
Add(new BufferModifiedRange(endAddress, first.EndAddress - endAddress,
|
||||
first.Value.SyncNumber, first.Value.Parent));
|
||||
first.SyncNumber, first.Parent));
|
||||
}
|
||||
|
||||
first.Size = address - first.Address;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (first.EndAddress > endAddress)
|
||||
{
|
||||
first.Value.Size = first.EndAddress - endAddress;
|
||||
first.Value.Address = endAddress;
|
||||
Update(first);
|
||||
first.Size = first.EndAddress - endAddress;
|
||||
first.Address = endAddress;
|
||||
}
|
||||
else
|
||||
{
|
||||
Remove(first.Value);
|
||||
first.Address = address;
|
||||
first.Size = size;
|
||||
first.SyncNumber = syncNumber;
|
||||
first.Parent = this;
|
||||
|
||||
Lock.ExitWriteLock();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,38 +215,39 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
return;
|
||||
}
|
||||
|
||||
BufferModifiedRange buffPre = null;
|
||||
BufferModifiedRange buffPost = null;
|
||||
bool extendsPost = false;
|
||||
bool extendsPre = false;
|
||||
|
||||
if (first.Address < address)
|
||||
{
|
||||
buffPre = new BufferModifiedRange(first.Address, address - first.Address,
|
||||
first.Value.SyncNumber, first.Value.Parent);
|
||||
extendsPre = true;
|
||||
first.Size = address - first.Address;
|
||||
first = first.Next;
|
||||
}
|
||||
|
||||
if (last.EndAddress > endAddress)
|
||||
{
|
||||
buffPost = new BufferModifiedRange(endAddress, last.EndAddress - endAddress,
|
||||
last.Value.SyncNumber, last.Value.Parent);
|
||||
extendsPost = true;
|
||||
last.Size = last.EndAddress - endAddress;
|
||||
last.Address = endAddress;
|
||||
last = last.Previous;
|
||||
}
|
||||
|
||||
RemoveRange(first, last);
|
||||
|
||||
if (extendsPre)
|
||||
if (first.Address < last.Address)
|
||||
{
|
||||
Add(buffPre);
|
||||
RemoveRange(first.Next, last);
|
||||
first.Address = address;
|
||||
first.Size = size;
|
||||
first.SyncNumber = syncNumber;
|
||||
first.Parent = this;
|
||||
}
|
||||
|
||||
if (extendsPost)
|
||||
else if (first.Address == last.Address)
|
||||
{
|
||||
Add(buffPost);
|
||||
first.Address = address;
|
||||
first.Size = size;
|
||||
first.SyncNumber = syncNumber;
|
||||
first.Parent = this;
|
||||
}
|
||||
|
||||
Add(new BufferModifiedRange(address, size, syncNumber, this));
|
||||
else
|
||||
{
|
||||
Add(new BufferModifiedRange(address, size, syncNumber, this));
|
||||
}
|
||||
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
|
||||
@@ -252,11 +261,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
public void GetRangesAtSync(ulong address, ulong size, ulong syncNumber, Action<ulong, ulong> rangeAction)
|
||||
{
|
||||
Lock.EnterReadLock();
|
||||
Span<RangeItem<BufferModifiedRange>> overlaps = FindOverlapsAsSpan(address, size);
|
||||
ReadOnlySpan<BufferModifiedRange> overlaps = FindOverlapsAsSpan(address, size);
|
||||
|
||||
for (int i = 0; i < overlaps.Length; i++)
|
||||
{
|
||||
BufferModifiedRange overlap = overlaps[i].Value;
|
||||
BufferModifiedRange overlap = overlaps[i];
|
||||
|
||||
if (overlap.SyncNumber == syncNumber)
|
||||
{
|
||||
@@ -277,18 +286,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
// We use the non-span method here because keeping the lock will cause a deadlock.
|
||||
Lock.EnterReadLock();
|
||||
RangeItem<BufferModifiedRange>[] overlaps = FindOverlapsAsArray(address, size, out int length);
|
||||
BufferModifiedRange[] overlaps = FindOverlapsAsArray(address, size, out int length);
|
||||
Lock.ExitReadLock();
|
||||
|
||||
if (length != 0)
|
||||
{
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
BufferModifiedRange overlap = overlaps[i].Value;
|
||||
BufferModifiedRange overlap = overlaps[i];
|
||||
rangeAction(overlap.Address, overlap.Size);
|
||||
}
|
||||
|
||||
ArrayPool<RangeItem<BufferModifiedRange>>.Shared.Return(overlaps);
|
||||
ArrayPool<BufferModifiedRange>.Shared.Return(overlaps);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,7 +310,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
public bool HasRange(ulong address, ulong size)
|
||||
{
|
||||
Lock.EnterReadLock();
|
||||
RangeItem<BufferModifiedRange> first = FindOverlapFast(address, size);
|
||||
BufferModifiedRange first = FindOverlapFast(address, size);
|
||||
bool result = first is not null;
|
||||
Lock.ExitReadLock();
|
||||
return result;
|
||||
@@ -336,7 +345,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="address">The start address of the flush range</param>
|
||||
/// <param name="endAddress">The end address of the flush range</param>
|
||||
private void RemoveRangesAndFlush(
|
||||
RangeItem<BufferModifiedRange>[] overlaps,
|
||||
BufferModifiedRange[] overlaps,
|
||||
int rangeCount,
|
||||
long highestDiff,
|
||||
ulong currentSync,
|
||||
@@ -349,7 +358,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
for (int i = 0; i < rangeCount; i++)
|
||||
{
|
||||
BufferModifiedRange overlap = overlaps[i].Value;
|
||||
BufferModifiedRange overlap = overlaps[i];
|
||||
|
||||
long diff = (long)(overlap.SyncNumber - currentSync);
|
||||
|
||||
@@ -358,7 +367,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
ulong clampAddress = Math.Max(address, overlap.Address);
|
||||
ulong clampEnd = Math.Min(endAddress, overlap.EndAddress);
|
||||
|
||||
ClearPart(overlap, clampAddress, clampEnd);
|
||||
if (i == 0 || i == rangeCount - 1)
|
||||
{
|
||||
ClearPart(overlap, clampAddress, clampEnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
Remove(overlap);
|
||||
}
|
||||
|
||||
RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, _flushAction);
|
||||
}
|
||||
@@ -398,7 +414,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
Lock.EnterWriteLock();
|
||||
// We use the non-span method here because the array is partially modified by the code, which would invalidate a span.
|
||||
RangeItem<BufferModifiedRange>[] overlaps = FindOverlapsAsArray(address, size, out int rangeCount);
|
||||
BufferModifiedRange[] overlaps = FindOverlapsAsArray(address, size, out int rangeCount);
|
||||
|
||||
if (rangeCount == 0)
|
||||
{
|
||||
@@ -414,7 +430,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
for (int i = 0; i < rangeCount; i++)
|
||||
{
|
||||
BufferModifiedRange overlap = overlaps![i].Value;
|
||||
BufferModifiedRange overlap = overlaps![i];
|
||||
|
||||
long diff = (long)(overlap.SyncNumber - currentSync);
|
||||
|
||||
@@ -436,7 +452,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress);
|
||||
|
||||
ArrayPool<RangeItem<BufferModifiedRange>>.Shared.Return(overlaps!);
|
||||
ArrayPool<BufferModifiedRange>.Shared.Return(overlaps!);
|
||||
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
@@ -452,7 +468,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
public void InheritRanges(BufferModifiedRangeList ranges, Action<ulong, ulong> registerRangeAction)
|
||||
{
|
||||
ranges.Lock.EnterReadLock();
|
||||
BufferModifiedRange[] inheritRanges = ranges.ToArray();
|
||||
int rangesCount = ranges.Count;
|
||||
BufferModifiedRange[] inheritRanges = ArrayPool<BufferModifiedRange>.Shared.Rent(ranges.Count);
|
||||
ranges.Items.AsSpan(0, ranges.Count).CopyTo(inheritRanges);
|
||||
ranges.Lock.ExitReadLock();
|
||||
|
||||
// Copy over the migration from the previous range list
|
||||
@@ -478,22 +496,26 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
ranges._migrationTarget = this;
|
||||
|
||||
Lock.EnterWriteLock();
|
||||
|
||||
foreach (BufferModifiedRange range in inheritRanges)
|
||||
|
||||
for (int i = 0; i < rangesCount; i++)
|
||||
{
|
||||
BufferModifiedRange range = inheritRanges[i];
|
||||
Add(range);
|
||||
}
|
||||
|
||||
Lock.ExitWriteLock();
|
||||
|
||||
ulong currentSync = _context.SyncNumber;
|
||||
foreach (BufferModifiedRange range in inheritRanges)
|
||||
for (int i = 0; i < rangesCount; i++)
|
||||
{
|
||||
BufferModifiedRange range = inheritRanges[i];
|
||||
if (range.SyncNumber != currentSync)
|
||||
{
|
||||
registerRangeAction(range.Address, range.Size);
|
||||
}
|
||||
}
|
||||
|
||||
ArrayPool<BufferModifiedRange>.Shared.Return(inheritRanges);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -534,18 +556,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
private void ClearPart(BufferModifiedRange overlap, ulong address, ulong endAddress)
|
||||
{
|
||||
Remove(overlap);
|
||||
|
||||
// If the overlap extends outside of the clear range, make sure those parts still exist.
|
||||
|
||||
if (overlap.Address < address)
|
||||
{
|
||||
Add(new BufferModifiedRange(overlap.Address, address - overlap.Address, overlap.SyncNumber, overlap.Parent));
|
||||
if (overlap.EndAddress > endAddress)
|
||||
{
|
||||
Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber, overlap.Parent));
|
||||
}
|
||||
|
||||
overlap.Size = address - overlap.Address;
|
||||
}
|
||||
|
||||
if (overlap.EndAddress > endAddress)
|
||||
else if (overlap.EndAddress > endAddress)
|
||||
{
|
||||
Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber, overlap.Parent));
|
||||
overlap.Size = overlap.EndAddress - endAddress;
|
||||
overlap.Address = endAddress;
|
||||
}
|
||||
else
|
||||
{
|
||||
Remove(overlap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -558,7 +587,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
ulong endAddress = address + size;
|
||||
Lock.EnterWriteLock();
|
||||
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlapsAsNodes(address, size);
|
||||
(BufferModifiedRange first, BufferModifiedRange last) = FindOverlapsAsNodes(address, size);
|
||||
|
||||
if (first is null)
|
||||
{
|
||||
@@ -570,26 +599,24 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
if (first.Address < address)
|
||||
{
|
||||
first.Value.Size = address - first.Address;
|
||||
Update(first);
|
||||
first.Size = address - first.Address;
|
||||
|
||||
if (first.EndAddress > endAddress)
|
||||
{
|
||||
Add(new BufferModifiedRange(endAddress, first.EndAddress - endAddress,
|
||||
first.Value.SyncNumber, first.Value.Parent));
|
||||
first.SyncNumber, first.Parent));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (first.EndAddress > endAddress)
|
||||
{
|
||||
first.Value.Size = first.EndAddress - endAddress;
|
||||
first.Value.Address = endAddress;
|
||||
Update(first);
|
||||
first.Size = first.EndAddress - endAddress;
|
||||
first.Address = endAddress;
|
||||
}
|
||||
else
|
||||
{
|
||||
Remove(first.Value);
|
||||
Remove(first);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -605,14 +632,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
if (first.Address < address)
|
||||
{
|
||||
buffPre = new BufferModifiedRange(first.Address, address - first.Address,
|
||||
first.Value.SyncNumber, first.Value.Parent);
|
||||
first.SyncNumber, first.Parent);
|
||||
extendsPre = true;
|
||||
}
|
||||
|
||||
if (last.EndAddress > endAddress)
|
||||
{
|
||||
buffPost = new BufferModifiedRange(endAddress, last.EndAddress - endAddress,
|
||||
last.Value.SyncNumber, last.Value.Parent);
|
||||
last.SyncNumber, last.Parent);
|
||||
extendsPost = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <summary>
|
||||
/// Represents a GPU virtual memory range.
|
||||
/// </summary>
|
||||
private class VirtualRange : INonOverlappingRange
|
||||
private class VirtualRange : INonOverlappingRange<VirtualRange>
|
||||
{
|
||||
/// <summary>
|
||||
/// GPU virtual address where the range starts.
|
||||
@@ -32,6 +32,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
public ulong EndAddress => Address + Size;
|
||||
|
||||
public VirtualRange Next { get; set; }
|
||||
public VirtualRange Previous { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Physical regions where the GPU virtual region is mapped.
|
||||
/// </summary>
|
||||
@@ -54,14 +57,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// Checks if a given range overlaps with the buffer.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="size">Size in bytes of the range</param>
|
||||
/// <param name="endAddress">End address of the range</param>
|
||||
/// <returns>True if the range overlaps, false otherwise</returns>
|
||||
public bool OverlapsWith(ulong address, ulong size)
|
||||
public bool OverlapsWith(ulong address, ulong endAddress)
|
||||
{
|
||||
return Address < address + size && address < EndAddress;
|
||||
return Address < endAddress && address < EndAddress;
|
||||
}
|
||||
|
||||
public INonOverlappingRange Split(ulong splitAddress)
|
||||
public INonOverlappingRange<VirtualRange> Split(ulong splitAddress)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -122,7 +125,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
ulong originalVa = gpuVa;
|
||||
|
||||
_virtualRanges.Lock.EnterWriteLock();
|
||||
(RangeItem<VirtualRange> first, RangeItem<VirtualRange> last) = _virtualRanges.FindOverlapsAsNodes(gpuVa, size);
|
||||
(VirtualRange first, VirtualRange last) = _virtualRanges.FindOverlapsAsNodes(gpuVa, size);
|
||||
|
||||
if (first is not null)
|
||||
{
|
||||
@@ -147,8 +150,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
else
|
||||
{
|
||||
found = first.Value.Range.Count == 1 || IsSparseAligned(first.Value.Range);
|
||||
range = first.Value.Range.Slice(gpuVa - first.Address, size);
|
||||
found = first.Range.Count == 1 || IsSparseAligned(first.Range);
|
||||
range = first.Range.Slice(gpuVa - first.Address, size);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -17,6 +17,6 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
|
||||
/// Action to be performed immediately before sync is created.
|
||||
/// </summary>
|
||||
/// <param name="syncpoint">True if the action is a guest syncpoint</param>
|
||||
void SyncPreAction(bool syncpoint) { }
|
||||
bool SyncPreAction(bool syncpoint) { return true; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1166,7 +1166,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
|
||||
public void SetRenderTargets(Span<ITexture> colors, ITexture depthStencil)
|
||||
{
|
||||
EnsureFramebuffer();
|
||||
|
||||
|
||||
@@ -71,17 +71,31 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
HasDepthStencil = isDepthStencil;
|
||||
}
|
||||
|
||||
public FramebufferParams(Device device, ITexture[] colors, ITexture depthStencil)
|
||||
public FramebufferParams(Device device, ReadOnlySpan<ITexture> colors, ITexture depthStencil)
|
||||
{
|
||||
_device = device;
|
||||
|
||||
int colorsCount = colors.Count(IsValidTextureView);
|
||||
int colorsCount = 0;
|
||||
_colorsCanonical = new TextureView[colors.Length];
|
||||
|
||||
for (int i = 0; i < colors.Length; i++)
|
||||
{
|
||||
ITexture color = colors[i];
|
||||
if (color is TextureView { Valid: true } view)
|
||||
{
|
||||
colorsCount++;
|
||||
_colorsCanonical[i] = view;
|
||||
}
|
||||
else
|
||||
{
|
||||
_colorsCanonical[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
int count = colorsCount + (IsValidTextureView(depthStencil) ? 1 : 0);
|
||||
|
||||
_attachments = new Auto<DisposableImageView>[count];
|
||||
_colors = new TextureView[colorsCount];
|
||||
_colorsCanonical = colors.Select(color => color is TextureView view && view.Valid ? view : null).ToArray();
|
||||
|
||||
AttachmentSamples = new uint[count];
|
||||
AttachmentFormats = new VkFormat[count];
|
||||
@@ -165,9 +179,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_totalCount = colors.Length;
|
||||
}
|
||||
|
||||
public FramebufferParams Update(ITexture[] colors, ITexture depthStencil)
|
||||
public FramebufferParams Update(ReadOnlySpan<ITexture> colors, ITexture depthStencil)
|
||||
{
|
||||
int colorsCount = colors.Count(IsValidTextureView);
|
||||
int colorsCount = 0;
|
||||
|
||||
foreach (ITexture color in colors)
|
||||
{
|
||||
if (IsValidTextureView(color))
|
||||
{
|
||||
colorsCount++;
|
||||
}
|
||||
}
|
||||
|
||||
int count = colorsCount + (IsValidTextureView(depthStencil) ? 1 : 0);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
@@ -10,6 +10,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
/// </summary>
|
||||
class MultiFenceHolder
|
||||
{
|
||||
public static readonly ObjectPool<FenceHolder[]> FencePool = new(() => new FenceHolder[CommandBufferPool.MaxCommandBuffers]);
|
||||
|
||||
private const int BufferUsageTrackingGranularity = 4096;
|
||||
|
||||
public FenceHolder[] Fences { get; }
|
||||
@@ -20,7 +22,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
/// </summary>
|
||||
public MultiFenceHolder()
|
||||
{
|
||||
Fences = ArrayPool<FenceHolder>.Shared.Rent(CommandBufferPool.MaxCommandBuffers);
|
||||
Fences = FencePool.Allocate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -29,7 +31,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
/// <param name="size">Size of the buffer</param>
|
||||
public MultiFenceHolder(int size)
|
||||
{
|
||||
Fences = ArrayPool<FenceHolder>.Shared.Rent(CommandBufferPool.MaxCommandBuffers);
|
||||
Fences = FencePool.Allocate();
|
||||
_bufferUsageBitmap = new BufferUsageBitmap(size, BufferUsageTrackingGranularity);
|
||||
}
|
||||
|
||||
|
||||
@@ -1035,7 +1035,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
private void SetRenderTargetsInternal(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked)
|
||||
private void SetRenderTargetsInternal(Span<ITexture> colors, ITexture depthStencil, bool filterWriteMasked)
|
||||
{
|
||||
CreateFramebuffer(colors, depthStencil, filterWriteMasked);
|
||||
CreateRenderPass();
|
||||
@@ -1043,7 +1043,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
SignalAttachmentChange();
|
||||
}
|
||||
|
||||
public void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
|
||||
public void SetRenderTargets(Span<ITexture> colors, ITexture depthStencil)
|
||||
{
|
||||
_framebufferUsingColorWriteMask = false;
|
||||
SetRenderTargetsInternal(colors, depthStencil, Gd.IsTBDR);
|
||||
@@ -1389,7 +1389,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_currentPipelineHandle = 0;
|
||||
}
|
||||
|
||||
private void CreateFramebuffer(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked)
|
||||
private void CreateFramebuffer(Span<ITexture> colors, ITexture depthStencil, bool filterWriteMasked)
|
||||
{
|
||||
if (filterWriteMasked)
|
||||
{
|
||||
@@ -1399,7 +1399,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
// Just try to remove duplicate attachments.
|
||||
// Save a copy of the array to rebind when mask changes.
|
||||
|
||||
void MaskOut()
|
||||
void MaskOut(ReadOnlySpan<ITexture> colors)
|
||||
{
|
||||
if (!_framebufferUsingColorWriteMask)
|
||||
{
|
||||
@@ -1436,12 +1436,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
if (vkBlend.ColorWriteMask == 0)
|
||||
{
|
||||
colors[i] = null;
|
||||
MaskOut();
|
||||
MaskOut(colors);
|
||||
}
|
||||
else if (vkBlend2.ColorWriteMask == 0)
|
||||
{
|
||||
colors[j] = null;
|
||||
MaskOut();
|
||||
MaskOut(colors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Silk.NET.Vulkan;
|
||||
using System.Buffers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
@@ -193,7 +193,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
_firstHandle = first.ID + 1;
|
||||
_handles.RemoveAt(0);
|
||||
ArrayPool<FenceHolder>.Shared.Return(first.Waitable.Fences);
|
||||
Array.Clear(first.Waitable.Fences);
|
||||
MultiFenceHolder.FencePool.Release(first.Waitable.Fences);
|
||||
first.Waitable = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,91 @@
|
||||
using Gommon;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
public partial class Debugger
|
||||
{
|
||||
private sealed record RcmdEntry(string[] Names, Func<Debugger, string, string> Handler, string[] HelpLines);
|
||||
|
||||
// Atmosphere/libraries/libmesosphere/source/kern_k_memory_block_manager.cpp
|
||||
private static readonly string[] _memoryStateNames =
|
||||
{
|
||||
"----- Free -----",
|
||||
"Io ",
|
||||
"Static ",
|
||||
"Code ",
|
||||
"CodeData ",
|
||||
"Normal ",
|
||||
"Shared ",
|
||||
"Alias ",
|
||||
"AliasCode ",
|
||||
"AliasCodeData ",
|
||||
"Ipc ",
|
||||
"Stack ",
|
||||
"ThreadLocal ",
|
||||
"Transfered ",
|
||||
"SharedTransfered",
|
||||
"SharedCode ",
|
||||
"Inaccessible ",
|
||||
"NonSecureIpc ",
|
||||
"NonDeviceIpc ",
|
||||
"Kernel ",
|
||||
"GeneratedCode ",
|
||||
"CodeOut ",
|
||||
"Coverage ",
|
||||
};
|
||||
|
||||
static Debugger()
|
||||
{
|
||||
_rcmdDelegates.Add(["help"],
|
||||
_ => _rcmdDelegates.Keys
|
||||
.Where(x => !x[0].Equals("help"))
|
||||
.Select(x => x.JoinToString('\n'))
|
||||
.JoinToString('\n') + '\n'
|
||||
);
|
||||
_rcmdDelegates.Add(["get info"], dbgr => dbgr.GetProcessInfo());
|
||||
_rcmdDelegates.Add(["backtrace", "bt"], dbgr => dbgr.GetStackTrace());
|
||||
_rcmdDelegates.Add(["registers", "reg"], dbgr => dbgr.GetRegisters());
|
||||
_rcmdDelegates.Add(["minidump"], dbgr => dbgr.GetMinidump());
|
||||
_rcmdDelegates.Add(new RcmdEntry(
|
||||
["help"],
|
||||
(dbgr, _) => _rcmdDelegates
|
||||
.Where(entry => entry.HelpLines.Length > 0)
|
||||
.SelectMany(entry => entry.HelpLines)
|
||||
.JoinToString('\n') + '\n',
|
||||
Array.Empty<string>()));
|
||||
|
||||
_rcmdDelegates.Add(new RcmdEntry(["get info"], (dbgr, _) => dbgr.GetProcessInfo(), ["get info"]));
|
||||
_rcmdDelegates.Add(new RcmdEntry(["backtrace", "bt"], (dbgr, _) => dbgr.GetStackTrace(), ["backtrace", "bt"]));
|
||||
_rcmdDelegates.Add(new RcmdEntry(["registers", "reg"], (dbgr, _) => dbgr.GetRegisters(), ["registers", "reg"]));
|
||||
_rcmdDelegates.Add(new RcmdEntry(["minidump"], (dbgr, _) => dbgr.GetMinidump(), ["minidump"]));
|
||||
_rcmdDelegates.Add(new RcmdEntry(["get mappings"], (dbgr, args) => dbgr.GetMemoryMappings(args), ["get mappings", "get mappings {address}"]));
|
||||
_rcmdDelegates.Add(new RcmdEntry(["get mapping"], (dbgr, args) => dbgr.GetMemoryMapping(args), ["get mapping {address}"]));
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string[], Func<Debugger, string>> _rcmdDelegates = new();
|
||||
private static readonly List<RcmdEntry> _rcmdDelegates = [];
|
||||
|
||||
public static Func<Debugger, string> FindRcmdDelegate(string command)
|
||||
public static string CallRcmdDelegate(Debugger debugger, string command)
|
||||
{
|
||||
Func<Debugger, string> searchResult = _ => $"Unknown command: {command}\n";
|
||||
string originalCommand = command ?? string.Empty;
|
||||
string trimmedCommand = originalCommand.Trim();
|
||||
|
||||
foreach ((string[] names, Func<Debugger, string> dlg) in _rcmdDelegates)
|
||||
foreach (RcmdEntry entry in _rcmdDelegates)
|
||||
{
|
||||
if (names.ContainsIgnoreCase(command.Trim()))
|
||||
foreach (string name in entry.Names)
|
||||
{
|
||||
searchResult = dlg;
|
||||
break;
|
||||
if (trimmedCommand.Equals(name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return entry.Handler(debugger, string.Empty);
|
||||
}
|
||||
|
||||
if (trimmedCommand.Length > name.Length &&
|
||||
trimmedCommand.StartsWith(name, StringComparison.OrdinalIgnoreCase) &&
|
||||
char.IsWhiteSpace(trimmedCommand[name.Length]))
|
||||
{
|
||||
string arguments = trimmedCommand[name.Length..].TrimStart();
|
||||
return entry.Handler(debugger, arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return searchResult;
|
||||
return $"Unknown command: {originalCommand}\n";
|
||||
}
|
||||
|
||||
public string GetStackTrace()
|
||||
@@ -86,5 +134,181 @@ namespace Ryujinx.HLE.Debugger
|
||||
return $"Error getting process info: {e.Message}\n";
|
||||
}
|
||||
}
|
||||
|
||||
public string GetMemoryMappings(string arguments)
|
||||
{
|
||||
if (Process?.MemoryManager is not { } memoryManager)
|
||||
{
|
||||
return "No application process found\n";
|
||||
}
|
||||
|
||||
string trimmedArgs = arguments?.Trim() ?? string.Empty;
|
||||
|
||||
ulong startAddress = 0;
|
||||
if (!string.IsNullOrEmpty(trimmedArgs))
|
||||
{
|
||||
if (!TryParseAddressArgument(trimmedArgs, out startAddress))
|
||||
{
|
||||
return $"Invalid address: {trimmedArgs}\n";
|
||||
}
|
||||
}
|
||||
|
||||
ulong requestedAddress = startAddress;
|
||||
ulong currentAddress = Math.Max(requestedAddress, memoryManager.AddrSpaceStart);
|
||||
StringBuilder sb = new();
|
||||
sb.AppendLine($"Mappings (starting from 0x{requestedAddress:x10}):");
|
||||
|
||||
if (currentAddress >= memoryManager.AddrSpaceEnd)
|
||||
{
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
while (currentAddress < memoryManager.AddrSpaceEnd)
|
||||
{
|
||||
KMemoryInfo info = memoryManager.QueryMemory(currentAddress);
|
||||
|
||||
try
|
||||
{
|
||||
if (info.Size == 0 || info.Address >= memoryManager.AddrSpaceEnd)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
sb.AppendLine(FormatMapping(info, indent: true));
|
||||
|
||||
if (info.Address > ulong.MaxValue - info.Size)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ulong nextAddress = info.Address + info.Size;
|
||||
if (nextAddress <= currentAddress)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
currentAddress = nextAddress;
|
||||
}
|
||||
finally
|
||||
{
|
||||
KMemoryInfo.Pool.Release(info);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public string GetMemoryMapping(string arguments)
|
||||
{
|
||||
if (Process?.MemoryManager is not { } memoryManager)
|
||||
{
|
||||
return "No application process found\n";
|
||||
}
|
||||
|
||||
string trimmedArgs = arguments?.Trim() ?? string.Empty;
|
||||
|
||||
if (string.IsNullOrEmpty(trimmedArgs))
|
||||
{
|
||||
return "Missing address argument for `get mapping`\n";
|
||||
}
|
||||
|
||||
if (!TryParseAddressArgument(trimmedArgs, out ulong address))
|
||||
{
|
||||
return $"Invalid address: {trimmedArgs}\n";
|
||||
}
|
||||
|
||||
KMemoryInfo info = memoryManager.QueryMemory(address);
|
||||
|
||||
try
|
||||
{
|
||||
return FormatMapping(info, indent: false) + '\n';
|
||||
}
|
||||
finally
|
||||
{
|
||||
KMemoryInfo.Pool.Release(info);
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatMapping(KMemoryInfo info, bool indent)
|
||||
{
|
||||
ulong endAddress;
|
||||
|
||||
if (info.Size == 0)
|
||||
{
|
||||
endAddress = info.Address;
|
||||
}
|
||||
else if (info.Address > ulong.MaxValue - (info.Size - 1))
|
||||
{
|
||||
endAddress = ulong.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
endAddress = info.Address + info.Size - 1;
|
||||
}
|
||||
|
||||
string prefix = indent ? " " : string.Empty;
|
||||
return $"{prefix}0x{info.Address:x10} - 0x{endAddress:x10} {GetPermissionString(info)} {GetMemoryStateName(info.State)} {GetAttributeFlags(info)} [{info.IpcRefCount}, {info.DeviceRefCount}]";
|
||||
}
|
||||
|
||||
private static string GetPermissionString(KMemoryInfo info)
|
||||
{
|
||||
if ((info.State & MemoryState.UserMask) == MemoryState.Unmapped)
|
||||
{
|
||||
return " ";
|
||||
}
|
||||
|
||||
return info.Permission switch
|
||||
{
|
||||
KMemoryPermission.ReadAndExecute => "r-x",
|
||||
KMemoryPermission.Read => "r--",
|
||||
KMemoryPermission.ReadAndWrite => "rw-",
|
||||
_ => "---"
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetMemoryStateName(MemoryState state)
|
||||
{
|
||||
int stateIndex = (int)(state & MemoryState.UserMask);
|
||||
if ((uint)stateIndex < _memoryStateNames.Length)
|
||||
{
|
||||
return _memoryStateNames[stateIndex];
|
||||
}
|
||||
|
||||
return "Unknown ";
|
||||
}
|
||||
|
||||
private static bool TryParseAddressArgument(string text, out ulong value)
|
||||
{
|
||||
value = 0;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string trimmed = text.Trim();
|
||||
|
||||
if (trimmed.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
trimmed = trimmed[2..];
|
||||
}
|
||||
|
||||
if (trimmed.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return ulong.TryParse(trimmed, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out value);
|
||||
}
|
||||
|
||||
private static string GetAttributeFlags(KMemoryInfo info)
|
||||
{
|
||||
char locked = info.Attribute.HasFlag(MemoryAttribute.Borrowed) ? 'L' : '-';
|
||||
char ipc = info.Attribute.HasFlag(MemoryAttribute.IpcMapped) ? 'I' : '-';
|
||||
char device = info.Attribute.HasFlag(MemoryAttribute.DeviceMapped) ? 'D' : '-';
|
||||
char uncached = info.Attribute.HasFlag(MemoryAttribute.Uncached) ? 'U' : '-';
|
||||
|
||||
return $"{locked}{ipc}{device}{uncached}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,9 +404,8 @@ namespace Ryujinx.HLE.Debugger.Gdb
|
||||
string command = Helpers.FromHex(hexCommand);
|
||||
Logger.Debug?.Print(LogClass.GdbStub, $"Received Rcmd: {command}");
|
||||
|
||||
Func<Debugger, string> rcmd = Debugger.FindRcmdDelegate(command);
|
||||
|
||||
Processor.ReplyHex(rcmd(Debugger));
|
||||
string response = Debugger.CallRcmdDelegate(Debugger, command);
|
||||
Processor.ReplyHex(response);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -483,10 +483,29 @@ namespace Ryujinx.HLE.FileSystem
|
||||
{
|
||||
if (Directory.Exists(keysSource))
|
||||
{
|
||||
foreach (string filePath in Directory.EnumerateFiles(keysSource, "*.keys"))
|
||||
string[] keyPaths = Directory.EnumerateFiles(keysSource, "*.keys").ToArray();
|
||||
|
||||
if (keyPaths.Length is 0)
|
||||
throw new FileNotFoundException($"Directory '{keysSource}' contained no '.keys' files.");
|
||||
|
||||
foreach (string filePath in keyPaths)
|
||||
{
|
||||
VerifyKeysFile(filePath);
|
||||
File.Copy(filePath, Path.Combine(installDirectory, Path.GetFileName(filePath)), true);
|
||||
try
|
||||
{
|
||||
VerifyKeysFile(filePath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, e.Message);
|
||||
continue;
|
||||
}
|
||||
|
||||
string destPath = Path.Combine(installDirectory, Path.GetFileName(filePath));
|
||||
|
||||
if (File.Exists(destPath))
|
||||
File.Delete(destPath);
|
||||
|
||||
File.Copy(filePath, destPath, true);
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -501,13 +520,25 @@ namespace Ryujinx.HLE.FileSystem
|
||||
|
||||
using FileStream file = File.OpenRead(keysSource);
|
||||
|
||||
if (info.Extension is ".keys")
|
||||
if (info.Extension is not ".keys")
|
||||
throw new InvalidFirmwarePackageException("Input file extension is not .keys");
|
||||
|
||||
try
|
||||
{
|
||||
VerifyKeysFile(keysSource);
|
||||
File.Copy(keysSource, Path.Combine(installDirectory, info.Name), true);
|
||||
}
|
||||
else
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new InvalidFirmwarePackageException("Input file is not a valid key package");
|
||||
}
|
||||
|
||||
string dest = Path.Combine(installDirectory, info.Name);
|
||||
|
||||
if (File.Exists(dest))
|
||||
File.Delete(dest);
|
||||
|
||||
// overwrite: true seems to not work on its own? https://github.com/Ryubing/Issues/issues/189
|
||||
File.Copy(keysSource, dest, true);
|
||||
}
|
||||
|
||||
private void FinishInstallation(string temporaryDirectory, string registeredDirectory)
|
||||
@@ -985,8 +1016,8 @@ namespace Ryujinx.HLE.FileSystem
|
||||
public static void VerifyKeysFile(string filePath)
|
||||
{
|
||||
// Verify the keys file format refers to https://github.com/Thealexbarney/LibHac/blob/master/KEYS.md
|
||||
string genericPattern = @"^[a-z0-9_]+ = [a-z0-9]+$";
|
||||
string titlePattern = @"^[a-z0-9]{32} = [a-z0-9]{32}$";
|
||||
string genericPattern = "^[a-z0-9_]+ = [a-z0-9]+$";
|
||||
string titlePattern = "^[a-z0-9]{32} = [a-z0-9]{32}$";
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
@@ -994,24 +1025,13 @@ namespace Ryujinx.HLE.FileSystem
|
||||
string fileName = Path.GetFileName(filePath);
|
||||
string[] lines = File.ReadAllLines(filePath);
|
||||
|
||||
bool verified;
|
||||
switch (fileName)
|
||||
bool verified = fileName switch
|
||||
{
|
||||
case "prod.keys":
|
||||
verified = VerifyKeys(lines, genericPattern);
|
||||
break;
|
||||
case "title.keys":
|
||||
verified = VerifyKeys(lines, titlePattern);
|
||||
break;
|
||||
case "console.keys":
|
||||
verified = VerifyKeys(lines, genericPattern);
|
||||
break;
|
||||
case "dev.keys":
|
||||
verified = VerifyKeys(lines, genericPattern);
|
||||
break;
|
||||
default:
|
||||
throw new FormatException($"Keys file name \"{fileName}\" not supported. Only \"prod.keys\", \"title.keys\", \"console.keys\", \"dev.keys\" are supported.");
|
||||
}
|
||||
"prod.keys" or "console.keys" or "dev.keys" => VerifyKeys(lines, genericPattern),
|
||||
"title.keys" => VerifyKeys(lines, titlePattern),
|
||||
_ => throw new FormatException(
|
||||
$"Keys file name \"{fileName}\" not supported. Only \"prod.keys\", \"title.keys\", \"console.keys\", \"dev.keys\" are supported.")
|
||||
};
|
||||
|
||||
if (!verified)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@ using Microsoft.IO;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
@@ -37,7 +38,7 @@ namespace Ryujinx.HLE.HOS.Ipc
|
||||
|
||||
public IpcMessage(ReadOnlySpan<byte> data, long cmdPtr)
|
||||
{
|
||||
using RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data);
|
||||
RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data);
|
||||
|
||||
BinaryReader reader = new(ms);
|
||||
|
||||
@@ -123,6 +124,8 @@ namespace Ryujinx.HLE.HOS.Ipc
|
||||
}
|
||||
|
||||
ObjectIds = [];
|
||||
|
||||
MemoryStreamManager.Shared.ReleaseStream(ms);
|
||||
}
|
||||
|
||||
public RecyclableMemoryStream GetStream(long cmdPtr, ulong recvListAddr)
|
||||
|
||||
@@ -20,6 +20,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
||||
_exchangeBufferDescriptors = new List<KBufferDescriptor>(MaxInternalBuffersCount);
|
||||
}
|
||||
|
||||
public KBufferDescriptorTable Clear()
|
||||
{
|
||||
_sendBufferDescriptors.Clear();
|
||||
_receiveBufferDescriptors.Clear();
|
||||
_exchangeBufferDescriptors.Clear();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Result AddSendBuffer(ulong src, ulong dst, ulong size, MemoryState state)
|
||||
{
|
||||
return Add(_sendBufferDescriptors, src, dst, size, state);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
@@ -32,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
||||
{
|
||||
KThread currentThread = KernelStatic.GetCurrentThread();
|
||||
|
||||
KSessionRequest request = new(currentThread, customCmdBuffAddr, customCmdBuffSize);
|
||||
KSessionRequest request = _parent.ServerSession.RequestPool.Allocate().Set(currentThread, customCmdBuffAddr, customCmdBuffSize);
|
||||
|
||||
KernelContext.CriticalSection.Enter();
|
||||
|
||||
@@ -55,7 +56,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
||||
{
|
||||
KThread currentThread = KernelStatic.GetCurrentThread();
|
||||
|
||||
KSessionRequest request = new(currentThread, customCmdBuffAddr, customCmdBuffSize, asyncEvent);
|
||||
KSessionRequest request = _parent.ServerSession.RequestPool.Allocate().Set(currentThread, customCmdBuffAddr, customCmdBuffSize, asyncEvent);
|
||||
|
||||
KernelContext.CriticalSection.Enter();
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
||||
{
|
||||
class KServerSession : KSynchronizationObject
|
||||
{
|
||||
public readonly ObjectPool<KSessionRequest> RequestPool = new(() => new KSessionRequest());
|
||||
|
||||
private static readonly MemoryState[] _ipcMemoryStates =
|
||||
[
|
||||
MemoryState.IpcBuffer3,
|
||||
@@ -274,6 +276,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
||||
KernelContext.CriticalSection.Leave();
|
||||
|
||||
WakeClientThread(request, clientResult);
|
||||
|
||||
RequestPool.Release(request);
|
||||
}
|
||||
|
||||
if (clientHeader.ReceiveListType < 2 &&
|
||||
@@ -627,6 +631,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
||||
CloseAllHandles(clientMsg, serverHeader, clientProcess);
|
||||
|
||||
FinishRequest(request, clientResult);
|
||||
|
||||
RequestPool.Release(request);
|
||||
}
|
||||
|
||||
if (clientHeader.ReceiveListType < 2 &&
|
||||
@@ -865,6 +871,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
||||
|
||||
// Unmap buffers from server.
|
||||
FinishRequest(request, clientResult);
|
||||
|
||||
RequestPool.Release(request);
|
||||
|
||||
return serverResult;
|
||||
}
|
||||
@@ -1098,6 +1106,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
||||
foreach (KSessionRequest request in IterateWithRemovalOfAllRequests())
|
||||
{
|
||||
FinishRequest(request, KernelResult.PortRemoteClosed);
|
||||
|
||||
RequestPool.Release(request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1117,6 +1127,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
||||
{
|
||||
SendResultToAsyncRequestClient(request, KernelResult.PortRemoteClosed);
|
||||
}
|
||||
|
||||
RequestPool.Release(request);
|
||||
}
|
||||
|
||||
WakeServerThreads(KernelResult.PortRemoteClosed);
|
||||
|
||||
@@ -5,18 +5,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
||||
{
|
||||
class KSessionRequest
|
||||
{
|
||||
public KBufferDescriptorTable BufferDescriptorTable { get; }
|
||||
public KBufferDescriptorTable BufferDescriptorTable { get; private set; }
|
||||
|
||||
public KThread ClientThread { get; }
|
||||
public KThread ClientThread { get; private set; }
|
||||
|
||||
public KProcess ServerProcess { get; set; }
|
||||
|
||||
public KWritableEvent AsyncEvent { get; }
|
||||
public KWritableEvent AsyncEvent { get; private set; }
|
||||
|
||||
public ulong CustomCmdBuffAddr { get; }
|
||||
public ulong CustomCmdBuffSize { get; }
|
||||
public ulong CustomCmdBuffAddr { get; private set; }
|
||||
public ulong CustomCmdBuffSize { get; private set; }
|
||||
|
||||
public KSessionRequest(
|
||||
public KSessionRequest Set(
|
||||
KThread clientThread,
|
||||
ulong customCmdBuffAddr,
|
||||
ulong customCmdBuffSize,
|
||||
@@ -27,7 +27,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
||||
CustomCmdBuffSize = customCmdBuffSize;
|
||||
AsyncEvent = asyncEvent;
|
||||
|
||||
BufferDescriptorTable = new KBufferDescriptorTable();
|
||||
BufferDescriptorTable = BufferDescriptorTable?.Clear() ?? new KBufferDescriptorTable();
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
@@ -12,12 +10,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
class KAddressArbiter
|
||||
{
|
||||
private const int HasListenersMask = 0x40000000;
|
||||
private static readonly ObjectPool<KThread[]> _threadArrayPool = new(() => []);
|
||||
|
||||
private readonly KernelContext _context;
|
||||
|
||||
private readonly List<KThread> _condVarThreads;
|
||||
private readonly List<KThread> _arbiterThreads;
|
||||
private readonly Dictionary<ulong, List<KThread>> _condVarThreads;
|
||||
private readonly Dictionary<ulong, List<KThread>> _arbiterThreads;
|
||||
private readonly ByDynamicPriority _byDynamicPriority;
|
||||
|
||||
public KAddressArbiter(KernelContext context)
|
||||
{
|
||||
@@ -25,6 +23,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
_condVarThreads = [];
|
||||
_arbiterThreads = [];
|
||||
_byDynamicPriority = new ByDynamicPriority();
|
||||
}
|
||||
|
||||
public Result ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle)
|
||||
@@ -140,9 +139,23 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
currentThread.MutexAddress = mutexAddress;
|
||||
currentThread.ThreadHandleForUserMutex = threadHandle;
|
||||
currentThread.CondVarAddress = condVarAddress;
|
||||
|
||||
_condVarThreads.Add(currentThread);
|
||||
if (_condVarThreads.TryGetValue(condVarAddress, out List<KThread> threads))
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
if (threads.Count > 0)
|
||||
{
|
||||
i = threads.BinarySearch(currentThread, _byDynamicPriority);
|
||||
if (i < 0) i = ~i;
|
||||
}
|
||||
|
||||
threads.Insert(i, currentThread);
|
||||
}
|
||||
else
|
||||
{
|
||||
_condVarThreads.Add(condVarAddress, [currentThread]);
|
||||
}
|
||||
|
||||
if (timeout != 0)
|
||||
{
|
||||
@@ -165,7 +178,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
currentThread.MutexOwner?.RemoveMutexWaiter(currentThread);
|
||||
|
||||
_condVarThreads.Remove(currentThread);
|
||||
_condVarThreads[condVarAddress].Remove(currentThread);
|
||||
|
||||
_context.CriticalSection.Leave();
|
||||
|
||||
@@ -200,13 +213,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
_context.CriticalSection.Enter();
|
||||
|
||||
static bool SignalProcessWideKeyPredicate(KThread thread, ulong address)
|
||||
int validThreads = 0;
|
||||
_condVarThreads.TryGetValue(address, out List<KThread> threads);
|
||||
|
||||
if (threads is not null && threads.Count > 0)
|
||||
{
|
||||
return thread.CondVarAddress == address;
|
||||
validThreads = WakeThreads(threads, count, TryAcquireMutex);
|
||||
}
|
||||
|
||||
int validThreads = WakeThreads(_condVarThreads, count, TryAcquireMutex, SignalProcessWideKeyPredicate, address);
|
||||
|
||||
|
||||
if (validThreads == 0)
|
||||
{
|
||||
KernelTransfer.KernelToUser(address, 0);
|
||||
@@ -315,9 +329,24 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
currentThread.MutexAddress = address;
|
||||
currentThread.WaitingInArbitration = true;
|
||||
|
||||
if (_arbiterThreads.TryGetValue(address, out List<KThread> threads))
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
_arbiterThreads.Add(currentThread);
|
||||
|
||||
if (threads.Count > 0)
|
||||
{
|
||||
i = threads.BinarySearch(currentThread, _byDynamicPriority);
|
||||
if (i < 0) i = ~i;
|
||||
}
|
||||
|
||||
threads.Insert(i, currentThread);
|
||||
}
|
||||
else
|
||||
{
|
||||
_arbiterThreads.Add(address, [currentThread]);
|
||||
}
|
||||
|
||||
currentThread.Reschedule(ThreadSchedState.Paused);
|
||||
|
||||
if (timeout > 0)
|
||||
@@ -336,7 +365,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
if (currentThread.WaitingInArbitration)
|
||||
{
|
||||
_arbiterThreads.Remove(currentThread);
|
||||
_arbiterThreads[address].Remove(currentThread);
|
||||
|
||||
currentThread.WaitingInArbitration = false;
|
||||
}
|
||||
@@ -392,9 +421,24 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
currentThread.MutexAddress = address;
|
||||
currentThread.WaitingInArbitration = true;
|
||||
|
||||
if (_arbiterThreads.TryGetValue(address, out List<KThread> threads))
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
_arbiterThreads.Add(currentThread);
|
||||
|
||||
if (threads.Count > 0)
|
||||
{
|
||||
i = threads.BinarySearch(currentThread, _byDynamicPriority);
|
||||
if (i < 0) i = ~i;
|
||||
}
|
||||
|
||||
threads.Insert(i, currentThread);
|
||||
}
|
||||
else
|
||||
{
|
||||
_arbiterThreads.Add(address, [currentThread]);
|
||||
}
|
||||
|
||||
currentThread.Reschedule(ThreadSchedState.Paused);
|
||||
|
||||
if (timeout > 0)
|
||||
@@ -413,7 +457,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
if (currentThread.WaitingInArbitration)
|
||||
{
|
||||
_arbiterThreads.Remove(currentThread);
|
||||
_arbiterThreads[address].Remove(currentThread);
|
||||
|
||||
currentThread.WaitingInArbitration = false;
|
||||
}
|
||||
@@ -486,15 +530,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
// or equal to the Count of threads to be signaled, or Count is zero
|
||||
// or negative. It is incremented if there are no threads waiting.
|
||||
int waitingCount = 0;
|
||||
|
||||
foreach (KThread thread in _arbiterThreads)
|
||||
|
||||
if (_arbiterThreads.TryGetValue(address, out List<KThread> threads))
|
||||
{
|
||||
if (thread.MutexAddress == address &&
|
||||
++waitingCount >= count)
|
||||
{
|
||||
break;
|
||||
}
|
||||
waitingCount = threads.Count;
|
||||
}
|
||||
|
||||
|
||||
if (waitingCount > 0)
|
||||
{
|
||||
@@ -561,55 +602,38 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
thread.WaitingInArbitration = false;
|
||||
}
|
||||
|
||||
static bool ArbiterThreadPredecate(KThread thread, ulong address)
|
||||
{
|
||||
return thread.MutexAddress == address;
|
||||
}
|
||||
_arbiterThreads.TryGetValue(address, out List<KThread> threads);
|
||||
|
||||
WakeThreads(_arbiterThreads, count, RemoveArbiterThread, ArbiterThreadPredecate, address);
|
||||
if (threads is not null && threads.Count > 0)
|
||||
{
|
||||
WakeThreads(threads, count, RemoveArbiterThread);
|
||||
}
|
||||
}
|
||||
|
||||
private static int WakeThreads(
|
||||
List<KThread> threads,
|
||||
int count,
|
||||
Action<KThread> removeCallback,
|
||||
Func<KThread, ulong, bool> predicate,
|
||||
ulong address = 0)
|
||||
Action<KThread> removeCallback)
|
||||
{
|
||||
KThread[] candidates = _threadArrayPool.Allocate();
|
||||
if (candidates.Length < threads.Count)
|
||||
{
|
||||
Array.Resize(ref candidates, threads.Count);
|
||||
}
|
||||
|
||||
int validCount = 0;
|
||||
|
||||
for (int i = 0; i < threads.Count; i++)
|
||||
{
|
||||
if (predicate(threads[i], address))
|
||||
{
|
||||
candidates[validCount++] = threads[i];
|
||||
}
|
||||
}
|
||||
|
||||
Span<KThread> candidatesSpan = candidates.AsSpan(..validCount);
|
||||
|
||||
candidatesSpan.Sort((x, y) => (x.DynamicPriority.CompareTo(y.DynamicPriority)));
|
||||
int validCount = count > 0 ? Math.Min(count, threads.Count) : threads.Count;
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
candidatesSpan = candidatesSpan[..Math.Min(count, candidatesSpan.Length)];
|
||||
}
|
||||
|
||||
foreach (KThread thread in candidatesSpan)
|
||||
for (int i = 0; i < validCount; i++)
|
||||
{
|
||||
KThread thread = threads[i];
|
||||
removeCallback(thread);
|
||||
threads.Remove(thread);
|
||||
}
|
||||
|
||||
_threadArrayPool.Release(candidates);
|
||||
|
||||
threads.RemoveRange(0, validCount);
|
||||
|
||||
return validCount;
|
||||
}
|
||||
|
||||
private class ByDynamicPriority : IComparer<KThread>
|
||||
{
|
||||
public int Compare(KThread x, KThread y)
|
||||
{
|
||||
return x!.DynamicPriority.CompareTo(y!.DynamicPriority);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,8 +61,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
public KSynchronizationObject SignaledObj { get; set; }
|
||||
|
||||
public ulong CondVarAddress { get; set; }
|
||||
|
||||
private ulong _entrypoint;
|
||||
private ThreadStart _customThreadStart;
|
||||
private bool _forcedUnschedulable;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<AccountState>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<AccountState>))]
|
||||
public enum AccountState
|
||||
{
|
||||
Closed,
|
||||
|
||||
@@ -416,7 +416,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
return ResultCode.InvalidParameters;
|
||||
}
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { albumReportOption });
|
||||
context.Device.UIHandler.TakeScreenshot();
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@ namespace Ryujinx.HLE.HOS.Services
|
||||
private int _selfId;
|
||||
private bool _isDomain;
|
||||
|
||||
// cache array so we don't recreate it all the time
|
||||
private object[] _parameters = [null];
|
||||
|
||||
public IpcService(ServerBase server = null, bool registerTipc = false)
|
||||
{
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
@@ -146,7 +149,9 @@ namespace Ryujinx.HLE.HOS.Services
|
||||
{
|
||||
Logger.Trace?.Print(LogClass.KernelIpc, $"{service.GetType().Name}: {processRequest.Name}");
|
||||
|
||||
result = (ResultCode)processRequest.Invoke(service, [context]);
|
||||
_parameters[0] = context;
|
||||
|
||||
result = (ResultCode)processRequest.Invoke(service, _parameters);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -196,7 +201,9 @@ namespace Ryujinx.HLE.HOS.Services
|
||||
{
|
||||
Logger.Debug?.Print(LogClass.KernelIpc, $"{GetType().Name}: {processRequest.Name}");
|
||||
|
||||
result = (ResultCode)processRequest.Invoke(this, [context]);
|
||||
_parameters[0] = context;
|
||||
|
||||
result = (ResultCode)processRequest.Invoke(this, _parameters);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -59,6 +59,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv
|
||||
|
||||
// TODO: This should call set:sys::GetDebugModeFlag
|
||||
private readonly bool _debugModeEnabled = false;
|
||||
|
||||
private byte[] _ioctl2Buffer = [];
|
||||
private byte[] _ioctlArgumentBuffer = [];
|
||||
private byte[] _ioctl3Buffer = [];
|
||||
|
||||
public INvDrvServices(ServiceCtx context) : base(context.Device.System.NvDrvServer)
|
||||
{
|
||||
@@ -128,27 +132,38 @@ namespace Ryujinx.HLE.HOS.Services.Nv
|
||||
|
||||
if (!context.Memory.TryReadUnsafe(inputDataPosition, (int)inputDataSize, out arguments))
|
||||
{
|
||||
arguments = new byte[inputDataSize];
|
||||
if (_ioctlArgumentBuffer.Length < (int)inputDataSize)
|
||||
{
|
||||
Array.Resize(ref _ioctlArgumentBuffer, (int)inputDataSize);
|
||||
}
|
||||
|
||||
arguments = _ioctlArgumentBuffer.AsSpan(0, (int)inputDataSize);
|
||||
|
||||
context.Memory.Read(inputDataPosition, arguments);
|
||||
}
|
||||
else
|
||||
{
|
||||
arguments = arguments.ToArray();
|
||||
}
|
||||
}
|
||||
else if (isWrite)
|
||||
{
|
||||
byte[] outputData = new byte[outputDataSize];
|
||||
|
||||
arguments = new Span<byte>(outputData);
|
||||
if (_ioctlArgumentBuffer.Length < (int)outputDataSize)
|
||||
{
|
||||
Array.Resize(ref _ioctlArgumentBuffer, (int)outputDataSize);
|
||||
}
|
||||
|
||||
arguments = _ioctlArgumentBuffer.AsSpan(0, (int)outputDataSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] temp = new byte[inputDataSize];
|
||||
if (!context.Memory.TryReadUnsafe(inputDataPosition, (int)inputDataSize, out arguments))
|
||||
{
|
||||
if (_ioctlArgumentBuffer.Length < (int)inputDataSize)
|
||||
{
|
||||
Array.Resize(ref _ioctlArgumentBuffer, (int)inputDataSize);
|
||||
}
|
||||
|
||||
arguments = _ioctlArgumentBuffer.AsSpan(0, (int)inputDataSize);
|
||||
|
||||
context.Memory.Read(inputDataPosition, temp);
|
||||
|
||||
arguments = new Span<byte>(temp);
|
||||
context.Memory.Read(inputDataPosition, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
return NvResult.Success;
|
||||
@@ -270,7 +285,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
|
||||
|
||||
if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0)
|
||||
{
|
||||
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray());
|
||||
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -474,13 +489,15 @@ namespace Ryujinx.HLE.HOS.Services.Nv
|
||||
|
||||
errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments);
|
||||
|
||||
byte[] inlineInBuffer = null;
|
||||
|
||||
if (!context.Memory.TryReadUnsafe(inlineInBufferPosition, (int)inlineInBufferSize, out Span<byte> inlineInBufferSpan))
|
||||
{
|
||||
inlineInBuffer = _byteArrayPool.Rent((int)inlineInBufferSize);
|
||||
inlineInBufferSpan = inlineInBuffer;
|
||||
context.Memory.Read(inlineInBufferPosition, inlineInBufferSpan[..(int)inlineInBufferSize]);
|
||||
if (_ioctl2Buffer.Length < (int)inlineInBufferSize)
|
||||
{
|
||||
Array.Resize(ref _ioctl2Buffer, (int)inlineInBufferSize);
|
||||
}
|
||||
|
||||
inlineInBufferSpan = _ioctl2Buffer.AsSpan(0, (int)inlineInBufferSize);
|
||||
context.Memory.Read(inlineInBufferPosition, inlineInBufferSpan);
|
||||
}
|
||||
|
||||
if (errorCode == NvResult.Success)
|
||||
@@ -489,7 +506,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
|
||||
|
||||
if (errorCode == NvResult.Success)
|
||||
{
|
||||
NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBufferSpan[..(int)inlineInBufferSize]);
|
||||
NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBufferSpan);
|
||||
|
||||
if (internalResult == NvInternalResult.NotImplemented)
|
||||
{
|
||||
@@ -500,15 +517,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv
|
||||
|
||||
if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0)
|
||||
{
|
||||
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray());
|
||||
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inlineInBuffer is not null)
|
||||
{
|
||||
_byteArrayPool.Return(inlineInBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
context.ResponseData.Write((uint)errorCode);
|
||||
@@ -531,13 +543,15 @@ namespace Ryujinx.HLE.HOS.Services.Nv
|
||||
|
||||
errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments);
|
||||
|
||||
byte[] inlineOutBuffer = null;
|
||||
|
||||
if (!context.Memory.TryReadUnsafe(inlineOutBufferPosition, (int)inlineOutBufferSize, out Span<byte> inlineOutBufferSpan))
|
||||
{
|
||||
inlineOutBuffer = _byteArrayPool.Rent((int)inlineOutBufferSize);
|
||||
inlineOutBufferSpan = inlineOutBuffer;
|
||||
context.Memory.Read(inlineOutBufferPosition, inlineOutBufferSpan[..(int)inlineOutBufferSize]);
|
||||
if (_ioctl3Buffer.Length < (int)inlineOutBufferSize)
|
||||
{
|
||||
Array.Resize(ref _ioctl3Buffer, (int)inlineOutBufferSize);
|
||||
}
|
||||
|
||||
inlineOutBufferSpan = _ioctl3Buffer.AsSpan(0, (int)inlineOutBufferSize);
|
||||
context.Memory.Read(inlineOutBufferPosition, inlineOutBufferSpan);
|
||||
}
|
||||
|
||||
if (errorCode == NvResult.Success)
|
||||
@@ -546,7 +560,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
|
||||
|
||||
if (errorCode == NvResult.Success)
|
||||
{
|
||||
NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBufferSpan[..(int)inlineOutBufferSize]);
|
||||
NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBufferSpan);
|
||||
|
||||
if (internalResult == NvInternalResult.NotImplemented)
|
||||
{
|
||||
@@ -557,16 +571,11 @@ namespace Ryujinx.HLE.HOS.Services.Nv
|
||||
|
||||
if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0)
|
||||
{
|
||||
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray());
|
||||
context.Memory.Write(inlineOutBufferPosition, inlineOutBufferSpan[..(int)inlineOutBufferSize].ToArray());
|
||||
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments);
|
||||
context.Memory.Write(inlineOutBufferPosition, inlineOutBufferSpan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inlineOutBuffer is not null)
|
||||
{
|
||||
_byteArrayPool.Return(inlineOutBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
context.ResponseData.Write((uint)errorCode);
|
||||
|
||||
@@ -454,8 +454,9 @@ namespace Ryujinx.HLE.HOS.Services
|
||||
|
||||
response.RawData = _responseDataStream.ToArray();
|
||||
|
||||
using RecyclableMemoryStream responseStream = response.GetStreamTipc();
|
||||
RecyclableMemoryStream responseStream = response.GetStreamTipc();
|
||||
_selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence());
|
||||
MemoryStreamManager.Shared.ReleaseStream(responseStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -464,8 +465,9 @@ namespace Ryujinx.HLE.HOS.Services
|
||||
|
||||
if (!isTipcCommunication)
|
||||
{
|
||||
using RecyclableMemoryStream responseStream = response.GetStream((long)_selfThread.TlsAddress, recvListAddr | ((ulong)PointerBufferSize << 48));
|
||||
RecyclableMemoryStream responseStream = response.GetStream((long)_selfThread.TlsAddress, recvListAddr | ((ulong)PointerBufferSize << 48));
|
||||
_selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence());
|
||||
MemoryStreamManager.Shared.ReleaseStream(responseStream);
|
||||
}
|
||||
|
||||
return shouldReply;
|
||||
|
||||
@@ -68,5 +68,10 @@ namespace Ryujinx.HLE.UI
|
||||
/// Displays the player select dialog and returns the selected profile.
|
||||
/// </summary>
|
||||
UserProfile ShowPlayerSelectDialog();
|
||||
|
||||
/// <summary>
|
||||
/// Takes a screenshot from the current renderer and saves it in the screenshots folder.
|
||||
/// </summary>
|
||||
void TakeScreenshot();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
|
||||
private int _waitingThreadHandle;
|
||||
|
||||
private MultiWaitHolderBase _signaledHolder;
|
||||
|
||||
ObjectPool<int[]> _objectHandlePool = new(() => new int[64]);
|
||||
ObjectPool<MultiWaitHolderBase[]> _objectPool = new(() => new MultiWaitHolderBase[64]);
|
||||
|
||||
public long CurrentTime { get; private set; }
|
||||
|
||||
@@ -76,11 +79,15 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
|
||||
|
||||
private MultiWaitHolderBase WaitAnyHandleImpl(bool infinite, long timeout)
|
||||
{
|
||||
Span<int> objectHandles = new int[64];
|
||||
int[] objectHandles = _objectHandlePool.Allocate();
|
||||
Span<int> objectHandlesSpan = objectHandles;
|
||||
objectHandlesSpan.Clear();
|
||||
|
||||
Span<MultiWaitHolderBase> objects = new MultiWaitHolderBase[64];
|
||||
MultiWaitHolderBase[] objects = _objectPool.Allocate();
|
||||
Span<MultiWaitHolderBase> objectsSpan = objects;
|
||||
objectsSpan.Clear();
|
||||
|
||||
int count = FillObjectsArray(objectHandles, objects);
|
||||
int count = FillObjectsArray(objectHandlesSpan, objectsSpan);
|
||||
|
||||
long endTime = infinite ? long.MaxValue : PerformanceCounter.ElapsedMilliseconds * 1000000;
|
||||
|
||||
@@ -98,7 +105,7 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
|
||||
}
|
||||
else
|
||||
{
|
||||
index = WaitSynchronization(objectHandles[..count], minTimeout);
|
||||
index = WaitSynchronization(objectHandlesSpan[..count], minTimeout);
|
||||
|
||||
DebugUtil.Assert(index != WaitInvalid);
|
||||
}
|
||||
@@ -116,12 +123,18 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
|
||||
{
|
||||
_signaledHolder = minTimeoutObject;
|
||||
|
||||
_objectHandlePool.Release(objectHandles);
|
||||
_objectPool.Release(objects);
|
||||
|
||||
return _signaledHolder;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_objectHandlePool.Release(objectHandles);
|
||||
_objectPool.Release(objects);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -131,6 +144,9 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
|
||||
{
|
||||
if (_signaledHolder != null)
|
||||
{
|
||||
_objectHandlePool.Release(objectHandles);
|
||||
_objectPool.Release(objects);
|
||||
|
||||
return _signaledHolder;
|
||||
}
|
||||
}
|
||||
@@ -139,8 +155,11 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
|
||||
default:
|
||||
lock (_lock)
|
||||
{
|
||||
_signaledHolder = objects[index];
|
||||
_signaledHolder = objectsSpan[index];
|
||||
|
||||
_objectHandlePool.Release(objectHandles);
|
||||
_objectPool.Release(objects);
|
||||
|
||||
return _signaledHolder;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,8 +532,6 @@ namespace Ryujinx.Input.HLE
|
||||
|
||||
hidKeyboard.Modifier |= value << entry.Target;
|
||||
}
|
||||
|
||||
ArrayPool<bool>.Shared.Return(keyboardState.KeysState);
|
||||
|
||||
return hidKeyboard;
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ namespace Ryujinx.Input.HLE
|
||||
{
|
||||
public class NpadManager : IDisposable
|
||||
{
|
||||
private static readonly ObjectPool<List<SixAxisInput>> _hleMotionStatesPool = new (() => new List<SixAxisInput>(NpadDevices.MaxControllers));
|
||||
private readonly CemuHookClient _cemuHookClient;
|
||||
|
||||
private readonly Lock _lock = new();
|
||||
@@ -40,6 +39,9 @@ namespace Ryujinx.Input.HLE
|
||||
private bool _enableKeyboard;
|
||||
private bool _enableMouse;
|
||||
private Switch _device;
|
||||
|
||||
private readonly List<GamepadInput> _hleInputStates = [];
|
||||
private readonly List<SixAxisInput> _hleMotionStates = new(NpadDevices.MaxControllers);
|
||||
|
||||
public NpadManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IGamepadDriver mouseDriver)
|
||||
{
|
||||
@@ -217,8 +219,8 @@ namespace Ryujinx.Input.HLE
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
List<GamepadInput> hleInputStates = [];
|
||||
List<SixAxisInput> hleMotionStates = _hleMotionStatesPool.Allocate();
|
||||
_hleInputStates.Clear();
|
||||
_hleMotionStates.Clear();
|
||||
|
||||
KeyboardInput? hleKeyboardInput = null;
|
||||
|
||||
@@ -260,14 +262,14 @@ namespace Ryujinx.Input.HLE
|
||||
inputState.PlayerId = playerIndex;
|
||||
motionState.Item1.PlayerId = playerIndex;
|
||||
|
||||
hleInputStates.Add(inputState);
|
||||
hleMotionStates.Add(motionState.Item1);
|
||||
_hleInputStates.Add(inputState);
|
||||
_hleMotionStates.Add(motionState.Item1);
|
||||
|
||||
if (isJoyconPair && !motionState.Item2.Equals(default))
|
||||
{
|
||||
motionState.Item2.PlayerId = playerIndex;
|
||||
|
||||
hleMotionStates.Add(motionState.Item2);
|
||||
_hleMotionStates.Add(motionState.Item2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,8 +278,8 @@ namespace Ryujinx.Input.HLE
|
||||
hleKeyboardInput = NpadController.GetHLEKeyboardInput(_keyboardDriver);
|
||||
}
|
||||
|
||||
_device.Hid.Npads.Update(hleInputStates);
|
||||
_device.Hid.Npads.UpdateSixAxis(hleMotionStates);
|
||||
_device.Hid.Npads.Update(_hleInputStates);
|
||||
_device.Hid.Npads.UpdateSixAxis(_hleMotionStates);
|
||||
|
||||
if (hleKeyboardInput.HasValue)
|
||||
{
|
||||
@@ -328,10 +330,7 @@ namespace Ryujinx.Input.HLE
|
||||
_device.Hid.Mouse.Update(0, 0);
|
||||
}
|
||||
|
||||
_device.TamperMachine.UpdateInput(hleInputStates);
|
||||
|
||||
hleMotionStates.Clear();
|
||||
_hleMotionStatesPool.Release(hleMotionStates);
|
||||
_device.TamperMachine.UpdateInput(_hleInputStates);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace Ryujinx.Input
|
||||
/// </summary>
|
||||
public interface IKeyboard : IGamepad
|
||||
{
|
||||
private static bool[] _keyState;
|
||||
|
||||
/// <summary>
|
||||
/// Check if a given key is pressed on the keyboard.
|
||||
/// </summary>
|
||||
@@ -29,15 +31,17 @@ namespace Ryujinx.Input
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static KeyboardStateSnapshot GetStateSnapshot(IKeyboard keyboard)
|
||||
{
|
||||
if (_keyState is null)
|
||||
{
|
||||
_keyState = new bool[(int)Key.Count];
|
||||
}
|
||||
|
||||
bool[] keysState = ArrayPool<bool>.Shared.Rent((int)Key.Count);
|
||||
|
||||
for (Key key = 0; key < Key.Count; key++)
|
||||
{
|
||||
keysState[(int)key] = keyboard.IsPressed(key);
|
||||
_keyState[(int)key] = keyboard.IsPressed(key);
|
||||
}
|
||||
|
||||
return new KeyboardStateSnapshot(keysState);
|
||||
return new KeyboardStateSnapshot(_keyState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace Ryujinx.Memory.Range
|
||||
/// <summary>
|
||||
/// Range of memory that can be split in two.
|
||||
/// </summary>
|
||||
public interface INonOverlappingRange : IRange
|
||||
public interface INonOverlappingRange<T> : IRangeListRange<T> where T : class, IRangeListRange<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Split this region into two, around the specified address.
|
||||
@@ -11,6 +11,6 @@ namespace Ryujinx.Memory.Range
|
||||
/// </summary>
|
||||
/// <param name="splitAddress">Address to split the region around</param>
|
||||
/// <returns>The second part of the split region, with start address at the given split.</returns>
|
||||
public INonOverlappingRange Split(ulong splitAddress);
|
||||
public INonOverlappingRange<T> Split(ulong splitAddress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ namespace Ryujinx.Memory.Range
|
||||
/// Check if this range overlaps with another.
|
||||
/// </summary>
|
||||
/// <param name="address">Base address</param>
|
||||
/// <param name="size">Size of the range</param>
|
||||
/// <param name="endAddress">EndAddress of the range</param>
|
||||
/// <returns>True if overlapping, false otherwise</returns>
|
||||
bool OverlapsWith(ulong address, ulong size);
|
||||
bool OverlapsWith(ulong address, ulong endAddress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Ryujinx.Memory.Range
|
||||
/// A range list that assumes ranges are non-overlapping, with list items that can be split in two to avoid overlaps.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the range.</typeparam>
|
||||
public unsafe class NonOverlappingRangeList<T> : RangeListBase<T> where T : class, INonOverlappingRange
|
||||
public class NonOverlappingRangeList<T> : RangeListBase<T> where T : class, INonOverlappingRange<T>
|
||||
{
|
||||
public readonly ReaderWriterLockSlim Lock = new();
|
||||
|
||||
@@ -32,83 +32,18 @@ namespace Ryujinx.Memory.Range
|
||||
/// <param name="item">The item to be added</param>
|
||||
public override void Add(T item)
|
||||
{
|
||||
Debug.Assert(item.Address != item.EndAddress);
|
||||
|
||||
int index = BinarySearch(item.Address);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index;
|
||||
}
|
||||
|
||||
RangeItem<T> rangeItem = _rangeItemPool.Allocate().Set(item);
|
||||
|
||||
Insert(index, rangeItem);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an item's end address on the list. Address must be the same.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to be updated</param>
|
||||
/// <returns>True if the item was located and updated, false otherwise</returns>
|
||||
protected override bool Update(T item)
|
||||
{
|
||||
int index = BinarySearch(item.Address);
|
||||
|
||||
if (index >= 0 && Items[index].Value.Equals(item))
|
||||
{
|
||||
RangeItem<T> rangeItem = new(item) { Previous = Items[index].Previous, Next = Items[index].Next };
|
||||
|
||||
if (index > 0)
|
||||
{
|
||||
Items[index - 1].Next = rangeItem;
|
||||
}
|
||||
|
||||
if (index < Count - 1)
|
||||
{
|
||||
Items[index + 1].Previous = rangeItem;
|
||||
}
|
||||
|
||||
Items[index] = rangeItem;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an item's end address on the list. Address must be the same.
|
||||
/// </summary>
|
||||
/// <param name="item">The RangeItem to be updated</param>
|
||||
/// <returns>True if the item was located and updated, false otherwise</returns>
|
||||
protected override bool Update(RangeItem<T> item)
|
||||
{
|
||||
int index = BinarySearch(item.Address);
|
||||
|
||||
RangeItem<T> rangeItem = new(item.Value) { Previous = item.Previous, Next = item.Next };
|
||||
|
||||
if (index > 0)
|
||||
{
|
||||
Items[index - 1].Next = rangeItem;
|
||||
}
|
||||
|
||||
if (index < Count - 1)
|
||||
{
|
||||
Items[index + 1].Previous = rangeItem;
|
||||
}
|
||||
|
||||
Items[index] = rangeItem;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void Insert(int index, RangeItem<T> item)
|
||||
{
|
||||
Debug.Assert(item.Address != item.EndAddress);
|
||||
|
||||
if (Count + 1 > Items.Length)
|
||||
{
|
||||
Array.Resize(ref Items, Items.Length + BackingGrowthSize);
|
||||
Array.Resize(ref Items, (int)(Items.Length * 1.5));
|
||||
}
|
||||
|
||||
if (index >= Count)
|
||||
@@ -145,8 +80,6 @@ namespace Ryujinx.Memory.Range
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void RemoveAt(int index)
|
||||
{
|
||||
_rangeItemPool.Release(Items[index]);
|
||||
|
||||
if (index < Count - 1)
|
||||
{
|
||||
Items[index + 1].Previous = index > 0 ? Items[index - 1] : null;
|
||||
@@ -173,7 +106,7 @@ namespace Ryujinx.Memory.Range
|
||||
{
|
||||
int index = BinarySearch(item.Address);
|
||||
|
||||
if (index >= 0 && Items[index].Value.Equals(item))
|
||||
if (index >= 0 && Items[index] == item)
|
||||
{
|
||||
RemoveAt(index);
|
||||
|
||||
@@ -188,7 +121,7 @@ namespace Ryujinx.Memory.Range
|
||||
/// </summary>
|
||||
/// <param name="startItem">The first item in the range of items to be removed</param>
|
||||
/// <param name="endItem">The last item in the range of items to be removed</param>
|
||||
public override void RemoveRange(RangeItem<T> startItem, RangeItem<T> endItem)
|
||||
public override void RemoveRange(T startItem, T endItem)
|
||||
{
|
||||
if (startItem is null)
|
||||
{
|
||||
@@ -197,7 +130,7 @@ namespace Ryujinx.Memory.Range
|
||||
|
||||
if (startItem == endItem)
|
||||
{
|
||||
Remove(startItem.Value);
|
||||
Remove(startItem);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -229,42 +162,45 @@ namespace Ryujinx.Memory.Range
|
||||
/// <param name="size">Size of the range</param>
|
||||
public void RemoveRange(ulong address, ulong size)
|
||||
{
|
||||
int startIndex = BinarySearchLeftEdge(address, address + size);
|
||||
(int startIndex, int endIndex) = BinarySearchEdges(address, address + size);
|
||||
|
||||
if (startIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int endIndex = startIndex;
|
||||
|
||||
while (Items[endIndex] is not null && Items[endIndex].Address < address + size)
|
||||
if (startIndex == endIndex - 1)
|
||||
{
|
||||
if (endIndex == Count - 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
endIndex++;
|
||||
RemoveAt(startIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (endIndex < Count - 1)
|
||||
RemoveRangeInternal(startIndex, endIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a range of items from the item list
|
||||
/// </summary>
|
||||
/// <param name="index">Start index of the range</param>
|
||||
/// <param name="endIndex">End index of the range (exclusive)</param>
|
||||
private void RemoveRangeInternal(int index, int endIndex)
|
||||
{
|
||||
if (endIndex < Count)
|
||||
{
|
||||
Items[endIndex + 1].Previous = startIndex > 0 ? Items[startIndex - 1] : null;
|
||||
Items[endIndex].Previous = index > 0 ? Items[index - 1] : null;
|
||||
}
|
||||
|
||||
if (startIndex > 0)
|
||||
if (index > 0)
|
||||
{
|
||||
Items[startIndex - 1].Next = endIndex < Count - 1 ? Items[endIndex + 1] : null;
|
||||
Items[index - 1].Next = endIndex < Count ? Items[endIndex] : null;
|
||||
}
|
||||
|
||||
|
||||
if (endIndex < Count - 1)
|
||||
if (endIndex < Count)
|
||||
{
|
||||
Array.Copy(Items, endIndex + 1, Items, startIndex, Count - endIndex - 1);
|
||||
Array.Copy(Items, endIndex, Items, index, Count - endIndex);
|
||||
}
|
||||
|
||||
Count -= endIndex - startIndex + 1;
|
||||
Count -= endIndex - index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -296,8 +232,8 @@ namespace Ryujinx.Memory.Range
|
||||
// So we need to return both the split 0-1 and 1-2 ranges.
|
||||
|
||||
Lock.EnterWriteLock();
|
||||
(RangeItem<T> first, RangeItem<T> last) = FindOverlapsAsNodes(address, size);
|
||||
list = new List<T>();
|
||||
(T first, T last) = FindOverlapsAsNodes(address, size);
|
||||
list = [];
|
||||
|
||||
if (first is null)
|
||||
{
|
||||
@@ -311,42 +247,41 @@ namespace Ryujinx.Memory.Range
|
||||
ulong lastAddress = address;
|
||||
ulong endAddress = address + size;
|
||||
|
||||
RangeItem<T> current = first;
|
||||
T current = first;
|
||||
while (last is not null && current is not null && current.Address < endAddress)
|
||||
{
|
||||
T region = current.Value;
|
||||
if (first == last && region.Address == address && region.Size == size)
|
||||
if (first == last && current.Address == address && current.Size == size)
|
||||
{
|
||||
// Exact match, no splitting required.
|
||||
list.Add(region);
|
||||
list.Add(current);
|
||||
Lock.ExitWriteLock();
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastAddress < region.Address)
|
||||
if (lastAddress < current.Address)
|
||||
{
|
||||
// There is a gap between this region and the last. We need to fill it.
|
||||
T fillRegion = factory(lastAddress, region.Address - lastAddress);
|
||||
T fillRegion = factory(lastAddress, current.Address - lastAddress);
|
||||
list.Add(fillRegion);
|
||||
Add(fillRegion);
|
||||
}
|
||||
|
||||
if (region.Address < address)
|
||||
if (current.Address < address)
|
||||
{
|
||||
// Split the region around our base address and take the high half.
|
||||
|
||||
region = Split(region, address);
|
||||
current = Split(current, address);
|
||||
}
|
||||
|
||||
if (region.EndAddress > address + size)
|
||||
if (current.EndAddress > address + size)
|
||||
{
|
||||
// Split the region around our end address and take the low half.
|
||||
|
||||
Split(region, address + size);
|
||||
Split(current, address + size);
|
||||
}
|
||||
|
||||
list.Add(region);
|
||||
lastAddress = region.EndAddress;
|
||||
list.Add(current);
|
||||
lastAddress = current.EndAddress;
|
||||
current = current.Next;
|
||||
}
|
||||
|
||||
@@ -374,7 +309,6 @@ namespace Ryujinx.Memory.Range
|
||||
private T Split(T region, ulong splitAddress)
|
||||
{
|
||||
T newRegion = (T)region.Split(splitAddress);
|
||||
Update(region);
|
||||
Add(newRegion);
|
||||
return newRegion;
|
||||
}
|
||||
@@ -386,16 +320,11 @@ namespace Ryujinx.Memory.Range
|
||||
/// <param name="size">Size in bytes of the range</param>
|
||||
/// <returns>The leftmost overlapping item, or null if none is found</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override RangeItem<T> FindOverlap(ulong address, ulong size)
|
||||
public override T FindOverlap(ulong address, ulong size)
|
||||
{
|
||||
int index = BinarySearchLeftEdge(address, address + size);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Items[index];
|
||||
return index < 0 ? null : Items[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -405,16 +334,11 @@ namespace Ryujinx.Memory.Range
|
||||
/// <param name="size">Size in bytes of the range</param>
|
||||
/// <returns>The overlapping item, or null if none is found</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override RangeItem<T> FindOverlapFast(ulong address, ulong size)
|
||||
public override T FindOverlapFast(ulong address, ulong size)
|
||||
{
|
||||
int index = BinarySearch(address, address + size);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Items[index];
|
||||
return index < 0 ? null : Items[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -424,23 +348,18 @@ namespace Ryujinx.Memory.Range
|
||||
/// <param name="size">Size in bytes of the range</param>
|
||||
/// <returns>The first and last overlapping items, or null if none are found</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public (RangeItem<T>, RangeItem<T>) FindOverlapsAsNodes(ulong address, ulong size)
|
||||
public (T, T) FindOverlapsAsNodes(ulong address, ulong size)
|
||||
{
|
||||
(int index, int endIndex) = BinarySearchEdges(address, address + size);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
return (Items[index], Items[endIndex - 1]);
|
||||
return index < 0 ? (null, null) : (Items[index], Items[endIndex - 1]);
|
||||
}
|
||||
|
||||
public RangeItem<T>[] FindOverlapsAsArray(ulong address, ulong size, out int length)
|
||||
public T[] FindOverlapsAsArray(ulong address, ulong size, out int length)
|
||||
{
|
||||
(int index, int endIndex) = BinarySearchEdges(address, address + size);
|
||||
|
||||
RangeItem<T>[] result;
|
||||
T[] result;
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
@@ -449,29 +368,20 @@ namespace Ryujinx.Memory.Range
|
||||
}
|
||||
else
|
||||
{
|
||||
result = ArrayPool<RangeItem<T>>.Shared.Rent(endIndex - index);
|
||||
result = ArrayPool<T>.Shared.Rent(endIndex - index);
|
||||
length = endIndex - index;
|
||||
|
||||
Array.Copy(Items, index, result, 0, endIndex - index);
|
||||
Items.AsSpan(index, endIndex - index).CopyTo(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Span<RangeItem<T>> FindOverlapsAsSpan(ulong address, ulong size)
|
||||
public ReadOnlySpan<T> FindOverlapsAsSpan(ulong address, ulong size)
|
||||
{
|
||||
(int index, int endIndex) = BinarySearchEdges(address, address + size);
|
||||
|
||||
Span<RangeItem<T>> result;
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
result = [];
|
||||
}
|
||||
else
|
||||
{
|
||||
result = Items.AsSpan().Slice(index, endIndex - index);
|
||||
}
|
||||
ReadOnlySpan<T> result = index < 0 ? [] : Items.AsSpan(index, endIndex - index);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -480,7 +390,7 @@ namespace Ryujinx.Memory.Range
|
||||
{
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
yield return Items[i].Value;
|
||||
yield return Items[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,14 @@ namespace Ryujinx.Memory.Range
|
||||
/// startIndex is inclusive.
|
||||
/// endIndex is exclusive.
|
||||
/// </remarks>
|
||||
public readonly struct OverlapResult<T> where T : IRange
|
||||
public readonly struct OverlapResult<T> where T : class, IRangeListRange<T>
|
||||
{
|
||||
public readonly int StartIndex = -1;
|
||||
public readonly int EndIndex = -1;
|
||||
public readonly RangeItem<T> QuickResult;
|
||||
public readonly T QuickResult;
|
||||
public int Count => EndIndex - StartIndex;
|
||||
|
||||
public OverlapResult(int startIndex, int endIndex, RangeItem<T> quickResult = null)
|
||||
public OverlapResult(int startIndex, int endIndex, T quickResult = null)
|
||||
{
|
||||
this.StartIndex = startIndex;
|
||||
this.EndIndex = endIndex;
|
||||
@@ -33,7 +33,7 @@ namespace Ryujinx.Memory.Range
|
||||
/// Sorted list of ranges that supports binary search.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the range.</typeparam>
|
||||
public class RangeList<T> : RangeListBase<T> where T : IRange
|
||||
public class RangeList<T> : RangeListBase<T> where T : class, IRangeListRange<T>
|
||||
{
|
||||
public readonly ReaderWriterLockSlim Lock = new();
|
||||
|
||||
@@ -61,104 +61,6 @@ namespace Ryujinx.Memory.Range
|
||||
index = ~index;
|
||||
}
|
||||
|
||||
Insert(index, new RangeItem<T>(item));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an item's end address on the list. Address must be the same.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to be updated</param>
|
||||
/// <returns>True if the item was located and updated, false otherwise</returns>
|
||||
protected override bool Update(T item)
|
||||
{
|
||||
int index = BinarySearch(item.Address);
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
while (index < Count)
|
||||
{
|
||||
if (Items[index].Value.Equals(item))
|
||||
{
|
||||
RangeItem<T> rangeItem = new(item) { Previous = Items[index].Previous, Next = Items[index].Next };
|
||||
|
||||
if (index > 0)
|
||||
{
|
||||
Items[index - 1].Next = rangeItem;
|
||||
}
|
||||
|
||||
if (index < Count - 1)
|
||||
{
|
||||
Items[index + 1].Previous = rangeItem;
|
||||
}
|
||||
|
||||
Items[index] = rangeItem;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Items[index].Address > item.Address)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an item's end address on the list. Address must be the same.
|
||||
/// </summary>
|
||||
/// <param name="item">The RangeItem to be updated</param>
|
||||
/// <returns>True if the item was located and updated, false otherwise</returns>
|
||||
protected override bool Update(RangeItem<T> item)
|
||||
{
|
||||
int index = BinarySearch(item.Address);
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
while (index < Count)
|
||||
{
|
||||
if (Items[index].Equals(item))
|
||||
{
|
||||
RangeItem<T> rangeItem = new(item.Value) { Previous = item.Previous, Next = item.Next };
|
||||
|
||||
if (index > 0)
|
||||
{
|
||||
Items[index - 1].Next = rangeItem;
|
||||
}
|
||||
|
||||
if (index < Count - 1)
|
||||
{
|
||||
Items[index + 1].Previous = rangeItem;
|
||||
}
|
||||
|
||||
Items[index] = rangeItem;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Items[index].Address > item.Address)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void Insert(int index, RangeItem<T> item)
|
||||
{
|
||||
Debug.Assert(item.Address != item.EndAddress);
|
||||
|
||||
Debug.Assert(item.Address % 32 == 0);
|
||||
|
||||
if (Count + 1 > Items.Length)
|
||||
{
|
||||
Array.Resize(ref Items, Items.Length + BackingGrowthSize);
|
||||
@@ -220,7 +122,7 @@ namespace Ryujinx.Memory.Range
|
||||
/// <param name="startItem">The first item in the range of items to be removed</param>
|
||||
/// <param name="endItem">The last item in the range of items to be removed</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void RemoveRange(RangeItem<T> startItem, RangeItem<T> endItem)
|
||||
public override void RemoveRange(T startItem, T endItem)
|
||||
{
|
||||
if (startItem is null)
|
||||
{
|
||||
@@ -229,30 +131,29 @@ namespace Ryujinx.Memory.Range
|
||||
|
||||
if (startItem == endItem)
|
||||
{
|
||||
Remove(startItem.Value);
|
||||
Remove(startItem);
|
||||
return;
|
||||
}
|
||||
|
||||
int startIndex = BinarySearch(startItem.Address);
|
||||
int endIndex = BinarySearch(endItem.Address);
|
||||
(int index, int endIndex) = BinarySearchEdges(startItem.Address, endItem.EndAddress);
|
||||
|
||||
if (endIndex < Count - 1)
|
||||
if (endIndex < Count)
|
||||
{
|
||||
Items[endIndex + 1].Previous = startIndex > 0 ? Items[startIndex - 1] : null;
|
||||
Items[endIndex].Previous = index > 0 ? Items[index - 1] : null;
|
||||
}
|
||||
|
||||
if (startIndex > 0)
|
||||
if (index > 0)
|
||||
{
|
||||
Items[startIndex - 1].Next = endIndex < Count - 1 ? Items[endIndex + 1] : null;
|
||||
Items[index - 1].Next = endIndex < Count ? Items[endIndex] : null;
|
||||
}
|
||||
|
||||
|
||||
if (endIndex < Count - 1)
|
||||
if (endIndex < Count)
|
||||
{
|
||||
Array.Copy(Items, endIndex + 1, Items, startIndex, Count - endIndex - 1);
|
||||
Array.Copy(Items, endIndex, Items, index, Count - endIndex);
|
||||
}
|
||||
|
||||
Count -= endIndex - startIndex + 1;
|
||||
Count -= endIndex - index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -268,7 +169,7 @@ namespace Ryujinx.Memory.Range
|
||||
{
|
||||
while (index < Count)
|
||||
{
|
||||
if (Items[index].Value.Equals(item))
|
||||
if (Items[index] == item)
|
||||
{
|
||||
RemoveAt(index);
|
||||
|
||||
@@ -298,7 +199,7 @@ namespace Ryujinx.Memory.Range
|
||||
/// <param name="size">Size in bytes of the range</param>
|
||||
/// <returns>The overlapping item, or the default value for the type if none found</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override RangeItem<T> FindOverlap(ulong address, ulong size)
|
||||
public override T FindOverlap(ulong address, ulong size)
|
||||
{
|
||||
int index = BinarySearchLeftEdge(address, address + size);
|
||||
|
||||
@@ -321,7 +222,7 @@ namespace Ryujinx.Memory.Range
|
||||
/// <param name="size">Size in bytes of the range</param>
|
||||
/// <returns>The overlapping item, or the default value for the type if none found</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override RangeItem<T> FindOverlapFast(ulong address, ulong size)
|
||||
public override T FindOverlapFast(ulong address, ulong size)
|
||||
{
|
||||
int index = BinarySearch(address, address + size);
|
||||
|
||||
@@ -340,7 +241,7 @@ namespace Ryujinx.Memory.Range
|
||||
/// <param name="size">Size in bytes of the range</param>
|
||||
/// <param name="output">Output array where matches will be written. It is automatically resized to fit the results</param>
|
||||
/// <returns>Range information of overlapping items found</returns>
|
||||
private OverlapResult<T> FindOverlaps(ulong address, ulong size, ref RangeItem<T>[] output)
|
||||
private OverlapResult<T> FindOverlaps(ulong address, ulong size, ref T[] output)
|
||||
{
|
||||
int outputCount = 0;
|
||||
|
||||
@@ -353,7 +254,7 @@ namespace Ryujinx.Memory.Range
|
||||
|
||||
for (int i = startIndex; i < Count; i++)
|
||||
{
|
||||
ref RangeItem<T> item = ref Items[i];
|
||||
T item = Items[i];
|
||||
|
||||
if (item.Address >= endAddress)
|
||||
{
|
||||
@@ -398,7 +299,7 @@ namespace Ryujinx.Memory.Range
|
||||
{
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
yield return Items[i].Value;
|
||||
yield return Items[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,56 +1,22 @@
|
||||
using Ryujinx.Common;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Memory.Range
|
||||
{
|
||||
public class RangeItem<TValue> where TValue : IRange
|
||||
public interface IRangeListRange<TValue> : IRange where TValue : class, IRangeListRange<TValue>
|
||||
{
|
||||
public RangeItem<TValue> Next;
|
||||
public RangeItem<TValue> Previous;
|
||||
|
||||
public ulong Address;
|
||||
public ulong EndAddress;
|
||||
|
||||
public TValue Value;
|
||||
|
||||
public RangeItem()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public RangeItem(TValue value)
|
||||
{
|
||||
Address = value.Address;
|
||||
EndAddress = value.Address + value.Size;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public RangeItem<TValue> Set(TValue value)
|
||||
{
|
||||
Next = null;
|
||||
Previous = null;
|
||||
Address = value.Address;
|
||||
EndAddress = value.Address + value.Size;
|
||||
Value = value;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool OverlapsWith(ulong address, ulong endAddress)
|
||||
{
|
||||
return Address < endAddress && address < EndAddress;
|
||||
}
|
||||
public TValue Next { get; set; }
|
||||
public TValue Previous { get; set; }
|
||||
}
|
||||
|
||||
public unsafe abstract class RangeListBase<T> : IEnumerable<T> where T : IRange
|
||||
public unsafe abstract class RangeListBase<T> : IEnumerable<T> where T : class, IRangeListRange<T>
|
||||
{
|
||||
protected static readonly ObjectPool<RangeItem<T>> _rangeItemPool = new(() => new RangeItem<T>());
|
||||
private const int BackingInitialSize = 1024;
|
||||
|
||||
protected RangeItem<T>[] Items;
|
||||
protected T[] Items;
|
||||
protected readonly int BackingGrowthSize;
|
||||
|
||||
public int Count { get; protected set; }
|
||||
@@ -62,32 +28,18 @@ namespace Ryujinx.Memory.Range
|
||||
protected RangeListBase(int backingInitialSize = BackingInitialSize)
|
||||
{
|
||||
BackingGrowthSize = backingInitialSize;
|
||||
Items = new RangeItem<T>[backingInitialSize];
|
||||
Items = new T[backingInitialSize];
|
||||
}
|
||||
|
||||
public abstract void Add(T item);
|
||||
|
||||
/// <summary>
|
||||
/// Updates an item's end address on the list. Address must be the same.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to be updated</param>
|
||||
/// <returns>True if the item was located and updated, false otherwise</returns>
|
||||
protected abstract bool Update(T item);
|
||||
|
||||
/// <summary>
|
||||
/// Updates an item's end address on the list. Address must be the same.
|
||||
/// </summary>
|
||||
/// <param name="item">The RangeItem to be updated</param>
|
||||
/// <returns>True if the item was located and updated, false otherwise</returns>
|
||||
protected abstract bool Update(RangeItem<T> item);
|
||||
|
||||
public abstract bool Remove(T item);
|
||||
|
||||
public abstract void RemoveRange(RangeItem<T> startItem, RangeItem<T> endItem);
|
||||
public abstract void RemoveRange(T startItem, T endItem);
|
||||
|
||||
public abstract RangeItem<T> FindOverlap(ulong address, ulong size);
|
||||
public abstract T FindOverlap(ulong address, ulong size);
|
||||
|
||||
public abstract RangeItem<T> FindOverlapFast(ulong address, ulong size);
|
||||
public abstract T FindOverlapFast(ulong address, ulong size);
|
||||
|
||||
/// <summary>
|
||||
/// Performs binary search on the internal list of items.
|
||||
@@ -106,7 +58,7 @@ namespace Ryujinx.Memory.Range
|
||||
|
||||
int middle = left + (range >> 1);
|
||||
|
||||
ref RangeItem<T> item = ref Items[middle];
|
||||
T item = Items[middle];
|
||||
|
||||
if (item.Address == address)
|
||||
{
|
||||
@@ -144,7 +96,7 @@ namespace Ryujinx.Memory.Range
|
||||
|
||||
int middle = left + (range >> 1);
|
||||
|
||||
ref RangeItem<T> item = ref Items[middle];
|
||||
T item = Items[middle];
|
||||
|
||||
if (item.OverlapsWith(address, endAddress))
|
||||
{
|
||||
@@ -185,7 +137,7 @@ namespace Ryujinx.Memory.Range
|
||||
|
||||
int middle = left + (range >> 1);
|
||||
|
||||
ref RangeItem<T> item = ref Items[middle];
|
||||
T item = Items[middle];
|
||||
|
||||
bool match = item.OverlapsWith(address, endAddress);
|
||||
|
||||
@@ -237,7 +189,7 @@ namespace Ryujinx.Memory.Range
|
||||
|
||||
int middle = right - (range >> 1);
|
||||
|
||||
ref RangeItem<T> item = ref Items[middle];
|
||||
T item = Items[middle];
|
||||
|
||||
bool match = item.OverlapsWith(address, endAddress);
|
||||
|
||||
@@ -282,7 +234,7 @@ namespace Ryujinx.Memory.Range
|
||||
|
||||
if (Count == 1)
|
||||
{
|
||||
ref RangeItem<T> item = ref Items[0];
|
||||
T item = Items[0];
|
||||
|
||||
if (item.OverlapsWith(address, endAddress))
|
||||
{
|
||||
@@ -312,7 +264,7 @@ namespace Ryujinx.Memory.Range
|
||||
|
||||
int middle = left + (range >> 1);
|
||||
|
||||
ref RangeItem<T> item = ref Items[middle];
|
||||
T item = Items[middle];
|
||||
|
||||
bool match = item.OverlapsWith(address, endAddress);
|
||||
|
||||
@@ -369,7 +321,7 @@ namespace Ryujinx.Memory.Range
|
||||
|
||||
int middle = right - (range >> 1);
|
||||
|
||||
ref RangeItem<T> item = ref Items[middle];
|
||||
T item = Items[middle];
|
||||
|
||||
bool match = item.OverlapsWith(address, endAddress);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Ryujinx.Memory.Tracking
|
||||
/// <summary>
|
||||
/// A region of memory.
|
||||
/// </summary>
|
||||
abstract class AbstractRegion : INonOverlappingRange
|
||||
abstract class AbstractRegion<T> : INonOverlappingRange<T> where T : class, INonOverlappingRange<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Base address.
|
||||
@@ -21,6 +21,9 @@ namespace Ryujinx.Memory.Tracking
|
||||
/// End address.
|
||||
/// </summary>
|
||||
public ulong EndAddress => Address + Size;
|
||||
|
||||
public T Next { get; set; }
|
||||
public T Previous { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new region.
|
||||
@@ -37,11 +40,11 @@ namespace Ryujinx.Memory.Tracking
|
||||
/// Check if this range overlaps with another.
|
||||
/// </summary>
|
||||
/// <param name="address">Base address</param>
|
||||
/// <param name="size">Size of the range</param>
|
||||
/// <param name="endAddress">End address</param>
|
||||
/// <returns>True if overlapping, false otherwise</returns>
|
||||
public bool OverlapsWith(ulong address, ulong size)
|
||||
public bool OverlapsWith(ulong address, ulong endAddress)
|
||||
{
|
||||
return Address < address + size && address < EndAddress;
|
||||
return Address < endAddress && address < EndAddress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -68,6 +71,6 @@ namespace Ryujinx.Memory.Tracking
|
||||
/// </summary>
|
||||
/// <param name="splitAddress">Address to split the region around</param>
|
||||
/// <returns>The second part of the split region, with start address at the given split.</returns>
|
||||
public abstract INonOverlappingRange Split(ulong splitAddress);
|
||||
public abstract INonOverlappingRange<T> Split(ulong splitAddress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,10 +81,10 @@ namespace Ryujinx.Memory.Tracking
|
||||
{
|
||||
NonOverlappingRangeList<VirtualRegion> regions = type == 0 ? _virtualRegions : _guestVirtualRegions;
|
||||
regions.Lock.EnterReadLock();
|
||||
Span<RangeItem<VirtualRegion>> overlaps = regions.FindOverlapsAsSpan(va, size);
|
||||
ReadOnlySpan<VirtualRegion> overlaps = regions.FindOverlapsAsSpan(va, size);
|
||||
for (int i = 0; i < overlaps.Length; i++)
|
||||
{
|
||||
VirtualRegion region = overlaps[i].Value;
|
||||
VirtualRegion region = overlaps[i];
|
||||
|
||||
// If the region has been fully remapped, signal that it has been mapped again.
|
||||
bool remapped = _memoryManager.IsRangeMapped(region.Address, region.Size);
|
||||
@@ -117,11 +117,11 @@ namespace Ryujinx.Memory.Tracking
|
||||
{
|
||||
NonOverlappingRangeList<VirtualRegion> regions = type == 0 ? _virtualRegions : _guestVirtualRegions;
|
||||
regions.Lock.EnterReadLock();
|
||||
Span<RangeItem<VirtualRegion>> overlaps = regions.FindOverlapsAsSpan(va, size);
|
||||
ReadOnlySpan<VirtualRegion> overlaps = regions.FindOverlapsAsSpan(va, size);
|
||||
|
||||
for (int i = 0; i < overlaps.Length; i++)
|
||||
{
|
||||
overlaps[i].Value.SignalMappingChanged(false);
|
||||
overlaps[i].SignalMappingChanged(false);
|
||||
}
|
||||
regions.Lock.ExitReadLock();
|
||||
}
|
||||
@@ -301,7 +301,7 @@ namespace Ryujinx.Memory.Tracking
|
||||
|
||||
// We use the non-span method here because keeping the lock will cause a deadlock.
|
||||
regions.Lock.EnterReadLock();
|
||||
RangeItem<VirtualRegion>[] overlaps = regions.FindOverlapsAsArray(address, size, out int length);
|
||||
VirtualRegion[] overlaps = regions.FindOverlapsAsArray(address, size, out int length);
|
||||
regions.Lock.ExitReadLock();
|
||||
|
||||
if (length == 0 && !precise)
|
||||
@@ -327,7 +327,7 @@ namespace Ryujinx.Memory.Tracking
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
VirtualRegion region = overlaps[i].Value;
|
||||
VirtualRegion region = overlaps[i];
|
||||
|
||||
if (precise)
|
||||
{
|
||||
@@ -341,7 +341,7 @@ namespace Ryujinx.Memory.Tracking
|
||||
|
||||
if (length != 0)
|
||||
{
|
||||
ArrayPool<RangeItem<VirtualRegion>>.Shared.Return(overlaps);
|
||||
ArrayPool<VirtualRegion>.Shared.Return(overlaps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Ryujinx.Memory.Tracking
|
||||
/// <summary>
|
||||
/// A region of virtual memory.
|
||||
/// </summary>
|
||||
class VirtualRegion : AbstractRegion
|
||||
class VirtualRegion : AbstractRegion<VirtualRegion>
|
||||
{
|
||||
public List<RegionHandle> Handles = [];
|
||||
|
||||
@@ -137,7 +137,7 @@ namespace Ryujinx.Memory.Tracking
|
||||
}
|
||||
}
|
||||
|
||||
public override INonOverlappingRange Split(ulong splitAddress)
|
||||
public override INonOverlappingRange<VirtualRegion> Split(ulong splitAddress)
|
||||
{
|
||||
VirtualRegion newRegion = new(_tracking, splitAddress, EndAddress - splitAddress, Guest, _lastPermission);
|
||||
Size = splitAddress - Address;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
@@ -10,23 +12,34 @@ namespace Ryujinx.UI.LocaleGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
IncrementalValuesProvider<AdditionalText> localeFile = context.AdditionalTextsProvider.Where(static x => x.Path.EndsWith("locales.json"));
|
||||
IncrementalValuesProvider<AdditionalText> localeFiles = context.AdditionalTextsProvider.Where(static x => Path.GetDirectoryName(x.Path)?.Replace('\\', '/').EndsWith("assets/Locales") ?? false);
|
||||
|
||||
IncrementalValuesProvider<string> contents = localeFile.Select((text, cancellationToken) => text.GetText(cancellationToken)!.ToString());
|
||||
IncrementalValueProvider<ImmutableArray<(string, string)>> collectedContents = localeFiles.Select((text, cancellationToken) => (text.GetText(cancellationToken)!.ToString(), Path.GetFileName(text.Path))).Collect();
|
||||
|
||||
context.RegisterSourceOutput(contents, (spc, content) =>
|
||||
context.RegisterSourceOutput(collectedContents, (spc, contents) =>
|
||||
{
|
||||
IEnumerable<string> lines = content.Split('\n').Where(x => x.Trim().StartsWith("\"ID\":")).Select(x => x.Split(':')[1].Trim().Replace("\"", string.Empty).Replace(",", string.Empty));
|
||||
|
||||
StringBuilder enumSourceBuilder = new();
|
||||
enumSourceBuilder.AppendLine("namespace Ryujinx.Ava.Common.Locale;");
|
||||
enumSourceBuilder.AppendLine("public enum LocaleKeys");
|
||||
enumSourceBuilder.AppendLine("{");
|
||||
foreach (string? line in lines)
|
||||
|
||||
foreach ((string, string) content in contents)
|
||||
{
|
||||
enumSourceBuilder.AppendLine($" {line},");
|
||||
IEnumerable<string> lines = content.Item1.Split('\n').Where(x => x.Trim().StartsWith("\"ID\":")).Select(x => x.Split(':')[1].Trim().Replace("\"", string.Empty).Replace(",", string.Empty));
|
||||
|
||||
foreach (string? line in lines)
|
||||
{
|
||||
if (content.Item2 == "Root.json")
|
||||
{
|
||||
enumSourceBuilder.AppendLine($" {line},");
|
||||
}
|
||||
else
|
||||
{
|
||||
enumSourceBuilder.AppendLine($" {content.Item2.Split('.')[0]}_{line},");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enumSourceBuilder.AppendLine("}");
|
||||
|
||||
spc.AddSource("LocaleKeys", enumSourceBuilder.ToString());
|
||||
|
||||
@@ -8,6 +8,8 @@ using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Ava.Common.Locale
|
||||
@@ -158,52 +160,86 @@ namespace Ryujinx.Ava.Common.Locale
|
||||
LocaleChanged?.Invoke();
|
||||
}
|
||||
|
||||
private static LocalesJson? _localeData;
|
||||
private static LocalesData? _localeData;
|
||||
|
||||
private static Dictionary<LocaleKeys, string> LoadJsonLanguage(string languageCode)
|
||||
{
|
||||
Dictionary<LocaleKeys, string> localeStrings = new();
|
||||
|
||||
_localeData ??= EmbeddedResources.ReadAllText("Ryujinx/Assets/Locale.json")
|
||||
.Into(it => JsonHelper.Deserialize(it, LocalesJsonContext.Default.LocalesJson));
|
||||
|
||||
foreach (LocalesEntry locale in _localeData.Value.Locales)
|
||||
if (_localeData is null)
|
||||
{
|
||||
if (locale.Translations.Count < _localeData.Value.Languages.Count)
|
||||
Dictionary<string, LocalesJson> locales = [];
|
||||
|
||||
foreach (string uri in EmbeddedResources.GetAllAvailableResources("Ryujinx/Assets/Locales", ".json"))
|
||||
{
|
||||
throw new Exception(
|
||||
$"Locale key {{{locale.ID}}} is missing languages! Has {locale.Translations.Count} translations, expected {_localeData.Value.Languages.Count}!");
|
||||
string path = uri[..^".json".Length];
|
||||
path = path.Replace('.', '/');
|
||||
path = path.Append(".json");
|
||||
|
||||
locales.TryAdd(Path.GetFileName(path), EmbeddedResources.ReadAllText(path)
|
||||
.Into(it => JsonHelper.Deserialize(it, LocalesJsonContext.Default.LocalesJson)));
|
||||
}
|
||||
|
||||
if (locale.Translations.Count > _localeData.Value.Languages.Count)
|
||||
|
||||
_localeData = new LocalesData
|
||||
{
|
||||
throw new Exception(
|
||||
$"Locale key {{{locale.ID}}} has too many languages! Has {locale.Translations.Count} translations, expected {_localeData.Value.Languages.Count}!");
|
||||
}
|
||||
Languages = EmbeddedResources.ReadAllText("Ryujinx/Assets/Languages.json")
|
||||
.Into(it => JsonHelper.Deserialize(it, LanguagesJsonContext.Default.LanguagesJson)).Languages.Keys.ToList(),
|
||||
LocalesFiles = locales
|
||||
};
|
||||
|
||||
|
||||
if (!Enum.TryParse<LocaleKeys>(locale.ID, out LocaleKeys localeKey))
|
||||
continue;
|
||||
}
|
||||
|
||||
string str = locale.Translations.TryGetValue(languageCode, out string val) && !string.IsNullOrEmpty(val)
|
||||
? val
|
||||
: locale.Translations[DefaultLanguageCode];
|
||||
|
||||
if (string.IsNullOrEmpty(str))
|
||||
foreach (LocalesJson file in _localeData.Value.LocalesFiles.Values)
|
||||
{
|
||||
foreach (LocalesEntry locale in file.Locales)
|
||||
{
|
||||
throw new Exception(
|
||||
$"Locale key '{locale.ID}' has no valid translations for desired language {languageCode}! {DefaultLanguageCode} is an empty string or null");
|
||||
}
|
||||
if (locale.Translations.Count < _localeData.Value.Languages.Count)
|
||||
{
|
||||
throw new Exception(
|
||||
$"Locale key {{{locale.ID}}} is missing languages! Has {locale.Translations.Count} translations, expected {_localeData.Value.Languages.Count}!");
|
||||
}
|
||||
|
||||
localeStrings[localeKey] = str;
|
||||
if (locale.Translations.Count > _localeData.Value.Languages.Count)
|
||||
{
|
||||
throw new Exception(
|
||||
$"Locale key {{{locale.ID}}} has too many languages! Has {locale.Translations.Count} translations, expected {_localeData.Value.Languages.Count}!");
|
||||
}
|
||||
|
||||
if (!Enum.TryParse<LocaleKeys>(locale.ID, out LocaleKeys localeKey))
|
||||
continue;
|
||||
|
||||
string str = locale.Translations.TryGetValue(languageCode, out string val) && !string.IsNullOrEmpty(val)
|
||||
? val
|
||||
: locale.Translations[DefaultLanguageCode];
|
||||
|
||||
if (string.IsNullOrEmpty(str))
|
||||
{
|
||||
throw new Exception(
|
||||
$"Locale key '{locale.ID}' has no valid translations for desired language {languageCode}! {DefaultLanguageCode} is an empty string or null");
|
||||
}
|
||||
|
||||
localeStrings[localeKey] = str;
|
||||
}
|
||||
}
|
||||
|
||||
return localeStrings;
|
||||
}
|
||||
}
|
||||
|
||||
public struct LocalesJson
|
||||
public struct LocalesData
|
||||
{
|
||||
public List<string> Languages { get; set; }
|
||||
public Dictionary<string, LocalesJson> LocalesFiles { get; set; }
|
||||
}
|
||||
|
||||
public struct LanguagesJson
|
||||
{
|
||||
public Dictionary<string, string> Languages { get; set; }
|
||||
}
|
||||
|
||||
public struct LocalesJson
|
||||
{
|
||||
public List<LocalesEntry> Locales { get; set; }
|
||||
}
|
||||
|
||||
@@ -216,4 +252,8 @@ namespace Ryujinx.Ava.Common.Locale
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSerializable(typeof(LocalesJson))]
|
||||
internal partial class LocalesJsonContext : JsonSerializerContext;
|
||||
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSerializable(typeof(LanguagesJson))]
|
||||
internal partial class LanguagesJsonContext : JsonSerializerContext;
|
||||
}
|
||||
|
||||
@@ -580,5 +580,10 @@ namespace Ryujinx.Headless
|
||||
{
|
||||
return AccountSaveDataManager.GetLastUsedUser();
|
||||
}
|
||||
|
||||
public void TakeScreenshot()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.GraphicsDriver;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.SystemInterop;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.Graphics.Vulkan.MoltenVK;
|
||||
using Ryujinx.Headless;
|
||||
using Ryujinx.SDL3.Common;
|
||||
@@ -46,7 +47,7 @@ namespace Ryujinx.Ava
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
Version = ReleaseInformation.Version;
|
||||
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041))
|
||||
@@ -55,8 +56,11 @@ namespace Ryujinx.Ava
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (Environment.CurrentDirectory.StartsWithIgnoreCase("C:\\Program Files") ||
|
||||
Environment.CurrentDirectory.StartsWithIgnoreCase("C:\\Program Files (x86)"))
|
||||
var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
|
||||
var programFilesX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
|
||||
|
||||
if (Environment.CurrentDirectory.StartsWithIgnoreCase(programFiles) ||
|
||||
Environment.CurrentDirectory.StartsWithIgnoreCase(programFilesX86))
|
||||
{
|
||||
_ = Win32NativeInterop.MessageBoxA(nint.Zero, "Ryujinx is not intended to be run from the Program Files folder. Please move it out and relaunch.", $"Ryujinx {Version}", MbIconwarning);
|
||||
return 0;
|
||||
@@ -73,11 +77,23 @@ namespace Ryujinx.Ava
|
||||
}
|
||||
}
|
||||
|
||||
bool noGuiArg = ConsumeCommandLineArgument(ref args, "--no-gui") || ConsumeCommandLineArgument(ref args, "nogui");
|
||||
bool coreDumpArg = ConsumeCommandLineArgument(ref args, "--core-dumps");
|
||||
|
||||
// TODO: Ryujinx causes core dumps on Linux when it exits "uncleanly", eg. through an unhandled exception.
|
||||
// This is undesirable and causes very odd behavior during development (the process stops responding,
|
||||
// the .NET debugger freezes or suddenly detaches, /tmp/ gets filled etc.), unless explicitly requested by the user.
|
||||
// This needs to be investigated, but calling prctl() is better than modifying system-wide settings or leaving this be.
|
||||
if (!coreDumpArg)
|
||||
{
|
||||
OsUtils.SetCoreDumpable(false);
|
||||
}
|
||||
|
||||
PreviewerDetached = true;
|
||||
|
||||
if (args.Length > 0 && args[0] is "--no-gui" or "nogui")
|
||||
if (noGuiArg)
|
||||
{
|
||||
HeadlessRyujinx.Entrypoint(args[1..]);
|
||||
HeadlessRyujinx.Entrypoint(args);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -112,6 +128,14 @@ namespace Ryujinx.Ava
|
||||
: [Win32RenderingMode.Software]
|
||||
});
|
||||
|
||||
private static bool ConsumeCommandLineArgument(ref string[] args, string targetArgument)
|
||||
{
|
||||
List<string> argList = [.. args];
|
||||
bool found = argList.Remove(targetArgument);
|
||||
args = argList.ToArray();
|
||||
return found;
|
||||
}
|
||||
|
||||
private static void Initialize(string[] args)
|
||||
{
|
||||
// Ensure Discord presence timestamp begins at the absolute start of when Ryujinx is launched
|
||||
@@ -177,7 +201,6 @@ namespace Ryujinx.Ava
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static string GetDirGameUserConfig(string gameId, bool changeFolderForGame = false)
|
||||
{
|
||||
if (string.IsNullOrEmpty(gameId))
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\locales.json" />
|
||||
<None Remove="Assets\**\*.json" />
|
||||
<None Remove="Assets\Styles\Styles.xaml" />
|
||||
<None Remove="Assets\Styles\Themes.xaml" />
|
||||
<None Remove="Assets\Icons\Controller_JoyConLeft.svg" />
|
||||
@@ -156,8 +156,8 @@
|
||||
<EmbeddedResource Include="..\..\docs\compatibility.csv" LogicalName="RyujinxGameCompatibilityList">
|
||||
<Link>Assets\RyujinxGameCompatibility.csv</Link>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="..\..\assets\locales.json">
|
||||
<Link>Assets\Locale.json</Link>
|
||||
<EmbeddedResource Include="..\..\assets\**\*.json">
|
||||
<LinkBase>Assets</LinkBase>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Assets\Styles\Styles.xaml" />
|
||||
<EmbeddedResource Include="Assets\Icons\Controller_JoyConLeft.svg" />
|
||||
@@ -178,6 +178,6 @@
|
||||
<EmbeddedResource Include="Assets\UIImages\Logo_Ryujinx_AntiAlias.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="..\..\assets\locales.json" />
|
||||
<AdditionalFiles Include="..\..\assets\Locales\*.json" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Ava.Systems.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<AudioBackend>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<AudioBackend>))]
|
||||
public enum AudioBackend
|
||||
{
|
||||
Dummy,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.HOS.SystemState;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Ava.Systems.Configuration.System
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<Language>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<Language>))]
|
||||
public enum Language
|
||||
{
|
||||
Japanese,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.HOS.SystemState;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Ava.Systems.Configuration.System
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<Region>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<Region>))]
|
||||
public enum Region
|
||||
{
|
||||
Japan,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Ava.Systems.Configuration.UI
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<FocusLostType>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<FocusLostType>))]
|
||||
public enum FocusLostType
|
||||
{
|
||||
DoNothing,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Ava.Systems.Configuration.UI
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<UpdaterType>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<UpdaterType>))]
|
||||
public enum UpdaterType
|
||||
{
|
||||
Off,
|
||||
|
||||
@@ -327,5 +327,10 @@ namespace Ryujinx.Ava.UI.Applet
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
public void TakeScreenshot()
|
||||
{
|
||||
_parent.ViewModel.AppHost.ScreenshotRequested = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,8 @@ namespace Ryujinx.Ava
|
||||
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
// Switches macOS key held behavior to repeat the input key instead of showing the character accents menu (like doing on an iOS keyboard would).
|
||||
// https://macos-defaults.com/keyboard/applepressandholdenabled.html
|
||||
Process.Start("/usr/bin/defaults", "write org.ryujinx.Ryujinx ApplePressAndHoldEnabled -bool false");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1333,7 +1333,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public void TakeScreenshot() => AppHost.ScreenshotRequested = true;
|
||||
public void TakeScreenshot()
|
||||
{
|
||||
AppHost.ScreenshotRequested = true;
|
||||
}
|
||||
|
||||
public void HideUi() => ShowMenuAndStatusBar = false;
|
||||
|
||||
|
||||
@@ -85,29 +85,16 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
|
||||
private static IEnumerable<MenuItem> GenerateLanguageMenuItems()
|
||||
{
|
||||
const string LocalePath = "Ryujinx/Assets/Locale.json";
|
||||
const string LanguagesPath = "Ryujinx/Assets/Languages.json";
|
||||
|
||||
string languageJson = EmbeddedResources.ReadAllText(LocalePath);
|
||||
string languageJson = EmbeddedResources.ReadAllText(LanguagesPath);
|
||||
string currentLanguageCode = LocaleManager.Instance.CurrentLanguageCode;
|
||||
|
||||
LocalesJson locales = JsonHelper.Deserialize(languageJson, LocalesJsonContext.Default.LocalesJson);
|
||||
LanguagesJson languages = JsonHelper.Deserialize(languageJson, LanguagesJsonContext.Default.LanguagesJson);
|
||||
|
||||
foreach (string language in locales.Languages)
|
||||
foreach ((string code, string language) in languages.Languages)
|
||||
{
|
||||
int index = locales.Locales.FindIndex(x => x.ID == "Language");
|
||||
string languageName;
|
||||
|
||||
if (index == -1)
|
||||
{
|
||||
languageName = language;
|
||||
}
|
||||
else
|
||||
{
|
||||
string tr = locales.Locales[index].Translations[language];
|
||||
languageName = string.IsNullOrEmpty(tr)
|
||||
? language
|
||||
: tr;
|
||||
}
|
||||
string languageName = string.IsNullOrEmpty(language) ? code : language;
|
||||
|
||||
MenuItem menuItem = new()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user