Compare commits

..

2 Commits

Author SHA1 Message Date
Evan Husted
9ef57edfd1 Undo a testing change 2024-12-24 14:38:24 -06:00
Evan Husted
1360ef2ec8 WIP AMOLED theme. Likely will not continue working on this as the only way forward is a hacky mess. 2024-12-24 14:35:49 -06:00
98 changed files with 1430 additions and 2817 deletions

View File

@@ -64,9 +64,14 @@ jobs:
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
- name: Publish Ryujinx.Headless.SDL2
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
- name: Set executable bit
run: |
chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
chmod +x ./publish_sdl2_headless/Ryujinx.Headless.SDL2 ./publish_sdl2_headless/Ryujinx.sh
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
- name: Build AppImage
@@ -114,6 +119,13 @@ jobs:
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}-AppImage
path: publish_appimage
- name: Upload Ryujinx.Headless.SDL2 artifact
uses: actions/upload-artifact@v4
with:
name: nogui-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
path: publish_sdl2_headless
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
build_macos:
name: macOS Universal (${{ matrix.configuration }})
runs-on: ubuntu-latest
@@ -159,9 +171,20 @@ jobs:
run: |
./distribution/macos/create_macos_build_ava.sh . publish_tmp publish ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
- name: Publish macOS Ryujinx.Headless.SDL2
run: |
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
- name: Upload Ryujinx artifact
uses: actions/upload-artifact@v4
with:
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
path: "publish/*.tar.gz"
if: github.event_name == 'pull_request'
- name: Upload Ryujinx.Headless.SDL2 artifact
uses: actions/upload-artifact@v4
with:
name: nogui-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
path: "publish_headless/*.tar.gz"
if: github.event_name == 'pull_request'

View File

@@ -21,9 +21,9 @@ env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
RYUJINX_BASE_VERSION: "1.2"
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "canary"
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "Ryubing"
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "GreemDev"
RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO: "Ryujinx"
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "Canary-Releases"
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "Ryujinx-Canary"
RELEASE: 1
jobs:
@@ -43,8 +43,8 @@ jobs:
with:
script: |
github.rest.git.createRef({
owner: "${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}",
repo: "${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO }}",
owner: context.repo.owner,
repo: context.repo.repo,
ref: 'refs/tags/Canary-${{ steps.version_info.outputs.build_version }}',
sha: context.sha
})
@@ -64,7 +64,7 @@ jobs:
| Windows 64-bit | [Canary Windows Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-win_x64.zip) |
| Linux 64-bit | [Canary Linux Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/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/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/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/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz) |
| macOS | [Canary macOS artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz) |
**Full Changelog**: https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO }}/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }}
omitBodyDuringUpdate: true
@@ -116,6 +116,7 @@ jobs:
- name: Publish
run: |
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained
- name: Packing Windows builds
if: matrix.platform.os == 'windows-latest'
@@ -124,6 +125,11 @@ jobs:
rm publish/libarmeilleure-jitsupport.dylib
7z a ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
popd
pushd publish_sdl2_headless
rm publish/libarmeilleure-jitsupport.dylib
7z a ../release_output/nogui-ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
popd
shell: bash
- name: Packing Linux builds
@@ -134,6 +140,12 @@ jobs:
chmod +x publish/Ryujinx.sh publish/Ryujinx
tar -czvf ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
popd
pushd publish_sdl2_headless
rm publish/libarmeilleure-jitsupport.dylib
chmod +x publish/Ryujinx.sh publish/Ryujinx.Headless.SDL2
tar -czvf ../release_output/nogui-ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
popd
shell: bash
#- name: Build AppImage (Linux)
@@ -179,21 +191,21 @@ jobs:
with:
name: ${{ steps.version_info.outputs.build_version }}
artifacts: "release_output/*.tar.gz,release_output/*.zip"
#artifacts: "release_output/*.tar.gz,release_output/*.zip,release_output/*AppImage*"
#artifacts: "release_output/*.tar.gz,release_output/*.zip/*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/${{ github.repository }}/releases/latest) instead if that sounds like something you don't want to deal with.
These builds are experimental and may sometimes not work, use [regular builds](https://github.com/GreemDev/Ryujinx/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/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-win_x64.zip) |
| Linux 64-bit | [Canary Linux Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/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/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/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/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz) |
| Windows 64-bit | https://github.com/${{ github.repository }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-win_x64.zip |
| Linux 64-bit | https://github.com/${{ github.repository }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz |
| Linux ARM 64-bit | https://github.com/${{ github.repository }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-linux_arm64.tar.gz |
| macOS | https://github.com/${{ github.repository }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz |
**Full Changelog**: https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO }}/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }}
"**Full Changelog**: https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO }}/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }}"
omitBodyDuringUpdate: true
allowUpdates: true
replacesArtifacts: true
@@ -250,11 +262,15 @@ jobs:
run: |
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 1
- name: Publish macOS Ryujinx.Headless.SDL2
run: |
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 1
- name: Pushing new release
uses: ncipollo/release-action@v1
with:
name: "Canary ${{ steps.version_info.outputs.build_version }}"
artifacts: "publish_ava/*.tar.gz"
artifacts: "publish_ava/*.tar.gz, publish_headless/*.tar.gz"
tag: ${{ steps.version_info.outputs.build_version }}
body: ""
omitBodyDuringUpdate: true

View File

@@ -38,16 +38,20 @@ jobs:
return core.error(`No artifacts found`);
}
let body = `Download the artifacts for this pull request:\n`;
let hidden_headless_artifacts = `\n\n <details><summary>GUI-less</summary>\n`;
let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`;
for (const art of artifacts) {
const url = `https://github.com/Ryubing/Ryujinx/actions/runs/${run_id}/artifacts/${art.id}`;
if(art.name.includes('Debug')) {
hidden_debug_artifacts += `\n* [${art.name}](${url})`;
hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else if(art.name.includes('nogui-ryujinx')) {
hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else {
body += `\n* [${art.name}](${url})`;
body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
}
}
hidden_headless_artifacts += `\n</details>`;
hidden_debug_artifacts += `\n</details>`;
body += hidden_headless_artifacts;
body += hidden_debug_artifacts;
const {data: comments} = await github.rest.issues.listComments({repo, owner, issue_number});

View File

@@ -21,7 +21,7 @@ env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
RYUJINX_BASE_VERSION: "1.2"
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "release"
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "Ryubing"
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "GreemDev"
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "Ryujinx"
RELEASE: 1
@@ -42,8 +42,8 @@ jobs:
with:
script: |
github.rest.git.createRef({
owner: "${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}",
repo: "${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}",
owner: context.repo.owner,
repo: context.repo.repo,
ref: 'refs/tags/${{ steps.version_info.outputs.build_version }}',
sha: context.sha
})
@@ -54,13 +54,13 @@ jobs:
name: ${{ steps.version_info.outputs.build_version }}
tag: ${{ steps.version_info.outputs.build_version }}
body: |
# Stable builds:
# Regular builds:
| Platform | Artifact |
|--|--|
| Windows 64-bit | [Stable Windows Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip) |
| Linux 64-bit | [Stable Linux Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/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/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/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/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz) |
| Windows 64-bit | [Release Windows Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip) |
| Linux 64-bit | [Release Linux Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz) |
| Linux ARM 64-bit | [Release Linux ARM Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_arm64.tar.gz) |
| macOS | [Release macOS Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz) |
**Full Changelog**: https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}
omitBodyDuringUpdate: true
@@ -112,6 +112,7 @@ jobs:
- name: Publish
run: |
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained
- name: Packing Windows builds
if: matrix.platform.os == 'windows-latest'
@@ -120,6 +121,11 @@ jobs:
rm libarmeilleure-jitsupport.dylib
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
popd
pushd publish_sdl2_headless
rm libarmeilleure-jitsupport.dylib
7z a ../release_output/nogui-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
popd
shell: bash
- name: Build AppImage (Linux)
@@ -166,6 +172,11 @@ jobs:
chmod +x Ryujinx.sh Ryujinx
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
popd
pushd publish_sdl2_headless
chmod +x Ryujinx.sh Ryujinx.Headless.SDL2
tar -czvf ../release_output/nogui-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
popd
shell: bash
- name: Pushing new release
@@ -175,15 +186,15 @@ jobs:
artifacts: "release_output/*.tar.gz,release_output/*.zip,release_output/*AppImage*"
tag: ${{ steps.version_info.outputs.build_version }}
body: |
# Stable builds:
# Regular builds:
| Platform | Artifact |
|--|--|
| Windows 64-bit | [Stable Windows Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip) |
| Linux 64-bit | [Stable Linux Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/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/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/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/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz) |
| Windows 64-bit | https://github.com/${{ github.repository }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip |
| Linux 64-bit | https://github.com/${{ github.repository }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz |
| Linux ARM 64-bit | https://github.com/${{ github.repository }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_arm64.tar.gz |
| macOS | https://github.com/${{ github.repository }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz |
**Full Changelog**: https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}
"**Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}"
omitBodyDuringUpdate: true
allowUpdates: true
replacesArtifacts: true
@@ -240,11 +251,15 @@ jobs:
run: |
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0
- name: Publish macOS Ryujinx.Headless.SDL2
run: |
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0
- name: Pushing new release
uses: ncipollo/release-action@v1
with:
name: ${{ steps.version_info.outputs.build_version }}
artifacts: "publish/*.tar.gz"
artifacts: "publish/*.tar.gz, publish_headless/*.tar.gz"
tag: ${{ steps.version_info.outputs.build_version }}
body: ""
omitBodyDuringUpdate: true

View File

@@ -44,7 +44,7 @@
<PackageVersion Include="Gommon" Version="2.6.8" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="shaderc.net" Version="0.1.0" />
<PackageVersion Include="SharpMetal" Version="1.0.0-preview21" />
<PackageVersion Include="SharpMetal" Version="1.0.0-preview20" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.21.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.21.0" />

View File

