Compare commits

..

No commits in common. "master" and "Canary-1.3.76" have entirely different histories.

45 changed files with 1120 additions and 1492 deletions

View file

@ -24,6 +24,41 @@ 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: 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 }}
@ -44,6 +79,16 @@ jobs:
- name: Overwrite csc problem matcher - name: Overwrite csc problem matcher
run: echo "::add-matcher::.github/csc.json" run: echo "::add-matcher::.github/csc.json"
- 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: Get version info - name: Get version info
id: version_info id: version_info
run: | run: |
@ -146,6 +191,33 @@ jobs:
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" 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"
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: |
# 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
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
@ -205,15 +277,31 @@ jobs:
./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" 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
uses: ncipollo/release-action@v1
with:
name: "Canary ${{ steps.version_info.outputs.build_version }}"
artifacts: "publish_ava/*.tar.gz"
tag: ${{ steps.version_info.outputs.build_version }}
body: ""
omitBodyDuringUpdate: true
allowUpdates: true
replacesArtifacts: true
owner: ${{ secrets.RC_OWNER }}
repo: ${{ secrets.RC_CANARY_NAME }}
token: ${{ secrets.ALT_RELEASE_TOKEN }}
create_gitlab_release: create_gitlab_release:
name: Create GitLab Release name: Create GitLab Release
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: needs:
- tag
- macos_release - macos_release
- release - release
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Get version info - name: Get version info
id: version_info id: version_info
run: | run: |
@ -238,12 +326,8 @@ jobs:
- name: Create release - name: Create release
run: | run: |
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=CreateReleaseFromGenericPackageFiles "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|main|Canary ${{ steps.version_info.outputs.build_version }}|**Full Changelog:** [${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}](https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }})" gli --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 - name: Send notification webhook
run: | run: |
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=SendUpdateMessage "${{ steps.version_info.outputs.build_version }}|FF4500|${{ secrets.CANARY_DISCORD_WEBHOOK }}|https://avatars.githubusercontent.com/u/192939710?s=200&v=4|false" gli --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 }}'

View file

@ -14,6 +14,38 @@ 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: 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 }}
@ -137,6 +169,30 @@ jobs:
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
- 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
@ -194,10 +250,25 @@ jobs:
./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" 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"
- name: Pushing new release
uses: ncipollo/release-action@v1
with:
name: ${{ steps.version_info.outputs.build_version }}
artifacts: "publish/*.tar.gz"
tag: ${{ steps.version_info.outputs.build_version }}
body: ""
omitBodyDuringUpdate: true
allowUpdates: true
replacesArtifacts: true
owner: ${{ secrets.RC_OWNER }}
repo: ${{ secrets.RC_STABLE_NAME }}
token: ${{ secrets.ALT_RELEASE_TOKEN }}
create_gitlab_release: create_gitlab_release:
name: Create GitLab Release name: Create GitLab Release
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: needs:
- tag
- macos_release - macos_release
- release - release
steps: steps:
@ -228,7 +299,3 @@ jobs:
- name: Send notification webhook - name: Send notification webhook
run: | run: |
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=SendUpdateMessage "${{ steps.version_info.outputs.build_version }}|32cd32|${{ secrets.STABLE_DISCORD_WEBHOOK }}|https://avatars.githubusercontent.com/u/192939710?s=200&v=4|false" gli --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 }}'

View file

@ -2,17 +2,20 @@
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.

View file

@ -40,10 +40,8 @@
<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" /> <PackageVersion Include="Ryujinx.LibHac" Version="0.20.0-alpha.110" />
<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" />

View file

