Compare commits

..

12 Commits

Author SHA1 Message Date
Evan Husted
66fdad6d64 hia: prevent infinite loading 2024-12-14 04:41:43 -06:00
Evan Husted
657bd4a7d0 missed this 2024-12-14 00:24:39 -06:00
Evan Husted
d3a6a8f66e hia: Fix last used profile loading, (hopefully) fixed logo loading 2024-12-11 16:21:13 -06:00
Evan Husted
12a506a644 hia: Fix Window icon loading 2024-12-11 15:41:06 -06:00
Evan Husted
6f56690c1c hia: Only reference AppDataManager properties after they've been initialized 2024-12-11 15:23:31 -06:00
Evan Husted
1398dad67c hia: fix 2 crashes 2024-12-11 15:01:28 -06:00
Evan Husted
2fc58d58a5 Fix accidental null passing 2024-12-11 14:15:57 -06:00
Evan Husted
75fa7c7d4d fixups 2024-12-09 03:28:16 -06:00
Evan Husted
a477658b85 Headless in Avalonia v2 2024-12-09 03:21:07 -06:00
Evan Husted
338777055f I love that I clicked delete on this and it was literally like, still there, not deleted. lol 2024-12-09 01:38:13 -06:00
Evan Husted
90db9a962d Iteration 2: Drop Headless-specific project, drop headless releases. Also prevent duplicate log targets by checking name equality. 2024-12-09 01:36:00 -06:00
Evan Husted
4a545a00c6 Headless in Avalonia, v1
It will get more advanced than this, this is for proof of concept.
2024-12-08 19:01:53 -06:00
25 changed files with 1523 additions and 1459 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

@@ -54,19 +54,7 @@ jobs:
with:
name: "Canary ${{ steps.version_info.outputs.build_version }}"
tag: ${{ steps.version_info.outputs.build_version }}
body: |
# Canary builds:
These builds are experimental and may sometimes not work, use [regular builds](https://github.com/${{ github.repository }}/releases/latest) instead if that sounds like something you don't want to deal with.
| Platform | Artifact |
|--|--|
| Windows 64 bit | [Canary Windows Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-win_x64.zip) |
| Linux 64 bit | [Canary Linux Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz) |
| Linux arm 64 bit | [Canary Linux arm Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-linux_arm64.tar.gz) |
| macOS | [Canary macOS artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz) |
**Full Changelog**: https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }}
body: "**Full Changelog**: https://github.com/${{ github.repository }}/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }}"
omitBodyDuringUpdate: true
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
@@ -116,7 +104,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 +112,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 +122,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 +167,9 @@ 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.
| 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 |
"**Full Changelog**: https://github.com/${{ github.repository }}/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }}"
body: "**Full Changelog**: https://github.com/${{ github.repository }}/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }}"
omitBodyDuringUpdate: true
allowUpdates: true
replacesArtifacts: true
@@ -262,29 +226,13 @@ 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: |
# 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.
| 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 |
"**Full Changelog**: https://github.com/${{ github.repository }}/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }}"
body: "**Full Changelog**: https://github.com/${{ github.repository }}/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }}"
omitBodyDuringUpdate: true
allowUpdates: true
replacesArtifacts: true

View File