@@ -57,6 +57,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.SDL2.Common", "src\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.SDL2", "src\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj", "{D99A395A-8569-4DB0-B336-900647890052}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Headless.SDL2", "src\Ryujinx.Headless.SDL2\Ryujinx.Headless.SDL2.csproj", "{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.FFmpeg", "src\Ryujinx.Graphics.Nvdec.FFmpeg\Ryujinx.Graphics.Nvdec.FFmpeg.csproj", "{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "src\Ryujinx\Ryujinx.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}"
@@ -78,16 +80,11 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Generators", "src\Ryujinx.Horizon.Kernel.Generators\Ryujinx.Horizon.Kernel.Generators.csproj", "{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.Metal", "src\Ryujinx.Graphics.Metal\Ryujinx.Graphics.Metal.csproj", "{C08931FA-1191-417A-864F-3882D93E683B}"
ProjectSection(ProjectDependencies) = postProject
{A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E} = {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.BuildValidationTasks", "src\Ryujinx.BuildValidationTasks\Ryujinx.BuildValidationTasks.csproj", "{4A89A234-4F19-497D-A576-DDE8CDFC5B22}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.Metal.SharpMetalExtensions", "src/Ryujinx.Graphics.Metal.SharpMetalExtensions\Ryujinx.Graphics.Metal.SharpMetalExtensions.csproj", "{81EA598C-DBA1-40B0-8DA4-4796B78F2037}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
@@ -97,6 +94,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.github\workflows\release.yml = .github\workflows\release.yml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.BuildValidationTasks", "src\Ryujinx.BuildValidationTasks\Ryujinx.BuildValidationTasks.csproj", "{4A89A234-4F19-497D-A576-DDE8CDFC5B22}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -211,6 +210,10 @@ Global
{D99A395A-8569-4DB0-B336-900647890052}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D99A395A-8569-4DB0-B336-900647890052}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D99A395A-8569-4DB0-B336-900647890052}.Release|Any CPU.Build.0 = Release|Any CPU
{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Debug|Any CPU.Build.0 = Debug|Any CPU
{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Release|Any CPU.ActiveCfg = Release|Any CPU
{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Release|Any CPU.Build.0 = Release|Any CPU
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -262,10 +265,6 @@ Global
{C08931FA-1191-417A-864F-3882D93E683B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C08931FA-1191-417A-864F-3882D93E683B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C08931FA-1191-417A-864F-3882D93E683B}.Release|Any CPU.Build.0 = Release|Any CPU
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -1,5 +1,4 @@
using ARMeilleure.State;
using Humanizer;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
@@ -59,8 +58,8 @@ namespace ARMeilleure.Translation.PTC
{
_ptc = ptc;
_timer = new Timer(SaveInterval.Seconds());
_timer.Elapsed += TimerElapsed;
_timer = new Timer(SaveInterval * 1000d);
_timer.Elapsed += PreSave;
_outerHeaderMagic = BinaryPrimitives.ReadUInt64LittleEndian(EncodingCache.UTF8NoBOM.GetBytes(OuterHeaderMagicString).AsSpan());
@@ -73,9 +72,6 @@ namespace ARMeilleure.Translation.PTC
Enabled = false;
}
private void TimerElapsed(object _, ElapsedEventArgs __)
=> new Thread(PreSave) { Name = "Ptc.DiskWriter" }.Start();
public void AddEntry(ulong address, ExecutionMode mode, bool highCq)
{
if (IsAddressInStaticCodeRange(address))
@@ -266,7 +262,7 @@ namespace ARMeilleure.Translation.PTC
compressedStream.SetLength(0L);
}
private void PreSave()
private void PreSave(object source, ElapsedEventArgs e)
{
_waitEvent.Reset();
@@ -432,7 +428,7 @@ namespace ARMeilleure.Translation.PTC
{
_disposed = true;
_timer.Elapsed -= TimerElapsed;
_timer.Elapsed -= PreSave;
_timer.Dispose();
Wait();

View File

@@ -36,8 +36,6 @@ namespace Ryujinx.Common.Configuration
};
}
public static float ToFloatY(this AspectRatio aspectRatio)
{
return aspectRatio switch

View File

@@ -1,11 +0,0 @@
using System;
namespace Ryujinx.Common.Configuration
{
[Flags]
public enum DirtyHacks
{
None = 0,
Xc2MenuSoftlockFix = 1 << 10
}
}

View File

@@ -9,6 +9,5 @@ namespace Ryujinx.Common.Configuration
Bilinear,
Nearest,
Fsr,
Area,
}
}

View File

@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
@@ -159,15 +158,20 @@ namespace Ryujinx.Common.Logging
}
private static ILogTarget GetTarget(string targetName)
=> _logTargets.FirstOrDefault(target => target.Name.Equals(targetName));
{
foreach (var target in _logTargets)
{
if (target.Name.Equals(targetName))
{
return target;
}
}
return null;
}
public static void AddTarget(ILogTarget target)
{
if (_logTargets.Any(t => t.Name == target.Name))
{
return;
}
_logTargets.Add(target);
Updated += target.Log;

View File

@@ -27,7 +27,11 @@ namespace Ryujinx.Common.Logging.Targets
private readonly int _overflowTimeout;
string ILogTarget.Name => _target.Name;
string ILogTarget.Name { get => _target.Name; }
public AsyncLogTargetWrapper(ILogTarget target)
: this(target, -1)
{ }
public AsyncLogTargetWrapper(ILogTarget target, int queueLimit = -1, AsyncLogTargetOverflowAction overflowAction = AsyncLogTargetOverflowAction.Block)
{

View File

@@ -1,206 +0,0 @@
using Gommon;
using Ryujinx.Common.Configuration;
using System;
using System.Linq;
using System.Runtime.InteropServices;
namespace Ryujinx.Common
{
public static class TitleIDs
{
private static string _currentApplication;
public static Optional<string> CurrentApplication
{
get => _currentApplication;
set
{
_currentApplication = value.OrElse(null);
CurrentApplicationChanged?.Invoke(_currentApplication);
}
}
public static event Action<Optional<string>> CurrentApplicationChanged;
public static GraphicsBackend SelectGraphicsBackend(string titleId, GraphicsBackend currentBackend)
{
switch (currentBackend)
{
case GraphicsBackend.OpenGl when OperatingSystem.IsMacOS():
return GraphicsBackend.Vulkan;
case GraphicsBackend.Vulkan or GraphicsBackend.OpenGl or GraphicsBackend.Metal:
return currentBackend;
}
if (!(OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture is Architecture.Arm64))
return GraphicsBackend.Vulkan;
return GreatMetalTitles.ContainsIgnoreCase(titleId) ? GraphicsBackend.Metal : GraphicsBackend.Vulkan;
}
public static readonly string[] GreatMetalTitles =
[
"01006f8002326000", // Animal Crossings: New Horizons
"01009bf0072d4000", // Captain Toad: Treasure Tracker
"0100a5c00d162000", // Cuphead
"010023800d64a000", // Deltarune
"010028600EBDA000", // Mario 3D World
"0100152000022000", // Mario Kart 8 Deluxe
"01005CA01580E000", // Persona 5
"01001f5010dfa000", // Pokemon Legends Arceus
"01008C0016544000", // Sea of Stars
"01006A800016E000", // Smash Ultimate
"0100000000010000", // Super Mario Odyessy
];
public static string GetDiscordGameAsset(string titleId)
=> DiscordGameAssetKeys.Contains(titleId) ? titleId : "game";
public static readonly string[] DiscordGameAssetKeys =
[
"010055d009f78000", // Fire Emblem: Three Houses
"0100a12011cc8000", // Fire Emblem: Shadow Dragon
"0100a6301214e000", // Fire Emblem Engage
"0100f15003e64000", // Fire Emblem Warriors
"010071f0143ea000", // Fire Emblem Warriors: Three Hopes
"01007e3006dda000", // Kirby Star Allies
"01004d300c5ae000", // Kirby and the Forgotten Land
"01006b601380e000", // Kirby's Return to Dream Land Deluxe
"01003fb00c5a8000", // Super Kirby Clash
"0100227010460000", // Kirby Fighters 2
"0100a8e016236000", // Kirby's Dream Buffet
"01007ef00011e000", // The Legend of Zelda: Breath of the Wild
"01006bb00c6f0000", // The Legend of Zelda: Link's Awakening
"01002da013484000", // The Legend of Zelda: Skyward Sword HD
"0100f2c0115b6000", // The Legend of Zelda: Tears of the Kingdom
"01008cf01baac000", // The Legend of Zelda: Echoes of Wisdom
"01000b900d8b0000", // Cadence of Hyrule
"0100ae00096ea000", // Hyrule Warriors: Definitive Edition
"01002b00111a2000", // Hyrule Warriors: Age of Calamity
"010048701995e000", // Luigi's Mansion 2 HD
"0100dca0064a6000", // Luigi's Mansion 3
"010093801237c000", // Metroid Dread
"010012101468c000", // Metroid Prime Remastered
"0100000000010000", // SUPER MARIO ODYSSEY
"0100ea80032ea000", // Super Mario Bros. U Deluxe
"01009b90006dc000", // Super Mario Maker 2
"010049900f546000", // Super Mario 3D All-Stars
"010049900F546001", // ^ 64
"010049900F546002", // ^ Sunshine
"010049900F546003", // ^ Galaxy
"010028600ebda000", // Super Mario 3D World + Bowser's Fury
"010015100b514000", // Super Mario Bros. Wonder
"0100152000022000", // Mario Kart 8 Deluxe
"010036b0034e4000", // Super Mario Party
"01006fe013472000", // Mario Party Superstars
"0100965017338000", // Super Mario Party Jamboree
"01006d0017f7a000", // Mario & Luigi: Brothership
"010067300059a000", // Mario + Rabbids: Kingdom Battle
"0100317013770000", // Mario + Rabbids: Sparks of Hope
"0100a3900c3e2000", // Paper Mario: The Origami King
"0100ecd018ebe000", // Paper Mario: The Thousand-Year Door
"0100bc0018138000", // Super Mario RPG
"0100bde00862a000", // Mario Tennis Aces
"0100c9c00e25c000", // Mario Golf: Super Rush
"010019401051c000", // Mario Strikers: Battle League
"010003000e146000", // Mario & Sonic at the Olympic Games Tokyo 2020
"0100b99019412000", // Mario vs. Donkey Kong
"0100aa80194b0000", // Pikmin 1
"0100d680194b2000", // Pikmin 2
"0100f4c009322000", // Pikmin 3 Deluxe
"0100b7c00933a000", // Pikmin 4
"010003f003a34000", // Pokémon: Let's Go Pikachu!
"0100187003a36000", // Pokémon: Let's Go Eevee!
"0100abf008968000", // Pokémon Sword
"01008db008c2c000", // Pokémon Shield
"0100000011d90000", // Pokémon Brilliant Diamond
"010018e011d92000", // Pokémon Shining Pearl
"01001f5010dfa000", // Pokémon Legends: Arceus
"0100a3d008c5c000", // Pokémon Scarlet
"01008f6008c5e000", // Pokémon Violet
"0100b3f000be2000", // Pokkén Tournament DX
"0100f4300bf2c000", // New Pokémon Snap
"01003bc0000a0000", // Splatoon 2 (US)
"0100f8f0000a2000", // Splatoon 2 (EU)
"01003c700009c000", // Splatoon 2 (JP)
"0100c2500fc20000", // Splatoon 3
"0100ba0018500000", // Splatoon 3: Splatfest World Premiere
"010040600c5ce000", // Tetris 99
"0100277011f1a000", // Super Mario Bros. 35
"0100ad9012510000", // PAC-MAN 99
"0100ccf019c8c000", // F-ZERO 99
"0100d870045b6000", // NES - Nintendo Switch Online
"01008d300c50c000", // SNES - Nintendo Switch Online
"0100c9a00ece6000", // N64 - Nintendo Switch Online
"0100e0601c632000", // N64 - Nintendo Switch Online 18+
"0100c62011050000", // GB - Nintendo Switch Online
"010012f017576000", // GBA - Nintendo Switch Online
"01000320000cc000", // 1-2 Switch
"0100300012f2a000", // Advance Wars 1+2: Re-Boot Camp
"01006f8002326000", // Animal Crossing: New Horizons
"0100620012d6e000", // Big Brain Academy: Brain vs. Brain
"010018300d006000", // BOXBOY! + BOXGIRL!
"0100c1f0051b6000", // Donkey Kong Country: Tropical Freeze
"0100ed000d390000", // Dr. Kawashima's Brain Training
"010067b017588000", // Endless Ocean Luminous
"0100d2f00d5c0000", // Nintendo Switch Sports
"01006b5012b32000", // Part Time UFO
"0100704000B3A000", // Snipperclips
"01006a800016e000", // Super Smash Bros. Ultimate
"0100a9400c9c2000", // Tokyo Mirage Sessions #FE Encore
"010076f0049a2000", // Bayonetta
"01007960049a0000", // Bayonetta 2
"01004a4010fea000", // Bayonetta 3
"0100cf5010fec000", // Bayonetta Origins: Cereza and the Lost Demon
"0100dcd01525a000", // Persona 3 Portable
"010062b01525c000", // Persona 4 Golden
"010075a016a3a000", // Persona 4 Arena Ultimax
"01005ca01580e000", // Persona 5 Royal
"0100801011c3e000", // Persona 5 Strikers
"010087701b092000", // Persona 5 Tactica
"01009aa000faa000", // Sonic Mania
"01004ad014bf0000", // Sonic Frontiers
"01005ea01c0fc000", // SONIC X SHADOW GENERATIONS
"01005ea01c0fc001", // ^
"010056e00853a000", // A Hat in Time
"0100dbf01000a000", // Burnout Paradise Remastered
"0100744001588000", // Cars 3: Driven to Win
"0100b41013c82000", // Cruis'n Blast
"01001b300b9be000", // Diablo III: Eternal Collection
"01008c8012920000", // Dying Light Platinum Edition
"010073c01af34000", // LEGO Horizon Adventures
"0100770008dd8000", // Monster Hunter Generations Ultimate
"0100b04011742000", // Monster Hunter Rise
"0100853015e86000", // No Man's Sky
"01007bb017812000", // Portal
"0100abd01785c000", // Portal 2
"01008e200c5c2000", // Muse Dash
"01007820196a6000", // Red Dead Redemption
"01002f7013224000", // Rune Factory 5
"01008d100d43e000", // Saints Row IV
"0100de600beee000", // Saints Row: The Third - The Full Package
"01001180021fa000", // Shovel Knight: Specter of Torment
"0100d7a01b7a2000", // Star Wars: Bounty Hunter
"0100800015926000", // Suika Game
"0100e46006708000", // Terraria
"01000a10041ea000", // The Elder Scrolls V: Skyrim
"010057a01e4d4000", // TSUKIHIME -A piece of blue glass moon-
"010080b00ad66000", // Undertale
];
}
}

View File

@@ -0,0 +1,12 @@
namespace Ryujinx.Graphics.GAL
{
public enum AntiAliasing
{
None,
Fxaa,
SmaaLow,
SmaaMedium,
SmaaHigh,
SmaaUltra,
}
}

View File

@@ -1,4 +1,3 @@
using Ryujinx.Common.Configuration;
using System;
namespace Ryujinx.Graphics.GAL

View File

@@ -1,4 +1,3 @@
using Ryujinx.Common.Configuration;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Window;
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;

View File

@@ -0,0 +1,10 @@
namespace Ryujinx.Graphics.GAL
{
public enum ScalingFilter
{
Bilinear,
Nearest,
Fsr,
Area,
}
}

View File

@@ -0,0 +1,9 @@
namespace Ryujinx.Graphics.GAL
{
public enum VSyncMode
{
Switch,
Unbounded,
Custom
}
}

View File

@@ -1,4 +1,3 @@
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;

View File

@@ -1,22 +0,0 @@
using SharpMetal;
using SharpMetal.Foundation;
using SharpMetal.ObjectiveCCore;
using SharpMetal.QuartzCore;
using System.Runtime.Versioning;
// ReSharper disable InconsistentNaming
namespace Ryujinx.Graphics.Metal.SharpMetalExtensions
{
[SupportedOSPlatform("macOS")]
public static class CAMetalLayerExtensions
{
private static readonly Selector sel_developerHUDProperties = "developerHUDProperties";
private static readonly Selector sel_setDeveloperHUDProperties = "setDeveloperHUDProperties:";
public static NSDictionary GetDeveloperHudProperties(this CAMetalLayer metalLayer)
=> new(ObjectiveCRuntime.IntPtr_objc_msgSend(metalLayer.NativePtr, sel_developerHUDProperties));
public static void SetDeveloperHudProperties(this CAMetalLayer metalLayer, NSDictionary dictionary)
=> ObjectiveCRuntime.objc_msgSend(metalLayer.NativePtr, sel_setDeveloperHUDProperties, dictionary);
}
}

View File

@@ -1,32 +0,0 @@
using SharpMetal.Foundation;
using SharpMetal.ObjectiveCCore;
using System.Runtime.Versioning;
// ReSharper disable InconsistentNaming
namespace Ryujinx.Graphics.Metal.SharpMetalExtensions
{
[SupportedOSPlatform("macOS")]
public static class NSHelper
{
private static readonly Selector sel_getCStringMaxLengthEncoding = "getCString:maxLength:encoding:";
private static readonly Selector sel_stringWithUTF8String = "stringWithUTF8String:";
public static unsafe string ToDotNetString(this NSString source)
{
char[] sourceBuffer = new char[source.Length];
fixed (char* pSourceBuffer = sourceBuffer)
{
ObjectiveC.bool_objc_msgSend(source,
sel_getCStringMaxLengthEncoding,
pSourceBuffer,
source.MaximumLengthOfBytes(NSStringEncoding.UTF16) + 1,
(ulong)NSStringEncoding.UTF16);
}
return new string(sourceBuffer);
}
public static NSString ToNSString(this string source)
=> new(ObjectiveC.IntPtr_objc_msgSend(new ObjectiveCClass(nameof(NSString)), sel_stringWithUTF8String, source));
}
}

View File

@@ -1,11 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SharpMetal" />
</ItemGroup>
</Project>

View File

@@ -7,7 +7,10 @@
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Metal.SharpMetalExtensions\Ryujinx.Graphics.Metal.SharpMetalExtensions.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SharpMetal" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,4 +1,3 @@
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Metal.Effects;
@@ -15,7 +14,7 @@ namespace Ryujinx.Graphics.Metal
public bool ScreenCaptureRequested { get; set; }
private readonly MetalRenderer _renderer;
private CAMetalLayer _metalLayer;
private readonly CAMetalLayer _metalLayer;
private int _width;
private int _height;
@@ -23,14 +22,12 @@ namespace Ryujinx.Graphics.Metal
private int _requestedWidth;
private int _requestedHeight;
// private bool _vsyncEnabled;
private AntiAliasing _currentAntiAliasing;
private bool _updateEffect;
private IPostProcessingEffect _effect;
private IScalingFilter _scalingFilter;
private bool _isLinear;
public bool IsVSyncEnabled => _metalLayer.DisplaySyncEnabled;
// private float _scalingFilterLevel;
private bool _updateScalingFilter;
private ScalingFilter _currentScalingFilter;
@@ -42,7 +39,7 @@ namespace Ryujinx.Graphics.Metal
_metalLayer = metalLayer;
}
private void ResizeIfNeeded()
private unsafe void ResizeIfNeeded()
{
if (_requestedWidth != 0 && _requestedHeight != 0)
{
@@ -56,7 +53,7 @@ namespace Ryujinx.Graphics.Metal
}
}
public void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback)
public unsafe void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback)
{
if (_renderer.Pipeline is Pipeline pipeline && texture is Texture tex)
{
@@ -143,7 +140,7 @@ namespace Ryujinx.Graphics.Metal
public void ChangeVSyncMode(VSyncMode vSyncMode)
{
_metalLayer.DisplaySyncEnabled = vSyncMode is VSyncMode.Switch;
//_vSyncMode = vSyncMode;
}
public void SetAntiAliasing(AntiAliasing effect)

View File

@@ -2,8 +2,8 @@ namespace Ryujinx.Graphics.Nvdec.Vp9
{
internal enum BitDepth
{
Bits8 = 8, // < 8 bits
Bits10 = 10, // < 10 bits
Bits12 = 12, // < 12 bits
Bits8 = 8, /**< 8 bits */
Bits10 = 10, /**< 10 bits */
Bits12 = 12, /**< 12 bits */
}
}

View File

@@ -42,7 +42,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries
_current = new CounterQueueEvent(this, glType, 0);
_consumerThread = new Thread(EventConsumer) { Name = "CPU.CounterQueue." + (int)type };
_consumerThread = new Thread(EventConsumer);
_consumerThread.Start();
}

View File

@@ -1,5 +1,4 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Configuration;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.OpenGL.Effects;
using Ryujinx.Graphics.OpenGL.Effects.Smaa;

View File

@@ -52,7 +52,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
_current = new CounterQueueEvent(this, type, 0);
_consumerThread = new Thread(EventConsumer) { Name = "CPU.CounterQueue." + (int)type };
_consumerThread = new Thread(EventConsumer);
_consumerThread.Start();
}

View File

@@ -1,4 +1,3 @@
using Gommon;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
@@ -891,12 +890,7 @@ namespace Ryujinx.Graphics.Vulkan
private void PrintGpuInformation()
{
string gpuInfoMessage = $"{GpuRenderer} ({GpuVersion})";
if (!GpuRenderer.StartsWithIgnoreCase(GpuVendor))
gpuInfoMessage = gpuInfoMessage.Prepend(GpuVendor);
Logger.Notice.Print(LogClass.Gpu, gpuInfoMessage);
Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})");
Logger.Notice.Print(LogClass.Gpu, $"GPU Memory: {GetTotalGPUMemory() / (1024 * 1024)} MiB");
}

View File

@@ -1,4 +1,3 @@
using Ryujinx.Common.Configuration;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Vulkan.Effects;
using Silk.NET.Vulkan;

View File

@@ -1,4 +1,3 @@
using Ryujinx.Common.Configuration;
using Ryujinx.Graphics.GAL;
using System;

View File

@@ -1034,16 +1034,16 @@ namespace Ryujinx.HLE.FileSystem
switch (fileName)
{
case "prod.keys":
verified = VerifyKeys(lines, genericPattern);
verified = verifyKeys(lines, genericPattern);
break;
case "title.keys":
verified = VerifyKeys(lines, titlePattern);
verified = verifyKeys(lines, titlePattern);
break;
case "console.keys":
verified = VerifyKeys(lines, genericPattern);
verified = verifyKeys(lines, genericPattern);
break;
case "dev.keys":
verified = VerifyKeys(lines, genericPattern);
verified = verifyKeys(lines, genericPattern);
break;
default:
throw new FormatException($"Keys file name \"{fileName}\" not supported. Only \"prod.keys\", \"title.keys\", \"console.keys\", \"dev.keys\" are supported.");
@@ -1056,10 +1056,9 @@ namespace Ryujinx.HLE.FileSystem
{
throw new FileNotFoundException($"Keys file not found at \"{filePath}\".");
}
}
return;
bool VerifyKeys(string[] lines, string regex)
private bool verifyKeys(string[] lines, string regex)
{
foreach (string line in lines)
{
@@ -1070,7 +1069,6 @@ namespace Ryujinx.HLE.FileSystem
}
return true;
}
}
public bool AreKeysAlredyPresent(string pathToCheck)
{

View File

@@ -9,6 +9,7 @@ using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.HLE.UI;
using System;
using VSyncMode = Ryujinx.Common.Configuration.VSyncMode;
namespace Ryujinx.HLE
{
@@ -189,11 +190,6 @@ namespace Ryujinx.HLE
/// </summary>
public Action RefreshInputConfig { internal get; set; }
/// <summary>
/// The desired hacky workarounds.
/// </summary>
public DirtyHacks Hacks { internal get; set; }
public HLEConfiguration(VirtualFileSystem virtualFileSystem,
LibHacHorizonManager libHacHorizonManager,
ContentManager contentManager,
@@ -223,8 +219,7 @@ namespace Ryujinx.HLE
bool multiplayerDisableP2p,
string multiplayerLdnPassphrase,
string multiplayerLdnServer,
int customVSyncInterval,
DirtyHacks dirtyHacks = DirtyHacks.None)
int customVSyncInterval)
{
VirtualFileSystem = virtualFileSystem;
LibHacHorizonManager = libHacHorizonManager;
@@ -256,7 +251,6 @@ namespace Ryujinx.HLE
MultiplayerDisableP2p = multiplayerDisableP2p;
MultiplayerLdnPassphrase = multiplayerLdnPassphrase;
MultiplayerLdnServer = multiplayerLdnServer;
Hacks = dirtyHacks;
}
}
}

View File

@@ -181,7 +181,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
is64Bits = true;
}
HostThread = new Thread(ThreadStart) { Name = "HLE.KThread" };
HostThread = new Thread(ThreadStart);
Context = owner?.CreateExecutionContext() ?? new ProcessExecutionContext();

View File