@ -7,8 +7,8 @@
# Ryujinx # Ryujinx
[![Latest release](https://img.shields.io/gitlab/v/release/ryubing%2Fryujinx?gitlab_url=https%3A%2F%2Fgit.ryujinx.app&label=stable&color=32cd32)](https://update.ryujinx.app/latest/stable) [![Latest release](https://img.shields.io/gitlab/v/release/ryubing%2Fryujinx?gitlab_url=https%3A%2F%2Fgit.ryujinx.app&label=stable&color=32cd32)](https://git.ryujinx.app/ryubing/ryujinx/-/releases)
[![Latest canary release](https://img.shields.io/gitlab/v/release/ryubing%2Fcanary?gitlab_url=https%3A%2F%2Fgit.ryujinx.app&label=canary&color=FF4500)](https://update.ryujinx.app/latest/canary) [![Latest canary release](https://img.shields.io/gitlab/v/release/ryubing%2Fcanary?gitlab_url=https%3A%2F%2Fgit.ryujinx.app&label=canary&color=FF4500)](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">

View file

@ -77,8 +77,6 @@ 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
@ -86,9 +84,10 @@ 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

File diff suppressed because it is too large Load diff

View file

@ -1125,7 +1125,6 @@
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
@ -2436,7 +2435,6 @@
0100E9C010EA8000,"Rise of Insanity",,playable,2020-08-30 15:42:14 0100E9C010EA8000,"Rise of Insanity",,playable,2020-08-30 15:42:14
01006BA00E652000,"Rise: Race The Future",,playable,2021-02-27 13:29:06 01006BA00E652000,"Rise: Race The Future",,playable,2021-02-27 13:29:06
010020C012F48000,"Rising Hell",,playable,2022-10-31 13:54:02 010020C012F48000,"Rising Hell",,playable,2022-10-31 13:54:02
0100D1801A0F4000,"Risk of Rain Returns",,playable,2025-06-28 04:24:04
010076D00E4BA000,"Risk of Rain 2",online-broken,playable,2024-03-04 17:01:05 010076D00E4BA000,"Risk of Rain 2",online-broken,playable,2024-03-04 17:01:05
0100E8300A67A000,"RISK® Global Domination",nvdec;online-broken,playable,2022-08-01 18:53:28 0100E8300A67A000,"RISK® Global Domination",nvdec;online-broken,playable,2022-08-01 18:53:28
010042500FABA000,"Ritual: Crown of Horns",,playable,2021-01-26 16:01:47 010042500FABA000,"Ritual: Crown of Horns",,playable,2021-01-26 16:01:47
@ -2747,7 +2745,6 @@
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
@ -3018,7 +3015,6 @@
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

1 title_id game_name labels status last_updated
1125 0100034012606000 Family Mysteries: Poisonous Promises audio;crash menus 2021-11-26 12:35:06
1126 010017C012726000 Fantasy Friends playable 2022-10-17 19:42:39
1127 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
1128 0100944003820000 Fantasy Strike online playable 2021-02-27 01:59:18
1129 01000E2012F6E000 Fantasy Tavern Sextet -Vol.1 New World Days- gpu;crash;Needs Update ingame 2022-12-05 16:48:00
1130 01005C10136CA000 Fantasy Tavern Sextet -Vol.2 Adventurer's Days- gpu;slow;crash ingame 2021-11-06 02:57:29
2435 0100E9C010EA8000 Rise of Insanity playable 2020-08-30 15:42:14
2436 01006BA00E652000 Rise: Race The Future playable 2021-02-27 13:29:06
2437 010020C012F48000 Rising Hell playable 2022-10-31 13:54:02
0100D1801A0F4000 Risk of Rain Returns playable 2025-06-28 04:24:04
2438 010076D00E4BA000 Risk of Rain 2 online-broken playable 2024-03-04 17:01:05
2439 0100E8300A67A000 RISK® Global Domination nvdec;online-broken playable 2022-08-01 18:53:28
2440 010042500FABA000 Ritual: Crown of Horns playable 2021-01-26 16:01:47
2745 01005D701264A000 SpyHack playable 2021-04-15 10:53:51
2746 010077B00E046000 Spyro™ Reignited Trilogy nvdec;UE4 playable 2022-09-11 18:38:33
2747 0100085012A0E000 Squeakers playable 2020-12-13 12:13:05
0100E1D01EB2E000 Squeakross: Home Squeak Home playable 2025-06-16 02:02:00
2748 010009300D31C000 Squidgies Takeover playable 2020-07-20 22:28:08
2749 0100FCD0102EC000 Squidlit playable 2020-08-06 12:38:32
2750 0100EBF00E702000 STAR OCEAN First Departure R nvdec playable 2021-07-05 19:29:16
3015 01009B101044C000 The Legend of Heroes: Trails of Cold Steel III Demo demo;nvdec playable 2021-04-23 01:07:32
3016 0100D3C010DE8000 The Legend of Heroes: Trails of Cold Steel IV nvdec playable 2021-04-23 14:01:05
3017 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
3018 01008CF01BAAC000 The Legend of Zelda Echoes of Wisdom nvdec;ASTC;intel-vendor-bug playable 2024-10-01 14:11:01
3019 0100509005AF2000 The Legend of Zelda: Breath of the Wild Demo demo ingame 2022-12-24 05:02:58
3020 01007EF00011E000 The Legend of Zelda™: Breath of the Wild gpu;amd-vendor-bug;mac-bug ingame 2024-09-23 19:35:46

View file

@ -4,22 +4,20 @@
<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" />
<!-- Only needed when using pre-release versions of Ryujinx.LibHac. --> <add key="LibHacAlpha" value="https://git.ryujinx.app/api/v4/projects/17/packages/nuget/index.json" />
<!--<add key="LibHacAlpha" value="https://git.ryujinx.app/api/v4/projects/17/packages/nuget/index.json" />--> <add key="RyubingPkgs" value="https://git.ryujinx.app/api/v4/projects/1/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="Ryujinx.UpdateClient"> <packageSource key="LibHacAlpha">
<package pattern="Ryujinx.UpdateClient" />
<package pattern="Ryujinx.Systems.Update.Common" />
</packageSource>
<!--<packageSource key="LibHacAlpha">
<package pattern="Ryujinx.LibHac" /> <package pattern="Ryujinx.LibHac" />
</packageSource>--> </packageSource>
</packageSourceMapping> </packageSourceMapping>
</configuration> </configuration>

View file

@ -133,6 +133,7 @@ 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
@ -168,8 +169,6 @@ 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
@ -195,7 +194,6 @@ 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

View file

@ -377,7 +377,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
bool cursorVisible = false; bool cursorVisible = false;
if (state.CursorBegin != state.CursorEnd && state.CursorEnd <= state.InputText.Length) if (state.CursorBegin != state.CursorEnd)
{ {
Debug.Assert(state.InputText.Length > 0); Debug.Assert(state.InputText.Length > 0);

View file

@ -21,21 +21,6 @@ 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)

View file

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

View file

@ -372,12 +372,6 @@
<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}" />

View file

@ -0,0 +1,9 @@
namespace Ryujinx.Ava.Common.Models.Github
{
public class GithubReleaseAssetJsonResponse
{
public string Name { get; set; }
public string State { get; set; }
public string BrowserDownloadUrl { get; set; }
}
}

View file

@ -0,0 +1,12 @@
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; }
}
}

View file

@ -0,0 +1,7 @@
using System.Text.Json.Serialization;
namespace Ryujinx.Ava.Common.Models.Github
{
[JsonSerializable(typeof(GithubReleasesJsonResponse), GenerationMode = JsonSourceGenerationMode.Metadata)]
public partial class GithubReleasesJsonSerializerContext : JsonSerializerContext;
}

View file

@ -35,7 +35,6 @@ 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; }
@ -160,8 +159,7 @@ 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))
{ {
@ -170,10 +168,15 @@ 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;
@ -181,6 +184,8 @@ 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);
@ -220,12 +225,6 @@ 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

View file

@ -65,8 +65,6 @@
<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" />

View file

@ -75,7 +75,6 @@ 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;
@ -176,7 +175,6 @@ 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"))
{ {
@ -463,15 +461,7 @@ namespace Ryujinx.Ava.Systems
DisplaySleep.Prevent(); DisplaySleep.Prevent();
if (ConfigurationState.Instance.System.UseInputGlobalConfig.Value && Program.UseExtraConfig) NpadManager.Initialize(Device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
{
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;
@ -567,7 +557,6 @@ namespace Ryujinx.Ava.Systems
public void Stop() public void Stop()
{ {
_isActive = false; _isActive = false;
_playTimer.Stop();
} }
private void Exit() private void Exit()
@ -619,7 +608,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, _playTimer.Elapsed); MainWindowViewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText);
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState; ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState; ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState;
@ -638,7 +627,6 @@ namespace Ryujinx.Ava.Systems
_gpuCancellationTokenSource.Dispose(); _gpuCancellationTokenSource.Dispose();
_chrono.Stop(); _chrono.Stop();
_playTimer.Stop();
} }
public void DisposeGpu() public void DisposeGpu()
@ -872,7 +860,6 @@ 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;
} }
@ -882,7 +869,6 @@ 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");
} }
@ -892,7 +878,6 @@ 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");
} }
@ -1159,24 +1144,6 @@ 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;

