Compare commits

..

68 Commits

Author SHA1 Message Date
Evan Husted
af44726cec Merge branch 'master' into validation-v2 2024-12-28 05:51:19 -06:00
Evan Husted
09107b67ff misc: Remove GAL/Configuration duplicate enums 2024-12-28 05:08:21 -06:00
Evan Husted
12b264af44 Headless in Avalonia v2 (#448)
Launch the Ryujinx.exe, first argument --no-gui or nogui, and the rest of the arguments should be your normal headless script. You can include the new option --use-main-config which will provide any arguments that you don't, filled in from your main config made by the UI.

Input config is not inherited at this time.
2024-12-28 03:49:06 -06:00
Evan Husted
0c21b07f19 Update nightly_pr_comment.yml 2024-12-28 03:01:59 -06:00
Evan Husted
18625cf775 Update nightly_pr_comment.yml 2024-12-28 02:45:33 -06:00
heihei123456780
77a9246825 Updated zh-CN translation (#440) 2024-12-28 02:42:25 -06:00
Evan Husted
709eeda94a (try) fix PR build comments 2024-12-28 02:00:46 -06:00
Evan Husted
7d54424048 misc: Use selector fields 2024-12-27 21:31:46 -06:00
Evan Husted
664c63c6a8 metal: Bump SharpMetal to preview 21 2024-12-27 20:47:43 -06:00
LotP1
8efefeaaca slight refactor
isGitRunner is now a variable passed to all tasks.
Validation project can now accept more arguments, only the first argument is used at the moment.
Execute is no longer static, and you can now pass extra data to the task constructor if needed.
2024-12-28 02:16:17 +01:00
LotP1
4e56b1493f validate that line endings are LF on build
if running on git it will now fail if the file contains CRLF line endings and tell the user to run a local build to fix it.
2024-12-28 02:01:23 +01:00
LotP1
43f6e4a873 remove dupelicates 2024-12-28 01:58:35 +01:00
LotP1
55f6b95d8d Merge remote-tracking branch 'upstream/master' into validation-v2 2024-12-28 01:50:18 +01:00
Elijah Fronzak
153d1ef06b ru_RU locale update (#450)
Co-authored-by: Evan Husted <greem@greemdev.net>
2024-12-27 18:45:42 -06:00
LotP1
4b84c5d240 fix duplicates 2024-12-28 01:39:03 +01:00
LotP1
9f22fc49c8 Merge branch 'master' into validation-v2 2024-12-28 01:38:32 +01:00
LotP1
4f206d0e73 Merge remote-tracking branch 'upstream/master' into validation-v2 2024-12-28 01:35:52 +01:00
Daniel Nylander
e1e4e5d2d5 Swedish translation (#446)
Co-authored-by: LotP1 <68976644+LotP1@users.noreply.github.com>
2024-12-27 17:58:58 -06:00
Evan Husted
44ee4190e6 JAVASCRIPT LOL 2024-12-27 16:11:23 -06:00
Evan Husted
9408452f93 ok that one was my fault 2024-12-27 16:10:03 -06:00
Evan Husted
38833ff60a i dont know why this is failing this is stupid 2024-12-27 16:07:23 -06:00
Evan Husted
1faa72f22f infra: fix big tables in releases 2024-12-27 15:44:20 -06:00
Evan Husted
56e45ae648 misc: Collapse LdnGameDataArray into the main class as an inner class
- privated the constructor; only obtainable by the static helper on the main LdnGameData class.
- constructor logic now in the static helper; constructor just directly sets the data it's given.
2024-12-27 15:33:31 -06:00
Evan Husted
07074272ca Revert "UI: Directly proxy window properties on the view model back to the stored window"
This reverts commit 9754d247b5.
2024-12-27 15:24:57 -06:00
LotP1
e2e8502278 force line endings to use LF 2024-12-27 12:10:50 +01:00
Evan Husted
9eb273a0f7 Org rename (they call me indecisive) 2024-12-27 02:05:37 -06:00
Evan Husted
ccddaa77d1 infra: Org 2024-12-27 01:59:29 -06:00
Evan Husted
01c2e67334 lol 2024-12-27 01:41:48 -06:00
Evan Husted
4c646721d6 infra: Testing moving canary to the future home of this fork 2024-12-27 01:38:51 -06:00
Evan Husted
d3bc3a1081 UI: Simplify LDN data logic 2024-12-27 01:32:23 -06:00
Evan Husted
c69881a0a2 UI: chore: remove direct static MainWindowViewModel reference 2024-12-27 00:47:57 -06:00
Evan Husted
aa806b1032 Rename program.cs to Program.cs 2024-12-26 21:07:29 -06:00
LotP1
aabb786016 fix spelling and names 2024-12-26 23:42:53 +01:00
LotP1
2a38f1bbbf only log runtime if a runtime is set 2024-12-26 23:34:43 +01:00
LotP1
fdece62f88 fix matching windows runner 2024-12-26 23:32:14 +01:00
LotP1
6fb58a611e revert .gitignore 2024-12-26 23:28:34 +01:00
LotP1
02ff76f89a revert packages.props 2024-12-26 23:26:50 +01:00
LotP1
d39b3a5864 fix locales.json to comply with validation 2024-12-26 23:20:29 +01:00
LotP1
f976c3c362 simplify 2024-12-26 23:20:06 +01:00
LotP1
d9f2b813a0 Merge remote-tracking branch 'upstream/master' into validation-v2 2024-12-26 23:12:30 +01:00
LotP1
8cb6767c7c slash instead of backslash 2024-12-26 23:10:36 +01:00
LotP1
80377c1553 run with dotnet dll runner 2024-12-26 23:04:05 +01:00
LotP1
1dbcaf1fd7 missing quote, will still fail 2024-12-26 22:57:07 +01:00
LotP1
bad1f72918 v4 first test, will fail due to new validation step 2024-12-26 22:54:22 +01:00
LotP1
a317184594 check target dir 2024-12-26 21:34:07 +01:00
LotP1
0cde245875 forgot to remove true 2024-12-26 21:31:24 +01:00
LotP1
e3638d13b1 correct form of --runtime 2024-12-26 21:29:10 +01:00
LotP1
2fe19044cf use RuntimeIdentifier instead 2024-12-26 21:25:45 +01:00
LotP1
9320decc45 test RuntimeIdentifiers 2024-12-26 21:06:08 +01:00
LotP1
35b23bcafb try simplified csproj and force runtime identifier 2024-12-26 20:45:37 +01:00
LotP1
4add4829c7 force restore 2024-12-26 20:38:46 +01:00
LotP1
eb46909808 manually run dotnet restore 2024-12-26 20:29:56 +01:00
LotP1
c91d9d8049 build target fix 2024-12-26 20:18:29 +01:00
LotP1
21f23f0a2e lets see what's actually going on 2024-12-26 20:13:18 +01:00
LotP1
9ca0926439 confirm that it actually builds 2024-12-26 20:07:45 +01:00
LotP1
7b8c8776a2 Revert "use outdir"
This reverts commit b00d80c17a.
2024-12-26 20:06:07 +01:00
LotP1
b00d80c17a use outdir 2024-12-26 20:00:04 +01:00
LotP1
8c0a82bb6e v4 2024-12-26 19:54:02 +01:00
LotP1
1bac6ab19e please 2024-12-26 00:00:05 +01:00
LotP1
dac137fd43 test stuff 2024-12-25 23:47:20 +01:00
LotP1
d83d80de73 here we go 2024-12-25 23:22:38 +01:00
LotP1
097836bfac NO-BREAK SPACE is now escaped 2024-12-25 22:14:20 +01:00
LotP1
695f9e277d v3 2024-12-25 22:13:54 +01:00
LotP1
b19ee23c6b this won't work 2024-12-25 20:48:28 +01:00
LotP1
748e93ba65 lets give this another shot
moved from netstandard to netframework
2024-12-25 17:09:57 +01:00
LotP1
732a1af863 copying is not necessary anymore 2024-12-25 04:02:22 +01:00
LotP1
f538cee2e8 please don't fail 2024-12-25 03:56:59 +01:00
LotP1
4275a778aa update gitignore 2024-12-24 23:28:44 +01:00
55 changed files with 2250 additions and 1046 deletions

View File

@@ -64,14 +64,9 @@ 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
@@ -119,13 +114,6 @@ 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
@@ -171,20 +159,9 @@ 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: "GreemDev"
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "Ryubing"
RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO: "Ryujinx"
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "Ryujinx-Canary"
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "Canary-Releases"
RELEASE: 1
jobs:
@@ -43,8 +43,8 @@ jobs:
with:
script: |
github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
owner: "${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}",
repo: "${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_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,7 +116,6 @@ 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'
@@ -125,11 +124,6 @@ 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
@@ -140,12 +134,6 @@ 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)
@@ -191,21 +179,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/*AppImage*"
#artifacts: "release_output/*.tar.gz,release_output/*.zip,release_output/*AppImage*"
tag: ${{ steps.version_info.outputs.build_version }}
body: |
# Canary builds:
These builds are experimental and may sometimes not work, use [regular builds](https://github.com/GreemDev/Ryujinx/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/${{ github.repository }}/releases/latest) instead if that sounds like something you don't want to deal with.
| Platform | Artifact |
|--|--|
| 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 |
| 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) |
"**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
@@ -262,15 +250,11 @@ 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, publish_headless/*.tar.gz"
artifacts: "publish_ava/*.tar.gz"
tag: ${{ steps.version_info.outputs.build_version }}
body: ""
omitBodyDuringUpdate: true

View File

@@ -38,20 +38,16 @@ 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}](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)`;
hidden_debug_artifacts += `\n* [${art.name}](${url})`;
} else {
body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
body += `\n* [${art.name}](${url})`;
}
}
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: "GreemDev"
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "Ryubing"
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "Ryujinx"
RELEASE: 1
@@ -42,8 +42,8 @@ jobs:
with:
script: |
github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
owner: "${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}",
repo: "${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_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: |
# Regular builds:
# Stable builds:
| Platform | Artifact |
|--|--|
| 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) |
| 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) |
**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,7 +112,6 @@ 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'
@@ -121,11 +120,6 @@ 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)
@@ -172,11 +166,6 @@ 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
@@ -186,15 +175,15 @@ jobs:
artifacts: "release_output/*.tar.gz,release_output/*.zip,release_output/*AppImage*"
tag: ${{ steps.version_info.outputs.build_version }}
body: |
# Regular builds:
# Stable builds:
| Platform | Artifact |
|--|--|
| 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/${{ github.repository }}/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}"
| 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) |
**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
allowUpdates: true
replacesArtifacts: true
@@ -251,15 +240,11 @@ 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, publish_headless/*.tar.gz"
artifacts: "publish/*.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-preview20" />
<PackageVersion Include="SharpMetal" Version="1.0.0-preview21" />
<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,8 +57,6 @@ 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}"
@@ -213,10 +211,6 @@ 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
@@ -261,13 +255,12 @@ Global
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.Build.0 = Release|Any CPU
{4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C08931FA-1191-417A-864F-3882D93E683B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{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
{4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Release|Any CPU.ActiveCfg = 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

View File

@@ -1,73 +0,0 @@
using System;
using Microsoft.Build.Utilities;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using Newtonsoft.Json;
using Microsoft.Build.Framework;
namespace Ryujinx.BuildValidationTasks
{
public class LocaleValidationTask : Task
{
public override bool Execute()
{
string path = System.Reflection.Assembly.GetExecutingAssembly().Location;
if (path.Split(["src"], StringSplitOptions.None).Length == 1)
{
//i assume that we are in a build directory in the solution dir
path = new FileInfo(path).Directory!.Parent!.GetDirectories("src")[0].GetDirectories("Ryujinx")[0].GetDirectories("Assets")[0].GetFiles("locales.json")[0].FullName;
}
else
{
path = path.Split(["src"], StringSplitOptions.None)[0];
path = new FileInfo(path).Directory!.GetDirectories("src")[0].GetDirectories("Ryujinx")[0].GetDirectories("Assets")[0].GetFiles("locales.json")[0].FullName;
}
string data;
using (StreamReader sr = new(path))
{
data = sr.ReadToEnd();
}
LocalesJson json = JsonConvert.DeserializeObject<LocalesJson>(data);
for (int i = 0; i < json.Locales.Count; i++)
{
LocalesEntry locale = json.Locales[i];
foreach (string langCode in json.Languages.Where(it => !locale.Translations.ContainsKey(it)))
{
locale.Translations.Add(langCode, string.Empty);
Log.LogMessage(MessageImportance.High, $"Added '{langCode}' to Locale '{locale.ID}'");
}
locale.Translations = locale.Translations.OrderBy(pair => pair.Key).ToDictionary(pair => pair.Key, pair => pair.Value);
json.Locales[i] = locale;
}
string jsonString = JsonConvert.SerializeObject(json, Formatting.Indented);
using (StreamWriter sw = new(path))
{
sw.Write(jsonString);
}
return true;
}
struct LocalesJson
{
public List<string> Languages { get; set; }
public List<LocalesEntry> Locales { get; set; }
}
struct LocalesEntry
{
public string ID { get; set; }
public Dictionary<string, string> Translations { get; set; }
}
}
}

View File

@@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text.Json;
using System.Text.Encodings.Web;
namespace Ryujinx.BuildValidationTasks
{
public class LocalesValidationTask : ValidationTask
{
public LocalesValidationTask() { }
public bool Execute(string projectPath, bool isGitRunner)
{
Console.WriteLine("Running Locale Validation Task...");
string path = projectPath + "src/Ryujinx/Assets/locales.json";
string data;
using (StreamReader sr = new(path))
{
data = sr.ReadToEnd();
}
LocalesJson json;
if (isGitRunner && data.Contains("\r\n"))
throw new FormatException("locales.json is using CRLF line endings! It should be using LF line endings, build locally to fix...");
try
{
json = JsonSerializer.Deserialize<LocalesJson>(data);
}
catch (JsonException e)
{
throw new JsonException(e.Message); //shorter and easier stacktrace
}
bool encounteredIssue = false;
for (int i = 0; i < json.Locales.Count; i++)
{
LocalesEntry locale = json.Locales[i];
foreach (string langCode in json.Languages.Where(lang => !locale.Translations.ContainsKey(lang)))
{
encounteredIssue = true;
if (!isGitRunner)
{
locale.Translations.Add(langCode, string.Empty);
Console.WriteLine($"Added '{langCode}' to Locale '{locale.ID}'");
}
else
{
Console.WriteLine($"Missing '{langCode}' in Locale '{locale.ID}'!");
}
}
foreach (string langCode in json.Languages.Where(lang => locale.Translations.ContainsKey(lang) && lang != "en_US" && locale.Translations[lang] == locale.Translations["en_US"]))
{
encounteredIssue = true;
if (!isGitRunner)
{
locale.Translations[langCode] = string.Empty;
Console.WriteLine($"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'! Resetting it...");
}
else
{
Console.WriteLine($"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'!");
}
}
locale.Translations = locale.Translations.OrderBy(pair => pair.Key).ToDictionary(pair => pair.Key, pair => pair.Value);
json.Locales[i] = locale;
}
if (isGitRunner && encounteredIssue)
throw new JsonException("1 or more locales are invalid!");
JsonSerializerOptions jsonOptions = new JsonSerializerOptions()
{
WriteIndented = true,
NewLine = "\n",
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
string jsonString = JsonSerializer.Serialize(json, jsonOptions);
using (StreamWriter sw = new(path))
{
sw.Write(jsonString);
}
Console.WriteLine("Finished Locale Validation Task!");
return true;
}
struct LocalesJson
{
public List<string> Languages { get; set; }
public List<LocalesEntry> Locales { get; set; }
}
struct LocalesEntry
{
public string ID { get; set; }
public Dictionary<string, string> Translations { get; set; }
}
}
}

View File

@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Ryujinx.BuildValidationTasks
{
public class Program
{
static void Main(string[] args)
{
// Display the number of command line arguments.
if (args.Length == 0)
throw new ArgumentException("Error: too few arguments!");
string path = args[0];
if (string.IsNullOrEmpty(path))
throw new ArgumentException("Error: path is null or empty!");
if (!Path.Exists(path))
throw new FileLoadException($"path {{{path}}} does not exist!");
path = Path.GetFullPath(path);
if (!Directory.GetDirectories(path).Contains($"{path}src"))
throw new FileLoadException($"path {{{path}}} is not a valid ryujinx project!");
bool isGitRunner = path.Contains("runner") || path.Contains("D:\\a\\Ryujinx\\Ryujinx");
if (isGitRunner)
Console.WriteLine("Is Git Runner!");
// Run tasks
// Pass extra info needed in the task constructors
new LocalesValidationTask().Execute(path, isGitRunner);
}
}
}

View File

@@ -1,19 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Utilities.Core" />
<PackageReference Include="Newtonsoft.Json" />
</ItemGroup>
<Target Name="PostBuildTarget" AfterTargets="AfterBuild">
<Message Text="Running Validation Project" Importance="high" />
<UsingTask TaskName="Ryujinx.BuildValidationTasks.LocaleValidationTask" TaskFactory="TaskHostFactory" AssemblyFile="$(OutDir)Ryujinx.BuildValidationTasks.dll" />
<Target Name="LocalesJsonValidation" AfterTargets="AfterRebuild">
<LocaleValidationTask />
<Exec WorkingDirectory="$(ProjectDir)bin\Debug\$(TargetFramework)\"
Command="dotnet Ryujinx.BuildValidationTasks.dll &quot;$(ProjectDir)..\..\\&quot;"
ConsoleToMsBuild="true"
/>
</Target>
</Project>
</Project>

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Ryujinx.BuildValidationTasks
{
public interface ValidationTask
{
public bool Execute(string projectPath, bool isGitRunner);
}
}

View File

@@ -35,6 +35,8 @@ namespace Ryujinx.Common.Configuration
#pragma warning restore IDE0055
};
}
public static float ToFloatY(this AspectRatio aspectRatio)
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
using SharpMetal;
using SharpMetal.Foundation;
using SharpMetal.ObjectiveCCore;
using SharpMetal.QuartzCore;
using System.Runtime.Versioning;
@@ -9,22 +10,13 @@ namespace Ryujinx.Graphics.Metal.SharpMetalExtensions
[SupportedOSPlatform("macOS")]
public static class CAMetalLayerExtensions
{
private static readonly Selector sel_displaySyncEnabled = "displaySyncEnabled";
private static readonly Selector sel_setDisplaySyncEnabled = "setDisplaySyncEnabled:";
private static readonly Selector sel_developerHUDProperties = "developerHUDProperties";
private static readonly Selector sel_setDeveloperHUDProperties = "setDeveloperHUDProperties:";
public static bool IsDisplaySyncEnabled(this CAMetalLayer metalLayer)
=> ObjectiveCRuntime.bool_objc_msgSend(metalLayer.NativePtr, sel_displaySyncEnabled);
public static void SetDisplaySyncEnabled(this CAMetalLayer metalLayer, bool enabled)
=> ObjectiveCRuntime.objc_msgSend(metalLayer.NativePtr, sel_setDisplaySyncEnabled, enabled);
public static NSDictionary GetDeveloperHudProperties(this CAMetalLayer metalLayer)
=> new(ObjectiveCRuntime.IntPtr_objc_msgSend(metalLayer.NativePtr, sel_developerHUDProperties));
public static nint GetDeveloperHudProperties(this CAMetalLayer metalLayer)
=> ObjectiveCRuntime.IntPtr_objc_msgSend(metalLayer.NativePtr, sel_developerHUDProperties);
public static void SetDeveloperHudProperties(this CAMetalLayer metalLayer, nint dictionaryPointer)
=> ObjectiveCRuntime.objc_msgSend(metalLayer.NativePtr, sel_setDeveloperHUDProperties, dictionaryPointer);
public static void SetDeveloperHudProperties(this CAMetalLayer metalLayer, NSDictionary dictionary)
=> ObjectiveCRuntime.objc_msgSend(metalLayer.NativePtr, sel_setDeveloperHUDProperties, dictionary);
}
}

View File

@@ -0,0 +1,32 @@
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

@@ -2,6 +2,7 @@
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>

View File

@@ -2,13 +2,10 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Metal.Effects;
using Ryujinx.Graphics.Metal.SharpMetalExtensions;
using SharpMetal.ObjectiveCCore;
using SharpMetal.QuartzCore;
using System;
using System.Runtime.Versioning;
using AntiAliasing = Ryujinx.Graphics.GAL.AntiAliasing;
using ScalingFilter = Ryujinx.Graphics.GAL.ScalingFilter;
namespace Ryujinx.Graphics.Metal
{
@@ -18,7 +15,7 @@ namespace Ryujinx.Graphics.Metal
public bool ScreenCaptureRequested { get; set; }
private readonly MetalRenderer _renderer;
private readonly CAMetalLayer _metalLayer;
private CAMetalLayer _metalLayer;
private int _width;
private int _height;
@@ -146,11 +143,11 @@ namespace Ryujinx.Graphics.Metal
{
switch (vSyncMode)
{
case VSyncMode.Unbounded:
_metalLayer.SetDisplaySyncEnabled(false);
case VSyncMode.Unbounded:
_metalLayer.DisplaySyncEnabled = false;
break;
case VSyncMode.Switch:
_metalLayer.SetDisplaySyncEnabled(true);
_metalLayer.DisplaySyncEnabled = true;
break;
}
}

View File

@@ -5,8 +5,6 @@ using Ryujinx.Graphics.OpenGL.Effects;
using Ryujinx.Graphics.OpenGL.Effects.Smaa;
using Ryujinx.Graphics.OpenGL.Image;
using System;
using AntiAliasing = Ryujinx.Graphics.GAL.AntiAliasing;
using ScalingFilter = Ryujinx.Graphics.GAL.ScalingFilter;
namespace Ryujinx.Graphics.OpenGL
{

View File

@@ -5,8 +5,6 @@ using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.KHR;
using System;
using System.Linq;
using AntiAliasing = Ryujinx.Graphics.GAL.AntiAliasing;
using ScalingFilter = Ryujinx.Graphics.GAL.ScalingFilter;
using VkFormat = Silk.NET.Vulkan.Format;
namespace Ryujinx.Graphics.Vulkan

View File

@@ -1,8 +1,6 @@
using Ryujinx.Common.Configuration;
using Ryujinx.Graphics.GAL;
using System;
using AntiAliasing = Ryujinx.Graphics.GAL.AntiAliasing;
using ScalingFilter = Ryujinx.Graphics.GAL.ScalingFilter;
namespace Ryujinx.Graphics.Vulkan
{

View File

@@ -1,3 +1,4 @@
using Gommon;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
@@ -6,12 +7,13 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Ryujinx.HLE.HOS.Services.Account.Acc
{
class AccountSaveDataManager
public class AccountSaveDataManager
{
private readonly string _profilesJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "Profiles.json");
private static readonly string _profilesJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "Profiles.json");
private static readonly ProfilesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
@@ -49,6 +51,16 @@ 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

@@ -102,7 +102,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
}
// Initialize GPU.
Graphics.Gpu.GraphicsConfig.TitleId = $"{programId:x16}";
Graphics.Gpu.GraphicsConfig.TitleId = programId.ToString("X16");
device.Gpu.HostInitalized.Set();
if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible))

View File

@@ -1,73 +0,0 @@
<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.

Before

Width:  |  Height:  |  Size: 9.1 KiB

View File

@@ -36,6 +36,8 @@ 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,16 +789,15 @@ 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);
var evt = new LdnGameDataReceivedEventArgs
LdnGameDataReceived?.Invoke(null, 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>()
});
@@ -806,7 +805,7 @@ namespace Ryujinx.UI.App.Common
}
else
{
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs()
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs
{
LdnData = Array.Empty<LdnGameData>()
});

View File

@@ -1,4 +1,7 @@
using LibHac.Ns;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.UI.App.Common
{
@@ -12,5 +15,28 @@ 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

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

View File

@@ -5,7 +5,6 @@ using Ryujinx.Common;
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

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;

View File

@@ -289,19 +289,19 @@ namespace Ryujinx.Ava
private void UpdateScalingFilterLevel(object sender, ReactiveEventArgs<int> e)
{
_renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
_renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
_renderer.Window?.SetScalingFilter(ConfigurationState.Instance.Graphics.ScalingFilter);
_renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel);
}
private void UpdateScalingFilter(object sender, ReactiveEventArgs<ScalingFilter> e)
{
_renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
_renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
_renderer.Window?.SetScalingFilter(ConfigurationState.Instance.Graphics.ScalingFilter);
_renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel);
}
private void UpdateColorSpacePassthrough(object sender, ReactiveEventArgs<bool> e)
{
_renderer.Window?.SetColorSpacePassthrough((bool)ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Value);
_renderer.Window?.SetColorSpacePassthrough(ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough);
}
public void UpdateVSyncMode(object sender, ReactiveEventArgs<VSyncMode> e)
@@ -526,7 +526,7 @@ namespace Ryujinx.Ava
private void UpdateAntiAliasing(object sender, ReactiveEventArgs<AntiAliasing> e)
{
_renderer?.Window?.SetAntiAliasing((Graphics.GAL.AntiAliasing)e.NewValue);
_renderer?.Window?.SetAntiAliasing(e.NewValue);
}
private void UpdateDockedModeState(object sender, ReactiveEventArgs<bool> e)
@@ -1057,10 +1057,10 @@ namespace Ryujinx.Ava
Device.Gpu.Renderer.Initialize(_glLogLevel);
_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);
_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);
Width = (int)RendererHost.Bounds.Width;
Height = (int)RendererHost.Bounds.Height;

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@ using Ryujinx.HLE.UI;
using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.Headless.SDL2
namespace Ryujinx.Headless
{
/// <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.SDL2
namespace Ryujinx.Headless
{
internal class HeadlessHostUiTheme : IHostUITheme
{

View File

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

View File

@@ -1,13 +1,9 @@
using CommandLine;
using Gommon;
using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.SDL2;
using Ryujinx.Ava;
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;
@@ -15,16 +11,12 @@ 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;
@@ -33,22 +25,16 @@ using Ryujinx.Input;
using Ryujinx.Input.HLE;
using Ryujinx.Input.SDL2;
using Ryujinx.SDL2.Common;
using Silk.NET.Vulkan;
using Ryujinx.UI.Common.Configuration;
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.SDL2
namespace Ryujinx.Headless
{
class Program
public partial class HeadlessRyujinx
{
public static string Version { get; private set; }
private static VirtualFileSystem _virtualFileSystem;
private static ContentManager _contentManager;
private static AccountManager _accountManager;
@@ -58,20 +44,18 @@ namespace Ryujinx.Headless.SDL2
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());
static void Main(string[] args)
public static void Entrypoint(string[] args)
{
Version = ReleaseInformation.Version;
// Make process DPI aware for proper window sizing on high-res screens.
ForceDpiAware.Windows();
Console.Title = $"Ryujinx Console {Version} (Headless SDL2)";
Console.Title = $"Ryujinx Console {Program.Version} (Headless)";
if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux())
{
@@ -99,7 +83,7 @@ namespace Ryujinx.Headless.SDL2
}
Parser.Default.ParseArguments<Options>(args)
.WithParsed(Load)
.WithParsed(options => Load(args, options))
.WithNotParsed(errors =>
{
Logger.Error?.PrintMsg(LogClass.Application, "Error parsing command-line arguments:");
@@ -107,239 +91,81 @@ namespace Ryujinx.Headless.SDL2
errors.ForEach(err => Logger.Error?.PrintMsg(LogClass.Application, $" - {err.Tag}"));
});
}
private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index)
public static void ReloadConfig(string customConfigPath = null)
{
if (inputId == null)
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
string configurationPath = null;
// Now load the configuration as the other subsystems are now registered
if (customConfigPath != null && File.Exists(customConfigPath))
{
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;
}
configurationPath = customConfigPath;
}
else if (File.Exists(localConfigurationPath))
{
configurationPath = localConfigurationPath;
}
else if (File.Exists(appDataConfigurationPath))
{
configurationPath = appDataConfigurationPath;
}
IGamepad gamepad = _inputManager.KeyboardDriver.GetGamepad(inputId);
bool isKeyboard = true;
if (gamepad == null)
if (configurationPath == null)
{
gamepad = _inputManager.GamepadDriver.GetGamepad(inputId);
isKeyboard = false;
// 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}");
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,
},
};
}
ConfigurationState.Instance.LoadDefault();
ConfigurationState.Instance.ToFileFormat().SaveConfig(configurationPath);
}
else
{
string profileBasePath;
Logger.Notice.Print(LogClass.Application, $"Loading configuration from: {configurationPath}");
if (isKeyboard)
if (ConfigurationFileFormat.TryLoad(configurationPath, out ConfigurationFileFormat configurationFileFormat))
{
profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "keyboard");
ConfigurationState.Instance.Load(configurationFileFormat, configurationPath);
}
else
{
profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "controller");
}
Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location: {configurationPath}");
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;
ConfigurationState.Instance.LoadDefault();
}
}
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)
static void Load(string[] originalArgs, Options option)
{
AppDataManager.Initialize(option.BaseDataDir);
Initialize();
bool useLastUsedProfile = false;
if (option.InheritConfig)
{
option.InheritMainConfig(originalArgs, ConfigurationState.Instance, out useLastUsedProfile);
}
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();
@@ -354,7 +180,7 @@ namespace Ryujinx.Headless.SDL2
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
GraphicsConfig.EnableShaderCache = true;
GraphicsConfig.EnableShaderCache = !option.DisableShaderCache;
if (OperatingSystem.IsMacOS())
{
@@ -365,15 +191,13 @@ namespace Ryujinx.Headless.SDL2
}
}
IGamepad gamepad;
if (option.ListInputIds)
{
Logger.Info?.Print(LogClass.Application, "Input Ids:");
foreach (string id in _inputManager.KeyboardDriver.GamepadsIds)
{
gamepad = _inputManager.KeyboardDriver.GetGamepad(id);
IGamepad gamepad = _inputManager.KeyboardDriver.GetGamepad(id);
Logger.Info?.Print(LogClass.Application, $"- {id} (\"{gamepad.Name}\")");
@@ -382,7 +206,7 @@ namespace Ryujinx.Headless.SDL2
foreach (string id in _inputManager.GamepadDriver.GamepadsIds)
{
gamepad = _inputManager.GamepadDriver.GetGamepad(id);
IGamepad gamepad = _inputManager.GamepadDriver.GetGamepad(id);
Logger.Info?.Print(LogClass.Application, $"- {id} (\"{gamepad.Name}\")");
@@ -399,7 +223,7 @@ namespace Ryujinx.Headless.SDL2
return;
}
_inputConfiguration = new List<InputConfig>();
_inputConfiguration ??= [];
_enableKeyboard = option.EnableKeyboard;
_enableMouse = option.EnableMouse;
@@ -412,9 +236,9 @@ namespace Ryujinx.Headless.SDL2
_inputConfiguration.Add(inputConfig);
}
}
LoadPlayerConfiguration(option.InputProfile1Name, option.InputId1, PlayerIndex.Player1);
LoadPlayerConfiguration(option.InputProfile2Name, option.InputId2, PlayerIndex.Player2);
LoadPlayerConfiguration(option.InputProfile2Name, option.InputId2, PlayerIndex.Player2);
LoadPlayerConfiguration(option.InputProfile3Name, option.InputId3, PlayerIndex.Player3);
LoadPlayerConfiguration(option.InputProfile4Name, option.InputId4, PlayerIndex.Player4);
LoadPlayerConfiguration(option.InputProfile5Name, option.InputId5, PlayerIndex.Player5);
@@ -422,6 +246,7 @@ namespace Ryujinx.Headless.SDL2
LoadPlayerConfiguration(option.InputProfile7Name, option.InputId7, PlayerIndex.Player7);
LoadPlayerConfiguration(option.InputProfile8Name, option.InputId8, PlayerIndex.Player8);
LoadPlayerConfiguration(option.InputProfileHandheldName, option.InputIdHandheld, PlayerIndex.Handheld);
if (_inputConfiguration.Count == 0)
{
@@ -433,7 +258,7 @@ namespace Ryujinx.Headless.SDL2
Logger.SetEnable(LogLevel.Stub, !option.LoggingDisableStub);
Logger.SetEnable(LogLevel.Info, !option.LoggingDisableInfo);
Logger.SetEnable(LogLevel.Warning, !option.LoggingDisableWarning);
Logger.SetEnable(LogLevel.Error, option.LoggingEnableError);
Logger.SetEnable(LogLevel.Error, !option.LoggingDisableError);
Logger.SetEnable(LogLevel.Trace, option.LoggingEnableTrace);
Logger.SetEnable(LogLevel.Guest, !option.LoggingDisableGuest);
Logger.SetEnable(LogLevel.AccessLog, option.LoggingEnableFsAccessLog);
@@ -522,88 +347,6 @@ namespace Ryujinx.Headless.SDL2
};
}
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

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

View File

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

View File

@@ -1,13 +1,168 @@
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.SDL2
namespace Ryujinx.Headless
{
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; }
@@ -95,10 +250,10 @@ namespace Ryujinx.Headless.SDL2
[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 inputs profiles.")]
[Option("list-input-profiles", Required = false, HelpText = "List input profiles.")]
public bool ListInputProfiles { get; set; }
[Option("list-inputs-ids", Required = false, HelpText = "List inputs ids.")]
[Option("list-input-ids", Required = false, HelpText = "List input IDs.")]
public bool ListInputIds { get; set; }
// System
@@ -172,7 +327,7 @@ namespace Ryujinx.Headless.SDL2
public bool LoggingDisableWarning { get; set; }
[Option("disable-error-logs", Required = false, HelpText = "Disables printing error log messages.")]
public bool LoggingEnableError { get; set; }
public bool LoggingDisableError { get; set; }
[Option("enable-trace-logs", Required = false, Default = false, HelpText = "Enables printing trace log messages.")]
public bool LoggingEnableTrace { get; set; }
@@ -215,7 +370,7 @@ namespace Ryujinx.Headless.SDL2
[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]")]
[Option("scaling-filter", Required = false, Default = ScalingFilter.Bilinear, HelpText = "Set the scaling filter. [Bilinear|Nearest|Fsr|Area]")]
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]")]

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

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

View File

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

View File

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

View File

@@ -14,6 +14,7 @@ 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;
@@ -52,9 +53,15 @@ 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();
IconProvider.Current
@@ -106,7 +113,7 @@ namespace Ryujinx.Ava
AppDomain.CurrentDomain.UnhandledException += (sender, e)
=> ProcessUnhandledException(sender, e.ExceptionObject as Exception, e.IsTerminating);
AppDomain.CurrentDomain.ProcessExit += (_, _) => Exit();
// Setup base data directory.
AppDataManager.Initialize(CommandLineState.BaseDirPathArg);
@@ -223,7 +230,7 @@ namespace Ryujinx.Ava
UseHardwareAcceleration = CommandLineState.OverrideHardwareAcceleration.Value;
}
private static void PrintSystemInfo()
internal static void PrintSystemInfo()
{
Logger.Notice.Print(LogClass.Application, $"{RyujinxApp.FullAppName} Version: {Version}");
SystemInfo.Gather().Print();
@@ -240,7 +247,7 @@ namespace Ryujinx.Ava
: $"Launch Mode: {AppDataManager.Mode}");
}
private static void ProcessUnhandledException(object sender, Exception ex, bool isTerminating)
internal static void ProcessUnhandledException(object sender, Exception ex, bool isTerminating)
{
Logger.Log log = Logger.Error ?? Logger.Notice;
string message = $"Unhandled exception caught: {ex}";
@@ -255,7 +262,7 @@ namespace Ryujinx.Ava
Exit();
}
public static void Exit()
internal static void Exit()
{
DiscordIntegrationModule.Exit();

View File

@@ -13,9 +13,13 @@
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
</PropertyGroup>
<Target Name="BuildValidationProj" BeforeTargets="BeforeRebuild">
<MSBuild Projects="..\Ryujinx.BuildValidationTasks\Ryujinx.BuildValidationTasks.csproj" Targets="Rebuild">
</MSBuild>
<Target Name="BuildValidationProj" BeforeTargets="BeforeBuild">
<Message Text="Building Validation Project for $(TargetFramework)" Importance="high" Condition="'$(RuntimeIdentifier)' == ''" />
<Message Text="Building Validation Project for $(TargetFramework) with runtime $(RuntimeIdentifier)" Importance="high" Condition="'$(RuntimeIdentifier)' != ''" />
<Exec WorkingDirectory="..\Ryujinx.BuildValidationTasks\" Command="dotnet restore Ryujinx.BuildValidationTasks.csproj --force --ucr true" Condition="'$(RuntimeIdentifier)' == ''" />
<Exec WorkingDirectory="..\Ryujinx.BuildValidationTasks\" Command="dotnet restore Ryujinx.BuildValidationTasks.csproj --force --runtime $(RuntimeIdentifier)" Condition="'$(RuntimeIdentifier)' != ''" />
<MSBuild Projects="..\Ryujinx.BuildValidationTasks\Ryujinx.BuildValidationTasks.csproj" Properties="Configuration=Debug" />
</Target>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="$([MSBuild]::IsOSPlatform('OSX'))">
@@ -47,6 +51,7 @@
<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" />
@@ -66,6 +71,7 @@
<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" />
@@ -74,7 +80,6 @@
<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" />
@@ -133,6 +138,7 @@
<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" />

View File

@@ -33,8 +33,11 @@ namespace Ryujinx.Ava
.ApplicationLifetime.Cast<IClassicDesktopStyleApplicationLifetime>()
.MainWindow.Cast<MainWindow>();
public static bool IsClipboardAvailable(out IClipboard clipboard)
=> (clipboard = MainWindow.Clipboard) != null;
public static bool IsClipboardAvailable(out IClipboard clipboard)
{
clipboard = MainWindow.Clipboard;
return clipboard != null;
}
public static void SetTaskbarProgress(TaskBarProgressBarState state) => MainWindow.PlatformFeatures.SetTaskBarProgressBarState(state);
public static void SetTaskbarProgressValue(ulong current, ulong total) => MainWindow.PlatformFeatures.SetTaskBarProgressBarValue(current, total);

View File

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

View File

@@ -9,6 +9,7 @@ using Avalonia.Threading;
using DynamicData;
using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
using Gommon;
using LibHac.Common;
using LibHac.Ns;
using Ryujinx.Ava.Common;
@@ -109,8 +110,13 @@ namespace Ryujinx.Ava.UI.ViewModels
private bool _areMimeTypesRegistered = FileAssociationHelper.AreMimeTypesRegistered;
private bool _canUpdate = true;
private Cursor _cursor;
private string _title;
private ApplicationData _currentApplicationData;
private readonly AutoResetEvent _rendererWaitEvent;
private WindowState _windowState;
private double _windowWidth;
private double _windowHeight;
private int _customVSyncInterval;
private int _customVSyncIntervalPercentageProxy;
@@ -119,8 +125,9 @@ namespace Ryujinx.Ava.UI.ViewModels
public ApplicationData ListSelectedApplication;
public ApplicationData GridSelectedApplication;
public IEnumerable<LdnGameData> LastLdnGameData;
// Key is Title ID
public SafeDictionary<string, LdnGameData.Array> LdnData = [];
// 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.
@@ -211,7 +218,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool CanUpdate
{
get => _canUpdate && EnableNonGameRunningControls && Updater.CanUpdate();
get => _canUpdate && EnableNonGameRunningControls && Updater.CanUpdate(false);
set
{
_canUpdate = value;
@@ -221,8 +228,12 @@ namespace Ryujinx.Ava.UI.ViewModels
public Cursor Cursor
{
get => Window.Cursor;
set => Window.Cursor = value;
get => _cursor;
set
{
_cursor = value;
OnPropertyChanged();
}
}
public ReadOnlyObservableCollection<ApplicationData> AppsObservableList
@@ -804,23 +815,35 @@ namespace Ryujinx.Ava.UI.ViewModels
public WindowState WindowState
{
get => Window.WindowState;
get => _windowState;
internal set
{
Window.WindowState = value;
_windowState = value;
OnPropertyChanged();
}
}
public double WindowWidth
{
get => Window.Width;
set => Window.Width = value;
get => _windowWidth;
set
{
_windowWidth = value;
OnPropertyChanged();
}
}
public double WindowHeight
{
get => Window.Height;
set => Window.Height = value;
get => _windowHeight;
set
{
_windowHeight = value;
OnPropertyChanged();
}
}
public bool IsGrid => Glyph == Glyph.Grid;
@@ -868,11 +891,11 @@ namespace Ryujinx.Ava.UI.ViewModels
public string Title
{
get => Window.Title;
get => _title;
set
{
Window.Title = value;
_title = value;
OnPropertyChanged();
}
}

View File

@@ -750,7 +750,7 @@ namespace Ryujinx.Ava.UI.ViewModels
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
MainWindow.UpdateGraphicsConfig();
MainWindow.MainWindowViewModel.VSyncModeSettingChanged();
RyujinxApp.MainWindow.ViewModel.VSyncModeSettingChanged();
SaveSettingsEvent?.Invoke();

View File

@@ -9,6 +9,11 @@
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:main="clr-namespace:Ryujinx.Ava.UI.Views.Main"
Cursor="{Binding Cursor}"
Title="{Binding Title}"
WindowState="{Binding WindowState}"
Width="{Binding WindowWidth}"
Height="{Binding WindowHeight}"
MinWidth="800"
MinHeight="500"
d:DesignHeight="720"

View File

@@ -39,8 +39,6 @@ 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;
@@ -76,7 +74,7 @@ namespace Ryujinx.Ava.UI.Windows
public MainWindow()
{
DataContext = ViewModel = MainWindowViewModel = new MainWindowViewModel
DataContext = ViewModel = new MainWindowViewModel
{
Window = this
};
@@ -169,24 +167,31 @@ namespace Ryujinx.Ava.UI.Windows
{
Dispatcher.UIThread.Post(() =>
{
var ldnGameDataArray = e.LdnData;
ViewModel.LastLdnGameData = ldnGameDataArray;
var ldnGameDataArray = e.LdnData.ToList();
ViewModel.LdnData.Clear();
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.ControlHolder.ByteSpan.Length > 0 && ViewModel.LastLdnGameData != null)
if (application.HasControlHolder && ViewModel.LdnData.TryGetValue(application.IdString, out var ldnGameDatas))
{
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();
application.PlayerCount = ldnGameDatas.PlayerCount;
application.GameCount = ldnGameDatas.GameCount;
}
else
{