@@ -1,4 +1,3 @@
using Gommon;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
@@ -7,13 +6,12 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Ryujinx.HLE.HOS.Services.Account.Acc
{
public class AccountSaveDataManager
class AccountSaveDataManager
{
private static readonly string _profilesJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "Profiles.json");
private readonly string _profilesJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "Profiles.json");
private static readonly ProfilesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
@@ -51,16 +49,6 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
}
}
public static Optional<UserProfile> GetLastUsedUser()
{
ProfilesJson profilesJson = JsonHelper.DeserializeFromFile(_profilesJsonPath, _serializerContext.ProfilesJson);
return profilesJson.Profiles
.FindFirst(profile => profile.AccountState == AccountState.Open)
.Convert(profileJson => new UserProfile(new UserId(profileJson.UserId), profileJson.Name,
profileJson.Image, profileJson.LastModifiedTimestamp));
}
public void Save(ConcurrentDictionary<string, UserProfile> profiles)
{
ProfilesJson profilesJson = new()

View File

@@ -1,9 +1,6 @@
using LibHac;
using LibHac.Common;
using LibHac.Sf;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using System.Threading;
namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
{
@@ -16,8 +13,6 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
_baseStorage = SharedRef<LibHac.FsSrv.Sf.IStorage>.CreateMove(ref baseStorage);
}
private const string Xc2TitleId = "0100e95004038000";
[CommandCmif(0)]
// Read(u64 offset, u64 length) -> buffer<u8, 0x46, 0> buffer
public ResultCode Read(ServiceCtx context)
@@ -39,13 +34,6 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
using var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true);
Result result = _baseStorage.Get.Read((long)offset, new OutBuffer(region.Memory.Span), (long)size);
if (context.Device.DirtyHacks.HasFlag(DirtyHacks.Xc2MenuSoftlockFix) && TitleIDs.CurrentApplication == Xc2TitleId)
{
// Add a load-bearing sleep to avoid XC2 softlock
// https://web.archive.org/web/20240728045136/https://github.com/Ryujinx/Ryujinx/issues/2357
Thread.Sleep(2);
}
return (ResultCode)result.Value;
}

View File

@@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using VSyncMode = Ryujinx.Common.Configuration.VSyncMode;
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
{

View File

@@ -4,10 +4,8 @@ using LibHac.Fs.Fsa;
using LibHac.Loader;
using LibHac.Ns;
using LibHac.Tools.FsSystem;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Gpu;
using Ryujinx.HLE.Loaders.Executables;
using Ryujinx.Memory;
using System;
@@ -104,7 +102,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
}
// Initialize GPU.
GraphicsConfig.TitleId = programId.ToString("X16");
Graphics.Gpu.GraphicsConfig.TitleId = $"{programId:x16}";
device.Gpu.HostInitalized.Set();
if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible))

View File

@@ -6,9 +6,7 @@ using LibHac.Ns;
using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Gpu;
using Ryujinx.HLE.Loaders.Executables;
using Ryujinx.HLE.Loaders.Processes.Extensions;
using System;
@@ -61,8 +59,6 @@ namespace Ryujinx.HLE.Loaders.Processes
{
_latestPid = processResult.ProcessId;
TitleIDs.CurrentApplication = processResult.ProgramIdText;
return true;
}
}
@@ -90,8 +86,6 @@ namespace Ryujinx.HLE.Loaders.Processes
{
_latestPid = processResult.ProcessId;
TitleIDs.CurrentApplication = processResult.ProgramIdText;
return true;
}
}
@@ -119,8 +113,6 @@ namespace Ryujinx.HLE.Loaders.Processes
if (processResult.ProgramId > 0x01000000000007FF)
{
_latestPid = processResult.ProcessId;
TitleIDs.CurrentApplication = processResult.ProgramIdText;
}
return true;
@@ -140,8 +132,6 @@ namespace Ryujinx.HLE.Loaders.Processes
{
_latestPid = processResult.ProcessId;
TitleIDs.CurrentApplication = processResult.ProgramIdText;
return true;
}
}
@@ -193,17 +183,14 @@ namespace Ryujinx.HLE.Loaders.Processes
if (nacpData.Value.PresenceGroupId != 0)
{
programId = nacpData.Value.PresenceGroupId;
TitleIDs.CurrentApplication = programId.ToString("X16");
}
else if (nacpData.Value.SaveDataOwnerId != 0)
{
programId = nacpData.Value.SaveDataOwnerId;
TitleIDs.CurrentApplication = programId.ToString("X16");
}
else if (nacpData.Value.AddOnContentBaseId != 0)
{
programId = nacpData.Value.AddOnContentBaseId - 0x1000;
TitleIDs.CurrentApplication = programId.ToString("X16");
}
}
@@ -217,7 +204,7 @@ namespace Ryujinx.HLE.Loaders.Processes
}
// Explicitly null TitleId to disable the shader cache.
GraphicsConfig.TitleId = null;
Graphics.Gpu.GraphicsConfig.TitleId = null;
_device.Gpu.HostInitalized.Set();
ProcessResult processResult = ProcessLoaderHelper.LoadNsos(_device,

View File

@@ -2,7 +2,6 @@ using LibHac.Common;
using LibHac.Ns;
using Ryujinx.Audio.Backends.CompatLayer;
using Ryujinx.Audio.Integration;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Graphics.Gpu;
using Ryujinx.HLE.FileSystem;
@@ -18,8 +17,6 @@ namespace Ryujinx.HLE
{
public class Switch : IDisposable
{
public static Switch Shared { get; private set; }
public HLEConfiguration Configuration { get; }
public IHardwareDeviceDriver AudioDeviceDriver { get; }
public MemoryBlock Memory { get; }
@@ -40,8 +37,6 @@ namespace Ryujinx.HLE
public bool IsFrameAvailable => Gpu.Window.IsFrameAvailable;
public DirtyHacks DirtyHacks { get; }
public Switch(HLEConfiguration configuration)
{
ArgumentNullException.ThrowIfNull(configuration.GpuRenderer);
@@ -77,11 +72,8 @@ namespace Ryujinx.HLE
System.EnablePtc = Configuration.EnablePtc;
System.FsIntegrityCheckLevel = Configuration.FsIntegrityCheckLevel;
System.GlobalAccessLogMode = Configuration.FsGlobalAccessLogMode;
DirtyHacks = Configuration.Hacks;
UpdateVSyncInterval();
#pragma warning restore IDE0055
Shared = this;
}
public void ProcessFrame()
@@ -150,9 +142,6 @@ namespace Ryujinx.HLE
AudioDeviceDriver.Dispose();
FileSystem.Dispose();
Memory.Dispose();
TitleIDs.CurrentApplication = null;
Shared = null;
}
}
}

View File