View file

@ -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)); // Just check user config data.HasIndependentConfiguration = File.Exists(Program.GetDirGameUserConfig(data.IdBaseString, false, false)); // Just check user config
} }
data.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper(); data.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper();

View file

@ -33,11 +33,19 @@ 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>
/// <param name="playTime">The active gameplay time this past session.</param> public void UpdatePostGame()
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));
} }
} }
} }

View file

@ -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 = 70; public const int CurrentVersion = 69;
/// <summary> /// <summary>
/// Version of the configuration file format /// Version of the configuration file format
@ -152,11 +152,6 @@ 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>

View file

@ -90,7 +90,6 @@ 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;
@ -147,7 +146,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;

View file

@ -326,12 +326,6 @@ 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>
@ -423,8 +417,6 @@ 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>();
@ -769,8 +761,6 @@ 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>

View file

@ -15,13 +15,12 @@ namespace Ryujinx.Ava.Systems.Configuration
{ {
public static void Initialize() public static void Initialize()
{ {
if (Instance != null || InstanceExtra!= null) if (Instance != 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()
@ -55,7 +54,6 @@ 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,
@ -180,7 +178,6 @@ 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;

View file

@ -0,0 +1,190 @@
using Gommon;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Common.Models.Github;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Common;
using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace Ryujinx.Ava.Systems
{
internal static partial class Updater
{
private static GitHubReleaseChannels.Channel? _currentGitHubReleaseChannel;
private static async Task<Optional<(Version Current, Version Incoming)>> CheckGitHubVersionAsync(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 from GitHub.");
// Get latest version number from GitHub API
try
{
using HttpClient jsonClient = ConstructHttpClient();
if (_currentGitHubReleaseChannel == null)
{
GitHubReleaseChannels releaseChannels = await GitHubReleaseChannels.GetAsync(jsonClient);
_currentGitHubReleaseChannel = ReleaseInformation.IsCanaryBuild
? releaseChannels.Canary
: releaseChannels.Stable;
Logger.Info?.Print(LogClass.Application, $"Loaded GitHub release channel for '{(ReleaseInformation.IsCanaryBuild ? "canary" : "stable")}'");
_changelogUrlFormat = _currentGitHubReleaseChannel.Value.UrlFormat;
}
string fetchedJson = await jsonClient.GetStringAsync(_currentGitHubReleaseChannel.Value.GetLatestReleaseApiUrl());
GithubReleasesJsonResponse fetched = JsonHelper.Deserialize(fetchedJson, _ghSerializerContext.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(_changelogUrlFormat.Format(currentVersion));
}
}
Logger.Info?.Print(LogClass.Application, "Up to date.");
_running = false;
return default;
}
break;
}
}
// If build not done, assume no new update is 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(_changelogUrlFormat.Format(currentVersion));
}
}
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 readonly struct GitHubReleaseChannels
{
public static async Task<GitHubReleaseChannels> GetAsync(HttpClient httpClient)
{
ReleaseChannelPair releaseChannelPair = await httpClient.GetFromJsonAsync("https://ryujinx.app/api/release-channels", ReleaseChannelPairContext.Default.ReleaseChannelPair);
return new GitHubReleaseChannels(releaseChannelPair);
}
internal GitHubReleaseChannels(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 string UrlFormat => $"https://github.com/{ToString()}/releases/{{0}}";
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; }
}
}

View file

@ -4,28 +4,44 @@ using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Helper; using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Systems.Update.Client;
using Ryujinx.Systems.Update.Common;
using System; using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ryujinx.Ava.Systems namespace Ryujinx.Ava.Systems
{ {
internal static partial class Updater internal static partial class Updater
{ {
private static VersionResponse _versionResponse; private static string CreateUpdateQueryUrl()
{
#pragma warning disable CS8524
var os = RunningPlatform.CurrentOS switch
#pragma warning restore CS8524
{
OperatingSystemType.MacOS => "mac",
OperatingSystemType.Linux => "linux",
OperatingSystemType.Windows => "win"
};
private static UpdateClient CreateUpdateClient() var arch = RunningPlatform.Architecture switch
=> UpdateClient.Builder() {
.WithServerEndpoint("https://update.ryujinx.app") // This is the default, and doesn't need to be provided; it's here for transparency. Architecture.Arm64 => "arm",
.WithLogger((format, args, caller) => Architecture.X64 => "amd64",
Logger.Info?.Print( _ => null
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 (arch is null)
return null;
var rc = ReleaseInformation.IsCanaryBuild ? "canary" : "stable";
return $"https://update.ryujinx.app/latest/query?os={os}&arch={arch}&rc={rc}";
}
private static async Task<Optional<(Version Current, Version Incoming)>> CheckGitLabVersionAsync(bool showVersionUpToDate = false)
{ {
if (!Version.TryParse(Program.Version, out Version currentVersion)) if (!Version.TryParse(Program.Version, out Version currentVersion))
{ {
@ -41,31 +57,42 @@ namespace Ryujinx.Ava.Systems
return default; return default;
} }
using UpdateClient updateClient = CreateUpdateClient(); if (CreateUpdateQueryUrl() is not {} updateUrl)
try
{ {
_versionResponse = await updateClient.QueryLatestAsync(ReleaseInformation.IsCanaryBuild Logger.Error?.Print(LogClass.Application, "Could not determine URL for updates.");
? 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; _running = false;
return default; return default;
} }
if (_versionResponse == null) Logger.Info?.Print(LogClass.Application, $"Checking for updates from {updateUrl}.");
// Get latest version number from GitLab API
using HttpClient jsonClient = ConstructHttpClient();
// GitLab instance is located in Ukraine. Connection times will vary across the world.
jsonClient.Timeout = TimeSpan.FromSeconds(10);
try
{ {
// logging is done via the UpdateClient library UpdaterResponse response =
await jsonClient.GetFromJsonAsync(updateUrl, UpdaterResponseJsonContext.Default.UpdaterResponse);
_buildVer = response.Tag;
_buildUrl = response.DownloadUrl;
_changelogUrlFormat = response.ReleaseUrlFormat;
}
catch (Exception e)
{
Logger.Error?.Print(LogClass.Application, $"An error occurred when parsing JSON response from API ({e.GetType().AsFullNamePrettyString()}): {e.Message}");
_running = false; _running = false;
return default; return default;
} }
// If build URL not found, assume no new update is available. // If build URL not found, assume no new update is available.
if (_versionResponse.ArtifactUrl is null or "") if (_buildUrl is null or "")
{ {
if (showVersionUpToDate) if (showVersionUpToDate)
{ {
@ -75,7 +102,7 @@ namespace Ryujinx.Ava.Systems
if (userResult is UserResult.Ok) if (userResult is UserResult.Ok)
{ {
OpenHelper.OpenUrl(_versionResponse.ReleaseUrlFormat.Format(currentVersion)); OpenHelper.OpenUrl(_changelogUrlFormat.Format(currentVersion));
} }
} }
@ -87,13 +114,13 @@ namespace Ryujinx.Ava.Systems
} }
if (!Version.TryParse(_versionResponse.Version, out Version newVersion)) if (!Version.TryParse(_buildVer, out Version newVersion))
{ {
Logger.Error?.Print(LogClass.Application, Logger.Error?.Print(LogClass.Application,
$"Failed to convert the received {RyujinxApp.FullAppName} version from the update server!"); $"Failed to convert the received {RyujinxApp.FullAppName} version from GitLab!");
await ContentDialogHelper.CreateWarningDialog( await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedServerMessage], LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
_running = false; _running = false;
@ -103,5 +130,17 @@ namespace Ryujinx.Ava.Systems
return (currentVersion, newVersion); return (currentVersion, newVersion);
} }
[JsonSerializable(typeof(UpdaterResponse))]
partial class UpdaterResponseJsonContext : JsonSerializerContext;
public class UpdaterResponse
{
[JsonPropertyName("tag")] public string Tag { get; set; }
[JsonPropertyName("download_url")] public string DownloadUrl { get; set; }
[JsonPropertyName("web_url")] public string ReleaseUrl { get; set; }
[JsonIgnore] public string ReleaseUrlFormat => ReleaseUrl.Replace(Tag, "{0}");
}
} }
} }

View file

@ -5,11 +5,13 @@ 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,17 +31,43 @@ namespace Ryujinx.Ava.Systems
{ {
internal static partial class Updater internal static partial class Updater
{ {
private static readonly GithubReleasesJsonSerializerContext _ghSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
private static readonly string _platformExt = BuildPlatformExtension();
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 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 = [];
private static string _changelogUrlFormat = null;
public static async Task<Optional<(Version, Version)>> CheckVersionAsync(bool showVersionUpToDate = false)
{
Optional<(Version, Version)> versionTuple;
try
{
versionTuple = await CheckGitLabVersionAsync(showVersionUpToDate);
}
catch (Exception e)
{
Logger.Error?.PrintMsg(LogClass.Application, "Update checking from GitLab failed; falling back to GitHub.");
Logger.Error?.PrintMsg(LogClass.Application, e.Message);
versionTuple = await CheckGitHubVersionAsync(showVersionUpToDate);
}
return versionTuple;
}
public static async Task BeginUpdateAsync(bool showVersionUpToDate = false) public static async Task BeginUpdateAsync(bool showVersionUpToDate = false)
{ {
if (_running) if (_running)
@ -66,7 +94,7 @@ namespace Ryujinx.Ava.Systems
if (userResult is UserResult.Ok) if (userResult is UserResult.Ok)
{ {
OpenHelper.OpenUrl(_versionResponse.ReleaseUrlFormat.Format(currentVersion)); OpenHelper.OpenUrl(_changelogUrlFormat.Format(currentVersion));
} }
} }
@ -86,7 +114,7 @@ namespace Ryujinx.Ava.Systems
// GitLab instance is located in Ukraine. Connection times will vary across the world. // GitLab instance is located in Ukraine. Connection times will vary across the world.
buildSizeClient.Timeout = TimeSpan.FromSeconds(10); buildSizeClient.Timeout = TimeSpan.FromSeconds(10);
HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_versionResponse.ArtifactUrl), HttpCompletionOption.ResponseHeadersRead); HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_buildUrl), HttpCompletionOption.ResponseHeadersRead);
_buildSize = message.Content.Headers.ContentRange.Length.Value; _buildSize = message.Content.Headers.ContentRange.Length.Value;
} }
@ -116,7 +144,7 @@ namespace Ryujinx.Ava.Systems
switch (shouldUpdate) switch (shouldUpdate)
{ {
case UserResult.Yes: case UserResult.Yes:
await UpdateRyujinx(_versionResponse.ArtifactUrl); await UpdateRyujinx(_buildUrl);
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:

View file

@ -2,104 +2,29 @@ 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 : IHostUITheme class AvaloniaHostUITheme(MainWindow parent) : IHostUITheme
{ {
private readonly MainWindow _parent; public string FontFamily { get; } = OperatingSystem.IsWindows() && OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000) ? "Segoe UI Variable" : parent.FontFamily.Name;
public string FontFamily { get; } public ThemeColor DefaultBackgroundColor { get; } = BrushToThemeColor(parent.Background);
public ThemeColor DefaultBackgroundColor { get; } public ThemeColor DefaultForegroundColor { get; } = BrushToThemeColor(parent.Foreground);
public ThemeColor DefaultForegroundColor { get; } public ThemeColor DefaultBorderColor { get; } = BrushToThemeColor(parent.BorderBrush);
public ThemeColor DefaultBorderColor { get; } public ThemeColor SelectionBackgroundColor { get; } = BrushToThemeColor(parent.ViewControls.SearchBox.SelectionBrush);
public ThemeColor SelectionBackgroundColor { get; } public ThemeColor SelectionForegroundColor { get; } = BrushToThemeColor(parent.ViewControls.SearchBox.SelectionForegroundBrush);
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( return new ThemeColor((float)solidColor.Color.A / 255,
(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();
} }
} }

View file

@ -110,8 +110,5 @@ 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);
} }
} }

View file

@ -4,7 +4,6 @@ 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
{ {
@ -12,37 +11,15 @@ 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();
@ -50,29 +27,19 @@ 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; private bool _onlyShowOwnedGames = true;
public bool OnlyShowOwnedGames public bool OnlyShowOwnedGames
{ {
@ -87,37 +54,17 @@ 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)
@ -125,43 +72,5 @@ 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,
};
}
}
} }
} }