@@ -38,20 +38,15 @@ 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) {
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)`;
} else {
body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
}
}
hidden_headless_artifacts += `\n</details>`;
hidden_debug_artifacts += `\n</details>`;
body += hidden_headless_artifacts;
body += hidden_debug_artifacts;
const {data: comments} = await github.rest.issues.listComments({repo, owner, issue_number});

View File

@@ -53,16 +53,7 @@ jobs:
with:
name: ${{ steps.version_info.outputs.build_version }}
tag: ${{ steps.version_info.outputs.build_version }}
body: |
# Regular 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) |
**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 }}
body: "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}"
omitBodyDuringUpdate: true
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
@@ -112,7 +103,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 +111,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 +157,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
@@ -185,16 +165,7 @@ jobs:
name: ${{ steps.version_info.outputs.build_version }}
artifacts: "release_output/*.tar.gz,release_output/*.zip,release_output/*AppImage*"
tag: ${{ steps.version_info.outputs.build_version }}
body: |
# Regular 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 }}"
body: "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}"
omitBodyDuringUpdate: true
allowUpdates: true
replacesArtifacts: true
@@ -251,26 +222,13 @@ 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: |
# Regular 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 }}"
body: "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}"
omitBodyDuringUpdate: true
allowUpdates: true
replacesArtifacts: true

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}"
@@ -204,10 +202,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

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;
@@ -158,20 +159,15 @@ namespace Ryujinx.Common.Logging
}
private static ILogTarget GetTarget(string targetName)
{
foreach (var target in _logTargets)
{
if (target.Name.Equals(targetName))
{
return target;
}
}
return null;
}
=> _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,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

@@ -1,73 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<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.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

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

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

@@ -1,7 +1,7 @@
{
"Language": "Українська",
"MenuBarFileOpenApplet": "Відкрити аплет",
"MenuBarFileOpenAppletOpenMiiApplet": "Аплет для редагування Mii",
"MenuBarFileOpenAppletOpenMiiApplet": "Mii Edit Applet",
"MenuBarFileOpenAppletOpenMiiAppletToolTip": "Відкрити аплет Mii Editor в автономному режимі",
"SettingsTabInputDirectMouseAccess": "Прямий доступ мишею",
"SettingsTabSystemMemoryManagerMode": "Режим диспетчера пам’яті:",
@@ -11,10 +11,10 @@
"SettingsTabSystemUseHypervisor": "Використовувати гіпервізор",
"MenuBarFile": "_Файл",
"MenuBarFileOpenFromFile": "_Завантажити програму з файлу",
"MenuBarFileOpenFromFileError": "У вибраному файлі не знайдено жодних додатків.",
"MenuBarFileOpenFromFileError": "No applications found in selected file.",
"MenuBarFileOpenUnpacked": "Завантажити _розпаковану гру",
"MenuBarFileLoadDlcFromFolder": "Завантажити DLC з теки",
"MenuBarFileLoadTitleUpdatesFromFolder": "Завантажити оновлення заголовків з теки",
"MenuBarFileLoadDlcFromFolder": "Load DLC From Folder",
"MenuBarFileLoadTitleUpdatesFromFolder": "Load Title Updates From Folder",
"MenuBarFileOpenEmuFolder": "Відкрити теку Ryujinx",
"MenuBarFileOpenLogsFolder": "Відкрити теку журналів змін",
"MenuBarFileExit": "_Вихід",
@@ -37,9 +37,9 @@
"MenuBarToolsManageFileTypes": "Керувати типами файлів",
"MenuBarToolsInstallFileTypes": "Установити типи файлів",
"MenuBarToolsUninstallFileTypes": "Видалити типи файлів",
"MenuBarToolsXCITrimmer": "Обрізати XCI файли",
"MenuBarView": "_Вид",
"MenuBarViewWindow": "Розмір вікна",
"MenuBarToolsXCITrimmer": "Trim XCI Files",
"MenuBarView": "_View",
"MenuBarViewWindow": "Window Size",
"MenuBarViewWindow720": "720p",
"MenuBarViewWindow1080": "1080p",
"MenuBarHelp": "_Допомога",
@@ -89,11 +89,11 @@
"GameListContextMenuOpenModsDirectoryToolTip": "Відкриває каталог, який містить модифікації Додатків",
"GameListContextMenuOpenSdModsDirectory": "Відкрити каталог модифікацій Atmosphere",
"GameListContextMenuOpenSdModsDirectoryToolTip": "Відкриває альтернативний каталог SD-карти Atmosphere, що містить модифікації Додатків. Корисно для модифікацій, зроблених для реального обладнання.",
"GameListContextMenuTrimXCI": "Перевірка та Нарізка XCI Файлів",
"GameListContextMenuTrimXCIToolTip": "Перевірка та Нарізка XCI Файлів для збереження місця на диску",
"GameListContextMenuTrimXCI": "Check and Trim XCI File",
"GameListContextMenuTrimXCIToolTip": "Check and Trim XCI File to Save Disk Space",
"StatusBarGamesLoaded": "{0}/{1} ігор завантажено",
"StatusBarSystemVersion": "Версія системи: {0}",
"StatusBarXCIFileTrimming": "Обрізано XCI Файлів '{0}'",
"StatusBarXCIFileTrimming": "Trimming XCI File '{0}'",
"LinuxVmMaxMapCountDialogTitle": "Виявлено низьку межу для відображення памʼяті",
"LinuxVmMaxMapCountDialogTextPrimary": "Бажаєте збільшити значення vm.max_map_count на {0}",
"LinuxVmMaxMapCountDialogTextSecondary": "Деякі ігри можуть спробувати створити більше відображень памʼяті, ніж дозволено наразі. Ryujinx завершить роботу, щойно цей ліміт буде перевищено.",
@@ -107,15 +107,15 @@
"SettingsTabGeneralEnableDiscordRichPresence": "Увімкнути розширену присутність Discord",
"SettingsTabGeneralCheckUpdatesOnLaunch": "Перевіряти наявність оновлень під час запуску",
"SettingsTabGeneralShowConfirmExitDialog": "Показати діалогове вікно «Підтвердити вихід».",
"SettingsTabGeneralRememberWindowState": "Запам'ятати Розмір/Позицію вікна",
"SettingsTabGeneralShowTitleBar": "Показувати рядок заголовка (Потрібен перезапуск)",
"SettingsTabGeneralRememberWindowState": "Remember Window Size/Position",
"SettingsTabGeneralShowTitleBar": "Show Title Bar (Requires restart)",
"SettingsTabGeneralHideCursor": "Сховати вказівник:",
"SettingsTabGeneralHideCursorNever": "Ніколи",
"SettingsTabGeneralHideCursorOnIdle": "Сховати у режимі очікування",
"SettingsTabGeneralHideCursorAlways": "Завжди",
"SettingsTabGeneralGameDirectories": "Тека ігор",
"SettingsTabGeneralAutoloadDirectories": "Автозавантаження каталогів DLC/Оновлень",
"SettingsTabGeneralAutoloadNote": "DLC та Оновлення, які посилаються на відсутні файли, будуть автоматично вимкнуті.",
"SettingsTabGeneralAutoloadDirectories": "Autoload DLC/Updates Directories",
"SettingsTabGeneralAutoloadNote": "DLC and Updates which refer to missing files will be unloaded automatically",
"SettingsTabGeneralAdd": "Додати",
"SettingsTabGeneralRemove": "Видалити",
"SettingsTabSystem": "Система",
@@ -148,18 +148,7 @@
"SettingsTabSystemSystemLanguageTraditionalChinese": "Традиційна китайська",
"SettingsTabSystemSystemTimeZone": "Часовий пояс системи:",
"SettingsTabSystemSystemTime": "Час системи:",
"SettingsTabSystemVSyncMode": "Вертикальна синхронізація (VSync):",
"SettingsTabSystemEnableCustomVSyncInterval": "Увімкнути користувацьку частоту оновлення (Експериментально)",
"SettingsTabSystemVSyncModeSwitch": "Switch",
"SettingsTabSystemVSyncModeUnbounded": "Безмежна",
"SettingsTabSystemVSyncModeCustom": "Користувацька",
"SettingsTabSystemVSyncModeTooltip": "Емульована вертикальна синхронізація. 'Switch' емулює частоту оновлення Switch 60 Гц. 'Безмежна' — частота оновлення не матиме обмежень.",
"SettingsTabSystemVSyncModeTooltipCustom": "Емульована вертикальна синхронізація. 'Switch' емулює частоту оновлення Switch 60 Гц. 'Безмежна' — частота оновлення не матиме обмежень. 'Користувацька' емулює вказану користувацьку частоту оновлення.",
"SettingsTabSystemEnableCustomVSyncIntervalTooltip": "Дозволяє користувачу вказати емульовану частоту оновлення. У деяких іграх це може прискорити або сповільнити логіку гри. У інших іграх це може дозволити обмежити FPS на певні кратні частоти оновлення або призвести до непередбачуваної поведінки. Це експериментальна функція, без гарантій того, як це вплине на ігровий процес. \n\nЗалиште ВИМКНЕНИМ, якщо не впевнені.",
"SettingsTabSystemCustomVSyncIntervalValueTooltip": "Цільове значення користувацької частоти оновлення.",
"SettingsTabSystemCustomVSyncIntervalSliderTooltip": "Користувацька частота оновлення, як відсоток від стандартної частоти оновлення Switch.",
"SettingsTabSystemCustomVSyncIntervalPercentage": "Користувацька частота оновлення %:",
"SettingsTabSystemCustomVSyncIntervalValue": "Значення користувацька частота оновлення:",
"SettingsTabSystemEnableVsync": "Вертикальна синхронізація",
"SettingsTabSystemEnablePptc": "PPTC (профільований постійний кеш перекладу)",
"SettingsTabSystemEnableLowPowerPptc": "Low-power PPTC",
"SettingsTabSystemEnableFsIntegrityChecks": "Перевірка цілісності FS",
@@ -170,11 +159,11 @@
"SettingsTabSystemAudioBackendSDL2": "SDL2",
"SettingsTabSystemHacks": "Хитрощі",
"SettingsTabSystemHacksNote": " (може викликати нестабільність)",
"SettingsTabSystemDramSize": "Використовувати альтернативне розташування пам'яті (для розробників)",
"SettingsTabSystemDramSize4GiB": "4Гб",
"SettingsTabSystemDramSize6GiB": "6Гб",
"SettingsTabSystemDramSize8GiB": "8Гб",
"SettingsTabSystemDramSize12GiB": "12Гб",
"SettingsTabSystemDramSize": "Використовувати альтернативне розташування пам'яті (розробники)",
"SettingsTabSystemDramSize4GiB": "4GiB",
"SettingsTabSystemDramSize6GiB": "6GiB",
"SettingsTabSystemDramSize8GiB": "8GiB",
"SettingsTabSystemDramSize12GiB": "12GiB",
"SettingsTabSystemIgnoreMissingServices": "Ігнорувати відсутні служби",
"SettingsTabSystemIgnoreApplet": "Ігнорувати Аплет",
"SettingsTabGraphics": "Графіка",
@@ -212,8 +201,8 @@
"SettingsTabLoggingEnableGuestLogs": "Увімкнути журнали гостей",
"SettingsTabLoggingEnableFsAccessLogs": "Увімкнути журнали доступу Fs",
"SettingsTabLoggingFsGlobalAccessLogMode": "Режим журналу глобального доступу Fs:",
"SettingsTabLoggingDeveloperOptions": "Параметри розробника (УВАГА: шкодить продуктивності!)",
"SettingsTabLoggingDeveloperOptionsNote": "УВАГА: Зміна параметрів нижче негативно впливає на продуктивність",
"SettingsTabLoggingDeveloperOptions": "Параметри розробника (УВАГА: знизиться продуктивність)",
"SettingsTabLoggingDeveloperOptionsNote": "УВАГА: Знижує продуктивність",
"SettingsTabLoggingGraphicsBackendLogLevel": "Рівень журналу графічного сервера:",
"SettingsTabLoggingGraphicsBackendLogLevelNone": "Немає",
"SettingsTabLoggingGraphicsBackendLogLevelError": "Помилка",
@@ -302,29 +291,29 @@
"ControllerSettingsMotionGyroDeadzone": "Мертва зона гіроскопа:",
"ControllerSettingsSave": "Зберегти",
"ControllerSettingsClose": "Закрити",
"KeyUnknown": "Невідома",
"KeyShiftLeft": "Shift Лівий",
"KeyShiftRight": "Shift Правий",
"KeyControlLeft": "Ctrl Лівий",
"KeyMacControlLeft": "⌃ Лівий",
"KeyControlRight": "Ctrl Правий",
"KeyMacControlRight": "⌃ Правий",
"KeyAltLeft": "Alt Лівий",
"KeyMacAltLeft": "⌥ Лівий",
"KeyAltRight": "Alt Правий",
"KeyMacAltRight": "⌥ Правий",
"KeyWinLeft": "⊞ Лівий",
"KeyMacWinLeft": "⌘ Лівий",
"KeyWinRight": "⊞ Правий",
"KeyMacWinRight": "⌘ Правий",
"KeyUnknown": "Unknown",
"KeyShiftLeft": "Shift Left",
"KeyShiftRight": "Shift Right",
"KeyControlLeft": "Ctrl Left",
"KeyMacControlLeft": "⌃ Left",
"KeyControlRight": "Ctrl Right",
"KeyMacControlRight": "⌃ Right",
"KeyAltLeft": "Alt Left",
"KeyMacAltLeft": "⌥ Left",
"KeyAltRight": "Alt Right",
"KeyMacAltRight": "⌥ Right",
"KeyWinLeft": "⊞ Left",
"KeyMacWinLeft": "⌘ Left",
"KeyWinRight": "⊞ Right",
"KeyMacWinRight": "⌘ Right",
"KeyMenu": "Menu",
"KeyUp": "Up",
"KeyDown": "Down",
"KeyLeft": "Вліво",
"KeyRight": "Вправо",
"KeyLeft": "Left",
"KeyRight": "Right",
"KeyEnter": "Enter",
"KeyEscape": "Escape",
"KeySpace": "Пробіл",
"KeySpace": "Space",
"KeyTab": "Tab",
"KeyBackSpace": "Backspace",
"KeyInsert": "Insert",
@@ -338,7 +327,7 @@
"KeyPrintScreen": "Print Screen",
"KeyPause": "Pause",
"KeyNumLock": "Num Lock",
"KeyClear": "Очистити",
"KeyClear": "Clear",
"KeyKeypad0": "Keypad 0",
"KeyKeypad1": "Keypad 1",
"KeyKeypad2": "Keypad 2",
@@ -377,17 +366,17 @@
"KeyPeriod": ".",
"KeySlash": "/",
"KeyBackSlash": "\\",
"KeyUnbound": "Відв'язати",
"GamepadLeftStick": "L Кнопка Стіку",
"GamepadRightStick": "R Кнопка Стіку",
"GamepadLeftShoulder": "Лівий Бампер",
"GamepadRightShoulder": "Правий Бампер",
"GamepadLeftTrigger": "Лівий Тригер",
"GamepadRightTrigger": "Правий Тригер",
"GamepadDpadUp": "Вверх",
"GamepadDpadDown": "Вниз",
"GamepadDpadLeft": "Вліво",
"GamepadDpadRight": "Вправо",
"KeyUnbound": "Unbound",
"GamepadLeftStick": "L Stick Button",
"GamepadRightStick": "R Stick Button",
"GamepadLeftShoulder": "Left Shoulder",
"GamepadRightShoulder": "Right Shoulder",
"GamepadLeftTrigger": "Left Trigger",
"GamepadRightTrigger": "Right Trigger",
"GamepadDpadUp": "Up",
"GamepadDpadDown": "Down",
"GamepadDpadLeft": "Left",
"GamepadDpadRight": "Right",
"GamepadMinus": "-",
"GamepadPlus": "+",
"GamepadGuide": "Guide",
@@ -397,12 +386,12 @@
"GamepadPaddle3": "Paddle 3",
"GamepadPaddle4": "Paddle 4",
"GamepadTouchpad": "Touchpad",
"GamepadSingleLeftTrigger0": "Лівий Тригер 0",
"GamepadSingleRightTrigger0": "Правий Тригер 0",
"GamepadSingleLeftTrigger1": "Лівий Тригер 1",
"GamepadSingleRightTrigger1": "Правий Тригер 1",
"StickLeft": "Лівий Стік",
"StickRight": "Правий Стік",
"GamepadSingleLeftTrigger0": "Left Trigger 0",
"GamepadSingleRightTrigger0": "Right Trigger 0",
"GamepadSingleLeftTrigger1": "Left Trigger 1",
"GamepadSingleRightTrigger1": "Right Trigger 1",
"StickLeft": "Left Stick",
"StickRight": "Right Stick",
"UserProfilesSelectedUserProfile": "Вибраний профіль користувача:",
"UserProfilesSaveProfileName": "Зберегти ім'я профілю",
"UserProfilesChangeProfileImage": "Змінити зображення профілю",
@@ -419,8 +408,8 @@
"InputDialogTitle": "Діалог введення",
"InputDialogOk": "Гаразд",
"InputDialogCancel": "Скасувати",
"InputDialogCancelling": "Скасування",
"InputDialogClose": "Закрити",
"InputDialogCancelling": "Cancelling",
"InputDialogClose": "Close",
"InputDialogAddNewProfileTitle": "Виберіть ім'я профілю",
"InputDialogAddNewProfileHeader": "Будь ласка, введіть ім'я профілю",
"InputDialogAddNewProfileSubtext": "(Макс. довжина: {0})",
@@ -428,7 +417,7 @@
"AvatarSetBackgroundColor": "Встановити колір фону",
"AvatarClose": "Закрити",
"ControllerSettingsLoadProfileToolTip": "Завантажити профіль",
"ControllerSettingsViewProfileToolTip": "Показати профіль",
"ControllerSettingsViewProfileToolTip": "View Profile",
"ControllerSettingsAddProfileToolTip": "Додати профіль",
"ControllerSettingsRemoveProfileToolTip": "Видалити профіль",
"ControllerSettingsSaveProfileToolTip": "Зберегти профіль",
@@ -438,7 +427,7 @@
"GameListContextMenuToggleFavorite": "Перемкнути вибране",
"GameListContextMenuToggleFavoriteToolTip": "Перемкнути улюблений статус гри",
"SettingsTabGeneralTheme": "Тема:",
"SettingsTabGeneralThemeAuto": "Авто.",
"SettingsTabGeneralThemeAuto": "Auto",
"SettingsTabGeneralThemeDark": "Темна",
"SettingsTabGeneralThemeLight": "Світла",
"ControllerSettingsConfigureGeneral": "Налаштування",
@@ -457,7 +446,7 @@
"DialogExitSubMessage": "Усі незбережені дані буде втрачено!",
"DialogMessageCreateSaveErrorMessage": "Під час створення вказаних даних збереження сталася помилка: {0}",
"DialogMessageFindSaveErrorMessage": "Під час пошуку вказаних даних збереження сталася помилка: {0}",
"FolderDialogExtractTitle": "Виберіть теку для видобування",
"FolderDialogExtractTitle": "Виберіть папку для видобування",
"DialogNcaExtractionMessage": "Видобування розділу {0} з {1}...",
"DialogNcaExtractionTitle": "Екстрактор розділів NCA",
"DialogNcaExtractionMainNcaNotFoundErrorMessage": "Помилка видобування. Основний NCA не був присутній у вибраному файлі.",
@@ -472,7 +461,7 @@
"DialogUpdaterExtractionMessage": "Видобування оновлення...",
"DialogUpdaterRenamingMessage": "Перейменування оновлення...",
"DialogUpdaterAddingFilesMessage": "Додавання нового оновлення...",
"DialogUpdaterShowChangelogMessage": "Показати список змін",
"DialogUpdaterShowChangelogMessage": "Show Changelog",
"DialogUpdaterCompleteMessage": "Оновлення завершено!",
"DialogUpdaterRestartMessage": "Перезапустити Ryujinx зараз?",
"DialogUpdaterNoInternetMessage": "Ви не підключені до Інтернету!",
@@ -521,12 +510,12 @@
"DialogFirmwareInstallerFirmwareInstallWaitMessage": "Встановлення прошивки...",
"DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Версію системи {0} успішно встановлено.",
"DialogKeysInstallerKeysNotFoundErrorMessage": "An invalid Keys file was found in {0}",
"DialogKeysInstallerKeysInstallTitle": "Встановлення Ключів",
"DialogKeysInstallerKeysInstallMessage": "Новий файл Ключів буде встановлено",
"DialogKeysInstallerKeysInstallSubMessage": "\n\nЦе замінить собою поточні файли Ключів.",
"DialogKeysInstallerKeysInstallConfirmMessage": "\n\nВи хочете продовжити?",
"DialogKeysInstallerKeysInstallWaitMessage": "Встановлення Ключів...",
"DialogKeysInstallerKeysInstallSuccessMessage": "Нові ключі встановлено.",
"DialogKeysInstallerKeysInstallTitle": "Install Keys",
"DialogKeysInstallerKeysInstallMessage": "New Keys file will be installed.",
"DialogKeysInstallerKeysInstallSubMessage": "\n\nThis may replace some of the current installed Keys.",
"DialogKeysInstallerKeysInstallConfirmMessage": "\n\nDo you want to continue?",
"DialogKeysInstallerKeysInstallWaitMessage": "Installing Keys...",
"DialogKeysInstallerKeysInstallSuccessMessage": "New Keys file successfully installed.",
"DialogUserProfileDeletionWarningMessage": "Якщо вибраний профіль буде видалено, інші профілі не відкриватимуться",
"DialogUserProfileDeletionConfirmMessage": "Ви хочете видалити вибраний профіль",
"DialogUserProfileUnsavedChangesTitle": "Увага — Незбережені зміни",
@@ -561,14 +550,14 @@
"MenuBarOptionsPauseEmulation": "Пауза",
"MenuBarOptionsResumeEmulation": "Продовжити",
"AboutUrlTooltipMessage": "Натисніть, щоб відкрити сайт Ryujinx у браузері за замовчування.",
"AboutDisclaimerMessage": "Ryujinx жодним чином не пов’язаний з Nintendo™,\nчи будь-яким із їхніх партнерів.",
"AboutDisclaimerMessage": "Ryujinx жодним чином не пов’язано з Nintendo™,\nчи будь-яким із їхніх партнерів.",
"AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) використовується в нашій емуляції Amiibo.",
"AboutGithubUrlTooltipMessage": "Натисніть, щоб відкрити сторінку GitHub Ryujinx у браузері за замовчуванням.",
"AboutDiscordUrlTooltipMessage": "Натисніть, щоб відкрити запрошення на сервер Discord Ryujinx у браузері за замовчуванням.",
"AboutRyujinxAboutTitle": "Про програму:",
"AboutRyujinxAboutContent": "Ryujinx — це емулятор для Nintendo Switch™.\nОтримуйте всі останні новини в нашому Discord.\nРозробники, які хочуть зробити внесок, можуть дізнатися більше на нашому GitHub або в Discord.",
"AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.",
"AboutRyujinxMaintainersTitle": "Підтримується:",
"AboutRyujinxFormerMaintainersTitle": "Минулі розробники:",
"AboutRyujinxFormerMaintainersTitle": "Formerly Maintained By:",
"AboutRyujinxMaintainersContentTooltipMessage": "Натисніть, щоб відкрити сторінку співавторів у вашому браузері за замовчування.",
"AmiiboSeriesLabel": "Серія Amiibo",
"AmiiboCharacterLabel": "Персонаж",
@@ -596,9 +585,9 @@
"AddGameDirBoxTooltip": "Введіть каталог ігор, щоб додати до списку",
"AddGameDirTooltip": "Додати каталог гри до списку",
"RemoveGameDirTooltip": "Видалити вибраний каталог гри",
"AddAutoloadDirBoxTooltip": "Введіть шлях автозавантаження для додавання до списку",
"AddAutoloadDirTooltip": "Додайте шлях автозавантаження для додавання до списку",
"RemoveAutoloadDirTooltip": "Видалити вибраний каталог автозавантаження",
"AddAutoloadDirBoxTooltip": "Enter an autoload directory to add to the list",
"AddAutoloadDirTooltip": "Add an autoload directory to the list",
"RemoveAutoloadDirTooltip": "Remove selected autoload directory",
"CustomThemeCheckTooltip": "Використовуйте користувацьку тему Avalonia для графічного інтерфейсу, щоб змінити вигляд меню емулятора",
"CustomThemePathTooltip": "Шлях до користувацької теми графічного інтерфейсу",
"CustomThemeBrowseTooltip": "Огляд користувацької теми графічного інтерфейсу",
@@ -611,7 +600,7 @@
"TimeTooltip": "Змінити час системи",
"VSyncToggleTooltip": "Емульована вертикальна синхронізація консолі. По суті, обмежувач кадрів для більшості ігор; його вимкнення може призвести до того, що ігри працюватимуть на вищій швидкості, екрани завантаження триватимуть довше чи зупинятимуться.\n\nМожна перемикати в грі гарячою клавішею (За умовчанням F1). Якщо ви плануєте вимкнути функцію, рекомендуємо зробити це через гарячу клавішу.\n\nЗалиште увімкненим, якщо не впевнені.",
"PptcToggleTooltip": "Зберігає перекладені функції JIT, щоб їх не потрібно було перекладати кожного разу, коли гра завантажується.\n\nЗменшує заїкання та значно прискорює час завантаження після першого завантаження гри.\n\nЗалиште увімкненим, якщо не впевнені.",
"LowPowerPptcToggleTooltip": "Завантажувати PPTC використовуючи третину від кількості ядер.",
"LowPowerPptcToggleTooltip": "Load the PPTC using a third of the amount of cores.",
"FsIntegrityToggleTooltip": "Перевіряє наявність пошкоджених файлів під час завантаження гри, і якщо виявлено пошкоджені файли, показує помилку хешу в журналі.\n\nНе впливає на продуктивність і призначений для усунення несправностей.\n\nЗалиште увімкненим, якщо не впевнені.",
"AudioBackendTooltip": "Змінює серверну частину, яка використовується для відтворення аудіо.\n\nSDL2 є кращим, тоді як OpenAL і SoundIO використовуються як резервні варіанти. Dummy не матиме звуку.\n\nВстановіть SDL2, якщо не впевнені.",
"MemoryManagerTooltip": "Змінює спосіб відображення та доступу до гостьової пам’яті. Значно впливає на продуктивність емульованого ЦП.\n\nВстановіть «Неперевірений хост», якщо не впевнені.",
@@ -644,10 +633,10 @@
"DebugLogTooltip": "Друкує повідомлення журналу налагодження на консолі.\n\nВикористовуйте це лише за спеціальною вказівкою співробітника, оскільки це ускладнить читання журналів і погіршить роботу емулятора.",
"LoadApplicationFileTooltip": "Відкриває файловий провідник, щоб вибрати для завантаження сумісний файл Switch",
"LoadApplicationFolderTooltip": "Відкриває файловий провідник, щоб вибрати сумісну з комутатором розпаковану програму для завантаження",
"LoadDlcFromFolderTooltip": "Відкрийте провідник файлів, щоб вибрати одну або кілька папок для масового завантаження DLC",
"LoadTitleUpdatesFromFolderTooltip": "Відкрийте провідник файлів, щоб вибрати одну або кілька папок для масового завантаження оновлень заголовків",
"OpenRyujinxFolderTooltip": "Відкриває теку файлової системи Ryujinx",
"OpenRyujinxLogsTooltip": "Відкриває теку, куди записуються журнали",
"LoadDlcFromFolderTooltip": "Open a file explorer to choose one or more folders to bulk load DLC from",
"LoadTitleUpdatesFromFolderTooltip": "Open a file explorer to choose one or more folders to bulk load title updates from",
"OpenRyujinxFolderTooltip": "Відкриває папку файлової системи Ryujinx",
"OpenRyujinxLogsTooltip": "Відкриває папку, куди записуються журнали",
"ExitTooltip": "Виходить з Ryujinx",
"OpenSettingsTooltip": "Відкриває вікно налаштувань",
"OpenProfileManagerTooltip": "Відкриває вікно диспетчера профілів користувачів",
@@ -675,6 +664,7 @@
"SettingsTabNetworkConnection": "Підключення до мережі",
"SettingsTabCpuCache": "Кеш ЦП",
"SettingsTabCpuMemory": "Пам'ять ЦП",
"DialogUpdaterFlatpakNotSupportedMessage": "Будь ласка, оновіть Ryujinx через FlatHub.",
"UpdaterDisabledWarningTitle": "Програму оновлення вимкнено!",
"ControllerSettingsRotate90": "Повернути на 90° за годинниковою стрілкою",
"IconSize": "Розмір значка",
@@ -696,14 +686,14 @@
"OpenSetupGuideMessage": "Відкрити посібник із налаштування",
"NoUpdate": "Немає оновлень",
"TitleUpdateVersionLabel": "Версія {0} - {1}",
"TitleBundledUpdateVersionLabel": "Комплектні: Версія {0}",
"TitleBundledDlcLabel": "Комплектні:",
"TitleXCIStatusPartialLabel": "Часткові",
"TitleXCIStatusTrimmableLabel": "Необрізані",
"TitleXCIStatusUntrimmableLabel": "Обрізані",
"TitleXCIStatusFailedLabel": "(Невдача)",
"TitleXCICanSaveLabel": "Зберегти {0:n0} Мб",
"TitleXCISavingLabel": "Збережено {0:n0} Мб",
"TitleBundledUpdateVersionLabel": "Bundled: Version {0}",
"TitleBundledDlcLabel": "Bundled:",
"TitleXCIStatusPartialLabel": "Partial",
"TitleXCIStatusTrimmableLabel": "Untrimmed",
"TitleXCIStatusUntrimmableLabel": "Trimmed",
"TitleXCIStatusFailedLabel": "(Failed)",
"TitleXCICanSaveLabel": "Save {0:n0} Mb",
"TitleXCISavingLabel": "Saved {0:n0} Mb",
"RyujinxInfo": "Ryujin x - Інформація",
"RyujinxConfirm": "Ryujinx - Підтвердження",
"FileDialogAllTypes": "Всі типи",
@@ -711,8 +701,8 @@
"SwkbdMinCharacters": "Мінімальна кількість символів: {0}",
"SwkbdMinRangeCharacters": "Має бути {0}-{1} символів",
"CabinetTitle": "Cabinet Dialog",
"CabinetDialog": "Вкажіть Ваше нове ім'я Amiibo",
"CabinetScanDialog": "Будь ласка, проскануйте Ваш Amiibo.",
"CabinetDialog": "Enter your Amiibo's new name",
"CabinetScanDialog": "Please scan your Amiibo now.",
"SoftwareKeyboard": "Програмна клавіатура",
"SoftwareKeyboardModeNumeric": "Повинно бути лише 0-9 або “.”",
"SoftwareKeyboardModeAlphabet": "Повинно бути лише не CJK-символи",
@@ -740,15 +730,12 @@
"AllSupportedFormats": "Усі підтримувані формати",
"RyujinxUpdater": "Програма оновлення Ryujinx",
"SettingsTabHotkeys": "Гарячі клавіші клавіатури",
"SettingsTabHotkeysToggleVSyncModeHotkey": "Перемкнути VSync режим:",
"SettingsTabHotkeysHotkeys": "Гарячі клавіші клавіатури",
"SettingsTabHotkeysToggleVsyncHotkey": "Увімк/вимк вертикальну синхронізацію:",
"SettingsTabHotkeysScreenshotHotkey": "Знімок екрана:",
"SettingsTabHotkeysShowUiHotkey": "Показати інтерфейс:",
"SettingsTabHotkeysPauseHotkey": "Пауза:",
"SettingsTabHotkeysToggleMuteHotkey": "Вимкнути звук:",
"SettingsTabHotkeysIncrementCustomVSyncIntervalHotkey": "Підвищити користувацьку частоту оновлення",
"SettingsTabHotkeysDecrementCustomVSyncIntervalHotkey": "Понизити користувацьку частоту оновлення",
"ControllerMotionTitle": "Налаштування керування рухом",
"ControllerRumbleTitle": "Налаштування вібрації",
"SettingsSelectThemeFileDialogTitle": "Виберіть файл теми",
@@ -761,53 +748,53 @@
"SelectDlcDialogTitle": "Виберіть файли DLC",
"SelectUpdateDialogTitle": "Виберіть файли оновлення",
"SelectModDialogTitle": "Виберіть теку з модами",
"TrimXCIFileDialogTitle": "Перевірити та Обрізати XCI файл",
"TrimXCIFileDialogPrimaryText": "Ця функція спочатку перевірить вільний простір, а потім обрізатиме файл XCI для економії місця на диску.",
"TrimXCIFileDialogSecondaryText": "Поточний розмір файла: {0:n} MB\nРозмір файлів гри: {1:n} MB\nЕкономія місця: {2:n} MB",
"TrimXCIFileNoTrimNecessary": "XCI файл не потребує обрізання. Перевірте журнали для додаткової інформації",
"TrimXCIFileNoUntrimPossible": "XCI файл не може бути обрізаний. Перевірте журнали для додаткової інформації",
"TrimXCIFileReadOnlyFileCannotFix": "XCI файл Тільки для Читання і не може бути прочитаним. Перевірте журнали додаткової інформації",
"TrimXCIFileFileSizeChanged": "Розмір файлу XCI змінився з моменту сканування. Перевірте, чи не записується файл, та спробуйте знову",
"TrimXCIFileFreeSpaceCheckFailed": "Файл XCI містить дані в зоні вільного простору, тому обрізка небезпечна",
"TrimXCIFileInvalidXCIFile": "XCI Файл містить недійсні дані. Перевірте журнали для додаткової інформації",
"TrimXCIFileFileIOWriteError": "XCI Файл файл не вдалося відкрити для запису. Перевірте журнали для додаткової інформації",
"TrimXCIFileFailedPrimaryText": "Не вдалося обрізати файл XCI",
"TrimXCIFileCancelled": "Операція перервана",
"TrimXCIFileFileUndertermined": "Операція не проводилася",
"TrimXCIFileDialogTitle": "Check and Trim XCI File",
"TrimXCIFileDialogPrimaryText": "This function will first check the empty space and then trim the XCI File to save disk space.",
"TrimXCIFileDialogSecondaryText": "Current File Size: {0:n} MB\nGame Data Size: {1:n} MB\nDisk Space Savings: {2:n} MB",
"TrimXCIFileNoTrimNecessary": "XCI File does not need to be trimmed. Check logs for further details",
"TrimXCIFileNoUntrimPossible": "XCI File cannot be untrimmed. Check logs for further details",
"TrimXCIFileReadOnlyFileCannotFix": "XCI File is Read Only and could not be made writable. Check logs for further details",
"TrimXCIFileFileSizeChanged": "XCI File has changed in size since it was scanned. Please check the file is not being written to and try again.",
"TrimXCIFileFreeSpaceCheckFailed": "XCI File has data in the free space area, it is not safe to trim",
"TrimXCIFileInvalidXCIFile": "XCI File contains invalid data. Check logs for further details",
"TrimXCIFileFileIOWriteError": "XCI File could not be opened for writing. Check logs for further details",
"TrimXCIFileFailedPrimaryText": "Trimming of the XCI file failed",
"TrimXCIFileCancelled": "The operation was cancelled",
"TrimXCIFileFileUndertermined": "No operation was performed",
"UserProfileWindowTitle": "Менеджер профілів користувачів",
"CheatWindowTitle": "Менеджер читів",
"DlcWindowTitle": "Менеджер вмісту для завантаження",
"ModWindowTitle": "Керувати модами для {0} ({1})",
"UpdateWindowTitle": "Менеджер оновлення назв",
"XCITrimmerWindowTitle": "Обрізка XCI Файлів",
"XCITrimmerTitleStatusCount": "{0} з {1} тайтл(ів) обрано",
"XCITrimmerTitleStatusCountWithFilter": "{0} з {1} тайтл(ів) обрано ({2} відображається)",
"XCITrimmerTitleStatusTrimming": "Обрізка {0} тайтл(ів)...",
"XCITrimmerTitleStatusUntrimming": "Необрізаних {0} тайтл(ів)...",
"XCITrimmerTitleStatusFailed": "Невдача",
"XCITrimmerPotentialSavings": "Потенційна економія",
"XCITrimmerActualSavings": "Зекономлено",
"XCITrimmerSavingsMb": "{0:n0} Мб",
"XCITrimmerSelectDisplayed": "Вибрати показане",
"XCITrimmerDeselectDisplayed": "Скасувати вибір показаного",
"XCITrimmerSortName": "Заголовок",
"XCITrimmerSortSaved": "Економія місця",
"XCITrimmerTrim": "Обрізка",
"XCITrimmerUntrim": "Зшивання",
"UpdateWindowUpdateAddedMessage": "{0} нове оновлення додано",
"UpdateWindowBundledContentNotice": "Вбудовані оновлення не можуть бути видалені, лише вимкнені.",
"XCITrimmerWindowTitle": "XCI File Trimmer",
"XCITrimmerTitleStatusCount": "{0} of {1} Title(s) Selected",
"XCITrimmerTitleStatusCountWithFilter": "{0} of {1} Title(s) Selected ({2} displayed)",
"XCITrimmerTitleStatusTrimming": "Trimming {0} Title(s)...",
"XCITrimmerTitleStatusUntrimming": "Untrimming {0} Title(s)...",
"XCITrimmerTitleStatusFailed": "Failed",
"XCITrimmerPotentialSavings": "Potential Savings",
"XCITrimmerActualSavings": "Actual Savings",
"XCITrimmerSavingsMb": "{0:n0} Mb",
"XCITrimmerSelectDisplayed": "Select Shown",
"XCITrimmerDeselectDisplayed": "Deselect Shown",
"XCITrimmerSortName": "Title",
"XCITrimmerSortSaved": "Space Savings",
"XCITrimmerTrim": "Trim",
"XCITrimmerUntrim": "Untrim",
"UpdateWindowUpdateAddedMessage": "{0} new update(s) added",
"UpdateWindowBundledContentNotice": "Bundled updates cannot be removed, only disabled.",
"CheatWindowHeading": "Коди доступні для {0} [{1}]",
"BuildId": "ID збірки:",
"DlcWindowBundledContentNotice": "Вбудований DLC не може бути видаленим, лише вимкненим.",
"DlcWindowHeading": "{0} DLC доступно",
"DlcWindowDlcAddedMessage": "{0} нового завантажувального вмісту додано",
"AutoloadDlcAddedMessage": "{0} нового завантажувального вмісту додано",
"AutoloadDlcRemovedMessage": "{0} відсутнього завантажувального вмісту видалено",
"AutoloadUpdateAddedMessage": "{0} нових оновлень додано",
"AutoloadUpdateRemovedMessage": "{0} відсутніх оновлень видалено",
"DlcWindowBundledContentNotice": "Bundled DLC cannot be removed, only disabled.",
"DlcWindowHeading": "{0} DLC(s) available",
"DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcRemovedMessage": "{0} missing downloadable content(s) removed",
"AutoloadUpdateAddedMessage": "{0} new update(s) added",
"AutoloadUpdateRemovedMessage": "{0} missing update(s) removed",
"ModWindowHeading": "{0} мод(ів)",
"UserProfilesEditProfile": "Редагувати вибране",
"Continue": "Продовжити",
"Continue": "Continue",
"Cancel": "Скасувати",
"Save": "Зберегти",
"Discard": "Скасувати",
@@ -845,7 +832,7 @@
"Name": "Назва",
"Size": "Розмір",
"Search": "Пошук",
"UserProfilesRecoverLostAccounts": "Відновлення профілів",
"UserProfilesRecoverLostAccounts": "Відновлення втрачених облікових записів",
"Recover": "Відновити",
"UserProfilesRecoverHeading": "Знайдено збереження для наступних облікових записів",
"UserProfilesRecoverEmptyList": "Немає профілів для відновлення",
@@ -877,15 +864,15 @@
"MultiplayerModeDisabled": "Вимкнено",
"MultiplayerModeLdnMitm": "ldn_mitm",
"MultiplayerModeLdnRyu": "RyuLDN",
"MultiplayerDisableP2P": "Вимкнути хостинг P2P мережі (може збільшити затримку)",
"MultiplayerDisableP2PTooltip": "Вимкнути хостинг P2P мережі, піри будуть підключатися через майстер-сервер замість прямого з'єднання з вами.",
"LdnPassphrase": "Мережевий пароль:",
"LdnPassphraseTooltip": "Ви зможете бачити лише ті ігри, які мають такий самий пароль, як і у вас.",
"LdnPassphraseInputTooltip": "Введіть пароль у форматі Ryujinx-<8 символів>. Ви зможете бачити лише ті ігри, які мають такий самий пароль, як і у вас.",
"LdnPassphraseInputPublic": "(публічний)",
"GenLdnPass": "Згенерувати випадкову",
"GenLdnPassTooltip": "Генерує новий пароль, яким можна поділитися з іншими гравцями.",
"ClearLdnPass": "Очистити",
"ClearLdnPassTooltip": "Очищає поточну пароль, повертаючись до публічної мережі.",
"InvalidLdnPassphrase": "Невірний пароль! Має бути в форматі \"Ryujinx-<8 символів>\""
"MultiplayerDisableP2P": "Disable P2P Network Hosting (may increase latency)",
"MultiplayerDisableP2PTooltip": "Disable P2P network hosting, peers will proxy through the master server instead of connecting to you directly.",
"LdnPassphrase": "Network Passphrase:",
"LdnPassphraseTooltip": "You will only be able to see hosted games with the same passphrase as you.",
"LdnPassphraseInputTooltip": "Enter a passphrase in the format Ryujinx-<8 hex chars>. You will only be able to see hosted games with the same passphrase as you.",
"LdnPassphraseInputPublic": "(public)",
"GenLdnPass": "Generate Random",
"GenLdnPassTooltip": "Generates a new passphrase, which can be shared with other players.",
"ClearLdnPass": "Clear",
"ClearLdnPassTooltip": "Clears the current passphrase, returning to the public network.",
"InvalidLdnPassphrase": "Invalid Passphrase! Must be in the format \"Ryujinx-<8 hex chars>\""
}

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,361 @@
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.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);
}
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,14 +11,9 @@ 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.OpenGL;
using Ryujinx.Graphics.Vulkan;
using Ryujinx.Graphics.Vulkan.MoltenVK;
using Ryujinx.Headless.SDL2.OpenGL;
using Ryujinx.Headless.SDL2.Vulkan;
using Ryujinx.HLE;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
@@ -31,22 +22,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;
@@ -56,20 +41,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())
{
@@ -97,7 +80,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:");
@@ -106,238 +89,80 @@ namespace Ryujinx.Headless.SDL2
});
}
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)
{
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();
@@ -352,7 +177,7 @@ namespace Ryujinx.Headless.SDL2
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
GraphicsConfig.EnableShaderCache = true;
GraphicsConfig.EnableShaderCache = !option.DisableShaderCache;
if (OperatingSystem.IsMacOS())
{
@@ -363,15 +188,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}\")");
@@ -380,7 +203,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}\")");
@@ -397,7 +220,7 @@ namespace Ryujinx.Headless.SDL2
return;
}
_inputConfiguration = new List<InputConfig>();
_inputConfiguration ??= [];
_enableKeyboard = option.EnableKeyboard;
_enableMouse = option.EnableMouse;
@@ -421,6 +244,7 @@ namespace Ryujinx.Headless.SDL2
LoadPlayerConfiguration(option.InputProfile8Name, option.InputId8, PlayerIndex.Player8);
LoadPlayerConfiguration(option.InputProfileHandheldName, option.InputIdHandheld, PlayerIndex.Handheld);
if (_inputConfiguration.Count == 0)
{
return;
@@ -431,7 +255,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);
@@ -515,83 +339,6 @@ namespace Ryujinx.Headless.SDL2
: new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode, options.IgnoreControllerApplet);
}
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);
}
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

@@ -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,14 +1,169 @@
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; }
@@ -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; }

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)
@@ -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;
@@ -53,6 +54,12 @@ 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();
@@ -222,7 +229,7 @@ namespace Ryujinx.Ava
UseHardwareAcceleration = CommandLineState.OverrideHardwareAcceleration.Value;
}
private static void PrintSystemInfo()
internal static void PrintSystemInfo()
{
Logger.Notice.Print(LogClass.Application, $"{App.FullAppName} Version: {Version}");
SystemInfo.Gather().Print();
@@ -239,7 +246,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}";
@@ -254,7 +261,7 @@ namespace Ryujinx.Ava
Exit();
}
public static void Exit()
internal static void Exit()
{
DiscordIntegrationModule.Exit();

View File

@@ -43,6 +43,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" />
@@ -162,6 +163,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\en_US.json" />