@@ -2,7 +2,7 @@ using Ryujinx.HLE.UI;
using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.Headless
namespace Ryujinx.Headless.SDL2
{
/// <summary>
/// Headless text processing class, right now there is no way to forward the input to it.

View File

@@ -1,6 +1,6 @@
using Ryujinx.HLE.UI;
namespace Ryujinx.Headless
namespace Ryujinx.Headless.SDL2
{
internal class HeadlessHostUiTheme : IHostUITheme
{

View File

@@ -5,7 +5,7 @@ using SharpMetal.QuartzCore;
using System.Runtime.Versioning;
using static SDL2.SDL;
namespace Ryujinx.Headless
namespace Ryujinx.Headless.SDL2.Metal
{
[SupportedOSPlatform("macos")]
class MetalWindow : WindowBase

View File

@@ -7,7 +7,7 @@ using Ryujinx.Input.HLE;
using System;
using static SDL2.SDL;
namespace Ryujinx.Headless
namespace Ryujinx.Headless.SDL2.OpenGL
{
class OpenGLWindow : WindowBase
{

View File

@@ -1,169 +1,14 @@
using CommandLine;
using Gommon;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.HLE;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.UI.Common.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Ryujinx.Headless
namespace Ryujinx.Headless.SDL2
{
public class Options
{
public void InheritMainConfig(string[] originalArgs, ConfigurationState configurationState, out bool needsProfileSet)
{
needsProfileSet = NeedsOverride(nameof(UserProfile));
if (NeedsOverride(nameof(IsFullscreen)))
IsFullscreen = configurationState.UI.StartFullscreen;
if (NeedsOverride(nameof(EnableKeyboard)))
EnableKeyboard = configurationState.Hid.EnableKeyboard;
if (NeedsOverride(nameof(EnableMouse)))
EnableMouse = configurationState.Hid.EnableMouse;
if (NeedsOverride(nameof(HideCursorMode)))
HideCursorMode = configurationState.HideCursor;
if (NeedsOverride(nameof(DisablePTC)))
DisablePTC = !configurationState.System.EnablePtc;
if (NeedsOverride(nameof(EnableInternetAccess)))
EnableInternetAccess = configurationState.System.EnableInternetAccess;
if (NeedsOverride(nameof(DisableFsIntegrityChecks)))
DisableFsIntegrityChecks = configurationState.System.EnableFsIntegrityChecks;
if (NeedsOverride(nameof(FsGlobalAccessLogMode)))
FsGlobalAccessLogMode = configurationState.System.FsGlobalAccessLogMode;
if (NeedsOverride(nameof(VSyncMode)))
VSyncMode = configurationState.Graphics.VSyncMode;
if (NeedsOverride(nameof(CustomVSyncInterval)))
CustomVSyncInterval = configurationState.Graphics.CustomVSyncInterval;
if (NeedsOverride(nameof(DisableShaderCache)))
DisableShaderCache = !configurationState.Graphics.EnableShaderCache;
if (NeedsOverride(nameof(EnableTextureRecompression)))
EnableTextureRecompression = configurationState.Graphics.EnableTextureRecompression;
if (NeedsOverride(nameof(DisableDockedMode)))
DisableDockedMode = !configurationState.System.EnableDockedMode;
if (NeedsOverride(nameof(SystemLanguage)))
SystemLanguage = (SystemLanguage)(int)configurationState.System.Language.Value;
if (NeedsOverride(nameof(SystemRegion)))
SystemRegion = (RegionCode)(int)configurationState.System.Region.Value;
if (NeedsOverride(nameof(SystemTimeZone)))
SystemTimeZone = configurationState.System.TimeZone;
if (NeedsOverride(nameof(SystemTimeOffset)))
SystemTimeOffset = configurationState.System.SystemTimeOffset;
if (NeedsOverride(nameof(MemoryManagerMode)))
MemoryManagerMode = configurationState.System.MemoryManagerMode;
if (NeedsOverride(nameof(AudioVolume)))
AudioVolume = configurationState.System.AudioVolume;
if (NeedsOverride(nameof(UseHypervisor)) && OperatingSystem.IsMacOS())
UseHypervisor = configurationState.System.UseHypervisor;
if (NeedsOverride(nameof(MultiplayerLanInterfaceId)))
MultiplayerLanInterfaceId = configurationState.Multiplayer.LanInterfaceId;
if (NeedsOverride(nameof(DisableFileLog)))
DisableFileLog = !configurationState.Logger.EnableFileLog;
if (NeedsOverride(nameof(LoggingEnableDebug)))
LoggingEnableDebug = configurationState.Logger.EnableDebug;
if (NeedsOverride(nameof(LoggingDisableStub)))
LoggingDisableStub = !configurationState.Logger.EnableStub;
if (NeedsOverride(nameof(LoggingDisableInfo)))
LoggingDisableInfo = !configurationState.Logger.EnableInfo;
if (NeedsOverride(nameof(LoggingDisableWarning)))
LoggingDisableWarning = !configurationState.Logger.EnableWarn;
if (NeedsOverride(nameof(LoggingDisableError)))
LoggingDisableError = !configurationState.Logger.EnableError;
if (NeedsOverride(nameof(LoggingEnableTrace)))
LoggingEnableTrace = configurationState.Logger.EnableTrace;
if (NeedsOverride(nameof(LoggingDisableGuest)))
LoggingDisableGuest = !configurationState.Logger.EnableGuest;
if (NeedsOverride(nameof(LoggingEnableFsAccessLog)))
LoggingEnableFsAccessLog = configurationState.Logger.EnableFsAccessLog;
if (NeedsOverride(nameof(LoggingGraphicsDebugLevel)))
LoggingGraphicsDebugLevel = configurationState.Logger.GraphicsDebugLevel;
if (NeedsOverride(nameof(ResScale)))
ResScale = configurationState.Graphics.ResScale;
if (NeedsOverride(nameof(MaxAnisotropy)))
MaxAnisotropy = configurationState.Graphics.MaxAnisotropy;
if (NeedsOverride(nameof(AspectRatio)))
AspectRatio = configurationState.Graphics.AspectRatio;
if (NeedsOverride(nameof(BackendThreading)))
BackendThreading = configurationState.Graphics.BackendThreading;
if (NeedsOverride(nameof(DisableMacroHLE)))
DisableMacroHLE = !configurationState.Graphics.EnableMacroHLE;
if (NeedsOverride(nameof(GraphicsShadersDumpPath)))
GraphicsShadersDumpPath = configurationState.Graphics.ShadersDumpPath;
if (NeedsOverride(nameof(GraphicsBackend)))
GraphicsBackend = configurationState.Graphics.GraphicsBackend;
if (NeedsOverride(nameof(AntiAliasing)))
AntiAliasing = configurationState.Graphics.AntiAliasing;
if (NeedsOverride(nameof(ScalingFilter)))
ScalingFilter = configurationState.Graphics.ScalingFilter;
if (NeedsOverride(nameof(ScalingFilterLevel)))
ScalingFilterLevel = configurationState.Graphics.ScalingFilterLevel;
if (NeedsOverride(nameof(DramSize)))
DramSize = configurationState.System.DramSize;
if (NeedsOverride(nameof(IgnoreMissingServices)))
IgnoreMissingServices = configurationState.System.IgnoreMissingServices;
if (NeedsOverride(nameof(IgnoreControllerApplet)))
IgnoreControllerApplet = configurationState.IgnoreApplet;
return;
bool NeedsOverride(string argKey) => originalArgs.None(arg => arg.TrimStart('-').EqualsIgnoreCase(OptionName(argKey)));
string OptionName(string propertyName) =>
typeof(Options)!.GetProperty(propertyName)!.GetCustomAttribute<OptionAttribute>()!.LongName;
}
// General
[Option("use-main-config", Required = false, Default = false, HelpText = "Use the settings from what was configured via the UI.")]
public bool InheritConfig { get; set; }
[Option("root-data-dir", Required = false, HelpText = "Set the custom folder path for Ryujinx data.")]
public string BaseDataDir { get; set; }
@@ -250,10 +95,10 @@ namespace Ryujinx.Headless
[Option("hide-cursor", Required = false, Default = HideCursorMode.OnIdle, HelpText = "Change when the cursor gets hidden.")]
public HideCursorMode HideCursorMode { get; set; }
[Option("list-input-profiles", Required = false, HelpText = "List input profiles.")]
[Option("list-input-profiles", Required = false, HelpText = "List inputs profiles.")]
public bool ListInputProfiles { get; set; }
[Option("list-input-ids", Required = false, HelpText = "List input IDs.")]
[Option("list-inputs-ids", Required = false, HelpText = "List inputs ids.")]
public bool ListInputIds { get; set; }
// System
@@ -327,7 +172,7 @@ namespace Ryujinx.Headless
public bool LoggingDisableWarning { get; set; }
[Option("disable-error-logs", Required = false, HelpText = "Disables printing error log messages.")]
public bool LoggingDisableError { get; set; }
public bool LoggingEnableError { get; set; }
[Option("enable-trace-logs", Required = false, Default = false, HelpText = "Enables printing trace log messages.")]
public bool LoggingEnableTrace { get; set; }
@@ -370,7 +215,7 @@ namespace Ryujinx.Headless
[Option("anti-aliasing", Required = false, Default = AntiAliasing.None, HelpText = "Set the type of anti aliasing being used. [None|Fxaa|SmaaLow|SmaaMedium|SmaaHigh|SmaaUltra]")]
public AntiAliasing AntiAliasing { get; set; }
[Option("scaling-filter", Required = false, Default = ScalingFilter.Bilinear, HelpText = "Set the scaling filter. [Bilinear|Nearest|Fsr|Area]")]
[Option("scaling-filter", Required = false, Default = ScalingFilter.Bilinear, HelpText = "Set the scaling filter. [Bilinear|Nearest|Fsr]")]
public ScalingFilter ScalingFilter { get; set; }
[Option("scaling-filter-level", Required = false, Default = 0, HelpText = "Set the scaling filter intensity (currently only applies to FSR). [0-100]")]

View File

@@ -1,9 +1,13 @@
using CommandLine;
using Gommon;
using Ryujinx.Ava;
using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.SDL2;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Logging.Targets;
@@ -11,12 +15,16 @@ using Ryujinx.Common.SystemInterop;
using Ryujinx.Common.Utilities;
using Ryujinx.Cpu;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.Multithreading;
using Ryujinx.Graphics.Gpu;
using Ryujinx.Graphics.Gpu.Shader;
using Ryujinx.Graphics.Metal;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.Graphics.Vulkan;
using Ryujinx.Graphics.Vulkan.MoltenVK;
using Ryujinx.Headless.SDL2.Metal;
using Ryujinx.Headless.SDL2.OpenGL;
using Ryujinx.Headless.SDL2.Vulkan;
using Ryujinx.HLE;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
@@ -25,16 +33,22 @@ using Ryujinx.Input;
using Ryujinx.Input.HLE;
using Ryujinx.Input.SDL2;
using Ryujinx.SDL2.Common;
using Ryujinx.UI.Common.Configuration;
using Silk.NET.Vulkan;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Threading;
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
using Key = Ryujinx.Common.Configuration.Hid.Key;
namespace Ryujinx.Headless
namespace Ryujinx.Headless.SDL2
{
public partial class HeadlessRyujinx
class Program
{
public static string Version { get; private set; }
private static VirtualFileSystem _virtualFileSystem;
private static ContentManager _contentManager;
private static AccountManager _accountManager;
@@ -44,18 +58,20 @@ namespace Ryujinx.Headless
private static Switch _emulationContext;
private static WindowBase _window;
private static WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution;
private static List<InputConfig> _inputConfiguration = [];
private static List<InputConfig> _inputConfiguration;
private static bool _enableKeyboard;
private static bool _enableMouse;
private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public static void Entrypoint(string[] args)
static void Main(string[] args)
{
Version = ReleaseInformation.Version;
// Make process DPI aware for proper window sizing on high-res screens.
ForceDpiAware.Windows();
Console.Title = $"Ryujinx Console {Program.Version} (Headless)";
Console.Title = $"Ryujinx Console {Version} (Headless SDL2)";
if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux())
{
@@ -83,7 +99,7 @@ namespace Ryujinx.Headless
}
Parser.Default.ParseArguments<Options>(args)
.WithParsed(options => Load(args, options))
.WithParsed(Load)
.WithNotParsed(errors =>
{
Logger.Error?.PrintMsg(LogClass.Application, "Error parsing command-line arguments:");
@@ -92,80 +108,238 @@ namespace Ryujinx.Headless
});
}
public static void ReloadConfig(string customConfigPath = null)
private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index)
{
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
if (inputId == null)
{
if (index == PlayerIndex.Player1)
{
Logger.Info?.Print(LogClass.Application, $"{index} not configured, defaulting to default keyboard.");
string configurationPath = null;
// Now load the configuration as the other subsystems are now registered
if (customConfigPath != null && File.Exists(customConfigPath))
{
configurationPath = customConfigPath;
}
else if (File.Exists(localConfigurationPath))
{
configurationPath = localConfigurationPath;
}
else if (File.Exists(appDataConfigurationPath))
{
configurationPath = appDataConfigurationPath;
}
if (configurationPath == null)
{
// No configuration, we load the default values and save it to disk
configurationPath = appDataConfigurationPath;
Logger.Notice.Print(LogClass.Application, $"No configuration file found. Saving default configuration to: {configurationPath}");
ConfigurationState.Instance.LoadDefault();
ConfigurationState.Instance.ToFileFormat().SaveConfig(configurationPath);
// Default to keyboard
inputId = "0";
}
else
{
Logger.Notice.Print(LogClass.Application, $"Loading configuration from: {configurationPath}");
Logger.Info?.Print(LogClass.Application, $"{index} not configured");
if (ConfigurationFileFormat.TryLoad(configurationPath, out ConfigurationFileFormat configurationFileFormat))
return null;
}
}
IGamepad gamepad = _inputManager.KeyboardDriver.GetGamepad(inputId);
bool isKeyboard = true;
if (gamepad == null)
{
ConfigurationState.Instance.Load(configurationFileFormat, configurationPath);
gamepad = _inputManager.GamepadDriver.GetGamepad(inputId);
isKeyboard = false;
if (gamepad == null)
{
Logger.Error?.Print(LogClass.Application, $"{index} gamepad not found (\"{inputId}\")");
return null;
}
}
string gamepadName = gamepad.Name;
gamepad.Dispose();
InputConfig config;
if (inputProfileName == null || inputProfileName.Equals("default"))
{
if (isKeyboard)
{
config = new StandardKeyboardInputConfig
{
Version = InputConfig.CurrentVersion,
Backend = InputBackendType.WindowKeyboard,
Id = null,
ControllerType = ControllerType.JoyconPair,
LeftJoycon = new LeftJoyconCommonConfig<Key>
{
DpadUp = Key.Up,
DpadDown = Key.Down,
DpadLeft = Key.Left,
DpadRight = Key.Right,
ButtonMinus = Key.Minus,
ButtonL = Key.E,
ButtonZl = Key.Q,
ButtonSl = Key.Unbound,
ButtonSr = Key.Unbound,
},
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
{
StickUp = Key.W,
StickDown = Key.S,
StickLeft = Key.A,
StickRight = Key.D,
StickButton = Key.F,
},
RightJoycon = new RightJoyconCommonConfig<Key>
{
ButtonA = Key.Z,
ButtonB = Key.X,
ButtonX = Key.C,
ButtonY = Key.V,
ButtonPlus = Key.Plus,
ButtonR = Key.U,
ButtonZr = Key.O,
ButtonSl = Key.Unbound,
ButtonSr = Key.Unbound,
},
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
{
StickUp = Key.I,
StickDown = Key.K,
StickLeft = Key.J,
StickRight = Key.L,
StickButton = Key.H,
},
};
}
else
{
Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location: {configurationPath}");
bool isNintendoStyle = gamepadName.Contains("Nintendo");
ConfigurationState.Instance.LoadDefault();
}
}
}
static void Load(string[] originalArgs, Options option)
config = new StandardControllerInputConfig
{
Initialize();
bool useLastUsedProfile = false;
if (option.InheritConfig)
Version = InputConfig.CurrentVersion,
Backend = InputBackendType.GamepadSDL2,
Id = null,
ControllerType = ControllerType.JoyconPair,
DeadzoneLeft = 0.1f,
DeadzoneRight = 0.1f,
RangeLeft = 1.0f,
RangeRight = 1.0f,
TriggerThreshold = 0.5f,
LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId>
{
option.InheritMainConfig(originalArgs, ConfigurationState.Instance, out useLastUsedProfile);
DpadUp = ConfigGamepadInputId.DpadUp,
DpadDown = ConfigGamepadInputId.DpadDown,
DpadLeft = ConfigGamepadInputId.DpadLeft,
DpadRight = ConfigGamepadInputId.DpadRight,
ButtonMinus = ConfigGamepadInputId.Minus,
ButtonL = ConfigGamepadInputId.LeftShoulder,
ButtonZl = ConfigGamepadInputId.LeftTrigger,
ButtonSl = ConfigGamepadInputId.Unbound,
ButtonSr = ConfigGamepadInputId.Unbound,
},
LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
{
Joystick = ConfigStickInputId.Left,
StickButton = ConfigGamepadInputId.LeftStick,
InvertStickX = false,
InvertStickY = false,
Rotate90CW = false,
},
RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
{
ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B,
ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A,
ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y,
ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X,
ButtonPlus = ConfigGamepadInputId.Plus,
ButtonR = ConfigGamepadInputId.RightShoulder,
ButtonZr = ConfigGamepadInputId.RightTrigger,
ButtonSl = ConfigGamepadInputId.Unbound,
ButtonSr = ConfigGamepadInputId.Unbound,
},
RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
{
Joystick = ConfigStickInputId.Right,
StickButton = ConfigGamepadInputId.RightStick,
InvertStickX = false,
InvertStickY = false,
Rotate90CW = false,
},
Motion = new StandardMotionConfigController
{
MotionBackend = MotionInputBackendType.GamepadDriver,
EnableMotion = true,
Sensitivity = 100,
GyroDeadzone = 1,
},
Rumble = new RumbleConfigController
{
StrongRumble = 1f,
WeakRumble = 1f,
EnableRumble = false,
},
};
}
}
else
{
string profileBasePath;
if (isKeyboard)
{
profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "keyboard");
}
else
{
profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "controller");
}
string path = Path.Combine(profileBasePath, inputProfileName + ".json");
if (!File.Exists(path))
{
Logger.Error?.Print(LogClass.Application, $"Input profile \"{inputProfileName}\" not found for \"{inputId}\"");
return null;
}
try
{
config = JsonHelper.DeserializeFromFile(path, _serializerContext.InputConfig);
}
catch (JsonException)
{
Logger.Error?.Print(LogClass.Application, $"Input profile \"{inputProfileName}\" parsing failed for \"{inputId}\"");
return null;
}
}
config.Id = inputId;
config.PlayerIndex = index;
string inputTypeName = isKeyboard ? "Keyboard" : "Gamepad";
Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} configured with {inputTypeName} \"{config.Id}\"");
// If both stick ranges are 0 (usually indicative of an outdated profile load) then both sticks will be set to 1.0.
if (config is StandardControllerInputConfig controllerConfig)
{
if (controllerConfig.RangeLeft <= 0.0f && controllerConfig.RangeRight <= 0.0f)
{
controllerConfig.RangeLeft = 1.0f;
controllerConfig.RangeRight = 1.0f;
Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} stick range reset. Save the profile now to update your configuration");
}
}
return config;
}
static void Load(Options option)
{
AppDataManager.Initialize(option.BaseDataDir);
if (useLastUsedProfile && AccountSaveDataManager.GetLastUsedUser().TryGet(out var profile))
option.UserProfile = profile.Name;
// Check if keys exists.
if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")))
{
if (!(AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys"))))
{
Logger.Error?.Print(LogClass.Application, "Keys not found");
}
}
ReloadConfig();
_virtualFileSystem = VirtualFileSystem.CreateInstance();
_libHacHorizonManager = new LibHacHorizonManager();
@@ -180,7 +354,7 @@ namespace Ryujinx.Headless
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
GraphicsConfig.EnableShaderCache = !option.DisableShaderCache;
GraphicsConfig.EnableShaderCache = true;
if (OperatingSystem.IsMacOS())
{
@@ -191,13 +365,15 @@ namespace Ryujinx.Headless
}
}
IGamepad gamepad;
if (option.ListInputIds)
{
Logger.Info?.Print(LogClass.Application, "Input Ids:");
foreach (string id in _inputManager.KeyboardDriver.GamepadsIds)
{
IGamepad gamepad = _inputManager.KeyboardDriver.GetGamepad(id);
gamepad = _inputManager.KeyboardDriver.GetGamepad(id);
Logger.Info?.Print(LogClass.Application, $"- {id} (\"{gamepad.Name}\")");
@@ -206,7 +382,7 @@ namespace Ryujinx.Headless
foreach (string id in _inputManager.GamepadDriver.GamepadsIds)
{
IGamepad gamepad = _inputManager.GamepadDriver.GetGamepad(id);
gamepad = _inputManager.GamepadDriver.GetGamepad(id);
Logger.Info?.Print(LogClass.Application, $"- {id} (\"{gamepad.Name}\")");
@@ -223,7 +399,7 @@ namespace Ryujinx.Headless
return;
}
_inputConfiguration ??= [];
_inputConfiguration = new List<InputConfig>();
_enableKeyboard = option.EnableKeyboard;
_enableMouse = option.EnableMouse;
@@ -247,7 +423,6 @@ namespace Ryujinx.Headless
LoadPlayerConfiguration(option.InputProfile8Name, option.InputId8, PlayerIndex.Player8);
LoadPlayerConfiguration(option.InputProfileHandheldName, option.InputIdHandheld, PlayerIndex.Handheld);
if (_inputConfiguration.Count == 0)
{
return;
@@ -258,7 +433,7 @@ namespace Ryujinx.Headless
Logger.SetEnable(LogLevel.Stub, !option.LoggingDisableStub);
Logger.SetEnable(LogLevel.Info, !option.LoggingDisableInfo);
Logger.SetEnable(LogLevel.Warning, !option.LoggingDisableWarning);
Logger.SetEnable(LogLevel.Error, !option.LoggingDisableError);
Logger.SetEnable(LogLevel.Error, option.LoggingEnableError);
Logger.SetEnable(LogLevel.Trace, option.LoggingEnableTrace);
Logger.SetEnable(LogLevel.Guest, !option.LoggingDisableGuest);
Logger.SetEnable(LogLevel.AccessLog, option.LoggingEnableFsAccessLog);
@@ -347,6 +522,88 @@ namespace Ryujinx.Headless
};
}
private static IRenderer CreateRenderer(Options options, WindowBase window)
{
if (options.GraphicsBackend == GraphicsBackend.Vulkan && window is VulkanWindow vulkanWindow)
{
string preferredGpuId = string.Empty;
Vk api = Vk.GetApi();
if (!string.IsNullOrEmpty(options.PreferredGPUVendor))
{
string preferredGpuVendor = options.PreferredGPUVendor.ToLowerInvariant();
var devices = VulkanRenderer.GetPhysicalDevices(api);
foreach (var device in devices)
{
if (device.Vendor.ToLowerInvariant() == preferredGpuVendor)
{
preferredGpuId = device.Id;
break;
}
}
}
return new VulkanRenderer(
api,
(instance, vk) => new SurfaceKHR((ulong)(vulkanWindow.CreateWindowSurface(instance.Handle))),
vulkanWindow.GetRequiredInstanceExtensions,
preferredGpuId);
}
if (options.GraphicsBackend == GraphicsBackend.Metal && window is MetalWindow metalWindow && OperatingSystem.IsMacOS())
{
return new MetalRenderer(metalWindow.GetLayer);
}
return new OpenGLRenderer();
}
private static Switch InitializeEmulationContext(WindowBase window, IRenderer renderer, Options options)
{
BackendThreading threadingMode = options.BackendThreading;
bool threadedGAL = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading);
if (threadedGAL)
{
renderer = new ThreadedRenderer(renderer);
}
HLEConfiguration configuration = new(_virtualFileSystem,
_libHacHorizonManager,
_contentManager,
_accountManager,
_userChannelPersistence,
renderer,
new SDL2HardwareDeviceDriver(),
options.DramSize,
window,
options.SystemLanguage,
options.SystemRegion,
options.VSyncMode,
!options.DisableDockedMode,
!options.DisablePTC,
options.EnableInternetAccess,
!options.DisableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None,
options.FsGlobalAccessLogMode,
options.SystemTimeOffset,
options.SystemTimeZone,
options.MemoryManagerMode,
options.IgnoreMissingServices,
options.AspectRatio,
options.AudioVolume,
options.UseHypervisor ?? true,
options.MultiplayerLanInterfaceId,
Common.Configuration.Multiplayer.MultiplayerMode.Disabled,
false,
string.Empty,
string.Empty,
options.CustomVSyncInterval);
return new Switch(configuration);
}
private static void ExecutionEntrypoint()
{
if (OperatingSystem.IsWindows())

View File

@@ -0,0 +1,73 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Version>1.0.0-dirty</Version>
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
<SigningCertificate Condition=" '$(SigningCertificate)' == '' ">-</SigningCertificate>
<TieredPGO>true</TieredPGO>
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenTK.Core" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="$([MSBuild]::IsOSPlatform('OSX'))">
<Exec Command="codesign --entitlements '$(ProjectDir)..\..\distribution\macos\entitlements.xml' -f -s $(SigningCertificate) '$(TargetDir)$(TargetName)'" />
</Target>
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" />
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
<ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj" />
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" />
<ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Metal\Ryujinx.Graphics.Metal.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" />
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64'" />
</ItemGroup>
<ItemGroup>
<Content Include="..\..\distribution\legal\THIRDPARTY.md">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>THIRDPARTY.md</TargetPath>
</Content>
<Content Include="..\..\LICENSE.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>LICENSE.txt</TargetPath>
</Content>
</ItemGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64' OR '$(RuntimeIdentifier)' == 'linux-arm64' OR ('$(RuntimeIdentifier)' == '' AND $([MSBuild]::IsOSPlatform('Linux')))">
<Content Include="..\..\distribution\linux\Ryujinx.sh">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Ryujinx.bmp" />
</ItemGroup>
<!-- Due to .net core 3.1 embedded resource loading -->
<PropertyGroup>
<EmbeddedResourceUseDependentUponConvention>false</EmbeddedResourceUseDependentUponConvention>
<ApplicationIcon>..\Ryujinx\Ryujinx.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup Condition="'$(RuntimeIdentifier)' != ''">
<PublishSingleFile>true</PublishSingleFile>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode>
</PropertyGroup>
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@@ -1,6 +1,6 @@
using System;
namespace Ryujinx.Headless
namespace Ryujinx.Headless.SDL2
{
class StatusUpdatedEventArgs(
string vSyncMode,

View File

@@ -6,7 +6,7 @@ using System;
using System.Runtime.InteropServices;
using static SDL2.SDL;
namespace Ryujinx.Headless
namespace Ryujinx.Headless.SDL2.Vulkan
{
class VulkanWindow : WindowBase
{

View File

@@ -1,6 +1,5 @@
using Humanizer;
using LibHac.Tools.Fs;
using Ryujinx.Ava;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Logging;
@@ -27,7 +26,7 @@ using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter;
using Switch = Ryujinx.HLE.Switch;
namespace Ryujinx.Headless
namespace Ryujinx.Headless.SDL2
{
abstract partial class WindowBase : IHostUIHandler, IDisposable
{
@@ -137,7 +136,7 @@ namespace Ryujinx.Headless
private void SetWindowIcon()
{
Stream iconStream = typeof(Program).Assembly.GetManifestResourceStream("HeadlessLogo");
Stream iconStream = typeof(WindowBase).Assembly.GetManifestResourceStream("Ryujinx.Headless.SDL2.Ryujinx.bmp");
byte[] iconBytes = new byte[iconStream!.Length];
if (iconStream.Read(iconBytes, 0, iconBytes.Length) != iconBytes.Length)
@@ -255,12 +254,12 @@ namespace Ryujinx.Headless
private void SetAntiAliasing()
{
Renderer?.Window.SetAntiAliasing(AntiAliasing);
Renderer?.Window.SetAntiAliasing((Graphics.GAL.AntiAliasing)AntiAliasing);
}
private void SetScalingFilter()
{
Renderer?.Window.SetScalingFilter(ScalingFilter);
Renderer?.Window.SetScalingFilter((Graphics.GAL.ScalingFilter)ScalingFilter);
Renderer?.Window.SetScalingFilterLevel(ScalingFilterLevel);
}
@@ -319,7 +318,7 @@ namespace Ryujinx.Headless
Device.VSyncMode.ToString(),
dockedMode,
Device.Configuration.AspectRatio.ToText(),
$"{Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)",
$"Game: {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)",
$"FIFO: {Device.Statistics.GetFifoPercent():0.00} %",
$"GPU: {_gpuDriverName}"));

View File

@@ -36,8 +36,6 @@ namespace Ryujinx.UI.App.Common
public string Path { get; set; }
public BlitStruct<ApplicationControlProperty> ControlHolder { get; set; }
public bool HasControlHolder => ControlHolder.ByteSpan.Length > 0;
public string TimePlayedString => ValueFormatUtils.FormatTimeSpan(TimePlayed);
public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed)?.Replace(" ", "\n") ?? LocalizedNever();

View File

@@ -789,15 +789,16 @@ namespace Ryujinx.UI.App.Common
using HttpClient httpClient = new HttpClient();
string ldnGameDataArrayString = await httpClient.GetStringAsync($"https://{ldnWebHost}/api/public_games");
ldnGameDataArray = JsonHelper.Deserialize(ldnGameDataArrayString, _ldnDataSerializerContext.IEnumerableLdnGameData);
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs
var evt = new LdnGameDataReceivedEventArgs
{
LdnData = ldnGameDataArray
});
};
LdnGameDataReceived?.Invoke(null, evt);
}
catch (Exception ex)
{
Logger.Warning?.Print(LogClass.Application, $"Failed to fetch the public games JSON from the API. Player and game count in the game list will be unavailable.\n{ex.Message}");
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs()
{
LdnData = Array.Empty<LdnGameData>()
});
@@ -805,7 +806,7 @@ namespace Ryujinx.UI.App.Common
}
else
{
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs()
{
LdnData = Array.Empty<LdnGameData>()
});

View File

@@ -1,7 +1,4 @@
using LibHac.Ns;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.UI.App.Common
{
@@ -15,28 +12,5 @@ namespace Ryujinx.UI.App.Common
public string Mode { get; set; }
public string Status { get; set; }
public IEnumerable<string> Players { get; set; }
public static Array GetArrayForApp(
IEnumerable<LdnGameData> receivedData, ref ApplicationControlProperty acp)
{
LibHac.Common.FixedArrays.Array8<ulong> communicationId = acp.LocalCommunicationId;
return new Array(receivedData.Where(game =>
communicationId.Items.Contains(Convert.ToUInt64(game.TitleId, 16))
));
}
public class Array
{
private readonly LdnGameData[] _ldnDatas;
internal Array(IEnumerable<LdnGameData> receivedData)
{
_ldnDatas = receivedData.ToArray();
}
public int PlayerCount => _ldnDatas.Sum(it => it.PlayerCount);
public int GameCount => _ldnDatas.Length;
}
}
}

View File

@@ -17,7 +17,7 @@ namespace Ryujinx.UI.Common.Configuration
/// <summary>
/// The current version of the file format
/// </summary>
public const int CurrentVersion = 58;
public const int CurrentVersion = 57;
/// <summary>
/// Version of the configuration file format
@@ -430,16 +430,6 @@ namespace Ryujinx.UI.Common.Configuration
/// </summary>
public bool UseHypervisor { get; set; }
/// <summary>
/// Show toggles for dirty hacks in the UI.
/// </summary>
public bool ShowDirtyHacks { get; set; }
/// <summary>
/// The packed value of the enabled dirty hacks.
/// </summary>
public int EnabledDirtyHacks { get; set; }
/// <summary>
/// Loads a configuration file from disk
/// </summary>

View File

@@ -736,9 +736,6 @@ namespace Ryujinx.UI.Common.Configuration
Multiplayer.LdnPassphrase.Value = configurationFileFormat.MultiplayerLdnPassphrase;
Multiplayer.LdnServer.Value = configurationFileFormat.LdnServer;
Hacks.ShowDirtyHacks.Value = configurationFileFormat.ShowDirtyHacks;
Hacks.Xc2MenuSoftlockFix.Value = ((DirtyHacks)configurationFileFormat.EnabledDirtyHacks).HasFlag(DirtyHacks.Xc2MenuSoftlockFix);
if (configurationFileUpdated)
{
ToFileFormat().SaveConfig(configurationFilePath);

View File

@@ -1,5 +1,4 @@
using ARMeilleure;
using Gommon;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
@@ -618,49 +617,6 @@ namespace Ryujinx.UI.Common.Configuration
}
}
public class HacksSection
{
/// <summary>
/// Show toggles for dirty hacks in the UI.
/// </summary>
public ReactiveObject<bool> ShowDirtyHacks { get; private set; }
public ReactiveObject<bool> Xc2MenuSoftlockFix { get; private set; }
public HacksSection()
{
ShowDirtyHacks = new ReactiveObject<bool>();
Xc2MenuSoftlockFix = new ReactiveObject<bool>();
Xc2MenuSoftlockFix.Event += HackChanged;
}
private void HackChanged(object sender, ReactiveEventArgs<bool> rxe)
{
Ryujinx.Common.Logging.Logger.Info?.Print(LogClass.Configuration, $"EnabledDirtyHacks set to: {EnabledHacks}", "LogValueChange");
}
public DirtyHacks EnabledHacks
{
get
{
DirtyHacks dirtyHacks = DirtyHacks.None;
if (Xc2MenuSoftlockFix)
Apply(DirtyHacks.Xc2MenuSoftlockFix);
return dirtyHacks;
void Apply(DirtyHacks hack)
{
if (dirtyHacks is not DirtyHacks.None)
dirtyHacks |= hack;
else
dirtyHacks = hack;
}
}
}
}
/// <summary>
/// The default configuration instance
/// </summary>
@@ -696,11 +652,6 @@ namespace Ryujinx.UI.Common.Configuration
/// </summary>
public MultiplayerSection Multiplayer { get; private set; }
/// <summary>
/// The Dirty Hacks section
/// </summary>
public HacksSection Hacks { get; private set; }
/// <summary>
/// Enables or disables Discord Rich Presence
/// </summary>
@@ -749,7 +700,6 @@ namespace Ryujinx.UI.Common.Configuration
Graphics = new GraphicsSection();
Hid = new HidSection();
Multiplayer = new MultiplayerSection();
Hacks = new HacksSection();
EnableDiscordIntegration = new ReactiveObject<bool>();
CheckUpdatesOnStart = new ReactiveObject<bool>();
ShowConfirmExit = new ReactiveObject<bool>();

View File

@@ -138,8 +138,6 @@ namespace Ryujinx.UI.Common.Configuration
MultiplayerDisableP2p = Multiplayer.DisableP2p,
MultiplayerLdnPassphrase = Multiplayer.LdnPassphrase,
LdnServer = Multiplayer.LdnServer,
ShowDirtyHacks = Hacks.ShowDirtyHacks,
EnabledDirtyHacks = (int)Hacks.EnabledHacks,
};
return configurationFile;

View File

@@ -1,5 +1,4 @@
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.SystemState;
using System.Text.Json.Serialization;
namespace Ryujinx.UI.Common.Configuration.System

View File

@@ -2,10 +2,10 @@ using DiscordRPC;
using Humanizer;
using Humanizer.Localisation;
using Ryujinx.Common;
using Ryujinx.HLE;
using Ryujinx.HLE.Loaders.Processes;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common.Configuration;
using System.Linq;
using System.Text;
namespace Ryujinx.UI.Common
@@ -45,16 +45,6 @@ namespace Ryujinx.UI.Common
};
ConfigurationState.Instance.EnableDiscordIntegration.Event += Update;
TitleIDs.CurrentApplicationChanged += titleId =>
{
if (titleId)
SwitchToPlayingState(
ApplicationLibrary.LoadAndSaveMetaData(titleId),
Switch.Shared.Processes.ActiveApplication
);
else
SwitchToMainState();
};
}
private static void Update(object sender, ReactiveEventArgs<bool> evnt)
@@ -80,13 +70,13 @@ namespace Ryujinx.UI.Common
}
}
private static void SwitchToPlayingState(ApplicationMetadata appMeta, ProcessResult procRes)
public static void SwitchToPlayingState(ApplicationMetadata appMeta, ProcessResult procRes)
{
_discordClient?.SetPresence(new RichPresence
{
Assets = new Assets
{
LargeImageKey = TitleIDs.GetDiscordGameAsset(procRes.ProgramIdText),
LargeImageKey = _discordGameAssetKeys.Contains(procRes.ProgramIdText) ? procRes.ProgramIdText : "game",
LargeImageText = TruncateToByteLength($"{appMeta.Title} (v{procRes.DisplayVersion})"),
SmallImageKey = "ryujinx",
SmallImageText = TruncateToByteLength(_description)
@@ -99,7 +89,7 @@ namespace Ryujinx.UI.Common
});
}
private static void SwitchToMainState() => _discordClient?.SetPresence(_discordPresenceMain);
public static void SwitchToMainState() => _discordClient?.SetPresence(_discordPresenceMain);
private static string TruncateToByteLength(string input)
{
@@ -132,5 +122,151 @@ namespace Ryujinx.UI.Common
{
_discordClient?.Dispose();
}
private static readonly string[] _discordGameAssetKeys =
[
"010055d009f78000", // Fire Emblem: Three Houses
"0100a12011cc8000", // Fire Emblem: Shadow Dragon
"0100a6301214e000", // Fire Emblem Engage
"0100f15003e64000", // Fire Emblem Warriors
"010071f0143ea000", // Fire Emblem Warriors: Three Hopes
"01007e3006dda000", // Kirby Star Allies
"01004d300c5ae000", // Kirby and the Forgotten Land
"01006b601380e000", // Kirby's Return to Dream Land Deluxe
"01003fb00c5a8000", // Super Kirby Clash
"0100227010460000", // Kirby Fighters 2
"0100a8e016236000", // Kirby's Dream Buffet
"01007ef00011e000", // The Legend of Zelda: Breath of the Wild
"01006bb00c6f0000", // The Legend of Zelda: Link's Awakening
"01002da013484000", // The Legend of Zelda: Skyward Sword HD
"0100f2c0115b6000", // The Legend of Zelda: Tears of the Kingdom
"01008cf01baac000", // The Legend of Zelda: Echoes of Wisdom
"01000b900d8b0000", // Cadence of Hyrule
"0100ae00096ea000", // Hyrule Warriors: Definitive Edition
"01002b00111a2000", // Hyrule Warriors: Age of Calamity
"010048701995e000", // Luigi's Mansion 2 HD
"0100dca0064a6000", // Luigi's Mansion 3
"010093801237c000", // Metroid Dread
"010012101468c000", // Metroid Prime Remastered
"0100000000010000", // SUPER MARIO ODYSSEY
"0100ea80032ea000", // Super Mario Bros. U Deluxe
"01009b90006dc000", // Super Mario Maker 2
"010049900f546000", // Super Mario 3D All-Stars
"010049900F546001", // ^ 64
"010049900F546002", // ^ Sunshine
"010049900F546003", // ^ Galaxy
"010028600ebda000", // Super Mario 3D World + Bowser's Fury
"010015100b514000", // Super Mario Bros. Wonder
"0100152000022000", // Mario Kart 8 Deluxe
"010036b0034e4000", // Super Mario Party
"01006fe013472000", // Mario Party Superstars
"0100965017338000", // Super Mario Party Jamboree
"01006d0017f7a000", // Mario & Luigi: Brothership
"010067300059a000", // Mario + Rabbids: Kingdom Battle
"0100317013770000", // Mario + Rabbids: Sparks of Hope
"0100a3900c3e2000", // Paper Mario: The Origami King
"0100ecd018ebe000", // Paper Mario: The Thousand-Year Door
"0100bc0018138000", // Super Mario RPG
"0100bde00862a000", // Mario Tennis Aces
"0100c9c00e25c000", // Mario Golf: Super Rush
"010019401051c000", // Mario Strikers: Battle League
"010003000e146000", // Mario & Sonic at the Olympic Games Tokyo 2020
"0100b99019412000", // Mario vs. Donkey Kong
"0100aa80194b0000", // Pikmin 1
"0100d680194b2000", // Pikmin 2
"0100f4c009322000", // Pikmin 3 Deluxe
"0100b7c00933a000", // Pikmin 4
"010003f003a34000", // Pokémon: Let's Go Pikachu!
"0100187003a36000", // Pokémon: Let's Go Eevee!
"0100abf008968000", // Pokémon Sword
"01008db008c2c000", // Pokémon Shield
"0100000011d90000", // Pokémon Brilliant Diamond
"010018e011d92000", // Pokémon Shining Pearl
"01001f5010dfa000", // Pokémon Legends: Arceus
"0100a3d008c5c000", // Pokémon Scarlet
"01008f6008c5e000", // Pokémon Violet
"0100b3f000be2000", // Pokkén Tournament DX
"0100f4300bf2c000", // New Pokémon Snap
"01003bc0000a0000", // Splatoon 2 (US)
"0100f8f0000a2000", // Splatoon 2 (EU)
"01003c700009c000", // Splatoon 2 (JP)
"0100c2500fc20000", // Splatoon 3
"0100ba0018500000", // Splatoon 3: Splatfest World Premiere
"010040600c5ce000", // Tetris 99
"0100277011f1a000", // Super Mario Bros. 35
"0100ad9012510000", // PAC-MAN 99
"0100ccf019c8c000", // F-ZERO 99
"0100d870045b6000", // NES - Nintendo Switch Online
"01008d300c50c000", // SNES - Nintendo Switch Online
"0100c9a00ece6000", // N64 - Nintendo Switch Online
"0100e0601c632000", // N64 - Nintendo Switch Online 18+
"0100c62011050000", // GB - Nintendo Switch Online
"010012f017576000", // GBA - Nintendo Switch Online
"01000320000cc000", // 1-2 Switch
"0100300012f2a000", // Advance Wars 1+2: Re-Boot Camp
"01006f8002326000", // Animal Crossing: New Horizons
"0100620012d6e000", // Big Brain Academy: Brain vs. Brain
"010018300d006000", // BOXBOY! + BOXGIRL!
"0100c1f0051b6000", // Donkey Kong Country: Tropical Freeze
"0100ed000d390000", // Dr. Kawashima's Brain Training
"010067b017588000", // Endless Ocean Luminous
"0100d2f00d5c0000", // Nintendo Switch Sports
"01006b5012b32000", // Part Time UFO
"0100704000B3A000", // Snipperclips
"01006a800016e000", // Super Smash Bros. Ultimate
"0100a9400c9c2000", // Tokyo Mirage Sessions #FE Encore
"010076f0049a2000", // Bayonetta
"01007960049a0000", // Bayonetta 2
"01004a4010fea000", // Bayonetta 3
"0100cf5010fec000", // Bayonetta Origins: Cereza and the Lost Demon
"0100dcd01525a000", // Persona 3 Portable
"010062b01525c000", // Persona 4 Golden
"010075a016a3a000", // Persona 4 Arena Ultimax
"01005ca01580e000", // Persona 5 Royal
"0100801011c3e000", // Persona 5 Strikers
"010087701b092000", // Persona 5 Tactica
"01009aa000faa000", // Sonic Mania
"01004ad014bf0000", // Sonic Frontiers
"01005ea01c0fc000", // SONIC X SHADOW GENERATIONS
"01005ea01c0fc001", // ^
"010056e00853a000", // A Hat in Time
"0100dbf01000a000", // Burnout Paradise Remastered
"0100744001588000", // Cars 3: Driven to Win
"0100b41013c82000", // Cruis'n Blast
"01001b300b9be000", // Diablo III: Eternal Collection
"01008c8012920000", // Dying Light Platinum Edition
"010073c01af34000", // LEGO Horizon Adventures
"0100770008dd8000", // Monster Hunter Generations Ultimate
"0100b04011742000", // Monster Hunter Rise
"0100853015e86000", // No Man's Sky
"01007bb017812000", // Portal
"0100abd01785c000", // Portal 2
"01008e200c5c2000", // Muse Dash
"01007820196a6000", // Red Dead Redemption
"01002f7013224000", // Rune Factory 5
"01008d100d43e000", // Saints Row IV
"0100de600beee000", // Saints Row: The Third - The Full Package
"01001180021fa000", // Shovel Knight: Specter of Torment
"0100d7a01b7a2000", // Star Wars: Bounty Hunter
"0100800015926000", // Suika Game
"0100e46006708000", // Terraria
"01000a10041ea000", // The Elder Scrolls V: Skyrim
"010057a01e4d4000", // TSUKIHIME -A piece of blue glass moon-
"010080b00ad66000", // Undertale
];
}
}