View file

@ -50,9 +50,6 @@ 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()
@ -87,8 +84,6 @@ 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;
@ -99,16 +94,31 @@ 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 ChosenProfile public string _profileChoose;
public string ProfileChoose
{ {
get => _chosenProfile; get => _profileChoose;
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
_chosenProfile = value; _profileChoose = value;
LoadProfile(); LoadProfile();
OnPropertyChanged(); OnPropertyChanged();
} }
@ -280,7 +290,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public InputConfig Config { get; set; } public InputConfig Config { get; set; }
public InputViewModel(UserControl owner, bool useGlobal = false) : this() public InputViewModel(UserControl owner) : this()
{ {
if (Program.PreviewerDetached) if (Program.PreviewerDetached)
{ {
@ -293,8 +303,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
_mainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates(); _mainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates();
UseGlobalConfig = useGlobal;
_isLoaded = false; _isLoaded = false;
LoadDevices(); LoadDevices();
@ -327,18 +335,9 @@ 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)
{ {
if (UseGlobalConfig && Program.UseExtraConfig) Config = inputConfig ?? ConfigurationState.Instance.Hid.InputConfig.Value.FirstOrDefault(inputConfig => inputConfig.PlayerIndex == _playerId);
{
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)
{ {
@ -903,7 +902,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
LoadProfiles(); LoadProfiles();
ChosenProfile = ProfileName; // Show new profile ProfileChoose = ProfileName; // Show new profile
} }
else else
{ {
@ -937,7 +936,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
LoadProfiles(); LoadProfiles();
ChosenProfile = ProfilesList[0].ToString(); // Show default profile ProfileChoose = ProfilesList[0].ToString(); // Show default profile
} }
} }
@ -967,14 +966,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
List<InputConfig> newConfig = []; List<InputConfig> newConfig = [];
if (UseGlobalConfig && Program.UseExtraConfig) newConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value);
{
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));
@ -1015,21 +1007,18 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
} }
} }
// Atomically replace and signal input change.
// NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event.
_mainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); _mainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
if (UseGlobalConfig && Program.UseExtraConfig) // Atomically replace and signal input change.
{ // NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event.
// In User Settings when "Use Global Input" is enabled, it saves global input to global setting ConfigurationState.Instance.Hid.InputConfig.Value = newConfig;
ConfigurationState.InstanceExtra.Hid.InputConfig.Value = newConfig;
ConfigurationState.InstanceExtra.ToFileFormat().SaveConfig(Program.GlobalConfigurationPath); ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
} }
else
{ public void NotifyChange(string property)
ConfigurationState.Instance.Hid.InputConfig.Value = newConfig; {
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); OnPropertyChanged(property);
}
} }
public void NotifyChanges() public void NotifyChanges()

