mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-07-23 14:19:52 -06:00
Compare commits
86 Commits
gitlab-rel
...
ea4d99349b
Author | SHA1 | Date | |
---|---|---|---|
ea4d99349b | |||
1b37038d5a | |||
cd2a7c9916 | |||
669179ca2e | |||
b1c1ad54e8 | |||
c4abaa6cf2 | |||
c5c8647de7 | |||
838296ccb6 | |||
009d319bc2 | |||
7d189ab2c0 | |||
bad1dd8899 | |||
7d5f7bc479 | |||
44f4e9af51 | |||
fe02ff3a3a | |||
9506eba982 | |||
5cad23f793 | |||
fb1655f1ad | |||
f630d5ba99 | |||
bc68502179 | |||
e547c4f5f4 | |||
36bb910e67 | |||
785641a402 | |||
d81dca0dcc | |||
737afbfa2f | |||
8682c51ef7 | |||
bbbbaaa669 | |||
890165707a | |||
0cc94fdf37 | |||
74a9b94227 | |||
d3208a4c44 | |||
5d136980a3 | |||
572ad1eac5 | |||
6bb2af0091 | |||
534a194ed9 | |||
331805791e | |||
6773406bb6 | |||
6226eadf55 | |||
b1cde5fd97 | |||
39944b2063 | |||
973c6ba5df | |||
6803c91da8 | |||
557c2a50b2 | |||
77a797f154 | |||
faf9e3cdd7 | |||
7bc80ed4fe | |||
a1d44ec496 | |||
bab3beb0ac | |||
aa9e74339b | |||
908273d848 | |||
b51ad11574 | |||
ea027d65a7 | |||
d03ae9c164 | |||
90e9492f6c | |||
512120db04 | |||
90582e9e93 | |||
b97fae08b5 | |||
eed6ef632d | |||
0409c15903 | |||
c58272ac20 | |||
9d83dfd19c | |||
ce31a47934 | |||
d31d1f91cf | |||
ef02194a77 | |||
a16764d191 | |||
5108ab790f | |||
71dc71fee8 | |||
c95bf748b2 | |||
b5e9acc50b | |||
e3fba4e32f | |||
efa25d471e | |||
b37aa61e47 | |||
8feeb977b7 | |||
b761a2c86d | |||
693837dca7 | |||
70abff072b | |||
1e861b99a9 | |||
13e404bde0 | |||
04561a0cd3 | |||
0652d7e740 | |||
f2aea4fb22 | |||
3950e8adff | |||
0e84f2b1f0 | |||
051c794cc4 | |||
053a9cb549 | |||
d688fed7d2 | |||
8f5102aa2a |
171
.github/workflows/canary.yml
vendored
171
.github/workflows/canary.yml
vendored
@ -24,54 +24,6 @@ env:
|
|||||||
RELEASE: 1
|
RELEASE: 1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tag:
|
|
||||||
name: Create tag
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
steps:
|
|
||||||
- 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
|
|
||||||
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 GitLab tag
|
|
||||||
run: gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=CreateTag "Canary-${{ steps.version_info.outputs.build_version }}|master"
|
|
||||||
|
|
||||||
- name: Create release
|
|
||||||
uses: ncipollo/release-action@v1
|
|
||||||
with:
|
|
||||||
name: "Canary ${{ steps.version_info.outputs.build_version }}"
|
|
||||||
tag: ${{ steps.version_info.outputs.build_version }}
|
|
||||||
body: |
|
|
||||||
# Canary builds:
|
|
||||||
|
|
||||||
These builds are experimental and may sometimes not work, use [regular builds](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_STABLE_NAME }}/releases/latest) instead if that sounds like something you don't want to deal with.
|
|
||||||
|
|
||||||
| Platform | Artifact |
|
|
||||||
|--|--|
|
|
||||||
| Windows 64-bit | [Canary Windows Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_CANARY_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-win_x64.zip) |
|
|
||||||
| Windows ARM 64-bit | [Canary Windows ARM Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_CANARY_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-win_arm64.zip) |
|
|
||||||
| Linux 64-bit | [Canary Linux Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_CANARY_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz) |
|
|
||||||
| Linux ARM 64-bit | [Canary Linux ARM Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_CANARY_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-linux_arm64.tar.gz) |
|
|
||||||
| macOS | [Canary macOS Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_CANARY_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz) |
|
|
||||||
|
|
||||||
**[Full Changelog](https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }})**
|
|
||||||
omitBodyDuringUpdate: true
|
|
||||||
owner: ${{ secrets.RC_OWNER }}
|
|
||||||
repo: ${{ secrets.RC_CANARY_NAME }}
|
|
||||||
token: ${{ secrets.ALT_RELEASE_TOKEN }}
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
name: Release for ${{ matrix.platform.name }}
|
name: Release for ${{ matrix.platform.name }}
|
||||||
runs-on: ${{ matrix.platform.os }}
|
runs-on: ${{ matrix.platform.os }}
|
||||||
@ -79,7 +31,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
platform:
|
platform:
|
||||||
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
||||||
- { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 }
|
#- { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 }
|
||||||
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
||||||
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
||||||
steps:
|
steps:
|
||||||
@ -106,7 +58,7 @@ jobs:
|
|||||||
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_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_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
|
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||||
sed -r --in-place '/^Name=Ryujinx$/s/Name=Ryujinx/Name=Ryujinx-Canary/' distribution/linux/Ryujinx.desktop
|
sed -r --in-place '/^Name=Ryujinx$/s/Name=Ryujinx/Name=Ryujinx-Canary/' distribution/linux/Ryujinx.desktop
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Create output dir
|
- name: Create output dir
|
||||||
@ -123,7 +75,24 @@ jobs:
|
|||||||
rm libarmeilleure-jitsupport.dylib
|
rm libarmeilleure-jitsupport.dylib
|
||||||
7z a ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
7z a ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
||||||
popd
|
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"
|
||||||
shell: bash
|
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
|
- name: Packing Linux builds
|
||||||
if: matrix.platform.os == 'ubuntu-latest'
|
if: matrix.platform.os == 'ubuntu-latest'
|
||||||
@ -133,6 +102,8 @@ jobs:
|
|||||||
chmod +x Ryujinx.sh Ryujinx
|
chmod +x Ryujinx.sh Ryujinx
|
||||||
tar -czvf ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
tar -czvf ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
||||||
popd
|
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"
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Build AppImage (Linux)
|
- name: Build AppImage (Linux)
|
||||||
@ -169,35 +140,11 @@ jobs:
|
|||||||
pushd publish_appimage
|
pushd publish_appimage
|
||||||
mv Ryujinx.AppImage ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage
|
mv Ryujinx.AppImage ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||||
mv Ryujinx.AppImage.zsync ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
|
mv Ryujinx.AppImage.zsync ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
|
||||||
popd
|
popd
|
||||||
shell: bash
|
|
||||||
|
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"
|
||||||
- name: Pushing new release
|
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.zsync"
|
||||||
uses: ncipollo/release-action@v1
|
shell: bash
|
||||||
with:
|
|
||||||
name: ${{ steps.version_info.outputs.build_version }}
|
|
||||||
artifacts: "release_output/*.tar.gz,release_output/*.zip,release_output/*AppImage*"
|
|
||||||
tag: ${{ steps.version_info.outputs.build_version }}
|
|
||||||
body: |
|
|
||||||
# Canary builds:
|
|
||||||
|
|
||||||
These builds are experimental and may sometimes not work, use [regular builds](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_STABLE_NAME }}/releases/latest) instead if that sounds like something you don't want to deal with.
|
|
||||||
|
|
||||||
| Platform | Artifact |
|
|
||||||
|--|--|
|
|
||||||
| Windows 64-bit | [Canary Windows Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_CANARY_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-win_x64.zip) |
|
|
||||||
| Windows ARM 64-bit | [Canary Windows ARM Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_CANARY_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-win_arm64.zip) |
|
|
||||||
| Linux 64-bit | [Canary Linux Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_CANARY_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz) |
|
|
||||||
| Linux ARM 64-bit | [Canary Linux ARM Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_CANARY_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-linux_arm64.tar.gz) |
|
|
||||||
| macOS | [Canary macOS Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_CANARY_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz) |
|
|
||||||
|
|
||||||
**[Full Changelog](https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }})**
|
|
||||||
omitBodyDuringUpdate: true
|
|
||||||
allowUpdates: true
|
|
||||||
replacesArtifacts: true
|
|
||||||
owner: ${{ secrets.RC_OWNER }}
|
|
||||||
repo: ${{ secrets.RC_CANARY_NAME }}
|
|
||||||
token: ${{ secrets.ALT_RELEASE_TOKEN }}
|
|
||||||
|
|
||||||
macos_release:
|
macos_release:
|
||||||
name: Release MacOS universal
|
name: Release MacOS universal
|
||||||
@ -214,6 +161,16 @@ jobs:
|
|||||||
wget https://apt.llvm.org/llvm.sh
|
wget https://apt.llvm.org/llvm.sh
|
||||||
chmod +x llvm.sh
|
chmod +x llvm.sh
|
||||||
sudo ./llvm.sh 17
|
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
|
- name: Install rcodesign
|
||||||
run: |
|
run: |
|
||||||
@ -246,17 +203,47 @@ jobs:
|
|||||||
- name: Publish macOS Ryujinx
|
- name: Publish macOS Ryujinx
|
||||||
run: |
|
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
|
./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"
|
||||||
|
|
||||||
- name: Pushing new release
|
create_gitlab_release:
|
||||||
uses: ncipollo/release-action@v1
|
name: Create GitLab Release
|
||||||
with:
|
runs-on: ubuntu-24.04
|
||||||
name: "Canary ${{ steps.version_info.outputs.build_version }}"
|
needs:
|
||||||
artifacts: "publish_ava/*.tar.gz"
|
- macos_release
|
||||||
tag: ${{ steps.version_info.outputs.build_version }}
|
- release
|
||||||
body: ""
|
steps:
|
||||||
omitBodyDuringUpdate: true
|
- uses: actions/checkout@v4
|
||||||
allowUpdates: true
|
|
||||||
replacesArtifacts: true
|
- name: Get version info
|
||||||
owner: ${{ secrets.RC_OWNER }}
|
id: version_info
|
||||||
repo: ${{ secrets.RC_CANARY_NAME }}
|
run: |
|
||||||
token: ${{ secrets.ALT_RELEASE_TOKEN }}
|
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
|
||||||
|
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 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 }}"
|
||||||
|
|
||||||
|
- 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 }})"
|
||||||
|
|
||||||
|
- 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"
|
||||||
|
|
||||||
|
- 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 }}'
|
||||||
|
33
.github/workflows/debug_release.yml
vendored
33
.github/workflows/debug_release.yml
vendored
@ -67,8 +67,21 @@ jobs:
|
|||||||
|
|
||||||
gh release download -R GreemDev/GLI -O gli.exe -p 'GitLabCli-win_x64.exe'
|
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.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
|
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
|
- name: Packing Linux builds
|
||||||
if: matrix.platform.os == 'ubuntu-latest'
|
if: matrix.platform.os == 'ubuntu-latest'
|
||||||
@ -79,13 +92,7 @@ jobs:
|
|||||||
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
||||||
popd
|
popd
|
||||||
|
|
||||||
mkdir -p $HOME/.bin
|
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"
|
||||||
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
|
||||||
chmod +x gli
|
|
||||||
mv gli $HOME/.bin/
|
|
||||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
|
||||||
|
|
||||||
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
|
shell: bash
|
||||||
|
|
||||||
- name: Build AppImage (Linux)
|
- name: Build AppImage (Linux)
|
||||||
@ -124,8 +131,8 @@ jobs:
|
|||||||
mv Ryujinx.AppImage.zsync ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
|
mv Ryujinx.AppImage.zsync ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
|
||||||
popd
|
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"
|
||||||
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"
|
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
|
shell: bash
|
||||||
|
|
||||||
macos_release:
|
macos_release:
|
||||||
@ -183,7 +190,7 @@ jobs:
|
|||||||
- name: Publish macOS Ryujinx
|
- name: Publish macOS Ryujinx
|
||||||
run: |
|
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
|
./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_ava/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz"
|
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:
|
create_gitlab_release:
|
||||||
name: Create GitLab Release
|
name: Create GitLab Release
|
||||||
@ -192,6 +199,8 @@ jobs:
|
|||||||
- macos_release
|
- macos_release
|
||||||
- release
|
- release
|
||||||
steps:
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Get version info
|
- name: Get version info
|
||||||
id: version_info
|
id: version_info
|
||||||
run: |
|
run: |
|
||||||
@ -212,4 +221,4 @@ jobs:
|
|||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
run: |
|
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"
|
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"
|
||||||
|
157
.github/workflows/release.yml
vendored
157
.github/workflows/release.yml
vendored
@ -14,51 +14,6 @@ env:
|
|||||||
RELEASE: 1
|
RELEASE: 1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tag:
|
|
||||||
name: Create tag
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
steps:
|
|
||||||
- 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
|
|
||||||
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 GitLab tag
|
|
||||||
run: gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=CreateTag "${{ steps.version_info.outputs.build_version }}|master"
|
|
||||||
|
|
||||||
- name: Create release
|
|
||||||
uses: ncipollo/release-action@v1
|
|
||||||
with:
|
|
||||||
name: ${{ steps.version_info.outputs.build_version }}
|
|
||||||
tag: ${{ steps.version_info.outputs.build_version }}
|
|
||||||
body: |
|
|
||||||
# Stable builds:
|
|
||||||
| Platform | Artifact |
|
|
||||||
|--|--|
|
|
||||||
| Windows 64-bit | [Stable Windows Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_STABLE_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip) |
|
|
||||||
| Windows ARM 64-bit | [Stable Windows ARM Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_STABLE_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-win_arm64.zip) |
|
|
||||||
| Linux 64-bit | [Stable Linux Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_STABLE_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz) |
|
|
||||||
| Linux ARM 64-bit | [Stable Linux ARM Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_STABLE_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_arm64.tar.gz) |
|
|
||||||
| macOS | [Stable macOS Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_STABLE_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz) |
|
|
||||||
|
|
||||||
**[Full Changelog](https://git.ryujinx.app/ryubing/ryujinx/-/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }})**
|
|
||||||
omitBodyDuringUpdate: true
|
|
||||||
owner: ${{ secrets.RC_OWNER }}
|
|
||||||
repo: ${{ secrets.RC_STABLE_NAME }}
|
|
||||||
token: ${{ secrets.ALT_RELEASE_TOKEN }}
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
name: Release for ${{ matrix.platform.name }}
|
name: Release for ${{ matrix.platform.name }}
|
||||||
runs-on: ${{ matrix.platform.os }}
|
runs-on: ${{ matrix.platform.os }}
|
||||||
@ -66,7 +21,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
platform:
|
platform:
|
||||||
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
||||||
- { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 }
|
#- { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 }
|
||||||
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
||||||
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
||||||
steps:
|
steps:
|
||||||
@ -109,7 +64,24 @@ jobs:
|
|||||||
rm libarmeilleure-jitsupport.dylib
|
rm libarmeilleure-jitsupport.dylib
|
||||||
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
||||||
popd
|
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
|
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
|
- name: Packing Linux builds
|
||||||
if: matrix.platform.os == 'ubuntu-latest'
|
if: matrix.platform.os == 'ubuntu-latest'
|
||||||
@ -119,7 +91,11 @@ jobs:
|
|||||||
chmod +x Ryujinx.sh Ryujinx
|
chmod +x Ryujinx.sh Ryujinx
|
||||||
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
||||||
popd
|
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
|
shell: bash
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build AppImage (Linux)
|
- name: Build AppImage (Linux)
|
||||||
if: matrix.platform.os == 'ubuntu-latest'
|
if: matrix.platform.os == 'ubuntu-latest'
|
||||||
@ -156,32 +132,11 @@ jobs:
|
|||||||
mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.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
|
mv Ryujinx.AppImage.zsync ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
|
||||||
popd
|
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
|
shell: bash
|
||||||
|
|
||||||
- name: Pushing new release
|
|
||||||
uses: ncipollo/release-action@v1
|
|
||||||
with:
|
|
||||||
name: ${{ steps.version_info.outputs.build_version }}
|
|
||||||
artifacts: "release_output/*.tar.gz,release_output/*.zip,release_output/*AppImage*"
|
|
||||||
tag: ${{ steps.version_info.outputs.build_version }}
|
|
||||||
body: |
|
|
||||||
# Stable builds:
|
|
||||||
| Platform | Artifact |
|
|
||||||
|--|--|
|
|
||||||
| Windows 64-bit | [Stable Windows Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_STABLE_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip) |
|
|
||||||
| Windows ARM 64-bit | [Stable Windows ARM Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_STABLE_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-win_arm64.zip) |
|
|
||||||
| Linux 64-bit | [Stable Linux Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_STABLE_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz) |
|
|
||||||
| Linux ARM 64-bit | [Stable Linux ARM Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_STABLE_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_arm64.tar.gz) |
|
|
||||||
| macOS | [Stable macOS Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_STABLE_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz) |
|
|
||||||
|
|
||||||
**[Full Changelog](https://git.ryujinx.app/ryubing/ryujinx/-/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }})**
|
|
||||||
omitBodyDuringUpdate: true
|
|
||||||
allowUpdates: true
|
|
||||||
replacesArtifacts: true
|
|
||||||
owner: ${{ secrets.RC_OWNER }}
|
|
||||||
repo: ${{ secrets.RC_STABLE_NAME }}
|
|
||||||
token: ${{ secrets.ALT_RELEASE_TOKEN }}
|
|
||||||
|
|
||||||
macos_release:
|
macos_release:
|
||||||
name: Release MacOS universal
|
name: Release MacOS universal
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
@ -197,6 +152,16 @@ jobs:
|
|||||||
wget https://apt.llvm.org/llvm.sh
|
wget https://apt.llvm.org/llvm.sh
|
||||||
chmod +x llvm.sh
|
chmod +x llvm.sh
|
||||||
sudo ./llvm.sh 17
|
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
|
- name: Install rcodesign
|
||||||
run: |
|
run: |
|
||||||
@ -227,17 +192,43 @@ jobs:
|
|||||||
- name: Publish macOS Ryujinx
|
- name: Publish macOS Ryujinx
|
||||||
run: |
|
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
|
./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 }}" >> $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: Pushing new release
|
- name: Install GitLabCli
|
||||||
uses: ncipollo/release-action@v1
|
run: |
|
||||||
with:
|
mkdir -p $HOME/.bin
|
||||||
name: ${{ steps.version_info.outputs.build_version }}
|
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||||
artifacts: "publish/*.tar.gz"
|
chmod +x gli
|
||||||
tag: ${{ steps.version_info.outputs.build_version }}
|
mv gli $HOME/.bin/
|
||||||
body: ""
|
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||||
omitBodyDuringUpdate: true
|
env:
|
||||||
allowUpdates: true
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
replacesArtifacts: true
|
|
||||||
owner: ${{ secrets.RC_OWNER }}
|
- name: Create release
|
||||||
repo: ${{ secrets.RC_STABLE_NAME }}
|
run: |
|
||||||
token: ${{ secrets.ALT_RELEASE_TOKEN }}
|
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 }}"
|
||||||
|
|
||||||
|
- 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"
|
||||||
|
|
||||||
|
- 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 }}'
|
||||||
|
@ -2,20 +2,17 @@
|
|||||||
|
|
||||||
All updates to this Ryujinx branch will be documented in this file.
|
All updates to this Ryujinx branch will be documented in this file.
|
||||||
|
|
||||||
|
## [1.3.2](<https://git.ryujinx.app/ryubing/ryujinx/-/releases/1.3.2>) - 2025-06-09
|
||||||
|
|
||||||
## [1.3.1](<https://git.ryujinx.app/ryubing/ryujinx/-/releases/1.3.1>) - 2025-04-23
|
## [1.3.1](<https://git.ryujinx.app/ryubing/ryujinx/-/releases/1.3.1>) - 2025-04-23
|
||||||
A list of notable changes can be found on the release linked in the version number above.
|
|
||||||
|
|
||||||
## [1.2.86](<https://github.com/Ryubing/Stable-Releases/releases/tag/1.2.86>) - 2025-03-13
|
## [1.2.86](<https://github.com/Ryubing/Stable-Releases/releases/tag/1.2.86>) - 2025-03-13
|
||||||
A list of notable changes can be found on the release linked in the version number above.
|
|
||||||
|
|
||||||
## [1.2.82](<https://web.archive.org/web/20250312010534/https://github.com/Ryubing/Ryujinx/releases/tag/1.2.82>) - 2025-02-16
|
## [1.2.82](<https://web.archive.org/web/20250312010534/https://github.com/Ryubing/Ryujinx/releases/tag/1.2.82>) - 2025-02-16
|
||||||
A list of notable changes can be found on the release linked in the version number above.
|
|
||||||
|
|
||||||
## [1.2.80-81](<https://web.archive.org/web/20250302064257/https://github.com/Ryubing/Ryujinx/releases/tag/1.2.81>) - 2025-01-22
|
## [1.2.80-81](<https://web.archive.org/web/20250302064257/https://github.com/Ryubing/Ryujinx/releases/tag/1.2.81>) - 2025-01-22
|
||||||
A list of notable changes can be found on the release linked in the version number above.
|
|
||||||
|
|
||||||
## [1.2.78](<https://web.archive.org/web/20250301174537/https://github.com/Ryubing/Ryujinx/releases/tag/1.2.78>) - 2024-12-19
|
## [1.2.78](<https://web.archive.org/web/20250301174537/https://github.com/Ryubing/Ryujinx/releases/tag/1.2.78>) - 2024-12-19
|
||||||
A list of notable changes can be found on the release linked in the version number above.
|
|
||||||
|
|
||||||
## [1.2.73-1.2.76](<https://web.archive.org/web/20250209202612/https://github.com/Ryubing/Ryujinx/releases/tag/1.2.76>) - 2024-11-19
|
## [1.2.73-1.2.76](<https://web.archive.org/web/20250209202612/https://github.com/Ryubing/Ryujinx/releases/tag/1.2.76>) - 2024-11-19
|
||||||
A list of notable changes can be found on the release linked in the version number above.
|
A list of notable changes can be found on the release linked in the version number above.
|
||||||
@ -254,4 +251,4 @@ Added Low-power PPTC mode strings to the translation files.
|
|||||||
- Autoload DLC/Updates from dir ([#12](https://github.com/GreemDev/Ryujinx/pull/12)).
|
- Autoload DLC/Updates from dir ([#12](https://github.com/GreemDev/Ryujinx/pull/12)).
|
||||||
- Changed executable icon to rainbow logo.
|
- Changed executable icon to rainbow logo.
|
||||||
- Extract Data > Logo now also extracts the square thumbnail you see for the game in the UI.
|
- Extract Data > Logo now also extracts the square thumbnail you see for the game in the UI.
|
||||||
- The "use random UUID hack" checkbox in the Amiibo screen now remembers its last state when you reopen the window in a given session.
|
- The "use random UUID hack" checkbox in the Amiibo screen now remembers its last state when you reopen the window in a given session.
|
||||||
|
@ -40,8 +40,10 @@
|
|||||||
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
||||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" Version="6.1.2-build3" />
|
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" Version="6.1.2-build3" />
|
||||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||||
<PackageVersion Include="Ryujinx.LibHac" Version="0.20.0-alpha.107" />
|
<PackageVersion Include="Ryujinx.LibHac" Version="0.20.0" />
|
||||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
|
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
|
||||||
|
<PackageVersion Include="Ryujinx.UpdateClient" Version="1.0.29" />
|
||||||
|
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="1.0.29" />
|
||||||
<PackageVersion Include="Gommon" Version="2.7.1.1" />
|
<PackageVersion Include="Gommon" Version="2.7.1.1" />
|
||||||
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
||||||
<PackageVersion Include="Sep" Version="0.6.0" />
|
<PackageVersion Include="Sep" Version="0.6.0" />
|
||||||
|
14
README.md
14
README.md
@ -7,8 +7,8 @@
|
|||||||
|
|
||||||
# Ryujinx
|
# Ryujinx
|
||||||
|
|
||||||
[](https://github.com/Ryubing/Stable-Releases/releases/latest)
|
[](https://git.ryujinx.app/ryubing/ryujinx/-/releases)
|
||||||
[](https://github.com/Ryubing/Canary-Releases/releases/latest)
|
[](https://git.ryujinx.app/ryubing/canary/-/releases)
|
||||||
<br>
|
<br>
|
||||||
<a href="https://discord.gg/PEuzjrFXUA">
|
<a href="https://discord.gg/PEuzjrFXUA">
|
||||||
<img src="https://img.shields.io/discord/1294443224030511104?color=5865F2&label=Ryubing&logo=discord&logoColor=white" alt="Discord">
|
<img src="https://img.shields.io/discord/1294443224030511104?color=5865F2&label=Ryubing&logo=discord&logoColor=white" alt="Discord">
|
||||||
@ -31,7 +31,7 @@
|
|||||||
<br>
|
<br>
|
||||||
This is not a Ryujinx revival project. This is not a Phoenix project.
|
This is not a Ryujinx revival project. This is not a Phoenix project.
|
||||||
<br>
|
<br>
|
||||||
Guides and documentation can be found on the <a href="https://git.ryujinx.app/ryubing/ryujinx/-/wikis/home">Wiki tab</a>.
|
Guides and documentation can be found on the <a href="https://git.ryujinx.app/groups/ryubing/-/wikis/home">Wiki tab</a>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@ -49,13 +49,13 @@ Stable builds are made every so often, based on the `master` branch, that then g
|
|||||||
These stable builds exist so that the end user can get a more **enjoyable and stable experience**.
|
These stable builds exist so that the end user can get a more **enjoyable and stable experience**.
|
||||||
They are released every month or so, to ensure consistent updates, while not being an annoying amount of individual updates to download over the course of that month.
|
They are released every month or so, to ensure consistent updates, while not being an annoying amount of individual updates to download over the course of that month.
|
||||||
|
|
||||||
You can find the latest stable release [here](https://github.com/Ryubing/Stable-Releases/releases/latest).
|
You can find the stable releases [here](https://git.ryujinx.app/ryubing/ryujinx/-/releases).
|
||||||
|
|
||||||
Canary builds are compiled automatically for each commit on the `master` branch.
|
Canary builds are compiled automatically for each commit on the `master` branch.
|
||||||
While we strive to ensure optimal stability and performance prior to pushing an update, these builds **may be unstable or completely broken**.
|
While we strive to ensure optimal stability and performance prior to pushing an update, these builds **may be unstable or completely broken**.
|
||||||
These canary builds are only recommended for experienced users.
|
These canary builds are only recommended for experienced users.
|
||||||
|
|
||||||
You can find the latest canary release [here](https://github.com/Ryubing/Canary-Releases/releases/latest).
|
You can find the canary releases [here](https://git.ryujinx.app/ryubing/canary/-/releases).
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
@ -111,7 +111,7 @@ See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](distribution/legal/THIRDPARTY
|
|||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
- [LibHac](https://github.com/Thealexbarney/LibHac) is used for our file-system.
|
- [LibHac](https://git.ryujinx.app/ryubing/libhac) is used for our file-system.
|
||||||
- [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation.
|
- [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation.
|
||||||
- [ldn_mitm](https://github.com/spacemeowx2/ldn_mitm) is used for one of our available multiplayer modes.
|
- [ldn_mitm](https://github.com/spacemeowx2/ldn_mitm) is used for one of our available multiplayer modes.
|
||||||
- [ShellLink](https://github.com/securifybv/ShellLink) is used for Windows shortcut generation.
|
- [ShellLink](https://github.com/securifybv/ShellLink) is used for Windows shortcut generation.
|
@ -77,6 +77,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Gene
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.BuildValidationTasks", "src\Ryujinx.BuildValidationTasks\Ryujinx.BuildValidationTasks.csproj", "{4A89A234-4F19-497D-A576-DDE8CDFC5B22}"
|
||||||
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
.editorconfig = .editorconfig
|
.editorconfig = .editorconfig
|
||||||
@ -84,10 +86,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||||||
.github\workflows\canary.yml = .github\workflows\canary.yml
|
.github\workflows\canary.yml = .github\workflows\canary.yml
|
||||||
Directory.Packages.props = Directory.Packages.props
|
Directory.Packages.props = Directory.Packages.props
|
||||||
.github\workflows\release.yml = .github\workflows\release.yml
|
.github\workflows\release.yml = .github\workflows\release.yml
|
||||||
|
nuget.config = nuget.config
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.BuildValidationTasks", "src\Ryujinx.BuildValidationTasks\Ryujinx.BuildValidationTasks.csproj", "{4A89A234-4F19-497D-A576-DDE8CDFC5B22}"
|
|
||||||
EndProject
|
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
1236
assets/locales.json
1236
assets/locales.json
File diff suppressed because it is too large
Load Diff
@ -33,23 +33,29 @@ echo -n "APPL????" > "$APP_BUNDLE_DIRECTORY/Contents/PkgInfo"
|
|||||||
echo "Running bundle fix up python script"
|
echo "Running bundle fix up python script"
|
||||||
python3 bundle_fix_up.py "$APP_BUNDLE_DIRECTORY" MacOS/Ryujinx
|
python3 bundle_fix_up.py "$APP_BUNDLE_DIRECTORY" MacOS/Ryujinx
|
||||||
|
|
||||||
# Resign all dyplib files as ad-hoc after changing them
|
|
||||||
find "$APP_BUNDLE_DIRECTORY/Contents/Frameworks" -type f -name "*.dylib" -exec codesign --force --sign - {} \;
|
|
||||||
|
|
||||||
# Now sign it
|
# Now sign it
|
||||||
echo "Starting signing process"
|
echo "Starting signing process"
|
||||||
if ! [ -x "$(command -v codesign)" ];
|
if ! [ -x "$(command -v codesign)" ];
|
||||||
then
|
then
|
||||||
if ! [ -x "$(command -v rcodesign)" ];
|
if ! [ -x "$(command -v rcodesign)" ];
|
||||||
then
|
then
|
||||||
echo "Cannot find rcodesign on your system, please install rcodesign."
|
echo "Cannot find rcodesign on your system, please install rcodesign and ensure it is in your search path."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# cargo install apple-codesign
|
|
||||||
echo "Using rcodesign for ad-hoc signing"
|
echo "Using rcodesign for ad-hoc signing"
|
||||||
|
|
||||||
|
echo "Resigning all frameworks dylib files as ad-hoc"
|
||||||
|
find "$APP_BUNDLE_DIRECTORY/Contents/Frameworks" -type f -name "*.dylib" -exec rcodesign sign {} \;
|
||||||
|
|
||||||
|
echo "Signing app bundle as ad-hoc"
|
||||||
rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$APP_BUNDLE_DIRECTORY"
|
rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$APP_BUNDLE_DIRECTORY"
|
||||||
else
|
else
|
||||||
echo "Using codesign for ad-hoc signing"
|
echo "Using codesign for ad-hoc signing"
|
||||||
|
|
||||||
|
echo "Resigning all frameworks dylib files as ad-hoc"
|
||||||
|
find "$APP_BUNDLE_DIRECTORY/Contents/Frameworks" -type f -name "*.dylib" -exec codesign --force --sign - {} \;
|
||||||
|
|
||||||
|
echo "Signing app bundle as ad-hoc"
|
||||||
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f -s - "$APP_BUNDLE_DIRECTORY"
|
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f -s - "$APP_BUNDLE_DIRECTORY"
|
||||||
fi
|
fi
|
||||||
|
@ -20,6 +20,18 @@ SOURCE_REVISION_ID=$6
|
|||||||
CONFIGURATION=$7
|
CONFIGURATION=$7
|
||||||
CANARY=$8
|
CANARY=$8
|
||||||
|
|
||||||
|
if [[ "$(uname)" == "Darwin" ]]; then
|
||||||
|
echo "Clearing xattr on all dot undercsore files"
|
||||||
|
find "$BASE_DIR" -type f -name "._*" -exec sh -c '
|
||||||
|
for f; do
|
||||||
|
dir=$(dirname "$f")
|
||||||
|
base=$(basename "$f")
|
||||||
|
orig="$dir/${base#._}"
|
||||||
|
[ -f "$orig" ] && xattr -c "$orig" || true
|
||||||
|
done
|
||||||
|
' sh {} +
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$CANARY" == "1" ]; then
|
if [ "$CANARY" == "1" ]; then
|
||||||
RELEASE_TAR_FILE_NAME=ryujinx-canary-$VERSION-macos_universal.app.tar
|
RELEASE_TAR_FILE_NAME=ryujinx-canary-$VERSION-macos_universal.app.tar
|
||||||
elif [ "$VERSION" == "1.1.0" ]; then
|
elif [ "$VERSION" == "1.1.0" ]; then
|
||||||
|
@ -20,6 +20,18 @@ SOURCE_REVISION_ID=$6
|
|||||||
CONFIGURATION=$7
|
CONFIGURATION=$7
|
||||||
CANARY=$8
|
CANARY=$8
|
||||||
|
|
||||||
|
if [[ "$(uname)" == "Darwin" ]]; then
|
||||||
|
echo "Clearing xattr on all dot undercsore files"
|
||||||
|
find "$BASE_DIR" -type f -name "._*" -exec sh -c '
|
||||||
|
for f; do
|
||||||
|
dir=$(dirname "$f")
|
||||||
|
base=$(basename "$f")
|
||||||
|
orig="$dir/${base#._}"
|
||||||
|
[ -f "$orig" ] && xattr -c "$orig" || true
|
||||||
|
done
|
||||||
|
' sh {} +
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$CANARY" == "1" ]; then
|
if [ "$CANARY" == "1" ]; then
|
||||||
RELEASE_TAR_FILE_NAME=nogui-ryujinx-canary-$VERSION-macos_universal.tar
|
RELEASE_TAR_FILE_NAME=nogui-ryujinx-canary-$VERSION-macos_universal.tar
|
||||||
elif [ "$VERSION" == "1.1.0" ]; then
|
elif [ "$VERSION" == "1.1.0" ]; then
|
||||||
|
@ -1125,6 +1125,7 @@
|
|||||||
0100034012606000,"Family Mysteries: Poisonous Promises",audio;crash,menus,2021-11-26 12:35:06
|
0100034012606000,"Family Mysteries: Poisonous Promises",audio;crash,menus,2021-11-26 12:35:06
|
||||||
010017C012726000,"Fantasy Friends",,playable,2022-10-17 19:42:39
|
010017C012726000,"Fantasy Friends",,playable,2022-10-17 19:42:39
|
||||||
0100767008502000,"FANTASY HERO ~unsigned legacy~",,playable,2022-07-26 12:28:52
|
0100767008502000,"FANTASY HERO ~unsigned legacy~",,playable,2022-07-26 12:28:52
|
||||||
|
0100755017EE0000,"FANTASY LIFE i: The Girl Who Steals Time",gpu;crash;vulkan-backend-bug,ingame,2025-06-08 20:41:00
|
||||||
0100944003820000,"Fantasy Strike",online,playable,2021-02-27 01:59:18
|
0100944003820000,"Fantasy Strike",online,playable,2021-02-27 01:59:18
|
||||||
01000E2012F6E000,"Fantasy Tavern Sextet -Vol.1 New World Days-",gpu;crash;Needs Update,ingame,2022-12-05 16:48:00
|
01000E2012F6E000,"Fantasy Tavern Sextet -Vol.1 New World Days-",gpu;crash;Needs Update,ingame,2022-12-05 16:48:00
|
||||||
01005C10136CA000,"Fantasy Tavern Sextet -Vol.2 Adventurer's Days-",gpu;slow;crash,ingame,2021-11-06 02:57:29
|
01005C10136CA000,"Fantasy Tavern Sextet -Vol.2 Adventurer's Days-",gpu;slow;crash,ingame,2021-11-06 02:57:29
|
||||||
@ -2745,6 +2746,7 @@
|
|||||||
01005D701264A000,"SpyHack",,playable,2021-04-15 10:53:51
|
01005D701264A000,"SpyHack",,playable,2021-04-15 10:53:51
|
||||||
010077B00E046000,"Spyro™ Reignited Trilogy",nvdec;UE4,playable,2022-09-11 18:38:33
|
010077B00E046000,"Spyro™ Reignited Trilogy",nvdec;UE4,playable,2022-09-11 18:38:33
|
||||||
0100085012A0E000,"Squeakers",,playable,2020-12-13 12:13:05
|
0100085012A0E000,"Squeakers",,playable,2020-12-13 12:13:05
|
||||||
|
0100E1D01EB2E000,"Squeakross: Home Squeak Home",,playable,2025-06-16 02:02:00
|
||||||
010009300D31C000,"Squidgies Takeover",,playable,2020-07-20 22:28:08
|
010009300D31C000,"Squidgies Takeover",,playable,2020-07-20 22:28:08
|
||||||
0100FCD0102EC000,"Squidlit",,playable,2020-08-06 12:38:32
|
0100FCD0102EC000,"Squidlit",,playable,2020-08-06 12:38:32
|
||||||
0100EBF00E702000,"STAR OCEAN First Departure R",nvdec,playable,2021-07-05 19:29:16
|
0100EBF00E702000,"STAR OCEAN First Departure R",nvdec,playable,2021-07-05 19:29:16
|
||||||
@ -3015,6 +3017,7 @@
|
|||||||
01009B101044C000,"The Legend of Heroes: Trails of Cold Steel III Demo",demo;nvdec,playable,2021-04-23 01:07:32
|
01009B101044C000,"The Legend of Heroes: Trails of Cold Steel III Demo",demo;nvdec,playable,2021-04-23 01:07:32
|
||||||
0100D3C010DE8000,"The Legend of Heroes: Trails of Cold Steel IV",nvdec,playable,2021-04-23 14:01:05
|
0100D3C010DE8000,"The Legend of Heroes: Trails of Cold Steel IV",nvdec,playable,2021-04-23 14:01:05
|
||||||
01005E5013862000,"THE LEGEND OF HEROES: ZERO NO KISEKI KAI [英雄傳說 零之軌跡:改]",crash,nothing,2021-09-30 14:41:07
|
01005E5013862000,"THE LEGEND OF HEROES: ZERO NO KISEKI KAI [英雄傳說 零之軌跡:改]",crash,nothing,2021-09-30 14:41:07
|
||||||
|
01009C901ACEE000,"The Legend of Nayuta: Boundless Trails",,ingame,2025-06-12 15:47
|
||||||
01008CF01BAAC000,"The Legend of Zelda Echoes of Wisdom",nvdec;ASTC;intel-vendor-bug,playable,2024-10-01 14:11:01
|
01008CF01BAAC000,"The Legend of Zelda Echoes of Wisdom",nvdec;ASTC;intel-vendor-bug,playable,2024-10-01 14:11:01
|
||||||
0100509005AF2000,"The Legend of Zelda: Breath of the Wild Demo",demo,ingame,2022-12-24 05:02:58
|
0100509005AF2000,"The Legend of Zelda: Breath of the Wild Demo",demo,ingame,2022-12-24 05:02:58
|
||||||
01007EF00011E000,"The Legend of Zelda™: Breath of the Wild",gpu;amd-vendor-bug;mac-bug,ingame,2024-09-23 19:35:46
|
01007EF00011E000,"The Legend of Zelda™: Breath of the Wild",gpu;amd-vendor-bug;mac-bug,ingame,2024-09-23 19:35:46
|
||||||
|
|
18
nuget.config
18
nuget.config
@ -4,20 +4,22 @@
|
|||||||
<packageSources>
|
<packageSources>
|
||||||
<clear />
|
<clear />
|
||||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||||
<add key="LibHacAlpha" value="https://git.ryujinx.app/api/v4/projects/17/packages/nuget/index.json" />
|
<!-- Only needed when using pre-release versions of Ryujinx.LibHac. -->
|
||||||
<add key="RyubingPkgs" value="https://git.ryujinx.app/api/v4/projects/1/packages/nuget/index.json" />
|
<!--<add key="LibHacAlpha" value="https://git.ryujinx.app/api/v4/projects/17/packages/nuget/index.json" />-->
|
||||||
|
<add key="Ryujinx.UpdateClient" value="https://git.ryujinx.app/api/v4/projects/71/packages/nuget/index.json" />
|
||||||
</packageSources>
|
</packageSources>
|
||||||
|
|
||||||
<!-- Define mappings by adding package patterns beneath the target source. -->
|
|
||||||
<!-- Ryujinx.LibHac packages will be restored from LibHacAlpha,
|
|
||||||
everything else from nuget.org. -->
|
|
||||||
<packageSourceMapping>
|
<packageSourceMapping>
|
||||||
<!-- key value for <packageSource> should match key values from <packageSources> element -->
|
<!-- key value for <packageSource> should match key values from <packageSources> element -->
|
||||||
|
<!-- These are defined and .NET still yells about multiple package sources with no mappings. Not sure what to do, this is in the docs lol -->
|
||||||
<packageSource key="nuget.org">
|
<packageSource key="nuget.org">
|
||||||
<package pattern="*" />
|
<package pattern="*" />
|
||||||
</packageSource>
|
</packageSource>
|
||||||
<packageSource key="LibHacAlpha">
|
<packageSource key="Ryujinx.UpdateClient">
|
||||||
<package pattern="Ryujinx.LibHac" />
|
<package pattern="Ryujinx.UpdateClient" />
|
||||||
|
<package pattern="Ryujinx.Systems.Update.Common" />
|
||||||
</packageSource>
|
</packageSource>
|
||||||
|
<!--<packageSource key="LibHacAlpha">
|
||||||
|
<package pattern="Ryujinx.LibHac" />
|
||||||
|
</packageSource>-->
|
||||||
</packageSourceMapping>
|
</packageSourceMapping>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
@ -3,6 +3,7 @@ using ARMeilleure.State;
|
|||||||
using ARMeilleure.Translation;
|
using ARMeilleure.Translation;
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using ExecutionContext = ARMeilleure.State.ExecutionContext;
|
||||||
|
|
||||||
namespace ARMeilleure.Instructions
|
namespace ARMeilleure.Instructions
|
||||||
{
|
{
|
||||||
@ -200,7 +201,11 @@ namespace ARMeilleure.Instructions
|
|||||||
|
|
||||||
ExecutionContext context = GetContext();
|
ExecutionContext context = GetContext();
|
||||||
|
|
||||||
context.CheckInterrupt();
|
// If debugging, we'll handle interrupts outside
|
||||||
|
if (!Optimizations.EnableDebugging)
|
||||||
|
{
|
||||||
|
context.CheckInterrupt();
|
||||||
|
}
|
||||||
|
|
||||||
Statistics.ResumeTimer();
|
Statistics.ResumeTimer();
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ namespace ARMeilleure
|
|||||||
|
|
||||||
public static bool AllowLcqInFunctionTable { get; set; } = true;
|
public static bool AllowLcqInFunctionTable { get; set; } = true;
|
||||||
public static bool UseUnmanagedDispatchLoop { get; set; } = true;
|
public static bool UseUnmanagedDispatchLoop { get; set; } = true;
|
||||||
|
public static bool EnableDebugging { get; set; } = false;
|
||||||
|
|
||||||
public static bool UseAdvSimdIfAvailable { get; set; } = true;
|
public static bool UseAdvSimdIfAvailable { get; set; } = true;
|
||||||
public static bool UseArm64AesIfAvailable { get; set; } = true;
|
public static bool UseArm64AesIfAvailable { get; set; } = true;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using ARMeilleure.Memory;
|
using ARMeilleure.Memory;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace ARMeilleure.State
|
namespace ARMeilleure.State
|
||||||
{
|
{
|
||||||
@ -10,7 +11,7 @@ namespace ARMeilleure.State
|
|||||||
|
|
||||||
internal nint NativeContextPtr => _nativeContext.BasePtr;
|
internal nint NativeContextPtr => _nativeContext.BasePtr;
|
||||||
|
|
||||||
private bool _interrupted;
|
internal bool Interrupted { get; private set; }
|
||||||
|
|
||||||
private readonly ICounter _counter;
|
private readonly ICounter _counter;
|
||||||
|
|
||||||
@ -65,6 +66,8 @@ namespace ARMeilleure.State
|
|||||||
|
|
||||||
public bool IsAarch32 { get; set; }
|
public bool IsAarch32 { get; set; }
|
||||||
|
|
||||||
|
public ulong ThreadUid { get; set; }
|
||||||
|
|
||||||
internal ExecutionMode ExecutionMode
|
internal ExecutionMode ExecutionMode
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -90,14 +93,19 @@ namespace ARMeilleure.State
|
|||||||
|
|
||||||
private readonly ExceptionCallbackNoArgs _interruptCallback;
|
private readonly ExceptionCallbackNoArgs _interruptCallback;
|
||||||
private readonly ExceptionCallback _breakCallback;
|
private readonly ExceptionCallback _breakCallback;
|
||||||
|
private readonly ExceptionCallbackNoArgs _stepCallback;
|
||||||
private readonly ExceptionCallback _supervisorCallback;
|
private readonly ExceptionCallback _supervisorCallback;
|
||||||
private readonly ExceptionCallback _undefinedCallback;
|
private readonly ExceptionCallback _undefinedCallback;
|
||||||
|
|
||||||
|
internal int ShouldStep;
|
||||||
|
public ulong DebugPc { get; set; }
|
||||||
|
|
||||||
public ExecutionContext(
|
public ExecutionContext(
|
||||||
IJitMemoryAllocator allocator,
|
IJitMemoryAllocator allocator,
|
||||||
ICounter counter,
|
ICounter counter,
|
||||||
ExceptionCallbackNoArgs interruptCallback = null,
|
ExceptionCallbackNoArgs interruptCallback = null,
|
||||||
ExceptionCallback breakCallback = null,
|
ExceptionCallback breakCallback = null,
|
||||||
|
ExceptionCallbackNoArgs stepCallback = null,
|
||||||
ExceptionCallback supervisorCallback = null,
|
ExceptionCallback supervisorCallback = null,
|
||||||
ExceptionCallback undefinedCallback = null)
|
ExceptionCallback undefinedCallback = null)
|
||||||
{
|
{
|
||||||
@ -105,6 +113,7 @@ namespace ARMeilleure.State
|
|||||||
_counter = counter;
|
_counter = counter;
|
||||||
_interruptCallback = interruptCallback;
|
_interruptCallback = interruptCallback;
|
||||||
_breakCallback = breakCallback;
|
_breakCallback = breakCallback;
|
||||||
|
_stepCallback = stepCallback;
|
||||||
_supervisorCallback = supervisorCallback;
|
_supervisorCallback = supervisorCallback;
|
||||||
_undefinedCallback = undefinedCallback;
|
_undefinedCallback = undefinedCallback;
|
||||||
|
|
||||||
@ -127,9 +136,9 @@ namespace ARMeilleure.State
|
|||||||
|
|
||||||
internal void CheckInterrupt()
|
internal void CheckInterrupt()
|
||||||
{
|
{
|
||||||
if (_interrupted)
|
if (Interrupted)
|
||||||
{
|
{
|
||||||
_interrupted = false;
|
Interrupted = false;
|
||||||
|
|
||||||
_interruptCallback?.Invoke(this);
|
_interruptCallback?.Invoke(this);
|
||||||
}
|
}
|
||||||
@ -139,16 +148,37 @@ namespace ARMeilleure.State
|
|||||||
|
|
||||||
public void RequestInterrupt()
|
public void RequestInterrupt()
|
||||||
{
|
{
|
||||||
_interrupted = true;
|
Interrupted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StepHandler()
|
||||||
|
{
|
||||||
|
_stepCallback?.Invoke(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RequestDebugStep()
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref ShouldStep, 1);
|
||||||
|
RequestInterrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void OnBreak(ulong address, int imm)
|
internal void OnBreak(ulong address, int imm)
|
||||||
{
|
{
|
||||||
|
if (Optimizations.EnableDebugging)
|
||||||
|
{
|
||||||
|
DebugPc = Pc;
|
||||||
|
}
|
||||||
|
|
||||||
_breakCallback?.Invoke(this, address, imm);
|
_breakCallback?.Invoke(this, address, imm);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void OnSupervisorCall(ulong address, int imm)
|
internal void OnSupervisorCall(ulong address, int imm)
|
||||||
{
|
{
|
||||||
|
if (Optimizations.EnableDebugging)
|
||||||
|
{
|
||||||
|
DebugPc = Pc;
|
||||||
|
}
|
||||||
|
|
||||||
_supervisorCallback?.Invoke(this, address, imm);
|
_supervisorCallback?.Invoke(this, address, imm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,11 @@ namespace ARMeilleure.State
|
|||||||
public ulong ExclusiveValueHigh;
|
public ulong ExclusiveValueHigh;
|
||||||
public int Running;
|
public int Running;
|
||||||
public long Tpidr2El0;
|
public long Tpidr2El0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is only set when Optimizations.EnableDebugging is true.
|
||||||
|
/// </summary>
|
||||||
|
public ulong CurrentPc;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static NativeCtxStorage _dummyStorage = new();
|
private static NativeCtxStorage _dummyStorage = new();
|
||||||
@ -39,6 +44,11 @@ namespace ARMeilleure.State
|
|||||||
|
|
||||||
public ulong GetPc()
|
public ulong GetPc()
|
||||||
{
|
{
|
||||||
|
if (Optimizations.EnableDebugging)
|
||||||
|
{
|
||||||
|
return GetStorage().CurrentPc;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: More precise tracking of PC value.
|
// TODO: More precise tracking of PC value.
|
||||||
return GetStorage().DispatchAddress;
|
return GetStorage().DispatchAddress;
|
||||||
}
|
}
|
||||||
@ -268,6 +278,11 @@ namespace ARMeilleure.State
|
|||||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Running);
|
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Running);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int GetCurrentPcOffset()
|
||||||
|
{
|
||||||
|
return StorageOffset(ref _dummyStorage, ref _dummyStorage.CurrentPc);
|
||||||
|
}
|
||||||
|
|
||||||
private static int StorageOffset<T>(ref NativeCtxStorage storage, ref T target)
|
private static int StorageOffset<T>(ref NativeCtxStorage storage, ref T target)
|
||||||
{
|
{
|
||||||
return (int)Unsafe.ByteOffset(ref Unsafe.As<NativeCtxStorage, T>(ref storage), ref target);
|
return (int)Unsafe.ByteOffset(ref Unsafe.As<NativeCtxStorage, T>(ref storage), ref target);
|
||||||
|
@ -33,7 +33,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||||
|
|
||||||
private const uint InternalVersion = 7008; //! To be incremented manually for each change to the ARMeilleure project.
|
private const uint InternalVersion = 7009; //! To be incremented manually for each change to the ARMeilleure project.
|
||||||
|
|
||||||
private const string ActualDir = "0";
|
private const string ActualDir = "0";
|
||||||
private const string BackupDir = "1";
|
private const string BackupDir = "1";
|
||||||
@ -193,7 +193,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
_infosStream.Seek(0L, SeekOrigin.Begin);
|
_infosStream.Seek(0L, SeekOrigin.Begin);
|
||||||
bool foundBadFunction = false;
|
bool foundBadFunction = false;
|
||||||
|
|
||||||
for (int index = 0; index < GetEntriesCount(); index++)
|
for (int index = 0; index < _infosStream.Length / Unsafe.SizeOf<InfoEntry>(); index++)
|
||||||
{
|
{
|
||||||
InfoEntry infoEntry = DeserializeStructure<InfoEntry>(_infosStream);
|
InfoEntry infoEntry = DeserializeStructure<InfoEntry>(_infosStream);
|
||||||
foreach (ulong address in blacklist)
|
foreach (ulong address in blacklist)
|
||||||
@ -303,6 +303,13 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (outerHeader.DebuggerMode != Optimizations.EnableDebugging)
|
||||||
|
{
|
||||||
|
InvalidateCompressedStream(compressedStream);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
nint intPtr = nint.Zero;
|
nint intPtr = nint.Zero;
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -479,6 +486,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
MemoryManagerMode = GetMemoryManagerMode(),
|
MemoryManagerMode = GetMemoryManagerMode(),
|
||||||
OSPlatform = GetOSPlatform(),
|
OSPlatform = GetOSPlatform(),
|
||||||
Architecture = (uint)RuntimeInformation.ProcessArchitecture,
|
Architecture = (uint)RuntimeInformation.ProcessArchitecture,
|
||||||
|
DebuggerMode = Optimizations.EnableDebugging,
|
||||||
|
|
||||||
UncompressedStreamSize =
|
UncompressedStreamSize =
|
||||||
(long)Unsafe.SizeOf<InnerHeader>() +
|
(long)Unsafe.SizeOf<InnerHeader>() +
|
||||||
@ -1068,7 +1076,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
return osPlatform;
|
return osPlatform;
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 86*/)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 87*/)]
|
||||||
private struct OuterHeader
|
private struct OuterHeader
|
||||||
{
|
{
|
||||||
public ulong Magic;
|
public ulong Magic;
|
||||||
@ -1080,6 +1088,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
public byte MemoryManagerMode;
|
public byte MemoryManagerMode;
|
||||||
public uint OSPlatform;
|
public uint OSPlatform;
|
||||||
public uint Architecture;
|
public uint Architecture;
|
||||||
|
public bool DebuggerMode;
|
||||||
|
|
||||||
public long UncompressedStreamSize;
|
public long UncompressedStreamSize;
|
||||||
|
|
||||||
|
@ -119,7 +119,25 @@ namespace ARMeilleure.Translation
|
|||||||
|
|
||||||
NativeInterface.RegisterThread(context, Memory, this);
|
NativeInterface.RegisterThread(context, Memory, this);
|
||||||
|
|
||||||
if (Optimizations.UseUnmanagedDispatchLoop)
|
if (Optimizations.EnableDebugging)
|
||||||
|
{
|
||||||
|
context.DebugPc = address;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (Interlocked.CompareExchange(ref context.ShouldStep, 0, 1) == 1)
|
||||||
|
{
|
||||||
|
context.DebugPc = Step(context, context.DebugPc);
|
||||||
|
context.StepHandler();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.DebugPc = ExecuteSingle(context, context.DebugPc);
|
||||||
|
}
|
||||||
|
context.CheckInterrupt();
|
||||||
|
}
|
||||||
|
while (context.Running && context.DebugPc != 0);
|
||||||
|
}
|
||||||
|
else if (Optimizations.UseUnmanagedDispatchLoop)
|
||||||
{
|
{
|
||||||
Stubs.DispatchLoop(context.NativeContextPtr, address);
|
Stubs.DispatchLoop(context.NativeContextPtr, address);
|
||||||
}
|
}
|
||||||
@ -175,8 +193,24 @@ namespace ARMeilleure.Translation
|
|||||||
return nextAddr;
|
return nextAddr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ulong Step(State.ExecutionContext context, ulong address)
|
private ulong Step(State.ExecutionContext context, ulong address)
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
OpCode opCode = Decoder.DecodeOpCode(Memory, address, context.ExecutionMode);
|
||||||
|
|
||||||
|
// For branch instructions during single-stepping, we handle them manually
|
||||||
|
// func.Execute() will sometimes execute the entire function call, which is not what we want
|
||||||
|
if (opCode.Instruction.Name is InstName.Bl or InstName.Blr or InstName.Blx or InstName.Br)
|
||||||
|
{
|
||||||
|
return ExecuteBranchInstructionForStepping(context, address, opCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true);
|
TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true);
|
||||||
|
|
||||||
address = func.Execute(Stubs.ContextWrapper, context);
|
address = func.Execute(Stubs.ContextWrapper, context);
|
||||||
@ -186,6 +220,94 @@ namespace ARMeilleure.Translation
|
|||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ulong ExecuteBranchInstructionForStepping(State.ExecutionContext context, ulong address, OpCode opCode)
|
||||||
|
{
|
||||||
|
switch (opCode.Instruction.Name)
|
||||||
|
{
|
||||||
|
case InstName.Bl:
|
||||||
|
if (opCode is IOpCodeBImm opBImm)
|
||||||
|
{
|
||||||
|
// Set link register
|
||||||
|
if (context.ExecutionMode == ExecutionMode.Aarch64)
|
||||||
|
{
|
||||||
|
context.SetX(30, address + (ulong)opCode.OpCodeSizeInBytes); // LR = X30
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// For ARM32, need to set the appropriate return address
|
||||||
|
uint returnAddr = opCode is OpCode32 op32 && op32.IsThumb
|
||||||
|
? (uint)address + (uint)opCode.OpCodeSizeInBytes | 1u // Thumb bit set
|
||||||
|
: (uint)address + (uint)opCode.OpCodeSizeInBytes;
|
||||||
|
context.SetX(14, returnAddr); // LR = R14
|
||||||
|
}
|
||||||
|
return (ulong)opBImm.Immediate;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case InstName.Blr:
|
||||||
|
if (opCode is OpCodeBReg opBReg)
|
||||||
|
{
|
||||||
|
// Set link register
|
||||||
|
if (context.ExecutionMode == ExecutionMode.Aarch64)
|
||||||
|
{
|
||||||
|
context.SetX(30, address + (ulong)opCode.OpCodeSizeInBytes); // LR = X30
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uint returnAddr = opCode is OpCode32 op32 && op32.IsThumb
|
||||||
|
? (uint)address + (uint)opCode.OpCodeSizeInBytes | 1u // Thumb bit set
|
||||||
|
: (uint)address + (uint)opCode.OpCodeSizeInBytes;
|
||||||
|
context.SetX(14, returnAddr); // LR = R14
|
||||||
|
}
|
||||||
|
return context.GetX(opBReg.Rn);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case InstName.Blx:
|
||||||
|
if (opCode is IOpCodeBImm opBlxImm)
|
||||||
|
{
|
||||||
|
// Handle mode switching for BLX
|
||||||
|
if (opCode is OpCode32 op32)
|
||||||
|
{
|
||||||
|
uint returnAddr = op32.IsThumb
|
||||||
|
? (uint)address + (uint)opCode.OpCodeSizeInBytes | 1u
|
||||||
|
: (uint)address + (uint)opCode.OpCodeSizeInBytes;
|
||||||
|
context.SetX(14, returnAddr);
|
||||||
|
|
||||||
|
// BLX switches between ARM and Thumb modes
|
||||||
|
context.SetPstateFlag(PState.TFlag, !op32.IsThumb);
|
||||||
|
}
|
||||||
|
return (ulong)opBlxImm.Immediate;
|
||||||
|
}
|
||||||
|
else if (opCode is IOpCode32BReg opBlxReg)
|
||||||
|
{
|
||||||
|
if (opCode is OpCode32 op32)
|
||||||
|
{
|
||||||
|
uint returnAddr = op32.IsThumb
|
||||||
|
? (uint)address + (uint)opCode.OpCodeSizeInBytes | 1u
|
||||||
|
: (uint)address + (uint)opCode.OpCodeSizeInBytes;
|
||||||
|
context.SetX(14, returnAddr);
|
||||||
|
|
||||||
|
// For BLX register, the target address determines the mode
|
||||||
|
ulong targetAddr = context.GetX(opBlxReg.Rm);
|
||||||
|
context.SetPstateFlag(PState.TFlag, (targetAddr & 1) != 0);
|
||||||
|
return targetAddr & ~1UL; // Clear the Thumb bit for the actual address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case InstName.Br:
|
||||||
|
if (opCode is OpCodeBReg opBr)
|
||||||
|
{
|
||||||
|
// BR doesn't set link register, just branches to the target
|
||||||
|
return context.GetX(opBr.Rn);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException($"Unhandled branch instruction: {opCode.Instruction.Name}");
|
||||||
|
}
|
||||||
|
|
||||||
internal TranslatedFunction GetOrTranslate(ulong address, ExecutionMode mode)
|
internal TranslatedFunction GetOrTranslate(ulong address, ExecutionMode mode)
|
||||||
{
|
{
|
||||||
if (!Functions.TryGetValue(address, out TranslatedFunction func))
|
if (!Functions.TryGetValue(address, out TranslatedFunction func))
|
||||||
@ -367,9 +489,13 @@ namespace ARMeilleure.Translation
|
|||||||
|
|
||||||
if (block.Exit)
|
if (block.Exit)
|
||||||
{
|
{
|
||||||
// Left option here as it may be useful if we need to return to managed rather than tail call in
|
// Return to managed rather than tail call.
|
||||||
// future. (eg. for debug)
|
bool useReturns = Optimizations.EnableDebugging;
|
||||||
bool useReturns = false;
|
|
||||||
|
if (Optimizations.EnableDebugging)
|
||||||
|
{
|
||||||
|
EmitPcUpdate(context, block.Address);
|
||||||
|
}
|
||||||
|
|
||||||
InstEmitFlowHelper.EmitVirtualJump(context, Const(block.Address), isReturn: useReturns);
|
InstEmitFlowHelper.EmitVirtualJump(context, Const(block.Address), isReturn: useReturns);
|
||||||
}
|
}
|
||||||
@ -393,6 +519,11 @@ namespace ARMeilleure.Translation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Optimizations.EnableDebugging)
|
||||||
|
{
|
||||||
|
EmitPcUpdate(context, opCode.Address);
|
||||||
|
}
|
||||||
|
|
||||||
Operand lblPredicateSkip = default;
|
Operand lblPredicateSkip = default;
|
||||||
|
|
||||||
if (context.IsInIfThenBlock && context.CurrentIfThenBlockCond != Condition.Al)
|
if (context.IsInIfThenBlock && context.CurrentIfThenBlockCond != Condition.Al)
|
||||||
@ -489,6 +620,14 @@ namespace ARMeilleure.Translation
|
|||||||
context.MarkLabel(lblExit);
|
context.MarkLabel(lblExit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static void EmitPcUpdate(EmitterContext context, ulong address)
|
||||||
|
{
|
||||||
|
long currentPcOffs = NativeContext.GetCurrentPcOffset();
|
||||||
|
|
||||||
|
Operand currentPcAddr = context.Add(context.LoadArgument(OperandType.I64, 0), Const(currentPcOffs));
|
||||||
|
context.Store(currentPcAddr, Const(address));
|
||||||
|
}
|
||||||
|
|
||||||
public void InvalidateJitCacheRegion(ulong address, ulong size)
|
public void InvalidateJitCacheRegion(ulong address, ulong size)
|
||||||
{
|
{
|
||||||
ulong[] overlapAddresses = [];
|
ulong[] overlapAddresses = [];
|
||||||
|
@ -13,6 +13,7 @@ namespace Ryujinx.Common.Logging
|
|||||||
Cpu,
|
Cpu,
|
||||||
Emulation,
|
Emulation,
|
||||||
FFmpeg,
|
FFmpeg,
|
||||||
|
GdbStub,
|
||||||
Font,
|
Font,
|
||||||
Gpu,
|
Gpu,
|
||||||
Hid,
|
Hid,
|
||||||
|
@ -32,59 +32,11 @@ namespace Ryujinx.Common
|
|||||||
|
|
||||||
public static string Version => IsValid ? BuildVersion : Assembly.GetEntryAssembly()!.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
public static string Version => IsValid ? BuildVersion : Assembly.GetEntryAssembly()!.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||||
|
|
||||||
public static string GetChangelogUrl(Version currentVersion, Version newVersion, ReleaseChannels.Channel releaseChannel) =>
|
public static string GetChangelogUrl(Version currentVersion, Version newVersion) =>
|
||||||
IsCanaryBuild
|
IsCanaryBuild
|
||||||
? $"https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-{currentVersion}...Canary-{newVersion}"
|
? $"https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-{currentVersion}...Canary-{newVersion}"
|
||||||
: GetChangelogForVersion(newVersion, releaseChannel);
|
: $"https://git.ryujinx.app/ryubing/ryujinx/-/releases/{newVersion}";
|
||||||
|
|
||||||
public static string GetChangelogForVersion(Version version, ReleaseChannels.Channel releaseChannel) =>
|
|
||||||
$"https://github.com/{releaseChannel}/releases/{version}";
|
|
||||||
|
|
||||||
public static async Task<ReleaseChannels> GetReleaseChannelsAsync(HttpClient httpClient)
|
|
||||||
{
|
|
||||||
ReleaseChannelPair releaseChannelPair = await httpClient.GetFromJsonAsync("https://ryujinx.app/api/release-channels", ReleaseChannelPairContext.Default.ReleaseChannelPair);
|
|
||||||
return new ReleaseChannels(releaseChannelPair);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly struct ReleaseChannels
|
|
||||||
{
|
|
||||||
internal ReleaseChannels(ReleaseChannelPair channelPair)
|
|
||||||
{
|
|
||||||
Stable = new Channel(channelPair.Stable);
|
|
||||||
Canary = new Channel(channelPair.Canary);
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly Channel Stable;
|
|
||||||
public readonly Channel Canary;
|
|
||||||
|
|
||||||
public readonly struct Channel
|
|
||||||
{
|
|
||||||
public Channel(string raw)
|
|
||||||
{
|
|
||||||
string[] parts = raw.Split('/');
|
|
||||||
Owner = parts[0];
|
|
||||||
Repo = parts[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly string Owner;
|
|
||||||
public readonly string Repo;
|
|
||||||
|
|
||||||
public override string ToString() => $"{Owner}/{Repo}";
|
|
||||||
|
|
||||||
public string GetLatestReleaseApiUrl() =>
|
|
||||||
$"https://api.github.com/repos/{ToString()}/releases/latest";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonSerializable(typeof(ReleaseChannelPair))]
|
|
||||||
partial class ReleaseChannelPairContext : JsonSerializerContext;
|
|
||||||
|
|
||||||
class ReleaseChannelPair
|
|
||||||
{
|
|
||||||
[JsonPropertyName("stable")]
|
|
||||||
public string Stable { get; set; }
|
|
||||||
[JsonPropertyName("canary")]
|
|
||||||
public string Canary { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -133,7 +133,6 @@ namespace Ryujinx.Common
|
|||||||
"0100c1f0051b6000", // Donkey Kong Country: Tropical Freeze
|
"0100c1f0051b6000", // Donkey Kong Country: Tropical Freeze
|
||||||
"0100ed000d390000", // Dr. Kawashima's Brain Training
|
"0100ed000d390000", // Dr. Kawashima's Brain Training
|
||||||
"010067b017588000", // Endless Ocean Luminous
|
"010067b017588000", // Endless Ocean Luminous
|
||||||
"0100d2f00d5c0000", // Nintendo Switch Sports
|
|
||||||
"01006b5012b32000", // Part Time UFO
|
"01006b5012b32000", // Part Time UFO
|
||||||
"0100704000B3A000", // Snipperclips
|
"0100704000B3A000", // Snipperclips
|
||||||
"01006a800016e000", // Super Smash Bros. Ultimate
|
"01006a800016e000", // Super Smash Bros. Ultimate
|
||||||
@ -169,6 +168,8 @@ namespace Ryujinx.Common
|
|||||||
"010056e00853a000", // A Hat in Time
|
"010056e00853a000", // A Hat in Time
|
||||||
"0100fd1014726000", // Baldurs Gate: Dark Alliance
|
"0100fd1014726000", // Baldurs Gate: Dark Alliance
|
||||||
"01008c2019598000", // Bluey: The Video Game
|
"01008c2019598000", // Bluey: The Video Game
|
||||||
|
"010096f00ff22000", // Borderlands 2: Game of the Year Edition
|
||||||
|
"010007400ff24000", // Borderlands: The Pre-Sequel Ultimate Edition
|
||||||
"0100c6800b934000", // Brawlhalla
|
"0100c6800b934000", // Brawlhalla
|
||||||
"0100dbf01000a000", // Burnout Paradise Remastered
|
"0100dbf01000a000", // Burnout Paradise Remastered
|
||||||
"0100744001588000", // Cars 3: Driven to Win
|
"0100744001588000", // Cars 3: Driven to Win
|
||||||
@ -194,6 +195,7 @@ namespace Ryujinx.Common
|
|||||||
"01008d100d43e000", // Saints Row IV
|
"01008d100d43e000", // Saints Row IV
|
||||||
"0100de600beee000", // Saints Row: The Third - The Full Package
|
"0100de600beee000", // Saints Row: The Third - The Full Package
|
||||||
"01001180021fa000", // Shovel Knight: Specter of Torment
|
"01001180021fa000", // Shovel Knight: Specter of Torment
|
||||||
|
"0100e1D01eb2e000", // Squeakross: Home Squeak Home
|
||||||
"0100e65002bb8000", // Stardew Valley
|
"0100e65002bb8000", // Stardew Valley
|
||||||
"0100d7a01b7a2000", // Star Wars: Bounty Hunter
|
"0100d7a01b7a2000", // Star Wars: Bounty Hunter
|
||||||
"0100800015926000", // Suika Game
|
"0100800015926000", // Suika Game
|
||||||
|
10
src/Ryujinx.Cpu/AppleHv/Arm/ExceptionLevel.cs
Normal file
10
src/Ryujinx.Cpu/AppleHv/Arm/ExceptionLevel.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
namespace Ryujinx.Cpu.AppleHv.Arm
|
||||||
|
{
|
||||||
|
enum ExceptionLevel : uint
|
||||||
|
{
|
||||||
|
PstateMask = 0xfffffff0,
|
||||||
|
EL1h = 0b0101,
|
||||||
|
El1t = 0b0100,
|
||||||
|
EL0 = 0b0000,
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,18 @@ namespace Ryujinx.Cpu.AppleHv
|
|||||||
class HvExecutionContext : IExecutionContext
|
class HvExecutionContext : IExecutionContext
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ulong Pc => _impl.ElrEl1;
|
public ulong Pc
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
|
||||||
|
if (currentEl == (uint)ExceptionLevel.EL1h)
|
||||||
|
{
|
||||||
|
return _impl.ElrEl1;
|
||||||
|
}
|
||||||
|
return _impl.Pc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public long TpidrEl0
|
public long TpidrEl0
|
||||||
@ -48,6 +59,9 @@ namespace Ryujinx.Cpu.AppleHv
|
|||||||
set => _impl.Fpsr = value;
|
set => _impl.Fpsr = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ulong ThreadUid { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsAarch32
|
public bool IsAarch32
|
||||||
{
|
{
|
||||||
@ -67,6 +81,7 @@ namespace Ryujinx.Cpu.AppleHv
|
|||||||
private readonly ICounter _counter;
|
private readonly ICounter _counter;
|
||||||
private readonly IHvExecutionContext _shadowContext;
|
private readonly IHvExecutionContext _shadowContext;
|
||||||
private IHvExecutionContext _impl;
|
private IHvExecutionContext _impl;
|
||||||
|
private int _shouldStep;
|
||||||
|
|
||||||
private readonly ExceptionCallbacks _exceptionCallbacks;
|
private readonly ExceptionCallbacks _exceptionCallbacks;
|
||||||
|
|
||||||
@ -103,6 +118,11 @@ namespace Ryujinx.Cpu.AppleHv
|
|||||||
_exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
|
_exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void StepHandler()
|
||||||
|
{
|
||||||
|
_exceptionCallbacks.StepCallback?.Invoke(this);
|
||||||
|
}
|
||||||
|
|
||||||
private void SupervisorCallHandler(ulong address, int imm)
|
private void SupervisorCallHandler(ulong address, int imm)
|
||||||
{
|
{
|
||||||
_exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
|
_exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
|
||||||
@ -127,6 +147,30 @@ namespace Ryujinx.Cpu.AppleHv
|
|||||||
return Interlocked.Exchange(ref _interruptRequested, 0) != 0;
|
return Interlocked.Exchange(ref _interruptRequested, 0) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void RequestDebugStep()
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref _shouldStep, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ulong DebugPc
|
||||||
|
{
|
||||||
|
get => Pc;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
|
||||||
|
if (currentEl == (uint)ExceptionLevel.EL1h)
|
||||||
|
{
|
||||||
|
_impl.ElrEl1 = value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_impl.Pc = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void StopRunning()
|
public void StopRunning()
|
||||||
{
|
{
|
||||||
@ -142,6 +186,22 @@ namespace Ryujinx.Cpu.AppleHv
|
|||||||
|
|
||||||
while (Running)
|
while (Running)
|
||||||
{
|
{
|
||||||
|
if (Interlocked.CompareExchange(ref _shouldStep, 0, 1) == 1)
|
||||||
|
{
|
||||||
|
uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
|
||||||
|
if (currentEl == (uint)ExceptionLevel.EL1h)
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_get_sys_reg(vcpu.Handle, HvSysReg.SPSR_EL1, out ulong spsr).ThrowOnError();
|
||||||
|
spsr |= (1 << 21);
|
||||||
|
HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.SPSR_EL1, spsr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Pstate |= (1 << 21);
|
||||||
|
}
|
||||||
|
HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.MDSCR_EL1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
HvApi.hv_vcpu_run(vcpu.Handle).ThrowOnError();
|
HvApi.hv_vcpu_run(vcpu.Handle).ThrowOnError();
|
||||||
|
|
||||||
HvExitReason reason = vcpu.ExitInfo->Reason;
|
HvExitReason reason = vcpu.ExitInfo->Reason;
|
||||||
@ -209,6 +269,20 @@ namespace Ryujinx.Cpu.AppleHv
|
|||||||
SupervisorCallHandler(elr - 4UL, id);
|
SupervisorCallHandler(elr - 4UL, id);
|
||||||
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
|
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
|
||||||
break;
|
break;
|
||||||
|
case ExceptionClass.SoftwareStepLowerEl:
|
||||||
|
HvApi.hv_vcpu_get_sys_reg(vcpuHandle, HvSysReg.SPSR_EL1, out ulong spsr).ThrowOnError();
|
||||||
|
spsr &= ~((ulong)(1 << 21));
|
||||||
|
HvApi.hv_vcpu_set_sys_reg(vcpuHandle, HvSysReg.SPSR_EL1, spsr).ThrowOnError();
|
||||||
|
HvApi.hv_vcpu_set_sys_reg(vcpuHandle, HvSysReg.MDSCR_EL1, 0);
|
||||||
|
ReturnToPool(vcpu);
|
||||||
|
StepHandler();
|
||||||
|
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
|
||||||
|
break;
|
||||||
|
case ExceptionClass.BrkAarch64:
|
||||||
|
ReturnToPool(vcpu);
|
||||||
|
BreakHandler(elr, (ushort)esr);
|
||||||
|
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Exception($"Unhandled guest exception {ec}.");
|
throw new Exception($"Unhandled guest exception {ec}.");
|
||||||
}
|
}
|
||||||
@ -219,10 +293,7 @@ namespace Ryujinx.Cpu.AppleHv
|
|||||||
// TODO: Invalidate only the range that was modified?
|
// TODO: Invalidate only the range that was modified?
|
||||||
return HvAddressSpace.KernelRegionTlbiEretAddress;
|
return HvAddressSpace.KernelRegionTlbiEretAddress;
|
||||||
}
|
}
|
||||||
else
|
return HvAddressSpace.KernelRegionEretAddress;
|
||||||
{
|
|
||||||
return HvAddressSpace.KernelRegionEretAddress;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DataAbort(MemoryTracking tracking, ulong vcpu, uint esr)
|
private static void DataAbort(MemoryTracking tracking, ulong vcpu, uint esr)
|
||||||
|
@ -18,6 +18,8 @@ namespace Ryujinx.Cpu.AppleHv
|
|||||||
|
|
||||||
public bool IsAarch32 { get; set; }
|
public bool IsAarch32 { get; set; }
|
||||||
|
|
||||||
|
public ulong ThreadUid { get; set; }
|
||||||
|
|
||||||
private readonly ulong[] _x;
|
private readonly ulong[] _x;
|
||||||
private readonly V128[] _v;
|
private readonly V128[] _v;
|
||||||
|
|
||||||
@ -46,5 +48,14 @@ namespace Ryujinx.Cpu.AppleHv
|
|||||||
{
|
{
|
||||||
_v[index] = value;
|
_v[index] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void RequestInterrupt()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetAndClearInterruptRequested()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ using ARMeilleure.State;
|
|||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Ryujinx.Cpu.AppleHv
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
{
|
{
|
||||||
@ -13,6 +14,8 @@ namespace Ryujinx.Cpu.AppleHv
|
|||||||
private static readonly SetSimdFpReg _setSimdFpReg;
|
private static readonly SetSimdFpReg _setSimdFpReg;
|
||||||
private static readonly nint _setSimdFpRegNativePtr;
|
private static readonly nint _setSimdFpRegNativePtr;
|
||||||
|
|
||||||
|
public ulong ThreadUid { get; set; }
|
||||||
|
|
||||||
static HvExecutionContextVcpu()
|
static HvExecutionContextVcpu()
|
||||||
{
|
{
|
||||||
// .NET does not support passing vectors by value, so we need to pass a pointer and use a native
|
// .NET does not support passing vectors by value, so we need to pass a pointer and use a native
|
||||||
@ -135,6 +138,7 @@ namespace Ryujinx.Cpu.AppleHv
|
|||||||
}
|
}
|
||||||
|
|
||||||
private readonly ulong _vcpu;
|
private readonly ulong _vcpu;
|
||||||
|
private int _interruptRequested;
|
||||||
|
|
||||||
public HvExecutionContextVcpu(ulong vcpu)
|
public HvExecutionContextVcpu(ulong vcpu)
|
||||||
{
|
{
|
||||||
@ -180,8 +184,16 @@ namespace Ryujinx.Cpu.AppleHv
|
|||||||
|
|
||||||
public void RequestInterrupt()
|
public void RequestInterrupt()
|
||||||
{
|
{
|
||||||
ulong vcpu = _vcpu;
|
if (Interlocked.Exchange(ref _interruptRequested, 1) == 0)
|
||||||
HvApi.hv_vcpus_exit(ref vcpu, 1);
|
{
|
||||||
|
ulong vcpu = _vcpu;
|
||||||
|
HvApi.hv_vcpus_exit(ref vcpu, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetAndClearInterruptRequested()
|
||||||
|
{
|
||||||
|
return Interlocked.Exchange(ref _interruptRequested, 0) != 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ namespace Ryujinx.Cpu.AppleHv
|
|||||||
|
|
||||||
uint Fpcr { get; set; }
|
uint Fpcr { get; set; }
|
||||||
uint Fpsr { get; set; }
|
uint Fpsr { get; set; }
|
||||||
|
ulong ThreadUid { get; set; }
|
||||||
|
|
||||||
ulong GetX(int index);
|
ulong GetX(int index);
|
||||||
void SetX(int index, ulong value);
|
void SetX(int index, ulong value);
|
||||||
@ -39,5 +40,8 @@ namespace Ryujinx.Cpu.AppleHv
|
|||||||
SetV(i, context.GetV(i));
|
SetV(i, context.GetV(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RequestInterrupt();
|
||||||
|
bool GetAndClearInterruptRequested();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,11 @@ namespace Ryujinx.Cpu
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly ExceptionCallback BreakCallback;
|
public readonly ExceptionCallback BreakCallback;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handler for CPU software interrupts caused by single-stepping.
|
||||||
|
/// </summary>
|
||||||
|
public readonly ExceptionCallbackNoArgs StepCallback;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handler for CPU software interrupts caused by the Arm SVC instruction.
|
/// Handler for CPU software interrupts caused by the Arm SVC instruction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -47,16 +52,19 @@ namespace Ryujinx.Cpu
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="interruptCallback">Handler for CPU interrupts triggered using <see cref="IExecutionContext.RequestInterrupt"/></param>
|
/// <param name="interruptCallback">Handler for CPU interrupts triggered using <see cref="IExecutionContext.RequestInterrupt"/></param>
|
||||||
/// <param name="breakCallback">Handler for CPU software interrupts caused by the Arm BRK instruction</param>
|
/// <param name="breakCallback">Handler for CPU software interrupts caused by the Arm BRK instruction</param>
|
||||||
|
/// <param name="stepCallback">Handler for CPU software interrupts caused by single-stepping</param>
|
||||||
/// <param name="supervisorCallback">Handler for CPU software interrupts caused by the Arm SVC instruction</param>
|
/// <param name="supervisorCallback">Handler for CPU software interrupts caused by the Arm SVC instruction</param>
|
||||||
/// <param name="undefinedCallback">Handler for CPU software interrupts caused by any undefined Arm instruction</param>
|
/// <param name="undefinedCallback">Handler for CPU software interrupts caused by any undefined Arm instruction</param>
|
||||||
public ExceptionCallbacks(
|
public ExceptionCallbacks(
|
||||||
ExceptionCallbackNoArgs interruptCallback = null,
|
ExceptionCallbackNoArgs interruptCallback = null,
|
||||||
ExceptionCallback breakCallback = null,
|
ExceptionCallback breakCallback = null,
|
||||||
|
ExceptionCallbackNoArgs stepCallback = null,
|
||||||
ExceptionCallback supervisorCallback = null,
|
ExceptionCallback supervisorCallback = null,
|
||||||
ExceptionCallback undefinedCallback = null)
|
ExceptionCallback undefinedCallback = null)
|
||||||
{
|
{
|
||||||
InterruptCallback = interruptCallback;
|
InterruptCallback = interruptCallback;
|
||||||
BreakCallback = breakCallback;
|
BreakCallback = breakCallback;
|
||||||
|
StepCallback = stepCallback;
|
||||||
SupervisorCallback = supervisorCallback;
|
SupervisorCallback = supervisorCallback;
|
||||||
UndefinedCallback = undefinedCallback;
|
UndefinedCallback = undefinedCallback;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using ARMeilleure.State;
|
using ARMeilleure.State;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Ryujinx.Cpu
|
namespace Ryujinx.Cpu
|
||||||
{
|
{
|
||||||
@ -46,6 +47,11 @@ namespace Ryujinx.Cpu
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsAarch32 { get; set; }
|
bool IsAarch32 { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Thread UID.
|
||||||
|
/// </summary>
|
||||||
|
public ulong ThreadUid { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates whenever the CPU is still running code.
|
/// Indicates whenever the CPU is still running code.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -108,5 +114,23 @@ namespace Ryujinx.Cpu
|
|||||||
/// If you only need to pause the thread temporarily, use <see cref="RequestInterrupt"/> instead.
|
/// If you only need to pause the thread temporarily, use <see cref="RequestInterrupt"/> instead.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
void StopRunning();
|
void StopRunning();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Requests the thread to stop running temporarily and call <see cref="ExceptionCallbacks.InterruptCallback"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The thread might not pause immediately.
|
||||||
|
/// One must not assume that guest code is no longer being executed by the thread after calling this function.
|
||||||
|
/// After single stepping, the thread should call call <see cref="ExceptionCallbacks.StepCallback"/>.
|
||||||
|
/// </remarks>
|
||||||
|
void RequestDebugStep();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current Program Counter (for debugging).
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// PC register for the debugger. Must not be accessed while the thread isn't stopped for debugging.
|
||||||
|
/// </remarks>
|
||||||
|
ulong DebugPc { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using ARMeilleure.Memory;
|
using ARMeilleure.Memory;
|
||||||
using ARMeilleure.State;
|
using ARMeilleure.State;
|
||||||
|
using ExecutionContext = ARMeilleure.State.ExecutionContext;
|
||||||
|
|
||||||
namespace Ryujinx.Cpu.Jit
|
namespace Ryujinx.Cpu.Jit
|
||||||
{
|
{
|
||||||
@ -53,6 +54,13 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
set => _impl.IsAarch32 = value;
|
set => _impl.IsAarch32 = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ulong ThreadUid
|
||||||
|
{
|
||||||
|
get => _impl.ThreadUid;
|
||||||
|
set => _impl.ThreadUid = value;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool Running => _impl.Running;
|
public bool Running => _impl.Running;
|
||||||
|
|
||||||
@ -65,6 +73,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
counter,
|
counter,
|
||||||
InterruptHandler,
|
InterruptHandler,
|
||||||
BreakHandler,
|
BreakHandler,
|
||||||
|
StepHandler,
|
||||||
SupervisorCallHandler,
|
SupervisorCallHandler,
|
||||||
UndefinedHandler);
|
UndefinedHandler);
|
||||||
|
|
||||||
@ -93,6 +102,11 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
_exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
|
_exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void StepHandler(ExecutionContext context)
|
||||||
|
{
|
||||||
|
_exceptionCallbacks.StepCallback?.Invoke(this);
|
||||||
|
}
|
||||||
|
|
||||||
private void SupervisorCallHandler(ExecutionContext context, ulong address, int imm)
|
private void SupervisorCallHandler(ExecutionContext context, ulong address, int imm)
|
||||||
{
|
{
|
||||||
_exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
|
_exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
|
||||||
@ -109,6 +123,16 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
_impl.RequestInterrupt();
|
_impl.RequestInterrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void RequestDebugStep() => _impl.RequestDebugStep();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ulong DebugPc
|
||||||
|
{
|
||||||
|
get => _impl.DebugPc;
|
||||||
|
set => _impl.DebugPc = value;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void StopRunning()
|
public void StopRunning()
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
using ARMeilleure;
|
||||||
using ARMeilleure.Memory;
|
using ARMeilleure.Memory;
|
||||||
using ARMeilleure.State;
|
using ARMeilleure.State;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Ryujinx.Cpu.LightningJit.State
|
namespace Ryujinx.Cpu.LightningJit.State
|
||||||
{
|
{
|
||||||
@ -51,6 +53,8 @@ namespace Ryujinx.Cpu.LightningJit.State
|
|||||||
}
|
}
|
||||||
|
|
||||||
public bool IsAarch32 { get; set; }
|
public bool IsAarch32 { get; set; }
|
||||||
|
|
||||||
|
public ulong ThreadUid { get; set; }
|
||||||
|
|
||||||
internal ExecutionMode ExecutionMode
|
internal ExecutionMode ExecutionMode
|
||||||
{
|
{
|
||||||
@ -77,15 +81,20 @@ namespace Ryujinx.Cpu.LightningJit.State
|
|||||||
|
|
||||||
private readonly ExceptionCallbackNoArgs _interruptCallback;
|
private readonly ExceptionCallbackNoArgs _interruptCallback;
|
||||||
private readonly ExceptionCallback _breakCallback;
|
private readonly ExceptionCallback _breakCallback;
|
||||||
|
private readonly ExceptionCallbackNoArgs _stepCallback;
|
||||||
private readonly ExceptionCallback _supervisorCallback;
|
private readonly ExceptionCallback _supervisorCallback;
|
||||||
private readonly ExceptionCallback _undefinedCallback;
|
private readonly ExceptionCallback _undefinedCallback;
|
||||||
|
|
||||||
|
internal int ShouldStep;
|
||||||
|
public ulong DebugPc { get; set; }
|
||||||
|
|
||||||
public ExecutionContext(IJitMemoryAllocator allocator, ICounter counter, ExceptionCallbacks exceptionCallbacks)
|
public ExecutionContext(IJitMemoryAllocator allocator, ICounter counter, ExceptionCallbacks exceptionCallbacks)
|
||||||
{
|
{
|
||||||
_nativeContext = new NativeContext(allocator);
|
_nativeContext = new NativeContext(allocator);
|
||||||
_counter = counter;
|
_counter = counter;
|
||||||
_interruptCallback = exceptionCallbacks.InterruptCallback;
|
_interruptCallback = exceptionCallbacks.InterruptCallback;
|
||||||
_breakCallback = exceptionCallbacks.BreakCallback;
|
_breakCallback = exceptionCallbacks.BreakCallback;
|
||||||
|
_stepCallback = exceptionCallbacks.StepCallback;
|
||||||
_supervisorCallback = exceptionCallbacks.SupervisorCallback;
|
_supervisorCallback = exceptionCallbacks.SupervisorCallback;
|
||||||
_undefinedCallback = exceptionCallbacks.UndefinedCallback;
|
_undefinedCallback = exceptionCallbacks.UndefinedCallback;
|
||||||
|
|
||||||
@ -117,6 +126,17 @@ namespace Ryujinx.Cpu.LightningJit.State
|
|||||||
_interrupted = true;
|
_interrupted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void StepHandler()
|
||||||
|
{
|
||||||
|
_stepCallback?.Invoke(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RequestDebugStep()
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref ShouldStep, 1);
|
||||||
|
RequestInterrupt();
|
||||||
|
}
|
||||||
|
|
||||||
internal void OnBreak(ulong address, int imm)
|
internal void OnBreak(ulong address, int imm)
|
||||||
{
|
{
|
||||||
_breakCallback?.Invoke(this, address, imm);
|
_breakCallback?.Invoke(this, address, imm);
|
||||||
|
@ -874,57 +874,42 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
public unsafe void ConvertIndexBuffer(VulkanRenderer gd,
|
public unsafe void ConvertIndexBuffer(VulkanRenderer gd,
|
||||||
CommandBufferScoped cbs,
|
CommandBufferScoped cbs,
|
||||||
BufferHolder src,
|
BufferHolder srcIndexBuffer,
|
||||||
BufferHolder dst,
|
BufferHolder dstIndexBuffer,
|
||||||
IndexBufferPattern pattern,
|
IndexBufferPattern pattern,
|
||||||
int indexSize,
|
int indexSize,
|
||||||
int srcOffset,
|
int srcOffset,
|
||||||
int indexCount)
|
int indexCount)
|
||||||
{
|
{
|
||||||
// TODO: Support conversion with primitive restart enabled.
|
// TODO: Support conversion with primitive restart enabled.
|
||||||
// TODO: Convert with a compute shader?
|
|
||||||
|
|
||||||
|
int primitiveCount = pattern.GetPrimitiveCount(indexCount);
|
||||||
int convertedCount = pattern.GetConvertedCount(indexCount);
|
int convertedCount = pattern.GetConvertedCount(indexCount);
|
||||||
int outputIndexSize = 4;
|
int outputIndexSize = 4;
|
||||||
|
|
||||||
|
Buffer dstBuffer = dstIndexBuffer.GetBuffer().Get(cbs, 0, convertedCount * outputIndexSize).Value;
|
||||||
|
|
||||||
Buffer srcBuffer = src.GetBuffer().Get(cbs, srcOffset, indexCount * indexSize).Value;
|
const int ParamsBufferSize = 16 * sizeof(int);
|
||||||
Buffer dstBuffer = dst.GetBuffer().Get(cbs, 0, convertedCount * outputIndexSize).Value;
|
|
||||||
|
|
||||||
gd.Api.CmdFillBuffer(cbs.CommandBuffer, dstBuffer, 0, Vk.WholeSize, 0);
|
Span<int> shaderParams = stackalloc int[ParamsBufferSize / sizeof(int)];
|
||||||
|
|
||||||
List<BufferCopy> bufferCopy = [];
|
shaderParams[8] = pattern.PrimitiveVertices;
|
||||||
int outputOffset = 0;
|
shaderParams[9] = pattern.PrimitiveVerticesOut;
|
||||||
|
shaderParams[10] = indexSize;
|
||||||
|
shaderParams[11] = outputIndexSize;
|
||||||
|
shaderParams[12] = pattern.BaseIndex;
|
||||||
|
shaderParams[13] = pattern.IndexStride;
|
||||||
|
shaderParams[14] = srcOffset;
|
||||||
|
shaderParams[15] = primitiveCount;
|
||||||
|
|
||||||
// Try to merge copies of adjacent indices to reduce copy count.
|
pattern.OffsetIndex.CopyTo(shaderParams[..pattern.OffsetIndex.Length]);
|
||||||
int sequenceStart = 0;
|
|
||||||
int sequenceLength = 0;
|
|
||||||
|
|
||||||
foreach (int index in pattern.GetIndexMapping(indexCount))
|
using var patternScoped = gd.BufferManager.ReserveOrCreate(gd, cbs, ParamsBufferSize);
|
||||||
{
|
var patternBuffer = patternScoped.Holder;
|
||||||
if (sequenceLength > 0)
|
|
||||||
{
|
|
||||||
if (index == sequenceStart + sequenceLength && indexSize == outputIndexSize)
|
|
||||||
{
|
|
||||||
sequenceLength++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit the copy so far.
|
patternBuffer.SetDataUnchecked<int>(patternScoped.Offset, shaderParams);
|
||||||
bufferCopy.Add(new BufferCopy((ulong)(srcOffset + sequenceStart * indexSize), (ulong)outputOffset, (ulong)(indexSize * sequenceLength)));
|
|
||||||
outputOffset += outputIndexSize * sequenceLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
sequenceStart = index;
|
_pipeline.SetCommandBuffer(cbs);
|
||||||
sequenceLength = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sequenceLength > 0)
|
|
||||||
{
|
|
||||||
// Commit final pending copy.
|
|
||||||
bufferCopy.Add(new BufferCopy((ulong)(srcOffset + sequenceStart * indexSize), (ulong)outputOffset, (ulong)(indexSize * sequenceLength)));
|
|
||||||
}
|
|
||||||
|
|
||||||
BufferCopy[] bufferCopyArray = bufferCopy.ToArray();
|
|
||||||
|
|
||||||
BufferHolder.InsertBufferBarrier(
|
BufferHolder.InsertBufferBarrier(
|
||||||
gd,
|
gd,
|
||||||
@ -937,10 +922,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
0,
|
0,
|
||||||
convertedCount * outputIndexSize);
|
convertedCount * outputIndexSize);
|
||||||
|
|
||||||
fixed (BufferCopy* pBufferCopy = bufferCopyArray)
|
_pipeline.SetUniformBuffers([new BufferAssignment(0, new BufferRange(patternScoped.Handle, patternScoped.Offset, ParamsBufferSize))]);
|
||||||
{
|
_pipeline.SetStorageBuffers(1, new[] { srcIndexBuffer.GetBuffer(), dstIndexBuffer.GetBuffer() });
|
||||||
gd.Api.CmdCopyBuffer(cbs.CommandBuffer, srcBuffer, dstBuffer, (uint)bufferCopyArray.Length, pBufferCopy);
|
|
||||||
}
|
_pipeline.SetProgram(_programConvertIndexBuffer);
|
||||||
|
_pipeline.DispatchCompute(BitUtils.DivRoundUp(primitiveCount, 16), 1, 1);
|
||||||
|
|
||||||
BufferHolder.InsertBufferBarrier(
|
BufferHolder.InsertBufferBarrier(
|
||||||
gd,
|
gd,
|
||||||
@ -952,6 +938,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
PipelineStageFlags.AllCommandsBit,
|
PipelineStageFlags.AllCommandsBit,
|
||||||
0,
|
0,
|
||||||
convertedCount * outputIndexSize);
|
convertedCount * outputIndexSize);
|
||||||
|
|
||||||
|
_pipeline.Finish(gd, cbs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CopyIncompatibleFormats(
|
public void CopyIncompatibleFormats(
|
||||||
|
@ -47,28 +47,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return primitiveCount * OffsetIndex.Length;
|
return primitiveCount * OffsetIndex.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<int> GetIndexMapping(int indexCount)
|
|
||||||
{
|
|
||||||
int primitiveCount = GetPrimitiveCount(indexCount);
|
|
||||||
int index = BaseIndex;
|
|
||||||
|
|
||||||
for (int i = 0; i < primitiveCount; i++)
|
|
||||||
{
|
|
||||||
if (RepeatStart)
|
|
||||||
{
|
|
||||||
// Used for triangle fan
|
|
||||||
yield return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int j = RepeatStart ? 1 : 0; j < OffsetIndex.Length; j++)
|
|
||||||
{
|
|
||||||
yield return index + OffsetIndex[j];
|
|
||||||
}
|
|
||||||
|
|
||||||
index += IndexStride;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public BufferHandle GetRepeatingBuffer(int vertexCount, out int indexCount)
|
public BufferHandle GetRepeatingBuffer(int vertexCount, out int indexCount)
|
||||||
{
|
{
|
||||||
int primitiveCount = GetPrimitiveCount(vertexCount);
|
int primitiveCount = GetPrimitiveCount(vertexCount);
|
||||||
|
203
src/Ryujinx.HLE/Debugger/BreakpointManager.cs
Normal file
203
src/Ryujinx.HLE/Debugger/BreakpointManager.cs
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||||
|
using Ryujinx.Memory;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
internal class Breakpoint
|
||||||
|
{
|
||||||
|
public byte[] OriginalData { get; }
|
||||||
|
|
||||||
|
public bool IsStep { get; }
|
||||||
|
|
||||||
|
public Breakpoint(byte[] originalData, bool isStep)
|
||||||
|
{
|
||||||
|
OriginalData = originalData;
|
||||||
|
IsStep = isStep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Manages software breakpoints for the debugger.
|
||||||
|
/// </summary>
|
||||||
|
public class BreakpointManager
|
||||||
|
{
|
||||||
|
private readonly Debugger _debugger;
|
||||||
|
private readonly ConcurrentDictionary<ulong, Breakpoint> _breakpoints = new();
|
||||||
|
|
||||||
|
private static readonly byte[] _aarch64BreakInstruction = { 0x00, 0x00, 0x20, 0xD4 }; // BRK #0
|
||||||
|
private static readonly byte[] _aarch32BreakInstruction = { 0xFE, 0xDE, 0xFF, 0xE7 }; // TRAP
|
||||||
|
private static readonly byte[] _aarch32ThumbBreakInstruction = { 0x80, 0xB6 };
|
||||||
|
|
||||||
|
public BreakpointManager(Debugger debugger)
|
||||||
|
{
|
||||||
|
_debugger = debugger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets a software breakpoint at a specified address.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">The memory address to set the breakpoint at.</param>
|
||||||
|
/// <param name="length">The length of the instruction to replace.</param>
|
||||||
|
/// <param name="isStep">Indicates if this is a single-step breakpoint.</param>
|
||||||
|
/// <returns>True if the breakpoint was set successfully; otherwise, false.</returns>
|
||||||
|
public bool SetBreakPoint(ulong address, ulong length, bool isStep = false)
|
||||||
|
{
|
||||||
|
if (_breakpoints.ContainsKey(address))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] breakInstruction = GetBreakInstruction(length);
|
||||||
|
if (breakInstruction == null)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.GdbStub, $"Unsupported instruction length for breakpoint: {length}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var originalInstruction = new byte[length];
|
||||||
|
if (!ReadMemory(address, originalInstruction))
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.GdbStub, $"Failed to read memory at 0x{address:X16} to set breakpoint.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!WriteMemory(address, breakInstruction))
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.GdbStub, $"Failed to write breakpoint at 0x{address:X16}.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var breakpoint = new Breakpoint(originalInstruction, isStep);
|
||||||
|
if (_breakpoints.TryAdd(address, breakpoint))
|
||||||
|
{
|
||||||
|
Logger.Debug?.Print(LogClass.GdbStub, $"Breakpoint set at 0x{address:X16}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Error?.Print(LogClass.GdbStub, $"Failed to add breakpoint at 0x{address:X16}.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears a software breakpoint at a specified address.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">The memory address of the breakpoint to clear.</param>
|
||||||
|
/// <param name="length">The length of the instruction (unused).</param>
|
||||||
|
/// <returns>True if the breakpoint was cleared successfully; otherwise, false.</returns>
|
||||||
|
public bool ClearBreakPoint(ulong address, ulong length)
|
||||||
|
{
|
||||||
|
if (_breakpoints.TryGetValue(address, out Breakpoint breakpoint))
|
||||||
|
{
|
||||||
|
if (!WriteMemory(address, breakpoint.OriginalData))
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.GdbStub, $"Failed to restore original instruction at 0x{address:X16} to clear breakpoint.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_breakpoints.TryRemove(address, out _);
|
||||||
|
Logger.Debug?.Print(LogClass.GdbStub, $"Breakpoint cleared at 0x{address:X16}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Warning?.Print(LogClass.GdbStub, $"No breakpoint found at address 0x{address:X16}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears all currently set software breakpoints.
|
||||||
|
/// </summary>
|
||||||
|
public void ClearAll()
|
||||||
|
{
|
||||||
|
foreach (var bp in _breakpoints)
|
||||||
|
{
|
||||||
|
if (!WriteMemory(bp.Key, bp.Value.OriginalData))
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.GdbStub, $"Failed to restore original instruction at 0x{bp.Key:X16} while clearing all breakpoints.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
_breakpoints.Clear();
|
||||||
|
Logger.Debug?.Print(LogClass.GdbStub, "All breakpoints cleared.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears all currently set single-step software breakpoints.
|
||||||
|
/// </summary>
|
||||||
|
public void ClearAllStepBreakpoints()
|
||||||
|
{
|
||||||
|
var stepBreakpoints = _breakpoints.Where(p => p.Value.IsStep).ToList();
|
||||||
|
|
||||||
|
if (stepBreakpoints.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var bp in stepBreakpoints)
|
||||||
|
{
|
||||||
|
if (_breakpoints.TryRemove(bp.Key, out Breakpoint removedBreakpoint))
|
||||||
|
{
|
||||||
|
WriteMemory(bp.Key, removedBreakpoint.OriginalData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Debug?.Print(LogClass.GdbStub, "All step breakpoints cleared.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private byte[] GetBreakInstruction(ulong length)
|
||||||
|
{
|
||||||
|
if (_debugger.IsProcessAarch32)
|
||||||
|
{
|
||||||
|
if (length == 2)
|
||||||
|
{
|
||||||
|
return _aarch32ThumbBreakInstruction;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length == 4)
|
||||||
|
{
|
||||||
|
return _aarch32BreakInstruction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (length == 4)
|
||||||
|
{
|
||||||
|
return _aarch64BreakInstruction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ReadMemory(ulong address, byte[] data)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_debugger.DebugProcess.CpuMemory.Read(address, data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (InvalidMemoryRegionException)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool WriteMemory(ulong address, byte[] data)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_debugger.DebugProcess.CpuMemory.Write(address, data);
|
||||||
|
_debugger.DebugProcess.InvalidateCacheRegion(address, (ulong)data.Length);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (InvalidMemoryRegionException)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
src/Ryujinx.HLE/Debugger/DebugState.cs
Normal file
9
src/Ryujinx.HLE/Debugger/DebugState.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
public enum DebugState
|
||||||
|
{
|
||||||
|
Running,
|
||||||
|
Stopping,
|
||||||
|
Stopped,
|
||||||
|
}
|
||||||
|
}
|
1295
src/Ryujinx.HLE/Debugger/Debugger.cs
Normal file
1295
src/Ryujinx.HLE/Debugger/Debugger.cs
Normal file
File diff suppressed because it is too large
Load Diff
15
src/Ryujinx.HLE/Debugger/GdbSignal.cs
Normal file
15
src/Ryujinx.HLE/Debugger/GdbSignal.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
enum GdbSignal
|
||||||
|
{
|
||||||
|
Zero = 0,
|
||||||
|
Int = 2,
|
||||||
|
Quit = 3,
|
||||||
|
Trap = 5,
|
||||||
|
Abort = 6,
|
||||||
|
Alarm = 14,
|
||||||
|
IO = 23,
|
||||||
|
XCPU = 24,
|
||||||
|
Unknown = 143
|
||||||
|
}
|
||||||
|
}
|
93
src/Ryujinx.HLE/Debugger/GdbXml/aarch64-core.xml
Normal file
93
src/Ryujinx.HLE/Debugger/GdbXml/aarch64-core.xml
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!-- Copyright (C) 2009-2022 Free Software Foundation, Inc.
|
||||||
|
Contributed by ARM Ltd.
|
||||||
|
Copying and distribution of this file, with or without modification,
|
||||||
|
are permitted in any medium without royalty provided the copyright
|
||||||
|
notice and this notice are preserved. -->
|
||||||
|
|
||||||
|
<!DOCTYPE feature SYSTEM "gdb-target.dtd">
|
||||||
|
<feature name="org.gnu.gdb.aarch64.core">
|
||||||
|
<reg name="x0" bitsize="64"/>
|
||||||
|
<reg name="x1" bitsize="64"/>
|
||||||
|
<reg name="x2" bitsize="64"/>
|
||||||
|
<reg name="x3" bitsize="64"/>
|
||||||
|
<reg name="x4" bitsize="64"/>
|
||||||
|
<reg name="x5" bitsize="64"/>
|
||||||
|
<reg name="x6" bitsize="64"/>
|
||||||
|
<reg name="x7" bitsize="64"/>
|
||||||
|
<reg name="x8" bitsize="64"/>
|
||||||
|
<reg name="x9" bitsize="64"/>
|
||||||
|
<reg name="x10" bitsize="64"/>
|
||||||
|
<reg name="x11" bitsize="64"/>
|
||||||
|
<reg name="x12" bitsize="64"/>
|
||||||
|
<reg name="x13" bitsize="64"/>
|
||||||
|
<reg name="x14" bitsize="64"/>
|
||||||
|
<reg name="x15" bitsize="64"/>
|
||||||
|
<reg name="x16" bitsize="64"/>
|
||||||
|
<reg name="x17" bitsize="64"/>
|
||||||
|
<reg name="x18" bitsize="64"/>
|
||||||
|
<reg name="x19" bitsize="64"/>
|
||||||
|
<reg name="x20" bitsize="64"/>
|
||||||
|
<reg name="x21" bitsize="64"/>
|
||||||
|
<reg name="x22" bitsize="64"/>
|
||||||
|
<reg name="x23" bitsize="64"/>
|
||||||
|
<reg name="x24" bitsize="64"/>
|
||||||
|
<reg name="x25" bitsize="64"/>
|
||||||
|
<reg name="x26" bitsize="64"/>
|
||||||
|
<reg name="x27" bitsize="64"/>
|
||||||
|
<reg name="x28" bitsize="64"/>
|
||||||
|
<reg name="x29" bitsize="64"/>
|
||||||
|
<reg name="x30" bitsize="64"/>
|
||||||
|
<reg name="sp" bitsize="64" type="data_ptr"/>
|
||||||
|
|
||||||
|
<reg name="pc" bitsize="64" type="code_ptr"/>
|
||||||
|
|
||||||
|
<flags id="cpsr_flags" size="4">
|
||||||
|
<!-- Stack Pointer. -->
|
||||||
|
<field name="SP" start="0" end="0"/>
|
||||||
|
|
||||||
|
<!-- Exception Level. -->
|
||||||
|
<field name="EL" start="2" end="3"/>
|
||||||
|
<!-- Execution state. -->
|
||||||
|
<field name="nRW" start="4" end="4"/>
|
||||||
|
|
||||||
|
<!-- FIQ interrupt mask. -->
|
||||||
|
<field name="F" start="6" end="6"/>
|
||||||
|
<!-- IRQ interrupt mask. -->
|
||||||
|
<field name="I" start="7" end="7"/>
|
||||||
|
<!-- SError interrupt mask. -->
|
||||||
|
<field name="A" start="8" end="8"/>
|
||||||
|
<!-- Debug exception mask. -->
|
||||||
|
<field name="D" start="9" end="9"/>
|
||||||
|
|
||||||
|
<!-- ARMv8.5-A: Branch Target Identification BTYPE. -->
|
||||||
|
<field name="BTYPE" start="10" end="11"/>
|
||||||
|
|
||||||
|
<!-- ARMv8.0-A: Speculative Store Bypass. -->
|
||||||
|
<field name="SSBS" start="12" end="12"/>
|
||||||
|
|
||||||
|
<!-- Illegal Execution state. -->
|
||||||
|
<field name="IL" start="20" end="20"/>
|
||||||
|
<!-- Software Step. -->
|
||||||
|
<field name="SS" start="21" end="21"/>
|
||||||
|
<!-- ARMv8.1-A: Privileged Access Never. -->
|
||||||
|
<field name="PAN" start="22" end="22"/>
|
||||||
|
<!-- ARMv8.2-A: User Access Override. -->
|
||||||
|
<field name="UAO" start="23" end="23"/>
|
||||||
|
<!-- ARMv8.4-A: Data Independent Timing. -->
|
||||||
|
<field name="DIT" start="24" end="24"/>
|
||||||
|
<!-- ARMv8.5-A: Tag Check Override. -->
|
||||||
|
<field name="TCO" start="25" end="25"/>
|
||||||
|
|
||||||
|
<!-- Overflow Condition flag. -->
|
||||||
|
<field name="V" start="28" end="28"/>
|
||||||
|
<!-- Carry Condition flag. -->
|
||||||
|
<field name="C" start="29" end="29"/>
|
||||||
|
<!-- Zero Condition flag. -->
|
||||||
|
<field name="Z" start="30" end="30"/>
|
||||||
|
<!-- Negative Condition flag. -->
|
||||||
|
<field name="N" start="31" end="31"/>
|
||||||
|
</flags>
|
||||||
|
<reg name="cpsr" bitsize="32" type="cpsr_flags"/>
|
||||||
|
|
||||||
|
</feature>
|
159
src/Ryujinx.HLE/Debugger/GdbXml/aarch64-fpu.xml
Normal file
159
src/Ryujinx.HLE/Debugger/GdbXml/aarch64-fpu.xml
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!-- Copyright (C) 2009-2022 Free Software Foundation, Inc.
|
||||||
|
Contributed by ARM Ltd.
|
||||||
|
Copying and distribution of this file, with or without modification,
|
||||||
|
are permitted in any medium without royalty provided the copyright
|
||||||
|
notice and this notice are preserved. -->
|
||||||
|
|
||||||
|
<!DOCTYPE feature SYSTEM "gdb-target.dtd">
|
||||||
|
<feature name="org.gnu.gdb.aarch64.fpu">
|
||||||
|
<vector id="v2d" type="ieee_double" count="2"/>
|
||||||
|
<vector id="v2u" type="uint64" count="2"/>
|
||||||
|
<vector id="v2i" type="int64" count="2"/>
|
||||||
|
<vector id="v4f" type="ieee_single" count="4"/>
|
||||||
|
<vector id="v4u" type="uint32" count="4"/>
|
||||||
|
<vector id="v4i" type="int32" count="4"/>
|
||||||
|
<vector id="v8f" type="ieee_half" count="8"/>
|
||||||
|
<vector id="v8u" type="uint16" count="8"/>
|
||||||
|
<vector id="v8i" type="int16" count="8"/>
|
||||||
|
<vector id="v8bf16" type="bfloat16" count="8"/>
|
||||||
|
<vector id="v16u" type="uint8" count="16"/>
|
||||||
|
<vector id="v16i" type="int8" count="16"/>
|
||||||
|
<vector id="v1u" type="uint128" count="1"/>
|
||||||
|
<vector id="v1i" type="int128" count="1"/>
|
||||||
|
<union id="vnd">
|
||||||
|
<field name="f" type="v2d"/>
|
||||||
|
<field name="u" type="v2u"/>
|
||||||
|
<field name="s" type="v2i"/>
|
||||||
|
</union>
|
||||||
|
<union id="vns">
|
||||||
|
<field name="f" type="v4f"/>
|
||||||
|
<field name="u" type="v4u"/>
|
||||||
|
<field name="s" type="v4i"/>
|
||||||
|
</union>
|
||||||
|
<union id="vnh">
|
||||||
|
<field name="bf" type="v8bf16"/>
|
||||||
|
<field name="f" type="v8f"/>
|
||||||
|
<field name="u" type="v8u"/>
|
||||||
|
<field name="s" type="v8i"/>
|
||||||
|
</union>
|
||||||
|
<union id="vnb">
|
||||||
|
<field name="u" type="v16u"/>
|
||||||
|
<field name="s" type="v16i"/>
|
||||||
|
</union>
|
||||||
|
<union id="vnq">
|
||||||
|
<field name="u" type="v1u"/>
|
||||||
|
<field name="s" type="v1i"/>
|
||||||
|
</union>
|
||||||
|
<union id="aarch64v">
|
||||||
|
<field name="d" type="vnd"/>
|
||||||
|
<field name="s" type="vns"/>
|
||||||
|
<field name="h" type="vnh"/>
|
||||||
|
<field name="b" type="vnb"/>
|
||||||
|
<field name="q" type="vnq"/>
|
||||||
|
</union>
|
||||||
|
<reg name="v0" bitsize="128" type="aarch64v" regnum="34"/>
|
||||||
|
<reg name="v1" bitsize="128" type="aarch64v" />
|
||||||
|
<reg name="v2" bitsize="128" type="aarch64v" />
|
||||||
|
<reg name="v3" bitsize="128" type="aarch64v" />
|
||||||
|
<reg name="v4" bitsize="128" type="aarch64v" />
|
||||||
|
<reg name="v5" bitsize="128" type="aarch64v" />
|
||||||
|
<reg name="v6" bitsize="128" type="aarch64v" />
|
||||||
|
<reg name="v7" bitsize="128" type="aarch64v" />
|
||||||
|
<reg name="v8" bitsize="128" type="aarch64v" />
|
||||||
|
<reg name="v9" bitsize="128" type="aarch64v" />
|
||||||
|
<reg name="v10" bitsize="128" type="aarch64v"/>
|
||||||
|
<reg name="v11" bitsize="128" type="aarch64v"/>
|
||||||
|
<reg name="v12" bitsize="128" type="aarch64v"/>
|
||||||
|
<reg name="v13" bitsize="128" type="aarch64v"/>
|
||||||
|
<reg name="v14" bitsize="128" type="aarch64v"/>
|
||||||
|
<reg name="v15" bitsize="128" type="aarch64v"/>
|
||||||
|
<reg name="v16" bitsize="128" type="aarch64v"/>
|
||||||
|
<reg name="v17" bitsize="128" type="aarch64v"/>
|
||||||
|
<reg name="v18" bitsize="128" type="aarch64v"/>
|
||||||
|
<reg name="v19" bitsize="128" type="aarch64v"/>
|
||||||
|
<reg name="v20" bitsize="128" type="aarch64v"/>
|
||||||
|
<reg name="v21" bitsize="128" type="aarch64v"/>
|
||||||
|
<reg name="v22" bitsize="128" type="aarch64v"/>
|
||||||
|
<reg name="v23" bitsize="128" type="aarch64v"/>
|
||||||
|
<reg name="v24" bitsize="128" type="aarch64v"/>
|
||||||
|
<reg name="v25" bitsize="128" type="aarch64v"/>
|
||||||
|
<reg name="v26" bitsize="128" type="aarch64v"/>
|
||||||
|
<reg name="v27" bitsize="128" type="aarch64v"/>
|
||||||
|
<reg name="v28" bitsize="128" type="aarch64v"/>
|
||||||
|
<reg name="v29" bitsize="128" type="aarch64v"/>
|
||||||
|
<reg name="v30" bitsize="128" type="aarch64v"/>
|
||||||
|
<reg name="v31" bitsize="128" type="aarch64v"/>
|
||||||
|
|
||||||
|
<flags id="fpsr_flags" size="4">
|
||||||
|
<!-- Invalid Operation cumulative floating-point exception bit. -->
|
||||||
|
<field name="IOC" start="0" end="0"/>
|
||||||
|
<!-- Divide by Zero cumulative floating-point exception bit. -->
|
||||||
|
<field name="DZC" start="1" end="1"/>
|
||||||
|
<!-- Overflow cumulative floating-point exception bit. -->
|
||||||
|
<field name="OFC" start="2" end="2"/>
|
||||||
|
<!-- Underflow cumulative floating-point exception bit. -->
|
||||||
|
<field name="UFC" start="3" end="3"/>
|
||||||
|
<!-- Inexact cumulative floating-point exception bit.. -->
|
||||||
|
<field name="IXC" start="4" end="4"/>
|
||||||
|
<!-- Input Denormal cumulative floating-point exception bit. -->
|
||||||
|
<field name="IDC" start="7" end="7"/>
|
||||||
|
<!-- Cumulative saturation bit, Advanced SIMD only. -->
|
||||||
|
<field name="QC" start="27" end="27"/>
|
||||||
|
<!-- When AArch32 is supported at any Exception level and AArch32
|
||||||
|
floating-point is implemented: Overflow condition flag for AArch32
|
||||||
|
floating-point comparison operations. -->
|
||||||
|
<field name="V" start="28" end="28"/>
|
||||||
|
<!-- When AArch32 is supported at any Exception level and AArch32
|
||||||
|
floating-point is implemented:
|
||||||
|
Carry condition flag for AArch32 floating-point comparison operations.
|
||||||
|
-->
|
||||||
|
<field name="C" start="29" end="29"/>
|
||||||
|
<!-- When AArch32 is supported at any Exception level and AArch32
|
||||||
|
floating-point is implemented:
|
||||||
|
Zero condition flag for AArch32 floating-point comparison operations.
|
||||||
|
-->
|
||||||
|
<field name="Z" start="30" end="30"/>
|
||||||
|
<!-- When AArch32 is supported at any Exception level and AArch32
|
||||||
|
floating-point is implemented:
|
||||||
|
Negative condition flag for AArch32 floating-point comparison
|
||||||
|
operations. -->
|
||||||
|
<field name="N" start="31" end="31"/>
|
||||||
|
</flags>
|
||||||
|
<reg name="fpsr" bitsize="32" type="fpsr_flags"/>
|
||||||
|
|
||||||
|
<flags id="fpcr_flags" size="4">
|
||||||
|
<!-- Flush Inputs to Zero (part of Armv8.7). -->
|
||||||
|
<field name="FIZ" start="0" end="0"/>
|
||||||
|
<!-- Alternate Handling (part of Armv8.7). -->
|
||||||
|
<field name="AH" start="1" end="1"/>
|
||||||
|
<!-- Controls how the output elements other than the lowest element of the
|
||||||
|
vector are determined for Advanced SIMD scalar instructions (part of
|
||||||
|
Armv8.7). -->
|
||||||
|
<field name="NEP" start="2" end="2"/>
|
||||||
|
<!-- Invalid Operation floating-point exception trap enable. -->
|
||||||
|
<field name="IOE" start="8" end="8"/>
|
||||||
|
<!-- Divide by Zero floating-point exception trap enable. -->
|
||||||
|
<field name="DZE" start="9" end="9"/>
|
||||||
|
<!-- Overflow floating-point exception trap enable. -->
|
||||||
|
<field name="OFE" start="10" end="10"/>
|
||||||
|
<!-- Underflow floating-point exception trap enable. -->
|
||||||
|
<field name="UFE" start="11" end="11"/>
|
||||||
|
<!-- Inexact floating-point exception trap enable. -->
|
||||||
|
<field name="IXE" start="12" end="12"/>
|
||||||
|
<!-- Input Denormal floating-point exception trap enable. -->
|
||||||
|
<field name="IDE" start="15" end="15"/>
|
||||||
|
<!-- Flush-to-zero mode control bit on half-precision data-processing
|
||||||
|
instructions. -->
|
||||||
|
<field name="FZ16" start="19" end="19"/>
|
||||||
|
<!-- Rounding Mode control field. -->
|
||||||
|
<field name="RMode" start="22" end="23"/>
|
||||||
|
<!-- Flush-to-zero mode control bit. -->
|
||||||
|
<field name="FZ" start="24" end="24"/>
|
||||||
|
<!-- Default NaN mode control bit. -->
|
||||||
|
<field name="DN" start="25" end="25"/>
|
||||||
|
<!-- Alternative half-precision control bit. -->
|
||||||
|
<field name="AHP" start="26" end="26"/>
|
||||||
|
</flags>
|
||||||
|
<reg name="fpcr" bitsize="32" type="fpcr_flags"/>
|
||||||
|
</feature>
|
27
src/Ryujinx.HLE/Debugger/GdbXml/arm-core.xml
Normal file
27
src/Ryujinx.HLE/Debugger/GdbXml/arm-core.xml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!-- Copyright (C) 2008 Free Software Foundation, Inc.
|
||||||
|
|
||||||
|
Copying and distribution of this file, with or without modification,
|
||||||
|
are permitted in any medium without royalty provided the copyright
|
||||||
|
notice and this notice are preserved. -->
|
||||||
|
|
||||||
|
<!DOCTYPE feature SYSTEM "gdb-target.dtd">
|
||||||
|
<feature name="org.gnu.gdb.arm.core">
|
||||||
|
<reg name="r0" bitsize="32"/>
|
||||||
|
<reg name="r1" bitsize="32"/>
|
||||||
|
<reg name="r2" bitsize="32"/>
|
||||||
|
<reg name="r3" bitsize="32"/>
|
||||||
|
<reg name="r4" bitsize="32"/>
|
||||||
|
<reg name="r5" bitsize="32"/>
|
||||||
|
<reg name="r6" bitsize="32"/>
|
||||||
|
<reg name="r7" bitsize="32"/>
|
||||||
|
<reg name="r8" bitsize="32"/>
|
||||||
|
<reg name="r9" bitsize="32"/>
|
||||||
|
<reg name="r10" bitsize="32"/>
|
||||||
|
<reg name="r11" bitsize="32"/>
|
||||||
|
<reg name="r12" bitsize="32"/>
|
||||||
|
<reg name="sp" bitsize="32" type="data_ptr"/>
|
||||||
|
<reg name="lr" bitsize="32"/>
|
||||||
|
<reg name="pc" bitsize="32" type="code_ptr"/>
|
||||||
|
<reg name="cpsr" bitsize="32" />
|
||||||
|
</feature>
|
86
src/Ryujinx.HLE/Debugger/GdbXml/arm-neon.xml
Normal file
86
src/Ryujinx.HLE/Debugger/GdbXml/arm-neon.xml
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!-- Copyright (C) 2008 Free Software Foundation, Inc.
|
||||||
|
|
||||||
|
Copying and distribution of this file, with or without modification,
|
||||||
|
are permitted in any medium without royalty provided the copyright
|
||||||
|
notice and this notice are preserved. -->
|
||||||
|
<!DOCTYPE feature SYSTEM "gdb-target.dtd">
|
||||||
|
<feature name="org.gnu.gdb.arm.vfp">
|
||||||
|
<vector id="neon_uint8x8" type="uint8" count="8"/>
|
||||||
|
<vector id="neon_uint16x4" type="uint16" count="4"/>
|
||||||
|
<vector id="neon_uint32x2" type="uint32" count="2"/>
|
||||||
|
<vector id="neon_float32x2" type="ieee_single" count="2"/>
|
||||||
|
<union id="neon_d">
|
||||||
|
<field name="u8" type="neon_uint8x8"/>
|
||||||
|
<field name="u16" type="neon_uint16x4"/>
|
||||||
|
<field name="u32" type="neon_uint32x2"/>
|
||||||
|
<field name="u64" type="uint64"/>
|
||||||
|
<field name="f32" type="neon_float32x2"/>
|
||||||
|
<field name="f64" type="ieee_double"/>
|
||||||
|
</union>
|
||||||
|
<vector id="neon_uint8x16" type="uint8" count="16"/>
|
||||||
|
<vector id="neon_uint16x8" type="uint16" count="8"/>
|
||||||
|
<vector id="neon_uint32x4" type="uint32" count="4"/>
|
||||||
|
<vector id="neon_uint64x2" type="uint64" count="2"/>
|
||||||
|
<vector id="neon_float32x4" type="ieee_single" count="4"/>
|
||||||
|
<vector id="neon_float64x2" type="ieee_double" count="2"/>
|
||||||
|
<union id="neon_q">
|
||||||
|
<field name="u8" type="neon_uint8x16"/>
|
||||||
|
<field name="u16" type="neon_uint16x8"/>
|
||||||
|
<field name="u32" type="neon_uint32x4"/>
|
||||||
|
<field name="u64" type="neon_uint64x2"/>
|
||||||
|
<field name="f32" type="neon_float32x4"/>
|
||||||
|
<field name="f64" type="neon_float64x2"/>
|
||||||
|
</union>
|
||||||
|
<reg name="d0" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d1" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d2" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d3" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d4" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d5" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d6" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d7" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d8" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d9" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d10" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d11" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d12" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d13" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d14" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d15" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d16" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d17" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d18" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d19" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d20" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d21" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d22" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d23" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d24" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d25" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d26" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d27" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d28" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d29" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d30" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d31" bitsize="64" type="neon_d"/>
|
||||||
|
|
||||||
|
<reg name="q0" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q1" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q2" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q3" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q4" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q5" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q6" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q7" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q8" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q9" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q10" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q11" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q12" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q13" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q14" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q15" bitsize="128" type="neon_q"/>
|
||||||
|
|
||||||
|
<reg name="fpscr" bitsize="32" type="int" group="float"/>
|
||||||
|
</feature>
|
14
src/Ryujinx.HLE/Debugger/GdbXml/target32.xml
Normal file
14
src/Ryujinx.HLE/Debugger/GdbXml/target32.xml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!-- Copyright (C) 2009-2013 Free Software Foundation, Inc.
|
||||||
|
Contributed by ARM Ltd.
|
||||||
|
|
||||||
|
Copying and distribution of this file, with or without modification,
|
||||||
|
are permitted in any medium without royalty provided the copyright
|
||||||
|
notice and this notice are preserved. -->
|
||||||
|
|
||||||
|
<!DOCTYPE target SYSTEM "gdb-target.dtd">
|
||||||
|
<target>
|
||||||
|
<architecture>arm</architecture>
|
||||||
|
<xi:include href="arm-core.xml"/>
|
||||||
|
<xi:include href="arm-neon.xml"/>
|
||||||
|
</target>
|
14
src/Ryujinx.HLE/Debugger/GdbXml/target64.xml
Normal file
14
src/Ryujinx.HLE/Debugger/GdbXml/target64.xml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!-- Copyright (C) 2009-2013 Free Software Foundation, Inc.
|
||||||
|
Contributed by ARM Ltd.
|
||||||
|
|
||||||
|
Copying and distribution of this file, with or without modification,
|
||||||
|
are permitted in any medium without royalty provided the copyright
|
||||||
|
notice and this notice are preserved. -->
|
||||||
|
|
||||||
|
<!DOCTYPE target SYSTEM "gdb-target.dtd">
|
||||||
|
<target>
|
||||||
|
<architecture>aarch64</architecture>
|
||||||
|
<xi:include href="aarch64-core.xml"/>
|
||||||
|
<xi:include href="aarch64-fpu.xml"/>
|
||||||
|
</target>
|
19
src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs
Normal file
19
src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using Ryujinx.Cpu;
|
||||||
|
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||||
|
using Ryujinx.Memory;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
internal interface IDebuggableProcess
|
||||||
|
{
|
||||||
|
void DebugStop();
|
||||||
|
void DebugContinue();
|
||||||
|
bool DebugStep(KThread thread);
|
||||||
|
KThread GetThread(ulong threadUid);
|
||||||
|
DebugState GetDebugState();
|
||||||
|
ulong[] GetThreadUids();
|
||||||
|
public void DebugInterruptHandler(IExecutionContext ctx);
|
||||||
|
IVirtualMemoryManager CpuMemory { get; }
|
||||||
|
void InvalidateCacheRegion(ulong address, ulong size);
|
||||||
|
}
|
||||||
|
}
|
6
src/Ryujinx.HLE/Debugger/Message/BreakInMessage.cs
Normal file
6
src/Ryujinx.HLE/Debugger/Message/BreakInMessage.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
struct BreakInMessage : IMessage
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
12
src/Ryujinx.HLE/Debugger/Message/CommandMessage.cs
Normal file
12
src/Ryujinx.HLE/Debugger/Message/CommandMessage.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
struct CommandMessage : IMessage
|
||||||
|
{
|
||||||
|
public string Command;
|
||||||
|
|
||||||
|
public CommandMessage(string cmd)
|
||||||
|
{
|
||||||
|
Command = cmd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
src/Ryujinx.HLE/Debugger/Message/IMessage.cs
Normal file
6
src/Ryujinx.HLE/Debugger/Message/IMessage.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
interface IMessage
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
6
src/Ryujinx.HLE/Debugger/Message/KillMessage.cs
Normal file
6
src/Ryujinx.HLE/Debugger/Message/KillMessage.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
struct KillMessage : IMessage
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
6
src/Ryujinx.HLE/Debugger/Message/SendNackMessage.cs
Normal file
6
src/Ryujinx.HLE/Debugger/Message/SendNackMessage.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
struct SendNackMessage : IMessage
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
18
src/Ryujinx.HLE/Debugger/Message/ThreadBreakMessage.cs
Normal file
18
src/Ryujinx.HLE/Debugger/Message/ThreadBreakMessage.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using IExecutionContext = Ryujinx.Cpu.IExecutionContext;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
public class ThreadBreakMessage : IMessage
|
||||||
|
{
|
||||||
|
public IExecutionContext Context { get; }
|
||||||
|
public ulong Address { get; }
|
||||||
|
public int Opcode { get; }
|
||||||
|
|
||||||
|
public ThreadBreakMessage(IExecutionContext context, ulong address, int opcode)
|
||||||
|
{
|
||||||
|
Context = context;
|
||||||
|
Address = address;
|
||||||
|
Opcode = opcode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
src/Ryujinx.HLE/Debugger/RegisterInformation.cs
Normal file
28
src/Ryujinx.HLE/Debugger/RegisterInformation.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
class RegisterInformation
|
||||||
|
{
|
||||||
|
public static readonly Dictionary<string, string> Features = new()
|
||||||
|
{
|
||||||
|
{ "target64.xml", GetEmbeddedResourceContent("target64.xml") },
|
||||||
|
{ "target32.xml", GetEmbeddedResourceContent("target32.xml") },
|
||||||
|
{ "aarch64-core.xml", GetEmbeddedResourceContent("aarch64-core.xml") },
|
||||||
|
{ "aarch64-fpu.xml", GetEmbeddedResourceContent("aarch64-fpu.xml") },
|
||||||
|
{ "arm-core.xml", GetEmbeddedResourceContent("arm-core.xml") },
|
||||||
|
{ "arm-neon.xml", GetEmbeddedResourceContent("arm-neon.xml") },
|
||||||
|
};
|
||||||
|
|
||||||
|
private static string GetEmbeddedResourceContent(string resourceName)
|
||||||
|
{
|
||||||
|
Stream stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("Ryujinx.HLE.Debugger.GdbXml." + resourceName);
|
||||||
|
StreamReader reader = new StreamReader(stream);
|
||||||
|
string result = reader.ReadToEnd();
|
||||||
|
reader.Dispose();
|
||||||
|
stream.Dispose();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
109
src/Ryujinx.HLE/Debugger/StringStream.cs
Normal file
109
src/Ryujinx.HLE/Debugger/StringStream.cs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
class StringStream
|
||||||
|
{
|
||||||
|
private readonly string Data;
|
||||||
|
private int Position;
|
||||||
|
|
||||||
|
public StringStream(string s)
|
||||||
|
{
|
||||||
|
Data = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public char ReadChar()
|
||||||
|
{
|
||||||
|
return Data[Position++];
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReadUntil(char needle)
|
||||||
|
{
|
||||||
|
int needlePos = Data.IndexOf(needle, Position);
|
||||||
|
|
||||||
|
if (needlePos == -1)
|
||||||
|
{
|
||||||
|
needlePos = Data.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
string result = Data.Substring(Position, needlePos - Position);
|
||||||
|
Position = needlePos + 1;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReadLength(int len)
|
||||||
|
{
|
||||||
|
string result = Data.Substring(Position, len);
|
||||||
|
Position += len;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReadRemaining()
|
||||||
|
{
|
||||||
|
string result = Data.Substring(Position);
|
||||||
|
Position = Data.Length;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong ReadRemainingAsHex()
|
||||||
|
{
|
||||||
|
return ulong.Parse(ReadRemaining(), NumberStyles.HexNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong ReadUntilAsHex(char needle)
|
||||||
|
{
|
||||||
|
return ulong.Parse(ReadUntil(needle), NumberStyles.HexNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong ReadLengthAsHex(int len)
|
||||||
|
{
|
||||||
|
return ulong.Parse(ReadLength(len), NumberStyles.HexNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong ReadLengthAsLEHex(int len)
|
||||||
|
{
|
||||||
|
Debug.Assert(len % 2 == 0);
|
||||||
|
|
||||||
|
ulong result = 0;
|
||||||
|
int pos = 0;
|
||||||
|
while (pos < len)
|
||||||
|
{
|
||||||
|
result += ReadLengthAsHex(2) << (4 * pos);
|
||||||
|
pos += 2;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong? ReadRemainingAsThreadUid()
|
||||||
|
{
|
||||||
|
string s = ReadRemaining();
|
||||||
|
return s == "-1" ? null : ulong.Parse(s, NumberStyles.HexNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ConsumePrefix(string prefix)
|
||||||
|
{
|
||||||
|
if (Data.Substring(Position).StartsWith(prefix))
|
||||||
|
{
|
||||||
|
Position += prefix.Length;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ConsumeRemaining(string match)
|
||||||
|
{
|
||||||
|
if (Data.Substring(Position) == match)
|
||||||
|
{
|
||||||
|
Position += match.Length;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsEmpty()
|
||||||
|
{
|
||||||
|
return Position >= Data.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -377,7 +377,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
|||||||
|
|
||||||
bool cursorVisible = false;
|
bool cursorVisible = false;
|
||||||
|
|
||||||
if (state.CursorBegin != state.CursorEnd)
|
if (state.CursorBegin != state.CursorEnd && state.CursorEnd <= state.InputText.Length)
|
||||||
{
|
{
|
||||||
Debug.Assert(state.InputText.Length > 0);
|
Debug.Assert(state.InputText.Length > 0);
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
mode = MemoryManagerMode.SoftwarePageTable;
|
mode = MemoryManagerMode.SoftwarePageTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
ICpuEngine cpuEngine = isArm64Host && (mode == MemoryManagerMode.HostMapped || mode == MemoryManagerMode.HostMappedUnsafe)
|
ICpuEngine cpuEngine = isArm64Host && (mode == MemoryManagerMode.HostMapped || mode == MemoryManagerMode.HostMappedUnsafe) && !context.Device.Configuration.EnableGdbStub
|
||||||
? new LightningJitEngine(_tickSource)
|
? new LightningJitEngine(_tickSource)
|
||||||
: new JitEngine(_tickSource);
|
: new JitEngine(_tickSource);
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ using LibHac.Fs.Shim;
|
|||||||
using LibHac.FsSystem;
|
using LibHac.FsSystem;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using Ryujinx.Cpu;
|
using Ryujinx.Cpu;
|
||||||
|
using Ryujinx.HLE.Debugger;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS.Kernel;
|
using Ryujinx.HLE.HOS.Kernel;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||||
@ -500,5 +501,21 @@ namespace Ryujinx.HLE.HOS
|
|||||||
|
|
||||||
IsPaused = pause;
|
IsPaused = pause;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal IDebuggableProcess DebugGetApplicationProcessDebugInterface()
|
||||||
|
{
|
||||||
|
lock (KernelContext.Processes)
|
||||||
|
{
|
||||||
|
return KernelContext.Processes.Values.FirstOrDefault(x => x.IsApplication)?.DebugInterface;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal KProcess DebugGetApplicationProcess()
|
||||||
|
{
|
||||||
|
lock (KernelContext.Processes)
|
||||||
|
{
|
||||||
|
return KernelContext.Processes.Values.FirstOrDefault(x => x.IsApplication);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ using Ryujinx.HLE.HOS.Kernel.Memory;
|
|||||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||||
using Ryujinx.HLE.Loaders.Elf;
|
using Ryujinx.HLE.Loaders.Elf;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@ -17,7 +18,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
|
|
||||||
private readonly KProcess _owner;
|
private readonly KProcess _owner;
|
||||||
|
|
||||||
private class Image
|
public class Image
|
||||||
{
|
{
|
||||||
public ulong BaseAddress { get; }
|
public ulong BaseAddress { get; }
|
||||||
public ulong Size { get; }
|
public ulong Size { get; }
|
||||||
@ -54,6 +55,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
|
|
||||||
trace.AppendLine($"Process: {_owner.Name}, PID: {_owner.Pid}");
|
trace.AppendLine($"Process: {_owner.Name}, PID: {_owner.Pid}");
|
||||||
|
|
||||||
|
string ThreadName = thread.GetThreadName();
|
||||||
|
|
||||||
|
if (!String.IsNullOrEmpty(ThreadName))
|
||||||
|
{
|
||||||
|
trace.AppendLine($"Thread ID: {thread.ThreadUid} ({ThreadName})");
|
||||||
|
} else {
|
||||||
|
trace.AppendLine($"Thread ID: {thread.ThreadUid}");
|
||||||
|
}
|
||||||
|
|
||||||
void AppendTrace(ulong address)
|
void AppendTrace(ulong address)
|
||||||
{
|
{
|
||||||
if (AnalyzePointer(out PointerInfo info, address, thread))
|
if (AnalyzePointer(out PointerInfo info, address, thread))
|
||||||
@ -283,7 +293,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetGuessedNsoNameFromIndex(int index)
|
public string GetGuessedNsoNameFromIndex(int index)
|
||||||
{
|
{
|
||||||
if ((uint)index > 11)
|
if ((uint)index > 11)
|
||||||
{
|
{
|
||||||
@ -316,6 +326,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Image> GetLoadedImages()
|
||||||
|
{
|
||||||
|
EnsureLoaded();
|
||||||
|
|
||||||
|
lock (_images)
|
||||||
|
{
|
||||||
|
return [.. _images];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void EnsureLoaded()
|
private void EnsureLoaded()
|
||||||
{
|
{
|
||||||
if (Interlocked.CompareExchange(ref _loaded, 1, 0) == 0)
|
if (Interlocked.CompareExchange(ref _loaded, 1, 0) == 0)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Cpu;
|
using Ryujinx.Cpu;
|
||||||
|
using Ryujinx.HLE.Debugger;
|
||||||
using Ryujinx.HLE.Exceptions;
|
using Ryujinx.HLE.Exceptions;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||||
@ -11,6 +12,8 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using ExceptionCallback = Ryujinx.Cpu.ExceptionCallback;
|
||||||
|
using ExceptionCallbackNoArgs = Ryujinx.Cpu.ExceptionCallbackNoArgs;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Kernel.Process
|
namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
{
|
{
|
||||||
@ -89,6 +92,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
public IVirtualMemoryManager CpuMemory => Context.AddressSpace;
|
public IVirtualMemoryManager CpuMemory => Context.AddressSpace;
|
||||||
|
|
||||||
public HleProcessDebugger Debugger { get; private set; }
|
public HleProcessDebugger Debugger { get; private set; }
|
||||||
|
public IDebuggableProcess DebugInterface { get; private set; }
|
||||||
|
protected int debugState = (int)DebugState.Running;
|
||||||
|
|
||||||
public KProcess(KernelContext context, bool allowCodeMemoryForJit = false) : base(context)
|
public KProcess(KernelContext context, bool allowCodeMemoryForJit = false) : base(context)
|
||||||
{
|
{
|
||||||
@ -110,6 +115,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
_threads = [];
|
_threads = [];
|
||||||
|
|
||||||
Debugger = new HleProcessDebugger(this);
|
Debugger = new HleProcessDebugger(this);
|
||||||
|
DebugInterface = new DebuggerInterface(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result InitializeKip(
|
public Result InitializeKip(
|
||||||
@ -679,6 +685,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
|
|
||||||
SetState(newState);
|
SetState(newState);
|
||||||
|
|
||||||
|
if (KernelContext.Device.Configuration.DebuggerSuspendOnStart && IsApplication)
|
||||||
|
{
|
||||||
|
mainThread.Suspend(ThreadSchedState.ThreadPauseFlag);
|
||||||
|
debugState = (int)DebugState.Stopped;
|
||||||
|
Logger.Notice.Print(LogClass.Kernel, $"Application is suspended on start for debugging.");
|
||||||
|
}
|
||||||
|
|
||||||
result = mainThread.Start();
|
result = mainThread.Start();
|
||||||
|
|
||||||
if (result != Result.Success)
|
if (result != Result.Success)
|
||||||
@ -727,9 +740,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
|
|
||||||
public IExecutionContext CreateExecutionContext()
|
public IExecutionContext CreateExecutionContext()
|
||||||
{
|
{
|
||||||
|
ExceptionCallback breakCallback = null;
|
||||||
|
ExceptionCallbackNoArgs stepCallback = null;
|
||||||
|
|
||||||
|
if (KernelContext.Device.Configuration.EnableGdbStub && KernelContext.Device.Debugger != null)
|
||||||
|
{
|
||||||
|
breakCallback = KernelContext.Device.Debugger.BreakHandler;
|
||||||
|
stepCallback = KernelContext.Device.Debugger.StepHandler;
|
||||||
|
}
|
||||||
|
|
||||||
return Context?.CreateExecutionContext(new ExceptionCallbacks(
|
return Context?.CreateExecutionContext(new ExceptionCallbacks(
|
||||||
InterruptHandler,
|
InterruptHandler,
|
||||||
null,
|
breakCallback,
|
||||||
|
stepCallback,
|
||||||
KernelContext.SyscallHandler.SvcCall,
|
KernelContext.SyscallHandler.SvcCall,
|
||||||
UndefinedInstructionHandler));
|
UndefinedInstructionHandler));
|
||||||
}
|
}
|
||||||
@ -1174,5 +1197,168 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
{
|
{
|
||||||
return Capabilities.IsSvcPermitted(svcId);
|
return Capabilities.IsSvcPermitted(svcId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class DebuggerInterface : IDebuggableProcess
|
||||||
|
{
|
||||||
|
private Barrier StepBarrier;
|
||||||
|
private readonly KProcess _parent;
|
||||||
|
private readonly KernelContext _kernelContext;
|
||||||
|
private KThread steppingThread;
|
||||||
|
|
||||||
|
public DebuggerInterface(KProcess p)
|
||||||
|
{
|
||||||
|
_parent = p;
|
||||||
|
_kernelContext = p.KernelContext;
|
||||||
|
StepBarrier = new(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DebugStop()
|
||||||
|
{
|
||||||
|
if (Interlocked.CompareExchange(ref _parent.debugState, (int)DebugState.Stopping,
|
||||||
|
(int)DebugState.Running) != (int)DebugState.Running)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_kernelContext.CriticalSection.Enter();
|
||||||
|
lock (_parent._threadingLock)
|
||||||
|
{
|
||||||
|
foreach (KThread thread in _parent._threads)
|
||||||
|
{
|
||||||
|
thread.Suspend(ThreadSchedState.ThreadPauseFlag);
|
||||||
|
thread.Context.RequestInterrupt();
|
||||||
|
if (!thread.DebugHalt.WaitOne(TimeSpan.FromMilliseconds(50)))
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Kernel, $"Failed to suspend thread {thread.ThreadUid} in time.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_parent.debugState = (int)DebugState.Stopped;
|
||||||
|
_kernelContext.CriticalSection.Leave();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DebugContinue()
|
||||||
|
{
|
||||||
|
if (Interlocked.CompareExchange(ref _parent.debugState, (int)DebugState.Running,
|
||||||
|
(int)DebugState.Stopped) != (int)DebugState.Stopped)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_kernelContext.CriticalSection.Enter();
|
||||||
|
lock (_parent._threadingLock)
|
||||||
|
{
|
||||||
|
foreach (KThread thread in _parent._threads)
|
||||||
|
{
|
||||||
|
thread.Resume(ThreadSchedState.ThreadPauseFlag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_kernelContext.CriticalSection.Leave();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DebugStep(KThread target)
|
||||||
|
{
|
||||||
|
if (_parent.debugState != (int)DebugState.Stopped)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_kernelContext.CriticalSection.Enter();
|
||||||
|
steppingThread = target;
|
||||||
|
bool waiting = target.MutexOwner != null || target.WaitingSync || target.WaitingInArbitration;
|
||||||
|
target.Context.RequestDebugStep();
|
||||||
|
if (waiting)
|
||||||
|
{
|
||||||
|
lock (_parent._threadingLock)
|
||||||
|
{
|
||||||
|
foreach (KThread thread in _parent._threads)
|
||||||
|
{
|
||||||
|
thread.Resume(ThreadSchedState.ThreadPauseFlag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
target.Resume(ThreadSchedState.ThreadPauseFlag);
|
||||||
|
}
|
||||||
|
_kernelContext.CriticalSection.Leave();
|
||||||
|
|
||||||
|
bool stepTimedOut = false;
|
||||||
|
if (!StepBarrier.SignalAndWait(TimeSpan.FromMilliseconds(2000)))
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Kernel, $"Failed to step thread {target.ThreadUid} in time.");
|
||||||
|
stepTimedOut = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_kernelContext.CriticalSection.Enter();
|
||||||
|
steppingThread = null;
|
||||||
|
if (waiting)
|
||||||
|
{
|
||||||
|
lock (_parent._threadingLock)
|
||||||
|
{
|
||||||
|
foreach (KThread thread in _parent._threads)
|
||||||
|
{
|
||||||
|
thread.Suspend(ThreadSchedState.ThreadPauseFlag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
target.Suspend(ThreadSchedState.ThreadPauseFlag);
|
||||||
|
}
|
||||||
|
_kernelContext.CriticalSection.Leave();
|
||||||
|
|
||||||
|
if (stepTimedOut)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
StepBarrier.SignalAndWait();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DebugState GetDebugState()
|
||||||
|
{
|
||||||
|
return (DebugState)_parent.debugState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong[] GetThreadUids()
|
||||||
|
{
|
||||||
|
lock (_parent._threadingLock)
|
||||||
|
{
|
||||||
|
var threads = _parent._threads.Where(x => !x.TerminationRequested).ToArray();
|
||||||
|
return threads.Select(x => x.ThreadUid).ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public KThread GetThread(ulong threadUid)
|
||||||
|
{
|
||||||
|
lock (_parent._threadingLock)
|
||||||
|
{
|
||||||
|
var threads = _parent._threads.Where(x => !x.TerminationRequested).ToArray();
|
||||||
|
return threads.FirstOrDefault(x => x.ThreadUid == threadUid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DebugInterruptHandler(IExecutionContext ctx)
|
||||||
|
{
|
||||||
|
_kernelContext.CriticalSection.Enter();
|
||||||
|
bool stepping = steppingThread != null;
|
||||||
|
_kernelContext.CriticalSection.Leave();
|
||||||
|
if (stepping)
|
||||||
|
{
|
||||||
|
StepBarrier.SignalAndWait();
|
||||||
|
StepBarrier.SignalAndWait();
|
||||||
|
}
|
||||||
|
_parent.InterruptHandler(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IVirtualMemoryManager CpuMemory { get { return _parent.CpuMemory; } }
|
||||||
|
|
||||||
|
public void InvalidateCacheRegion(ulong address, ulong size)
|
||||||
|
{
|
||||||
|
_parent.Context.InvalidateCacheRegion(address, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using ARMeilleure.State;
|
using ARMeilleure.State;
|
||||||
using Ryujinx.Cpu;
|
using Ryujinx.Cpu;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Kernel.Process
|
namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
{
|
{
|
||||||
@ -17,10 +18,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
|
|
||||||
public bool IsAarch32 { get => false; set { } }
|
public bool IsAarch32 { get => false; set { } }
|
||||||
|
|
||||||
|
public ulong ThreadUid { get; set; }
|
||||||
|
|
||||||
public bool Running { get; private set; } = true;
|
public bool Running { get; private set; } = true;
|
||||||
|
|
||||||
private readonly ulong[] _x = new ulong[32];
|
private readonly ulong[] _x = new ulong[32];
|
||||||
|
|
||||||
|
public ulong DebugPc { get; set; }
|
||||||
|
|
||||||
public ulong GetX(int index) => _x[index];
|
public ulong GetX(int index) => _x[index];
|
||||||
public void SetX(int index, ulong value) => _x[index] = value;
|
public void SetX(int index, ulong value) => _x[index] = value;
|
||||||
|
|
||||||
@ -31,6 +36,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void RequestDebugStep()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public void StopRunning()
|
public void StopRunning()
|
||||||
{
|
{
|
||||||
Running = false;
|
Running = false;
|
||||||
|
@ -301,6 +301,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
|
|
||||||
currentThread.SchedulerWaitEvent.Reset();
|
currentThread.SchedulerWaitEvent.Reset();
|
||||||
currentThread.ThreadContext.Unlock();
|
currentThread.ThreadContext.Unlock();
|
||||||
|
currentThread.DebugHalt.Set();
|
||||||
|
|
||||||
// Wake all the threads that might be waiting until this thread context is unlocked.
|
// Wake all the threads that might be waiting until this thread context is unlocked.
|
||||||
for (int core = 0; core < CpuCoresCount; core++)
|
for (int core = 0; core < CpuCoresCount; core++)
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Cpu;
|
using Ryujinx.Cpu;
|
||||||
|
using Ryujinx.HLE.Debugger;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||||
using Ryujinx.HLE.HOS.Kernel.SupervisorCall;
|
using Ryujinx.HLE.HOS.Kernel.SupervisorCall;
|
||||||
using Ryujinx.Horizon.Common;
|
using Ryujinx.Horizon.Common;
|
||||||
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Kernel.Threading
|
namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||||
@ -16,6 +19,23 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
private const int TlsUserDisableCountOffset = 0x100;
|
private const int TlsUserDisableCountOffset = 0x100;
|
||||||
private const int TlsUserInterruptFlagOffset = 0x102;
|
private const int TlsUserInterruptFlagOffset = 0x102;
|
||||||
|
|
||||||
|
// Tls -> ThreadType
|
||||||
|
private const int TlsThreadTypeOffsetAArch64 = 0x1F8;
|
||||||
|
private const int TlsThreadTypeOffsetAArch32 = 0x1FC;
|
||||||
|
|
||||||
|
// Tls -> ThreadType -> Version
|
||||||
|
private const int TlsThreadTypeVersionOffsetAArch64 = 0x46;
|
||||||
|
private const int TlsThreadTypeVersionOffsetAArch32 = 0x26;
|
||||||
|
|
||||||
|
// Tls -> ThreadType (Version 0) -> ThreadNamePointer
|
||||||
|
private const int TlsThreadTypeVersion0ThreadNamePointerOffsetAArch64 = 0x1A8;
|
||||||
|
private const int TlsThreadTypeVersion0ThreadNamePointerOffsetAArch32 = 0xE8;
|
||||||
|
|
||||||
|
// Tls -> ThreadType (Version 1) -> ThreadNamePointer
|
||||||
|
private const int TlsThreadTypeThreadNamePointerOffsetAArch64 = 0x1A0;
|
||||||
|
private const int TlsThreadTypeThreadNamePointerOffsetAArch32 = 0xE4;
|
||||||
|
|
||||||
|
|
||||||
public const int MaxWaitSyncObjects = 64;
|
public const int MaxWaitSyncObjects = 64;
|
||||||
|
|
||||||
private ManualResetEvent _schedulerWaitEvent;
|
private ManualResetEvent _schedulerWaitEvent;
|
||||||
@ -114,6 +134,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
|
|
||||||
private readonly Lock _activityOperationLock = new();
|
private readonly Lock _activityOperationLock = new();
|
||||||
|
|
||||||
|
internal readonly ManualResetEvent DebugHalt = new(false);
|
||||||
|
|
||||||
public KThread(KernelContext context) : base(context)
|
public KThread(KernelContext context) : base(context)
|
||||||
{
|
{
|
||||||
WaitSyncObjects = new KSynchronizationObject[MaxWaitSyncObjects];
|
WaitSyncObjects = new KSynchronizationObject[MaxWaitSyncObjects];
|
||||||
@ -202,8 +224,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
}
|
}
|
||||||
|
|
||||||
Context.TpidrroEl0 = (long)_tlsAddress;
|
Context.TpidrroEl0 = (long)_tlsAddress;
|
||||||
|
Context.DebugPc = _entrypoint;
|
||||||
|
|
||||||
ThreadUid = KernelContext.NewThreadUid();
|
ThreadUid = KernelContext.NewThreadUid();
|
||||||
|
Context.ThreadUid = ThreadUid;
|
||||||
|
|
||||||
HostThread.Name = customThreadStart != null ? $"HLE.OsThread.{ThreadUid}" : $"HLE.GuestThread.{ThreadUid}";
|
HostThread.Name = customThreadStart != null ? $"HLE.OsThread.{ThreadUid}" : $"HLE.GuestThread.{ThreadUid}";
|
||||||
|
|
||||||
@ -307,7 +331,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
{
|
{
|
||||||
KernelContext.CriticalSection.Enter();
|
KernelContext.CriticalSection.Enter();
|
||||||
|
|
||||||
if (Owner != null && Owner.PinnedThreads[KernelStatic.GetCurrentThread().CurrentCore] == this)
|
KThread currentThread = KernelStatic.GetCurrentThread();
|
||||||
|
|
||||||
|
if (Owner != null && currentThread != null && Owner.PinnedThreads[currentThread.CurrentCore] == this)
|
||||||
{
|
{
|
||||||
Owner.UnpinThread(this);
|
Owner.UnpinThread(this);
|
||||||
}
|
}
|
||||||
@ -362,7 +388,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
{
|
{
|
||||||
ThreadSchedState state = PrepareForTermination();
|
ThreadSchedState state = PrepareForTermination();
|
||||||
|
|
||||||
if (state != ThreadSchedState.TerminationPending)
|
if (KernelStatic.GetCurrentThread() == this && state != ThreadSchedState.TerminationPending)
|
||||||
{
|
{
|
||||||
KernelContext.Synchronization.WaitFor(new KSynchronizationObject[] { this }, -1, out _);
|
KernelContext.Synchronization.WaitFor(new KSynchronizationObject[] { this }, -1, out _);
|
||||||
}
|
}
|
||||||
@ -1248,6 +1274,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
private void ThreadStart()
|
private void ThreadStart()
|
||||||
{
|
{
|
||||||
_schedulerWaitEvent.WaitOne();
|
_schedulerWaitEvent.WaitOne();
|
||||||
|
DebugHalt.Reset();
|
||||||
KernelStatic.SetKernelContext(KernelContext, this);
|
KernelStatic.SetKernelContext(KernelContext, this);
|
||||||
|
|
||||||
if (_customThreadStart != null)
|
if (_customThreadStart != null)
|
||||||
@ -1431,5 +1458,84 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
{
|
{
|
||||||
Owner.CpuMemory.Write<ushort>(_tlsAddress + TlsUserInterruptFlagOffset, 0);
|
Owner.CpuMemory.Write<ushort>(_tlsAddress + TlsUserInterruptFlagOffset, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetThreadName()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ulong threadNamePtr = 0;
|
||||||
|
if (Context.IsAarch32)
|
||||||
|
{
|
||||||
|
uint threadTypePtr32 = Owner.CpuMemory.Read<uint>(_tlsAddress + TlsThreadTypeOffsetAArch32);
|
||||||
|
if (threadTypePtr32 == 0)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
ushort version = Owner.CpuMemory.Read<ushort>(threadTypePtr32 + TlsThreadTypeVersionOffsetAArch32);
|
||||||
|
switch (version)
|
||||||
|
{
|
||||||
|
case 0x0000:
|
||||||
|
case 0xFFFF:
|
||||||
|
threadNamePtr = Owner.CpuMemory.Read<uint>(threadTypePtr32 + TlsThreadTypeVersion0ThreadNamePointerOffsetAArch32);
|
||||||
|
break;
|
||||||
|
case 0x0001:
|
||||||
|
threadNamePtr = Owner.CpuMemory.Read<uint>(threadTypePtr32 + TlsThreadTypeThreadNamePointerOffsetAArch32);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Logger.Warning?.Print(LogClass.Kernel, $"Unknown ThreadType struct version: {version}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ulong threadTypePtr64 = Owner.CpuMemory.Read<ulong>(_tlsAddress + TlsThreadTypeOffsetAArch64);
|
||||||
|
if (threadTypePtr64 == 0)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
ushort version = Owner.CpuMemory.Read<ushort>(threadTypePtr64 + TlsThreadTypeVersionOffsetAArch64);
|
||||||
|
switch (version)
|
||||||
|
{
|
||||||
|
case 0x0000:
|
||||||
|
case 0xFFFF:
|
||||||
|
threadNamePtr = Owner.CpuMemory.Read<ulong>(threadTypePtr64 + TlsThreadTypeVersion0ThreadNamePointerOffsetAArch64);
|
||||||
|
break;
|
||||||
|
case 0x0001:
|
||||||
|
threadNamePtr = Owner.CpuMemory.Read<ulong>(threadTypePtr64 + TlsThreadTypeThreadNamePointerOffsetAArch64);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Logger.Warning?.Print(LogClass.Kernel, $"Unknown ThreadType struct version: {version}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (threadNamePtr == 0)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
List<byte> nameBytes = new();
|
||||||
|
for (int i = 0; i < 0x20; i++)
|
||||||
|
{
|
||||||
|
byte b = Owner.CpuMemory.Read<byte>(threadNamePtr + (ulong)i);
|
||||||
|
if (b == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
nameBytes.Add(b);
|
||||||
|
}
|
||||||
|
return Encoding.UTF8.GetString(nameBytes.ToArray());
|
||||||
|
|
||||||
|
} catch (InvalidMemoryRegionException)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Kernel, "Failed to get thread name.");
|
||||||
|
return "";
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.Error?.Print(LogClass.Kernel, $"Error getting thread name: {e.Message}");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,21 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
|||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[CommandCmif(3)] // 20.0.0+
|
||||||
|
// CreateLibraryAppletEx(u32, u32, u64) -> object<nn::am::service::ILibraryAppletAccessor>
|
||||||
|
public ResultCode CreateLibraryAppletEx(ServiceCtx context)
|
||||||
|
{
|
||||||
|
AppletId appletId = (AppletId)context.RequestData.ReadInt32();
|
||||||
|
|
||||||
|
_ = context.RequestData.ReadInt32(); // libraryAppletMode
|
||||||
|
|
||||||
|
_ = context.RequestData.ReadUInt64(); // threadId
|
||||||
|
|
||||||
|
MakeObject(context, new ILibraryAppletAccessor(appletId, context.Device.System));
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
[CommandCmif(10)]
|
[CommandCmif(10)]
|
||||||
// CreateStorage(u64) -> object<nn::am::service::IStorage>
|
// CreateStorage(u64) -> object<nn::am::service::IStorage>
|
||||||
public ResultCode CreateStorage(ServiceCtx context)
|
public ResultCode CreateStorage(ServiceCtx context)
|
||||||
|
@ -885,7 +885,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
|
|||||||
// F_SETFL
|
// F_SETFL
|
||||||
else if (cmd == 0x4)
|
else if (cmd == 0x4)
|
||||||
{
|
{
|
||||||
socket.Blocking = (arg & 0x800) != 0;
|
socket.Blocking = (arg & 0x800) == 0;
|
||||||
result = 0;
|
result = 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -194,6 +194,21 @@ namespace Ryujinx.HLE
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Action RefreshInputConfig { internal get; set; }
|
public Action RefreshInputConfig { internal get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables gdbstub to allow for debugging of the guest .
|
||||||
|
/// </summary>
|
||||||
|
public bool EnableGdbStub { internal get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A TCP port to use to expose a gdbstub for a debugger to connect to.
|
||||||
|
/// </summary>
|
||||||
|
public ushort GdbStubPort { internal get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Suspend execution when starting an application
|
||||||
|
/// </summary>
|
||||||
|
public bool DebuggerSuspendOnStart { internal get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The desired hacky workarounds.
|
/// The desired hacky workarounds.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -222,6 +237,9 @@ namespace Ryujinx.HLE
|
|||||||
bool multiplayerDisableP2p,
|
bool multiplayerDisableP2p,
|
||||||
string multiplayerLdnPassphrase,
|
string multiplayerLdnPassphrase,
|
||||||
string multiplayerLdnServer,
|
string multiplayerLdnServer,
|
||||||
|
bool enableGdbStub,
|
||||||
|
ushort gdbStubPort,
|
||||||
|
bool debuggerSuspendOnStart,
|
||||||
int customVSyncInterval,
|
int customVSyncInterval,
|
||||||
EnabledDirtyHack[] dirtyHacks = null)
|
EnabledDirtyHack[] dirtyHacks = null)
|
||||||
{
|
{
|
||||||
@ -248,6 +266,9 @@ namespace Ryujinx.HLE
|
|||||||
MultiplayerDisableP2p = multiplayerDisableP2p;
|
MultiplayerDisableP2p = multiplayerDisableP2p;
|
||||||
MultiplayerLdnPassphrase = multiplayerLdnPassphrase;
|
MultiplayerLdnPassphrase = multiplayerLdnPassphrase;
|
||||||
MultiplayerLdnServer = multiplayerLdnServer;
|
MultiplayerLdnServer = multiplayerLdnServer;
|
||||||
|
EnableGdbStub = enableGdbStub;
|
||||||
|
GdbStubPort = gdbStubPort;
|
||||||
|
DebuggerSuspendOnStart = debuggerSuspendOnStart;
|
||||||
Hacks = dirtyHacks ?? [];
|
Hacks = dirtyHacks ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,12 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<None Remove="Debugger\GdbXml\aarch64-core.xml" />
|
||||||
|
<None Remove="Debugger\GdbXml\aarch64-fpu.xml" />
|
||||||
|
<None Remove="Debugger\GdbXml\arm-core.xml" />
|
||||||
|
<None Remove="Debugger\GdbXml\arm-neon.xml" />
|
||||||
|
<None Remove="Debugger\GdbXml\target64.xml" />
|
||||||
|
<None Remove="Debugger\GdbXml\target32.xml" />
|
||||||
<None Remove="Homebrew.npdm" />
|
<None Remove="Homebrew.npdm" />
|
||||||
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Logo_Ryujinx.png" />
|
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Logo_Ryujinx.png" />
|
||||||
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnA.png" />
|
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnA.png" />
|
||||||
@ -42,6 +48,12 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="Debugger\GdbXml\aarch64-core.xml" />
|
||||||
|
<EmbeddedResource Include="Debugger\GdbXml\aarch64-fpu.xml" />
|
||||||
|
<EmbeddedResource Include="Debugger\GdbXml\arm-core.xml" />
|
||||||
|
<EmbeddedResource Include="Debugger\GdbXml\arm-neon.xml" />
|
||||||
|
<EmbeddedResource Include="Debugger\GdbXml\target64.xml" />
|
||||||
|
<EmbeddedResource Include="Debugger\GdbXml\target32.xml" />
|
||||||
<EmbeddedResource Include="Homebrew.npdm" />
|
<EmbeddedResource Include="Homebrew.npdm" />
|
||||||
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Logo_Ryujinx.png" />
|
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Logo_Ryujinx.png" />
|
||||||
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnA.png" />
|
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnA.png" />
|
||||||
|
@ -14,6 +14,7 @@ using Ryujinx.HLE.Loaders.Processes;
|
|||||||
using Ryujinx.HLE.UI;
|
using Ryujinx.HLE.UI;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Ryujinx.HLE
|
namespace Ryujinx.HLE
|
||||||
{
|
{
|
||||||
@ -41,6 +42,7 @@ namespace Ryujinx.HLE
|
|||||||
public Hid Hid { get; }
|
public Hid Hid { get; }
|
||||||
public TamperMachine TamperMachine { get; }
|
public TamperMachine TamperMachine { get; }
|
||||||
public IHostUIHandler UIHandler { get; }
|
public IHostUIHandler UIHandler { get; }
|
||||||
|
public Debugger.Debugger Debugger { get; }
|
||||||
|
|
||||||
public int CpuCoresCount = 4; // Switch has a quad-core Tegra X1 SoC
|
public int CpuCoresCount = 4; // Switch has a quad-core Tegra X1 SoC
|
||||||
|
|
||||||
@ -72,6 +74,7 @@ namespace Ryujinx.HLE
|
|||||||
AudioDeviceDriver = new CompatLayerHardwareDeviceDriver(Configuration.AudioDeviceDriver);
|
AudioDeviceDriver = new CompatLayerHardwareDeviceDriver(Configuration.AudioDeviceDriver);
|
||||||
Memory = new MemoryBlock(Configuration.MemoryConfiguration.ToDramSize(), memoryAllocationFlags);
|
Memory = new MemoryBlock(Configuration.MemoryConfiguration.ToDramSize(), memoryAllocationFlags);
|
||||||
Gpu = new GpuContext(Configuration.GpuRenderer, DirtyHacks);
|
Gpu = new GpuContext(Configuration.GpuRenderer, DirtyHacks);
|
||||||
|
Debugger = Configuration.EnableGdbStub ? new Debugger.Debugger(this, Configuration.GdbStubPort) : null;
|
||||||
System = new HOS.Horizon(this);
|
System = new HOS.Horizon(this);
|
||||||
Statistics = new PerformanceStatistics(this);
|
Statistics = new PerformanceStatistics(this);
|
||||||
Hid = new Hid(this, System.HidStorage);
|
Hid = new Hid(this, System.HidStorage);
|
||||||
@ -173,6 +176,7 @@ namespace Ryujinx.HLE
|
|||||||
AudioDeviceDriver.Dispose();
|
AudioDeviceDriver.Dispose();
|
||||||
FileSystem.Dispose();
|
FileSystem.Dispose();
|
||||||
Memory.Dispose();
|
Memory.Dispose();
|
||||||
|
Debugger?.Dispose();
|
||||||
|
|
||||||
TitleIDs.CurrentApplication.Value = null;
|
TitleIDs.CurrentApplication.Value = null;
|
||||||
Shared = null;
|
Shared = null;
|
||||||
|
@ -372,6 +372,12 @@
|
|||||||
<Setter Property="BorderThickness"
|
<Setter Property="BorderThickness"
|
||||||
Value="2"/>
|
Value="2"/>
|
||||||
</Style>
|
</Style>
|
||||||
|
<Style Selector="Border.listbox-item-style">
|
||||||
|
<Setter Property="Padding" Value="10" />
|
||||||
|
<Setter Property="Margin" Value="5,0,5,0" />
|
||||||
|
<Setter Property="CornerRadius" Value="5" />
|
||||||
|
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
|
||||||
|
</Style>
|
||||||
<Style Selector="ListBox ListBoxItem:selected /template/ ContentPresenter">
|
<Style Selector="ListBox ListBoxItem:selected /template/ ContentPresenter">
|
||||||
<Setter Property="Background"
|
<Setter Property="Background"
|
||||||
Value="{DynamicResource AppListBackgroundColor}" />
|
Value="{DynamicResource AppListBackgroundColor}" />
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
namespace Ryujinx.Ava.Common.Models.Github
|
|
||||||
{
|
|
||||||
public class GithubReleaseAssetJsonResponse
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string State { get; set; }
|
|
||||||
public string BrowserDownloadUrl { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Common.Models.Github
|
|
||||||
{
|
|
||||||
public class GithubReleasesJsonResponse
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
public string TagName { get; set; }
|
|
||||||
public List<GithubReleaseAssetJsonResponse> Assets { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Common.Models.Github
|
|
||||||
{
|
|
||||||
[JsonSerializable(typeof(GithubReleasesJsonResponse), GenerationMode = JsonSourceGenerationMode.Metadata)]
|
|
||||||
public partial class GithubReleasesJsonSerializerContext : JsonSerializerContext;
|
|
||||||
}
|
|
@ -338,6 +338,9 @@ namespace Ryujinx.Headless
|
|||||||
false,
|
false,
|
||||||
string.Empty,
|
string.Empty,
|
||||||
string.Empty,
|
string.Empty,
|
||||||
|
options.EnableGdbStub,
|
||||||
|
options.GdbStubPort,
|
||||||
|
options.DebuggerSuspendOnStart,
|
||||||
options.CustomVSyncInterval
|
options.CustomVSyncInterval
|
||||||
)
|
)
|
||||||
.Configure(
|
.Configure(
|
||||||
|
@ -423,6 +423,17 @@ namespace Ryujinx.Headless
|
|||||||
[Option("skip-user-profiles-manager", Required = false, Default = false, HelpText = "Enable skips the Profiles Manager popup during gameplay. Select the desired profile before starting the game")]
|
[Option("skip-user-profiles-manager", Required = false, Default = false, HelpText = "Enable skips the Profiles Manager popup during gameplay. Select the desired profile before starting the game")]
|
||||||
public bool SkipUserProfilesManager { get; set; }
|
public bool SkipUserProfilesManager { get; set; }
|
||||||
|
|
||||||
|
// Debug
|
||||||
|
|
||||||
|
[Option("enable-gdb-stub", Required = false, Default = false, HelpText = "Enables the GDB stub so that a developer can attach a debugger to the emulated process.")]
|
||||||
|
public bool EnableGdbStub { get; set; }
|
||||||
|
|
||||||
|
[Option("gdb-stub-port", Required = false, Default = 55555, HelpText = "Specifies which TCP port the GDB stub listens on.")]
|
||||||
|
public ushort GdbStubPort { get; set; }
|
||||||
|
|
||||||
|
[Option("suspend-on-start", Required = false, Default = false, HelpText = "Suspend execution when starting an application.")]
|
||||||
|
public bool DebuggerSuspendOnStart { get; set; }
|
||||||
|
|
||||||
// Values
|
// Values
|
||||||
|
|
||||||
[Value(0, MetaName = "input", HelpText = "Input to load.", Required = true)]
|
[Value(0, MetaName = "input", HelpText = "Input to load.", Required = true)]
|
||||||
|
@ -35,6 +35,7 @@ namespace Ryujinx.Ava
|
|||||||
public static string Version { get; private set; }
|
public static string Version { get; private set; }
|
||||||
public static string ConfigurationPath { get; private set; }
|
public static string ConfigurationPath { get; private set; }
|
||||||
public static string GlobalConfigurationPath { get; private set; }
|
public static string GlobalConfigurationPath { get; private set; }
|
||||||
|
public static bool UseExtraConfig { get; set; }
|
||||||
public static bool PreviewerDetached { get; private set; }
|
public static bool PreviewerDetached { get; private set; }
|
||||||
public static bool UseHardwareAcceleration { get; private set; }
|
public static bool UseHardwareAcceleration { get; private set; }
|
||||||
public static string BackendThreadingArg { get; private set; }
|
public static string BackendThreadingArg { get; private set; }
|
||||||
@ -159,7 +160,8 @@ namespace Ryujinx.Ava
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetDirGameUserConfig(string gameId, bool rememberGlobalDir = false, bool changeFolderForGame = false)
|
|
||||||
|
public static string GetDirGameUserConfig(string gameId, bool changeFolderForGame = false)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(gameId))
|
if (string.IsNullOrEmpty(gameId))
|
||||||
{
|
{
|
||||||
@ -168,15 +170,10 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
string gameDir = Path.Combine(AppDataManager.GamesDirPath, gameId, ReleaseInformation.ConfigName);
|
string gameDir = Path.Combine(AppDataManager.GamesDirPath, gameId, ReleaseInformation.ConfigName);
|
||||||
|
|
||||||
// Should load with the game if there is a custom setting for the game
|
|
||||||
if (rememberGlobalDir)
|
|
||||||
{
|
|
||||||
GlobalConfigurationPath = ConfigurationPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changeFolderForGame)
|
if (changeFolderForGame)
|
||||||
{
|
{
|
||||||
ConfigurationPath = gameDir;
|
ConfigurationPath = gameDir;
|
||||||
|
UseExtraConfig = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return gameDir;
|
return gameDir;
|
||||||
@ -184,8 +181,6 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
public static void ReloadConfig()
|
public static void ReloadConfig()
|
||||||
{
|
{
|
||||||
//It is necessary that when a user setting appears, the global setting remains available
|
|
||||||
GlobalConfigurationPath = null;
|
|
||||||
|
|
||||||
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
|
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
|
||||||
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
|
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
|
||||||
@ -225,6 +220,12 @@ namespace Ryujinx.Ava
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When you first load the program, copy to remember the path for the global configuration
|
||||||
|
if (GlobalConfigurationPath == null)
|
||||||
|
{
|
||||||
|
GlobalConfigurationPath = ConfigurationPath;
|
||||||
|
}
|
||||||
|
|
||||||
UseHardwareAcceleration = ConfigurationState.Instance.EnableHardwareAcceleration;
|
UseHardwareAcceleration = ConfigurationState.Instance.EnableHardwareAcceleration;
|
||||||
|
|
||||||
// Check if graphics backend was overridden
|
// Check if graphics backend was overridden
|
||||||
|
@ -65,6 +65,8 @@
|
|||||||
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
|
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
|
||||||
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" />
|
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" />
|
||||||
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'win-arm64'" />
|
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'win-arm64'" />
|
||||||
|
<PackageReference Include="Ryujinx.UpdateClient" />
|
||||||
|
<PackageReference Include="Ryujinx.Systems.Update.Common" />
|
||||||
<PackageReference Include="securifybv.ShellLink" />
|
<PackageReference Include="securifybv.ShellLink" />
|
||||||
<PackageReference Include="Sep" />
|
<PackageReference Include="Sep" />
|
||||||
<PackageReference Include="Silk.NET.Vulkan" />
|
<PackageReference Include="Silk.NET.Vulkan" />
|
||||||
|
@ -75,6 +75,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
|
|
||||||
private readonly long _ticksPerFrame;
|
private readonly long _ticksPerFrame;
|
||||||
private readonly Stopwatch _chrono;
|
private readonly Stopwatch _chrono;
|
||||||
|
private readonly Stopwatch _playTimer;
|
||||||
private long _ticks;
|
private long _ticks;
|
||||||
|
|
||||||
private readonly AccountManager _accountManager;
|
private readonly AccountManager _accountManager;
|
||||||
@ -175,6 +176,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
|
|
||||||
_chrono = new Stopwatch();
|
_chrono = new Stopwatch();
|
||||||
_ticksPerFrame = Stopwatch.Frequency / TargetFps;
|
_ticksPerFrame = Stopwatch.Frequency / TargetFps;
|
||||||
|
_playTimer = new Stopwatch();
|
||||||
|
|
||||||
if (ApplicationPath.StartsWith("@SystemContent"))
|
if (ApplicationPath.StartsWith("@SystemContent"))
|
||||||
{
|
{
|
||||||
@ -216,6 +218,10 @@ namespace Ryujinx.Ava.Systems
|
|||||||
ConfigurationState.Instance.Multiplayer.LdnServer.Event += UpdateLdnServerState;
|
ConfigurationState.Instance.Multiplayer.LdnServer.Event += UpdateLdnServerState;
|
||||||
ConfigurationState.Instance.Multiplayer.DisableP2p.Event += UpdateDisableP2pState;
|
ConfigurationState.Instance.Multiplayer.DisableP2p.Event += UpdateDisableP2pState;
|
||||||
|
|
||||||
|
ConfigurationState.Instance.Debug.EnableGdbStub.Event += UpdateEnableGdbStubState;
|
||||||
|
ConfigurationState.Instance.Debug.GdbStubPort.Event += UpdateGdbStubPortState;
|
||||||
|
ConfigurationState.Instance.Debug.DebuggerSuspendOnStart.Event += UpdateDebuggerSuspendOnStartState;
|
||||||
|
|
||||||
_gpuCancellationTokenSource = new CancellationTokenSource();
|
_gpuCancellationTokenSource = new CancellationTokenSource();
|
||||||
_gpuDoneEvent = new ManualResetEvent(false);
|
_gpuDoneEvent = new ManualResetEvent(false);
|
||||||
}
|
}
|
||||||
@ -461,7 +467,15 @@ namespace Ryujinx.Ava.Systems
|
|||||||
|
|
||||||
DisplaySleep.Prevent();
|
DisplaySleep.Prevent();
|
||||||
|
|
||||||
NpadManager.Initialize(Device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
|
if (ConfigurationState.Instance.System.UseInputGlobalConfig.Value && Program.UseExtraConfig)
|
||||||
|
{
|
||||||
|
NpadManager.Initialize(Device, ConfigurationState.InstanceExtra.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NpadManager.Initialize(Device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
|
||||||
|
}
|
||||||
|
|
||||||
TouchScreenManager.Initialize(Device);
|
TouchScreenManager.Initialize(Device);
|
||||||
|
|
||||||
_viewModel.IsGameRunning = true;
|
_viewModel.IsGameRunning = true;
|
||||||
@ -554,9 +568,25 @@ namespace Ryujinx.Ava.Systems
|
|||||||
Device.Configuration.MultiplayerDisableP2p = e.NewValue;
|
Device.Configuration.MultiplayerDisableP2p = e.NewValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateEnableGdbStubState(object sender, ReactiveEventArgs<bool> e)
|
||||||
|
{
|
||||||
|
Device.Configuration.EnableGdbStub = e.NewValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateGdbStubPortState(object sender, ReactiveEventArgs<ushort> e)
|
||||||
|
{
|
||||||
|
Device.Configuration.GdbStubPort = e.NewValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateDebuggerSuspendOnStartState(object sender, ReactiveEventArgs<bool> e)
|
||||||
|
{
|
||||||
|
Device.Configuration.DebuggerSuspendOnStart = e.NewValue;
|
||||||
|
}
|
||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
_isActive = false;
|
_isActive = false;
|
||||||
|
_playTimer.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Exit()
|
private void Exit()
|
||||||
@ -608,7 +638,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
private void Dispose()
|
private void Dispose()
|
||||||
{
|
{
|
||||||
if (Device.Processes != null)
|
if (Device.Processes != null)
|
||||||
MainWindowViewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText);
|
MainWindowViewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText, _playTimer.Elapsed);
|
||||||
|
|
||||||
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
|
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
|
||||||
ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState;
|
ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState;
|
||||||
@ -627,6 +657,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
_gpuCancellationTokenSource.Dispose();
|
_gpuCancellationTokenSource.Dispose();
|
||||||
|
|
||||||
_chrono.Stop();
|
_chrono.Stop();
|
||||||
|
_playTimer.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DisposeGpu()
|
public void DisposeGpu()
|
||||||
@ -860,6 +891,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText,
|
ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText,
|
||||||
appMetadata => appMetadata.UpdatePreGame()
|
appMetadata => appMetadata.UpdatePreGame()
|
||||||
);
|
);
|
||||||
|
_playTimer.Start();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -869,6 +901,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
Device?.System.TogglePauseEmulation(false);
|
Device?.System.TogglePauseEmulation(false);
|
||||||
|
|
||||||
_viewModel.IsPaused = false;
|
_viewModel.IsPaused = false;
|
||||||
|
_playTimer.Start();
|
||||||
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI);
|
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI);
|
||||||
Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed");
|
Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed");
|
||||||
}
|
}
|
||||||
@ -878,6 +911,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
Device?.System.TogglePauseEmulation(true);
|
Device?.System.TogglePauseEmulation(true);
|
||||||
|
|
||||||
_viewModel.IsPaused = true;
|
_viewModel.IsPaused = true;
|
||||||
|
_playTimer.Stop();
|
||||||
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI, LocaleManager.Instance[LocaleKeys.Paused]);
|
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI, LocaleManager.Instance[LocaleKeys.Paused]);
|
||||||
Logger.Info?.Print(LogClass.Emulation, "Emulation was paused");
|
Logger.Info?.Print(LogClass.Emulation, "Emulation was paused");
|
||||||
}
|
}
|
||||||
@ -1144,6 +1178,24 @@ namespace Ryujinx.Ava.Systems
|
|||||||
|
|
||||||
_dialogShown = true;
|
_dialogShown = true;
|
||||||
|
|
||||||
|
// The hard-coded hotkey mapped to exit is Escape, but it's also the same key
|
||||||
|
// that causes the dialog we launch to close (without doing anything). In release
|
||||||
|
// mode, a race is observed that between ShowExitPrompt() appearing on KeyDown
|
||||||
|
// and the ContentDialog we create seeing the key state before KeyUp. Merely waiting
|
||||||
|
// for the key to no longer be pressed appears to be insufficient.
|
||||||
|
// NB: Using _keyboardInterface.IsPressed(Key.Escape) does not currently work.
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
while (GetAsyncKeyState(0x1B) != 0)
|
||||||
|
{
|
||||||
|
await Task.Delay(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Task.Delay(250);
|
||||||
|
}
|
||||||
|
|
||||||
shouldExit = await ContentDialogHelper.CreateStopEmulationDialog();
|
shouldExit = await ContentDialogHelper.CreateStopEmulationDialog();
|
||||||
|
|
||||||
_dialogShown = false;
|
_dialogShown = false;
|
||||||
|
@ -556,7 +556,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
|
|||||||
data.Favorite = appMetadata.Favorite;
|
data.Favorite = appMetadata.Favorite;
|
||||||
data.TimePlayed = appMetadata.TimePlayed;
|
data.TimePlayed = appMetadata.TimePlayed;
|
||||||
data.LastPlayed = appMetadata.LastPlayed;
|
data.LastPlayed = appMetadata.LastPlayed;
|
||||||
data.HasIndependentConfiguration = File.Exists(Program.GetDirGameUserConfig(data.IdBaseString, false, false)); // Just check user config
|
data.HasIndependentConfiguration = File.Exists(Program.GetDirGameUserConfig(data.IdBaseString)); // Just check user config
|
||||||
}
|
}
|
||||||
|
|
||||||
data.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper();
|
data.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper();
|
||||||
|
@ -33,19 +33,11 @@ namespace Ryujinx.Ava.Systems.AppLibrary
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates <see cref="LastPlayed"/> and <see cref="TimePlayed"/>. Call this after a game ends.
|
/// Updates <see cref="LastPlayed"/> and <see cref="TimePlayed"/>. Call this after a game ends.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void UpdatePostGame()
|
/// <param name="playTime">The active gameplay time this past session.</param>
|
||||||
|
public void UpdatePostGame(TimeSpan playTime)
|
||||||
{
|
{
|
||||||
DateTime? prevLastPlayed = LastPlayed;
|
|
||||||
UpdatePreGame();
|
UpdatePreGame();
|
||||||
|
TimePlayed += playTime;
|
||||||
if (!prevLastPlayed.HasValue)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TimeSpan diff = DateTime.UtcNow - prevLastPlayed.Value;
|
|
||||||
double newTotalSeconds = TimePlayed.Add(diff).TotalSeconds;
|
|
||||||
TimePlayed = TimeSpan.FromSeconds(Math.Round(newTotalSeconds, MidpointRounding.AwayFromZero));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current version of the file format
|
/// The current version of the file format
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int CurrentVersion = 69;
|
public const int CurrentVersion = 70;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Version of the configuration file format
|
/// Version of the configuration file format
|
||||||
@ -152,6 +152,11 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool MatchSystemTime { get; set; }
|
public bool MatchSystemTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable or disable use global input config (Independent from controllers binding)
|
||||||
|
/// </summary>
|
||||||
|
public bool UseInputGlobalConfig { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enables or disables Docked Mode
|
/// Enables or disables Docked Mode
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -459,6 +464,21 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool UseHypervisor { get; set; }
|
public bool UseHypervisor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables or disables the GDB stub
|
||||||
|
/// </summary>
|
||||||
|
public bool EnableGdbStub { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Which TCP port should the GDB stub listen on
|
||||||
|
/// </summary>
|
||||||
|
public ushort GdbStubPort { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Suspend execution when starting an application
|
||||||
|
/// </summary>
|
||||||
|
public bool DebuggerSuspendOnStart { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Show toggles for dirty hacks in the UI.
|
/// Show toggles for dirty hacks in the UI.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -90,6 +90,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
System.TimeZone.Value = cff.SystemTimeZone;
|
System.TimeZone.Value = cff.SystemTimeZone;
|
||||||
System.SystemTimeOffset.Value = shouldLoadFromFile ? cff.SystemTimeOffset : System.SystemTimeOffset.Value; // Get from global config only
|
System.SystemTimeOffset.Value = shouldLoadFromFile ? cff.SystemTimeOffset : System.SystemTimeOffset.Value; // Get from global config only
|
||||||
System.MatchSystemTime.Value = shouldLoadFromFile ? cff.MatchSystemTime : System.MatchSystemTime.Value; // Get from global config only
|
System.MatchSystemTime.Value = shouldLoadFromFile ? cff.MatchSystemTime : System.MatchSystemTime.Value; // Get from global config only
|
||||||
|
System.UseInputGlobalConfig.Value = cff.UseInputGlobalConfig;
|
||||||
System.EnableDockedMode.Value = cff.DockedMode;
|
System.EnableDockedMode.Value = cff.DockedMode;
|
||||||
System.EnablePtc.Value = cff.EnablePtc;
|
System.EnablePtc.Value = cff.EnablePtc;
|
||||||
System.EnableLowPowerPtc.Value = cff.EnableLowPowerPtc;
|
System.EnableLowPowerPtc.Value = cff.EnableLowPowerPtc;
|
||||||
@ -146,7 +147,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
Hid.EnableMouse.Value = cff.EnableMouse;
|
Hid.EnableMouse.Value = cff.EnableMouse;
|
||||||
Hid.DisableInputWhenOutOfFocus.Value = shouldLoadFromFile ? cff.DisableInputWhenOutOfFocus : Hid.DisableInputWhenOutOfFocus.Value; // Get from global config only
|
Hid.DisableInputWhenOutOfFocus.Value = shouldLoadFromFile ? cff.DisableInputWhenOutOfFocus : Hid.DisableInputWhenOutOfFocus.Value; // Get from global config only
|
||||||
Hid.Hotkeys.Value = shouldLoadFromFile ? cff.Hotkeys : Hid.Hotkeys.Value; // Get from global config only
|
Hid.Hotkeys.Value = shouldLoadFromFile ? cff.Hotkeys : Hid.Hotkeys.Value; // Get from global config only
|
||||||
Hid.InputConfig.Value = cff.InputConfig ?? [];
|
Hid.InputConfig.Value = cff.InputConfig ?? [] ;
|
||||||
Hid.RainbowSpeed.Value = cff.RainbowSpeed;
|
Hid.RainbowSpeed.Value = cff.RainbowSpeed;
|
||||||
|
|
||||||
Multiplayer.LanInterfaceId.Value = cff.MultiplayerLanInterfaceId;
|
Multiplayer.LanInterfaceId.Value = cff.MultiplayerLanInterfaceId;
|
||||||
@ -155,6 +156,10 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
Multiplayer.LdnPassphrase.Value = cff.MultiplayerLdnPassphrase;
|
Multiplayer.LdnPassphrase.Value = cff.MultiplayerLdnPassphrase;
|
||||||
Multiplayer.LdnServer.Value = cff.LdnServer;
|
Multiplayer.LdnServer.Value = cff.LdnServer;
|
||||||
|
|
||||||
|
Debug.EnableGdbStub.Value = cff.EnableGdbStub;
|
||||||
|
Debug.GdbStubPort.Value = cff.GdbStubPort;
|
||||||
|
Debug.DebuggerSuspendOnStart.Value = cff.DebuggerSuspendOnStart;
|
||||||
|
|
||||||
{
|
{
|
||||||
Hacks.ShowDirtyHacks.Value = shouldLoadFromFile ? cff.ShowDirtyHacks : Hacks.ShowDirtyHacks.Value; // Get from global config only
|
Hacks.ShowDirtyHacks.Value = shouldLoadFromFile ? cff.ShowDirtyHacks : Hacks.ShowDirtyHacks.Value; // Get from global config only
|
||||||
|
|
||||||
|
@ -326,6 +326,12 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ReactiveObject<bool> MatchSystemTime { get; private set; }
|
public ReactiveObject<bool> MatchSystemTime { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable or disable use global input config (Independent from controllers binding)
|
||||||
|
/// </summary>
|
||||||
|
public ReactiveObject<bool> UseInputGlobalConfig { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enables or disables Docked Mode
|
/// Enables or disables Docked Mode
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -417,6 +423,8 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
SystemTimeOffset.LogChangesToValue(nameof(SystemTimeOffset));
|
SystemTimeOffset.LogChangesToValue(nameof(SystemTimeOffset));
|
||||||
MatchSystemTime = new ReactiveObject<bool>();
|
MatchSystemTime = new ReactiveObject<bool>();
|
||||||
MatchSystemTime.LogChangesToValue(nameof(MatchSystemTime));
|
MatchSystemTime.LogChangesToValue(nameof(MatchSystemTime));
|
||||||
|
UseInputGlobalConfig = new ReactiveObject<bool>();
|
||||||
|
UseInputGlobalConfig.LogChangesToValue(nameof(UseInputGlobalConfig));
|
||||||
EnableDockedMode = new ReactiveObject<bool>();
|
EnableDockedMode = new ReactiveObject<bool>();
|
||||||
EnableDockedMode.LogChangesToValue(nameof(EnableDockedMode));
|
EnableDockedMode.LogChangesToValue(nameof(EnableDockedMode));
|
||||||
EnablePtc = new ReactiveObject<bool>();
|
EnablePtc = new ReactiveObject<bool>();
|
||||||
@ -695,6 +703,37 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Debug configuration section
|
||||||
|
/// </summary>
|
||||||
|
public class DebugSection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enables or disables the GDB stub
|
||||||
|
/// </summary>
|
||||||
|
public ReactiveObject<bool> EnableGdbStub { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Which TCP port should the GDB stub listen on
|
||||||
|
/// </summary>
|
||||||
|
public ReactiveObject<ushort> GdbStubPort { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Suspend execution when starting an application
|
||||||
|
/// </summary>
|
||||||
|
public ReactiveObject<bool> DebuggerSuspendOnStart { get; private set; }
|
||||||
|
|
||||||
|
public DebugSection()
|
||||||
|
{
|
||||||
|
EnableGdbStub = new ReactiveObject<bool>();
|
||||||
|
EnableGdbStub.LogChangesToValue(nameof(EnableGdbStub));
|
||||||
|
GdbStubPort = new ReactiveObject<ushort>();
|
||||||
|
GdbStubPort.LogChangesToValue(nameof(GdbStubPort));
|
||||||
|
DebuggerSuspendOnStart = new ReactiveObject<bool>();
|
||||||
|
DebuggerSuspendOnStart.LogChangesToValue(nameof(DebuggerSuspendOnStart));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class HacksSection
|
public class HacksSection
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -761,6 +800,8 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static ConfigurationState Instance { get; private set; }
|
public static ConfigurationState Instance { get; private set; }
|
||||||
|
|
||||||
|
public static ConfigurationState InstanceExtra{ get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The UI section
|
/// The UI section
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -791,6 +832,11 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public MultiplayerSection Multiplayer { get; private set; }
|
public MultiplayerSection Multiplayer { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Debug
|
||||||
|
/// </summary>
|
||||||
|
public DebugSection Debug { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Dirty Hacks section
|
/// The Dirty Hacks section
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -844,6 +890,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
Graphics = new GraphicsSection();
|
Graphics = new GraphicsSection();
|
||||||
Hid = new HidSection();
|
Hid = new HidSection();
|
||||||
Multiplayer = new MultiplayerSection();
|
Multiplayer = new MultiplayerSection();
|
||||||
|
Debug = new DebugSection();
|
||||||
Hacks = new HacksSection();
|
Hacks = new HacksSection();
|
||||||
UpdateCheckerType = new ReactiveObject<UpdaterType>();
|
UpdateCheckerType = new ReactiveObject<UpdaterType>();
|
||||||
FocusLostActionType = new ReactiveObject<FocusLostType>();
|
FocusLostActionType = new ReactiveObject<FocusLostType>();
|
||||||
@ -883,6 +930,9 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
Multiplayer.DisableP2p,
|
Multiplayer.DisableP2p,
|
||||||
Multiplayer.LdnPassphrase,
|
Multiplayer.LdnPassphrase,
|
||||||
Multiplayer.GetLdnServer(),
|
Multiplayer.GetLdnServer(),
|
||||||
|
Debug.EnableGdbStub,
|
||||||
|
Debug.GdbStubPort,
|
||||||
|
Debug.DebuggerSuspendOnStart,
|
||||||
Graphics.CustomVSyncInterval,
|
Graphics.CustomVSyncInterval,
|
||||||
Hacks.ShowDirtyHacks ? Hacks.EnabledHacks : null);
|
Hacks.ShowDirtyHacks ? Hacks.EnabledHacks : null);
|
||||||
}
|
}
|
||||||
|
@ -15,12 +15,13 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
{
|
{
|
||||||
public static void Initialize()
|
public static void Initialize()
|
||||||
{
|
{
|
||||||
if (Instance != null)
|
if (Instance != null || InstanceExtra!= null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Configuration is already initialized");
|
throw new InvalidOperationException("Configuration is already initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
Instance = new ConfigurationState();
|
Instance = new ConfigurationState();
|
||||||
|
InstanceExtra= new ConfigurationState();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConfigurationFileFormat ToFileFormat()
|
public ConfigurationFileFormat ToFileFormat()
|
||||||
@ -54,6 +55,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
SystemTimeZone = System.TimeZone,
|
SystemTimeZone = System.TimeZone,
|
||||||
SystemTimeOffset = System.SystemTimeOffset,
|
SystemTimeOffset = System.SystemTimeOffset,
|
||||||
MatchSystemTime = System.MatchSystemTime,
|
MatchSystemTime = System.MatchSystemTime,
|
||||||
|
UseInputGlobalConfig = System.UseInputGlobalConfig,
|
||||||
DockedMode = System.EnableDockedMode,
|
DockedMode = System.EnableDockedMode,
|
||||||
EnableDiscordIntegration = EnableDiscordIntegration,
|
EnableDiscordIntegration = EnableDiscordIntegration,
|
||||||
UpdateCheckerType = UpdateCheckerType,
|
UpdateCheckerType = UpdateCheckerType,
|
||||||
@ -145,6 +147,9 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
MultiplayerDisableP2p = Multiplayer.DisableP2p,
|
MultiplayerDisableP2p = Multiplayer.DisableP2p,
|
||||||
MultiplayerLdnPassphrase = Multiplayer.LdnPassphrase,
|
MultiplayerLdnPassphrase = Multiplayer.LdnPassphrase,
|
||||||
LdnServer = Multiplayer.LdnServer,
|
LdnServer = Multiplayer.LdnServer,
|
||||||
|
EnableGdbStub = Debug.EnableGdbStub,
|
||||||
|
GdbStubPort = Debug.GdbStubPort,
|
||||||
|
DebuggerSuspendOnStart = Debug.DebuggerSuspendOnStart,
|
||||||
ShowDirtyHacks = Hacks.ShowDirtyHacks,
|
ShowDirtyHacks = Hacks.ShowDirtyHacks,
|
||||||
DirtyHacks = Hacks.EnabledHacks.Select(it => it.Pack()).ToArray(),
|
DirtyHacks = Hacks.EnabledHacks.Select(it => it.Pack()).ToArray(),
|
||||||
};
|
};
|
||||||
@ -178,6 +183,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
System.Region.Value = Region.USA;
|
System.Region.Value = Region.USA;
|
||||||
System.TimeZone.Value = "UTC";
|
System.TimeZone.Value = "UTC";
|
||||||
System.SystemTimeOffset.Value = 0;
|
System.SystemTimeOffset.Value = 0;
|
||||||
|
System.UseInputGlobalConfig.Value = false;
|
||||||
System.EnableDockedMode.Value = true;
|
System.EnableDockedMode.Value = true;
|
||||||
EnableDiscordIntegration.Value = true;
|
EnableDiscordIntegration.Value = true;
|
||||||
UpdateCheckerType.Value = UpdaterType.PromptAtStartup;
|
UpdateCheckerType.Value = UpdaterType.PromptAtStartup;
|
||||||
@ -321,6 +327,9 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
Debug.EnableGdbStub.Value = false;
|
||||||
|
Debug.GdbStubPort.Value = 55555;
|
||||||
|
Debug.DebuggerSuspendOnStart.Value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GraphicsBackend DefaultGraphicsBackend()
|
private static GraphicsBackend DefaultGraphicsBackend()
|
||||||
|
107
src/Ryujinx/Systems/Updater/Updater.GitLab.cs
Normal file
107
src/Ryujinx/Systems/Updater/Updater.GitLab.cs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
using Gommon;
|
||||||
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Common.Helper;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Systems.Update.Client;
|
||||||
|
using Ryujinx.Systems.Update.Common;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Systems
|
||||||
|
{
|
||||||
|
internal static partial class Updater
|
||||||
|
{
|
||||||
|
private static VersionResponse _versionResponse;
|
||||||
|
|
||||||
|
private static UpdateClient CreateUpdateClient()
|
||||||
|
=> UpdateClient.Builder()
|
||||||
|
.WithServerEndpoint("https://update.ryujinx.app") // This is the default, and doesn't need to be provided; it's here for transparency.
|
||||||
|
.WithLogger((format, args, caller) =>
|
||||||
|
Logger.Info?.Print(
|
||||||
|
LogClass.Application,
|
||||||
|
args.Length is 0 ? format : format.Format(args),
|
||||||
|
caller: caller)
|
||||||
|
);
|
||||||
|
|
||||||
|
public static async Task<Optional<(Version Current, Version Incoming)>> CheckVersionAsync(bool showVersionUpToDate = false)
|
||||||
|
{
|
||||||
|
if (!Version.TryParse(Program.Version, out Version currentVersion))
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application,
|
||||||
|
$"Failed to convert the current {RyujinxApp.FullAppName} version!");
|
||||||
|
|
||||||
|
await ContentDialogHelper.CreateWarningDialog(
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage],
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
|
||||||
|
|
||||||
|
_running = false;
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
using UpdateClient updateClient = CreateUpdateClient();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_versionResponse = await updateClient.QueryLatestAsync(ReleaseInformation.IsCanaryBuild
|
||||||
|
? ReleaseChannel.Canary
|
||||||
|
: ReleaseChannel.Stable);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, $"An error occurred when requesting for updates ({e.GetType().AsFullNamePrettyString()}): {e.Message}");
|
||||||
|
|
||||||
|
_running = false;
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_versionResponse == null)
|
||||||
|
{
|
||||||
|
// logging is done via the UpdateClient library
|
||||||
|
_running = false;
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If build URL not found, assume no new update is available.
|
||||||
|
if (_versionResponse.ArtifactUrl is null or "")
|
||||||
|
{
|
||||||
|
if (showVersionUpToDate)
|
||||||
|
{
|
||||||
|
UserResult userResult = await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog(
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
|
||||||
|
string.Empty);
|
||||||
|
|
||||||
|
if (userResult is UserResult.Ok)
|
||||||
|
{
|
||||||
|
OpenHelper.OpenUrl(_versionResponse.ReleaseUrlFormat.Format(currentVersion));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Info?.Print(LogClass.Application, "Up to date.");
|
||||||
|
|
||||||
|
_running = false;
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!Version.TryParse(_versionResponse.Version, out Version newVersion))
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application,
|
||||||
|
$"Failed to convert the received {RyujinxApp.FullAppName} version from the update server!");
|
||||||
|
|
||||||
|
await ContentDialogHelper.CreateWarningDialog(
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedServerMessage],
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
|
||||||
|
|
||||||
|
_running = false;
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (currentVersion, newVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,13 +5,11 @@ using ICSharpCode.SharpZipLib.GZip;
|
|||||||
using ICSharpCode.SharpZipLib.Tar;
|
using ICSharpCode.SharpZipLib.Tar;
|
||||||
using ICSharpCode.SharpZipLib.Zip;
|
using ICSharpCode.SharpZipLib.Zip;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.Common.Models.Github;
|
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.Utilities;
|
using Ryujinx.Ava.Utilities;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Helper;
|
using Ryujinx.Common.Helper;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.Utilities;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
@ -29,147 +27,19 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Ryujinx.Ava.Systems
|
namespace Ryujinx.Ava.Systems
|
||||||
{
|
{
|
||||||
internal static class Updater
|
internal static partial class Updater
|
||||||
{
|
{
|
||||||
private static ReleaseChannels.Channel? _currentReleaseChannel;
|
|
||||||
|
|
||||||
private const string GitHubApiUrl = "https://api.github.com";
|
|
||||||
|
|
||||||
private static readonly GithubReleasesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
|
||||||
|
|
||||||
private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory;
|
private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||||
private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
|
private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
|
||||||
private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish");
|
private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish");
|
||||||
private const int ConnectionCount = 4;
|
private const int ConnectionCount = 4;
|
||||||
|
|
||||||
private static string _buildVer;
|
|
||||||
|
|
||||||
|
|
||||||
private static readonly string _platformExt = BuildPlatformExtension();
|
|
||||||
|
|
||||||
private static string _buildUrl;
|
|
||||||
private static long _buildSize;
|
private static long _buildSize;
|
||||||
private static bool _updateSuccessful;
|
private static bool _updateSuccessful;
|
||||||
private static bool _running;
|
private static bool _running;
|
||||||
|
|
||||||
private static readonly string[] _windowsDependencyDirs = [];
|
private static readonly string[] _windowsDependencyDirs = [];
|
||||||
|
|
||||||
public static async Task<Optional<(Version Current, Version Incoming)>> CheckVersionAsync(bool showVersionUpToDate = false)
|
|
||||||
{
|
|
||||||
if (!Version.TryParse(Program.Version, out Version currentVersion))
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Application, $"Failed to convert the current {RyujinxApp.FullAppName} version!");
|
|
||||||
|
|
||||||
await ContentDialogHelper.CreateWarningDialog(
|
|
||||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage],
|
|
||||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
|
|
||||||
|
|
||||||
_running = false;
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Application, "Checking for updates.");
|
|
||||||
|
|
||||||
// Get latest version number from GitHub API
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using HttpClient jsonClient = ConstructHttpClient();
|
|
||||||
|
|
||||||
if (_currentReleaseChannel == null)
|
|
||||||
{
|
|
||||||
ReleaseChannels releaseChannels = await ReleaseInformation.GetReleaseChannelsAsync(jsonClient);
|
|
||||||
|
|
||||||
_currentReleaseChannel = ReleaseInformation.IsCanaryBuild
|
|
||||||
? releaseChannels.Canary
|
|
||||||
: releaseChannels.Stable;
|
|
||||||
}
|
|
||||||
|
|
||||||
string fetchedJson = await jsonClient.GetStringAsync(_currentReleaseChannel.Value.GetLatestReleaseApiUrl());
|
|
||||||
GithubReleasesJsonResponse fetched = JsonHelper.Deserialize(fetchedJson, _serializerContext.GithubReleasesJsonResponse);
|
|
||||||
_buildVer = fetched.TagName;
|
|
||||||
|
|
||||||
foreach (GithubReleaseAssetJsonResponse asset in fetched.Assets)
|
|
||||||
{
|
|
||||||
if (asset.Name.StartsWith("ryujinx") && asset.Name.EndsWith(_platformExt))
|
|
||||||
{
|
|
||||||
_buildUrl = asset.BrowserDownloadUrl;
|
|
||||||
|
|
||||||
if (asset.State != "uploaded")
|
|
||||||
{
|
|
||||||
if (showVersionUpToDate)
|
|
||||||
{
|
|
||||||
UserResult userResult = await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog(
|
|
||||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
|
|
||||||
string.Empty);
|
|
||||||
|
|
||||||
if (userResult is UserResult.Ok)
|
|
||||||
{
|
|
||||||
OpenHelper.OpenUrl(ReleaseInformation.GetChangelogForVersion(currentVersion, _currentReleaseChannel.Value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Application, "Up to date.");
|
|
||||||
|
|
||||||
_running = false;
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If build not done, assume no new update are available.
|
|
||||||
if (_buildUrl is null)
|
|
||||||
{
|
|
||||||
if (showVersionUpToDate)
|
|
||||||
{
|
|
||||||
UserResult userResult = await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog(
|
|
||||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
|
|
||||||
string.Empty);
|
|
||||||
|
|
||||||
if (userResult is UserResult.Ok)
|
|
||||||
{
|
|
||||||
OpenHelper.OpenUrl(ReleaseInformation.GetChangelogForVersion(currentVersion, _currentReleaseChannel.Value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Application, "Up to date.");
|
|
||||||
|
|
||||||
_running = false;
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Application, exception.Message);
|
|
||||||
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(
|
|
||||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]);
|
|
||||||
|
|
||||||
_running = false;
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Version.TryParse(_buildVer, out Version newVersion))
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Application, $"Failed to convert the received {RyujinxApp.FullAppName} version from GitHub!");
|
|
||||||
|
|
||||||
await ContentDialogHelper.CreateWarningDialog(
|
|
||||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage],
|
|
||||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
|
|
||||||
|
|
||||||
_running = false;
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (currentVersion, newVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task BeginUpdateAsync(bool showVersionUpToDate = false)
|
public static async Task BeginUpdateAsync(bool showVersionUpToDate = false)
|
||||||
{
|
{
|
||||||
if (_running)
|
if (_running)
|
||||||
@ -196,7 +66,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
|
|
||||||
if (userResult is UserResult.Ok)
|
if (userResult is UserResult.Ok)
|
||||||
{
|
{
|
||||||
OpenHelper.OpenUrl(ReleaseInformation.GetChangelogForVersion(currentVersion, _currentReleaseChannel.Value));
|
OpenHelper.OpenUrl(_versionResponse.ReleaseUrlFormat.Format(currentVersion));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,8 +82,11 @@ namespace Ryujinx.Ava.Systems
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0");
|
buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0");
|
||||||
|
|
||||||
|
// GitLab instance is located in Ukraine. Connection times will vary across the world.
|
||||||
|
buildSizeClient.Timeout = TimeSpan.FromSeconds(10);
|
||||||
|
|
||||||
HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_buildUrl), HttpCompletionOption.ResponseHeadersRead);
|
HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_versionResponse.ArtifactUrl), HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
_buildSize = message.Content.Headers.ContentRange.Length.Value;
|
_buildSize = message.Content.Headers.ContentRange.Length.Value;
|
||||||
}
|
}
|
||||||
@ -243,11 +116,11 @@ namespace Ryujinx.Ava.Systems
|
|||||||
switch (shouldUpdate)
|
switch (shouldUpdate)
|
||||||
{
|
{
|
||||||
case UserResult.Yes:
|
case UserResult.Yes:
|
||||||
await UpdateRyujinx(_buildUrl);
|
await UpdateRyujinx(_versionResponse.ArtifactUrl);
|
||||||
break;
|
break;
|
||||||
// Secondary button maps to no, which in this case is the show changelog button.
|
// Secondary button maps to no, which in this case is the show changelog button.
|
||||||
case UserResult.No:
|
case UserResult.No:
|
||||||
OpenHelper.OpenUrl(ReleaseInformation.GetChangelogUrl(currentVersion, newVersion, _currentReleaseChannel.Value));
|
OpenHelper.OpenUrl(ReleaseInformation.GetChangelogUrl(currentVersion, newVersion));
|
||||||
goto RequestUserToUpdate;
|
goto RequestUserToUpdate;
|
||||||
default:
|
default:
|
||||||
_running = false;
|
_running = false;
|
||||||
@ -261,7 +134,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
HttpClient result = new();
|
HttpClient result = new();
|
||||||
|
|
||||||
// Required by GitHub to interact with APIs.
|
// Required by GitHub to interact with APIs.
|
||||||
result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0");
|
result.DefaultRequestHeaders.Add("User-Agent", $"Ryujinx-Updater/{ReleaseInformation.Version}");
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
@ -2,29 +2,104 @@ using Avalonia.Media;
|
|||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.HLE.UI;
|
using Ryujinx.HLE.UI;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Applet
|
namespace Ryujinx.Ava.UI.Applet
|
||||||
{
|
{
|
||||||
class AvaloniaHostUITheme(MainWindow parent) : IHostUITheme
|
class AvaloniaHostUITheme : IHostUITheme
|
||||||
{
|
{
|
||||||
public string FontFamily { get; } = OperatingSystem.IsWindows() && OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000) ? "Segoe UI Variable" : parent.FontFamily.Name;
|
private readonly MainWindow _parent;
|
||||||
|
|
||||||
public ThemeColor DefaultBackgroundColor { get; } = BrushToThemeColor(parent.Background);
|
public string FontFamily { get; }
|
||||||
public ThemeColor DefaultForegroundColor { get; } = BrushToThemeColor(parent.Foreground);
|
public ThemeColor DefaultBackgroundColor { get; }
|
||||||
public ThemeColor DefaultBorderColor { get; } = BrushToThemeColor(parent.BorderBrush);
|
public ThemeColor DefaultForegroundColor { get; }
|
||||||
public ThemeColor SelectionBackgroundColor { get; } = BrushToThemeColor(parent.ViewControls.SearchBox.SelectionBrush);
|
public ThemeColor DefaultBorderColor { get; }
|
||||||
public ThemeColor SelectionForegroundColor { get; } = BrushToThemeColor(parent.ViewControls.SearchBox.SelectionForegroundBrush);
|
public ThemeColor SelectionBackgroundColor { get; }
|
||||||
|
public ThemeColor SelectionForegroundColor { get; }
|
||||||
|
|
||||||
|
public AvaloniaHostUITheme(MainWindow parent)
|
||||||
|
{
|
||||||
|
_parent = parent;
|
||||||
|
|
||||||
|
// Initialize font property
|
||||||
|
FontFamily = GetSystemFontFamily();
|
||||||
|
|
||||||
|
// Initialize all properties that depend on parent
|
||||||
|
DefaultBackgroundColor = BrushToThemeColor(parent.Background);
|
||||||
|
DefaultForegroundColor = BrushToThemeColor(parent.Foreground);
|
||||||
|
DefaultBorderColor = BrushToThemeColor(parent.BorderBrush);
|
||||||
|
SelectionBackgroundColor = BrushToThemeColor(parent.ViewControls.SearchBox.SelectionBrush);
|
||||||
|
SelectionForegroundColor = BrushToThemeColor(parent.ViewControls.SearchBox.SelectionForegroundBrush);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetSystemFontFamily()
|
||||||
|
{
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
return GetWindowsFontByLanguage();
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsMacOS())
|
||||||
|
{
|
||||||
|
return GetMacOSFontByLanguage();
|
||||||
|
}
|
||||||
|
else // Linux and other platforms
|
||||||
|
{
|
||||||
|
return GetLinuxFontByLanguage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetWindowsFontByLanguage()
|
||||||
|
{
|
||||||
|
var culture = CultureInfo.CurrentUICulture;
|
||||||
|
string langCode = culture.Name;
|
||||||
|
|
||||||
|
return culture.TwoLetterISOLanguageName switch
|
||||||
|
{
|
||||||
|
"zh" => langCode == "zh-CN" || langCode == "zh-Hans" || langCode == "zh-SG"
|
||||||
|
? "Microsoft YaHei UI" // Simplified Chinese
|
||||||
|
: "Microsoft JhengHei UI", // Traditional Chinese
|
||||||
|
|
||||||
|
"ja" => "Yu Gothic UI", // Japanese
|
||||||
|
"ko" => "Malgun Gothic", // Korean
|
||||||
|
_ => OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000)
|
||||||
|
? "Segoe UI Variable" // Other languages - Windows 11+
|
||||||
|
: _parent.FontFamily.Name // Fallback to parent window font
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetMacOSFontByLanguage()
|
||||||
|
{
|
||||||
|
return CultureInfo.CurrentUICulture.TwoLetterISOLanguageName switch
|
||||||
|
{
|
||||||
|
"zh" => "PingFang SC", // Chinese (both simplified and traditional)
|
||||||
|
"ja" => "Hiragino Sans", // Japanese
|
||||||
|
"ko" => "Apple SD Gothic Neo", // Korean
|
||||||
|
_ => _parent.FontFamily.Name // Fallback to parent window font
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetLinuxFontByLanguage()
|
||||||
|
{
|
||||||
|
return CultureInfo.CurrentUICulture.TwoLetterISOLanguageName switch
|
||||||
|
{
|
||||||
|
"zh" => "Noto Sans CJK SC", // Chinese
|
||||||
|
"ja" => "Noto Sans CJK JP", // Japanese
|
||||||
|
"ko" => "Noto Sans CJK KR", // Korean
|
||||||
|
_ => _parent.FontFamily.Name // Fallback to parent window font
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private static ThemeColor BrushToThemeColor(IBrush brush)
|
private static ThemeColor BrushToThemeColor(IBrush brush)
|
||||||
{
|
{
|
||||||
if (brush is SolidColorBrush solidColor)
|
if (brush is SolidColorBrush solidColor)
|
||||||
{
|
{
|
||||||
return new ThemeColor((float)solidColor.Color.A / 255,
|
return new ThemeColor(
|
||||||
|
(float)solidColor.Color.A / 255,
|
||||||
(float)solidColor.Color.R / 255,
|
(float)solidColor.Color.R / 255,
|
||||||
(float)solidColor.Color.G / 255,
|
(float)solidColor.Color.G / 255,
|
||||||
(float)solidColor.Color.B / 255);
|
(float)solidColor.Color.B / 255
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ThemeColor();
|
return new ThemeColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,5 +110,8 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
|
|
||||||
[LibraryImport("user32.dll", SetLastError = true)]
|
[LibraryImport("user32.dll", SetLastError = true)]
|
||||||
public static partial nint SetWindowLongPtrW(nint hWnd, int nIndex, nint value);
|
public static partial nint SetWindowLongPtrW(nint hWnd, int nIndex, nint value);
|
||||||
|
|
||||||
|
[LibraryImport("user32.dll", SetLastError = true)]
|
||||||
|
public static partial ushort GetAsyncKeyState(int nVirtKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ using Ryujinx.Ava.Systems.AppLibrary;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.ViewModels
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
{
|
{
|
||||||
@ -11,15 +12,37 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
private readonly ApplicationLibrary _appLibrary;
|
private readonly ApplicationLibrary _appLibrary;
|
||||||
|
|
||||||
|
private (int Status, int Name) _sorting;
|
||||||
|
|
||||||
|
public bool IsSortedByTitle => true;
|
||||||
|
public bool IsSortedByStatus => true;
|
||||||
|
|
||||||
|
// Avalonia takes names of status from these variables
|
||||||
|
public LocaleKeys IsStringPlayable => LocaleKeys.CompatibilityListPlayable;
|
||||||
|
public LocaleKeys IsStringInGame => LocaleKeys.CompatibilityListIngame;
|
||||||
|
public LocaleKeys IsStringMenus => LocaleKeys.CompatibilityListMenus;
|
||||||
|
public LocaleKeys IsStringBoots => LocaleKeys.CompatibilityListBoots;
|
||||||
|
public LocaleKeys IsStringNothing => LocaleKeys.CompatibilityListNothing;
|
||||||
|
|
||||||
|
public string PlayableInfoText { get; set; }
|
||||||
|
public string InGameInfoText { get; set; }
|
||||||
|
public string MenusInfoText { get; set; }
|
||||||
|
public string BootsInfoText { get; set; }
|
||||||
|
public string NothingInfoText { get; set; }
|
||||||
|
|
||||||
|
|
||||||
private IEnumerable<CompatibilityEntry> _currentEntries = CompatibilityDatabase.Entries;
|
private IEnumerable<CompatibilityEntry> _currentEntries = CompatibilityDatabase.Entries;
|
||||||
|
|
||||||
private string[] _ownedGameTitleIds = [];
|
private string[] _ownedGameTitleIds = [];
|
||||||
|
|
||||||
|
private Func<CompatibilityEntry, object> _sortKeySelector = x => x.GameName; // Default sort by GameName
|
||||||
|
|
||||||
public IEnumerable<CompatibilityEntry> CurrentEntries => OnlyShowOwnedGames
|
public IEnumerable<CompatibilityEntry> CurrentEntries => OnlyShowOwnedGames
|
||||||
? _currentEntries.Where(x =>
|
? _currentEntries.Where(x =>
|
||||||
x.TitleId.Check(tid => _ownedGameTitleIds.ContainsIgnoreCase(tid)))
|
x.TitleId.Check(tid => _ownedGameTitleIds.ContainsIgnoreCase(tid)))
|
||||||
: _currentEntries;
|
: _currentEntries;
|
||||||
|
|
||||||
public CompatibilityViewModel() { }
|
public CompatibilityViewModel() {}
|
||||||
|
|
||||||
private void AppCountUpdated(object _, ApplicationCountUpdatedEventArgs __)
|
private void AppCountUpdated(object _, ApplicationCountUpdatedEventArgs __)
|
||||||
=> _ownedGameTitleIds = _appLibrary.Applications.Keys.Select(x => x.ToString("X16")).ToArray();
|
=> _ownedGameTitleIds = _appLibrary.Applications.Keys.Select(x => x.ToString("X16")).ToArray();
|
||||||
@ -27,19 +50,29 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public CompatibilityViewModel(ApplicationLibrary appLibrary)
|
public CompatibilityViewModel(ApplicationLibrary appLibrary)
|
||||||
{
|
{
|
||||||
_appLibrary = appLibrary;
|
_appLibrary = appLibrary;
|
||||||
|
|
||||||
AppCountUpdated(null, null);
|
AppCountUpdated(null, null);
|
||||||
|
CountByStatus();
|
||||||
_appLibrary.ApplicationCountUpdated += AppCountUpdated;
|
_appLibrary.ApplicationCountUpdated += AppCountUpdated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void CountByStatus()
|
||||||
|
{
|
||||||
|
PlayableInfoText = LocaleManager.Instance[LocaleKeys.CompatibilityListPlayable] + ": " + CurrentEntries.Count(x => x.Status == LocaleKeys.CompatibilityListPlayable);
|
||||||
|
InGameInfoText = LocaleManager.Instance[LocaleKeys.CompatibilityListIngame] + ": " + CurrentEntries.Count(x => x.Status == LocaleKeys.CompatibilityListIngame);
|
||||||
|
MenusInfoText = LocaleManager.Instance[LocaleKeys.CompatibilityListMenus] + ": " + CurrentEntries.Count(x => x.Status == LocaleKeys.CompatibilityListMenus);
|
||||||
|
BootsInfoText = LocaleManager.Instance[LocaleKeys.CompatibilityListBoots] + ": " + CurrentEntries.Count(x => x.Status == LocaleKeys.CompatibilityListBoots);
|
||||||
|
NothingInfoText = LocaleManager.Instance[LocaleKeys.CompatibilityListNothing] + ": " + CurrentEntries.Count(x => x.Status == LocaleKeys.CompatibilityListNothing);
|
||||||
|
|
||||||
|
_onlyShowOwnedGames = true;
|
||||||
|
}
|
||||||
|
|
||||||
void IDisposable.Dispose()
|
void IDisposable.Dispose()
|
||||||
{
|
{
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
_appLibrary.ApplicationCountUpdated -= AppCountUpdated;
|
_appLibrary.ApplicationCountUpdated -= AppCountUpdated;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _onlyShowOwnedGames = true;
|
private bool _onlyShowOwnedGames;
|
||||||
|
|
||||||
public bool OnlyShowOwnedGames
|
public bool OnlyShowOwnedGames
|
||||||
{
|
{
|
||||||
@ -54,17 +87,37 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void NameSorting(int nameSort = 0)
|
||||||
|
{
|
||||||
|
_sorting.Name = nameSort;
|
||||||
|
SortApply();
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(SortName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StatusSorting(int statusSort = 0)
|
||||||
|
{
|
||||||
|
_sorting.Status = statusSort;
|
||||||
|
SortApply();
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(SortName));
|
||||||
|
}
|
||||||
|
|
||||||
public void Search(string searchTerm)
|
public void Search(string searchTerm)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(searchTerm))
|
if (string.IsNullOrEmpty(searchTerm))
|
||||||
{
|
{
|
||||||
SetEntries(CompatibilityDatabase.Entries);
|
SetEntries(CompatibilityDatabase.Entries);
|
||||||
|
SortApply();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetEntries(CompatibilityDatabase.Entries.Where(x =>
|
SetEntries(CompatibilityDatabase.Entries.Where(x =>
|
||||||
x.GameName.ContainsIgnoreCase(searchTerm)
|
x.GameName.ContainsIgnoreCase(searchTerm)
|
||||||
|| x.TitleId.Check(tid => tid.ContainsIgnoreCase(searchTerm))));
|
|| x.TitleId.Check(tid => tid.ContainsIgnoreCase(searchTerm))));
|
||||||
|
|
||||||
|
SortApply();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetEntries(IEnumerable<CompatibilityEntry> entries)
|
private void SetEntries(IEnumerable<CompatibilityEntry> entries)
|
||||||
@ -72,5 +125,43 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
_currentEntries = entries.ToList();
|
_currentEntries = entries.ToList();
|
||||||
OnPropertyChanged(nameof(CurrentEntries));
|
OnPropertyChanged(nameof(CurrentEntries));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SortApply()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_currentEntries = (_sorting switch
|
||||||
|
{
|
||||||
|
(0, 0) => _currentEntries.OrderBy(x => _sortKeySelector(x) ?? string.Empty), // A - Z
|
||||||
|
(0, 1) => _currentEntries.OrderByDescending(x => _sortKeySelector(x) ?? string.Empty), // Z - A
|
||||||
|
(1, 0) => _currentEntries.OrderBy(x => x.Status).ThenBy(x => x.GameName, StringComparer.OrdinalIgnoreCase), // Status Playable - Nothing, then A - Z
|
||||||
|
(1, 1) => _currentEntries.OrderBy(x => x.Status).ThenByDescending(x => x.GameName, StringComparer.OrdinalIgnoreCase), // Status Nothing - Playable, then A - Z
|
||||||
|
(2, 0) => _currentEntries.OrderByDescending(x => x.Status).ThenBy(x => x.GameName, StringComparer.OrdinalIgnoreCase), // Status Playable - Nothing, then Z - A
|
||||||
|
(2, 1) => _currentEntries.OrderByDescending(x => x.Status).ThenByDescending(x => x.GameName, StringComparer.OrdinalIgnoreCase), // Status Nothing - Playable, then Z - A
|
||||||
|
_ => _currentEntries.OrderBy(x => x.Status)
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(CurrentEntries));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string SortName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (_sorting.Name) switch
|
||||||
|
{
|
||||||
|
(0) => LocaleManager.Instance[LocaleKeys.GameListSortStatusNameAscending],
|
||||||
|
(1) => LocaleManager.Instance[LocaleKeys.GameListSortStatusNameDescending],
|
||||||
|
_ => string.Empty,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,9 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
private string _controllerImage;
|
private string _controllerImage;
|
||||||
private int _device;
|
private int _device;
|
||||||
private object _configViewModel;
|
private object _configViewModel;
|
||||||
|
private bool _isChangeTrackingActive;
|
||||||
|
private string _chosenProfile;
|
||||||
|
[ObservableProperty] private bool _isModified;
|
||||||
[ObservableProperty] private string _profileName;
|
[ObservableProperty] private string _profileName;
|
||||||
[ObservableProperty] private bool _notificationIsVisible; // Automatically call the NotificationView property with OnPropertyChanged()
|
[ObservableProperty] private bool _notificationIsVisible; // Automatically call the NotificationView property with OnPropertyChanged()
|
||||||
[ObservableProperty] private string _notificationText; // Automatically call the NotificationText property with OnPropertyChanged()
|
[ObservableProperty] private string _notificationText; // Automatically call the NotificationText property with OnPropertyChanged()
|
||||||
@ -84,6 +87,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
public AvaloniaList<string> ProfilesList { get; set; }
|
public AvaloniaList<string> ProfilesList { get; set; }
|
||||||
public AvaloniaList<string> DeviceList { get; set; }
|
public AvaloniaList<string> DeviceList { get; set; }
|
||||||
|
|
||||||
|
public bool UseGlobalConfig;
|
||||||
|
|
||||||
// XAML Flags
|
// XAML Flags
|
||||||
public bool ShowSettings => _device > 0;
|
public bool ShowSettings => _device > 0;
|
||||||
public bool IsController => _device > 1;
|
public bool IsController => _device > 1;
|
||||||
@ -94,31 +99,16 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
public bool HasLed => SelectedGamepad.Features.HasFlag(GamepadFeaturesFlag.Led);
|
public bool HasLed => SelectedGamepad.Features.HasFlag(GamepadFeaturesFlag.Led);
|
||||||
public bool CanClearLed => SelectedGamepad.Name.ContainsIgnoreCase("DualSense");
|
public bool CanClearLed => SelectedGamepad.Name.ContainsIgnoreCase("DualSense");
|
||||||
|
|
||||||
public bool _isChangeTrackingActive;
|
|
||||||
|
|
||||||
public bool _isModified;
|
|
||||||
|
|
||||||
public bool IsModified
|
|
||||||
{
|
|
||||||
get => _isModified;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_isModified = value;
|
|
||||||
OnPropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public event Action NotifyChangesEvent;
|
public event Action NotifyChangesEvent;
|
||||||
|
|
||||||
public string _profileChoose;
|
public string ChosenProfile
|
||||||
public string ProfileChoose
|
|
||||||
{
|
{
|
||||||
get => _profileChoose;
|
get => _chosenProfile;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
// When you select a profile, the settings from the profile will be applied.
|
// When you select a profile, the settings from the profile will be applied.
|
||||||
// To save the settings, you still need to click the apply button
|
// To save the settings, you still need to click the apply button
|
||||||
_profileChoose = value;
|
_chosenProfile = value;
|
||||||
LoadProfile();
|
LoadProfile();
|
||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
}
|
}
|
||||||
@ -290,7 +280,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
|
|
||||||
public InputConfig Config { get; set; }
|
public InputConfig Config { get; set; }
|
||||||
|
|
||||||
public InputViewModel(UserControl owner) : this()
|
public InputViewModel(UserControl owner, bool useGlobal = false) : this()
|
||||||
{
|
{
|
||||||
if (Program.PreviewerDetached)
|
if (Program.PreviewerDetached)
|
||||||
{
|
{
|
||||||
@ -303,6 +293,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
|
|
||||||
_mainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates();
|
_mainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates();
|
||||||
|
|
||||||
|
UseGlobalConfig = useGlobal;
|
||||||
|
|
||||||
_isLoaded = false;
|
_isLoaded = false;
|
||||||
|
|
||||||
LoadDevices();
|
LoadDevices();
|
||||||
@ -335,9 +327,18 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
PlayerIndexes.Add(new(PlayerIndex.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsHandheld]));
|
PlayerIndexes.Add(new(PlayerIndex.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsHandheld]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void LoadConfiguration(InputConfig inputConfig = null)
|
private void LoadConfiguration(InputConfig inputConfig = null)
|
||||||
{
|
{
|
||||||
Config = inputConfig ?? ConfigurationState.Instance.Hid.InputConfig.Value.FirstOrDefault(inputConfig => inputConfig.PlayerIndex == _playerId);
|
if (UseGlobalConfig && Program.UseExtraConfig)
|
||||||
|
{
|
||||||
|
Config = inputConfig ?? ConfigurationState.InstanceExtra.Hid.InputConfig.Value.FirstOrDefault(inputConfig => inputConfig.PlayerIndex == _playerId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Config = inputConfig ?? ConfigurationState.Instance.Hid.InputConfig.Value.FirstOrDefault(inputConfig => inputConfig.PlayerIndex == _playerId);
|
||||||
|
}
|
||||||
|
|
||||||
if (Config is StandardKeyboardInputConfig keyboardInputConfig)
|
if (Config is StandardKeyboardInputConfig keyboardInputConfig)
|
||||||
{
|
{
|
||||||
@ -902,7 +903,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
|
|
||||||
LoadProfiles();
|
LoadProfiles();
|
||||||
|
|
||||||
ProfileChoose = ProfileName; // Show new profile
|
ChosenProfile = ProfileName; // Show new profile
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -936,7 +937,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
|
|
||||||
LoadProfiles();
|
LoadProfiles();
|
||||||
|
|
||||||
ProfileChoose = ProfilesList[0].ToString(); // Show default profile
|
ChosenProfile = ProfilesList[0].ToString(); // Show default profile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -966,7 +967,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
|
|
||||||
List<InputConfig> newConfig = [];
|
List<InputConfig> newConfig = [];
|
||||||
|
|
||||||
newConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value);
|
if (UseGlobalConfig && Program.UseExtraConfig)
|
||||||
|
{
|
||||||
|
newConfig.AddRange(ConfigurationState.InstanceExtra.Hid.InputConfig.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value);
|
||||||
|
}
|
||||||
|
|
||||||
newConfig.Remove(newConfig.FirstOrDefault(x => x == null));
|
newConfig.Remove(newConfig.FirstOrDefault(x => x == null));
|
||||||
|
|
||||||
@ -1007,18 +1015,21 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_mainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
|
|
||||||
|
|
||||||
// Atomically replace and signal input change.
|
// Atomically replace and signal input change.
|
||||||
// NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event.
|
// NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event.
|
||||||
ConfigurationState.Instance.Hid.InputConfig.Value = newConfig;
|
_mainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
|
||||||
|
|
||||||
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
if (UseGlobalConfig && Program.UseExtraConfig)
|
||||||
}
|
{
|
||||||
|
// In User Settings when "Use Global Input" is enabled, it saves global input to global setting
|
||||||
public void NotifyChange(string property)
|
ConfigurationState.InstanceExtra.Hid.InputConfig.Value = newConfig;
|
||||||
{
|
ConfigurationState.InstanceExtra.ToFileFormat().SaveConfig(Program.GlobalConfigurationPath);
|
||||||
OnPropertyChanged(property);
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ConfigurationState.Instance.Hid.InputConfig.Value = newConfig;
|
||||||
|
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void NotifyChanges()
|
public void NotifyChanges()
|
||||||
|
@ -310,10 +310,15 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
private void TotalTimePlayed_Recalculated(Optional<TimeSpan> ts)
|
private void TotalTimePlayed_Recalculated(Optional<TimeSpan> ts)
|
||||||
{
|
{
|
||||||
ShowTotalTimePlayed = ts.HasValue;
|
|
||||||
|
|
||||||
if (ts.HasValue)
|
if (ts.HasValue)
|
||||||
LocaleManager.Instance.SetDynamicValues(LocaleKeys.GameListLabelTotalTimePlayed, ValueFormatUtils.FormatTimeSpan(ts.Value));
|
{
|
||||||
|
var formattedPlayTime = ValueFormatUtils.FormatTimeSpan(ts.Value);
|
||||||
|
LocaleManager.Instance.SetDynamicValues(LocaleKeys.GameListLabelTotalTimePlayed, formattedPlayTime);
|
||||||
|
ShowTotalTimePlayed = formattedPlayTime != string.Empty;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShowTotalTimePlayed = ts.HasValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ShowTotalTimePlayed
|
public bool ShowTotalTimePlayed
|
||||||
@ -334,7 +339,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
_listSelectedApplication = value;
|
_listSelectedApplication = value;
|
||||||
|
|
||||||
if (_listSelectedApplication != null && ListAppContextMenu == null)
|
if (_listSelectedApplication != null && ListAppContextMenu == null)
|
||||||
|
|
||||||
ListAppContextMenu = new ApplicationContextMenu();
|
ListAppContextMenu = new ApplicationContextMenu();
|
||||||
else if (_listSelectedApplication == null && ListAppContextMenu != null)
|
else if (_listSelectedApplication == null && ListAppContextMenu != null)
|
||||||
ListAppContextMenu = null!;
|
ListAppContextMenu = null!;
|
||||||
@ -1575,28 +1579,31 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public bool InitializeUserConfig(ApplicationData application)
|
public bool InitializeUserConfig(ApplicationData application)
|
||||||
{
|
{
|
||||||
// Code where conditions will be met before loading the user configuration (Global Config)
|
// Code where conditions will be met before loading the user configuration (Global Config)
|
||||||
string BackendThreadingInit = Program.BackendThreadingArg;
|
string backendThreadingInit = Program.BackendThreadingArg ?? ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString();
|
||||||
|
|
||||||
BackendThreadingInit ??= ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString();
|
|
||||||
|
|
||||||
// If a configuration is found in the "/games/xxxxxxxxxxxxxx" folder, the program will load the user setting.
|
// If a configuration is found in the "/games/xxxxxxxxxxxxxx" folder, the program will load the user setting.
|
||||||
string idGame = application.IdBaseString;
|
string idGame = application.IdBaseString;
|
||||||
if (ConfigurationFileFormat.TryLoad(Program.GetDirGameUserConfig(idGame), out ConfigurationFileFormat configurationFileFormat))
|
if (ConfigurationFileFormat.TryLoad(Program.GetDirGameUserConfig(idGame), out ConfigurationFileFormat configurationFileFormat))
|
||||||
{
|
{
|
||||||
// Loads the user configuration, having previously changed the global configuration to the user configuration
|
// Loads the user configuration, having previously changed the global configuration to the user configuration
|
||||||
ConfigurationState.Instance.Load(configurationFileFormat, Program.GetDirGameUserConfig(idGame, true, true), idGame);
|
ConfigurationState.Instance.Load(configurationFileFormat, Program.GetDirGameUserConfig(idGame, true), idGame);
|
||||||
|
|
||||||
|
if (ConfigurationFileFormat.TryLoad(Program.GlobalConfigurationPath, out ConfigurationFileFormat configurationFileFormatExtra))
|
||||||
|
{
|
||||||
|
//This is where the global configuration will be stored.
|
||||||
|
//This allows you to change the global configuration settings during the game (for example, the global input setting)
|
||||||
|
ConfigurationState.InstanceExtra.Load(configurationFileFormatExtra, Program.GlobalConfigurationPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Code where conditions will be executed after loading user configuration
|
// Code where conditions will be executed after loading user configuration
|
||||||
if (ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() != BackendThreadingInit)
|
if (ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() != backendThreadingInit)
|
||||||
{
|
{
|
||||||
|
Rebooter.RebootAppWithGame(application.Path,
|
||||||
List<string> Arguments = new()
|
[
|
||||||
{
|
"--bt",
|
||||||
"--bt", ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() // BackendThreading
|
ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString()
|
||||||
};
|
]);
|
||||||
|
|
||||||
Rebooter.RebootAppWithGame(application.Path, Arguments);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1685,8 +1692,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
RendererHostControl.Focus();
|
RendererHostControl.Focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
public static void UpdateGameMetadata(string titleId)
|
public static void UpdateGameMetadata(string titleId, TimeSpan playTime)
|
||||||
=> ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => appMetadata.UpdatePostGame());
|
=> ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => appMetadata.UpdatePostGame(playTime));
|
||||||
|
|
||||||
public void RefreshFirmwareStatus()
|
public void RefreshFirmwareStatus()
|
||||||
{
|
{
|
||||||
@ -1991,7 +1998,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
// just checking for file presence
|
// just checking for file presence
|
||||||
viewModel.SelectedApplication.HasIndependentConfiguration = File.Exists(
|
viewModel.SelectedApplication.HasIndependentConfiguration = File.Exists(
|
||||||
Program.GetDirGameUserConfig(viewModel.SelectedApplication.IdString, false, false));
|
Program.GetDirGameUserConfig(viewModel.SelectedApplication.IdString));
|
||||||
|
|
||||||
viewModel.RefreshView();
|
viewModel.RefreshView();
|
||||||
});
|
});
|
||||||
|
@ -53,6 +53,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
[ObservableProperty] private bool _isVulkanAvailable = true;
|
[ObservableProperty] private bool _isVulkanAvailable = true;
|
||||||
[ObservableProperty] private bool _gameListNeedsRefresh;
|
[ObservableProperty] private bool _gameListNeedsRefresh;
|
||||||
private readonly List<string> _gpuIds = [];
|
private readonly List<string> _gpuIds = [];
|
||||||
|
public bool _useInputGlobalConfig;
|
||||||
private int _graphicsBackendIndex;
|
private int _graphicsBackendIndex;
|
||||||
private int _scalingFilter;
|
private int _scalingFilter;
|
||||||
private int _scalingFilterLevel;
|
private int _scalingFilterLevel;
|
||||||
@ -64,11 +65,16 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public event Action CloseWindow;
|
public event Action CloseWindow;
|
||||||
public event Action SaveSettingsEvent;
|
public event Action SaveSettingsEvent;
|
||||||
|
public event Action<bool> LocalGlobalInputSwitchEvent;
|
||||||
private int _networkInterfaceIndex;
|
private int _networkInterfaceIndex;
|
||||||
private int _multiplayerModeIndex;
|
private int _multiplayerModeIndex;
|
||||||
private string _ldnPassphrase;
|
private string _ldnPassphrase;
|
||||||
[ObservableProperty] private string _ldnServer;
|
[ObservableProperty] private string _ldnServer;
|
||||||
|
|
||||||
|
private bool _enableGDBStub;
|
||||||
|
private ushort _gdbStubPort;
|
||||||
|
private bool _debuggerSuspendOnStart;
|
||||||
|
|
||||||
public SettingsHacksViewModel DirtyHacks { get; }
|
public SettingsHacksViewModel DirtyHacks { get; }
|
||||||
|
|
||||||
private readonly bool _isGameRunning;
|
private readonly bool _isGameRunning;
|
||||||
@ -84,6 +90,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public bool IsGameTitleNotNull => !string.IsNullOrEmpty(GameTitle);
|
public bool IsGameTitleNotNull => !string.IsNullOrEmpty(GameTitle);
|
||||||
public double PanelOpacity => IsGameTitleNotNull ? 0.5 : 1;
|
public double PanelOpacity => IsGameTitleNotNull ? 0.5 : 1;
|
||||||
|
|
||||||
|
|
||||||
public int ResolutionScale
|
public int ResolutionScale
|
||||||
{
|
{
|
||||||
get => _resolutionScale;
|
get => _resolutionScale;
|
||||||
@ -141,13 +148,26 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public bool EnableDockedMode { get; set; }
|
public bool EnableDockedMode { get; set; }
|
||||||
public bool EnableKeyboard { get; set; }
|
public bool EnableKeyboard { get; set; }
|
||||||
public bool EnableMouse { get; set; }
|
public bool EnableMouse { get; set; }
|
||||||
public bool DisableInputWhenOutOfFocus { get; set; }
|
public bool DisableInputWhenOutOfFocus { get; set; }
|
||||||
|
|
||||||
public int FocusLostActionType { get; set; }
|
public int FocusLostActionType { get; set; }
|
||||||
|
|
||||||
|
public bool UseGlobalInputConfig
|
||||||
|
{
|
||||||
|
get => _useInputGlobalConfig;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_useInputGlobalConfig = value;
|
||||||
|
LocalGlobalInputSwitchEvent?.Invoke(_useInputGlobalConfig);
|
||||||
|
OnPropertyChanged(nameof(InputPanelOpacity));
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double InputPanelOpacity => UseGlobalInputConfig ? 0.5 : 1;
|
||||||
|
|
||||||
public VSyncMode VSyncMode
|
public VSyncMode VSyncMode
|
||||||
{
|
{
|
||||||
get => _vSyncMode;
|
get => _vSyncMode;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value is VSyncMode.Custom or VSyncMode.Switch or VSyncMode.Unbounded)
|
if (value is VSyncMode.Custom or VSyncMode.Switch or VSyncMode.Unbounded)
|
||||||
@ -371,7 +391,37 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public bool IsInvalidLdnPassphraseVisible { get; set; }
|
public bool IsInvalidLdnPassphraseVisible { get; set; }
|
||||||
|
|
||||||
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(false)
|
public bool EnableGdbStub
|
||||||
|
{
|
||||||
|
get => _enableGDBStub;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_enableGDBStub = value;
|
||||||
|
ConfigurationState.Instance.Debug.EnableGdbStub.Value = _enableGDBStub;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort GDBStubPort
|
||||||
|
{
|
||||||
|
get => _gdbStubPort;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_gdbStubPort = value;
|
||||||
|
ConfigurationState.Instance.Debug.GdbStubPort.Value = _gdbStubPort;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DebuggerSuspendOnStart
|
||||||
|
{
|
||||||
|
get => _debuggerSuspendOnStart;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_debuggerSuspendOnStart = value;
|
||||||
|
ConfigurationState.Instance.Debug.DebuggerSuspendOnStart.Value = _debuggerSuspendOnStart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
|
||||||
{
|
{
|
||||||
_virtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
_contentManager = contentManager;
|
_contentManager = contentManager;
|
||||||
@ -392,7 +442,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
string gameName,
|
string gameName,
|
||||||
string gameId,
|
string gameId,
|
||||||
byte[] gameIconData,
|
byte[] gameIconData,
|
||||||
bool enableToLoadCustomConfig) : this(enableToLoadCustomConfig)
|
bool customConfig) : this()
|
||||||
{
|
{
|
||||||
_virtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
_contentManager = contentManager;
|
_contentManager = contentManager;
|
||||||
@ -408,9 +458,18 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
_gameTitle = gameName;
|
_gameTitle = gameName;
|
||||||
_gameId = gameId;
|
_gameId = gameId;
|
||||||
|
|
||||||
if (enableToLoadCustomConfig) // During the game. If there is no user config, then load the global config window
|
if (customConfig) // During the game. If there is no user config, then load the global config window
|
||||||
{
|
{
|
||||||
string gameDir = Program.GetDirGameUserConfig(gameId, false, true);
|
string gameDir = Program.GetDirGameUserConfig(gameId, true);
|
||||||
|
|
||||||
|
Program.UseExtraConfig = true;
|
||||||
|
|
||||||
|
if (ConfigurationFileFormat.TryLoad(Program.GlobalConfigurationPath, out ConfigurationFileFormat configurationFileFormatExtra))
|
||||||
|
{
|
||||||
|
// Extra load global configuration for input setting and save global input setting with other global config
|
||||||
|
ConfigurationState.InstanceExtra.Load(configurationFileFormatExtra, Program.GlobalConfigurationPath);
|
||||||
|
}
|
||||||
|
|
||||||
if (ConfigurationFileFormat.TryLoad(gameDir, out ConfigurationFileFormat configurationFileFormat))
|
if (ConfigurationFileFormat.TryLoad(gameDir, out ConfigurationFileFormat configurationFileFormat))
|
||||||
{
|
{
|
||||||
ConfigurationState.Instance.Load(configurationFileFormat, gameDir, gameId);
|
ConfigurationState.Instance.Load(configurationFileFormat, gameDir, gameId);
|
||||||
@ -426,7 +485,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public SettingsViewModel(bool noLoadGlobalConfig = false)
|
public SettingsViewModel()
|
||||||
{
|
{
|
||||||
GameDirectories = [];
|
GameDirectories = [];
|
||||||
AutoloadDirectories = [];
|
AutoloadDirectories = [];
|
||||||
@ -550,9 +609,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadCurrentConfiguration()
|
public void LoadCurrentConfiguration(bool global = false)
|
||||||
{
|
{
|
||||||
ConfigurationState config = ConfigurationState.Instance;
|
ConfigurationState config = global ? ConfigurationState.InstanceExtra: ConfigurationState.Instance;
|
||||||
|
|
||||||
// User Interface
|
// User Interface
|
||||||
EnableDiscordIntegration = config.EnableDiscordIntegration;
|
EnableDiscordIntegration = config.EnableDiscordIntegration;
|
||||||
@ -578,6 +637,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
|
UseGlobalInputConfig = config.System.UseInputGlobalConfig;
|
||||||
EnableDockedMode = config.System.EnableDockedMode;
|
EnableDockedMode = config.System.EnableDockedMode;
|
||||||
EnableKeyboard = config.Hid.EnableKeyboard;
|
EnableKeyboard = config.Hid.EnableKeyboard;
|
||||||
EnableMouse = config.Hid.EnableMouse;
|
EnableMouse = config.Hid.EnableMouse;
|
||||||
@ -654,15 +714,21 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
|
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
|
||||||
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
||||||
|
|
||||||
|
// Multiplayer
|
||||||
MultiplayerModeIndex = (int)config.Multiplayer.Mode.Value;
|
MultiplayerModeIndex = (int)config.Multiplayer.Mode.Value;
|
||||||
DisableP2P = config.Multiplayer.DisableP2p;
|
DisableP2P = config.Multiplayer.DisableP2p;
|
||||||
LdnPassphrase = config.Multiplayer.LdnPassphrase;
|
LdnPassphrase = config.Multiplayer.LdnPassphrase;
|
||||||
LdnServer = config.Multiplayer.LdnServer;
|
LdnServer = config.Multiplayer.LdnServer;
|
||||||
|
|
||||||
|
// Debug
|
||||||
|
EnableGdbStub = config.Debug.EnableGdbStub.Value;
|
||||||
|
GDBStubPort = config.Debug.GdbStubPort.Value;
|
||||||
|
DebuggerSuspendOnStart = config.Debug.DebuggerSuspendOnStart.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveSettings()
|
public void SaveSettings(bool global = false)
|
||||||
{
|
{
|
||||||
ConfigurationState config = ConfigurationState.Instance;
|
ConfigurationState config = global ? ConfigurationState.InstanceExtra: ConfigurationState.Instance;
|
||||||
|
|
||||||
// User Interface
|
// User Interface
|
||||||
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
|
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
|
||||||
@ -684,6 +750,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
|
config.System.UseInputGlobalConfig.Value = UseGlobalInputConfig;
|
||||||
config.System.EnableDockedMode.Value = EnableDockedMode;
|
config.System.EnableDockedMode.Value = EnableDockedMode;
|
||||||
config.Hid.EnableKeyboard.Value = EnableKeyboard;
|
config.Hid.EnableKeyboard.Value = EnableKeyboard;
|
||||||
config.Hid.EnableMouse.Value = EnableMouse;
|
config.Hid.EnableMouse.Value = EnableMouse;
|
||||||
@ -773,12 +840,18 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
|
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
|
||||||
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
|
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
|
||||||
|
|
||||||
|
// Multiplayer
|
||||||
config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]];
|
config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]];
|
||||||
config.Multiplayer.Mode.Value = (MultiplayerMode)MultiplayerModeIndex;
|
config.Multiplayer.Mode.Value = (MultiplayerMode)MultiplayerModeIndex;
|
||||||
config.Multiplayer.DisableP2p.Value = DisableP2P;
|
config.Multiplayer.DisableP2p.Value = DisableP2P;
|
||||||
config.Multiplayer.LdnPassphrase.Value = LdnPassphrase;
|
config.Multiplayer.LdnPassphrase.Value = LdnPassphrase;
|
||||||
config.Multiplayer.LdnServer.Value = LdnServer;
|
config.Multiplayer.LdnServer.Value = LdnServer;
|
||||||
|
|
||||||
|
// Debug
|
||||||
|
config.Debug.EnableGdbStub.Value = EnableGdbStub;
|
||||||
|
config.Debug.GdbStubPort.Value = GDBStubPort;
|
||||||
|
config.Debug.DebuggerSuspendOnStart.Value = DebuggerSuspendOnStart;
|
||||||
|
|
||||||
// Dirty Hacks
|
// Dirty Hacks
|
||||||
config.Hacks.Xc2MenuSoftlockFix.Value = DirtyHacks.Xc2MenuSoftlockFix;
|
config.Hacks.Xc2MenuSoftlockFix.Value = DirtyHacks.Xc2MenuSoftlockFix;
|
||||||
config.Hacks.DisableNifmIsAnyInternetRequestAccepted.Value =
|
config.Hacks.DisableNifmIsAnyInternetRequestAccepted.Value =
|
||||||
@ -796,11 +869,14 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
private static void RevertIfNotSaved()
|
private static void RevertIfNotSaved()
|
||||||
{
|
{
|
||||||
// maybe this is an unnecessary check(all options need to be tested)
|
/*
|
||||||
|
maybe this is an unnecessary check(all options need to be tested)
|
||||||
if (string.IsNullOrEmpty(Program.GlobalConfigurationPath))
|
if (string.IsNullOrEmpty(Program.GlobalConfigurationPath))
|
||||||
{
|
{
|
||||||
Program.ReloadConfig();
|
Program.ReloadConfig();
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
Program.ReloadConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyButton()
|
public void ApplyButton()
|
||||||
@ -810,7 +886,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public void DeleteConfigGame()
|
public void DeleteConfigGame()
|
||||||
{
|
{
|
||||||
string gameDir = Program.GetDirGameUserConfig(GameId, false, false);
|
string gameDir = Program.GetDirGameUserConfig(GameId);
|
||||||
|
|
||||||
if (File.Exists(gameDir))
|
if (File.Exists(gameDir))
|
||||||
{
|
{
|
||||||
|
@ -100,7 +100,7 @@
|
|||||||
Name="ProfileBox"
|
Name="ProfileBox"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
SelectedItem="{Binding ProfileChoose, Mode=TwoWay}"
|
SelectedItem="{Binding ChosenProfile, Mode=TwoWay}"
|
||||||
SelectionChanged="ComboBox_SelectionChanged"
|
SelectionChanged="ComboBox_SelectionChanged"
|
||||||
ItemsSource="{Binding ProfilesList}"
|
ItemsSource="{Binding ProfilesList}"
|
||||||
Text="{Binding ProfileName, Mode=TwoWay}" />
|
Text="{Binding ProfileName, Mode=TwoWay}" />
|
||||||
@ -203,7 +203,6 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
<ContentControl IsVisible="{Binding NotificationIsVisible}">
|
<ContentControl IsVisible="{Binding NotificationIsVisible}">
|
||||||
<ContentControl.Content>
|
<ContentControl.Content>
|
||||||
|
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Margin="5,20,0,0"
|
Margin="5,20,0,0"
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Ava.Systems.Configuration;
|
||||||
using Ryujinx.Ava.UI.Controls;
|
using Ryujinx.Ava.UI.Controls;
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
@ -14,7 +15,7 @@ namespace Ryujinx.Ava.UI.Views.Input
|
|||||||
|
|
||||||
public InputView()
|
public InputView()
|
||||||
{
|
{
|
||||||
ViewModel = new InputViewModel(this);
|
ViewModel = new InputViewModel(this, ConfigurationState.Instance.System.UseInputGlobalConfig);
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
@ -24,6 +25,13 @@ namespace Ryujinx.Ava.UI.Views.Input
|
|||||||
ViewModel.Save();
|
ViewModel.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ToggleLocalGlobalInput(bool enableConfigGlobal)
|
||||||
|
{
|
||||||
|
Dispose();
|
||||||
|
ViewModel = new InputViewModel(this, enableConfigGlobal); // Create new Input Page with global input configs
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
private async void PlayerIndexBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
private async void PlayerIndexBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (PlayerIndexBox != null)
|
if (PlayerIndexBox != null)
|
||||||
|
@ -64,6 +64,7 @@
|
|||||||
MinWidth="200"
|
MinWidth="200"
|
||||||
Height="6"
|
Height="6"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
|
Margin="0, 0, 5, 0"
|
||||||
Foreground="{DynamicResource SystemAccentColorLight2}"
|
Foreground="{DynamicResource SystemAccentColorLight2}"
|
||||||
IsVisible="{Binding StatusBarVisible}"
|
IsVisible="{Binding StatusBarVisible}"
|
||||||
Maximum="{Binding StatusBarProgressMaximum}"
|
Maximum="{Binding StatusBarProgressMaximum}"
|
||||||
|
66
src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml
Normal file
66
src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<UserControl
|
||||||
|
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsDebugView"
|
||||||
|
xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
||||||
|
xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common"
|
||||||
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
|
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
x:DataType="viewModels:SettingsViewModel">
|
||||||
|
<Design.DataContext>
|
||||||
|
<viewModels:SettingsViewModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
<ScrollViewer
|
||||||
|
Name="DebugPage"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
HorizontalScrollBarVisibility="Disabled"
|
||||||
|
VerticalScrollBarVisibility="Auto">
|
||||||
|
<Border Classes="settings">
|
||||||
|
<StackPanel
|
||||||
|
Margin="10"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Orientation="Vertical"
|
||||||
|
Spacing="10">
|
||||||
|
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabDebugTitle}" />
|
||||||
|
<TextBlock Foreground="{DynamicResource SecondaryTextColor}" Text="{ext:Locale SettingsTabDebugNote}" />
|
||||||
|
<StackPanel
|
||||||
|
Margin="10,0,0,0"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Orientation="Vertical">
|
||||||
|
<CheckBox IsChecked="{Binding EnableGdbStub}">
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabDebugEnableGDBStub}"
|
||||||
|
ToolTip.Tip="{ext:Locale SettingsTabDebugGDBStubToggleTooltip}" />
|
||||||
|
</CheckBox>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel
|
||||||
|
Margin="10,0,0,0"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center"
|
||||||
|
Text="{ext:Locale SettingsTabDebugGDBStubPort}"
|
||||||
|
Width="250" />
|
||||||
|
<ui:NumberBox Value="{Binding GDBStubPort}"
|
||||||
|
Width="350"
|
||||||
|
SmallChange="1"
|
||||||
|
LargeChange="10"
|
||||||
|
SimpleNumberFormat="F0"
|
||||||
|
SpinButtonPlacementMode="Inline"
|
||||||
|
Minimum="1024"
|
||||||
|
Maximum="65535" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel
|
||||||
|
Margin="10,0,0,0"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Orientation="Vertical">
|
||||||
|
<CheckBox IsChecked="{Binding DebuggerSuspendOnStart}">
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabDebugSuspendOnStart}"
|
||||||
|
ToolTip.Tip="{ext:Locale SettingsTabDebugSuspendOnStartTooltip}" />
|
||||||
|
</CheckBox>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</ScrollViewer>
|
||||||
|
</UserControl>
|
13
src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml.cs
Normal file
13
src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.Views.Settings
|
||||||
|
{
|
||||||
|
public partial class SettingsDebugView : UserControl
|
||||||
|
{
|
||||||
|
public SettingsDebugView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -56,7 +56,27 @@
|
|||||||
SelectedIndex="{Binding PreferredGpuIndex}"
|
SelectedIndex="{Binding PreferredGpuIndex}"
|
||||||
ItemsSource="{Binding AvailableGpus}"/>
|
ItemsSource="{Binding AvailableGpus}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center"
|
||||||
|
ToolTip.Tip="{ext:Locale GraphicsBackendThreadingTooltip}"
|
||||||
|
Text="{ext:Locale SettingsTabGraphicsBackendMultithreading}"
|
||||||
|
Width="250" />
|
||||||
|
<ComboBox Width="350"
|
||||||
|
HorizontalContentAlignment="Left"
|
||||||
|
ToolTip.Tip="{ext:Locale GalThreadingTooltip}"
|
||||||
|
SelectedIndex="{Binding GraphicsBackendMultithreadingIndex}">
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale CommonAuto}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale CommonOff}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale CommonOn}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
<Separator Height="1" />
|
<Separator Height="1" />
|
||||||
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGraphicsFeatures}" />
|
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGraphicsFeatures}" />
|
||||||
<StackPanel Margin="10,0,0,0" Orientation="Vertical" Spacing="10">
|
<StackPanel Margin="10,0,0,0" Orientation="Vertical" Spacing="10">
|
||||||
@ -255,32 +275,7 @@
|
|||||||
</ComboBox>
|
</ComboBox>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel
|
|
||||||
Margin="10,0,0,0"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
Orientation="Vertical"
|
|
||||||
Spacing="10">
|
|
||||||
<StackPanel Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center"
|
|
||||||
ToolTip.Tip="{ext:Locale GraphicsBackendThreadingTooltip}"
|
|
||||||
Text="{ext:Locale SettingsTabGraphicsBackendMultithreading}"
|
|
||||||
Width="250" />
|
|
||||||
<ComboBox Width="350"
|
|
||||||
HorizontalContentAlignment="Left"
|
|
||||||
ToolTip.Tip="{ext:Locale GalThreadingTooltip}"
|
|
||||||
SelectedIndex="{Binding GraphicsBackendMultithreadingIndex}">
|
|
||||||
<ComboBoxItem>
|
|
||||||
<TextBlock Text="{ext:Locale CommonAuto}" />
|
|
||||||
</ComboBoxItem>
|
|
||||||
<ComboBoxItem>
|
|
||||||
<TextBlock Text="{ext:Locale CommonOff}" />
|
|
||||||
</ComboBoxItem>
|
|
||||||
<ComboBoxItem>
|
|
||||||
<TextBlock Text="{ext:Locale CommonOn}" />
|
|
||||||
</ComboBoxItem>
|
|
||||||
</ComboBox>
|
|
||||||
</StackPanel>
|
|
||||||
</StackPanel>
|
|
||||||
<Separator Height="1" />
|
<Separator Height="1" />
|
||||||
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGraphicsDeveloperOptions}" />
|
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGraphicsDeveloperOptions}" />
|
||||||
<StackPanel
|
<StackPanel
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<UserControl
|
<UserControl
|
||||||
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsInputView"
|
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsInputView"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
@ -22,9 +22,16 @@
|
|||||||
<Panel
|
<Panel
|
||||||
Margin="10">
|
Margin="10">
|
||||||
<Grid RowDefinitions="Auto,*,Auto">
|
<Grid RowDefinitions="Auto,*,Auto">
|
||||||
<views:InputView
|
<StackPanel>
|
||||||
Grid.Row="0"
|
<!--
|
||||||
Name="InputView" />
|
Opacity="{Binding PanelOpacityInput}">
|
||||||
|
|
||||||
|
IsEnabled="{Binding !EnableConfigGlobal}">
|
||||||
|
-->
|
||||||
|
<views:InputView
|
||||||
|
Grid.Row="0"
|
||||||
|
Name="InputView" />
|
||||||
|
</StackPanel>
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Orientation="Vertical"
|
Orientation="Vertical"
|
||||||
Grid.Row="2">
|
Grid.Row="2">
|
||||||
@ -34,6 +41,13 @@
|
|||||||
<StackPanel
|
<StackPanel
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
Spacing="10">
|
Spacing="10">
|
||||||
|
<CheckBox
|
||||||
|
ToolTip.Tip="{ext:Locale UseGlobalInputTooltip}"
|
||||||
|
MinWidth="0"
|
||||||
|
IsChecked="{Binding UseGlobalInputConfig}">
|
||||||
|
<TextBlock
|
||||||
|
Text="{ext:Locale SettingsTabInputUseGlobalInput}" />
|
||||||
|
</CheckBox>
|
||||||
<CheckBox
|
<CheckBox
|
||||||
ToolTip.Tip="{ext:Locale DockModeToggleTooltip}"
|
ToolTip.Tip="{ext:Locale DockModeToggleTooltip}"
|
||||||
MinWidth="0"
|
MinWidth="0"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<window:StyleableAppWindow xmlns="https://github.com/avaloniaui"
|
<window:StyleableAppWindow xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:helpers="using:Ryujinx.Ava.UI.Helpers"
|
xmlns:helpers="using:Ryujinx.Ava.UI.Helpers"
|
||||||
@ -17,65 +17,277 @@
|
|||||||
<window:StyleableAppWindow.DataContext>
|
<window:StyleableAppWindow.DataContext>
|
||||||
<viewModels:CompatibilityViewModel />
|
<viewModels:CompatibilityViewModel />
|
||||||
</window:StyleableAppWindow.DataContext>
|
</window:StyleableAppWindow.DataContext>
|
||||||
<Grid RowDefinitions="Auto,*">
|
<Grid RowDefinitions="Auto,Auto,*">
|
||||||
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto,Auto" Name="FlushControls">
|
|
||||||
<controls:RyujinxLogo
|
<!-- UI FlushControls -->
|
||||||
|
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto,Auto,Auto" Name="FlushControls">
|
||||||
|
<controls:RyujinxLogo
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Margin="15, 0, 7, 0"
|
Margin="15, 0, 7, 0"
|
||||||
ToolTip.Tip="{ext:WindowTitle CompatibilityListTitle, False}"/>
|
ToolTip.Tip="{ext:WindowTitle CompatibilityListTitle, False}"/>
|
||||||
<TextBox Name="SearchBoxFlush" Grid.Column="1" Margin="0, 5, 0, 5" HorizontalAlignment="Stretch" Watermark="{ext:Locale CompatibilityListSearchBoxWatermarkWithCount}" TextChanged="TextBox_OnTextChanged" />
|
<TextBox Name="SearchBoxFlush" Grid.Column="1" Margin="0, 5, 0, 5" HorizontalAlignment="Stretch" Watermark="{ext:Locale CompatibilityListSearchBoxWatermarkWithCount}" TextChanged="TextBox_OnTextChanged" />
|
||||||
|
<StackPanel Grid.Column="2" Orientation="Horizontal" Margin="10, 5, 0, 5">
|
||||||
|
<TextBlock
|
||||||
|
Margin="10,0"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
DockPanel.Dock="Right"
|
||||||
|
Text="{ext:Locale CommonSort}" />
|
||||||
|
<DropDownButton
|
||||||
|
Width="150"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Content="{Binding SortName}"
|
||||||
|
DockPanel.Dock="Right">
|
||||||
|
<DropDownButton.Flyout>
|
||||||
|
<Flyout Placement="Bottom">
|
||||||
|
<StackPanel
|
||||||
|
Margin="0"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Orientation="Vertical">
|
||||||
|
<StackPanel>
|
||||||
|
<RadioButton
|
||||||
|
Checked="Sort_Name_Checked"
|
||||||
|
Content="{ext:Locale GameListSortStatusNameAscending}"
|
||||||
|
GroupName="Sort"
|
||||||
|
IsChecked="{Binding IsSortedByTitle, Mode=OneTime}"
|
||||||
|
Tag="0" />
|
||||||
|
<RadioButton
|
||||||
|
Checked="Sort_Name_Checked"
|
||||||
|
Content="{ext:Locale GameListSortStatusNameDescending}"
|
||||||
|
GroupName="Sort"
|
||||||
|
Tag="1" />
|
||||||
|
</StackPanel>
|
||||||
|
<Border
|
||||||
|
Width="60"
|
||||||
|
Height="2"
|
||||||
|
Margin="5"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
BorderBrush="White"
|
||||||
|
BorderThickness="0,1,0,0">
|
||||||
|
<Separator Height="0" HorizontalAlignment="Stretch" />
|
||||||
|
</Border>
|
||||||
|
<RadioButton
|
||||||
|
Checked="Sort_Status_Checked"
|
||||||
|
Content="{ext:Locale GameListSortStatusDisable}"
|
||||||
|
GroupName="Order"
|
||||||
|
IsChecked="{Binding IsSortedByStatus, Mode=OneTime}"
|
||||||
|
Tag="0" />
|
||||||
|
<RadioButton
|
||||||
|
Checked="Sort_Status_Checked"
|
||||||
|
Content="{ext:Locale GameListSortStatusAscending}"
|
||||||
|
GroupName="Order"
|
||||||
|
Tag="1" />
|
||||||
|
<RadioButton
|
||||||
|
Checked="Sort_Status_Checked"
|
||||||
|
Content="{ext:Locale GameListSortStatusDescending}"
|
||||||
|
GroupName="Order"
|
||||||
|
Tag="2" />
|
||||||
|
</StackPanel>
|
||||||
|
</Flyout>
|
||||||
|
</DropDownButton.Flyout>
|
||||||
|
</DropDownButton>
|
||||||
|
</StackPanel>
|
||||||
|
<CheckBox Grid.Column="3" Margin="7, 0, 0, 0" IsChecked="{Binding OnlyShowOwnedGames}" />
|
||||||
|
<TextBlock Grid.Column="4" Padding="0, 0, 138, 0" Margin="-10, 0, 18, 0" Text="{ext:Locale CompatibilityListOnlyShowOwnedGames}" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- UI NormalControls -->
|
||||||
|
<Grid Grid.Row="0" ColumnDefinitions="*,Auto,Auto,Auto" Name="NormalControls">
|
||||||
|
<TextBox Name="SearchBoxNormal" Grid.Column="0" Margin="15, 0, 0, 5" HorizontalAlignment="Stretch" Watermark="{ext:Locale CompatibilityListSearchBoxWatermarkWithCount}" TextChanged="TextBox_OnTextChanged" />
|
||||||
|
<StackPanel Grid.Column="1" Orientation="Horizontal" Margin="10, 0, 5, 5">
|
||||||
|
<TextBlock
|
||||||
|
Margin="10,0"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
DockPanel.Dock="Right"
|
||||||
|
Text="{ext:Locale CommonSort}" />
|
||||||
|
<DropDownButton
|
||||||
|
Width="150"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Content="{Binding SortName}"
|
||||||
|
DockPanel.Dock="Right">
|
||||||
|
<DropDownButton.Flyout>
|
||||||
|
<Flyout Placement="Bottom">
|
||||||
|
<StackPanel
|
||||||
|
Margin="0"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Orientation="Vertical">
|
||||||
|
<StackPanel>
|
||||||
|
<RadioButton
|
||||||
|
Checked="Sort_Name_Checked"
|
||||||
|
Content="{ext:Locale GameListSortStatusNameAscending}"
|
||||||
|
GroupName="Sort"
|
||||||
|
IsChecked="{Binding IsSortedByTitle, Mode=OneTime}"
|
||||||
|
Tag="0" />
|
||||||
|
<RadioButton
|
||||||
|
Checked="Sort_Name_Checked"
|
||||||
|
Content="{ext:Locale GameListSortStatusNameDescending}"
|
||||||
|
GroupName="Sort"
|
||||||
|
Tag="1" />
|
||||||
|
</StackPanel>
|
||||||
|
<Border
|
||||||
|
Width="60"
|
||||||
|
Height="2"
|
||||||
|
Margin="5"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
BorderBrush="White"
|
||||||
|
BorderThickness="0,1,0,0">
|
||||||
|
<Separator Height="0" HorizontalAlignment="Stretch" />
|
||||||
|
</Border>
|
||||||
|
<RadioButton
|
||||||
|
Checked="Sort_Status_Checked"
|
||||||
|
Content="{ext:Locale GameListSortStatusDisable}"
|
||||||
|
GroupName="Order"
|
||||||
|
IsChecked="{Binding IsSortedByStatus, Mode=OneTime}"
|
||||||
|
Tag="0" />
|
||||||
|
<RadioButton
|
||||||
|
Checked="Sort_Status_Checked"
|
||||||
|
Content="{ext:Locale GameListSortStatusAscending}"
|
||||||
|
GroupName="Order"
|
||||||
|
Tag="1" />
|
||||||
|
<RadioButton
|
||||||
|
Checked="Sort_Status_Checked"
|
||||||
|
Content="{ext:Locale GameListSortStatusDescending}"
|
||||||
|
GroupName="Order"
|
||||||
|
Tag="2" />
|
||||||
|
</StackPanel>
|
||||||
|
</Flyout>
|
||||||
|
</DropDownButton.Flyout>
|
||||||
|
</DropDownButton>
|
||||||
|
</StackPanel>
|
||||||
<CheckBox Grid.Column="2" Margin="7, 0, 0, 0" IsChecked="{Binding OnlyShowOwnedGames}" />
|
<CheckBox Grid.Column="2" Margin="7, 0, 0, 0" IsChecked="{Binding OnlyShowOwnedGames}" />
|
||||||
<TextBlock Grid.Column="3" Padding="0, 0, 138, 0" Margin="-10, 0, 18, 0" Text="{ext:Locale CompatibilityListOnlyShowOwnedGames}" />
|
<TextBlock Grid.Column="3" Padding="0, 0, 1, 0" Margin="-10, 0, 18, 0" Text="{ext:Locale CompatibilityListOnlyShowOwnedGames}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid Grid.Row="0" ColumnDefinitions="*,Auto,Auto" Name="NormalControls">
|
|
||||||
<TextBox Name="SearchBoxNormal" Grid.Column="0" Margin="15, 0, 0, 5" HorizontalAlignment="Stretch" Watermark="{ext:Locale CompatibilityListSearchBoxWatermark}" TextChanged="TextBox_OnTextChanged" />
|
<!-- Description Field Above ScrollViewer -->
|
||||||
<CheckBox Grid.Column="1" Margin="7, 0, 0, 0" IsChecked="{Binding OnlyShowOwnedGames}" />
|
<Grid Grid.Row="1" ColumnDefinitions="*,Auto" Margin="10, 5, 10, 5">
|
||||||
<TextBlock Grid.Column="2" Padding="0, 0, 1, 0" Margin="-10, 0, 18, 0" Text="{ext:Locale CompatibilityListOnlyShowOwnedGames}" />
|
<Grid Grid.Column="0">
|
||||||
|
<Border Classes="listbox-item-style">
|
||||||
|
<Grid MinWidth="800"
|
||||||
|
ColumnDefinitions="Auto,Auto,Auto,*,Auto"
|
||||||
|
Background="Transparent">
|
||||||
|
<TextBlock Grid.Column="0"
|
||||||
|
Text="{ext:Locale CompatibilityListGamesAndApplications}"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Width="525"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Text="ID"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Width="135"
|
||||||
|
Padding="7, 0, 0, 0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<TextBlock Grid.Column="2"
|
||||||
|
Padding="7, 0"
|
||||||
|
Text="{ext:Locale CompatibilityListStatus}"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Width="100"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
TextWrapping="NoWrap" />
|
||||||
|
<TextBlock Grid.Column="3"
|
||||||
|
Text="{ext:Locale CompatibilityListDescription}"
|
||||||
|
FontWeight="Bold"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
TextWrapping="WrapWithOverflow" />
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
<Grid Grid.Column="1">
|
||||||
|
<DropDownButton
|
||||||
|
Width="80"
|
||||||
|
Height="35"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Content="Info"
|
||||||
|
DockPanel.Dock="Right">
|
||||||
|
<DropDownButton.Flyout>
|
||||||
|
<Flyout Placement="Bottom">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Padding="0,5"
|
||||||
|
Text="Compatibility verified:" />
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Foreground="{Binding IsStringPlayable, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
|
||||||
|
Text="{Binding PlayableInfoText }" />
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Foreground="{Binding IsStringInGame, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
|
||||||
|
Text="{Binding InGameInfoText }" />
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Foreground="{Binding IsStringMenus, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
|
||||||
|
Text="{Binding MenusInfoText }" />
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Foreground="{Binding IsStringBoots, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
|
||||||
|
Text="{Binding BootsInfoText }" />
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Foreground="{Binding IsStringNothing, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
|
||||||
|
Text="{Binding NothingInfoText }" />
|
||||||
|
</StackPanel>
|
||||||
|
</Flyout>
|
||||||
|
</DropDownButton.Flyout>
|
||||||
|
</DropDownButton>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<ScrollViewer Grid.Row="1">
|
|
||||||
<ListBox Margin="12, 0, 13, 0"
|
<!-- List of compatible games -->
|
||||||
Background="Transparent"
|
<ScrollViewer Grid.Row="2">
|
||||||
ItemsSource="{Binding CurrentEntries}">
|
<ListBox Margin="12, 0, 13, 0"
|
||||||
<ListBox.ItemTemplate>
|
Background="Transparent"
|
||||||
<DataTemplate DataType="{x:Type systems:CompatibilityEntry}">
|
ItemsSource="{Binding CurrentEntries}">
|
||||||
<Grid MinWidth="800"
|
<ListBox.ItemTemplate>
|
||||||
Margin="10"
|
<DataTemplate DataType="{x:Type systems:CompatibilityEntry}">
|
||||||
ColumnDefinitions="Auto,Auto,Auto,*"
|
<Grid MinWidth="800"
|
||||||
Background="Transparent"
|
Margin="10"
|
||||||
ToolTip.Tip="{Binding LocalizedLastUpdated}">
|
ColumnDefinitions="Auto,Auto,Auto,*"
|
||||||
<TextBlock Grid.Column="0"
|
Background="Transparent"
|
||||||
Text="{Binding GameName}"
|
ToolTip.Tip="{Binding LocalizedLastUpdated}">
|
||||||
Width="525"
|
<TextBlock Grid.Column="0"
|
||||||
VerticalAlignment="Center"
|
Text="{Binding GameName}"
|
||||||
HorizontalAlignment="Center"
|
Width="525"
|
||||||
TextWrapping="Wrap" />
|
VerticalAlignment="Center"
|
||||||
<TextBlock Grid.Column="1"
|
HorizontalAlignment="Center"
|
||||||
Width="135"
|
TextWrapping="Wrap" />
|
||||||
Padding="7, 0, 0, 0"
|
<TextBlock Grid.Column="1"
|
||||||
FontFamily="{StaticResource JetBrainsMono}"
|
Width="135"
|
||||||
Text="{Binding FormattedTitleId}"
|
Padding="7, 0, 0, 0"
|
||||||
VerticalAlignment="Center"
|
FontFamily="{StaticResource JetBrainsMono}"
|
||||||
HorizontalAlignment="Center"
|
Text="{Binding FormattedTitleId}"
|
||||||
TextWrapping="Wrap" />
|
VerticalAlignment="Center"
|
||||||
<TextBlock Grid.Column="2"
|
HorizontalAlignment="Center"
|
||||||
Padding="7, 0"
|
TextWrapping="Wrap" />
|
||||||
Text="{Binding LocalizedStatus}"
|
<TextBlock Grid.Column="2"
|
||||||
Width="90"
|
Padding="7, 0"
|
||||||
Background="Transparent"
|
Text="{Binding LocalizedStatus}"
|
||||||
ToolTip.Tip="{Binding LocalizedStatusDescription}"
|
Width="100"
|
||||||
Foreground="{Binding Status, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
|
Background="Transparent"
|
||||||
VerticalAlignment="Center"
|
ToolTip.Tip="{Binding LocalizedStatusDescription}"
|
||||||
HorizontalAlignment="Center"
|
Foreground="{Binding Status, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
|
||||||
TextWrapping="NoWrap" />
|
VerticalAlignment="Center"
|
||||||
<TextBlock Grid.Column="3"
|
HorizontalAlignment="Center"
|
||||||
Text="{Binding FormattedIssueLabels}"
|
TextWrapping="NoWrap" />
|
||||||
VerticalAlignment="Center"
|
<TextBlock Grid.Column="3"
|
||||||
HorizontalAlignment="Left"
|
Text="{Binding FormattedIssueLabels}"
|
||||||
TextWrapping="WrapWithOverflow" />
|
VerticalAlignment="Center"
|
||||||
</Grid>
|
HorizontalAlignment="Left"
|
||||||
</DataTemplate>
|
TextWrapping="WrapWithOverflow" />
|
||||||
</ListBox.ItemTemplate>
|
</Grid>
|
||||||
</ListBox>
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
<Grid></Grid>
|
<Grid></Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
using Ryujinx.Ava.Systems.Configuration;
|
using Ryujinx.Ava.Systems.Configuration;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -42,5 +43,28 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
cvm.Search(searchBox.Text);
|
cvm.Search(searchBox.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Sort_Name_Checked(object sender, RoutedEventArgs args)
|
||||||
|
{
|
||||||
|
if (sender is RadioButton { Tag: string sortStrategy })
|
||||||
|
{
|
||||||
|
if (DataContext is not CompatibilityViewModel cvm)
|
||||||
|
return;
|
||||||
|
|
||||||
|
cvm.NameSorting(int.Parse(sortStrategy));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Sort_Status_Checked(object sender, RoutedEventArgs args)
|
||||||
|
{
|
||||||
|
if (sender is RadioButton { Tag: string sortStrategy })
|
||||||
|
{
|
||||||
|
if (DataContext is not CompatibilityViewModel cvm)
|
||||||
|
return;
|
||||||
|
|
||||||
|
cvm.StatusSorting(int.Parse(sortStrategy));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,30 +120,34 @@
|
|||||||
IconSource="Code" />
|
IconSource="Code" />
|
||||||
</ui:NavigationView.MenuItems>
|
</ui:NavigationView.MenuItems>
|
||||||
</ui:NavigationView>
|
</ui:NavigationView>
|
||||||
|
<Grid Grid.Row="3"
|
||||||
<ReversibleStackPanel
|
ColumnDefinitions="Auto,*,Auto">
|
||||||
Grid.Row="2"
|
<StackPanel Grid.Column="0" Orientation="Horizontal" Margin="10" Spacing="10">
|
||||||
Margin="10"
|
<Button
|
||||||
Spacing="10"
|
IsVisible="{Binding !IsGameRunning}"
|
||||||
Orientation="Horizontal"
|
Content="{ext:Locale UserProfilesDelete}"
|
||||||
HorizontalAlignment="Right"
|
Command="{Binding DeleteConfigGame}"
|
||||||
ReverseOrder="{x:Static helper:RunningPlatform.IsMacOS}">
|
Classes="red"/>
|
||||||
<Button
|
</StackPanel>
|
||||||
Content="{ext:Locale SettingsButtonSave}"
|
<ReversibleStackPanel
|
||||||
Command="{Binding SaveUserConfig}" />
|
Grid.Column="2"
|
||||||
<Button
|
Margin="10"
|
||||||
HotKey="Escape"
|
Spacing="10"
|
||||||
Content="{ext:Locale SettingsButtonClose}"
|
Orientation="Horizontal"
|
||||||
Command="{Binding CancelButton}" />
|
HorizontalAlignment="Right"
|
||||||
<Button
|
ReverseOrder="{x:Static helper:RunningPlatform.IsMacOS}">
|
||||||
IsVisible="{Binding IsGameRunning}"
|
<Button
|
||||||
Content="{ext:Locale SettingsButtonApply}"
|
Classes="accent"
|
||||||
Command="{Binding ApplyButton}" />
|
Content="{ext:Locale SettingsButtonSave}"
|
||||||
<Button
|
Command="{Binding SaveUserConfig}" />
|
||||||
IsVisible="{Binding !IsGameRunning}"
|
<Button
|
||||||
Content="{ext:Locale UserProfilesDelete}"
|
HotKey="Escape"
|
||||||
Command="{Binding DeleteConfigGame}"
|
Content="{ext:Locale SettingsButtonClose}"
|
||||||
Classes="red"/>
|
Command="{Binding CancelButton}" />
|
||||||
</ReversibleStackPanel>
|
<Button
|
||||||
|
Content="{ext:Locale SettingsButtonApply}"
|
||||||
|
Command="{Binding ApplyButton}" />
|
||||||
|
</ReversibleStackPanel>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</window:StyleableAppWindow>
|
</window:StyleableAppWindow>
|
||||||
|
@ -28,6 +28,8 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
ViewModel.CloseWindow += Close;
|
ViewModel.CloseWindow += Close;
|
||||||
ViewModel.SaveSettingsEvent += SaveSettings;
|
ViewModel.SaveSettingsEvent += SaveSettings;
|
||||||
|
|
||||||
|
ViewModel.LocalGlobalInputSwitchEvent += ToggleLocalGlobalInput;
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
Load();
|
Load();
|
||||||
}
|
}
|
||||||
@ -37,6 +39,11 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
InputPage.InputView?.SaveCurrentProfile();
|
InputPage.InputView?.SaveCurrentProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ToggleLocalGlobalInput(bool enableConfigGlobal)
|
||||||
|
{
|
||||||
|
InputPage.InputView?.ToggleLocalGlobalInput(enableConfigGlobal);
|
||||||
|
}
|
||||||
|
|
||||||
private void Load()
|
private void Load()
|
||||||
{
|
{
|
||||||
Pages.Children.Clear();
|
Pages.Children.Clear();
|
||||||
@ -90,6 +97,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
protected override void OnClosing(WindowClosingEventArgs e)
|
protected override void OnClosing(WindowClosingEventArgs e)
|
||||||
{
|
{
|
||||||
|
Program.UseExtraConfig = false;
|
||||||
InputPage.Dispose(); // You need to unload the gamepad settings, otherwise the controls will be blocked
|
InputPage.Dispose(); // You need to unload the gamepad settings, otherwise the controls will be blocked
|
||||||
base.OnClosing(e);
|
base.OnClosing(e);
|
||||||
}
|
}
|
||||||
|
@ -213,13 +213,13 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Application_Opened(object sender, ApplicationOpenedEventArgs args)
|
public async void Application_Opened(object sender, ApplicationOpenedEventArgs args)
|
||||||
{
|
{
|
||||||
if (args.Application != null)
|
if (args.Application != null)
|
||||||
{
|
{
|
||||||
ViewModel.SelectedIcon = args.Application.Icon;
|
ViewModel.SelectedIcon = args.Application.Icon;
|
||||||
|
|
||||||
ViewModel.LoadApplication(args.Application).Wait();
|
await ViewModel.LoadApplication(args.Application);
|
||||||
}
|
}
|
||||||
|
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user