View File

@@ -1,64 +0,0 @@
using LibHac.Common;
using LibHac.Ncm;
using LibHac.Ns;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.HLE;
using Ryujinx.HLE.FileSystem;
using Ryujinx.UI.App.Common;
namespace Ryujinx.UI.Common.Helper
{
public readonly struct AppletMetadata
{
private readonly ContentManager _contentManager;
public string Name { get; }
public ulong ProgramId { get; }
public string Version { get; }
public AppletMetadata(ContentManager contentManager, string name, ulong programId, string version = "1.0.0")
: this(name, programId, version)
{
_contentManager = contentManager;
}
public AppletMetadata(string name, ulong programId, string version = "1.0.0")
{
Name = name;
ProgramId = programId;
Version = version;
}
public string GetContentPath(ContentManager contentManager)
=> (contentManager ?? _contentManager)
.GetInstalledContentPath(ProgramId, StorageId.BuiltInSystem, NcaContentType.Program);
public bool CanStart(ContentManager contentManager, out ApplicationData appData, out BlitStruct<ApplicationControlProperty> appControl)
{
contentManager ??= _contentManager;
if (contentManager == null)
{
appData = null;
appControl = new BlitStruct<ApplicationControlProperty>(0);
return false;
}
appData = new()
{
Name = Name,
Id = ProgramId,
Path = GetContentPath(contentManager)
};
if (string.IsNullOrEmpty(appData.Path))
{
appControl = new BlitStruct<ApplicationControlProperty>(0);
return false;
}
appControl = StructHelpers.CreateCustomNacpData(Name, Version);
return true;
}
}
}

View File