View file

@ -310,15 +310,10 @@ namespace Ryujinx.Ava.UI.ViewModels
private void TotalTimePlayed_Recalculated(Optional<TimeSpan> ts) private void TotalTimePlayed_Recalculated(Optional<TimeSpan> ts)
{ {
if (ts.HasValue)
{
var formattedPlayTime = ValueFormatUtils.FormatTimeSpan(ts.Value);
LocaleManager.Instance.SetDynamicValues(LocaleKeys.GameListLabelTotalTimePlayed, formattedPlayTime);
ShowTotalTimePlayed = formattedPlayTime != string.Empty;
return;
}
ShowTotalTimePlayed = ts.HasValue; ShowTotalTimePlayed = ts.HasValue;
if (ts.HasValue)
LocaleManager.Instance.SetDynamicValues(LocaleKeys.GameListLabelTotalTimePlayed, ValueFormatUtils.FormatTimeSpan(ts.Value));
} }
public bool ShowTotalTimePlayed public bool ShowTotalTimePlayed
@ -339,6 +334,7 @@ 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!;
@ -1579,31 +1575,28 @@ 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 ?? ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString(); string BackendThreadingInit = Program.BackendThreadingArg;
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), idGame); ConfigurationState.Instance.Load(configurationFileFormat, Program.GetDirGameUserConfig(idGame, true, 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", {
ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() "--bt", ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() // BackendThreading
]); };
Rebooter.RebootAppWithGame(application.Path, Arguments);
return true; return true;
} }
@ -1692,8 +1685,8 @@ namespace Ryujinx.Ava.UI.ViewModels
RendererHostControl.Focus(); RendererHostControl.Focus();
}); });
public static void UpdateGameMetadata(string titleId, TimeSpan playTime) public static void UpdateGameMetadata(string titleId)
=> ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => appMetadata.UpdatePostGame(playTime)); => ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => appMetadata.UpdatePostGame());
public void RefreshFirmwareStatus() public void RefreshFirmwareStatus()
{ {
@ -1998,7 +1991,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)); Program.GetDirGameUserConfig(viewModel.SelectedApplication.IdString, false, false));
viewModel.RefreshView(); viewModel.RefreshView();
}); });