@@ -132,7 +132,7 @@ namespace Ryujinx.UI.Common.Helper
if (uninstall)
{
// If the types don't already exist, there's nothing to do, and we can call this operation successful.
// If the types don't already exist, there's nothing to do and we can call this operation successful.
if (!AreMimeTypesRegisteredWindows())
{
return true;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 KiB

View File

@@ -33,7 +33,7 @@
<EmbeddedResource Include="Resources\Icon_XCI.png" />
<EmbeddedResource Include="Resources\Logo_Amiibo.png" />
<EmbeddedResource Include="Resources\Logo_Ryujinx.png" />
<EmbeddedResource Include="Resources\Logo_Ryujinx_AntiAlias.png" />
<EmbeddedResource Include="Resources\Logo_Thiccjinx.png" />
<EmbeddedResource Include="Resources\Logo_Discord_Dark.png" />
<EmbeddedResource Include="Resources\Logo_Discord_Light.png" />
<EmbeddedResource Include="Resources\Logo_GitHub_Dark.png" />

View File

@@ -143,6 +143,23 @@ namespace Ryujinx.Ava
public ulong ApplicationId { get; private set; }
public bool ScreenshotRequested { get; set; }
public bool ShouldInitMetal
{
get
{
return OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64 &&
(
(
(
ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Auto &&
RendererHost.KnownGreatMetalTitles.ContainsIgnoreCase(ApplicationId.ToString("X16"))
) ||
ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Metal
)
);
}
}
public AppHost(
RendererHost renderer,
InputManager inputManager,
@@ -289,19 +306,19 @@ namespace Ryujinx.Ava
private void UpdateScalingFilterLevel(object sender, ReactiveEventArgs<int> e)
{
_renderer.Window?.SetScalingFilter(ConfigurationState.Instance.Graphics.ScalingFilter);
_renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel);
_renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
_renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
}
private void UpdateScalingFilter(object sender, ReactiveEventArgs<ScalingFilter> e)
{
_renderer.Window?.SetScalingFilter(ConfigurationState.Instance.Graphics.ScalingFilter);
_renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel);
_renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
_renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
}
private void UpdateColorSpacePassthrough(object sender, ReactiveEventArgs<bool> e)
{
_renderer.Window?.SetColorSpacePassthrough(ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough);
_renderer.Window?.SetColorSpacePassthrough((bool)ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Value);
}
public void UpdateVSyncMode(object sender, ReactiveEventArgs<VSyncMode> e)
@@ -311,8 +328,7 @@ namespace Ryujinx.Ava
Device.VSyncMode = e.NewValue;
Device.UpdateVSyncInterval();
}
_renderer.Window?.ChangeVSyncMode(e.NewValue);
_renderer.Window?.ChangeVSyncMode((Ryujinx.Graphics.GAL.VSyncMode)e.NewValue);
_viewModel.ShowCustomVSyncIntervalPicker = (e.NewValue == VSyncMode.Custom);
}
@@ -527,7 +543,7 @@ namespace Ryujinx.Ava
private void UpdateAntiAliasing(object sender, ReactiveEventArgs<AntiAliasing> e)
{
_renderer?.Window?.SetAntiAliasing(e.NewValue);
_renderer?.Window?.SetAntiAliasing((Graphics.GAL.AntiAliasing)e.NewValue);
}
private void UpdateDockedModeState(object sender, ReactiveEventArgs<bool> e)
@@ -578,6 +594,7 @@ namespace Ryujinx.Ava
public void Stop()
{
_isActive = false;
DiscordIntegrationModule.SwitchToMainState();
}
private void Exit()
@@ -862,10 +879,12 @@ namespace Ryujinx.Ava
return false;
}
ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText,
ApplicationMetadata appMeta = ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText,
appMetadata => appMetadata.UpdatePreGame()
);
DiscordIntegrationModule.SwitchToPlayingState(appMeta, Device.Processes.ActiveApplication);
return true;
}
@@ -893,20 +912,27 @@ namespace Ryujinx.Ava
VirtualFileSystem.ReloadKeySet();
// Initialize Renderer.
GraphicsBackend backend = TitleIDs.SelectGraphicsBackend(ApplicationId.ToString("X16"), ConfigurationState.Instance.Graphics.GraphicsBackend);
IRenderer renderer;
GraphicsBackend backend = ConfigurationState.Instance.Graphics.GraphicsBackend;
IRenderer renderer = backend switch
if (ShouldInitMetal)
{
#pragma warning disable CA1416 // This call site is reachable on all platforms
// SelectGraphicsBackend does a check for Mac, on top of checking if it's an ARM Mac. This isn't a problem.
GraphicsBackend.Metal => new MetalRenderer((RendererHost.EmbeddedWindow as EmbeddedWindowMetal)!.CreateSurface),
// The condition does a check for Mac, on top of checking if it's an ARM Mac. This isn't a problem.
renderer = new MetalRenderer((RendererHost.EmbeddedWindow as EmbeddedWindowMetal)!.CreateSurface);
#pragma warning restore CA1416
GraphicsBackend.Vulkan => VulkanRenderer.Create(
}
else if (backend == GraphicsBackend.Vulkan || (backend == GraphicsBackend.Auto && !ShouldInitMetal))
{
renderer = VulkanRenderer.Create(
ConfigurationState.Instance.Graphics.PreferredGpu,
(RendererHost.EmbeddedWindow as EmbeddedWindowVulkan)!.CreateSurface,
VulkanHelper.GetRequiredInstanceExtensions),
_ => new OpenGLRenderer()
};
VulkanHelper.GetRequiredInstanceExtensions);
}
else
{
renderer = new OpenGLRenderer();
}
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;
@@ -921,7 +947,7 @@ namespace Ryujinx.Ava
// Initialize Configuration.
var memoryConfiguration = ConfigurationState.Instance.System.DramSize.Value;
Device = new Switch(new HLEConfiguration(
Device = new HLE.Switch(new HLEConfiguration(
VirtualFileSystem,
_viewModel.LibHacHorizonManager,
ContentManager,
@@ -951,8 +977,7 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.Multiplayer.DisableP2p,
ConfigurationState.Instance.Multiplayer.LdnPassphrase,
ConfigurationState.Instance.Multiplayer.LdnServer,
ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value,
ConfigurationState.Instance.Hacks.ShowDirtyHacks ? ConfigurationState.Instance.Hacks.EnabledHacks : DirtyHacks.None));
ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value));
}
private static IHardwareDeviceDriver InitializeAudio()
@@ -1056,10 +1081,10 @@ namespace Ryujinx.Ava
Device.Gpu.Renderer.Initialize(_glLogLevel);
_renderer?.Window?.SetAntiAliasing(ConfigurationState.Instance.Graphics.AntiAliasing);
_renderer?.Window?.SetScalingFilter(ConfigurationState.Instance.Graphics.ScalingFilter);
_renderer?.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel);
_renderer?.Window?.SetColorSpacePassthrough(ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough);
_renderer?.Window?.SetAntiAliasing((Graphics.GAL.AntiAliasing)ConfigurationState.Instance.Graphics.AntiAliasing.Value);
_renderer?.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
_renderer?.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
_renderer?.Window?.SetColorSpacePassthrough(ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Value);
Width = (int)RendererHost.Bounds.Width;
Height = (int)RendererHost.Bounds.Height;
@@ -1073,7 +1098,7 @@ namespace Ryujinx.Ava
Device.Gpu.SetGpuThread();
Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
_renderer.Window.ChangeVSyncMode(Device.VSyncMode);
_renderer.Window.ChangeVSyncMode((Ryujinx.Graphics.GAL.VSyncMode)Device.VSyncMode);
while (_isActive)
{

View File

@@ -1,5 +1,6 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ryu="clr-namespace:Ryujinx.Ava">
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
@@ -52,5 +53,22 @@
<Color x:Key="Unbounded">#FFFF4554</Color>
<Color x:Key="Custom">#6483F5</Color>
</ResourceDictionary>
<ResourceDictionary x:Key="{x:Static ryu:RyujinxApp.AmoledThemeVariant}">
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
Color="{DynamicResource DataGridSelectionColor}" />
<Color x:Key="ControlFillColorSecondary">#008AA8</Color>
<Color x:Key="DataGridSelectionColor">#FF00FABB</Color>
<Color x:Key="ThemeContentBackgroundColor">#FF000000</Color>
<Color x:Key="ThemeControlBorderColor">#2D000000</Color>
<Color x:Key="ThemeForegroundColor">#FFFFFFFF</Color>
<Color x:Key="MenuFlyoutPresenterBorderColor">#79000000</Color>
<Color x:Key="AppListBackgroundColor">#50000000</Color>
<Color x:Key="AppListHoverBackgroundColor">#40000000</Color>
<Color x:Key="SecondaryTextColor">#A0FFFFFF</Color>
<Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
<Color x:Key="Switch">#FF2EEAC9</Color>
<Color x:Key="Unbounded">#FFFF4554</Color>
<Color x:Key="Custom">#6483F5</Color>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
using Gommon;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.UI.Common.Configuration;
using System;
@@ -16,6 +17,7 @@ namespace Ryujinx.Ava.Common.Locale
private const string DefaultLanguageCode = "en_US";
private readonly Dictionary<LocaleKeys, string> _localeStrings;
private Dictionary<LocaleKeys, string> _localeDefaultStrings;
private readonly ConcurrentDictionary<LocaleKeys, object[]> _dynamicValues;
private string _localeLanguageCode;
@@ -25,6 +27,7 @@ namespace Ryujinx.Ava.Common.Locale
public LocaleManager()
{
_localeStrings = new Dictionary<LocaleKeys, string>();
_localeDefaultStrings = new Dictionary<LocaleKeys, string>();
_dynamicValues = new ConcurrentDictionary<LocaleKeys, object[]>();
Load();
@@ -35,6 +38,8 @@ namespace Ryujinx.Ava.Common.Locale
var localeLanguageCode = !string.IsNullOrEmpty(ConfigurationState.Instance.UI.LanguageCode.Value) ?
ConfigurationState.Instance.UI.LanguageCode.Value : CultureInfo.CurrentCulture.Name.Replace('-', '_');
// Load en_US as default, if the target language translation is missing or incomplete.
LoadDefaultLanguage();
LoadLanguage(localeLanguageCode);
// Save whatever we ended up with.
@@ -61,14 +66,26 @@ namespace Ryujinx.Ava.Common.Locale
}
catch
{
// If formatting the text failed,
// continue to the below line & return the text without formatting.
// If formatting failed use the default text instead.
if (_localeDefaultStrings.TryGetValue(key, out value))
try
{
return string.Format(value, dynamicValue);
}
catch
{
// If formatting the default text failed return the key.
return key.ToString();
}
}
return value;
}
return key.ToString(); // If the locale text doesn't exist return the key.
// If the locale doesn't contain the key return the default one.
return _localeDefaultStrings.TryGetValue(key, out string defaultValue)
? defaultValue
: key.ToString(); // If the locale text doesn't exist return the key.
}
set
{
@@ -92,11 +109,16 @@ namespace Ryujinx.Ava.Common.Locale
{
_dynamicValues[key] = values;
OnPropertyChanged("Translation");
OnPropertyChanged("Item");
return this[key];
}
private void LoadDefaultLanguage()
{
_localeDefaultStrings = LoadJsonLanguage(DefaultLanguageCode);
}
public void LoadLanguage(string languageCode)
{
var locale = LoadJsonLanguage(languageCode);
@@ -104,7 +126,7 @@ namespace Ryujinx.Ava.Common.Locale
if (locale == null)
{
_localeLanguageCode = DefaultLanguageCode;
locale = LoadJsonLanguage(_localeLanguageCode);
locale = _localeDefaultStrings;
}
else
{
@@ -116,13 +138,17 @@ namespace Ryujinx.Ava.Common.Locale
_localeStrings[key] = val;
}
OnPropertyChanged("Translation");
OnPropertyChanged("Item");
LocaleChanged?.Invoke();
}
#nullable enable
private static LocalesJson? _localeData;
#nullable disable
private static Dictionary<LocaleKeys, string> LoadJsonLanguage(string languageCode)
{
var localeStrings = new Dictionary<LocaleKeys, string>();
@@ -132,29 +158,18 @@ namespace Ryujinx.Ava.Common.Locale
foreach (LocalesEntry locale in _localeData.Value.Locales)
{
if (locale.Translations.Count < _localeData.Value.Languages.Count)
if (locale.Translations.Count != _localeData.Value.Languages.Count)
{
throw new Exception($"Locale key {{{locale.ID}}} is missing languages! Has {locale.Translations.Count} translations, expected {_localeData.Value.Languages.Count}!");
}
if (locale.Translations.Count > _localeData.Value.Languages.Count)
{
throw new Exception($"Locale key {{{locale.ID}}} has too many languages! Has {locale.Translations.Count} translations, expected {_localeData.Value.Languages.Count}!");
}
if (!Enum.TryParse<LocaleKeys>(locale.ID, out var localeKey))
continue;
var str = locale.Translations.TryGetValue(languageCode, out string val) && !string.IsNullOrEmpty(val)
localeStrings[localeKey] =
locale.Translations.TryGetValue(languageCode, out string val) && val != string.Empty
? val
: locale.Translations[DefaultLanguageCode];
if (string.IsNullOrEmpty(str))
{
throw new Exception($"Locale key '{locale.ID}' has no valid translations for desired language {languageCode}! {DefaultLanguageCode} is an empty string or null");
}
localeStrings[localeKey] = str;
}
return localeStrings;

View File

@@ -14,7 +14,7 @@ namespace Ryujinx.Ava.Common.Markup
{
internal abstract class BasicMarkupExtension<T> : MarkupExtension
{
public abstract string Name { get; }
public virtual string Name => "Item";
public virtual Action<object, T?>? Setter => null;
protected abstract T? Value { get; }

View File

@@ -6,19 +6,16 @@ namespace Ryujinx.Ava.Common.Markup
{
internal class IconExtension(string iconString) : BasicMarkupExtension<Icon>
{
public override string Name => "Icon";
protected override Icon Value => new() { Value = iconString };
}
internal class SpinningIconExtension(string iconString) : BasicMarkupExtension<Icon>
{
public override string Name => "SIcon";
protected override Icon Value => new() { Value = iconString, Animation = IconAnimation.Spin };
}
internal class LocaleExtension(LocaleKeys key) : BasicMarkupExtension<string>
{
public override string Name => "Translation";
protected override string Value => LocaleManager.Instance[key];
protected override void ConfigureBindingExtension(CompiledBindingExtension bindingExtension)

View File

@@ -1,367 +0,0 @@
using DiscordRPC;
using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.SDL2;
using Ryujinx.Ava;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.Multithreading;
using Ryujinx.Graphics.Metal;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.Graphics.Vulkan;
using Ryujinx.HLE;
using Ryujinx.Input;
using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration;
using Silk.NET.Vulkan;
using System;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
using Key = Ryujinx.Common.Configuration.Hid.Key;
namespace Ryujinx.Headless
{
public partial class HeadlessRyujinx
{
public static void Initialize()
{
// Ensure Discord presence timestamp begins at the absolute start of when Ryujinx is launched
DiscordIntegrationModule.StartedAt = Timestamps.Now;
// Delete backup files after updating.
Task.Run(Updater.CleanupUpdate);
// Hook unhandled exception and process exit events.
AppDomain.CurrentDomain.UnhandledException += (sender, e)
=> Program.ProcessUnhandledException(sender, e.ExceptionObject as Exception, e.IsTerminating);
AppDomain.CurrentDomain.ProcessExit += (_, _) => Program.Exit();
// Initialize the configuration.
ConfigurationState.Initialize();
// Initialize Discord integration.
DiscordIntegrationModule.Initialize();
// Logging system information.
Program.PrintSystemInfo();
}
private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index)
{
if (inputId == null)
{
if (index == PlayerIndex.Player1)
{
Logger.Info?.Print(LogClass.Application, $"{index} not configured, defaulting to default keyboard.");
// Default to keyboard
inputId = "0";
}
else
{
Logger.Info?.Print(LogClass.Application, $"{index} not configured");
return null;
}
}
IGamepad gamepad = _inputManager.KeyboardDriver.GetGamepad(inputId);
bool isKeyboard = true;
if (gamepad == null)
{
gamepad = _inputManager.GamepadDriver.GetGamepad(inputId);
isKeyboard = false;
if (gamepad == null)
{
Logger.Error?.Print(LogClass.Application, $"{index} gamepad not found (\"{inputId}\")");
return null;
}
}
string gamepadName = gamepad.Name;
gamepad.Dispose();
InputConfig config;
if (inputProfileName == null || inputProfileName.Equals("default"))
{
if (isKeyboard)
{
config = new StandardKeyboardInputConfig
{
Version = InputConfig.CurrentVersion,
Backend = InputBackendType.WindowKeyboard,
Id = null,
ControllerType = ControllerType.JoyconPair,
LeftJoycon = new LeftJoyconCommonConfig<Key>
{
DpadUp = Key.Up,
DpadDown = Key.Down,
DpadLeft = Key.Left,
DpadRight = Key.Right,
ButtonMinus = Key.Minus,
ButtonL = Key.E,
ButtonZl = Key.Q,
ButtonSl = Key.Unbound,
ButtonSr = Key.Unbound,
},
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
{
StickUp = Key.W,
StickDown = Key.S,
StickLeft = Key.A,
StickRight = Key.D,
StickButton = Key.F,
},
RightJoycon = new RightJoyconCommonConfig<Key>
{
ButtonA = Key.Z,
ButtonB = Key.X,
ButtonX = Key.C,
ButtonY = Key.V,
ButtonPlus = Key.Plus,
ButtonR = Key.U,
ButtonZr = Key.O,
ButtonSl = Key.Unbound,
ButtonSr = Key.Unbound,
},
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
{
StickUp = Key.I,
StickDown = Key.K,
StickLeft = Key.J,
StickRight = Key.L,
StickButton = Key.H,
},
};
}
else
{
bool isNintendoStyle = gamepadName.Contains("Nintendo");
config = new StandardControllerInputConfig
{
Version = InputConfig.CurrentVersion,
Backend = InputBackendType.GamepadSDL2,
Id = null,
ControllerType = ControllerType.JoyconPair,
DeadzoneLeft = 0.1f,
DeadzoneRight = 0.1f,
RangeLeft = 1.0f,
RangeRight = 1.0f,
TriggerThreshold = 0.5f,
LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId>
{
DpadUp = ConfigGamepadInputId.DpadUp,
DpadDown = ConfigGamepadInputId.DpadDown,
DpadLeft = ConfigGamepadInputId.DpadLeft,
DpadRight = ConfigGamepadInputId.DpadRight,
ButtonMinus = ConfigGamepadInputId.Minus,
ButtonL = ConfigGamepadInputId.LeftShoulder,
ButtonZl = ConfigGamepadInputId.LeftTrigger,
ButtonSl = ConfigGamepadInputId.Unbound,
ButtonSr = ConfigGamepadInputId.Unbound,
},
LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
{
Joystick = ConfigStickInputId.Left,
StickButton = ConfigGamepadInputId.LeftStick,
InvertStickX = false,
InvertStickY = false,
Rotate90CW = false,
},
RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
{
ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B,
ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A,
ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y,
ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X,
ButtonPlus = ConfigGamepadInputId.Plus,
ButtonR = ConfigGamepadInputId.RightShoulder,
ButtonZr = ConfigGamepadInputId.RightTrigger,
ButtonSl = ConfigGamepadInputId.Unbound,
ButtonSr = ConfigGamepadInputId.Unbound,
},
RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
{
Joystick = ConfigStickInputId.Right,
StickButton = ConfigGamepadInputId.RightStick,
InvertStickX = false,
InvertStickY = false,
Rotate90CW = false,
},
Motion = new StandardMotionConfigController
{
MotionBackend = MotionInputBackendType.GamepadDriver,
EnableMotion = true,
Sensitivity = 100,
GyroDeadzone = 1,
},
Rumble = new RumbleConfigController
{
StrongRumble = 1f,
WeakRumble = 1f,
EnableRumble = false,
},
};
}
}
else
{
string profileBasePath;
if (isKeyboard)
{
profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "keyboard");
}
else
{
profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "controller");
}
string path = Path.Combine(profileBasePath, inputProfileName + ".json");
if (!File.Exists(path))
{
Logger.Error?.Print(LogClass.Application, $"Input profile \"{inputProfileName}\" not found for \"{inputId}\"");
return null;
}
try
{
config = JsonHelper.DeserializeFromFile(path, _serializerContext.InputConfig);
}
catch (JsonException)
{
Logger.Error?.Print(LogClass.Application, $"Input profile \"{inputProfileName}\" parsing failed for \"{inputId}\"");
return null;
}
}
config.Id = inputId;
config.PlayerIndex = index;
string inputTypeName = isKeyboard ? "Keyboard" : "Gamepad";
Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} configured with {inputTypeName} \"{config.Id}\"");
// If both stick ranges are 0 (usually indicative of an outdated profile load) then both sticks will be set to 1.0.
if (config is StandardControllerInputConfig controllerConfig)
{
if (controllerConfig.RangeLeft <= 0.0f && controllerConfig.RangeRight <= 0.0f)
{
controllerConfig.RangeLeft = 1.0f;
controllerConfig.RangeRight = 1.0f;
Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} stick range reset. Save the profile now to update your configuration");
}
}
return config;
}
private static IRenderer CreateRenderer(Options options, WindowBase window)
{
if (options.GraphicsBackend == GraphicsBackend.Vulkan && window is VulkanWindow vulkanWindow)
{
string preferredGpuId = string.Empty;
Vk api = Vk.GetApi();
if (!string.IsNullOrEmpty(options.PreferredGPUVendor))
{
string preferredGpuVendor = options.PreferredGPUVendor.ToLowerInvariant();
var devices = VulkanRenderer.GetPhysicalDevices(api);
foreach (var device in devices)
{
if (device.Vendor.ToLowerInvariant() == preferredGpuVendor)
{
preferredGpuId = device.Id;
break;
}
}
}
return new VulkanRenderer(
api,
(instance, vk) => new SurfaceKHR((ulong)(vulkanWindow.CreateWindowSurface(instance.Handle))),
vulkanWindow.GetRequiredInstanceExtensions,
preferredGpuId);
}
if (options.GraphicsBackend == GraphicsBackend.Metal && window is MetalWindow metalWindow && OperatingSystem.IsMacOS())
{
return new MetalRenderer(metalWindow.GetLayer);
}
return new OpenGLRenderer();
}
private static Switch InitializeEmulationContext(WindowBase window, IRenderer renderer, Options options)
{
BackendThreading threadingMode = options.BackendThreading;
bool threadedGAL = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading);
if (threadedGAL)
{
renderer = new ThreadedRenderer(renderer);
}
HLEConfiguration configuration = new(_virtualFileSystem,
_libHacHorizonManager,
_contentManager,
_accountManager,
_userChannelPersistence,
renderer,
new SDL2HardwareDeviceDriver(),
options.DramSize,
window,
options.SystemLanguage,
options.SystemRegion,
options.VSyncMode,
!options.DisableDockedMode,
!options.DisablePTC,
options.EnableInternetAccess,
!options.DisableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None,
options.FsGlobalAccessLogMode,
options.SystemTimeOffset,
options.SystemTimeZone,
options.MemoryManagerMode,
options.IgnoreMissingServices,
options.AspectRatio,
options.AudioVolume,
options.UseHypervisor ?? true,
options.MultiplayerLanInterfaceId,
Common.Configuration.Multiplayer.MultiplayerMode.Disabled,
false,
string.Empty,
string.Empty,
options.CustomVSyncInterval);
return new Switch(configuration);
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -14,7 +14,6 @@ using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Graphics.Vulkan.MoltenVK;
using Ryujinx.Headless;
using Ryujinx.SDL2.Common;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common;
@@ -54,12 +53,6 @@ namespace Ryujinx.Ava
PreviewerDetached = true;
if (args.Length > 0 && args[0] is "--no-gui" or "nogui")
{
HeadlessRyujinx.Entrypoint(args[1..]);
return 0;
}
Initialize(args);
LoggerAdapter.Register();
@@ -230,7 +223,7 @@ namespace Ryujinx.Ava
UseHardwareAcceleration = CommandLineState.OverrideHardwareAcceleration.Value;
}
internal static void PrintSystemInfo()
private static void PrintSystemInfo()
{
Logger.Notice.Print(LogClass.Application, $"{RyujinxApp.FullAppName} Version: {Version}");
SystemInfo.Gather().Print();
@@ -247,7 +240,7 @@ namespace Ryujinx.Ava
: $"Launch Mode: {AppDataManager.Mode}");
}
internal static void ProcessUnhandledException(object sender, Exception ex, bool isTerminating)
private static void ProcessUnhandledException(object sender, Exception ex, bool isTerminating)
{
Logger.Log log = Logger.Error ?? Logger.Notice;
string message = $"Unhandled exception caught: {ex}";
@@ -262,7 +255,7 @@ namespace Ryujinx.Ava
Exit();
}
internal static void Exit()
public static void Exit()
{
DiscordIntegrationModule.Exit();

View File

@@ -47,7 +47,6 @@
<PackageReference Include="Avalonia.Markup.Xaml.Loader" />
<PackageReference Include="Avalonia.Svg" />
<PackageReference Include="Avalonia.Svg.Skia" />
<PackageReference Include="CommandLineParser" />
<PackageReference Include="DynamicData" />
<PackageReference Include="FluentAvaloniaUI" />
<PackageReference Include="Projektanker.Icons.Avalonia" />
@@ -67,7 +66,6 @@
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Metal\Ryujinx.Graphics.Metal.csproj" />
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
<ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" />
@@ -76,6 +74,7 @@
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" />
<ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" />
<ProjectReference Include="..\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj" />
<ProjectReference Include="..\Ryujinx.UI.LocaleGenerator\Ryujinx.UI.LocaleGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
@@ -134,15 +133,8 @@
<EmbeddedResource Include="Assets\Icons\Controller_JoyConPair.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConRight.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_ProCon.svg" />
<EmbeddedResource Include="Headless\Ryujinx.bmp" LogicalName="HeadlessLogo" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="Assets\locales.json" />
</ItemGroup>
<ItemGroup>
<Compile Update="UI\Views\Settings\SettingsHacksView.axaml.cs">
<DependentUpon>SettingsHacksView.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
</Project>

View File

@@ -98,6 +98,9 @@ namespace Ryujinx.Ava
private void ThemeChanged_Event(object _, ReactiveEventArgs<string> rArgs) => ApplyConfiguredTheme(rArgs.NewValue);
public static readonly ThemeVariant AmoledThemeVariant = new("Amoled", ThemeVariant.Dark);
public void ApplyConfiguredTheme(string baseStyle)
{
try
@@ -116,6 +119,7 @@ namespace Ryujinx.Ava
"Auto" => DetectSystemTheme(),
"Light" => ThemeVariant.Light,
"Dark" => ThemeVariant.Dark,
"AMOLED" => AmoledThemeVariant,
_ => ThemeVariant.Default,
};
}

View File

@@ -55,6 +55,7 @@
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ClipToBounds="True"
Background="{DynamicResource ThemeControlBorderColor}"
CornerRadius="5">
<Grid ColumnDefinitions="Auto,10,*,150,100">
<Image
@@ -105,6 +106,7 @@
Click="IdString_OnClick"
HorizontalContentAlignment="Left"
VerticalAlignment="Center"
Background="{DynamicResource AppListBackgroundColor}"
Margin="-1, 0, 0, 0"
Padding="0" >

View File

@@ -1,19 +0,0 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
namespace Ryujinx.Ava.UI.Controls
{
public class MiniVerticalSeparator : Border
{
public MiniVerticalSeparator()
{
Width = 2;
Height = 12;
Margin = new Thickness();
BorderBrush = Brushes.Gray;
Background = Brushes.Gray;
BorderThickness = new Thickness(1);
}
}
}

View File

@@ -19,7 +19,7 @@ namespace Ryujinx.Ava.UI.Helpers
AvaLogger.Sink = new LoggerAdapter();
}
private static RyuLogger.Log? GetLog(AvaLogLevel level, string area)
private static RyuLogger.Log? GetLog(AvaLogLevel level)
{
return level switch
{
@@ -27,7 +27,7 @@ namespace Ryujinx.Ava.UI.Helpers
AvaLogLevel.Debug => RyuLogger.Debug,
AvaLogLevel.Information => RyuLogger.Debug,
AvaLogLevel.Warning => RyuLogger.Debug,
AvaLogLevel.Error => area is "IME" ? RyuLogger.Debug : RyuLogger.Error,
AvaLogLevel.Error => RyuLogger.Error,
AvaLogLevel.Fatal => RyuLogger.Error,
_ => throw new ArgumentOutOfRangeException(nameof(level), level, null),
};
@@ -35,17 +35,17 @@ namespace Ryujinx.Ava.UI.Helpers
public bool IsEnabled(AvaLogLevel level, string area)
{
return GetLog(level, area) != null;
return GetLog(level) != null;
}
public void Log(AvaLogLevel level, string area, object source, string messageTemplate)
{
GetLog(level, area)?.PrintMsg(RyuLogClass.UI, Format(level, area, messageTemplate, source, null));
GetLog(level)?.PrintMsg(RyuLogClass.UI, Format(level, area, messageTemplate, source, null));
}
public void Log(AvaLogLevel level, string area, object source, string messageTemplate, params object[] propertyValues)
{
GetLog(level, area)?.PrintMsg(RyuLogClass.UI, Format(level, area, messageTemplate, source, propertyValues));
GetLog(level)?.PrintMsg(RyuLogClass.UI, Format(level, area, messageTemplate, source, propertyValues));
}
private static string Format(AvaLogLevel level, string area, string template, object source, object[] v)

View File

@@ -1,7 +1,6 @@
using Avalonia;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Gommon;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.UI.Common.Models;
using System;
@@ -33,11 +32,11 @@ namespace Ryujinx.Ava.UI.Helpers
if (app.CurrentSavingsB < app.PotentialSavingsB)
{
return LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleXCICanSaveLabel, ((app.PotentialSavingsB - app.CurrentSavingsB) / _bytesPerMB).CoerceAtLeast(0));
return LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleXCICanSaveLabel, (app.PotentialSavingsB - app.CurrentSavingsB) / _bytesPerMB);
}
else
{
return LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleXCISavingLabel, (app.CurrentSavingsB / _bytesPerMB).CoerceAtLeast(0));
return LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleXCISavingLabel, app.CurrentSavingsB / _bytesPerMB);
}
}

View File

@@ -1,4 +1,3 @@
using Gommon;
using LibHac.Fs;
using LibHac.Ncm;
using Ryujinx.Ava.UI.ViewModels;
@@ -48,7 +47,7 @@ namespace Ryujinx.Ava.UI.Models
TitleId = info.ProgramId;
UserId = info.UserId;
var appData = RyujinxApp.MainWindow.ViewModel.Applications.FirstOrDefault(x => x.IdString.EqualsIgnoreCase(TitleIdString));
var appData = MainWindow.MainWindowViewModel.Applications.FirstOrDefault(x => x.IdString.Equals(TitleIdString, StringComparison.OrdinalIgnoreCase));
InGameList = appData != null;

View File

@@ -1,7 +1,6 @@
using Avalonia;
using Avalonia.Controls;
using Gommon;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.UI.Common.Configuration;
@@ -32,6 +31,15 @@ namespace Ryujinx.Ava.UI.Renderer
Initialize();
}
public static readonly string[] KnownGreatMetalTitles =
[
"01006A800016E000", // Smash Ultimate
"0100000000010000", // Super Mario Odyessy
"01008C0016544000", // Sea of Stars
"01005CA01580E000", // Persona 5
"010028600EBDA000", // Mario 3D World
];
public GraphicsBackend Backend =>
EmbeddedWindow switch
{
@@ -45,8 +53,16 @@ namespace Ryujinx.Ava.UI.Renderer
{
InitializeComponent();
switch (TitleIDs.SelectGraphicsBackend(titleId, ConfigurationState.Instance.Graphics.GraphicsBackend))
switch (ConfigurationState.Instance.Graphics.GraphicsBackend.Value)
{
case GraphicsBackend.Auto:
EmbeddedWindow =
OperatingSystem.IsMacOS() &&
RuntimeInformation.ProcessArchitecture == Architecture.Arm64 &&
KnownGreatMetalTitles.ContainsIgnoreCase(titleId)
? new EmbeddedWindowMetal()
: new EmbeddedWindowVulkan();
break;
case GraphicsBackend.OpenGl:
EmbeddedWindow = new EmbeddedWindowOpenGL();
break;

View File

@@ -1,4 +1,3 @@
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
@@ -12,14 +11,5 @@ namespace Ryujinx.Ava.UI.ViewModels
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void OnPropertiesChanged(string firstPropertyName, params ReadOnlySpan<string> propertyNames)
{
OnPropertyChanged(firstPropertyName);
foreach (var propertyName in propertyNames)
{
OnPropertyChanged(propertyName);
}
}
}
}

View File

@@ -9,7 +9,6 @@ using Avalonia.Threading;
using DynamicData;
using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
using Gommon;
using LibHac.Common;
using LibHac.Ns;
using Ryujinx.Ava.Common;
@@ -126,14 +125,13 @@ namespace Ryujinx.Ava.UI.ViewModels
public ApplicationData ListSelectedApplication;
public ApplicationData GridSelectedApplication;
// Key is Title ID
public SafeDictionary<string, LdnGameData.Array> LdnData = [];
public IEnumerable<LdnGameData> LastLdnGameData;
// The UI specifically uses a thicker bordered variant of the icon to avoid crunching out the border at lower resolutions.
// For an example of this, download canary 1.2.95, then open the settings menu, and look at the icon in the top-left.
// The border gets reduced to colored pixels in the 4 corners.
public static readonly Bitmap IconBitmap =
new(Assembly.GetAssembly(typeof(ConfigurationState))!.GetManifestResourceStream("Ryujinx.UI.Common.Resources.Logo_Ryujinx_AntiAlias.png")!);
new(Assembly.GetAssembly(typeof(ConfigurationState))!.GetManifestResourceStream("Ryujinx.UI.Common.Resources.Logo_Thiccjinx.png")!);
public MainWindow Window { get; init; }

View File

@@ -1,7 +1,6 @@
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Threading;
using Gommon;
using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.OpenAL;
using Ryujinx.Audio.Backends.SDL2;
@@ -63,9 +62,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private int _networkInterfaceIndex;
private int _multiplayerModeIndex;
private string _ldnPassphrase;
private string _ldnServer;
private bool _xc2MenuSoftlockFix = ConfigurationState.Instance.Hacks.Xc2MenuSoftlockFix;
private string _LdnServer;
public int ResolutionScale
{
@@ -74,7 +71,8 @@ namespace Ryujinx.Ava.UI.ViewModels
{
_resolutionScale = value;
OnPropertiesChanged(nameof(CustomResolutionScale), nameof(IsCustomResolutionScaleActive));
OnPropertyChanged(nameof(CustomResolutionScale));
OnPropertyChanged(nameof(IsCustomResolutionScaleActive));
}
}
@@ -165,7 +163,9 @@ namespace Ryujinx.Ava.UI.ViewModels
get => _vSyncMode;
set
{
if (value is VSyncMode.Custom or VSyncMode.Switch or VSyncMode.Unbounded)
if (value == VSyncMode.Custom ||
value == VSyncMode.Switch ||
value == VSyncMode.Unbounded)
{
_vSyncMode = value;
OnPropertyChanged();
@@ -181,9 +181,8 @@ namespace Ryujinx.Ava.UI.ViewModels
int newInterval = (int)((value / 100f) * 60);
_customVSyncInterval = newInterval;
_customVSyncIntervalPercentageProxy = value;
OnPropertiesChanged(
nameof(CustomVSyncInterval),
nameof(CustomVSyncIntervalPercentageText));
OnPropertyChanged((nameof(CustomVSyncInterval)));
OnPropertyChanged((nameof(CustomVSyncIntervalPercentageText)));
}
}
@@ -191,7 +190,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
get
{
string text = CustomVSyncIntervalPercentageProxy + "%";
string text = CustomVSyncIntervalPercentageProxy.ToString() + "%";
return text;
}
}
@@ -222,9 +221,8 @@ namespace Ryujinx.Ava.UI.ViewModels
_customVSyncInterval = value;
int newPercent = (int)((value / 60f) * 100);
_customVSyncIntervalPercentageProxy = newPercent;
OnPropertiesChanged(
nameof(CustomVSyncIntervalPercentageProxy),
nameof(CustomVSyncIntervalPercentageText));
OnPropertyChanged(nameof(CustomVSyncIntervalPercentageProxy));
OnPropertyChanged(nameof(CustomVSyncIntervalPercentageText));
OnPropertyChanged();
}
}
@@ -259,8 +257,6 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool UseHypervisor { get; set; }
public bool DisableP2P { get; set; }
public bool ShowDirtyHacks => ConfigurationState.Instance.Hacks.ShowDirtyHacks;
public string TimeZone { get; set; }
public string ShaderDumpPath { get; set; }
@@ -277,17 +273,6 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public bool Xc2MenuSoftlockFixEnabled
{
get => _xc2MenuSoftlockFix;
set
{
_xc2MenuSoftlockFix = value;
OnPropertyChanged();
}
}
public int Language { get; set; }
public int Region { get; set; }
public int FsGlobalAccessLogMode { get; set; }
@@ -388,10 +373,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public string LdnServer
{
get => _ldnServer;
get => _LdnServer;
set
{
_ldnServer = value;
_LdnServer = value;
OnPropertyChanged();
}
}
@@ -562,6 +547,7 @@ namespace Ryujinx.Ava.UI.ViewModels
"Auto" => 0,
"Light" => 1,
"Dark" => 2,
"AMOLED" => 3,
_ => 0
};
@@ -671,6 +657,7 @@ namespace Ryujinx.Ava.UI.ViewModels
0 => "Auto",
1 => "Light",
2 => "Dark",
3 => "AMOLED",
_ => "Auto"
};
@@ -761,13 +748,10 @@ namespace Ryujinx.Ava.UI.ViewModels
config.Multiplayer.LdnPassphrase.Value = LdnPassphrase;
config.Multiplayer.LdnServer.Value = LdnServer;
// Dirty Hacks
config.Hacks.Xc2MenuSoftlockFix.Value = Xc2MenuSoftlockFixEnabled;
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
MainWindow.UpdateGraphicsConfig();
RyujinxApp.MainWindow.ViewModel.VSyncModeSettingChanged();
MainWindow.MainWindowViewModel.VSyncModeSettingChanged();
SaveSettingsEvent?.Invoke();
@@ -796,18 +780,5 @@ namespace Ryujinx.Ava.UI.ViewModels
RevertIfNotSaved();
CloseWindow?.Invoke();
}
public static string Xc2MenuFixTooltip { get; } = Lambda.String(sb =>
{
sb.AppendLine(
"This fix applies a 2ms delay (via 'Thread.Sleep(2)') every time the game tries to read data from the emulated Switch filesystem.")
.AppendLine();
sb.AppendLine("From the issue on GitHub:").AppendLine();
sb.Append(
"When clicking very fast from game main menu to 2nd submenu, " +
"there is a low chance that the game will softlock, " +
"the submenu won't show up, while background music is still there.");
});
}
}