View file

@ -53,7 +53,6 @@ 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;
@ -65,7 +64,6 @@ 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;
@ -86,7 +84,6 @@ 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;
@ -145,22 +142,9 @@ namespace Ryujinx.Ava.UI.ViewModels
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;
@ -387,7 +371,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsInvalidLdnPassphraseVisible { get; set; } public bool IsInvalidLdnPassphraseVisible { get; set; }
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this() public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(false)
{ {
_virtualFileSystem = virtualFileSystem; _virtualFileSystem = virtualFileSystem;
_contentManager = contentManager; _contentManager = contentManager;
@ -408,7 +392,7 @@ namespace Ryujinx.Ava.UI.ViewModels
string gameName, string gameName,
string gameId, string gameId,
byte[] gameIconData, byte[] gameIconData,
bool customConfig) : this() bool enableToLoadCustomConfig) : this(enableToLoadCustomConfig)
{ {
_virtualFileSystem = virtualFileSystem; _virtualFileSystem = virtualFileSystem;
_contentManager = contentManager; _contentManager = contentManager;
@ -424,18 +408,9 @@ namespace Ryujinx.Ava.UI.ViewModels
_gameTitle = gameName; _gameTitle = gameName;
_gameId = gameId; _gameId = gameId;
if (customConfig) // During the game. If there is no user config, then load the global config window if (enableToLoadCustomConfig) // During the game. If there is no user config, then load the global config window
{ {
string gameDir = Program.GetDirGameUserConfig(gameId, true); string gameDir = Program.GetDirGameUserConfig(gameId, false, 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);
@ -451,7 +426,7 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
public SettingsViewModel() public SettingsViewModel(bool noLoadGlobalConfig = false)
{ {
GameDirectories = []; GameDirectories = [];
AutoloadDirectories = []; AutoloadDirectories = [];
@ -575,9 +550,9 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
public void LoadCurrentConfiguration(bool global = false) public void LoadCurrentConfiguration()
{ {
ConfigurationState config = global ? ConfigurationState.InstanceExtra: ConfigurationState.Instance; ConfigurationState config = ConfigurationState.Instance;
// User Interface // User Interface
EnableDiscordIntegration = config.EnableDiscordIntegration; EnableDiscordIntegration = config.EnableDiscordIntegration;
@ -603,7 +578,6 @@ 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;
@ -686,9 +660,9 @@ namespace Ryujinx.Ava.UI.ViewModels
LdnServer = config.Multiplayer.LdnServer; LdnServer = config.Multiplayer.LdnServer;
} }
public void SaveSettings(bool global = false) public void SaveSettings()
{ {
ConfigurationState config = global ? ConfigurationState.InstanceExtra: ConfigurationState.Instance; ConfigurationState config = ConfigurationState.Instance;
// User Interface // User Interface
config.EnableDiscordIntegration.Value = EnableDiscordIntegration; config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
@ -710,7 +684,6 @@ 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;
@ -823,14 +796,11 @@ 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()
@ -840,7 +810,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public void DeleteConfigGame() public void DeleteConfigGame()
{ {
string gameDir = Program.GetDirGameUserConfig(GameId); string gameDir = Program.GetDirGameUserConfig(GameId, false, false);
if (File.Exists(gameDir)) if (File.Exists(gameDir))
{ {

View file

@ -100,7 +100,7 @@
Name="ProfileBox" Name="ProfileBox"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Center" VerticalAlignment="Center"
SelectedItem="{Binding ChosenProfile, Mode=TwoWay}" SelectedItem="{Binding ProfileChoose, 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,6 +203,7 @@
</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"

View file

@ -1,7 +1,6 @@
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;
@ -15,7 +14,7 @@ namespace Ryujinx.Ava.UI.Views.Input
public InputView() public InputView()
{ {
ViewModel = new InputViewModel(this, ConfigurationState.Instance.System.UseInputGlobalConfig); ViewModel = new InputViewModel(this);
InitializeComponent(); InitializeComponent();
} }
@ -25,13 +24,6 @@ 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)

View file

@ -64,7 +64,6 @@
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}"

View file

@ -56,27 +56,7 @@
SelectedIndex="{Binding PreferredGpuIndex}" SelectedIndex="{Binding PreferredGpuIndex}"
ItemsSource="{Binding AvailableGpus}"/> ItemsSource="{Binding AvailableGpus}"/>
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal"> </StackPanel>
<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">
@ -275,7 +255,32 @@
</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

View file

@ -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,16 +22,9 @@
<Panel <Panel
Margin="10"> Margin="10">
<Grid RowDefinitions="Auto,*,Auto"> <Grid RowDefinitions="Auto,*,Auto">
<StackPanel> <views:InputView
<!-- Grid.Row="0"
Opacity="{Binding PanelOpacityInput}"> Name="InputView" />
IsEnabled="{Binding !EnableConfigGlobal}">
-->
<views:InputView
Grid.Row="0"
Name="InputView" />
</StackPanel>
<StackPanel <StackPanel
Orientation="Vertical" Orientation="Vertical"
Grid.Row="2"> Grid.Row="2">
@ -41,13 +34,6 @@
<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"

View file

@ -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,277 +17,65 @@
<window:StyleableAppWindow.DataContext> <window:StyleableAppWindow.DataContext>
<viewModels:CompatibilityViewModel /> <viewModels:CompatibilityViewModel />
</window:StyleableAppWindow.DataContext> </window:StyleableAppWindow.DataContext>
<Grid RowDefinitions="Auto,Auto,*"> <Grid RowDefinitions="Auto,*">
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto,Auto" Name="FlushControls">
<!-- UI FlushControls -->
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto,Auto,Auto" Name="FlushControls">
<controls:RyujinxLogo <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, 1, 0" Margin="-10, 0, 18, 0" Text="{ext:Locale CompatibilityListOnlyShowOwnedGames}" /> <TextBlock Grid.Column="3" Padding="0, 0, 138, 0" Margin="-10, 0, 18, 0" Text="{ext:Locale CompatibilityListOnlyShowOwnedGames}" />
</Grid> </Grid>
<Grid Grid.Row="0" ColumnDefinitions="*,Auto,Auto" Name="NormalControls">
<!-- Description Field Above ScrollViewer --> <TextBox Name="SearchBoxNormal" Grid.Column="0" Margin="15, 0, 0, 5" HorizontalAlignment="Stretch" Watermark="{ext:Locale CompatibilityListSearchBoxWatermark}" TextChanged="TextBox_OnTextChanged" />
<Grid Grid.Row="1" ColumnDefinitions="*,Auto" Margin="10, 5, 10, 5"> <CheckBox Grid.Column="1" Margin="7, 0, 0, 0" IsChecked="{Binding OnlyShowOwnedGames}" />
<Grid Grid.Column="0"> <TextBlock Grid.Column="2" Padding="0, 0, 1, 0" Margin="-10, 0, 18, 0" Text="{ext:Locale CompatibilityListOnlyShowOwnedGames}" />
<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">
<!-- List of compatible games --> <ListBox Margin="12, 0, 13, 0"
<ScrollViewer Grid.Row="2"> Background="Transparent"
<ListBox Margin="12, 0, 13, 0" ItemsSource="{Binding CurrentEntries}">
Background="Transparent" <ListBox.ItemTemplate>
ItemsSource="{Binding CurrentEntries}"> <DataTemplate DataType="{x:Type systems:CompatibilityEntry}">
<ListBox.ItemTemplate> <Grid MinWidth="800"
<DataTemplate DataType="{x:Type systems:CompatibilityEntry}"> Margin="10"
<Grid MinWidth="800" ColumnDefinitions="Auto,Auto,Auto,*"
Margin="10" Background="Transparent"
ColumnDefinitions="Auto,Auto,Auto,*" ToolTip.Tip="{Binding LocalizedLastUpdated}">
Background="Transparent" <TextBlock Grid.Column="0"
ToolTip.Tip="{Binding LocalizedLastUpdated}"> Text="{Binding GameName}"
<TextBlock Grid.Column="0" Width="525"
Text="{Binding GameName}" VerticalAlignment="Center"
Width="525" HorizontalAlignment="Center"
VerticalAlignment="Center" TextWrapping="Wrap" />
HorizontalAlignment="Center" <TextBlock Grid.Column="1"
TextWrapping="Wrap" /> Width="135"
<TextBlock Grid.Column="1" Padding="7, 0, 0, 0"
Width="135" FontFamily="{StaticResource JetBrainsMono}"
Padding="7, 0, 0, 0" Text="{Binding FormattedTitleId}"
FontFamily="{StaticResource JetBrainsMono}" VerticalAlignment="Center"
Text="{Binding FormattedTitleId}" HorizontalAlignment="Center"
VerticalAlignment="Center" TextWrapping="Wrap" />
HorizontalAlignment="Center" <TextBlock Grid.Column="2"
TextWrapping="Wrap" /> Padding="7, 0"
<TextBlock Grid.Column="2" Text="{Binding LocalizedStatus}"
Padding="7, 0" Width="90"
Text="{Binding LocalizedStatus}" Background="Transparent"
Width="100" ToolTip.Tip="{Binding LocalizedStatusDescription}"
Background="Transparent" Foreground="{Binding Status, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
ToolTip.Tip="{Binding LocalizedStatusDescription}" VerticalAlignment="Center"
Foreground="{Binding Status, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}" HorizontalAlignment="Center"
VerticalAlignment="Center" TextWrapping="NoWrap" />
HorizontalAlignment="Center" <TextBlock Grid.Column="3"
TextWrapping="NoWrap" /> Text="{Binding FormattedIssueLabels}"
<TextBlock Grid.Column="3" VerticalAlignment="Center"
Text="{Binding FormattedIssueLabels}" HorizontalAlignment="Left"
VerticalAlignment="Center" TextWrapping="WrapWithOverflow" />
HorizontalAlignment="Left" </Grid>
TextWrapping="WrapWithOverflow" /> </DataTemplate>
</Grid> </ListBox.ItemTemplate>
</DataTemplate> </ListBox>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer> </ScrollViewer>
<Grid></Grid> <Grid></Grid>
</Grid> </Grid>

View file

@ -1,6 +1,5 @@
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;
@ -43,28 +42,5 @@ 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));
}
}
} }
} }

View file

@ -120,34 +120,30 @@
IconSource="Code" /> IconSource="Code" />
</ui:NavigationView.MenuItems> </ui:NavigationView.MenuItems>
</ui:NavigationView> </ui:NavigationView>
<Grid Grid.Row="3"
ColumnDefinitions="Auto,*,Auto"> <ReversibleStackPanel
<StackPanel Grid.Column="0" Orientation="Horizontal" Margin="10" Spacing="10"> Grid.Row="2"
<Button Margin="10"
IsVisible="{Binding !IsGameRunning}" Spacing="10"
Content="{ext:Locale UserProfilesDelete}" Orientation="Horizontal"
Command="{Binding DeleteConfigGame}" HorizontalAlignment="Right"
Classes="red"/> ReverseOrder="{x:Static helper:RunningPlatform.IsMacOS}">
</StackPanel> <Button
<ReversibleStackPanel Content="{ext:Locale SettingsButtonSave}"
Grid.Column="2" Command="{Binding SaveUserConfig}" />
Margin="10" <Button
Spacing="10" HotKey="Escape"
Orientation="Horizontal" Content="{ext:Locale SettingsButtonClose}"
HorizontalAlignment="Right" Command="{Binding CancelButton}" />
ReverseOrder="{x:Static helper:RunningPlatform.IsMacOS}"> <Button
<Button IsVisible="{Binding IsGameRunning}"
Classes="accent" Content="{ext:Locale SettingsButtonApply}"
Content="{ext:Locale SettingsButtonSave}" Command="{Binding ApplyButton}" />
Command="{Binding SaveUserConfig}" /> <Button
<Button IsVisible="{Binding !IsGameRunning}"
HotKey="Escape" Content="{ext:Locale UserProfilesDelete}"
Content="{ext:Locale SettingsButtonClose}" Command="{Binding DeleteConfigGame}"
Command="{Binding CancelButton}" /> Classes="red"/>
<Button </ReversibleStackPanel>
Content="{ext:Locale SettingsButtonApply}"
Command="{Binding ApplyButton}" />
</ReversibleStackPanel>
</Grid>
</Grid> </Grid>
</window:StyleableAppWindow> </window:StyleableAppWindow>

View file

@ -28,8 +28,6 @@ namespace Ryujinx.Ava.UI.Windows
ViewModel.CloseWindow += Close; ViewModel.CloseWindow += Close;
ViewModel.SaveSettingsEvent += SaveSettings; ViewModel.SaveSettingsEvent += SaveSettings;
ViewModel.LocalGlobalInputSwitchEvent += ToggleLocalGlobalInput;
InitializeComponent(); InitializeComponent();
Load(); Load();
} }
@ -39,11 +37,6 @@ 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();
@ -97,7 +90,6 @@ 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);
} }

View file

@ -213,13 +213,13 @@ namespace Ryujinx.Ava.UI.Windows
} }
} }
public async void Application_Opened(object sender, ApplicationOpenedEventArgs args) public 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;
await ViewModel.LoadApplication(args.Application); ViewModel.LoadApplication(args.Application).Wait();
} }
args.Handled = true; args.Handled = true;