View File

@@ -91,42 +91,39 @@ namespace Ryujinx.Ava.UI.ViewModels
private void SortingChanged()
{
OnPropertiesChanged(
nameof(IsSortedByName),
nameof(IsSortedBySaved),
nameof(SortingAscending),
nameof(SortingField),
nameof(SortingFieldName));
OnPropertyChanged(nameof(IsSortedByName));
OnPropertyChanged(nameof(IsSortedBySaved));
OnPropertyChanged(nameof(SortingAscending));
OnPropertyChanged(nameof(SortingField));
OnPropertyChanged(nameof(SortingFieldName));
SortAndFilter();
}
private void DisplayedChanged()
{
OnPropertiesChanged(nameof(Status), nameof(DisplayedXCIFiles), nameof(SelectedDisplayedXCIFiles));
OnPropertyChanged(nameof(Status));
OnPropertyChanged(nameof(DisplayedXCIFiles));
OnPropertyChanged(nameof(SelectedDisplayedXCIFiles));
}
private void ApplicationsChanged()
{
OnPropertiesChanged(
nameof(AllXCIFiles),
nameof(Status),
nameof(PotentialSavings),
nameof(ActualSavings),
nameof(CanTrim),
nameof(CanUntrim));
OnPropertyChanged(nameof(AllXCIFiles));
OnPropertyChanged(nameof(Status));
OnPropertyChanged(nameof(PotentialSavings));
OnPropertyChanged(nameof(ActualSavings));
OnPropertyChanged(nameof(CanTrim));
OnPropertyChanged(nameof(CanUntrim));
DisplayedChanged();
SortAndFilter();
}
private void SelectionChanged(bool displayedChanged = true)
{
OnPropertiesChanged(
nameof(Status),
nameof(CanTrim),
nameof(CanUntrim),
nameof(SelectedXCIFiles));
OnPropertyChanged(nameof(Status));
OnPropertyChanged(nameof(CanTrim));
OnPropertyChanged(nameof(CanUntrim));
OnPropertyChanged(nameof(SelectedXCIFiles));
if (displayedChanged)
OnPropertyChanged(nameof(SelectedDisplayedXCIFiles));
@@ -134,12 +131,11 @@ namespace Ryujinx.Ava.UI.ViewModels
private void ProcessingChanged()
{
OnPropertiesChanged(
nameof(Processing),
nameof(Cancel),
nameof(Status),
nameof(CanTrim),
nameof(CanUntrim));
OnPropertyChanged(nameof(Processing));
OnPropertyChanged(nameof(Cancel));
OnPropertyChanged(nameof(Status));
OnPropertyChanged(nameof(CanTrim));
OnPropertyChanged(nameof(CanUntrim));
}
private IEnumerable<XCITrimmerFileModel> GetSelectedDisplayedXCIFiles()
@@ -364,7 +360,7 @@ namespace Ryujinx.Ava.UI.ViewModels
value = _processingApplication.Value with { PercentageProgress = null };
if (value.HasValue)
_displayedXCIFiles.ReplaceWith(value);
_displayedXCIFiles.ReplaceWith(value.Value);
_processingApplication = value;
OnPropertyChanged();

View File

@@ -17,7 +17,8 @@
Margin="7, 0"
Height="25"
Width="25"
ToolTip.Tip="{Binding Title}" />
ToolTip.Tip="{Binding Title}"
Source="resm:Ryujinx.UI.Common.Resources.Logo_Thiccjinx.png?assembly=Ryujinx.UI.Common" />
<Menu
Name="Menu"
Height="32"

View File

@@ -3,6 +3,8 @@ using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Threading;
using Gommon;
using LibHac.Ncm;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
@@ -10,6 +12,8 @@ using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption;
using Ryujinx.HLE;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper;
@@ -29,7 +33,6 @@ namespace Ryujinx.Ava.UI.Views.Main
InitializeComponent();
RyuLogo.IsVisible = !ConfigurationState.Instance.ShowTitleBar;
RyuLogo.Source = MainWindowViewModel.IconBitmap;
ToggleFileTypesMenuItem.ItemsSource = GenerateToggleFileTypeItems();
ChangeLanguageMenuItem.ItemsSource = GenerateLanguageMenuItems();
@@ -121,13 +124,26 @@ namespace Ryujinx.Ava.UI.Views.Main
ViewModel.LoadConfigurableHotKeys();
}
public static readonly AppletMetadata MiiApplet = new("miiEdit", 0x0100000000001009);
public async void OpenMiiApplet(object sender, RoutedEventArgs e)
{
if (MiiApplet.CanStart(ViewModel.ContentManager, out var appData, out var nacpData))
const string AppletName = "miiEdit";
const ulong AppletProgramId = 0x0100000000001009;
const string AppletVersion = "1.0.0";
string contentPath = ViewModel.ContentManager.GetInstalledContentPath(AppletProgramId, StorageId.BuiltInSystem, NcaContentType.Program);
if (!string.IsNullOrEmpty(contentPath))
{
await ViewModel.LoadApplication(appData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen, nacpData);
ApplicationData applicationData = new()
{
Name = AppletName,
Id = AppletProgramId,
Path = contentPath
};
var nacpData = StructHelpers.CreateCustomNacpData(AppletName, AppletVersion);
await ViewModel.LoadApplication(applicationData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen, nacpData);
}
}

View File

@@ -132,7 +132,14 @@
</Flyout>
</Button.Flyout>
</Button>
<controls:MiniVerticalSeparator IsVisible="{Binding !ShowLoadProgress}" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
Background="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Name="DockedStatus"
Margin="5,0,5,0"
@@ -142,7 +149,14 @@
PointerReleased="DockedStatus_PointerReleased"
Text="{Binding DockedStatusText}"
TextAlignment="Start" />
<controls:MiniVerticalSeparator IsVisible="{Binding !ShowLoadProgress}" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
Background="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<SplitButton
Name="AspectRatioStatus"
Padding="5,0,5,0"
@@ -189,7 +203,14 @@
</MenuFlyout>
</SplitButton.Flyout>
</SplitButton>
<controls:MiniVerticalSeparator IsVisible="{Binding !ShowLoadProgress}" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
Background="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<ToggleSplitButton
Name="VolumeStatus"
Padding="5,0,5,0"
@@ -233,7 +254,14 @@
</Flyout>
</ToggleSplitButton.Flyout>
</ToggleSplitButton>
<controls:MiniVerticalSeparator IsVisible="{Binding !ShowLoadProgress}" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
Background="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Margin="5,0,5,0"
HorizontalAlignment="Left"
@@ -241,7 +269,14 @@
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding GameStatusText}"
TextAlignment="Start" />
<controls:MiniVerticalSeparator IsVisible="{Binding !ShowLoadProgress}" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
Background="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Margin="5,0,5,0"
HorizontalAlignment="Left"
@@ -263,7 +298,13 @@
VerticalAlignment="Center"
IsVisible="{Binding ShowShaderCompilationHint}"
Text="{Binding ShaderCountText}" />
<controls:MiniVerticalSeparator IsVisible="{Binding ShowShaderCompilationHint}" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding ShowShaderCompilationHint}" />
<TextBlock
Margin="5,0,5,0"
HorizontalAlignment="Left"
@@ -271,7 +312,14 @@
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding BackendText}"
TextAlignment="Start" />
<controls:MiniVerticalSeparator IsVisible="{Binding !ShowLoadProgress}" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
Background="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Margin="5,0,0,0"
HorizontalAlignment="Left"
@@ -286,7 +334,13 @@
VerticalAlignment="Center"
IsVisible="{Binding ShowFirmwareStatus}"
Orientation="Horizontal">
<controls:MiniVerticalSeparator IsVisible="{Binding IsGameRunning}" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding IsGameRunning}" />
<TextBlock
Name="FirmwareStatus"
Margin="5, 0, 0, 0"

View File

@@ -62,7 +62,12 @@ namespace Ryujinx.Ava.UI.Views.Main
// Change the volume by 5% at a time
float newValue = Window.ViewModel.Volume + (float)e.Delta.Y * 0.05f;
Window.ViewModel.Volume = Math.Clamp(newValue, 0, 1);
Window.ViewModel.Volume = newValue switch
{
< 0 => 0,
> 1 => 1,
_ => newValue,
};
e.Handled = true;
}

View File

@@ -1,48 +0,0 @@
<UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsHacksView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d"
x:DataType="viewModels:SettingsViewModel">
<Design.DataContext>
<viewModels:SettingsViewModel />
</Design.DataContext>
<ScrollViewer
Name="HacksPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border Classes="settings">
<StackPanel
Margin="10"
HorizontalAlignment="Center"
Orientation="Vertical"
Spacing="5">
<TextBlock
HorizontalAlignment="Center"
Classes="h1"
Text="Dirty Hacks" />
<TextBlock
Foreground="{DynamicResource SecondaryTextColor}"
TextDecorations="Underline"
Text="Game-specific hacks &amp; tricks to alleviate performance issues or crashing. May cause issues." />
<StackPanel
Margin="0,10,0,0"
Orientation="Horizontal"
HorizontalAlignment="Center"
ToolTip.Tip="{Binding Xc2MenuFixTooltip}">
<CheckBox
Margin="0"
IsChecked="{Binding Xc2MenuSoftlockFixEnabled}"/>
<TextBlock
VerticalAlignment="Center"
Text="Xenoblade Chronicles 2 Menu Softlock Fix" />
</StackPanel>
</StackPanel>
</Border>
</ScrollViewer>
</UserControl>

View File

@@ -1,17 +0,0 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.UI.Common.Configuration;
namespace Ryujinx.Ava.UI.Views.Settings
{
public partial class SettingsHacksView : UserControl
{
public SettingsViewModel ViewModel;
public SettingsHacksView()
{
InitializeComponent();
}
}
}

View File

@@ -77,6 +77,9 @@
<ComboBoxItem>
<TextBlock Text="{ext:Locale SettingsTabGeneralThemeDark}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="AMOLED" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
</StackPanel>

View File

@@ -39,6 +39,8 @@ namespace Ryujinx.Ava.UI.Windows
{
public partial class MainWindow : StyleableAppWindow
{
internal static MainWindowViewModel MainWindowViewModel { get; private set; }
public MainWindowViewModel ViewModel { get; }
internal readonly AvaHostUIHandler UiHandler;
@@ -74,7 +76,7 @@ namespace Ryujinx.Ava.UI.Windows
public MainWindow()
{
DataContext = ViewModel = new MainWindowViewModel
DataContext = ViewModel = MainWindowViewModel = new MainWindowViewModel
{
Window = this
};
@@ -89,15 +91,13 @@ namespace Ryujinx.Ava.UI.Windows
TitleBar.ExtendsContentIntoTitleBar = !ConfigurationState.Instance.ShowTitleBar;
TitleBar.TitleBarHitTestType = (ConfigurationState.Instance.ShowTitleBar) ? TitleBarHitTestType.Simple : TitleBarHitTestType.Complex;
// Correctly size window when 'TitleBar' is enabled (Nov. 14, 2024)
TitleBarHeight = (ConfigurationState.Instance.ShowTitleBar ? TitleBar.Height : 0);
// NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point.
StatusBarHeight = StatusBarView.StatusBar.MinHeight;
MenuBarHeight = MenuBar.MinHeight;
TitleBar.Height = MenuBarHeight;
// Correctly size window when 'TitleBar' is enabled (Nov. 14, 2024)
TitleBarHeight = (ConfigurationState.Instance.ShowTitleBar ? TitleBar.Height : 0);
ApplicationList.DataContext = DataContext;
ApplicationGrid.DataContext = DataContext;
@@ -167,31 +167,24 @@ namespace Ryujinx.Ava.UI.Windows
{
Dispatcher.UIThread.Post(() =>
{
var ldnGameDataArray = e.LdnData.ToList();
ViewModel.LdnData.Clear();
var ldnGameDataArray = e.LdnData;
ViewModel.LastLdnGameData = ldnGameDataArray;
foreach (var application in ViewModel.Applications)
{
ref var controlHolder = ref application.ControlHolder.Value;
ViewModel.LdnData[application.IdString] =
LdnGameData.GetArrayForApp(
ldnGameDataArray,
ref controlHolder
);
UpdateApplicationWithLdnData(application);
}
ViewModel.RefreshView();
});
}
private void UpdateApplicationWithLdnData(ApplicationData application)
{
if (application.HasControlHolder && ViewModel.LdnData.TryGetValue(application.IdString, out var ldnGameDatas))
if (application.ControlHolder.ByteSpan.Length > 0 && ViewModel.LastLdnGameData != null)
{
application.PlayerCount = ldnGameDatas.PlayerCount;
application.GameCount = ldnGameDatas.GameCount;
IEnumerable<LdnGameData> ldnGameData = ViewModel.LastLdnGameData.Where(game => application.ControlHolder.Value.LocalCommunicationId.Items.Contains(Convert.ToUInt64(game.TitleId, 16)));
application.PlayerCount = ldnGameData.Sum(game => game.PlayerCount);
application.GameCount = ldnGameData.Count();
}
else
{

View File

@@ -37,7 +37,6 @@
<settings:SettingsAudioView Name="AudioPage" />
<settings:SettingsNetworkView Name="NetworkPage" />
<settings:SettingsLoggingView Name="LoggingPage" />
<settings:SettingsHacksView Name="HacksPage" />
</Grid>
<ui:NavigationView
Grid.Row="1"
@@ -92,11 +91,6 @@
Content="{ext:Locale SettingsTabLogging}"
Tag="LoggingPage"
IconSource="Document" />
<ui:NavigationViewItem
IsVisible="{Binding ShowDirtyHacks}"
Content="Dirty Hacks"
Tag="HacksPage"
IconSource="Code" />
</ui:NavigationView.MenuItems>
<ui:NavigationView.Styles>
<Style Selector="Grid#PlaceholderGrid">

View File

@@ -86,10 +86,6 @@ namespace Ryujinx.Ava.UI.Windows
case nameof(LoggingPage):
NavPanel.Content = LoggingPage;
break;
case nameof(HacksPage):
HacksPage.ViewModel = ViewModel;
NavPanel.Content = HacksPage;
break;
default:
throw new NotImplementedException();
}