mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-06-06 04:19:15 +00:00
Compare commits
81 Commits
0a59f8d154
...
refactor/a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a6a95754c | ||
|
|
49891ba7af | ||
|
|
89ea41ef84 | ||
|
|
f8167eb625 | ||
|
|
bab160d650 | ||
|
|
e8cc252d9a | ||
|
|
8065dec744 | ||
|
|
e9c31bea3b | ||
|
|
5511ff5686 | ||
|
|
bf7f978f9d | ||
|
|
1f9bfab923 | ||
|
|
5d8cb3e378 | ||
|
|
708186d8d2 | ||
|
|
ad34237fc6 | ||
|
|
bf083a716c | ||
|
|
2d2661298c | ||
|
|
c4788154fd | ||
|
|
49dd56953c | ||
|
|
722eb93554 | ||
|
|
b0179e6433 | ||
|
|
1d3d4197b7 | ||
|
|
d2b2d65061 | ||
|
|
e1dcaef709 | ||
|
|
b222f671f3 | ||
|
|
4d0cd61b6a | ||
|
|
4e86159bce | ||
|
|
0d66cfa281 | ||
|
|
e656de5fff | ||
|
|
518dd65484 | ||
|
|
88421959a6 | ||
|
|
87ce5162be | ||
|
|
a3e10a1e5a | ||
|
|
1e06c86d47 | ||
|
|
3a3e5e5c5a | ||
|
|
c1c47d308d | ||
|
|
2b929c5537 | ||
|
|
ddfb56c424 | ||
|
|
c9c4ed67b9 | ||
|
|
44d77f8e59 | ||
|
|
bd11cbde08 | ||
|
|
f749cf90b6 | ||
|
|
1b1ceeaa11 | ||
|
|
3f9d37da83 | ||
|
|
0c0b6404b1 | ||
|
|
47c0180ba4 | ||
|
|
8705fabdb0 | ||
|
|
2a4eb8c529 | ||
|
|
51d0d888fb | ||
|
|
a69eab4619 | ||
|
|
93f23cde4f | ||
|
|
93c76a5839 | ||
|
|
0040f884d4 | ||
|
|
bd3b147002 | ||
|
|
b3ac7a2b94 | ||
|
|
96f8d519e6 | ||
|
|
433dd58f8c | ||
|
|
4347afff77 | ||
|
|
46f5d1d31a | ||
|
|
578b6bf00e | ||
|
|
9905ee17c2 | ||
|
|
7f0e82fe48 | ||
|
|
69c22a744e | ||
|
|
35b1531102 | ||
|
|
ea1185e96a | ||
|
|
31e0430b06 | ||
|
|
efb8658194 | ||
|
|
e909ea3210 | ||
|
|
2a688abeec | ||
|
|
9f88d68268 | ||
|
|
fe685c3f8f | ||
|
|
2edc165e26 | ||
|
|
fa7cb22240 | ||
|
|
c5b4add186 | ||
|
|
0079e0bfa5 | ||
|
|
25b70e05d7 | ||
|
|
78eb95733a | ||
|
|
17a768a5df | ||
|
|
339cddd0e2 | ||
|
|
66d9bc5a35 | ||
|
|
3e393449aa | ||
|
|
744c41937b |
5
.forgejo/issue_template/config.yml
Normal file
5
.forgejo/issue_template/config.yml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: true
|
||||||
|
contact_links:
|
||||||
|
- name: Ryubing Issue Tracker
|
||||||
|
url: https://github.com/Ryubing/Issues/issues/
|
||||||
|
about: "Please use this GitHub repository instead of creating issues on this Forgejo repository. Blank issues should only be used by maintainers and authorized bots. Issues made on this repository can and will be deleted."
|
||||||
@@ -10,6 +10,10 @@ gpu:
|
|||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file: ['src/Ryujinx.Graphics.*/**', 'src/Spv.Generator/**', 'src/Ryujinx.ShaderTools/**']
|
- any-glob-to-any-file: ['src/Ryujinx.Graphics.*/**', 'src/Spv.Generator/**', 'src/Ryujinx.ShaderTools/**']
|
||||||
|
|
||||||
|
input:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: ['src/Ryujinx.Input*/**', 'src/Ryujinx/UI/Views/Input/**']
|
||||||
|
|
||||||
'graphics-backend:opengl':
|
'graphics-backend:opengl':
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file: 'src/Ryujinx.Graphics.OpenGL/**'
|
- any-glob-to-any-file: 'src/Ryujinx.Graphics.OpenGL/**'
|
||||||
@@ -18,17 +22,17 @@ gpu:
|
|||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file: ['src/Ryujinx.Graphics.Vulkan/**', 'src/Spv.Generator/**']
|
- any-glob-to-any-file: ['src/Ryujinx.Graphics.Vulkan/**', 'src/Spv.Generator/**']
|
||||||
|
|
||||||
'graphics-backend:metal':
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file: ['src/Ryujinx.Graphics.Metal/**', 'src/Ryujinx.Graphics.Metal.SharpMetalExtensions/**']
|
|
||||||
|
|
||||||
gui:
|
gui:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.UI.Common/**', 'src/Ryujinx.UI.LocaleGenerator/**']
|
- any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.UI.LocaleGenerator/**']
|
||||||
|
|
||||||
horizon:
|
'horizon/hle':
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file: ['src/Ryujinx.HLE/**', 'src/Ryujinx.Horizon/**']
|
- any-glob-to-any-file: ['src/Ryujinx.HLE/**', 'src/Ryujinx.HLE.Generators/**', 'src/Ryujinx.Horizon/**']
|
||||||
|
|
||||||
|
i18n:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: ['assets/**/*.json', 'src/Ryujinx.UI.LocaleGenerator/**']
|
||||||
|
|
||||||
kernel:
|
kernel:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
@@ -36,7 +40,7 @@ kernel:
|
|||||||
|
|
||||||
infra:
|
infra:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file: ['.github/**', 'distribution/**', 'Directory.Packages.props', 'src/Ryujinx.BuildValidationTasks/**']
|
- any-glob-to-any-file: ['.forgejo/**', 'distribution/**', 'Directory.Packages.props', 'src/Ryujinx.BuildValidationTasks/**']
|
||||||
|
|
||||||
documentation:
|
documentation:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
@@ -44,4 +48,4 @@ documentation:
|
|||||||
|
|
||||||
ldn:
|
ldn:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file: 'src/Ryujinx.HLE/HOS/Services/Ldn/**'
|
- any-glob-to-any-file: ['src/Ryujinx.HLE/HOS/Services/Ldn/**', 'src/Ryujinx/UI/Windows/LdnGamesListWindow.*', 'src/Ryujinx/UI/ViewModels/LdnGamesListViewModel.cs']
|
||||||
35
.forgejo/renovate.json
Normal file
35
.forgejo/renovate.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"renovate/config"
|
||||||
|
],
|
||||||
|
"enabledManagers": ["nuget", "github-actions"],
|
||||||
|
"packageRules": [
|
||||||
|
{
|
||||||
|
// require approval for *all* NuGet package updates, not just major versions.
|
||||||
|
"matchDepTypes": "nuget",
|
||||||
|
"dependencyDashboardApproval": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Ignore Gommon for automatic updates. I make breaking changes on minor updates not infrequently.
|
||||||
|
"matchDepNames": "Gommon",
|
||||||
|
"matchDepTypes": "nuget",
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "group Silk.NET packages",
|
||||||
|
"extends": ["renovate/config//groups/silkdotnet.json"],
|
||||||
|
"groupName": "Silk.NET"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "group OpenTK packages",
|
||||||
|
"extends": ["renovate/config//groups/opentk.json"],
|
||||||
|
"groupName": "OpenTK"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "group Svg.Controls packages",
|
||||||
|
"extends": ["renovate/config//groups/svgcontrols.json"],
|
||||||
|
"groupName": "Svg.Controls"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
176
.forgejo/workflows/build.yml
Normal file
176
.forgejo/workflows/build.yml
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
name: Build PR
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
paths:
|
||||||
|
- '**'
|
||||||
|
- '!.forgejo/**'
|
||||||
|
- '!*.yml'
|
||||||
|
- '!*.config'
|
||||||
|
- '!*.md'
|
||||||
|
- '.forgejo/workflows/*.yml'
|
||||||
|
|
||||||
|
env:
|
||||||
|
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||||
|
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||||
|
RELEASE: 0
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: ${{ matrix.platform.name }} (${{ matrix.configuration }})
|
||||||
|
runs-on: docker
|
||||||
|
container:
|
||||||
|
image: ghcr.io/gruke-build/ubuntu:dotnet-latest
|
||||||
|
timeout-minutes: 45
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
configuration: [Release]
|
||||||
|
platform:
|
||||||
|
- { name: win-x64, zip_os_name: win_x64 }
|
||||||
|
#- { name: win-arm64, zip_os_name: win_arm64 }
|
||||||
|
- { name: linux-x64, zip_os_name: linux_x64 }
|
||||||
|
- { name: linux-arm64, zip_os_name: linux_arm64 }
|
||||||
|
#- { name: osx-x64, zip_os_name: osx_x64 }
|
||||||
|
|
||||||
|
fail-fast: false
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Overwrite csc problem matcher
|
||||||
|
run: echo "::add-matcher::.forgejo/csc.json"
|
||||||
|
|
||||||
|
- name: Get version info
|
||||||
|
id: version_info
|
||||||
|
run: |
|
||||||
|
echo "result=$(gli get-next-version -c Canary -R)" >> $FORGEJO_OUTPUT
|
||||||
|
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Change config filename
|
||||||
|
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||||
|
shell: bash
|
||||||
|
if: forgejo.event_name == 'pull_request'
|
||||||
|
|
||||||
|
- name: 'Cache: ~/.nuget/packages'
|
||||||
|
uses: actions/cache@v5
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.nuget/packages
|
||||||
|
key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props') }}
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: dotnet build -c "${{ matrix.configuration }}" -p:Version="${{ steps.version_info.outputs.result }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:ExtraDefineConstants=DISABLE_UPDATER
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
uses: actions/unstable-commands@v1
|
||||||
|
with:
|
||||||
|
commands: dotnet test --no-build -c "${{ matrix.configuration }}"
|
||||||
|
timeout-minutes: 10
|
||||||
|
retry-codes: 139
|
||||||
|
if: matrix.platform.name != 'linux-arm64'
|
||||||
|
|
||||||
|
- name: Publish Ryujinx
|
||||||
|
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ steps.version_info.outputs.result }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained
|
||||||
|
if: forgejo.event_name == 'pull_request'
|
||||||
|
|
||||||
|
- name: Packing Windows builds
|
||||||
|
if: contains(matrix.platform.name, 'win')
|
||||||
|
run: |
|
||||||
|
7z a artifact/ryujinx-${{ matrix.configuration }}-${{ steps.version_info.outputs.result }}+${{ steps.version_info.outputs.git_short_hash }}-${{ matrix.platform.zip_os_name }}.7z publish
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Upload Ryujinx Windows artifact
|
||||||
|
uses: actions/upload-artifact@v5
|
||||||
|
with:
|
||||||
|
name: ryujinx-${{ matrix.configuration }}-${{ steps.version_info.outputs.result }}+${{ steps.version_info.outputs.git_short_hash }}-${{ matrix.platform.zip_os_name }}
|
||||||
|
path: artifact
|
||||||
|
if: forgejo.event_name == 'pull_request' && contains(matrix.platform.name, 'win')
|
||||||
|
|
||||||
|
- name: Build AppImage
|
||||||
|
if: forgejo.event_name == 'pull_request' && contains(matrix.platform.name, 'linux')
|
||||||
|
run: |
|
||||||
|
chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
|
||||||
|
|
||||||
|
PLATFORM_NAME="${{ matrix.platform.name }}"
|
||||||
|
|
||||||
|
chmod +x distribution/linux/appimage/build-appimage.sh
|
||||||
|
|
||||||
|
# Explicitly set $ARCH for appimagetool ($ARCH_NAME is for the file name)
|
||||||
|
if [ "$PLATFORM_NAME" = "linux-x64" ]; then
|
||||||
|
ARCH_NAME=x64
|
||||||
|
export ARCH=x86_64
|
||||||
|
elif [ "$PLATFORM_NAME" = "linux-arm64" ]; then
|
||||||
|
ARCH_NAME=arm64
|
||||||
|
export ARCH=aarch64
|
||||||
|
else
|
||||||
|
echo "Unexpected PLATFORM_NAME "$PLATFORM_NAME""
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
BUILDDIR=publish OUTDIR=publish_appimage distribution/linux/appimage/build-appimage.sh
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Upload Ryujinx AppImage artifact
|
||||||
|
uses: actions/upload-artifact@v5
|
||||||
|
if: forgejo.event_name == 'pull_request' && contains(matrix.platform.name, 'linux')
|
||||||
|
with:
|
||||||
|
name: ryujinx-${{ matrix.configuration }}-${{ steps.version_info.outputs.result }}+${{ steps.version_info.outputs.git_short_hash }}-${{ matrix.platform.zip_os_name }}-AppImage
|
||||||
|
path: publish_appimage
|
||||||
|
|
||||||
|
build_macos:
|
||||||
|
name: macOS Universal (${{ matrix.configuration }})
|
||||||
|
runs-on: docker
|
||||||
|
container:
|
||||||
|
image: ghcr.io/gruke-build/ubuntu:dotnet-latest
|
||||||
|
timeout-minutes: 45
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
configuration: [ Release ]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Setup LLVM 17
|
||||||
|
run: |
|
||||||
|
wget https://apt.llvm.org/llvm.sh
|
||||||
|
chmod +x llvm.sh
|
||||||
|
sudo ./llvm.sh 17
|
||||||
|
|
||||||
|
- name: Install rcodesign
|
||||||
|
run: |
|
||||||
|
gli ghr -R indygreg/apple-platform-rs -p apple-codesign-*-x86_64-unknown-linux-musl.tar.gz -O apple-codesign.tar.gz
|
||||||
|
tar -xzvf apple-codesign.tar.gz --wildcards '*/rcodesign' --strip-components=1
|
||||||
|
rm apple-codesign.tar.gz
|
||||||
|
sudo mv rcodesign /usr/bin/rcodesign
|
||||||
|
|
||||||
|
- name: Get version info
|
||||||
|
id: version_info
|
||||||
|
run: |
|
||||||
|
echo "result=$(gli get-next-version -c Stable -R)" >> $FORGEJO_OUTPUT
|
||||||
|
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Change config filename
|
||||||
|
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||||
|
shell: bash
|
||||||
|
if: forgejo.event_name == 'pull_request'
|
||||||
|
|
||||||
|
- name: 'Cache: ~/.nuget/packages'
|
||||||
|
uses: actions/cache@v5
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.nuget/packages
|
||||||
|
key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props') }}
|
||||||
|
|
||||||
|
- name: Publish macOS Ryujinx
|
||||||
|
run: |
|
||||||
|
bash distribution/macos/create_macos_pr_build_ava.sh . publish_tmp publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.result }}" "${{ steps.version_info.outputs.git_short_hash }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Upload Ryujinx artifact
|
||||||
|
uses: actions/upload-artifact@v5
|
||||||
|
with:
|
||||||
|
name: ryujinx-${{ matrix.configuration }}-${{ steps.version_info.outputs.result }}+${{ steps.version_info.outputs.git_short_hash }}-macos_universal
|
||||||
|
path: "publish/*.tar.gz"
|
||||||
|
if: forgejo.event_name == 'pull_request'
|
||||||
@@ -6,7 +6,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '.github/**'
|
- '.forgejo/**'
|
||||||
- 'docs/**'
|
- 'docs/**'
|
||||||
- 'assets/**'
|
- 'assets/**'
|
||||||
- '*.yml'
|
- '*.yml'
|
||||||
@@ -25,44 +25,28 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Release for ${{ matrix.platform.name }}
|
name: Release for ${{ matrix.platform.name }}
|
||||||
runs-on: ${{ matrix.platform.os }}
|
runs-on: docker
|
||||||
|
container:
|
||||||
|
image: ${{ matrix.platform.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
platform:
|
platform:
|
||||||
- { name: win-x64, os: ubuntu-latest, zip_os_name: win_x64 }
|
- { name: win-x64, os: ghcr.io/gruke-build/ubuntu:dotnet-latest, zip_os_name: win_x64 }
|
||||||
#- { name: win-arm64, os: ubuntu-latest, zip_os_name: win_arm64 }
|
#- { name: win-arm64, os: ghcr.io/gruke-build/ubuntu:dotnet-latest, zip_os_name: win_arm64 }
|
||||||
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
- { name: linux-x64, os: ghcr.io/gruke-build/ubuntu:dotnet-latest, zip_os_name: linux_x64 }
|
||||||
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
- { name: linux-arm64, os: ghcr.io/gruke-build/ubuntu:dotnet-latest, zip_os_name: linux_arm64 }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: actions/setup-dotnet@v4
|
|
||||||
with:
|
|
||||||
global-json-file: global.json
|
|
||||||
|
|
||||||
- name: Overwrite csc problem matcher
|
- name: Overwrite csc problem matcher
|
||||||
run: echo "::add-matcher::.github/csc.json"
|
run: echo "::add-matcher::.forgejo/csc.json"
|
||||||
|
|
||||||
- name: Install 7zip
|
|
||||||
run: |
|
|
||||||
sudo apt install -y 7zip
|
|
||||||
|
|
||||||
- name: Install gli
|
|
||||||
run: |
|
|
||||||
mkdir -p $HOME/.bin
|
|
||||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.31
|
|
||||||
chmod +x gli
|
|
||||||
mv gli $HOME/.bin/
|
|
||||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Get version info
|
- name: Get version info
|
||||||
id: version_info
|
id: version_info
|
||||||
run: |
|
run: |
|
||||||
echo "build_version=$(gli get-next-version -c Canary -R)" >> $GITHUB_OUTPUT
|
echo "build_version=$(gli get-next-version -c Canary -R)" >> $FORGEJO_OUTPUT
|
||||||
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $GITHUB_OUTPUT
|
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $FORGEJO_OUTPUT
|
||||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Configure for release
|
- name: Configure for release
|
||||||
@@ -87,12 +71,9 @@ jobs:
|
|||||||
pushd publish
|
pushd publish
|
||||||
rm libarmeilleure-jitsupport.dylib
|
rm libarmeilleure-jitsupport.dylib
|
||||||
7z a ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
7z a ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
||||||
|
7z a ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.7z ../publish
|
||||||
popd
|
popd
|
||||||
|
|
||||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip
|
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Packing Linux builds
|
- name: Packing Linux builds
|
||||||
if: contains(matrix.platform.name, 'linux')
|
if: contains(matrix.platform.name, 'linux')
|
||||||
@@ -101,9 +82,8 @@ jobs:
|
|||||||
rm libarmeilleure-jitsupport.dylib
|
rm libarmeilleure-jitsupport.dylib
|
||||||
chmod +x Ryujinx.sh Ryujinx
|
chmod +x Ryujinx.sh Ryujinx
|
||||||
tar -czvf ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
tar -czvf ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
||||||
|
tar -cJvf ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.xz ../publish
|
||||||
popd
|
popd
|
||||||
|
|
||||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz
|
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Build AppImage (Linux)
|
- name: Build AppImage (Linux)
|
||||||
@@ -112,14 +92,6 @@ jobs:
|
|||||||
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
|
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
|
||||||
PLATFORM_NAME="${{ matrix.platform.name }}"
|
PLATFORM_NAME="${{ matrix.platform.name }}"
|
||||||
|
|
||||||
sudo apt install -y zsync desktop-file-utils appstream
|
|
||||||
|
|
||||||
mkdir -p tools
|
|
||||||
export PATH="$PATH:$(readlink -f tools)"
|
|
||||||
|
|
||||||
# Setup appimagetool
|
|
||||||
wget -q -O tools/appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
|
|
||||||
chmod +x tools/appimagetool
|
|
||||||
chmod +x distribution/linux/appimage/build-appimage.sh
|
chmod +x distribution/linux/appimage/build-appimage.sh
|
||||||
|
|
||||||
# Explicitly set $ARCH for appimagetool ($ARCH_NAME is for the file name)
|
# Explicitly set $ARCH for appimagetool ($ARCH_NAME is for the file name)
|
||||||
@@ -139,53 +111,46 @@ jobs:
|
|||||||
pushd publish_appimage
|
pushd publish_appimage
|
||||||
mv Ryujinx.AppImage ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage
|
mv Ryujinx.AppImage ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||||
popd
|
popd
|
||||||
|
|
||||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage
|
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
|
- name: Create release
|
||||||
|
uses: actions/create-release@v1
|
||||||
|
with:
|
||||||
|
name: "Canary ${{ steps.version_info.outputs.build_version }}"
|
||||||
|
body: "**Full Changelog:** [`${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}`](https://git.ryujinx.app/projects/Ryubing/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }})"
|
||||||
|
repository: "Ryubing/Canary"
|
||||||
|
token: ${{ secrets.RELEASER_TOKEN }}
|
||||||
|
tag_name: ${{ steps.version_info.outputs.build_version }}
|
||||||
|
files: |-
|
||||||
|
release_output/**
|
||||||
|
|
||||||
macos_release:
|
macos_release:
|
||||||
name: Release MacOS universal
|
name: Release MacOS universal
|
||||||
runs-on: ubuntu-24.04
|
runs-on: docker
|
||||||
|
container:
|
||||||
|
image: ghcr.io/gruke-build/ubuntu:dotnet-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: actions/setup-dotnet@v4
|
|
||||||
with:
|
|
||||||
global-json-file: global.json
|
|
||||||
|
|
||||||
- name: Setup LLVM 17
|
- name: Setup LLVM 17
|
||||||
run: |
|
run: |
|
||||||
wget https://apt.llvm.org/llvm.sh
|
wget https://apt.llvm.org/llvm.sh
|
||||||
chmod +x llvm.sh
|
chmod +x llvm.sh
|
||||||
sudo ./llvm.sh 17
|
sudo ./llvm.sh 17
|
||||||
|
|
||||||
- name: Install gli
|
|
||||||
run: |
|
|
||||||
mkdir -p $HOME/.bin
|
|
||||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.30
|
|
||||||
chmod +x gli
|
|
||||||
mv gli $HOME/.bin/
|
|
||||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Install rcodesign
|
- name: Install rcodesign
|
||||||
run: |
|
run: |
|
||||||
mkdir -p $HOME/.bin
|
gli ghr -R indygreg/apple-platform-rs -p apple-codesign-*-x86_64-unknown-linux-musl.tar.gz -O apple-codesign.tar.gz
|
||||||
gh release download -R indygreg/apple-platform-rs -O apple-codesign.tar.gz -p 'apple-codesign-*-x86_64-unknown-linux-musl.tar.gz'
|
|
||||||
tar -xzvf apple-codesign.tar.gz --wildcards '*/rcodesign' --strip-components=1
|
tar -xzvf apple-codesign.tar.gz --wildcards '*/rcodesign' --strip-components=1
|
||||||
rm apple-codesign.tar.gz
|
rm apple-codesign.tar.gz
|
||||||
mv rcodesign $HOME/.bin/
|
mv rcodesign /usr/bin/rcodesign
|
||||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Get version info
|
- name: Get version info
|
||||||
id: version_info
|
id: version_info
|
||||||
run: |
|
run: |
|
||||||
echo "build_version=$(gli get-next-version -c Canary -R)" >> $GITHUB_OUTPUT
|
echo "build_version=$(gli get-next-version -c Canary -R)" >> $FORGEJO_OUTPUT
|
||||||
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $GITHUB_OUTPUT
|
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $FORGEJO_OUTPUT
|
||||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Configure for release
|
- name: Configure for release
|
||||||
@@ -201,46 +166,46 @@ jobs:
|
|||||||
- name: Publish macOS Ryujinx
|
- name: Publish macOS Ryujinx
|
||||||
run: |
|
run: |
|
||||||
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 1
|
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 1
|
||||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r 5 -p publish_ava/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz
|
|
||||||
|
|
||||||
create_gitlab_release:
|
- name: Create release
|
||||||
name: Create GitLab Release
|
uses: actions/create-release@v1
|
||||||
runs-on: ubuntu-24.04
|
with:
|
||||||
|
name: "Canary ${{ steps.version_info.outputs.build_version }}"
|
||||||
|
body: "**Full Changelog:** [`${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}`](https://git.ryujinx.app/projects/Ryubing/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }})"
|
||||||
|
repository: "Ryubing/Canary"
|
||||||
|
token: ${{ secrets.RELEASER_TOKEN }}
|
||||||
|
tag_name: ${{ steps.version_info.outputs.build_version }}
|
||||||
|
files: |-
|
||||||
|
publish_ava/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz
|
||||||
|
|
||||||
|
post_ci:
|
||||||
|
name: Post CI Steps
|
||||||
|
runs-on: docker
|
||||||
|
container:
|
||||||
|
image: ghcr.io/gruke-build/ubuntu:act-latest
|
||||||
needs:
|
needs:
|
||||||
- macos_release
|
- macos_release
|
||||||
- release
|
- release
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install gli
|
|
||||||
run: |
|
|
||||||
mkdir -p $HOME/.bin
|
|
||||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.30
|
|
||||||
chmod +x gli
|
|
||||||
mv gli $HOME/.bin/
|
|
||||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Get version info
|
- name: Get version info
|
||||||
id: version_info
|
id: version_info
|
||||||
run: |
|
run: |
|
||||||
echo "build_version=$(gli get-next-version -c Canary -R)" >> $GITHUB_OUTPUT
|
echo "build_version=$(gli get-next-version -c Canary -R)" >> $FORGEJO_OUTPUT
|
||||||
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $GITHUB_OUTPUT
|
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $FORGEJO_OUTPUT
|
||||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Create tag
|
- name: Create tag
|
||||||
run: |
|
run: |
|
||||||
gli create-tag -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Canary-${{ steps.version_info.outputs.build_version }} -r ${{ steps.version_info.outputs.git_short_hash }}
|
gli create-tag -T ${{ secrets.RELEASER_TOKEN }} -P projects/Ryubing -n Canary-${{ steps.version_info.outputs.build_version }} -r ${{ steps.version_info.outputs.git_short_hash }}
|
||||||
|
|
||||||
- name: Create release
|
|
||||||
run: |
|
|
||||||
gli create-release-from-generic-package-files -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r main -t "Canary ${{ steps.version_info.outputs.build_version }}" -b "**Full Changelog:** [${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}](https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }})"
|
|
||||||
|
|
||||||
|
- name: Link to actual source archives for Canary
|
||||||
|
run: |
|
||||||
|
gli canary-release -T ${{ secrets.RELEASER_TOKEN }} -P Ryubing/Canary -r ${{ steps.version_info.outputs.build_version }}
|
||||||
|
|
||||||
- name: Send notification webhook
|
- name: Send notification webhook
|
||||||
run: |
|
run: |
|
||||||
gli send-update-message -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -t ${{ steps.version_info.outputs.build_version }} -c FF4500 -w ${{ secrets.CANARY_DISCORD_WEBHOOK }} -i https://avatars.githubusercontent.com/u/192939710?s=200&v=4
|
gli send-update-message -T ${{ secrets.RELEASER_TOKEN }} -P Ryubing/Canary -t ${{ steps.version_info.outputs.build_version }} -c FF4500 -w ${{ secrets.CANARY_DISCORD_WEBHOOK }} -i https://avatars.githubusercontent.com/u/192939710?s=200&v=4
|
||||||
|
|
||||||
- name: Notify update server of new builds
|
- name: Notify update server of new builds
|
||||||
run: |
|
run: |
|
||||||
@@ -5,24 +5,22 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
triage:
|
triage:
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
# Grab sources to get latest labeler.yml
|
# Grab sources to get latest labeler.yml
|
||||||
- name: Fetch sources
|
- name: Fetch sources
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
# Ensure we pin the source origin as pull_request_target run under forks.
|
# Ensure we pin the source origin as pull_request_target run under forks.
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
repository: GreemDev/Ryujinx
|
repository: projects/Ryubing
|
||||||
ref: master
|
ref: master
|
||||||
|
|
||||||
- name: Update labels based on changes
|
- name: Update labels based on changes
|
||||||
uses: actions/labeler@v5
|
uses: actions/labeler@v6
|
||||||
with:
|
with:
|
||||||
|
repo-token: ${{ secrets.LABELER_TOKEN }}
|
||||||
|
configuration-path: .forgejo/labeler.yml
|
||||||
sync-labels: true
|
sync-labels: true
|
||||||
dot: true
|
dot: true
|
||||||
@@ -19,48 +19,32 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Release for ${{ matrix.platform.name }}
|
name: Release for ${{ matrix.platform.name }}
|
||||||
runs-on: ${{ matrix.platform.os }}
|
runs-on: docker
|
||||||
|
container:
|
||||||
|
image: ${{ matrix.platform.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
platform:
|
platform:
|
||||||
- { name: win-x64, os: ubuntu-latest, zip_os_name: win_x64 }
|
- { name: win-x64, os: ghcr.io/gruke-build/ubuntu:act-latest, zip_os_name: win_x64 }
|
||||||
#- { name: win-arm64, os: ubuntu-latest, zip_os_name: win_arm64 }
|
#- { name: win-arm64, os: ghcr.io/gruke-build/ubuntu:act-latest, zip_os_name: win_arm64 }
|
||||||
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
- { name: linux-x64, os: ghcr.io/gruke-build/ubuntu:act-latest, zip_os_name: linux_x64 }
|
||||||
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
- { name: linux-arm64, os: ghcr.io/gruke-build/ubuntu:act-latest, zip_os_name: linux_arm64 }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: actions/setup-dotnet@v4
|
|
||||||
with:
|
|
||||||
global-json-file: global.json
|
|
||||||
|
|
||||||
- name: Overwrite csc problem matcher
|
- name: Overwrite csc problem matcher
|
||||||
run: echo "::add-matcher::.github/csc.json"
|
run: echo "::add-matcher::.github/csc.json"
|
||||||
|
|
||||||
- name: Install 7zip
|
|
||||||
run: |
|
|
||||||
sudo apt install -y 7zip
|
|
||||||
|
|
||||||
- name: Install gli
|
|
||||||
run: |
|
|
||||||
mkdir -p $HOME/.bin
|
|
||||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.31
|
|
||||||
chmod +x gli
|
|
||||||
mv gli $HOME/.bin/
|
|
||||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Get version info
|
- name: Get version info
|
||||||
id: version_info
|
id: version_info
|
||||||
run: |
|
run: |
|
||||||
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
|
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
|
||||||
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $GITHUB_OUTPUT
|
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $FORGEJO_OUTPUT
|
||||||
else
|
else
|
||||||
echo "build_version=$(gli get-next-version -c Stable -R)" >> $GITHUB_OUTPUT
|
echo "build_version=$(gli get-next-version -c Stable -R)" >> $FORGEJO_OUTPUT
|
||||||
fi
|
fi
|
||||||
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $GITHUB_OUTPUT
|
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $FORGEJO_OUTPUT
|
||||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Configure for release
|
- name: Configure for release
|
||||||
@@ -84,12 +68,9 @@ jobs:
|
|||||||
pushd publish
|
pushd publish
|
||||||
rm libarmeilleure-jitsupport.dylib
|
rm libarmeilleure-jitsupport.dylib
|
||||||
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
||||||
|
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.7z ../publish
|
||||||
popd
|
popd
|
||||||
|
|
||||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip
|
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Packing Linux builds
|
- name: Packing Linux builds
|
||||||
if: contains(matrix.platform.name, 'linux')
|
if: contains(matrix.platform.name, 'linux')
|
||||||
@@ -98,12 +79,9 @@ jobs:
|
|||||||
rm libarmeilleure-jitsupport.dylib
|
rm libarmeilleure-jitsupport.dylib
|
||||||
chmod +x Ryujinx.sh Ryujinx
|
chmod +x Ryujinx.sh Ryujinx
|
||||||
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
||||||
|
tar -cJvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.xz ../publish
|
||||||
popd
|
popd
|
||||||
|
|
||||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz
|
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build AppImage (Linux)
|
- name: Build AppImage (Linux)
|
||||||
if: contains(matrix.platform.name, 'linux')
|
if: contains(matrix.platform.name, 'linux')
|
||||||
@@ -111,14 +89,6 @@ jobs:
|
|||||||
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
|
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
|
||||||
PLATFORM_NAME="${{ matrix.platform.name }}"
|
PLATFORM_NAME="${{ matrix.platform.name }}"
|
||||||
|
|
||||||
sudo apt install -y zsync desktop-file-utils appstream
|
|
||||||
|
|
||||||
mkdir -p tools
|
|
||||||
export PATH="$PATH:$(readlink -f tools)"
|
|
||||||
|
|
||||||
# Setup appimagetool
|
|
||||||
wget -q -O tools/appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
|
|
||||||
chmod +x tools/appimagetool
|
|
||||||
chmod +x distribution/linux/appimage/build-appimage.sh
|
chmod +x distribution/linux/appimage/build-appimage.sh
|
||||||
|
|
||||||
# Explicitly set $ARCH for appimagetool ($ARCH_NAME is for the file name)
|
# Explicitly set $ARCH for appimagetool ($ARCH_NAME is for the file name)
|
||||||
@@ -138,17 +108,27 @@ jobs:
|
|||||||
pushd publish_appimage
|
pushd publish_appimage
|
||||||
mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
|
mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||||
popd
|
popd
|
||||||
|
|
||||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
|
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
|
- name: Create release
|
||||||
|
uses: actions/create-release@v1
|
||||||
|
with:
|
||||||
|
name: "${{ steps.version_info.outputs.build_version }}"
|
||||||
|
repository: "projects/Ryubing"
|
||||||
|
token: ${{ secrets.RELEASER_TOKEN }}
|
||||||
|
tag_name: ${{ steps.version_info.outputs.build_version }}
|
||||||
|
files: |-
|
||||||
|
release_output/**
|
||||||
|
|
||||||
macos_release:
|
macos_release:
|
||||||
name: Release MacOS universal
|
name: Release MacOS universal
|
||||||
runs-on: ubuntu-24.04
|
runs-on: docker
|
||||||
|
container:
|
||||||
|
image: ghcr.io/gruke-build/ubuntu:dotnet-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: actions/setup-dotnet@v4
|
- uses: actions/setup-dotnet@v5
|
||||||
with:
|
with:
|
||||||
global-json-file: global.json
|
global-json-file: global.json
|
||||||
|
|
||||||
@@ -157,38 +137,24 @@ jobs:
|
|||||||
wget https://apt.llvm.org/llvm.sh
|
wget https://apt.llvm.org/llvm.sh
|
||||||
chmod +x llvm.sh
|
chmod +x llvm.sh
|
||||||
sudo ./llvm.sh 17
|
sudo ./llvm.sh 17
|
||||||
|
|
||||||
- name: Install gli
|
|
||||||
run: |
|
|
||||||
mkdir -p $HOME/.bin
|
|
||||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.30
|
|
||||||
chmod +x gli
|
|
||||||
mv gli $HOME/.bin/
|
|
||||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Install rcodesign
|
- name: Install rcodesign
|
||||||
run: |
|
run: |
|
||||||
mkdir -p $HOME/.bin
|
gli ghr -R indygreg/apple-platform-rs -p apple-codesign-*-x86_64-unknown-linux-musl.tar.gz -O apple-codesign.tar.gz
|
||||||
gh release download -R indygreg/apple-platform-rs -O apple-codesign.tar.gz -p 'apple-codesign-*-x86_64-unknown-linux-musl.tar.gz'
|
|
||||||
tar -xzvf apple-codesign.tar.gz --wildcards '*/rcodesign' --strip-components=1
|
tar -xzvf apple-codesign.tar.gz --wildcards '*/rcodesign' --strip-components=1
|
||||||
rm apple-codesign.tar.gz
|
rm apple-codesign.tar.gz
|
||||||
mv rcodesign $HOME/.bin/
|
mv rcodesign /usr/bin/rcodesign
|
||||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Get version info
|
- name: Get version info
|
||||||
id: version_info
|
id: version_info
|
||||||
run: |
|
run: |
|
||||||
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
|
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
|
||||||
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $GITHUB_OUTPUT
|
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $FORGEJO_OUTPUT
|
||||||
else
|
else
|
||||||
echo "build_version=$(gli get-next-version -c Stable -R)" >> $GITHUB_OUTPUT
|
echo "build_version=$(gli get-next-version -c Stable -R)" >> $FORGEJO_OUTPUT
|
||||||
fi
|
fi
|
||||||
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $GITHUB_OUTPUT
|
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $FORGEJO_OUTPUT
|
||||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Configure for release
|
- name: Configure for release
|
||||||
@@ -201,49 +167,42 @@ jobs:
|
|||||||
|
|
||||||
- name: Publish macOS Ryujinx
|
- name: Publish macOS Ryujinx
|
||||||
run: |
|
run: |
|
||||||
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0
|
bash distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0
|
||||||
|
|
||||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r 5 -p publish/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz
|
- name: Create release
|
||||||
|
uses: actions/create-release@v1
|
||||||
create_gitlab_release:
|
with:
|
||||||
name: Create GitLab Release
|
name: "${{ steps.version_info.outputs.build_version }}"
|
||||||
runs-on: ubuntu-24.04
|
repository: "projects/Ryubing"
|
||||||
|
token: ${{ secrets.RELEASER_TOKEN }}
|
||||||
|
tag_name: ${{ steps.version_info.outputs.build_version }}
|
||||||
|
files: |-
|
||||||
|
publish_ava/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz
|
||||||
|
|
||||||
|
post_ci:
|
||||||
|
name: Post-CI Steps
|
||||||
|
runs-on: docker
|
||||||
|
container:
|
||||||
|
image: ghcr.io/gruke-build/ubuntu:act-latest
|
||||||
needs:
|
needs:
|
||||||
- macos_release
|
- macos_release
|
||||||
- release
|
- release
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install gli
|
|
||||||
run: |
|
|
||||||
mkdir -p $HOME/.bin
|
|
||||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.30
|
|
||||||
chmod +x gli
|
|
||||||
mv gli $HOME/.bin/
|
|
||||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Get version info
|
- name: Get version info
|
||||||
id: version_info
|
id: version_info
|
||||||
run: |
|
run: |
|
||||||
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
|
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
|
||||||
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $GITHUB_OUTPUT
|
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $FORGEJO_OUTPUT
|
||||||
else
|
else
|
||||||
echo "build_version=$(gli get-next-version -c Stable -R)" >> $GITHUB_OUTPUT
|
echo "build_version=$(gli get-next-version -c Stable -R)" >> $FORGEJO_OUTPUT
|
||||||
fi
|
fi
|
||||||
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $GITHUB_OUTPUT
|
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $FORGEJO_OUTPUT
|
||||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
|
||||||
echo "commit_message=$(git log -1 --pretty=%B)" >> $GITHUB_OUTPUT
|
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Create release
|
|
||||||
run: |
|
|
||||||
gli create-release-from-generic-package-files -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r ${{ steps.version_info.outputs.git_short_hash }} -t "${{ steps.version_info.outputs.build_version }}" -b "msd:${{ steps.version_info.outputs.build_version }}"
|
|
||||||
|
|
||||||
- name: Send notification webhook
|
- name: Send notification webhook
|
||||||
run: |
|
run: |
|
||||||
gli send-update-message -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -t ${{ steps.version_info.outputs.build_version }} -c 32cd32 -w ${{ secrets.STABLE_DISCORD_WEBHOOK }} -i https://avatars.githubusercontent.com/u/192939710?s=200&v=4
|
gli send-update-message -T ${{ secrets.RELEASER_TOKEN }} -P projects/Ryubing -t ${{ steps.version_info.outputs.build_version }} -c 32cd32 -w ${{ secrets.STABLE_DISCORD_WEBHOOK }} -i https://avatars.githubusercontent.com/u/192939710?s=200&v=4
|
||||||
|
|
||||||
- name: Notify update server of new builds
|
- name: Notify update server of new builds
|
||||||
run: |
|
run: |
|
||||||
86
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
86
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,86 +0,0 @@
|
|||||||
name: Bug Report
|
|
||||||
description: File a bug report
|
|
||||||
title: "[Bug]"
|
|
||||||
labels: bug
|
|
||||||
body:
|
|
||||||
- type: textarea
|
|
||||||
id: issue
|
|
||||||
attributes:
|
|
||||||
label: Description of the issue
|
|
||||||
description: What's the issue you encountered?
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: repro
|
|
||||||
attributes:
|
|
||||||
label: Reproduction steps
|
|
||||||
description: How can the issue be reproduced?
|
|
||||||
placeholder: Describe each step as precisely as possible
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: log
|
|
||||||
attributes:
|
|
||||||
label: Log file
|
|
||||||
description: "A log file will help our developers to better diagnose and fix the issue. UPLOAD THE FILE. DO NOT COPY AND PASTE THE FILE'S CONTENT."
|
|
||||||
placeholder: Logs files can be found under "Logs" folder in Ryujinx program folder. They can also be accessed by opening Ryujinx, then going to File > Open Logs Folder. You can drag and drop the log on to the text area (do not copy paste).
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
id: os
|
|
||||||
attributes:
|
|
||||||
label: OS
|
|
||||||
placeholder: "e.g. Windows 10"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
id: ryujinx-version
|
|
||||||
attributes:
|
|
||||||
label: Ryujinx version
|
|
||||||
placeholder: "e.g. 1.0.470"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
id: game-version
|
|
||||||
attributes:
|
|
||||||
label: Game version
|
|
||||||
placeholder: "e.g. 1.1.1"
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: input
|
|
||||||
id: cpu
|
|
||||||
attributes:
|
|
||||||
label: CPU
|
|
||||||
placeholder: "e.g. i7-6700"
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: input
|
|
||||||
id: gpu
|
|
||||||
attributes:
|
|
||||||
label: GPU
|
|
||||||
placeholder: "e.g. NVIDIA RTX 2070"
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: input
|
|
||||||
id: ram
|
|
||||||
attributes:
|
|
||||||
label: RAM
|
|
||||||
placeholder: "e.g. 16GB"
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
id: mods
|
|
||||||
attributes:
|
|
||||||
label: List of applied mods
|
|
||||||
placeholder: You can list applied mods here.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
id: additional-context
|
|
||||||
attributes:
|
|
||||||
label: Additional context?
|
|
||||||
description: |
|
|
||||||
- Additional info about your environment:
|
|
||||||
- Any other information relevant to your issue.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +0,0 @@
|
|||||||
blank_issues_enabled: false
|
|
||||||
contact_links:
|
|
||||||
- name: Ryujinx Discord
|
|
||||||
url: https://discord.gg/N2FmfVc
|
|
||||||
about: This is for development related issues. For support and technical issues, please come to our Discord server.
|
|
||||||
31
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
31
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -1,31 +0,0 @@
|
|||||||
name: Feature Request
|
|
||||||
description: Suggest a new feature for Ryujinx.
|
|
||||||
title: "[Feature Request]"
|
|
||||||
labels: enhancement
|
|
||||||
body:
|
|
||||||
- type: textarea
|
|
||||||
id: overview
|
|
||||||
attributes:
|
|
||||||
label: Overview
|
|
||||||
description: Include the basic, high-level concepts for this feature here.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: details
|
|
||||||
attributes:
|
|
||||||
label: Smaller details
|
|
||||||
description: These may include specific methods of implementation etc.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: request
|
|
||||||
attributes:
|
|
||||||
label: Nature of request
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: feature
|
|
||||||
attributes:
|
|
||||||
label: Why would this feature be useful?
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
name: Missing CPU Instruction
|
|
||||||
description: CPU Instruction is missing in Ryujinx.
|
|
||||||
title: "[CPU]"
|
|
||||||
labels: [cpu, not-implemented]
|
|
||||||
body:
|
|
||||||
- type: textarea
|
|
||||||
id: instruction
|
|
||||||
attributes:
|
|
||||||
label: CPU instruction
|
|
||||||
description: What CPU instruction is missing?
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: name
|
|
||||||
attributes:
|
|
||||||
label: Instruction name
|
|
||||||
description: Include the name from [armconverter.com](https://armconverter.com/?disasm) or [shell-storm.org](http://shell-storm.org/online/Online-Assembler-and-Disassembler/?arch=arm64&endianness=big&dis_with_raw=True&dis_with_ins=True) in the above code block
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: required
|
|
||||||
attributes:
|
|
||||||
label: Required by
|
|
||||||
description: Add links to the [compatibility list page(s)](https://github.com/Ryujinx/Ryujinx-Games-List/issues) of the game(s) that require this instruction.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
25
.github/ISSUE_TEMPLATE/missing_service_call.yml
vendored
25
.github/ISSUE_TEMPLATE/missing_service_call.yml
vendored
@@ -1,25 +0,0 @@
|
|||||||
name: Missing Service Call
|
|
||||||
description: Service call is missing in Ryujinx.
|
|
||||||
labels: not-implemented
|
|
||||||
body:
|
|
||||||
- type: textarea
|
|
||||||
id: instruction
|
|
||||||
attributes:
|
|
||||||
label: Service call
|
|
||||||
description: What service call is missing?
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: name
|
|
||||||
attributes:
|
|
||||||
label: Service description
|
|
||||||
description: Include the description/explanation from [Switchbrew](https://switchbrew.org/w/index.php?title=Services_API) and/or [SwIPC](https://reswitched.github.io/SwIPC/) in the above code block
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: required
|
|
||||||
attributes:
|
|
||||||
label: Required by
|
|
||||||
description: Add links to the [compatibility list page(s)](https://github.com/Ryujinx/Ryujinx-Games-List/issues) of the game(s) that require this service.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
name: Missing Shader Instruction
|
|
||||||
description: Shader Instruction is missing in Ryujinx.
|
|
||||||
title: "[GPU]"
|
|
||||||
labels: [gpu, not-implemented]
|
|
||||||
body:
|
|
||||||
- type: textarea
|
|
||||||
id: instruction
|
|
||||||
attributes:
|
|
||||||
label: Shader instruction
|
|
||||||
description: What shader instruction is missing?
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: required
|
|
||||||
attributes:
|
|
||||||
label: Required by
|
|
||||||
description: Add links to the [compatibility list page(s)](https://github.com/Ryujinx/Ryujinx-Games-List/issues) of the game(s) that require this instruction.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
168
.github/workflows/build.yml
vendored
168
.github/workflows/build.yml
vendored
@@ -1,168 +0,0 @@
|
|||||||
name: Build job
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
|
|
||||||
env:
|
|
||||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
|
||||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
|
||||||
RYUJINX_BASE_VERSION: "1.2.0"
|
|
||||||
RELEASE: 0
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: ${{ matrix.platform.name }} (${{ matrix.configuration }})
|
|
||||||
runs-on: ${{ matrix.platform.os }}
|
|
||||||
timeout-minutes: 45
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
configuration: [Debug, Release]
|
|
||||||
platform:
|
|
||||||
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
|
||||||
- { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 }
|
|
||||||
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
|
||||||
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
|
||||||
- { name: osx-x64, os: macos-13, zip_os_name: osx_x64 }
|
|
||||||
|
|
||||||
fail-fast: false
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: actions/setup-dotnet@v4
|
|
||||||
with:
|
|
||||||
global-json-file: global.json
|
|
||||||
|
|
||||||
- name: Overwrite csc problem matcher
|
|
||||||
run: echo "::add-matcher::.github/csc.json"
|
|
||||||
|
|
||||||
- name: Get git short hash
|
|
||||||
id: git_short_hash
|
|
||||||
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Change config filename
|
|
||||||
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
|
||||||
shell: bash
|
|
||||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
|
||||||
|
|
||||||
- name: Change config filename for macOS
|
|
||||||
run: sed -r -i '' 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
|
||||||
shell: bash
|
|
||||||
if: github.event_name == 'pull_request' && matrix.platform.os == 'macos-13'
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: dotnet build -c "${{ matrix.configuration }}" -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
uses: TSRBerry/unstable-commands@v1
|
|
||||||
with:
|
|
||||||
commands: dotnet test --no-build -c "${{ matrix.configuration }}"
|
|
||||||
timeout-minutes: 10
|
|
||||||
retry-codes: 139
|
|
||||||
if: matrix.platform.name != 'linux-arm64'
|
|
||||||
|
|
||||||
- name: Publish Ryujinx
|
|
||||||
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: Set executable bit
|
|
||||||
run: |
|
|
||||||
chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
|
|
||||||
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
|
|
||||||
|
|
||||||
- name: Build AppImage
|
|
||||||
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
|
|
||||||
run: |
|
|
||||||
PLATFORM_NAME="${{ matrix.platform.name }}"
|
|
||||||
|
|
||||||
sudo apt install -y zsync desktop-file-utils appstream
|
|
||||||
|
|
||||||
mkdir -p tools
|
|
||||||
export PATH="$PATH:$(readlink -f tools)"
|
|
||||||
|
|
||||||
# Setup appimagetool
|
|
||||||
wget -q -O tools/appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
|
|
||||||
chmod +x tools/appimagetool
|
|
||||||
chmod +x distribution/linux/appimage/build-appimage.sh
|
|
||||||
|
|
||||||
# Explicitly set $ARCH for appimagetool ($ARCH_NAME is for the file name)
|
|
||||||
if [ "$PLATFORM_NAME" = "linux-x64" ]; then
|
|
||||||
ARCH_NAME=x64
|
|
||||||
export ARCH=x86_64
|
|
||||||
elif [ "$PLATFORM_NAME" = "linux-arm64" ]; then
|
|
||||||
ARCH_NAME=arm64
|
|
||||||
export ARCH=aarch64
|
|
||||||
else
|
|
||||||
echo "Unexpected PLATFORM_NAME "$PLATFORM_NAME""
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
export UFLAG="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|*-$ARCH_NAME.AppImage.zsync"
|
|
||||||
BUILDDIR=publish OUTDIR=publish_appimage distribution/linux/appimage/build-appimage.sh
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Upload Ryujinx artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
|
|
||||||
path: publish
|
|
||||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
|
||||||
|
|
||||||
- name: Upload Ryujinx (AppImage) artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
|
|
||||||
with:
|
|
||||||
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}-AppImage
|
|
||||||
path: publish_appimage
|
|
||||||
|
|
||||||
build_macos:
|
|
||||||
name: macOS Universal (${{ matrix.configuration }})
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 45
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
configuration: [ Debug, Release ]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: actions/setup-dotnet@v4
|
|
||||||
with:
|
|
||||||
global-json-file: global.json
|
|
||||||
|
|
||||||
- name: Setup LLVM 17
|
|
||||||
run: |
|
|
||||||
wget https://apt.llvm.org/llvm.sh
|
|
||||||
chmod +x llvm.sh
|
|
||||||
sudo ./llvm.sh 17
|
|
||||||
|
|
||||||
- name: Install rcodesign
|
|
||||||
run: |
|
|
||||||
mkdir -p $HOME/.bin
|
|
||||||
gh release download -R indygreg/apple-platform-rs -O apple-codesign.tar.gz -p 'apple-codesign-*-x86_64-unknown-linux-musl.tar.gz'
|
|
||||||
tar -xzvf apple-codesign.tar.gz --wildcards '*/rcodesign' --strip-components=1
|
|
||||||
rm apple-codesign.tar.gz
|
|
||||||
mv rcodesign $HOME/.bin/
|
|
||||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Get git short hash
|
|
||||||
id: git_short_hash
|
|
||||||
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Change config filename
|
|
||||||
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
|
||||||
shell: bash
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
|
|
||||||
- name: Publish macOS Ryujinx
|
|
||||||
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: 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'
|
|
||||||
25
.github/workflows/checks.yml
vendored
25
.github/workflows/checks.yml
vendored
@@ -1,25 +0,0 @@
|
|||||||
name: Build PR
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches: [ master ]
|
|
||||||
paths:
|
|
||||||
- '**'
|
|
||||||
- '!.github/**'
|
|
||||||
- '!*.yml'
|
|
||||||
- '!*.config'
|
|
||||||
- '!*.md'
|
|
||||||
- '.github/workflows/*.yml'
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
checks: write
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: pr-checks-${{ github.event.number }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
pr_build:
|
|
||||||
uses: ./.github/workflows/build.yml
|
|
||||||
secrets: inherit
|
|
||||||
61
.github/workflows/nightly_pr_comment.yml
vendored
61
.github/workflows/nightly_pr_comment.yml
vendored
@@ -1,61 +0,0 @@
|
|||||||
name: Comment PR artifacts links
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_run:
|
|
||||||
workflows: ['Build PR']
|
|
||||||
types: [completed]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
pr_comment:
|
|
||||||
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/github-script@v6
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const {owner, repo} = context.repo;
|
|
||||||
const run_id = ${{github.event.workflow_run.id}};
|
|
||||||
const pull_head_sha = '${{github.event.workflow_run.head_sha}}';
|
|
||||||
|
|
||||||
const issue_number = await (async () => {
|
|
||||||
const pulls = await github.rest.pulls.list({owner, repo});
|
|
||||||
for await (const {data} of github.paginate.iterator(pulls)) {
|
|
||||||
for (const pull of data) {
|
|
||||||
if (pull.head.sha === pull_head_sha) {
|
|
||||||
return pull.number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
if (issue_number) {
|
|
||||||
core.info(`Using pull request ${issue_number}`);
|
|
||||||
} else {
|
|
||||||
return core.error(`No matching pull request found`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const {data: {artifacts}} = await github.rest.actions.listWorkflowRunArtifacts({owner, repo, run_id});
|
|
||||||
if (!artifacts.length) {
|
|
||||||
return core.error(`No artifacts found`);
|
|
||||||
}
|
|
||||||
let body = `Download the artifacts for this pull request:\n`;
|
|
||||||
let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`;
|
|
||||||
for (const art of artifacts) {
|
|
||||||
const url = `https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip`;
|
|
||||||
if (art.name.includes('Debug')) {
|
|
||||||
hidden_debug_artifacts += `\n* [${art.name}](${url})`;
|
|
||||||
} else {
|
|
||||||
body += `\n* [${art.name}](${url})`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hidden_debug_artifacts += `\n</details>`;
|
|
||||||
body += hidden_debug_artifacts;
|
|
||||||
|
|
||||||
const {data: comments} = await github.rest.issues.listComments({repo, owner, issue_number});
|
|
||||||
const existing_comment = comments.find((c) => c.user.login === 'github-actions[bot]');
|
|
||||||
if (existing_comment) {
|
|
||||||
core.info(`Updating comment ${existing_comment.id}`);
|
|
||||||
await github.rest.issues.updateComment({repo, owner, comment_id: existing_comment.id, body});
|
|
||||||
} else {
|
|
||||||
core.info(`Creating a comment`);
|
|
||||||
await github.rest.issues.createComment({repo, owner, issue_number, body});
|
|
||||||
}
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -72,9 +72,6 @@ ipch/
|
|||||||
_ReSharper*/
|
_ReSharper*/
|
||||||
*.[Rr]e[Ss]harper
|
*.[Rr]e[Ss]harper
|
||||||
|
|
||||||
#.NET
|
|
||||||
.dotnet-home/
|
|
||||||
|
|
||||||
# TeamCity is a build add-in
|
# TeamCity is a build add-in
|
||||||
_TeamCity*
|
_TeamCity*
|
||||||
|
|
||||||
|
|||||||
@@ -3,59 +3,65 @@
|
|||||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="Avalonia" Version="11.3.12" />
|
<PackageVersion Include="Avalonia" Version="11.3.15" />
|
||||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.12" />
|
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" />
|
||||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.12" />
|
<PackageVersion Include="Avalonia.Desktop" Version="11.3.15" />
|
||||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.12" />
|
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.15" />
|
||||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.12" />
|
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.15" />
|
||||||
<PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.9.4" />
|
<PackageVersion Include="SharpCompress" Version="0.48.0" />
|
||||||
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.9.4" />
|
<PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.9.5" />
|
||||||
|
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.9.5" />
|
||||||
<PackageVersion Include="Microsoft.Build.Framework" Version="17.11.4" />
|
<PackageVersion Include="Microsoft.Build.Framework" Version="17.11.4" />
|
||||||
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.12.6" />
|
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.12.50" />
|
||||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
|
||||||
<PackageVersion Include="Projektanker.Icons.Avalonia" Version="9.6.2" />
|
<PackageVersion Include="Projektanker.Icons.Avalonia" Version="9.6.2" />
|
||||||
<PackageVersion Include="Projektanker.Icons.Avalonia.FontAwesome" Version="9.6.2" />
|
<PackageVersion Include="Projektanker.Icons.Avalonia.FontAwesome" Version="9.6.2" />
|
||||||
<PackageVersion Include="Projektanker.Icons.Avalonia.MaterialDesign" Version="9.6.2" />
|
<PackageVersion Include="Projektanker.Icons.Avalonia.MaterialDesign" Version="9.6.2" />
|
||||||
<PackageVersion Include="ppy.SDL3-CS" Version="2025.920.0" />
|
<PackageVersion Include="Ryujinx.SDL3-CS" Version="2026.501.0" />
|
||||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||||
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.2" />
|
||||||
<PackageVersion Include="Concentus" Version="2.2.2" />
|
<PackageVersion Include="Concentus" Version="2.2.2" />
|
||||||
<PackageVersion Include="DiscordRichPresence" Version="1.6.1.70" />
|
<PackageVersion Include="DiscordRichPresence" Version="1.6.1.70" />
|
||||||
<PackageVersion Include="DynamicData" Version="9.4.1" />
|
<PackageVersion Include="DynamicData" Version="9.4.31" />
|
||||||
<PackageVersion Include="FluentAvaloniaUI" Version="2.5.0" />
|
<PackageVersion Include="FluentAvaloniaUI" Version="2.5.1" />
|
||||||
<PackageVersion Include="Humanizer" Version="2.14.1" />
|
<PackageVersion Include="Humanizer" Version="2.14.1" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
|
||||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.3.0" />
|
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.18.0" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
|
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
|
||||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||||
<PackageVersion Include="NetCoreServer" Version="8.0.7" />
|
<PackageVersion Include="NetCoreServer" Version="8.0.7" />
|
||||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
<PackageVersion Include="NUnit" Version="3.14.0" />
|
||||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageVersion Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||||
<PackageVersion Include="OpenTK.Core" Version="4.8.2" />
|
<PackageVersion Include="OpenTK.Core" Version="4.9.4" />
|
||||||
<PackageVersion Include="OpenTK.Graphics" Version="4.8.2" />
|
<PackageVersion Include="OpenTK.Graphics" Version="4.9.4" />
|
||||||
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.2" />
|
<!-- OpenTk.Audio.OpenAL has moved to OpenTk.Audio -->
|
||||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.2" />
|
<!--<PackageVersion Include="OpenTK.Audio" Version="5.0.0-pre.15" />-->
|
||||||
|
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.9.4" />
|
||||||
|
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.9.4" />
|
||||||
<PackageVersion Include="Open.NAT.Core" Version="2.1.0.5" />
|
<PackageVersion Include="Open.NAT.Core" Version="2.1.0.5" />
|
||||||
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
<!-- Ryujinx.Audio.OpenAL.Dependencies is from the original project, last updated 12/30/20 -->
|
||||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" Version="6.1.2-build3" />
|
<!--<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />-->
|
||||||
|
<PackageVersion Include="Ryujinx.Audio.OpenAL" Version="1.25.1" />
|
||||||
|
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" Version="6.1.4-build6" />
|
||||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||||
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.129" />
|
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.133" />
|
||||||
<PackageVersion Include="Ryujinx.UpdateClient" Version="1.0.44" />
|
<PackageVersion Include="Ryujinx.UpdateClient" Version="2.0.6" />
|
||||||
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="1.0.44" />
|
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="2.0.6" />
|
||||||
<PackageVersion Include="Gommon" Version="2.8.0.1" />
|
<PackageVersion Include="Gommon" Version="2.8.1.2" />
|
||||||
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
||||||
<PackageVersion Include="Sep" Version="0.11.1" />
|
<PackageVersion Include="Sep" Version="0.13.0" />
|
||||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
||||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
|
||||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.22.0" />
|
<PackageVersion Include="Silk.NET.Vulkan" Version="2.22.0" />
|
||||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.22.0" />
|
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.22.0" />
|
||||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.22.0" />
|
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.22.0" />
|
||||||
<PackageVersion Include="SkiaSharp" Version="2.88.9" />
|
<PackageVersion Include="SkiaSharp" Version="2.88.9" />
|
||||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.9" />
|
<PackageVersion Include="SkiaSharp.NativeAssets.Win32" Version="2.88.9" />
|
||||||
|
<PackageVersion Include="SkiaSharp.NativeAssets.macOS" Version="2.88.9" />
|
||||||
|
<PackageVersion Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.9" />
|
||||||
<PackageVersion Include="SPB" Version="0.0.4-build32" />
|
<PackageVersion Include="SPB" Version="0.0.4-build32" />
|
||||||
<PackageVersion Include="System.IO.Hashing" Version="9.0.2" />
|
<PackageVersion Include="System.IO.Hashing" Version="9.0.15" />
|
||||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.1.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
26
README.md
26
README.md
@@ -5,10 +5,10 @@
|
|||||||
</td>
|
</td>
|
||||||
<td align="center" width="75%">
|
<td align="center" width="75%">
|
||||||
|
|
||||||
# Ryujinx
|
<h1 class="ryu-gradient-text">Ryujinx</h1>
|
||||||
|
|
||||||
[](https://update.ryujinx.app/latest/stable)
|
[](https://update.ryujinx.app/latest/stable)
|
||||||
[](https://update.ryujinx.app/latest/canary)
|
[](https://update.ryujinx.app/latest/canary)
|
||||||
<br>
|
<br>
|
||||||
<a href="https://discord.gg/PEuzjrFXUA">
|
<a href="https://discord.gg/PEuzjrFXUA">
|
||||||
<img src="https://img.shields.io/discord/1294443224030511104?color=5865F2&label=Ryubing&logo=discord&logoColor=white" alt="Discord">
|
<img src="https://img.shields.io/discord/1294443224030511104?color=5865F2&label=Ryubing&logo=discord&logoColor=white" alt="Discord">
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
Ryujinx is an open-source Nintendo Switch emulator, originally created by gdkchan, written in C#.
|
Ryujinx is an open-source Nintendo Switch emulator, originally created by gdkchan, written in C#.
|
||||||
This emulator aims at providing excellent accuracy and performance, a user-friendly interface and consistent builds.
|
This emulator aims at providing excellent accuracy and performance, a user-friendly interface and consistent builds.
|
||||||
It was written from scratch and development on the project began in September 2017.
|
It was written from scratch and development on the project began in September 2017.
|
||||||
Ryujinx is available on a self-managed GitLab instance under the <a href="https://git.ryujinx.app/ryubing/ryujinx/-/blob/master/LICENSE.txt?ref_type=heads" target="_blank">MIT license</a>.
|
Ryujinx is available on a self-managed <a class="forgejo-gradient-text" href="https://github.com/Ryubing/forgejo" target="_blank">modified Forgejo</a> instance under the <a href="https://git.ryujinx.app/projects/Ryubing/src/branch/master/LICENSE.txt" target="_blank">MIT license</a>.
|
||||||
<br />
|
<br />
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@@ -31,11 +31,11 @@
|
|||||||
<br>
|
<br>
|
||||||
This is not a Ryujinx revival project. This is not a Phoenix project.
|
This is not a Ryujinx revival project. This is not a Phoenix project.
|
||||||
<br>
|
<br>
|
||||||
Guides and documentation can be found on the <a href="https://git.ryujinx.app/groups/ryubing/-/wikis/home">Wiki tab</a>.
|
Guides and documentation can be found on the <a href="https://git.ryujinx.app/projects/Ryubing/wiki/Home">Wiki tab</a>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://git.ryujinx.app/ryubing/ryujinx/-/raw/master/docs/shell.png?ref_type=heads&inline=false" alt="Ryujinx example">
|
<img src="https://git.ryujinx.app/projects/Ryubing/raw/branch/master/docs/shell.png" alt="Ryujinx example">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@@ -49,17 +49,17 @@ Stable builds are made every so often, based on the `master` branch, that then g
|
|||||||
These stable builds exist so that the end user can get a more **enjoyable and stable experience**.
|
These stable builds exist so that the end user can get a more **enjoyable and stable experience**.
|
||||||
They are released every month or so, to ensure consistent updates, while not being an annoying amount of individual updates to download over the course of that month.
|
They are released every month or so, to ensure consistent updates, while not being an annoying amount of individual updates to download over the course of that month.
|
||||||
|
|
||||||
You can find the stable releases [here](https://git.ryujinx.app/ryubing/ryujinx/-/releases).
|
You can find the stable releases [here](https://git.ryujinx.app/projects/Ryubing/releases).
|
||||||
|
|
||||||
Canary builds are compiled automatically for each commit on the `master` branch.
|
Canary builds are compiled automatically for each commit on the `master` branch.
|
||||||
While we strive to ensure optimal stability and performance prior to pushing an update, these builds **may be unstable or completely broken**.
|
While we strive to ensure optimal stability and performance prior to pushing an update, these builds **may be unstable or completely broken**.
|
||||||
These canary builds are only recommended for experienced users.
|
These canary builds are only recommended for experienced users.
|
||||||
|
|
||||||
You can find the canary releases [here](https://git.ryujinx.app/ryubing/canary/-/releases).
|
You can find the canary releases [here](https://git.ryujinx.app/Ryubing/Canary/releases).
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
If you are planning to contribute or just want to learn more about this project please read through our [documentation](docs/README.md).
|
If you are planning to contribute or just want to learn more about this project please read through our [documentation](https://git.ryujinx.app/projects/Ryubing/src/branch/master/docs/README.md).
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@@ -105,13 +105,13 @@ If you are planning to contribute or just want to learn more about this project
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This software is licensed under the terms of the [MIT license](LICENSE.txt).
|
This software is licensed under the terms of the [MIT license](https://git.ryujinx.app/projects/Ryubing/src/branch/master/LICENSE.txt).
|
||||||
This project makes use of code authored by the libvpx project, licensed under BSD and the ffmpeg project, licensed under LGPLv3.
|
This project makes use of code authored by the libvpx project, licensed under BSD and the ffmpeg project, licensed under LGPLv3.
|
||||||
See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](distribution/legal/THIRDPARTY.md) for more details.
|
See [LICENSE.txt](https://git.ryujinx.app/projects/Ryubing/src/branch/master/LICENSE.txt) and [THIRDPARTY.md](https://git.ryujinx.app/projects/Ryubing/src/branch/master/distribution/legal/THIRDPARTY.md) for more details.
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
- [LibHac](https://git.ryujinx.app/ryubing/libhac) is used for our file-system.
|
- [LibHac](https://git.ryujinx.app/projects/LibHac) is used for our file-system.
|
||||||
- [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation.
|
- [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation.
|
||||||
- [ldn_mitm](https://github.com/spacemeowx2/ldn_mitm) is used for one of our available multiplayer modes.
|
- [ldn_mitm](https://github.com/spacemeowx2/ldn_mitm) is used for one of our available multiplayer modes.
|
||||||
- [ShellLink](https://github.com/securifybv/ShellLink) is used for Windows shortcut generation.
|
- [ShellLink](https://github.com/securifybv/ShellLink) is used for Windows shortcut generation.
|
||||||
|
|||||||
16
Ryujinx.sln
16
Ryujinx.sln
@@ -86,11 +86,11 @@ EndProject
|
|||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
.editorconfig = .editorconfig
|
.editorconfig = .editorconfig
|
||||||
.github\workflows\build.yml = .github\workflows\build.yml
|
.forgejo\workflows\build.yml = .forgejo\workflows\build.yml
|
||||||
.github\workflows\canary.yml = .github\workflows\canary.yml
|
.forgejo\workflows\canary.yml = .forgejo\workflows\canary.yml
|
||||||
Directory.Packages.props = Directory.Packages.props
|
Directory.Packages.props = Directory.Packages.props
|
||||||
Directory.Build.props = Directory.Build.props
|
Directory.Build.props = Directory.Build.props
|
||||||
.github\workflows\release.yml = .github\workflows\release.yml
|
.forgejo\workflows\release.yml = .forgejo\workflows\release.yml
|
||||||
nuget.config = nuget.config
|
nuget.config = nuget.config
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
@@ -573,6 +573,16 @@ Global
|
|||||||
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x86.Build.0 = Release|Any CPU
|
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
|||||||
"ID": "MenuBarActions_StartCapture",
|
"ID": "MenuBarActions_StartCapture",
|
||||||
"Translations": {
|
"Translations": {
|
||||||
"ar_SA": "",
|
"ar_SA": "",
|
||||||
"de_DE": "",
|
"de_DE": "RenderDoc Frame-Aufnahme starten",
|
||||||
"el_GR": "",
|
"el_GR": "",
|
||||||
"en_US": "Start RenderDoc Frame Capture",
|
"en_US": "Start RenderDoc Frame Capture",
|
||||||
"es_ES": "Iniciar una captura de fotograma de RenderDoc",
|
"es_ES": "Iniciar una captura de fotograma de RenderDoc",
|
||||||
@@ -22,14 +22,14 @@
|
|||||||
"tr_TR": "",
|
"tr_TR": "",
|
||||||
"uk_UA": "",
|
"uk_UA": "",
|
||||||
"zh_CN": "启动 RenderDoc 帧捕获",
|
"zh_CN": "启动 RenderDoc 帧捕获",
|
||||||
"zh_TW": ""
|
"zh_TW": "啟動 RenderDoc 畫格擷取"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ID": "MenuBarActions_EndCapture",
|
"ID": "MenuBarActions_EndCapture",
|
||||||
"Translations": {
|
"Translations": {
|
||||||
"ar_SA": "",
|
"ar_SA": "",
|
||||||
"de_DE": "",
|
"de_DE": "RenderDoc Frame-Aufnahme beenden",
|
||||||
"el_GR": "",
|
"el_GR": "",
|
||||||
"en_US": "End RenderDoc Frame Capture",
|
"en_US": "End RenderDoc Frame Capture",
|
||||||
"es_ES": "Detener la captura de fotograma de RenderDoc",
|
"es_ES": "Detener la captura de fotograma de RenderDoc",
|
||||||
@@ -47,14 +47,14 @@
|
|||||||
"tr_TR": "",
|
"tr_TR": "",
|
||||||
"uk_UA": "",
|
"uk_UA": "",
|
||||||
"zh_CN": "结束 RenderDoc 帧捕获",
|
"zh_CN": "结束 RenderDoc 帧捕获",
|
||||||
"zh_TW": ""
|
"zh_TW": "停止 RenderDoc 畫格擷取"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ID": "MenuBarActions_DiscardCapture",
|
"ID": "MenuBarActions_DiscardCapture",
|
||||||
"Translations": {
|
"Translations": {
|
||||||
"ar_SA": "",
|
"ar_SA": "",
|
||||||
"de_DE": "",
|
"de_DE": "RenderDoc Frame-Aufnahme verwerfen",
|
||||||
"el_GR": "",
|
"el_GR": "",
|
||||||
"en_US": "Discard RenderDoc Frame Capture",
|
"en_US": "Discard RenderDoc Frame Capture",
|
||||||
"es_ES": "Descartar la captura de fotograma de RenderDoc",
|
"es_ES": "Descartar la captura de fotograma de RenderDoc",
|
||||||
@@ -72,14 +72,14 @@
|
|||||||
"tr_TR": "",
|
"tr_TR": "",
|
||||||
"uk_UA": "",
|
"uk_UA": "",
|
||||||
"zh_CN": "丢弃 RenderDoc 帧捕获",
|
"zh_CN": "丢弃 RenderDoc 帧捕获",
|
||||||
"zh_TW": ""
|
"zh_TW": "捨棄 RenderDoc 畫格擷取"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ID": "MenuBarActions_DiscardCapture_ToolTip",
|
"ID": "MenuBarActions_DiscardCapture_ToolTip",
|
||||||
"Translations": {
|
"Translations": {
|
||||||
"ar_SA": "",
|
"ar_SA": "",
|
||||||
"de_DE": "",
|
"de_DE": "Beendet die jetzige RenderDoc Frame-Aufnahme, verwirft sofort das Ergebnis.",
|
||||||
"el_GR": "",
|
"el_GR": "",
|
||||||
"en_US": "Ends the currently active RenderDoc Frame Capture, immediately discarding its result.",
|
"en_US": "Ends the currently active RenderDoc Frame Capture, immediately discarding its result.",
|
||||||
"es_ES": "Finaliza la captura de fotograma de RenderDoc actualmente activa y descarta inmediatamente su resultado.",
|
"es_ES": "Finaliza la captura de fotograma de RenderDoc actualmente activa y descarta inmediatamente su resultado.",
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
"tr_TR": "",
|
"tr_TR": "",
|
||||||
"uk_UA": "",
|
"uk_UA": "",
|
||||||
"zh_CN": "结束当前正在进行的 RenderDoc 帧捕获,并立即丢弃其结果。",
|
"zh_CN": "结束当前正在进行的 RenderDoc 帧捕获,并立即丢弃其结果。",
|
||||||
"zh_TW": ""
|
"zh_TW": "停止正在執行的 RenderDoc 畫格擷取,且立即捨棄其結果。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -22,5 +22,5 @@ chmod +x AppDir/AppRun AppDir/usr/bin/Ryujinx*
|
|||||||
|
|
||||||
mkdir -p "$OUTDIR"
|
mkdir -p "$OUTDIR"
|
||||||
|
|
||||||
appimagetool -n --comp zstd --mksquashfs-opt -Xcompression-level --mksquashfs-opt 21 \
|
appimagetool --appimage-extract-and-run -n --comp zstd --mksquashfs-opt -Xcompression-level --mksquashfs-opt 21 \
|
||||||
AppDir "$OUTDIR"/Ryujinx.AppImage
|
AppDir "$OUTDIR"/Ryujinx.AppImage
|
||||||
120
distribution/macos/create_macos_pr_build_ava.sh
Normal file
120
distribution/macos/create_macos_pr_build_ava.sh
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ "$#" -lt 8 ]; then
|
||||||
|
echo "usage <BASE_DIR> <TEMP_DIRECTORY> <OUTPUT_DIRECTORY> <ENTITLEMENTS_FILE_PATH> <VERSION> <SOURCE_REVISION_ID> <CONFIGURATION>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$1"
|
||||||
|
mkdir -p "$2"
|
||||||
|
mkdir -p "$3"
|
||||||
|
|
||||||
|
BASE_DIR=$(readlink -f "$1")
|
||||||
|
TEMP_DIRECTORY=$(readlink -f "$2")
|
||||||
|
OUTPUT_DIRECTORY=$(readlink -f "$3")
|
||||||
|
ENTITLEMENTS_FILE_PATH=$(readlink -f "$4")
|
||||||
|
VERSION=$5
|
||||||
|
SOURCE_REVISION_ID=$6
|
||||||
|
CONFIGURATION=$7
|
||||||
|
|
||||||
|
if [[ "$(uname)" == "Darwin" ]]; then
|
||||||
|
echo "Clearing xattr on all dot undercsore files"
|
||||||
|
find "$BASE_DIR" -type f -name "._*" -exec sh -c '
|
||||||
|
for f; do
|
||||||
|
dir=$(dirname "$f")
|
||||||
|
base=$(basename "$f")
|
||||||
|
orig="$dir/${base#._}"
|
||||||
|
[ -f "$orig" ] && xattr -c "$orig" || true
|
||||||
|
done
|
||||||
|
' sh {} +
|
||||||
|
fi
|
||||||
|
|
||||||
|
RELEASE_TAR_FILE_NAME=ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar
|
||||||
|
|
||||||
|
ARM64_APP_BUNDLE="$TEMP_DIRECTORY/output_arm64/Ryujinx.app"
|
||||||
|
X64_APP_BUNDLE="$TEMP_DIRECTORY/output_x64/Ryujinx.app"
|
||||||
|
UNIVERSAL_APP_BUNDLE="$OUTPUT_DIRECTORY/Ryujinx.app"
|
||||||
|
EXECUTABLE_SUB_PATH=Contents/MacOS/Ryujinx
|
||||||
|
|
||||||
|
rm -rf "$TEMP_DIRECTORY"
|
||||||
|
mkdir -p "$TEMP_DIRECTORY"
|
||||||
|
|
||||||
|
DOTNET_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained true $EXTRA_ARGS)
|
||||||
|
|
||||||
|
dotnet restore
|
||||||
|
dotnet build -c "$CONFIGURATION" src/Ryujinx
|
||||||
|
dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx
|
||||||
|
dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx
|
||||||
|
|
||||||
|
# Get rid of the support library for ARMeilleure for x64 (that's only for arm64)
|
||||||
|
rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib"
|
||||||
|
|
||||||
|
# Get rid of libsoundio from arm64 builds as we don't have a arm64 variant
|
||||||
|
# TODO: remove this once done
|
||||||
|
rm -rf "$TEMP_DIRECTORY/publish_arm64/libsoundio.dylib"
|
||||||
|
|
||||||
|
pushd "$BASE_DIR/distribution/macos"
|
||||||
|
./create_app_bundle.sh "$TEMP_DIRECTORY/publish_x64" "$TEMP_DIRECTORY/output_x64" "$ENTITLEMENTS_FILE_PATH"
|
||||||
|
./create_app_bundle.sh "$TEMP_DIRECTORY/publish_arm64" "$TEMP_DIRECTORY/output_arm64" "$ENTITLEMENTS_FILE_PATH"
|
||||||
|
popd
|
||||||
|
|
||||||
|
rm -rf "$UNIVERSAL_APP_BUNDLE"
|
||||||
|
mkdir -p "$OUTPUT_DIRECTORY"
|
||||||
|
|
||||||
|
# Let's copy one of the two different app bundle and remove the executable
|
||||||
|
cp -R "$ARM64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE"
|
||||||
|
rm "$UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH"
|
||||||
|
|
||||||
|
# Make its libraries universal
|
||||||
|
python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_APP_BUNDLE" "$X64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE" "**/*.dylib"
|
||||||
|
|
||||||
|
if ! [ -x "$(command -v lipo)" ];
|
||||||
|
then
|
||||||
|
if ! [ -x "$(command -v llvm-lipo-17)" ];
|
||||||
|
then
|
||||||
|
LIPO=llvm-lipo
|
||||||
|
else
|
||||||
|
LIPO=llvm-lipo-17
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
LIPO=lipo
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Make the executable universal
|
||||||
|
$LIPO "$ARM64_APP_BUNDLE/$EXECUTABLE_SUB_PATH" "$X64_APP_BUNDLE/$EXECUTABLE_SUB_PATH" -output "$UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH" -create
|
||||||
|
|
||||||
|
# Patch up the Info.plist to have appropriate version
|
||||||
|
sed -r -i.bck "s/\%\%RYUJINX_BUILD_VERSION\%\%/$VERSION/g;" "$UNIVERSAL_APP_BUNDLE/Contents/Info.plist"
|
||||||
|
sed -r -i.bck "s/\%\%RYUJINX_BUILD_GIT_HASH\%\%/$SOURCE_REVISION_ID/g;" "$UNIVERSAL_APP_BUNDLE/Contents/Info.plist"
|
||||||
|
rm "$UNIVERSAL_APP_BUNDLE/Contents/Info.plist.bck"
|
||||||
|
|
||||||
|
# Now sign it
|
||||||
|
if ! [ -x "$(command -v codesign)" ];
|
||||||
|
then
|
||||||
|
if ! [ -x "$(command -v rcodesign)" ];
|
||||||
|
then
|
||||||
|
echo "Cannot find rcodesign on your system, please install rcodesign."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes.
|
||||||
|
# cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign"
|
||||||
|
echo "Using rcodesign for ad-hoc signing"
|
||||||
|
rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$UNIVERSAL_APP_BUNDLE"
|
||||||
|
else
|
||||||
|
echo "Using codesign for ad-hoc signing"
|
||||||
|
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f -s - "$UNIVERSAL_APP_BUNDLE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Creating archive"
|
||||||
|
pushd "$OUTPUT_DIRECTORY"
|
||||||
|
tar --exclude "Ryujinx.app/Contents/MacOS/Ryujinx" -cvf "$RELEASE_TAR_FILE_NAME" Ryujinx.app 1> /dev/null
|
||||||
|
python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "Ryujinx.app/Contents/MacOS/Ryujinx" "Ryujinx.app/Contents/MacOS/Ryujinx"
|
||||||
|
gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz"
|
||||||
|
rm "$RELEASE_TAR_FILE_NAME"
|
||||||
|
|
||||||
|
popd
|
||||||
|
|
||||||
|
echo "Done"
|
||||||
@@ -5,8 +5,7 @@
|
|||||||
<clear />
|
<clear />
|
||||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||||
<!-- Only needed when using pre-release versions of Ryujinx.LibHac. -->
|
<!-- Only needed when using pre-release versions of Ryujinx.LibHac. -->
|
||||||
<add key="LibHacAlpha" value="https://git.ryujinx.app/api/v4/projects/17/packages/nuget/index.json" />
|
<add key="LibHacAlpha" value="https://git.ryujinx.app/api/packages/projects/nuget/index.json" />
|
||||||
<add key="Ryujinx.UpdateClient" value="https://git.ryujinx.app/api/v4/projects/71/packages/nuget/index.json" />
|
|
||||||
</packageSources>
|
</packageSources>
|
||||||
<packageSourceMapping>
|
<packageSourceMapping>
|
||||||
<!-- key value for <packageSource> should match key values from <packageSources> element -->
|
<!-- key value for <packageSource> should match key values from <packageSources> element -->
|
||||||
@@ -14,10 +13,6 @@
|
|||||||
<packageSource key="nuget.org">
|
<packageSource key="nuget.org">
|
||||||
<package pattern="*" />
|
<package pattern="*" />
|
||||||
</packageSource>
|
</packageSource>
|
||||||
<packageSource key="Ryujinx.UpdateClient">
|
|
||||||
<package pattern="Ryujinx.UpdateClient" />
|
|
||||||
<package pattern="Ryujinx.Systems.Update.Common" />
|
|
||||||
</packageSource>
|
|
||||||
<packageSource key="LibHacAlpha">
|
<packageSource key="LibHacAlpha">
|
||||||
<package pattern="Ryujinx.LibHac" />
|
<package pattern="Ryujinx.LibHac" />
|
||||||
</packageSource>
|
</packageSource>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Ryujinx.Common.Configuration.Hid.Keyboard
|
namespace Ryujinx.Common.Configuration.Hid.Keyboard
|
||||||
{
|
{
|
||||||
public class StandardKeyboardInputConfig : GenericKeyboardInputConfig<PhysicalKey> { }
|
public class StandardKeyboardInputConfig : GenericKeyboardInputConfig<Key> { }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Ryujinx.Common.Configuration.Hid
|
|
||||||
{
|
|
||||||
[JsonConverter(typeof(JsonStringEnumConverter<PhysicalKey>))]
|
|
||||||
public enum PhysicalKey
|
|
||||||
{
|
|
||||||
Unknown,
|
|
||||||
ShiftLeft,
|
|
||||||
ShiftRight,
|
|
||||||
ControlLeft,
|
|
||||||
ControlRight,
|
|
||||||
AltLeft,
|
|
||||||
AltRight,
|
|
||||||
WinLeft,
|
|
||||||
WinRight,
|
|
||||||
Menu,
|
|
||||||
F1,
|
|
||||||
F2,
|
|
||||||
F3,
|
|
||||||
F4,
|
|
||||||
F5,
|
|
||||||
F6,
|
|
||||||
F7,
|
|
||||||
F8,
|
|
||||||
F9,
|
|
||||||
F10,
|
|
||||||
F11,
|
|
||||||
F12,
|
|
||||||
F13,
|
|
||||||
F14,
|
|
||||||
F15,
|
|
||||||
F16,
|
|
||||||
F17,
|
|
||||||
F18,
|
|
||||||
F19,
|
|
||||||
F20,
|
|
||||||
F21,
|
|
||||||
F22,
|
|
||||||
F23,
|
|
||||||
F24,
|
|
||||||
F25,
|
|
||||||
F26,
|
|
||||||
F27,
|
|
||||||
F28,
|
|
||||||
F29,
|
|
||||||
F30,
|
|
||||||
F31,
|
|
||||||
F32,
|
|
||||||
F33,
|
|
||||||
F34,
|
|
||||||
F35,
|
|
||||||
Up,
|
|
||||||
Down,
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
Enter,
|
|
||||||
Escape,
|
|
||||||
Space,
|
|
||||||
Tab,
|
|
||||||
BackSpace,
|
|
||||||
Insert,
|
|
||||||
Delete,
|
|
||||||
PageUp,
|
|
||||||
PageDown,
|
|
||||||
Home,
|
|
||||||
End,
|
|
||||||
CapsLock,
|
|
||||||
ScrollLock,
|
|
||||||
PrintScreen,
|
|
||||||
Pause,
|
|
||||||
NumLock,
|
|
||||||
Clear,
|
|
||||||
Keypad0,
|
|
||||||
Keypad1,
|
|
||||||
Keypad2,
|
|
||||||
Keypad3,
|
|
||||||
Keypad4,
|
|
||||||
Keypad5,
|
|
||||||
Keypad6,
|
|
||||||
Keypad7,
|
|
||||||
Keypad8,
|
|
||||||
Keypad9,
|
|
||||||
KeypadDivide,
|
|
||||||
KeypadMultiply,
|
|
||||||
KeypadSubtract,
|
|
||||||
KeypadAdd,
|
|
||||||
KeypadDecimal,
|
|
||||||
KeypadEnter,
|
|
||||||
A,
|
|
||||||
B,
|
|
||||||
C,
|
|
||||||
D,
|
|
||||||
E,
|
|
||||||
F,
|
|
||||||
G,
|
|
||||||
H,
|
|
||||||
I,
|
|
||||||
J,
|
|
||||||
K,
|
|
||||||
L,
|
|
||||||
M,
|
|
||||||
N,
|
|
||||||
O,
|
|
||||||
P,
|
|
||||||
Q,
|
|
||||||
R,
|
|
||||||
S,
|
|
||||||
T,
|
|
||||||
U,
|
|
||||||
V,
|
|
||||||
W,
|
|
||||||
X,
|
|
||||||
Y,
|
|
||||||
Z,
|
|
||||||
Number0,
|
|
||||||
Number1,
|
|
||||||
Number2,
|
|
||||||
Number3,
|
|
||||||
Number4,
|
|
||||||
Number5,
|
|
||||||
Number6,
|
|
||||||
Number7,
|
|
||||||
Number8,
|
|
||||||
Number9,
|
|
||||||
Tilde,
|
|
||||||
Grave,
|
|
||||||
Minus,
|
|
||||||
Plus,
|
|
||||||
BracketLeft,
|
|
||||||
BracketRight,
|
|
||||||
Semicolon,
|
|
||||||
Quote,
|
|
||||||
Comma,
|
|
||||||
Period,
|
|
||||||
Slash,
|
|
||||||
BackSlash,
|
|
||||||
Unbound,
|
|
||||||
|
|
||||||
Count,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,20 +12,12 @@ namespace Ryujinx.Common.Helper
|
|||||||
private static partial nint GetConsoleWindow();
|
private static partial nint GetConsoleWindow();
|
||||||
|
|
||||||
[SupportedOSPlatform("windows")]
|
[SupportedOSPlatform("windows")]
|
||||||
[LibraryImport("user32")]
|
[LibraryImport("kernel32", SetLastError = true)]
|
||||||
[return: MarshalAs(UnmanagedType.Bool)]
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
private static partial bool ShowWindow(nint hWnd, int nCmdShow);
|
private static partial bool FreeConsole();
|
||||||
|
|
||||||
[SupportedOSPlatform("windows")]
|
|
||||||
[LibraryImport("user32")]
|
|
||||||
private static partial nint GetForegroundWindow();
|
|
||||||
|
|
||||||
[SupportedOSPlatform("windows")]
|
|
||||||
[LibraryImport("user32")]
|
|
||||||
[return: MarshalAs(UnmanagedType.Bool)]
|
|
||||||
private static partial bool SetForegroundWindow(nint hWnd);
|
|
||||||
|
|
||||||
public static bool SetConsoleWindowStateSupported => OperatingSystem.IsWindows();
|
public static bool SetConsoleWindowStateSupported => OperatingSystem.IsWindows();
|
||||||
|
public static bool HasConsoleWindow => OperatingSystem.IsWindows() && GetConsoleWindow() != nint.Zero;
|
||||||
|
|
||||||
public static void SetConsoleWindowState(bool show)
|
public static void SetConsoleWindowState(bool show)
|
||||||
{
|
{
|
||||||
@@ -42,22 +34,31 @@ namespace Ryujinx.Common.Helper
|
|||||||
[SupportedOSPlatform("windows")]
|
[SupportedOSPlatform("windows")]
|
||||||
private static void SetConsoleWindowStateWindows(bool show)
|
private static void SetConsoleWindowStateWindows(bool show)
|
||||||
{
|
{
|
||||||
const int SW_HIDE = 0;
|
if (show)
|
||||||
const int SW_SHOW = 5;
|
|
||||||
|
|
||||||
nint hWnd = GetConsoleWindow();
|
|
||||||
|
|
||||||
if (hWnd == nint.Zero)
|
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Application, "Attempted to show/hide console window but console window does not exist");
|
if (GetConsoleWindow() != nint.Zero)
|
||||||
|
{
|
||||||
|
Logger.SetConsoleTargetEnabled(true);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetForegroundWindow(hWnd);
|
Logger.SetConsoleTargetEnabled(false);
|
||||||
|
DetachConsole();
|
||||||
|
}
|
||||||
|
|
||||||
hWnd = GetForegroundWindow();
|
[SupportedOSPlatform("windows")]
|
||||||
|
private static void DetachConsole()
|
||||||
|
{
|
||||||
|
if (GetConsoleWindow() == nint.Zero)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ShowWindow(hWnd, show ? SW_SHOW : SW_HIDE);
|
if (!FreeConsole())
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, "Attempted to detach console window but the operation failed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ namespace Ryujinx.Common.Logging
|
|||||||
ServiceNgct,
|
ServiceNgct,
|
||||||
ServiceNifm,
|
ServiceNifm,
|
||||||
ServiceNim,
|
ServiceNim,
|
||||||
|
ServiceNotification,
|
||||||
ServiceNs,
|
ServiceNs,
|
||||||
ServiceNsd,
|
ServiceNsd,
|
||||||
ServiceNtc,
|
ServiceNtc,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ namespace Ryujinx.Common.Logging
|
|||||||
Error,
|
Error,
|
||||||
Guest,
|
Guest,
|
||||||
AccessLog,
|
AccessLog,
|
||||||
|
NetLog,
|
||||||
Notice,
|
Notice,
|
||||||
Trace,
|
Trace,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ namespace Ryujinx.Common.Logging
|
|||||||
public static Log? Error { get; private set; }
|
public static Log? Error { get; private set; }
|
||||||
public static Log? Guest { get; private set; }
|
public static Log? Guest { get; private set; }
|
||||||
public static Log? AccessLog { get; private set; }
|
public static Log? AccessLog { get; private set; }
|
||||||
|
public static Log? NetLog { get; private set; }
|
||||||
public static Log? Stub { get; private set; }
|
public static Log? Stub { get; private set; }
|
||||||
public static Log? Trace { get; private set; }
|
public static Log? Trace { get; private set; }
|
||||||
public static Log Notice { get; } // Always enabled
|
public static Log Notice { get; } // Always enabled
|
||||||
@@ -136,11 +137,7 @@ namespace Ryujinx.Common.Logging
|
|||||||
|
|
||||||
_time = Stopwatch.StartNew();
|
_time = Stopwatch.StartNew();
|
||||||
|
|
||||||
// Logger should log to console by default
|
SetConsoleTargetEnabled(true);
|
||||||
AddTarget(new AsyncLogTargetWrapper(
|
|
||||||
new ConsoleLogTarget("console"),
|
|
||||||
1000,
|
|
||||||
AsyncLogTargetOverflowAction.Discard));
|
|
||||||
|
|
||||||
Notice = new Log(LogLevel.Notice);
|
Notice = new Log(LogLevel.Notice);
|
||||||
|
|
||||||
@@ -173,6 +170,21 @@ namespace Ryujinx.Common.Logging
|
|||||||
Updated += target.Log;
|
Updated += target.Log;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void SetConsoleTargetEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
AddTarget(new AsyncLogTargetWrapper(
|
||||||
|
new ConsoleLogTarget("console"),
|
||||||
|
1000,
|
||||||
|
AsyncLogTargetOverflowAction.Discard));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RemoveTarget("console");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void RemoveTarget(string target)
|
public static void RemoveTarget(string target)
|
||||||
{
|
{
|
||||||
ILogTarget logTarget = GetTarget(target);
|
ILogTarget logTarget = GetTarget(target);
|
||||||
@@ -236,6 +248,7 @@ namespace Ryujinx.Common.Logging
|
|||||||
case LogLevel.Error : Error = enabled ? new Log(LogLevel.Error) : null; break;
|
case LogLevel.Error : Error = enabled ? new Log(LogLevel.Error) : null; break;
|
||||||
case LogLevel.Guest : Guest = enabled ? new Log(LogLevel.Guest) : null; break;
|
case LogLevel.Guest : Guest = enabled ? new Log(LogLevel.Guest) : null; break;
|
||||||
case LogLevel.AccessLog : AccessLog = enabled ? new Log(LogLevel.AccessLog) : null; break;
|
case LogLevel.AccessLog : AccessLog = enabled ? new Log(LogLevel.AccessLog) : null; break;
|
||||||
|
case LogLevel.NetLog : NetLog = enabled ? new Log(LogLevel.NetLog) : null; break;
|
||||||
case LogLevel.Stub : Stub = enabled ? new Log(LogLevel.Stub) : null; break;
|
case LogLevel.Stub : Stub = enabled ? new Log(LogLevel.Stub) : null; break;
|
||||||
case LogLevel.Trace : Trace = enabled ? new Log(LogLevel.Trace) : null; break;
|
case LogLevel.Trace : Trace = enabled ? new Log(LogLevel.Trace) : null; break;
|
||||||
case LogLevel.Notice : break;
|
case LogLevel.Notice : break;
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ namespace Ryujinx.Common
|
|||||||
|
|
||||||
public static string GetChangelogUrl(Version currentVersion, Version newVersion) =>
|
public static string GetChangelogUrl(Version currentVersion, Version newVersion) =>
|
||||||
IsCanaryBuild
|
IsCanaryBuild
|
||||||
? $"https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-{currentVersion}...Canary-{newVersion}"
|
? $"https://git.ryujinx.app/projects/Ryubing/compare/Canary-{currentVersion}...Canary-{newVersion}"
|
||||||
: $"https://git.ryujinx.app/ryubing/ryujinx/-/releases/{newVersion}";
|
: $"https://git.ryujinx.app/projects/Ryubing/releases/tag/{newVersion}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ namespace Ryujinx.Common
|
|||||||
|
|
||||||
public const string AmiiboTagsUrl = "https://raw.githubusercontent.com/Ryubing/Nfc/refs/heads/main/tags.json";
|
public const string AmiiboTagsUrl = "https://raw.githubusercontent.com/Ryubing/Nfc/refs/heads/main/tags.json";
|
||||||
|
|
||||||
public const string FaqWikiUrl = "https://git.ryujinx.app/ryubing/ryujinx/-/wikis/FAQ-&-Troubleshooting";
|
public const string FaqWikiUrl = "https://git.ryujinx.app/projects/Ryubing/wiki/FAQ-%26-Troubleshooting";
|
||||||
|
|
||||||
public const string SetupGuideWikiUrl =
|
public const string SetupGuideWikiUrl =
|
||||||
"https://git.ryujinx.app/ryubing/ryujinx/-/wikis/Setup-&-Configuration-Guide";
|
"https://git.ryujinx.app/projects/Ryubing/wiki/Setup-%26-Configuration-Guide";
|
||||||
|
|
||||||
public const string MultiplayerWikiUrl =
|
public const string MultiplayerWikiUrl =
|
||||||
"https://git.ryujinx.app/ryubing/ryujinx/-/wikis/Multiplayer-(LDN-Local-Wireless)-Guide";
|
"https://git.ryujinx.app/projects/Ryubing/wiki/Multiplayer-(LDN-Local-Wireless)-Guide";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
private static readonly int _pageMask = _pageSize - 1;
|
private static readonly int _pageMask = _pageSize - 1;
|
||||||
|
|
||||||
private const int CodeAlignment = 4; // Bytes.
|
private const int CodeAlignment = 4; // Bytes.
|
||||||
private const int CacheSize = 256 * 1024 * 1024;
|
// TODO: JIT Cache size should be application dependent, not global.
|
||||||
|
private const int CacheSize = 1024 * (1024 * 1024); // Megabytes * Size of Megabytes (since its in bytes).
|
||||||
|
|
||||||
private static JitCacheInvalidation _jitCacheInvalidator;
|
private static JitCacheInvalidation _jitCacheInvalidator;
|
||||||
|
|
||||||
@@ -33,6 +34,14 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
[SupportedOSPlatform("windows")]
|
[SupportedOSPlatform("windows")]
|
||||||
[LibraryImport("kernel32.dll", SetLastError = true)]
|
[LibraryImport("kernel32.dll", SetLastError = true)]
|
||||||
public static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize);
|
public static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize);
|
||||||
|
|
||||||
|
[SupportedOSPlatform("macos")]
|
||||||
|
[LibraryImport("libSystem.dylib", EntryPoint = "sys_icache_invalidate")]
|
||||||
|
internal static partial void SysICacheInvalidate(nint start, nuint len);
|
||||||
|
|
||||||
|
[SupportedOSPlatform("linux")]
|
||||||
|
[LibraryImport("libgcc_s.so.1", EntryPoint = "__clear_cache")]
|
||||||
|
internal static partial void ClearCache(nint begin, nint end);
|
||||||
|
|
||||||
public static void Initialize(IJitMemoryAllocator allocator)
|
public static void Initialize(IJitMemoryAllocator allocator)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -551,7 +551,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
level,
|
level,
|
||||||
x,
|
x,
|
||||||
width,
|
width,
|
||||||
format.PixelFormat,
|
(InternalFormat) format.PixelFormat,
|
||||||
mipSize,
|
mipSize,
|
||||||
data);
|
data);
|
||||||
}
|
}
|
||||||
@@ -579,7 +579,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
layer,
|
layer,
|
||||||
width,
|
width,
|
||||||
1,
|
1,
|
||||||
format.PixelFormat,
|
(InternalFormat) format.PixelFormat,
|
||||||
mipSize,
|
mipSize,
|
||||||
data);
|
data);
|
||||||
}
|
}
|
||||||
@@ -609,7 +609,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
y,
|
y,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
format.PixelFormat,
|
(InternalFormat) format.PixelFormat,
|
||||||
mipSize,
|
mipSize,
|
||||||
data);
|
data);
|
||||||
}
|
}
|
||||||
@@ -643,7 +643,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
1,
|
1,
|
||||||
format.PixelFormat,
|
(InternalFormat) format.PixelFormat,
|
||||||
mipSize,
|
mipSize,
|
||||||
data);
|
data);
|
||||||
}
|
}
|
||||||
@@ -675,7 +675,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
y,
|
y,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
format.PixelFormat,
|
(InternalFormat) format.PixelFormat,
|
||||||
mipSize,
|
mipSize,
|
||||||
data);
|
data);
|
||||||
}
|
}
|
||||||
@@ -744,7 +744,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
level,
|
level,
|
||||||
0,
|
0,
|
||||||
width,
|
width,
|
||||||
format.PixelFormat,
|
(InternalFormat) format.PixelFormat,
|
||||||
mipSize,
|
mipSize,
|
||||||
data);
|
data);
|
||||||
}
|
}
|
||||||
@@ -773,7 +773,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
0,
|
0,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
format.PixelFormat,
|
(InternalFormat) format.PixelFormat,
|
||||||
mipSize,
|
mipSize,
|
||||||
data);
|
data);
|
||||||
}
|
}
|
||||||
@@ -807,7 +807,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
depth,
|
depth,
|
||||||
format.PixelFormat,
|
(InternalFormat) format.PixelFormat,
|
||||||
mipSize,
|
mipSize,
|
||||||
data);
|
data);
|
||||||
}
|
}
|
||||||
@@ -843,7 +843,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
0,
|
0,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
format.PixelFormat,
|
(InternalFormat) format.PixelFormat,
|
||||||
mipSize / 6,
|
mipSize / 6,
|
||||||
data + faceOffset);
|
data + faceOffset);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
public void Map(BufferHandle handle, int size)
|
public void Map(BufferHandle handle, int size)
|
||||||
{
|
{
|
||||||
GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle);
|
GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle);
|
||||||
nint ptr = GL.MapBufferRange(BufferTarget.CopyWriteBuffer, nint.Zero, size, BufferAccessMask.MapReadBit | BufferAccessMask.MapPersistentBit);
|
nint ptr = GL.MapBufferRange(BufferTarget.CopyWriteBuffer, nint.Zero, size, MapBufferAccessMask.MapReadBit | MapBufferAccessMask.MapPersistentBit);
|
||||||
|
|
||||||
_maps[handle] = ptr;
|
_maps[handle] = ptr;
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
GL.BindBuffer(BufferTarget.CopyWriteBuffer, _copyBufferHandle);
|
GL.BindBuffer(BufferTarget.CopyWriteBuffer, _copyBufferHandle);
|
||||||
GL.BufferStorage(BufferTarget.CopyWriteBuffer, requiredSize, nint.Zero, BufferStorageFlags.MapReadBit | BufferStorageFlags.MapPersistentBit);
|
GL.BufferStorage(BufferTarget.CopyWriteBuffer, requiredSize, nint.Zero, BufferStorageFlags.MapReadBit | BufferStorageFlags.MapPersistentBit);
|
||||||
|
|
||||||
_bufferMap = GL.MapBufferRange(BufferTarget.CopyWriteBuffer, nint.Zero, requiredSize, BufferAccessMask.MapReadBit | BufferAccessMask.MapPersistentBit);
|
_bufferMap = GL.MapBufferRange(BufferTarget.CopyWriteBuffer, nint.Zero, requiredSize, MapBufferAccessMask.MapReadBit | MapBufferAccessMask.MapPersistentBit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -924,8 +924,8 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
GL.Disable(EnableCap.CullFace);
|
GL.Disable(EnableCap.CullFace);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
GL.CullFace(face.Convert());
|
GL.CullFace((TriangleFace) face.Convert());
|
||||||
|
|
||||||
GL.Enable(EnableCap.CullFace);
|
GL.Enable(EnableCap.CullFace);
|
||||||
}
|
}
|
||||||
@@ -1085,12 +1085,12 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
{
|
{
|
||||||
if (frontMode == backMode)
|
if (frontMode == backMode)
|
||||||
{
|
{
|
||||||
GL.PolygonMode(MaterialFace.FrontAndBack, frontMode.Convert());
|
GL.PolygonMode((TriangleFace) MaterialFace.FrontAndBack, frontMode.Convert());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
GL.PolygonMode(MaterialFace.Front, frontMode.Convert());
|
GL.PolygonMode((TriangleFace) MaterialFace.Front, frontMode.Convert());
|
||||||
GL.PolygonMode(MaterialFace.Back, backMode.Convert());
|
GL.PolygonMode((TriangleFace) MaterialFace.Back, backMode.Convert());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
GL.CompileShader(shaderHandle);
|
GL.CompileShader(shaderHandle);
|
||||||
break;
|
break;
|
||||||
case TargetLanguage.Spirv:
|
case TargetLanguage.Spirv:
|
||||||
GL.ShaderBinary(1, ref shaderHandle, (BinaryFormat)All.ShaderBinaryFormatSpirVArb, shader.BinaryCode, shader.BinaryCode.Length);
|
GL.ShaderBinary(1, ref shaderHandle, ShaderBinaryFormat.ShaderBinaryFormatSpirV, shader.BinaryCode, shader.BinaryCode.Length);
|
||||||
GL.SpecializeShader(shaderHandle, "main", 0, (int[])null, (int[])null);
|
GL.SpecializeShader(shaderHandle, "main", 0, (int[])null, (int[])null);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
|||||||
GL.BufferStorage(BufferTarget.QueryBuffer, sizeof(long), (nint)(&defaultValue), BufferStorageFlags.MapReadBit | BufferStorageFlags.MapWriteBit | BufferStorageFlags.MapPersistentBit);
|
GL.BufferStorage(BufferTarget.QueryBuffer, sizeof(long), (nint)(&defaultValue), BufferStorageFlags.MapReadBit | BufferStorageFlags.MapWriteBit | BufferStorageFlags.MapPersistentBit);
|
||||||
}
|
}
|
||||||
|
|
||||||
_bufferMap = GL.MapBufferRange(BufferTarget.QueryBuffer, nint.Zero, sizeof(long), BufferAccessMask.MapReadBit | BufferAccessMask.MapWriteBit | BufferAccessMask.MapPersistentBit);
|
_bufferMap = GL.MapBufferRange(BufferTarget.QueryBuffer, nint.Zero, sizeof(long), MapBufferAccessMask.MapReadBit | MapBufferAccessMask.MapWriteBit | MapBufferAccessMask.MapPersistentBit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Ryujinx.Common.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -114,7 +115,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
cbs.AddDependant(this);
|
cbs.AddDependant(this);
|
||||||
|
|
||||||
// We need to add a dependency on the command buffer to all objects this object
|
// We need to add a dependency on the command buffer to all objects this object
|
||||||
// references aswell.
|
// references as well.
|
||||||
if (_referencedObjs != null)
|
if (_referencedObjs != null)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _referencedObjs.Length; i++)
|
for (int i = 0; i < _referencedObjs.Length; i++)
|
||||||
@@ -175,7 +176,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.Assert(_referenceCount >= 0);
|
Debug.Assert(_referenceCount >= 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,22 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
|||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[CommandCmif(1)]
|
||||||
|
// PushOutData(object<nn::am::service::IStorage>)
|
||||||
|
public ResultCode PushOutData(ServiceCtx context)
|
||||||
|
{
|
||||||
|
IStorage appletData = GetObject<IStorage>(context, 0);
|
||||||
|
|
||||||
|
if (appletData == null || appletData.Data.Length == 0) // is this necessary?
|
||||||
|
{
|
||||||
|
return ResultCode.NullObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
_appletStandalone.InputData.Enqueue(appletData.Data);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
[CommandCmif(11)]
|
[CommandCmif(11)]
|
||||||
// GetLibraryAppletInfo() -> nn::am::service::LibraryAppletInfo
|
// GetLibraryAppletInfo() -> nn::am::service::LibraryAppletInfo
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.Memory;
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.HLE.HOS.Services.Caps.Types;
|
using Ryujinx.HLE.HOS.Services.Caps.Types;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
@@ -9,16 +11,20 @@ using System.Security.Cryptography;
|
|||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Caps
|
namespace Ryujinx.HLE.HOS.Services.Caps
|
||||||
{
|
{
|
||||||
class CaptureManager
|
internal class CaptureManager
|
||||||
{
|
{
|
||||||
private readonly string _sdCardPath;
|
public CaptureManager(Switch device)
|
||||||
|
{
|
||||||
|
_ = device;
|
||||||
|
}
|
||||||
|
private readonly string _sdCardPath = FileSystem.VirtualFileSystem.GetSdCardPath();
|
||||||
|
|
||||||
private uint _shimLibraryVersion;
|
private uint _shimLibraryVersion;
|
||||||
|
|
||||||
public CaptureManager(Switch device)
|
private const int ScreenshotWidth = 1280;
|
||||||
{
|
private const int ScreenshotHeight = 720;
|
||||||
_sdCardPath = FileSystem.VirtualFileSystem.GetSdCardPath();
|
private const int ScreenshotBytesPerPixel = 4;
|
||||||
}
|
private const int ScreenshotDataSize = ScreenshotWidth * ScreenshotHeight * ScreenshotBytesPerPixel; // 0x384000
|
||||||
|
|
||||||
public ResultCode SetShimLibraryVersion(ServiceCtx context)
|
public ResultCode SetShimLibraryVersion(ServiceCtx context)
|
||||||
{
|
{
|
||||||
@@ -53,81 +59,94 @@ namespace Ryujinx.HLE.HOS.Services.Caps
|
|||||||
return resultCode;
|
return resultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResultCode SaveScreenShot(byte[] screenshotData, ulong appletResourceUserId, ulong titleId, out ApplicationAlbumEntry applicationAlbumEntry)
|
public ResultCode SaveScreenShot(
|
||||||
|
byte[] screenshotData,
|
||||||
|
ulong appletResourceUserId,
|
||||||
|
ulong titleId,
|
||||||
|
out ApplicationAlbumEntry applicationAlbumEntry)
|
||||||
{
|
{
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceCaps, new
|
||||||
|
{
|
||||||
|
appletResourceUserId,
|
||||||
|
titleId,
|
||||||
|
screenshotDataLength = screenshotData?.Length ?? 0,
|
||||||
|
});
|
||||||
|
|
||||||
applicationAlbumEntry = default;
|
applicationAlbumEntry = default;
|
||||||
|
|
||||||
if (screenshotData.Length == 0)
|
if (screenshotData == null || screenshotData.Length == 0)
|
||||||
{
|
{
|
||||||
return ResultCode.NullInputBuffer;
|
return ResultCode.NullInputBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
if (screenshotData.Length < ScreenshotDataSize)
|
||||||
// NOTE: On our current implementation, appletResourceUserId starts at 0, disable it for now.
|
{
|
||||||
if (appletResourceUserId == 0)
|
Logger.Warning?.PrintMsg(
|
||||||
|
LogClass.ServiceCaps,
|
||||||
|
$"Invalid screenshot buffer size 0x{screenshotData.Length:X}; expected at least 0x{ScreenshotDataSize:X}.");
|
||||||
|
|
||||||
|
return ResultCode.NullInputBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTime currentDateTime = DateTime.Now;
|
||||||
|
|
||||||
|
applicationAlbumEntry = new ApplicationAlbumEntry()
|
||||||
|
{
|
||||||
|
Size = (ulong)Unsafe.SizeOf<ApplicationAlbumEntry>(),
|
||||||
|
TitleId = titleId,
|
||||||
|
AlbumFileDateTime = new AlbumFileDateTime()
|
||||||
|
{
|
||||||
|
Year = (ushort)currentDateTime.Year,
|
||||||
|
Month = (byte)currentDateTime.Month,
|
||||||
|
Day = (byte)currentDateTime.Day,
|
||||||
|
Hour = (byte)currentDateTime.Hour,
|
||||||
|
Minute = (byte)currentDateTime.Minute,
|
||||||
|
Second = (byte)currentDateTime.Second,
|
||||||
|
UniqueId = 0,
|
||||||
|
},
|
||||||
|
AlbumStorage = AlbumStorage.Sd,
|
||||||
|
ContentType = ContentType.Screenshot,
|
||||||
|
Padding = new Array5<byte>(),
|
||||||
|
Unknown0x1f = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// NOTE: The hex hash is a HMAC-SHA256 (first 32 bytes) using a hardcoded secret key over the titleId, we can simulate it by hashing the titleId instead.
|
||||||
|
string hash = Convert.ToHexString(SHA256.HashData(BitConverter.GetBytes(titleId)))[..0x20];
|
||||||
|
|
||||||
|
string folderPath = Path.Combine(
|
||||||
|
_sdCardPath,
|
||||||
|
"Nintendo",
|
||||||
|
"Album",
|
||||||
|
currentDateTime.Year.ToString("0000", CultureInfo.InvariantCulture),
|
||||||
|
currentDateTime.Month.ToString("00", CultureInfo.InvariantCulture),
|
||||||
|
currentDateTime.Day.ToString("00", CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
|
string filePath = GenerateFilePath(folderPath, applicationAlbumEntry, currentDateTime, hash);
|
||||||
|
|
||||||
|
_ = Directory.CreateDirectory(folderPath);
|
||||||
|
|
||||||
|
while (File.Exists(filePath))
|
||||||
|
{
|
||||||
|
applicationAlbumEntry.AlbumFileDateTime.UniqueId++;
|
||||||
|
filePath = GenerateFilePath(folderPath, applicationAlbumEntry, currentDateTime, hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
using SKBitmap bitmap = new(new SKImageInfo(ScreenshotWidth, ScreenshotHeight, SKColorType.Rgba8888));
|
||||||
|
|
||||||
|
IntPtr pixels = bitmap.GetPixels();
|
||||||
|
|
||||||
|
if (pixels == IntPtr.Zero)
|
||||||
{
|
{
|
||||||
return ResultCode.InvalidArgument;
|
return ResultCode.InvalidArgument;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
Marshal.Copy(screenshotData, 0, pixels, ScreenshotDataSize);
|
||||||
// Doesn't occur in our case.
|
|
||||||
if (applicationAlbumEntry == null)
|
|
||||||
{
|
|
||||||
return ResultCode.NullOutputBuffer;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (screenshotData.Length >= 0x384000)
|
using SKData data = bitmap.Encode(SKEncodedImageFormat.Jpeg, 80);
|
||||||
{
|
using FileStream file = File.OpenWrite(filePath);
|
||||||
DateTime currentDateTime = DateTime.Now;
|
data.SaveTo(file);
|
||||||
|
|
||||||
applicationAlbumEntry = new ApplicationAlbumEntry()
|
return ResultCode.Success;
|
||||||
{
|
|
||||||
Size = (ulong)Unsafe.SizeOf<ApplicationAlbumEntry>(),
|
|
||||||
TitleId = titleId,
|
|
||||||
AlbumFileDateTime = new AlbumFileDateTime()
|
|
||||||
{
|
|
||||||
Year = (ushort)currentDateTime.Year,
|
|
||||||
Month = (byte)currentDateTime.Month,
|
|
||||||
Day = (byte)currentDateTime.Day,
|
|
||||||
Hour = (byte)currentDateTime.Hour,
|
|
||||||
Minute = (byte)currentDateTime.Minute,
|
|
||||||
Second = (byte)currentDateTime.Second,
|
|
||||||
UniqueId = 0,
|
|
||||||
},
|
|
||||||
AlbumStorage = AlbumStorage.Sd,
|
|
||||||
ContentType = ContentType.Screenshot,
|
|
||||||
Padding = new Array5<byte>(),
|
|
||||||
Unknown0x1f = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
// NOTE: The hex hash is a HMAC-SHA256 (first 32 bytes) using a hardcoded secret key over the titleId, we can simulate it by hashing the titleId instead.
|
|
||||||
string hash = Convert.ToHexString(SHA256.HashData(BitConverter.GetBytes(titleId)))[..0x20];
|
|
||||||
string folderPath = Path.Combine(_sdCardPath, "Nintendo", "Album", currentDateTime.Year.ToString("00"), currentDateTime.Month.ToString("00"), currentDateTime.Day.ToString("00"));
|
|
||||||
string filePath = GenerateFilePath(folderPath, applicationAlbumEntry, currentDateTime, hash);
|
|
||||||
|
|
||||||
// TODO: Handle that using the FS service implementation and return the right error code instead of throwing exceptions.
|
|
||||||
Directory.CreateDirectory(folderPath);
|
|
||||||
|
|
||||||
while (File.Exists(filePath))
|
|
||||||
{
|
|
||||||
applicationAlbumEntry.AlbumFileDateTime.UniqueId++;
|
|
||||||
|
|
||||||
filePath = GenerateFilePath(folderPath, applicationAlbumEntry, currentDateTime, hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: The saved JPEG file doesn't have the limitation in the extra EXIF data.
|
|
||||||
using SKBitmap bitmap = new(new SKImageInfo(1280, 720, SKColorType.Rgba8888));
|
|
||||||
Marshal.Copy(screenshotData, 0, bitmap.GetPixels(), screenshotData.Length);
|
|
||||||
using SKData data = bitmap.Encode(SKEncodedImageFormat.Jpeg, 80);
|
|
||||||
using FileStream file = File.OpenWrite(filePath);
|
|
||||||
data.SaveTo(file);
|
|
||||||
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultCode.NullInputBuffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GenerateFilePath(string folderPath, ApplicationAlbumEntry applicationAlbumEntry, DateTime currentDateTime, string hash)
|
private string GenerateFilePath(string folderPath, ApplicationAlbumEntry applicationAlbumEntry, DateTime currentDateTime, string hash)
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.HOS.Services.Caps.Types;
|
using Ryujinx.HLE.HOS.Services.Caps.Types;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Caps
|
namespace Ryujinx.HLE.HOS.Services.Caps
|
||||||
{
|
{
|
||||||
[Service("caps:su")] // 6.0.0+
|
[Service("caps:su")] // 6.0.0+
|
||||||
class IScreenShotApplicationService : IpcService
|
internal class IScreenShotApplicationService : IpcService
|
||||||
{
|
{
|
||||||
public IScreenShotApplicationService(ServiceCtx context) { }
|
private const ulong ScreenshotDataSize = 0x384000;
|
||||||
|
private const ulong ApplicationDataSize = 0x404;
|
||||||
|
|
||||||
|
public IScreenShotApplicationService(ServiceCtx context)
|
||||||
|
{
|
||||||
|
_ = context;
|
||||||
|
}
|
||||||
[CommandCmif(32)] // 7.0.0+
|
[CommandCmif(32)] // 7.0.0+
|
||||||
// SetShimLibraryVersion(pid, u64, nn::applet::AppletResourceUserId)
|
// SetShimLibraryVersion(pid, u64, nn::applet::AppletResourceUserId)
|
||||||
public ResultCode SetShimLibraryVersion(ServiceCtx context)
|
public ResultCode SetShimLibraryVersion(ServiceCtx context)
|
||||||
@@ -33,6 +39,15 @@ namespace Ryujinx.HLE.HOS.Services.Caps
|
|||||||
ulong screenshotDataPosition = context.Request.SendBuff[0].Position;
|
ulong screenshotDataPosition = context.Request.SendBuff[0].Position;
|
||||||
ulong screenshotDataSize = context.Request.SendBuff[0].Size;
|
ulong screenshotDataSize = context.Request.SendBuff[0].Size;
|
||||||
|
|
||||||
|
if (screenshotDataSize < ScreenshotDataSize)
|
||||||
|
{
|
||||||
|
Logger.Warning?.PrintMsg(
|
||||||
|
LogClass.ServiceCaps,
|
||||||
|
$"Invalid screenshot buffer size 0x{screenshotDataSize:X}; expected at least 0x{ScreenshotDataSize:X}.");
|
||||||
|
|
||||||
|
return ResultCode.NullInputBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray();
|
byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray();
|
||||||
|
|
||||||
ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry);
|
ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry);
|
||||||
@@ -60,6 +75,24 @@ namespace Ryujinx.HLE.HOS.Services.Caps
|
|||||||
ulong screenshotDataPosition = context.Request.SendBuff[1].Position;
|
ulong screenshotDataPosition = context.Request.SendBuff[1].Position;
|
||||||
ulong screenshotDataSize = context.Request.SendBuff[1].Size;
|
ulong screenshotDataSize = context.Request.SendBuff[1].Size;
|
||||||
|
|
||||||
|
if (applicationDataSize != ApplicationDataSize)
|
||||||
|
{
|
||||||
|
Logger.Warning?.PrintMsg(
|
||||||
|
LogClass.ServiceCaps,
|
||||||
|
$"Invalid ApplicationData size 0x{applicationDataSize:X}; expected 0x{ApplicationDataSize:X}.");
|
||||||
|
|
||||||
|
return ResultCode.InvalidArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (screenshotDataSize < ScreenshotDataSize)
|
||||||
|
{
|
||||||
|
Logger.Warning?.PrintMsg(
|
||||||
|
LogClass.ServiceCaps,
|
||||||
|
$"Invalid screenshot buffer size 0x{screenshotDataSize:X}; expected at least 0x{ScreenshotDataSize:X}.");
|
||||||
|
|
||||||
|
return ResultCode.NullInputBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Parse the application data: At 0x00 it's UserData (Size of 0x400), at 0x404 it's a uint UserDataSize (Always empty for now).
|
// TODO: Parse the application data: At 0x00 it's UserData (Size of 0x400), at 0x404 it's a uint UserDataSize (Always empty for now).
|
||||||
_ = context.Memory.GetSpan(applicationDataPosition, (int)applicationDataSize).ToArray();
|
_ = context.Memory.GetSpan(applicationDataPosition, (int)applicationDataSize).ToArray();
|
||||||
|
|
||||||
@@ -88,6 +121,23 @@ namespace Ryujinx.HLE.HOS.Services.Caps
|
|||||||
ulong screenshotDataPosition = context.Request.SendBuff[1].Position;
|
ulong screenshotDataPosition = context.Request.SendBuff[1].Position;
|
||||||
ulong screenshotDataSize = context.Request.SendBuff[1].Size;
|
ulong screenshotDataSize = context.Request.SendBuff[1].Size;
|
||||||
|
|
||||||
|
if (userIdListSize != 0x88)
|
||||||
|
{
|
||||||
|
Logger.Warning?.PrintMsg(
|
||||||
|
LogClass.ServiceCaps,
|
||||||
|
$"Invalid UserIdList size 0x{userIdListSize:X}; expected 0x88.");
|
||||||
|
return ResultCode.InvalidArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (screenshotDataSize < ScreenshotDataSize)
|
||||||
|
{
|
||||||
|
Logger.Warning?.PrintMsg(
|
||||||
|
LogClass.ServiceCaps,
|
||||||
|
$"Invalid screenshot buffer size 0x{screenshotDataSize:X}; expected at least 0x{ScreenshotDataSize:X}.");
|
||||||
|
|
||||||
|
return ResultCode.NullInputBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Parse the UserIdList.
|
// TODO: Parse the UserIdList.
|
||||||
_ = context.Memory.GetSpan(userIdListPosition, (int)userIdListSize).ToArray();
|
_ = context.Memory.GetSpan(userIdListPosition, (int)userIdListSize).ToArray();
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using Ryujinx.Common.Logging;
|
|||||||
using Ryujinx.Common.Memory;
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.Cpu;
|
using Ryujinx.Cpu;
|
||||||
using Ryujinx.HLE.Exceptions;
|
|
||||||
using Ryujinx.HLE.HOS.Ipc;
|
using Ryujinx.HLE.HOS.Ipc;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||||
@@ -15,7 +14,6 @@ using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
|||||||
using Ryujinx.Horizon.Common;
|
using Ryujinx.Horizon.Common;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.NetworkInformation;
|
using System.Net.NetworkInformation;
|
||||||
@@ -68,10 +66,11 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
{
|
{
|
||||||
if (localCommunicationId == localCommunicationIdChecked)
|
if (localCommunicationId == localCommunicationIdChecked)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,"CheckLocalCommumicationIdPermission: Checked!");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,"CheckLocalCommumicationIdPermission: Check failed!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +81,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
if (_nifmResultCode != ResultCode.Success)
|
if (_nifmResultCode != ResultCode.Success)
|
||||||
{
|
{
|
||||||
context.ResponseData.Write((int)NetworkState.Error);
|
context.ResponseData.Write((int)NetworkState.Error);
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,$"GetState: _nifmResultCode = {_nifmResultCode.ToString()}");
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,12 +113,14 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
|
|
||||||
if (_nifmResultCode != ResultCode.Success)
|
if (_nifmResultCode != ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,$"GetNetworkInfo: _nifmResultCode = {_nifmResultCode.ToString()}");
|
||||||
return _nifmResultCode;
|
return _nifmResultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo);
|
ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo);
|
||||||
if (resultCode != ResultCode.Success)
|
if (resultCode != ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,$"GetState: resultCode = {resultCode.ToString()}");
|
||||||
return resultCode;
|
return resultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,18 +136,22 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
if (_state == NetworkState.StationConnected)
|
if (_state == NetworkState.StationConnected)
|
||||||
{
|
{
|
||||||
networkInfo = _station.NetworkInfo;
|
networkInfo = _station.NetworkInfo;
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,"GetNetworkInfoImpl: _station");
|
||||||
}
|
}
|
||||||
else if (_state == NetworkState.AccessPointCreated)
|
else if (_state == NetworkState.AccessPointCreated)
|
||||||
{
|
{
|
||||||
networkInfo = _accessPoint.NetworkInfo;
|
networkInfo = _accessPoint.NetworkInfo;
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,"GetNetworkInfoImpl: _accessPoint");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
networkInfo = new NetworkInfo();
|
networkInfo = new NetworkInfo();
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,"GetNetworkInfoImpl: Invalid state!");
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,$"GetNetworkInfoImpl: networkInfo = {networkInfo}");
|
||||||
return ResultCode.InvalidState;
|
return ResultCode.InvalidState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,$"GetNetworkInfoImpl: networkInfo = {networkInfo}");
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,7 +203,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.ServiceLdn, $"Console's LDN IP is \"{unicastAddress.Address}\".");
|
Logger.NetLog?.Print(LogClass.ServiceLdn, $"Console's LDN IP is \"{unicastAddress.Address}\".");
|
||||||
|
|
||||||
context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(unicastAddress.Address));
|
context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(unicastAddress.Address));
|
||||||
context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(unicastAddress.IPv4Mask));
|
context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(unicastAddress.IPv4Mask));
|
||||||
@@ -206,7 +211,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.ServiceLdn, $"LDN obtained proxy IP.");
|
Logger.NetLog?.Print(LogClass.ServiceLdn, $"LDN obtained proxy IP.");
|
||||||
|
|
||||||
context.ResponseData.Write(config.ProxyIp);
|
context.ResponseData.Write(config.ProxyIp);
|
||||||
context.ResponseData.Write(config.ProxySubnetMask);
|
context.ResponseData.Write(config.ProxySubnetMask);
|
||||||
@@ -227,7 +232,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
// NOTE: Returns ResultCode.InvalidArgument if _disconnectReason is null, doesn't occur in our case.
|
// NOTE: Returns ResultCode.InvalidArgument if _disconnectReason is null, doesn't occur in our case.
|
||||||
|
|
||||||
context.ResponseData.Write((short)_disconnectReason);
|
context.ResponseData.Write((short)_disconnectReason);
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetDisconnectReason: {_disconnectReason}");
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,12 +252,14 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
{
|
{
|
||||||
if (_nifmResultCode != ResultCode.Success)
|
if (_nifmResultCode != ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetSecurityParameter: _nifmResultCode = {_nifmResultCode}");
|
||||||
return _nifmResultCode;
|
return _nifmResultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo);
|
ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo);
|
||||||
if (resultCode != ResultCode.Success)
|
if (resultCode != ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetSecurityParameter: resultCode = {resultCode}");
|
||||||
return resultCode;
|
return resultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +270,8 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
};
|
};
|
||||||
|
|
||||||
context.ResponseData.WriteStruct(securityParameter);
|
context.ResponseData.WriteStruct(securityParameter);
|
||||||
|
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetSecurityParameter: securityParameter = {securityParameter}");
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,12 +281,14 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
{
|
{
|
||||||
if (_nifmResultCode != ResultCode.Success)
|
if (_nifmResultCode != ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetNetworkConfig: _nifmResultCode = {_nifmResultCode}");
|
||||||
return _nifmResultCode;
|
return _nifmResultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo);
|
ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo);
|
||||||
if (resultCode != ResultCode.Success)
|
if (resultCode != ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetNetworkConfig: resultCode = {resultCode}");
|
||||||
return resultCode;
|
return resultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,6 +302,8 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
};
|
};
|
||||||
|
|
||||||
context.ResponseData.WriteStruct(networkConfig);
|
context.ResponseData.WriteStruct(networkConfig);
|
||||||
|
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetNetworkConfig: networkConfig = {networkConfig}");
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
@@ -322,12 +334,14 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
|
|
||||||
if (_nifmResultCode != ResultCode.Success)
|
if (_nifmResultCode != ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetNetworkInfoLatestUpdate: _nifmResultCode = {_nifmResultCode}");
|
||||||
return _nifmResultCode;
|
return _nifmResultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo);
|
ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo);
|
||||||
if (resultCode != ResultCode.Success)
|
if (resultCode != ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetNetworkInfoLatestUpdate: resultCode = {resultCode}");
|
||||||
return resultCode;
|
return resultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,6 +392,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
|
|
||||||
if (_nifmResultCode != ResultCode.Success)
|
if (_nifmResultCode != ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ScanImpl: _nifmResultCode = {_nifmResultCode}");
|
||||||
return _nifmResultCode;
|
return _nifmResultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,6 +415,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
{
|
{
|
||||||
if (scanFilter.Ssid.Length <= 31)
|
if (scanFilter.Ssid.Length <= 31)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ScanImpl: resultCode = {resultCode}");
|
||||||
return resultCode;
|
return resultCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -408,11 +424,13 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
{
|
{
|
||||||
if (scanFilterFlag > ScanFilterFlag.All)
|
if (scanFilterFlag > ScanFilterFlag.All)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ScanImpl: resultCode = {resultCode}");
|
||||||
return resultCode;
|
return resultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_state - 3 >= NetworkState.AccessPoint)
|
if (_state - 3 >= NetworkState.AccessPoint)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "ScanImpl: Invalid state!");
|
||||||
resultCode = ResultCode.InvalidState;
|
resultCode = ResultCode.InvalidState;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -437,7 +455,8 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ScanImpl: resultCode = {resultCode}");
|
||||||
return resultCode;
|
return resultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,6 +481,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ScanInternal: availableGames = {availableGames}");
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,7 +522,8 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
throw new ArgumentException($"{GetType().FullName}: Protocol value is not 1 or 3!! Protocol value: {protocolValue}");
|
throw new ArgumentException($"{GetType().FullName}: Protocol value is not 1 or 3!! Protocol value: {protocolValue}");
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceLdn, $"Protocol value: {protocolValue}");
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"SetProtocol: protocolValue = {protocolValue}");
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceLdn, new { protocolValue});
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,11 +533,13 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
{
|
{
|
||||||
if (_nifmResultCode != ResultCode.Success)
|
if (_nifmResultCode != ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"OpenAccessPoint: _nifmResultCode = {_nifmResultCode}");
|
||||||
return _nifmResultCode;
|
return _nifmResultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_state != NetworkState.Initialized)
|
if (_state != NetworkState.Initialized)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "OpenAccessPoint: Invalid state!");
|
||||||
return ResultCode.InvalidState;
|
return ResultCode.InvalidState;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,6 +561,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
{
|
{
|
||||||
if (_nifmResultCode != ResultCode.Success)
|
if (_nifmResultCode != ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"CloseAccessPoint: _nifmResultCode = {_nifmResultCode}");
|
||||||
return _nifmResultCode;
|
return _nifmResultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -547,6 +571,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "CloseAccessPoint: Invalid state!");
|
||||||
return ResultCode.InvalidState;
|
return ResultCode.InvalidState;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -596,11 +621,13 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
bool isLocalCommunicationIdValid = CheckLocalCommunicationIdPermission(context, (ulong)networkConfig.IntentId.LocalCommunicationId);
|
bool isLocalCommunicationIdValid = CheckLocalCommunicationIdPermission(context, (ulong)networkConfig.IntentId.LocalCommunicationId);
|
||||||
if (!isLocalCommunicationIdValid && NetworkClient.NeedsRealId)
|
if (!isLocalCommunicationIdValid && NetworkClient.NeedsRealId)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "CreateNetworkImpl: Invalid object!");
|
||||||
return ResultCode.InvalidObject;
|
return ResultCode.InvalidObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_nifmResultCode != ResultCode.Success)
|
if (_nifmResultCode != ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"CreateNetworkImpl: _nifmResultCode = {_nifmResultCode}");
|
||||||
return _nifmResultCode;
|
return _nifmResultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -629,16 +656,22 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
AddressList addressList = MemoryMarshal.Cast<byte, AddressList>(addressListBytes)[0];
|
AddressList addressList = MemoryMarshal.Cast<byte, AddressList>(addressListBytes)[0];
|
||||||
|
|
||||||
_accessPoint.CreateNetworkPrivate(securityConfig, securityParameter, userConfig, networkConfig, addressList);
|
_accessPoint.CreateNetworkPrivate(securityConfig, securityParameter, userConfig, networkConfig, addressList);
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"CreateNetworkImpl: Created private network! " +
|
||||||
|
$"| securityConfig = {securityConfig} | securityParameter = {securityParameter} " +
|
||||||
|
$"| userConfig = {userConfig} | networkConfig = {networkConfig} | addressList = {addressList}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_accessPoint.CreateNetwork(securityConfig, userConfig, networkConfig);
|
_accessPoint.CreateNetwork(securityConfig, userConfig, networkConfig);
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"CreateNetworkImpl: Created network! " +
|
||||||
|
$"| securityConfig = {securityConfig} | userConfig = {userConfig} | networkConfig = {networkConfig}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "CreateNetworkImpl: Invalid state!");
|
||||||
return ResultCode.InvalidState;
|
return ResultCode.InvalidState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -660,6 +693,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
{
|
{
|
||||||
if (_nifmResultCode != ResultCode.Success)
|
if (_nifmResultCode != ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"DestroyNetworkImpl: _nifmResultCode = {_nifmResultCode}");
|
||||||
return _nifmResultCode;
|
return _nifmResultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -676,9 +710,11 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
|
|
||||||
CloseAccessPoint();
|
CloseAccessPoint();
|
||||||
|
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "DestroyNetworkImpl: Invalid state!");
|
||||||
return ResultCode.InvalidState;
|
return ResultCode.InvalidState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "DestroyNetworkImpl: Invalid argument!");
|
||||||
return ResultCode.InvalidArgument;
|
return ResultCode.InvalidArgument;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -695,14 +731,17 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
{
|
{
|
||||||
if (_nifmResultCode != ResultCode.Success)
|
if (_nifmResultCode != ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"RejectImpl: _nifmResultCode = {_nifmResultCode}");
|
||||||
return _nifmResultCode;
|
return _nifmResultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_state != NetworkState.AccessPointCreated)
|
if (_state != NetworkState.AccessPointCreated)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "RejectImpl: Invalid state!");
|
||||||
return ResultCode.InvalidState; // Must be network host to reject nodes.
|
return ResultCode.InvalidState; // Must be network host to reject nodes.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"RejectImpl: disconnectReason = {disconnectReason} | nodeId = {nodeId}");
|
||||||
return NetworkClient.Reject(disconnectReason, nodeId);
|
return NetworkClient.Reject(disconnectReason, nodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -714,11 +753,13 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
|
|
||||||
if (_nifmResultCode != ResultCode.Success)
|
if (_nifmResultCode != ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"SetAdvertiseData: _nifmResultCode = {_nifmResultCode}");
|
||||||
return _nifmResultCode;
|
return _nifmResultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bufferSize is 0 or > LdnConst.AdvertiseDataSizeMax)
|
if (bufferSize is 0 or > LdnConst.AdvertiseDataSizeMax)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "SetAdvertiseData: Invalid argument!");
|
||||||
return ResultCode.InvalidArgument;
|
return ResultCode.InvalidArgument;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -727,11 +768,12 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
byte[] advertiseData = new byte[bufferSize];
|
byte[] advertiseData = new byte[bufferSize];
|
||||||
|
|
||||||
context.Memory.Read(bufferPosition, advertiseData);
|
context.Memory.Read(bufferPosition, advertiseData);
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"SetAdvertiseData: advertiseData = {advertiseData}");
|
||||||
return _accessPoint.SetAdvertiseData(advertiseData);
|
return _accessPoint.SetAdvertiseData(advertiseData);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "SetAdvertiseData: Invalid state!");
|
||||||
return ResultCode.InvalidState;
|
return ResultCode.InvalidState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -744,20 +786,24 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
|
|
||||||
if (_nifmResultCode != ResultCode.Success)
|
if (_nifmResultCode != ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"SetStationAcceptPolicy: _nifmResultCode = {_nifmResultCode}");
|
||||||
return _nifmResultCode;
|
return _nifmResultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (acceptPolicy > AcceptPolicy.WhiteList)
|
if (acceptPolicy > AcceptPolicy.WhiteList)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "SetStationAcceptPolicy: Invalid argument!");
|
||||||
return ResultCode.InvalidArgument;
|
return ResultCode.InvalidArgument;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_state is NetworkState.AccessPoint or NetworkState.AccessPointCreated)
|
if (_state is NetworkState.AccessPoint or NetworkState.AccessPointCreated)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"SetStationAcceptPolicy: acceptPolicy = {acceptPolicy}");
|
||||||
return _accessPoint.SetStationAcceptPolicy(acceptPolicy);
|
return _accessPoint.SetStationAcceptPolicy(acceptPolicy);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "SetStationAcceptPolicy: Invalid state!");
|
||||||
return ResultCode.InvalidState;
|
return ResultCode.InvalidState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -768,6 +814,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
{
|
{
|
||||||
if (_nifmResultCode != ResultCode.Success)
|
if (_nifmResultCode != ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"AddAcceptFilterEntry: _nifmResultCode = {_nifmResultCode}");
|
||||||
return _nifmResultCode;
|
return _nifmResultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -782,6 +829,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
{
|
{
|
||||||
if (_nifmResultCode != ResultCode.Success)
|
if (_nifmResultCode != ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ClearAcceptFilter: _nifmResultCode = {_nifmResultCode}");
|
||||||
return _nifmResultCode;
|
return _nifmResultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -796,11 +844,13 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
{
|
{
|
||||||
if (_nifmResultCode != ResultCode.Success)
|
if (_nifmResultCode != ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"OpenStation: _nifmResultCode = {_nifmResultCode}");
|
||||||
return _nifmResultCode;
|
return _nifmResultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_state != NetworkState.Initialized)
|
if (_state != NetworkState.Initialized)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "OpenStation: Invalid state!");
|
||||||
return ResultCode.InvalidState;
|
return ResultCode.InvalidState;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -813,6 +863,8 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
|
|
||||||
// NOTE: Calls nifm service and returns related result codes.
|
// NOTE: Calls nifm service and returns related result codes.
|
||||||
// Since we use our own implementation we can return ResultCode.Success.
|
// Since we use our own implementation we can return ResultCode.Success.
|
||||||
|
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"OpenStation: _station = {_station}");
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
@@ -823,6 +875,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
{
|
{
|
||||||
if (_nifmResultCode != ResultCode.Success)
|
if (_nifmResultCode != ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"CloseStation: _nifmResultCode = {_nifmResultCode}");
|
||||||
return _nifmResultCode;
|
return _nifmResultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -832,11 +885,13 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "CloseStation: Invalid state!");
|
||||||
return ResultCode.InvalidState;
|
return ResultCode.InvalidState;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetState(NetworkState.Initialized);
|
SetState(NetworkState.Initialized);
|
||||||
|
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "CloseStation: Closed.");
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -901,11 +956,13 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
bool isLocalCommunicationIdValid = CheckLocalCommunicationIdPermission(context, (ulong)networkInfo.NetworkId.IntentId.LocalCommunicationId);
|
bool isLocalCommunicationIdValid = CheckLocalCommunicationIdPermission(context, (ulong)networkInfo.NetworkId.IntentId.LocalCommunicationId);
|
||||||
if (!isLocalCommunicationIdValid && NetworkClient.NeedsRealId)
|
if (!isLocalCommunicationIdValid && NetworkClient.NeedsRealId)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "ConnectImpl: Invalid object!");
|
||||||
return ResultCode.InvalidObject;
|
return ResultCode.InvalidObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_nifmResultCode != ResultCode.Success)
|
if (_nifmResultCode != ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ConnectImpl: _nifmResultCode = {_nifmResultCode}");
|
||||||
return _nifmResultCode;
|
return _nifmResultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -925,6 +982,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
{
|
{
|
||||||
if (_state != NetworkState.Station)
|
if (_state != NetworkState.Station)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "ConnectImpl: Invalid state!");
|
||||||
resultCode = ResultCode.InvalidState;
|
resultCode = ResultCode.InvalidState;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -932,10 +990,16 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
if (isPrivate)
|
if (isPrivate)
|
||||||
{
|
{
|
||||||
resultCode = _station.ConnectPrivate(securityConfig, securityParameter, userConfig, localCommunicationVersion, optionUnknown, networkConfig);
|
resultCode = _station.ConnectPrivate(securityConfig, securityParameter, userConfig, localCommunicationVersion, optionUnknown, networkConfig);
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ConnectImpl: Private connection established! " +
|
||||||
|
$"| securityConfig = {securityConfig} | securityParameter = {securityParameter} | userConfig = {userConfig} " +
|
||||||
|
$"| localCommunicationVersion = {localCommunicationVersion} | optionUnknown = {optionUnknown} | networkConfig = {networkConfig}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
resultCode = _station.Connect(securityConfig, userConfig, localCommunicationVersion, optionUnknown, networkInfo);
|
resultCode = _station.Connect(securityConfig, userConfig, localCommunicationVersion, optionUnknown, networkInfo);
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ConnectImpl: Connection established! " +
|
||||||
|
$"| securityConfig = {securityConfig} | userConfig = {userConfig} " +
|
||||||
|
$"| localCommunicationVersion = {localCommunicationVersion} | optionUnknown = {optionUnknown} | networkConfig = {networkConfig}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -943,6 +1007,8 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ConnectImpl: resultCode = {resultCode}");
|
||||||
|
|
||||||
return resultCode;
|
return resultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -957,6 +1023,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
{
|
{
|
||||||
if (_nifmResultCode != ResultCode.Success)
|
if (_nifmResultCode != ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"DisconnectImpl: _nifmResultCode = {_nifmResultCode}");
|
||||||
return _nifmResultCode;
|
return _nifmResultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -970,14 +1037,17 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
|
|
||||||
_disconnectReason = disconnectReason;
|
_disconnectReason = disconnectReason;
|
||||||
|
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"DisconnectImpl: _disconnectReason = {_disconnectReason}");
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
CloseStation();
|
CloseStation();
|
||||||
|
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "DisconnectImpl: Invalid state!");
|
||||||
return ResultCode.InvalidState;
|
return ResultCode.InvalidState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "DisconnectImpl: Invalid argument!");
|
||||||
return ResultCode.InvalidArgument;
|
return ResultCode.InvalidArgument;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -994,6 +1064,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
{
|
{
|
||||||
if (_nifmResultCode != ResultCode.Success)
|
if (_nifmResultCode != ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"Finalize: _disconnectReason = {_disconnectReason}");
|
||||||
return _nifmResultCode;
|
return _nifmResultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1010,11 +1081,13 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
_stateChangeEventHandle = 0;
|
_stateChangeEventHandle = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"Finalize: resultCode = {resultCode}");
|
||||||
return resultCode;
|
return resultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResultCode FinalizeImpl(bool isCausedBySystem)
|
private ResultCode FinalizeImpl(bool isCausedBySystem)
|
||||||
{
|
{
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "FinalizeImpl");
|
||||||
DisconnectReason disconnectReason;
|
DisconnectReason disconnectReason;
|
||||||
|
|
||||||
switch (_state)
|
switch (_state)
|
||||||
@@ -1138,7 +1211,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
NetworkClient.SetGameVersion(context.Device.Processes.ActiveApplication.ApplicationControlProperties.DisplayVersion);
|
NetworkClient.SetGameVersion(context.Device.Processes.ActiveApplication.ApplicationControlProperties.DisplayVersion);
|
||||||
|
|
||||||
resultCode = ResultCode.Success;
|
resultCode = ResultCode.Success;
|
||||||
|
|
||||||
_nifmResultCode = resultCode;
|
_nifmResultCode = resultCode;
|
||||||
|
|
||||||
SetState(NetworkState.Initialized);
|
SetState(NetworkState.Initialized);
|
||||||
@@ -1152,6 +1224,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"InitializeImpl: resultCode = {resultCode}");
|
||||||
return resultCode;
|
return resultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu
|
|||||||
|
|
||||||
protected override void OnConnected()
|
protected override void OnConnected()
|
||||||
{
|
{
|
||||||
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"LDN TCP client connected a new session with Id {Id}");
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"LDN TCP client connected a new session with Id {Id}");
|
||||||
|
|
||||||
UpdatePassphraseIfNeeded();
|
UpdatePassphraseIfNeeded();
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu
|
|||||||
|
|
||||||
protected override void OnDisconnected()
|
protected override void OnDisconnected()
|
||||||
{
|
{
|
||||||
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"LDN TCP client disconnected a session with Id {Id}");
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"LDN TCP client disconnected a session with Id {Id}");
|
||||||
|
|
||||||
_passphrase = null;
|
_passphrase = null;
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu
|
|||||||
|
|
||||||
protected override void OnError(SocketError error)
|
protected override void OnError(SocketError error)
|
||||||
{
|
{
|
||||||
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"LDN TCP client caught an error with code {error}");
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"LDN TCP client caught an error with code {error}");
|
||||||
|
|
||||||
_error.Set();
|
_error.Set();
|
||||||
}
|
}
|
||||||
@@ -428,7 +428,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.ServiceLdn, $"Created a wireless P2P network on port {request.ExternalProxyPort}.");
|
Logger.NetLog?.Print(LogClass.ServiceLdn, $"Created a wireless P2P network on port {request.ExternalProxyPort}.");
|
||||||
_hostedProxy.Start();
|
_hostedProxy.Start();
|
||||||
|
|
||||||
(_, UnicastIPAddressInformation unicastAddress) = NetworkHelpers.GetLocalInterface();
|
(_, UnicastIPAddressInformation unicastAddress) = NetworkHelpers.GetLocalInterface();
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.Memory;
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
||||||
@@ -36,10 +37,12 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
if (Connected)
|
if (Connected)
|
||||||
{
|
{
|
||||||
_parent.SetState(NetworkState.StationConnected);
|
_parent.SetState(NetworkState.StationConnected);
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,$"NetworkChanged: {NetworkInfo}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_parent.SetDisconnectReason(e.DisconnectReasonOrDefault(DisconnectReason.DestroyedByUser));
|
_parent.SetDisconnectReason(e.DisconnectReasonOrDefault(DisconnectReason.DestroyedByUser));
|
||||||
|
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,"NetworkChanged: Disconnected (DestroyedByUser)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -81,8 +81,10 @@ namespace Ryujinx.HLE.HOS.Services.Mii
|
|||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResultCode UpdateLatest<T>(DatabaseSessionMetadata metadata, IStoredData<T> oldMiiData, SourceFlag flag, IStoredData<T> newMiiData) where T : unmanaged
|
public ResultCode UpdateLatest<T>(DatabaseSessionMetadata metadata, T oldMiiData, SourceFlag flag, out T newMiiData) where T : unmanaged, IStoredData<T>
|
||||||
{
|
{
|
||||||
|
newMiiData = default;
|
||||||
|
|
||||||
if (!flag.HasFlag(SourceFlag.Database))
|
if (!flag.HasFlag(SourceFlag.Database))
|
||||||
{
|
{
|
||||||
return ResultCode.NotFound;
|
return ResultCode.NotFound;
|
||||||
@@ -106,7 +108,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii
|
|||||||
|
|
||||||
newMiiData.SetFromStoreData(storeData);
|
newMiiData.SetFromStoreData(storeData);
|
||||||
|
|
||||||
if (oldMiiData == newMiiData)
|
if (oldMiiData.Equals(newMiiData))
|
||||||
{
|
{
|
||||||
return ResultCode.NotUpdated;
|
return ResultCode.NotUpdated;
|
||||||
}
|
}
|
||||||
@@ -286,6 +288,18 @@ namespace Ryujinx.HLE.HOS.Services.Mii
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ResultCode Append(DatabaseSessionMetadata metadata, CharInfo charInfo)
|
||||||
|
{
|
||||||
|
ResultCode result = _miiDatabase.Append(metadata, _utilityImpl, charInfo);
|
||||||
|
|
||||||
|
if (result == ResultCode.Success)
|
||||||
|
{
|
||||||
|
result = _miiDatabase.SaveDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public ResultCode ConvertCharInfoToCoreData(CharInfo charInfo, out CoreData coreData)
|
public ResultCode ConvertCharInfoToCoreData(CharInfo charInfo, out CoreData coreData)
|
||||||
{
|
{
|
||||||
coreData = new CoreData();
|
coreData = new CoreData();
|
||||||
|
|||||||
@@ -449,6 +449,32 @@ namespace Ryujinx.HLE.HOS.Services.Mii
|
|||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ResultCode Append(DatabaseSessionMetadata metadata, UtilityImpl utilityImpl, CharInfo charInfo)
|
||||||
|
{
|
||||||
|
if (!charInfo.IsValid())
|
||||||
|
{
|
||||||
|
return ResultCode.InvalidCharInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (charInfo.Type == 1)
|
||||||
|
{
|
||||||
|
return ResultCode.InvalidOperationOnSpecialMii;
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreData coreData = new();
|
||||||
|
coreData.SetFromCharInfo(charInfo);
|
||||||
|
|
||||||
|
StoreData storeData;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
storeData = StoreData.BuildFromCoreData(utilityImpl, coreData);
|
||||||
|
}
|
||||||
|
while (_database.GetIndexByCreatorId(out _, storeData.CreateId));
|
||||||
|
|
||||||
|
return AddOrReplace(metadata, storeData);
|
||||||
|
}
|
||||||
|
|
||||||
public ResultCode Delete(DatabaseSessionMetadata metadata, CreateId createId)
|
public ResultCode Delete(DatabaseSessionMetadata metadata, CreateId createId)
|
||||||
{
|
{
|
||||||
if (!_database.GetIndexByCreatorId(out int index, createId))
|
if (!_database.GetIndexByCreatorId(out int index, createId))
|
||||||
|
|||||||
@@ -54,9 +54,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService
|
|||||||
|
|
||||||
protected override ResultCode UpdateLatest(CharInfo oldCharInfo, SourceFlag flag, out CharInfo newCharInfo)
|
protected override ResultCode UpdateLatest(CharInfo oldCharInfo, SourceFlag flag, out CharInfo newCharInfo)
|
||||||
{
|
{
|
||||||
newCharInfo = default;
|
return _database.UpdateLatest(_metadata, oldCharInfo, flag, out newCharInfo);
|
||||||
|
|
||||||
return _database.UpdateLatest(_metadata, oldCharInfo, flag, newCharInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ResultCode BuildRandom(Age age, Gender gender, Race race, out CharInfo charInfo)
|
protected override ResultCode BuildRandom(Age age, Gender gender, Race race, out CharInfo charInfo)
|
||||||
@@ -113,14 +111,14 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService
|
|||||||
|
|
||||||
protected override ResultCode UpdateLatest1(StoreData oldStoreData, SourceFlag flag, out StoreData newStoreData)
|
protected override ResultCode UpdateLatest1(StoreData oldStoreData, SourceFlag flag, out StoreData newStoreData)
|
||||||
{
|
{
|
||||||
newStoreData = default;
|
|
||||||
|
|
||||||
if (!_isSystem)
|
if (!_isSystem)
|
||||||
{
|
{
|
||||||
|
newStoreData = default;
|
||||||
|
|
||||||
return ResultCode.PermissionDenied;
|
return ResultCode.PermissionDenied;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _database.UpdateLatest(_metadata, oldStoreData, flag, newStoreData);
|
return _database.UpdateLatest(_metadata, oldStoreData, flag, out newStoreData);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ResultCode FindIndex(CreateId createId, bool isSpecial, out int index)
|
protected override ResultCode FindIndex(CreateId createId, bool isSpecial, out int index)
|
||||||
@@ -262,5 +260,10 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService
|
|||||||
{
|
{
|
||||||
return _database.ConvertCharInfoToCoreData(charInfo, out coreData);
|
return _database.ConvertCharInfoToCoreData(charInfo, out coreData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override ResultCode Append(CharInfo charInfo)
|
||||||
|
{
|
||||||
|
return _database.Append(_metadata, charInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -340,6 +340,15 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[CommandCmif(26)] // 10.2.0+
|
||||||
|
// Append(nn::mii::CharInfo char_info)
|
||||||
|
public ResultCode Append(ServiceCtx context)
|
||||||
|
{
|
||||||
|
CharInfo charInfo = context.RequestData.ReadStruct<CharInfo>();
|
||||||
|
|
||||||
|
return Append(charInfo);
|
||||||
|
}
|
||||||
|
|
||||||
private Span<byte> CreateByteSpanFromBuffer(ServiceCtx context, IpcBuffDesc ipcBuff, bool isOutput)
|
private Span<byte> CreateByteSpanFromBuffer(ServiceCtx context, IpcBuffDesc ipcBuff, bool isOutput)
|
||||||
{
|
{
|
||||||
byte[] rawData;
|
byte[] rawData;
|
||||||
@@ -421,5 +430,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService
|
|||||||
protected abstract ResultCode ConvertCoreDataToCharInfo(CoreData coreData, out CharInfo charInfo);
|
protected abstract ResultCode ConvertCoreDataToCharInfo(CoreData coreData, out CharInfo charInfo);
|
||||||
|
|
||||||
protected abstract ResultCode ConvertCharInfoToCoreData(CharInfo charInfo, out CoreData coreData);
|
protected abstract ResultCode ConvertCharInfoToCoreData(CharInfo charInfo, out CoreData coreData);
|
||||||
|
|
||||||
|
protected abstract ResultCode Append(CharInfo charInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
namespace Ryujinx.HLE.HOS.Services.Notification
|
||||||
|
{
|
||||||
|
[Service("notif:s")] // 9.0.0+
|
||||||
|
class INotificationServices : IpcService
|
||||||
|
{
|
||||||
|
public INotificationServices(ServiceCtx context) { }
|
||||||
|
|
||||||
|
[CommandCmif(1000)] // 9.0.0+
|
||||||
|
// GetNotificationCount() -> nn::notification::server::INotificationSystemEventAccessor
|
||||||
|
public ResultCode GetNotificationCount(ServiceCtx context)
|
||||||
|
{
|
||||||
|
MakeObject(context, new INotificationSystemEventAccessor(context));
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(1040)] // 9.0.0+
|
||||||
|
// GetNotificationSendingNotifier() -> nn::notification::server::INotificationSystemEventAccessor
|
||||||
|
public ResultCode GetNotificationSendingNotifier(ServiceCtx context)
|
||||||
|
{
|
||||||
|
MakeObject(context, new INotificationSystemEventAccessor(context));
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,33 @@
|
|||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Notification
|
namespace Ryujinx.HLE.HOS.Services.Notification
|
||||||
{
|
{
|
||||||
[Service("notif:a")] // 9.0.0+
|
[Service("notif:a")] // 9.0.0+
|
||||||
class INotificationServicesForApplication : IpcService
|
class INotificationServicesForApplication : IpcService
|
||||||
{
|
{
|
||||||
public INotificationServicesForApplication(ServiceCtx context) { }
|
public INotificationServicesForApplication(ServiceCtx context) { }
|
||||||
|
|
||||||
|
// Leaving this here since I can never find it: https://switchbrew.org/wiki/Glue_services
|
||||||
|
|
||||||
|
[CommandCmif(520)] // 9.0.0+
|
||||||
|
// ListAlarmSettings(nn::arp::ApplicationCertificate) -> s32 AlarmSettingsCount
|
||||||
|
public ResultCode ListAlarmSettings(ServiceCtx context)
|
||||||
|
{
|
||||||
|
// TO-DO: Currently just returns 0. Should read in an ApplicationCertificate.
|
||||||
|
int alarmSettingsCount = 0;
|
||||||
|
context.ResponseData.Write(alarmSettingsCount);
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(1000)] // 9.0.0+
|
||||||
|
// Initialize(PID-descriptor, u64 pid_reserved)
|
||||||
|
public ResultCode Intialize(ServiceCtx context)
|
||||||
|
{
|
||||||
|
ulong pid = context.Request.HandleDesc.PId;
|
||||||
|
context.RequestData.ReadUInt64(); // pid placeholder, zero
|
||||||
|
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceNotification, new { pid });
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace Ryujinx.HLE.HOS.Services.Notification
|
|
||||||
{
|
|
||||||
[Service("notif:s")] // 9.0.0+
|
|
||||||
class INotificationServicesForSystem : IpcService
|
|
||||||
{
|
|
||||||
public INotificationServicesForSystem(ServiceCtx context) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.HLE.HOS.Ipc;
|
||||||
|
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||||
|
using Ryujinx.Horizon.Common;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Notification
|
||||||
|
{
|
||||||
|
class INotificationSystemEventAccessor : IpcService
|
||||||
|
{
|
||||||
|
|
||||||
|
private readonly KEvent _getNotificationSendingNotifierEvent;
|
||||||
|
private int _getNotificationSendingNotifierEventHandle;
|
||||||
|
public INotificationSystemEventAccessor(ServiceCtx context) { }
|
||||||
|
|
||||||
|
[CommandCmif(0)] // 9.0.0+
|
||||||
|
// GetNotificationSendingNotifier() -> nn::notification::server::INotificationSystemEventAccessor
|
||||||
|
public ResultCode GetSystemEvent(ServiceCtx context)
|
||||||
|
{
|
||||||
|
if (_getNotificationSendingNotifierEventHandle == 0)
|
||||||
|
{
|
||||||
|
if (context.Process.HandleTable.GenerateHandle(_getNotificationSendingNotifierEvent.ReadableEvent, out _getNotificationSendingNotifierEventHandle) != Result.Success)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Out of handles!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_getNotificationSendingNotifierEventHandle);
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ using Ryujinx.HLE.Loaders.Executables;
|
|||||||
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
@@ -27,10 +28,16 @@ namespace Ryujinx.HLE.Loaders.Processes
|
|||||||
|
|
||||||
private ulong _latestPid;
|
private ulong _latestPid;
|
||||||
|
|
||||||
public ProcessResult ActiveApplication
|
#nullable enable
|
||||||
|
public ProcessResult? ActiveApplication
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
return _processesByPid.GetValueOrDefault(_latestPid);
|
||||||
|
|
||||||
|
// Using this if statement locks up the UI and prevents a new game from loading.
|
||||||
|
// Haven't quite deduced why yet.
|
||||||
|
|
||||||
if (!_processesByPid.TryGetValue(_latestPid, out ProcessResult value))
|
if (!_processesByPid.TryGetValue(_latestPid, out ProcessResult value))
|
||||||
throw new RyujinxException(
|
throw new RyujinxException(
|
||||||
$"The HLE Process map did not have a process with ID {_latestPid}. Are you missing firmware?");
|
$"The HLE Process map did not have a process with ID {_latestPid}. Are you missing firmware?");
|
||||||
@@ -38,6 +45,7 @@ namespace Ryujinx.HLE.Loaders.Processes
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
public ProcessLoader(Switch device)
|
public ProcessLoader(Switch device)
|
||||||
{
|
{
|
||||||
@@ -144,7 +152,7 @@ namespace Ryujinx.HLE.Loaders.Processes
|
|||||||
public bool LoadUnpackedNca(string exeFsDirPath, string romFsPath = null)
|
public bool LoadUnpackedNca(string exeFsDirPath, string romFsPath = null)
|
||||||
{
|
{
|
||||||
ProcessResult processResult = new LocalFileSystem(exeFsDirPath).Load(_device, romFsPath);
|
ProcessResult processResult = new LocalFileSystem(exeFsDirPath).Load(_device, romFsPath);
|
||||||
|
|
||||||
if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult))
|
if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult))
|
||||||
{
|
{
|
||||||
if (processResult.Start(_device))
|
if (processResult.Start(_device))
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ using LibHac.Ns;
|
|||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Cpu;
|
using Ryujinx.Cpu;
|
||||||
using Ryujinx.HLE.HOS.SystemState;
|
using Ryujinx.HLE.HOS.SystemState;
|
||||||
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
|
||||||
using Ryujinx.Horizon.Common;
|
using Ryujinx.Horizon.Common;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.Loaders.Processes
|
namespace Ryujinx.HLE.Loaders.Processes
|
||||||
@@ -52,6 +51,7 @@ namespace Ryujinx.HLE.Loaders.Processes
|
|||||||
|
|
||||||
if (metaLoader is not null)
|
if (metaLoader is not null)
|
||||||
{
|
{
|
||||||
|
Logger.Info?.Print(LogClass.Application,$"metaLoader: {metaLoader}");
|
||||||
ulong programId = metaLoader.ProgramId;
|
ulong programId = metaLoader.ProgramId;
|
||||||
|
|
||||||
Name = ApplicationControlProperties.Title[(int)titleLanguage].NameString.ToString();
|
Name = ApplicationControlProperties.Title[(int)titleLanguage].NameString.ToString();
|
||||||
@@ -71,8 +71,15 @@ namespace Ryujinx.HLE.Loaders.Processes
|
|||||||
ProgramId = programId;
|
ProgramId = programId;
|
||||||
ProgramIdText = $"{programId:x16}";
|
ProgramIdText = $"{programId:x16}";
|
||||||
Is64Bit = metaLoader.IsProgram64Bit;
|
Is64Bit = metaLoader.IsProgram64Bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application,$"metaLoader is null !!!");
|
||||||
|
ProcessId = 0;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DiskCacheEnabled = diskCacheEnabled;
|
DiskCacheEnabled = diskCacheEnabled;
|
||||||
AllowCodeMemoryForJit = allowCodeMemoryForJit;
|
AllowCodeMemoryForJit = allowCodeMemoryForJit;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,9 @@
|
|||||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
|
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
|
||||||
<PackageReference Include="MsgPack.Cli" />
|
<PackageReference Include="MsgPack.Cli" />
|
||||||
<PackageReference Include="SkiaSharp" />
|
<PackageReference Include="SkiaSharp" />
|
||||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux" />
|
<PackageReference Include="SkiaSharp.NativeAssets.Win32" />
|
||||||
|
<PackageReference Include="SkiaSharp.NativeAssets.macOS" />
|
||||||
|
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" />
|
||||||
<PackageReference Include="NetCoreServer" />
|
<PackageReference Include="NetCoreServer" />
|
||||||
<PackageReference Include="Open.NAT.Core" />
|
<PackageReference Include="Open.NAT.Core" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -151,7 +151,9 @@ namespace Ryujinx.Input.SDL3
|
|||||||
result |= GamepadFeaturesFlag.Led;
|
result |= GamepadFeaturesFlag.Led;
|
||||||
}
|
}
|
||||||
SDL_UnlockProperties(propID);
|
SDL_UnlockProperties(propID);
|
||||||
SDL_DestroyProperties(propID);
|
|
||||||
|
// NOTE: Do not call SDL_DestroyProperties here. These properties are owned
|
||||||
|
// internally by SDL and are freed when SDL_CloseGamepad is called (in Dispose).
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -331,28 +331,18 @@ namespace Ryujinx.Input.SDL3
|
|||||||
|
|
||||||
public IEnumerable<IGamepad> GetGamepads()
|
public IEnumerable<IGamepad> GetGamepads()
|
||||||
{
|
{
|
||||||
lock (_gamepadsIds)
|
string[] ids;
|
||||||
|
lock (_lock)
|
||||||
{
|
{
|
||||||
foreach (var gamepad in _gamepadsIds)
|
ids = _gamepadsIds.Values
|
||||||
{
|
.Concat(_joyConsIds.Values)
|
||||||
yield return GetGamepad(gamepad.Value);
|
.Concat(_linkedJoyConsIds.Values)
|
||||||
}
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (_joyConsIds)
|
foreach (string id in ids)
|
||||||
{
|
{
|
||||||
foreach (var gamepad in _joyConsIds)
|
yield return GetGamepad(id);
|
||||||
{
|
|
||||||
yield return GetGamepad(gamepad.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_linkedJoyConsIds)
|
|
||||||
{
|
|
||||||
foreach (var gamepad in _linkedJoyConsIds)
|
|
||||||
{
|
|
||||||
yield return GetGamepad(gamepad.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,25 @@ using System.Runtime.CompilerServices;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using SDL;
|
using SDL;
|
||||||
using static SDL.SDL3;
|
using static SDL.SDL3;
|
||||||
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
|
||||||
|
using ConfigKey = Ryujinx.Common.Configuration.Hid.Key;
|
||||||
|
|
||||||
namespace Ryujinx.Input.SDL3
|
namespace Ryujinx.Input.SDL3
|
||||||
{
|
{
|
||||||
class SDL3Keyboard : IKeyboard
|
class SDL3Keyboard : IKeyboard
|
||||||
{
|
{
|
||||||
|
private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, Key From)
|
||||||
|
{
|
||||||
|
public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not Key.Unbound;
|
||||||
|
}
|
||||||
|
|
||||||
private readonly Lock _userMappingLock = new();
|
private readonly Lock _userMappingLock = new();
|
||||||
|
|
||||||
|
#pragma warning disable IDE0052 // Remove unread private member
|
||||||
|
private readonly SDL3KeyboardDriver _driver;
|
||||||
|
#pragma warning restore IDE0052
|
||||||
private StandardKeyboardInputConfig _configuration;
|
private StandardKeyboardInputConfig _configuration;
|
||||||
private readonly List<KeyboardInputMappingHelper.KeyboardButtonMapping> _buttonsUserMapping;
|
private readonly List<ButtonMappingEntry> _buttonsUserMapping;
|
||||||
|
|
||||||
|
|
||||||
private static readonly SDL_Keycode[] _keysDriverMapping =
|
private static readonly SDL_Keycode[] _keysDriverMapping =
|
||||||
@@ -161,8 +171,9 @@ namespace Ryujinx.Input.SDL3
|
|||||||
SDL_Keycode.SDLK_0
|
SDL_Keycode.SDLK_0
|
||||||
];
|
];
|
||||||
|
|
||||||
public SDL3Keyboard(string id, string name)
|
public SDL3Keyboard(SDL3KeyboardDriver driver, string id, string name)
|
||||||
{
|
{
|
||||||
|
_driver = driver;
|
||||||
Id = id;
|
Id = id;
|
||||||
Name = name;
|
Name = name;
|
||||||
_buttonsUserMapping = [];
|
_buttonsUserMapping = [];
|
||||||
@@ -184,9 +195,9 @@ namespace Ryujinx.Input.SDL3
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private unsafe static int ToSDL3Scancode(ConfigPhysicalKey key)
|
private unsafe static int ToSDL3Scancode(Key key)
|
||||||
{
|
{
|
||||||
if (key is >= ConfigPhysicalKey.Unknown and <= ConfigPhysicalKey.Menu)
|
if (key is >= Key.Unknown and <= Key.Menu)
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -194,18 +205,18 @@ namespace Ryujinx.Input.SDL3
|
|||||||
return (int)SDL_GetScancodeFromKey(_keysDriverMapping[(int)key], null);
|
return (int)SDL_GetScancodeFromKey(_keysDriverMapping[(int)key], null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SDL_Keymod GetKeyboardModifierMask(ConfigPhysicalKey key)
|
private static SDL_Keymod GetKeyboardModifierMask(Key key)
|
||||||
{
|
{
|
||||||
return key switch
|
return key switch
|
||||||
{
|
{
|
||||||
ConfigPhysicalKey.ShiftLeft => SDL_Keymod.SDL_KMOD_LSHIFT,
|
Key.ShiftLeft => SDL_Keymod.SDL_KMOD_LSHIFT,
|
||||||
ConfigPhysicalKey.ShiftRight => SDL_Keymod.SDL_KMOD_RSHIFT,
|
Key.ShiftRight => SDL_Keymod.SDL_KMOD_RSHIFT,
|
||||||
ConfigPhysicalKey.ControlLeft => SDL_Keymod.SDL_KMOD_LCTRL,
|
Key.ControlLeft => SDL_Keymod.SDL_KMOD_LCTRL,
|
||||||
ConfigPhysicalKey.ControlRight => SDL_Keymod.SDL_KMOD_RCTRL,
|
Key.ControlRight => SDL_Keymod.SDL_KMOD_RCTRL,
|
||||||
ConfigPhysicalKey.AltLeft => SDL_Keymod.SDL_KMOD_LALT,
|
Key.AltLeft => SDL_Keymod.SDL_KMOD_LALT,
|
||||||
ConfigPhysicalKey.AltRight => SDL_Keymod.SDL_KMOD_RALT,
|
Key.AltRight => SDL_Keymod.SDL_KMOD_RALT,
|
||||||
ConfigPhysicalKey.WinLeft => SDL_Keymod.SDL_KMOD_LGUI,
|
Key.WinLeft => SDL_Keymod.SDL_KMOD_LGUI,
|
||||||
ConfigPhysicalKey.WinRight => SDL_Keymod.SDL_KMOD_RGUI,
|
Key.WinRight => SDL_Keymod.SDL_KMOD_RGUI,
|
||||||
// NOTE: Menu key isn't supported by SDL3.
|
// NOTE: Menu key isn't supported by SDL3.
|
||||||
_ => SDL_Keymod.SDL_KMOD_NONE
|
_ => SDL_Keymod.SDL_KMOD_NONE
|
||||||
};
|
};
|
||||||
@@ -221,9 +232,9 @@ namespace Ryujinx.Input.SDL3
|
|||||||
rawKeyboardState = SDL_GetKeyboardState(null);
|
rawKeyboardState = SDL_GetKeyboardState(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool[] keysState = new bool[(int)ConfigPhysicalKey.Count];
|
bool[] keysState = new bool[(int)Key.Count];
|
||||||
|
|
||||||
for (ConfigPhysicalKey key = 0; key < ConfigPhysicalKey.Count; key++)
|
for (Key key = 0; key < Key.Count; key++)
|
||||||
{
|
{
|
||||||
int index = ToSDL3Scancode(key);
|
int index = ToSDL3Scancode(key);
|
||||||
if (index == -1)
|
if (index == -1)
|
||||||
@@ -253,6 +264,36 @@ namespace Ryujinx.Input.SDL3
|
|||||||
return value * ConvertRate;
|
return value * ConvertRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick<ConfigKey> stickConfig)
|
||||||
|
{
|
||||||
|
short stickX = 0;
|
||||||
|
short stickY = 0;
|
||||||
|
|
||||||
|
if (snapshot.IsPressed((Key)stickConfig.StickUp))
|
||||||
|
{
|
||||||
|
stickY += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.IsPressed((Key)stickConfig.StickDown))
|
||||||
|
{
|
||||||
|
stickY -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.IsPressed((Key)stickConfig.StickRight))
|
||||||
|
{
|
||||||
|
stickX += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.IsPressed((Key)stickConfig.StickLeft))
|
||||||
|
{
|
||||||
|
stickX -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 stick = Vector2.Normalize(new Vector2(stickX, stickY));
|
||||||
|
|
||||||
|
return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
|
||||||
|
}
|
||||||
|
|
||||||
public GamepadStateSnapshot GetMappedStateSnapshot()
|
public GamepadStateSnapshot GetMappedStateSnapshot()
|
||||||
{
|
{
|
||||||
KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot();
|
KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot();
|
||||||
@@ -265,9 +306,9 @@ namespace Ryujinx.Input.SDL3
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (KeyboardInputMappingHelper.KeyboardButtonMapping entry in _buttonsUserMapping)
|
foreach (ButtonMappingEntry entry in _buttonsUserMapping)
|
||||||
{
|
{
|
||||||
if (!entry.IsValid)
|
if (entry.From == Key.Unknown || entry.From == Key.Unbound || entry.To == GamepadButtonInputId.Unbound)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -279,8 +320,8 @@ namespace Ryujinx.Input.SDL3
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(short leftStickX, short leftStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.LeftJoyconStick);
|
(short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick);
|
||||||
(short rightStickX, short rightStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.RightJoyconStick);
|
(short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick);
|
||||||
|
|
||||||
result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
|
result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
|
||||||
result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY));
|
result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY));
|
||||||
@@ -316,15 +357,38 @@ namespace Ryujinx.Input.SDL3
|
|||||||
{
|
{
|
||||||
_configuration = (StandardKeyboardInputConfig)configuration;
|
_configuration = (StandardKeyboardInputConfig)configuration;
|
||||||
|
|
||||||
|
// First clear the buttons mapping
|
||||||
_buttonsUserMapping.Clear();
|
_buttonsUserMapping.Clear();
|
||||||
|
|
||||||
_buttonsUserMapping.AddRange(KeyboardInputMappingHelper.BuildButtonMappings(_configuration));
|
// Then configure left joycon
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl));
|
||||||
|
|
||||||
|
// Finally configure right joycon
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetLed(uint packedRgb)
|
public void SetLed(uint packedRgb)
|
||||||
{
|
{
|
||||||
Logger.Debug?.Print(LogClass.UI, "SetLed called on an SDL3Keyboard");
|
Logger.Info?.Print(LogClass.UI, "SetLed called on an SDL3Keyboard");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetTriggerThreshold(float triggerThreshold)
|
public void SetTriggerThreshold(float triggerThreshold)
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ namespace Ryujinx.Input.SDL3
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SDL3Keyboard(_keyboardIdentifers[0], "All keyboards");
|
return new SDL3Keyboard(this, _keyboardIdentifers[0], "All keyboards");
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<IGamepad> GetGamepads()
|
public IEnumerable<IGamepad> GetGamepads()
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using Ryujinx.Common.Logging;
|
|
||||||
|
|
||||||
namespace Ryujinx.Input.Assigner
|
namespace Ryujinx.Input.Assigner
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -10,42 +8,22 @@ namespace Ryujinx.Input.Assigner
|
|||||||
private readonly IKeyboard _keyboard;
|
private readonly IKeyboard _keyboard;
|
||||||
|
|
||||||
private KeyboardStateSnapshot _keyboardState;
|
private KeyboardStateSnapshot _keyboardState;
|
||||||
private Button? _pressedButton;
|
|
||||||
|
|
||||||
public KeyboardKeyAssigner(IKeyboard keyboard)
|
public KeyboardKeyAssigner(IKeyboard keyboard)
|
||||||
{
|
{
|
||||||
_keyboard = keyboard;
|
_keyboard = keyboard;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initialize()
|
public void Initialize() { }
|
||||||
{
|
|
||||||
_pressedButton = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ReadInput()
|
public void ReadInput()
|
||||||
{
|
{
|
||||||
_keyboardState = _keyboard.GetKeyboardStateSnapshot();
|
_keyboardState = _keyboard.GetKeyboardStateSnapshot();
|
||||||
|
|
||||||
if (_pressedButton is not null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Button? buttonFromState = GetPressedButtonFromState();
|
|
||||||
Button? buttonFromBufferedPress = buttonFromState is null ? GetPressedButtonFromBufferedPress() : null;
|
|
||||||
|
|
||||||
_pressedButton = buttonFromState ?? buttonFromBufferedPress;
|
|
||||||
|
|
||||||
if (_pressedButton is not null)
|
|
||||||
{
|
|
||||||
string source = buttonFromState is not null ? "state" : "buffered-press";
|
|
||||||
Logger.Debug?.Print(LogClass.UI, $"Keyboard assigner registered key={_pressedButton.Value.AsHidType<Key>()}, source={source}, cancelPressed={ShouldCancel()}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsAnyButtonPressed()
|
public bool IsAnyButtonPressed()
|
||||||
{
|
{
|
||||||
return _pressedButton is not null;
|
return GetPressedButton() is not null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ShouldCancel()
|
public bool ShouldCancel()
|
||||||
@@ -55,53 +33,18 @@ namespace Ryujinx.Input.Assigner
|
|||||||
|
|
||||||
public Button? GetPressedButton()
|
public Button? GetPressedButton()
|
||||||
{
|
{
|
||||||
return !ShouldCancel() ? _pressedButton : null;
|
Button? keyPressed = null;
|
||||||
}
|
|
||||||
|
|
||||||
private Button? GetPressedButtonFromState()
|
|
||||||
{
|
|
||||||
Key aliasedKey = GetAliasedPressedKey();
|
|
||||||
|
|
||||||
if (aliasedKey != Key.Unknown)
|
|
||||||
{
|
|
||||||
return new Button(aliasedKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Key key = Key.Unknown; key < Key.Count; key++)
|
for (Key key = Key.Unknown; key < Key.Count; key++)
|
||||||
{
|
{
|
||||||
if (_keyboardState.IsPressed(key))
|
if (_keyboardState.IsPressed(key))
|
||||||
{
|
{
|
||||||
return new Button(key);
|
keyPressed = new(key);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return !ShouldCancel() ? keyPressed : null;
|
||||||
}
|
|
||||||
|
|
||||||
private Button? GetPressedButtonFromBufferedPress()
|
|
||||||
{
|
|
||||||
return _keyboard.TryConsumePressedKey(out Key key) ? new Button(key) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Key GetAliasedPressedKey()
|
|
||||||
{
|
|
||||||
// On some layouts (for example AltGr on Windows), Right Alt is reported as Ctrl+Alt.
|
|
||||||
// Prefer AltRight in that case so the binding reflects the physical key used.
|
|
||||||
if (_keyboardState.IsPressed(Key.ControlLeft) && _keyboardState.IsPressed(Key.AltRight))
|
|
||||||
{
|
|
||||||
return Key.AltRight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// On some Copilot keyboards, the key in the right-control position is reported as
|
|
||||||
// ShiftLeft+Win+F23. Prefer ControlRight so the binding reflects that physical key.
|
|
||||||
if (_keyboardState.IsPressed(Key.ShiftLeft) &&
|
|
||||||
_keyboardState.IsPressed(Key.F23) &&
|
|
||||||
(_keyboardState.IsPressed(Key.WinLeft) || _keyboardState.IsPressed(Key.WinRight)))
|
|
||||||
{
|
|
||||||
return Key.ControlRight;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Key.Unknown;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using Ryujinx.Common;
|
|||||||
using Ryujinx.Common.Configuration.Hid;
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||||
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
|
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
|
||||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.HOS.Services.Hid;
|
using Ryujinx.HLE.HOS.Services.Hid;
|
||||||
using System;
|
using System;
|
||||||
@@ -235,9 +234,7 @@ namespace Ryujinx.Input.HLE
|
|||||||
_gamepad?.Dispose();
|
_gamepad?.Dispose();
|
||||||
|
|
||||||
Id = config.Id;
|
Id = config.Id;
|
||||||
_gamepad = config is StandardKeyboardInputConfig && GamepadDriver is IKeyboardModeDriver keyboardModeDriver
|
_gamepad = GamepadDriver.GetGamepad(Id);
|
||||||
? keyboardModeDriver.GetKeyboard(Id, KeyboardInputMode.Physical)
|
|
||||||
: GamepadDriver.GetGamepad(Id);
|
|
||||||
|
|
||||||
UpdateUserConfiguration(config);
|
UpdateUserConfiguration(config);
|
||||||
|
|
||||||
@@ -566,7 +563,7 @@ namespace Ryujinx.Input.HLE
|
|||||||
float low = Math.Min(1f, (float)((rightVibrationValue.AmplitudeLow * 0.85 + rightVibrationValue.AmplitudeHigh * 0.15) * controllerConfig.Rumble.StrongRumble));
|
float low = Math.Min(1f, (float)((rightVibrationValue.AmplitudeLow * 0.85 + rightVibrationValue.AmplitudeHigh * 0.15) * controllerConfig.Rumble.StrongRumble));
|
||||||
float high = Math.Min(1f, (float)((leftVibrationValue.AmplitudeLow * 0.15 + leftVibrationValue.AmplitudeHigh * 0.85) * controllerConfig.Rumble.WeakRumble));
|
float high = Math.Min(1f, (float)((leftVibrationValue.AmplitudeLow * 0.15 + leftVibrationValue.AmplitudeHigh * 0.85) * controllerConfig.Rumble.WeakRumble));
|
||||||
|
|
||||||
_gamepad.Rumble(low, high, uint.MaxValue);
|
_gamepad?.Rumble(low, high, uint.MaxValue);
|
||||||
|
|
||||||
Logger.Debug?.Print(LogClass.Hid, $"Effect for {controllerConfig.PlayerIndex} " +
|
Logger.Debug?.Print(LogClass.Hid, $"Effect for {controllerConfig.PlayerIndex} " +
|
||||||
$"L.low.amp={leftVibrationValue.AmplitudeLow}, " +
|
$"L.low.amp={leftVibrationValue.AmplitudeLow}, " +
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ namespace Ryujinx.Input.HLE
|
|||||||
private bool _isDisposed;
|
private bool _isDisposed;
|
||||||
|
|
||||||
private List<InputConfig> _inputConfig;
|
private List<InputConfig> _inputConfig;
|
||||||
private List<InputConfig> _requestedInputConfig;
|
|
||||||
private bool _enableKeyboard;
|
private bool _enableKeyboard;
|
||||||
private bool _enableMouse;
|
private bool _enableMouse;
|
||||||
private Switch _device;
|
private Switch _device;
|
||||||
@@ -53,7 +52,6 @@ namespace Ryujinx.Input.HLE
|
|||||||
_gamepadDriver = gamepadDriver;
|
_gamepadDriver = gamepadDriver;
|
||||||
_mouseDriver = mouseDriver;
|
_mouseDriver = mouseDriver;
|
||||||
_inputConfig = [];
|
_inputConfig = [];
|
||||||
_requestedInputConfig = [];
|
|
||||||
|
|
||||||
_gamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
|
_gamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
|
||||||
_gamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
|
_gamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
|
||||||
@@ -91,14 +89,14 @@ namespace Ryujinx.Input.HLE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ReloadConfiguration(_requestedInputConfig, _enableKeyboard, _enableMouse);
|
ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleOnGamepadConnected(string id)
|
private void HandleOnGamepadConnected(string id)
|
||||||
{
|
{
|
||||||
// Force input reload
|
// Force input reload
|
||||||
ReloadConfiguration(_requestedInputConfig, _enableKeyboard, _enableMouse);
|
ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@@ -129,13 +127,11 @@ namespace Ryujinx.Input.HLE
|
|||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
_requestedInputConfig = inputConfig?.ToList() ?? [];
|
|
||||||
|
|
||||||
NpadController[] oldControllers = _controllers.ToArray();
|
NpadController[] oldControllers = _controllers.ToArray();
|
||||||
|
|
||||||
List<InputConfig> validInputs = [];
|
List<InputConfig> validInputs = [];
|
||||||
|
|
||||||
foreach (InputConfig inputConfigEntry in _requestedInputConfig)
|
foreach (InputConfig inputConfigEntry in inputConfig)
|
||||||
{
|
{
|
||||||
NpadController controller;
|
NpadController controller;
|
||||||
int index = (int)inputConfigEntry.PlayerIndex;
|
int index = (int)inputConfigEntry.PlayerIndex;
|
||||||
@@ -151,17 +147,7 @@ namespace Ryujinx.Input.HLE
|
|||||||
controller = new(_cemuHookClient);
|
controller = new(_cemuHookClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
InputConfig activeConfig = inputConfigEntry;
|
bool isValid = DriverConfigurationUpdate(ref controller, inputConfigEntry);
|
||||||
bool isValid = DriverConfigurationUpdate(ref controller, activeConfig);
|
|
||||||
|
|
||||||
if (!isValid &&
|
|
||||||
enableKeyboard &&
|
|
||||||
inputConfigEntry is StandardControllerInputConfig &&
|
|
||||||
TryGetKeyboardFallback(inputConfigEntry, out StandardKeyboardInputConfig fallbackConfig))
|
|
||||||
{
|
|
||||||
activeConfig = fallbackConfig;
|
|
||||||
isValid = DriverConfigurationUpdate(ref controller, activeConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isValid)
|
if (!isValid)
|
||||||
{
|
{
|
||||||
@@ -171,7 +157,7 @@ namespace Ryujinx.Input.HLE
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
_controllers[index] = controller;
|
_controllers[index] = controller;
|
||||||
validInputs.Add(activeConfig);
|
validInputs.Add(inputConfigEntry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +169,7 @@ namespace Ryujinx.Input.HLE
|
|||||||
oldControllers[i] = null;
|
oldControllers[i] = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_inputConfig = validInputs;
|
_inputConfig = inputConfig;
|
||||||
_enableKeyboard = enableKeyboard;
|
_enableKeyboard = enableKeyboard;
|
||||||
_enableMouse = enableMouse;
|
_enableMouse = enableMouse;
|
||||||
|
|
||||||
@@ -191,79 +177,6 @@ namespace Ryujinx.Input.HLE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryGetKeyboardFallback(InputConfig inputConfig, out StandardKeyboardInputConfig fallbackConfig)
|
|
||||||
{
|
|
||||||
fallbackConfig = null;
|
|
||||||
|
|
||||||
ReadOnlySpan<string> keyboardIds = _keyboardDriver.GamepadsIds;
|
|
||||||
|
|
||||||
if (keyboardIds.IsEmpty)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
string keyboardId = keyboardIds[0];
|
|
||||||
|
|
||||||
using IGamepad keyboard = _keyboardDriver.GetGamepad(keyboardId);
|
|
||||||
|
|
||||||
if (keyboard == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fallbackConfig = new StandardKeyboardInputConfig
|
|
||||||
{
|
|
||||||
Version = InputConfig.CurrentVersion,
|
|
||||||
Backend = InputBackendType.WindowKeyboard,
|
|
||||||
Id = keyboardId,
|
|
||||||
Name = keyboard.Name,
|
|
||||||
PlayerIndex = inputConfig.PlayerIndex,
|
|
||||||
ControllerType = inputConfig.ControllerType,
|
|
||||||
LeftJoycon = new LeftJoyconCommonConfig<PhysicalKey>
|
|
||||||
{
|
|
||||||
DpadUp = PhysicalKey.Up,
|
|
||||||
DpadDown = PhysicalKey.Down,
|
|
||||||
DpadLeft = PhysicalKey.Left,
|
|
||||||
DpadRight = PhysicalKey.Right,
|
|
||||||
ButtonMinus = PhysicalKey.Minus,
|
|
||||||
ButtonL = PhysicalKey.E,
|
|
||||||
ButtonZl = PhysicalKey.Q,
|
|
||||||
ButtonSl = PhysicalKey.Unbound,
|
|
||||||
ButtonSr = PhysicalKey.Unbound,
|
|
||||||
},
|
|
||||||
LeftJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
|
||||||
{
|
|
||||||
StickUp = PhysicalKey.W,
|
|
||||||
StickDown = PhysicalKey.S,
|
|
||||||
StickLeft = PhysicalKey.A,
|
|
||||||
StickRight = PhysicalKey.D,
|
|
||||||
StickButton = PhysicalKey.F,
|
|
||||||
},
|
|
||||||
RightJoycon = new RightJoyconCommonConfig<PhysicalKey>
|
|
||||||
{
|
|
||||||
ButtonA = PhysicalKey.Z,
|
|
||||||
ButtonB = PhysicalKey.X,
|
|
||||||
ButtonX = PhysicalKey.C,
|
|
||||||
ButtonY = PhysicalKey.V,
|
|
||||||
ButtonPlus = PhysicalKey.Plus,
|
|
||||||
ButtonR = PhysicalKey.U,
|
|
||||||
ButtonZr = PhysicalKey.O,
|
|
||||||
ButtonSl = PhysicalKey.Unbound,
|
|
||||||
ButtonSr = PhysicalKey.Unbound,
|
|
||||||
},
|
|
||||||
RightJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
|
||||||
{
|
|
||||||
StickUp = PhysicalKey.I,
|
|
||||||
StickDown = PhysicalKey.K,
|
|
||||||
StickLeft = PhysicalKey.J,
|
|
||||||
StickRight = PhysicalKey.L,
|
|
||||||
StickButton = PhysicalKey.H,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UnblockInputUpdates()
|
public void UnblockInputUpdates()
|
||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
@@ -421,7 +334,7 @@ namespace Ryujinx.Input.HLE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public InputConfig GetPlayerInputConfigByIndex(int index)
|
internal InputConfig GetPlayerInputConfigByIndex(int index)
|
||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
|
||||||
|
|
||||||
namespace Ryujinx.Input
|
namespace Ryujinx.Input
|
||||||
{
|
{
|
||||||
@@ -34,26 +33,15 @@ namespace Ryujinx.Input
|
|||||||
{
|
{
|
||||||
if (_keyState is null)
|
if (_keyState is null)
|
||||||
{
|
{
|
||||||
_keyState = new bool[(int)ConfigPhysicalKey.Count];
|
_keyState = new bool[(int)Key.Count];
|
||||||
}
|
}
|
||||||
|
|
||||||
for (ConfigPhysicalKey key = 0; key < ConfigPhysicalKey.Count; key++)
|
for (Key key = 0; key < Key.Count; key++)
|
||||||
{
|
{
|
||||||
_keyState[(int)key] = keyboard.IsPressed((Key)(int)key);
|
_keyState[(int)key] = keyboard.IsPressed(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new KeyboardStateSnapshot(_keyState);
|
return new KeyboardStateSnapshot(_keyState);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Try to consume a recently pressed key.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key">The pressed key, if available.</param>
|
|
||||||
/// <returns>True if a key press was consumed.</returns>
|
|
||||||
bool TryConsumePressedKey(out Key key)
|
|
||||||
{
|
|
||||||
key = Key.Unknown;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace Ryujinx.Input
|
|
||||||
{
|
|
||||||
public interface IKeyboardModeDriver : IGamepadDriver
|
|
||||||
{
|
|
||||||
IKeyboard GetKeyboard(string id, KeyboardInputMode mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
|
||||||
|
|
||||||
namespace Ryujinx.Input
|
|
||||||
{
|
|
||||||
public static class KeyboardInputMappingHelper
|
|
||||||
{
|
|
||||||
public readonly record struct KeyboardButtonMapping(GamepadButtonInputId To, ConfigPhysicalKey From)
|
|
||||||
{
|
|
||||||
public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not ConfigPhysicalKey.Unknown and not ConfigPhysicalKey.Unbound;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static KeyboardButtonMapping[] BuildButtonMappings(StandardKeyboardInputConfig configuration) =>
|
|
||||||
[
|
|
||||||
// Left JoyCon
|
|
||||||
new(GamepadButtonInputId.LeftStick, configuration.LeftJoyconStick.StickButton),
|
|
||||||
new(GamepadButtonInputId.DpadUp, configuration.LeftJoycon.DpadUp),
|
|
||||||
new(GamepadButtonInputId.DpadDown, configuration.LeftJoycon.DpadDown),
|
|
||||||
new(GamepadButtonInputId.DpadLeft, configuration.LeftJoycon.DpadLeft),
|
|
||||||
new(GamepadButtonInputId.DpadRight, configuration.LeftJoycon.DpadRight),
|
|
||||||
new(GamepadButtonInputId.Minus, configuration.LeftJoycon.ButtonMinus),
|
|
||||||
new(GamepadButtonInputId.LeftShoulder, configuration.LeftJoycon.ButtonL),
|
|
||||||
new(GamepadButtonInputId.LeftTrigger, configuration.LeftJoycon.ButtonZl),
|
|
||||||
new(GamepadButtonInputId.SingleRightTrigger0, configuration.LeftJoycon.ButtonSr),
|
|
||||||
new(GamepadButtonInputId.SingleLeftTrigger0, configuration.LeftJoycon.ButtonSl),
|
|
||||||
|
|
||||||
// Right JoyCon
|
|
||||||
new(GamepadButtonInputId.RightStick, configuration.RightJoyconStick.StickButton),
|
|
||||||
new(GamepadButtonInputId.A, configuration.RightJoycon.ButtonA),
|
|
||||||
new(GamepadButtonInputId.B, configuration.RightJoycon.ButtonB),
|
|
||||||
new(GamepadButtonInputId.X, configuration.RightJoycon.ButtonX),
|
|
||||||
new(GamepadButtonInputId.Y, configuration.RightJoycon.ButtonY),
|
|
||||||
new(GamepadButtonInputId.Plus, configuration.RightJoycon.ButtonPlus),
|
|
||||||
new(GamepadButtonInputId.RightShoulder, configuration.RightJoycon.ButtonR),
|
|
||||||
new(GamepadButtonInputId.RightTrigger, configuration.RightJoycon.ButtonZr),
|
|
||||||
new(GamepadButtonInputId.SingleRightTrigger1, configuration.RightJoycon.ButtonSr),
|
|
||||||
new(GamepadButtonInputId.SingleLeftTrigger1, configuration.RightJoycon.ButtonSl),
|
|
||||||
];
|
|
||||||
|
|
||||||
public static (short X, short Y) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick<ConfigPhysicalKey> stickConfig)
|
|
||||||
{
|
|
||||||
short stickX = 0;
|
|
||||||
short stickY = 0;
|
|
||||||
|
|
||||||
if (snapshot.IsPressed(stickConfig.StickUp))
|
|
||||||
{
|
|
||||||
stickY += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (snapshot.IsPressed(stickConfig.StickDown))
|
|
||||||
{
|
|
||||||
stickY -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (snapshot.IsPressed(stickConfig.StickRight))
|
|
||||||
{
|
|
||||||
stickX += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (snapshot.IsPressed(stickConfig.StickLeft))
|
|
||||||
{
|
|
||||||
stickX -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stickX == 0 && stickY == 0)
|
|
||||||
{
|
|
||||||
return (0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector2 stick = Vector2.Normalize(new Vector2(stickX, stickY));
|
|
||||||
|
|
||||||
return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace Ryujinx.Input
|
|
||||||
{
|
|
||||||
public enum KeyboardInputMode
|
|
||||||
{
|
|
||||||
Semantic,
|
|
||||||
Physical,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
|
||||||
|
|
||||||
namespace Ryujinx.Input
|
namespace Ryujinx.Input
|
||||||
{
|
{
|
||||||
@@ -26,8 +25,5 @@ namespace Ryujinx.Input
|
|||||||
/// <returns>True if the given key is pressed</returns>
|
/// <returns>True if the given key is pressed</returns>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public bool IsPressed(Key key) => KeysState[(int)key];
|
public bool IsPressed(Key key) => KeysState[(int)key];
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public bool IsPressed(ConfigPhysicalKey key) => KeysState[(int)key];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.SDL3-CS" />
|
<PackageReference Include="Ryujinx.SDL3-CS" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect
|
|||||||
public void EnsureTypeSize()
|
public void EnsureTypeSize()
|
||||||
{
|
{
|
||||||
Assert.AreEqual(0x18, Unsafe.SizeOf<BiquadFilterEffectParameter1>());
|
Assert.AreEqual(0x18, Unsafe.SizeOf<BiquadFilterEffectParameter1>());
|
||||||
Assert.AreEqual(0x24, Unsafe.SizeOf<BiquadFilterEffectParameter2>());
|
Assert.AreEqual(0x28, Unsafe.SizeOf<BiquadFilterEffectParameter2>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
187
src/Ryujinx.Tests/HLE/CaptureManagerTests.cs
Normal file
187
src/Ryujinx.Tests/HLE/CaptureManagerTests.cs
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
using NUnit.Framework;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Caps;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Caps.Types;
|
||||||
|
using SkiaSharp;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Tests.HLE
|
||||||
|
{
|
||||||
|
public class CaptureManagerTests
|
||||||
|
{
|
||||||
|
private const int ScreenshotWidth = 1280;
|
||||||
|
private const int ScreenshotHeight = 720;
|
||||||
|
private const int BytesPerPixel = 4;
|
||||||
|
|
||||||
|
private const int ScreenshotDataSize = ScreenshotWidth * ScreenshotHeight * BytesPerPixel; // 0x384000
|
||||||
|
private const int PaddedScreenshotDataSize = ScreenshotWidth * 768 * BytesPerPixel; // 0x3C0000
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SaveScreenShotRejectsBufferSmallerThan720p()
|
||||||
|
{
|
||||||
|
using TempSdCard tempSdCard = new();
|
||||||
|
|
||||||
|
CaptureManager captureManager = CreateCaptureManager(tempSdCard.Path);
|
||||||
|
byte[] screenshotData = new byte[ScreenshotDataSize - 1];
|
||||||
|
|
||||||
|
ResultCode result = captureManager.SaveScreenShot(
|
||||||
|
screenshotData,
|
||||||
|
appletResourceUserId: 0,
|
||||||
|
titleId: 0x0100000000001000,
|
||||||
|
out _);
|
||||||
|
|
||||||
|
Assert.That(result, Is.EqualTo(ResultCode.NullInputBuffer));
|
||||||
|
Assert.That(Directory.Exists(Path.Combine(tempSdCard.Path, "Nintendo", "Album")), Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SaveScreenShotAcceptsExact720pBuffer()
|
||||||
|
{
|
||||||
|
using TempSdCard tempSdCard = new();
|
||||||
|
|
||||||
|
CaptureManager captureManager = CreateCaptureManager(tempSdCard.Path);
|
||||||
|
byte[] screenshotData = CreateTestPattern(ScreenshotDataSize);
|
||||||
|
|
||||||
|
ResultCode result = captureManager.SaveScreenShot(
|
||||||
|
screenshotData,
|
||||||
|
appletResourceUserId: 0,
|
||||||
|
titleId: 0x0100000000001000,
|
||||||
|
out ApplicationAlbumEntry applicationAlbumEntry);
|
||||||
|
|
||||||
|
string filePath = GetSingleAlbumFile(tempSdCard.Path);
|
||||||
|
|
||||||
|
using SKBitmap bitmap = SKBitmap.Decode(filePath);
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(result, Is.EqualTo(ResultCode.Success));
|
||||||
|
Assert.That(bitmap.Width, Is.EqualTo(ScreenshotWidth));
|
||||||
|
Assert.That(bitmap.Height, Is.EqualTo(ScreenshotHeight));
|
||||||
|
Assert.That(applicationAlbumEntry.TitleId, Is.EqualTo(0x0100000000001000));
|
||||||
|
Assert.That(applicationAlbumEntry.AlbumStorage, Is.EqualTo(AlbumStorage.Sd));
|
||||||
|
Assert.That(applicationAlbumEntry.ContentType, Is.EqualTo(ContentType.Screenshot));
|
||||||
|
Assert.That(applicationAlbumEntry.Unknown0x1f, Is.EqualTo(1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SaveScreenShotAcceptsBufferLargerThan720p()
|
||||||
|
{
|
||||||
|
using TempSdCard tempSdCard = new();
|
||||||
|
|
||||||
|
CaptureManager captureManager = CreateCaptureManager(tempSdCard.Path);
|
||||||
|
byte[] screenshotData = CreateTestPattern(PaddedScreenshotDataSize);
|
||||||
|
|
||||||
|
ResultCode result = captureManager.SaveScreenShot(
|
||||||
|
screenshotData,
|
||||||
|
appletResourceUserId: 0,
|
||||||
|
titleId: 0x0100000000001000,
|
||||||
|
out ApplicationAlbumEntry applicationAlbumEntry);
|
||||||
|
|
||||||
|
string filePath = GetSingleAlbumFile(tempSdCard.Path);
|
||||||
|
|
||||||
|
using SKBitmap bitmap = SKBitmap.Decode(filePath);
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(result, Is.EqualTo(ResultCode.Success));
|
||||||
|
Assert.That(bitmap.Width, Is.EqualTo(ScreenshotWidth));
|
||||||
|
Assert.That(bitmap.Height, Is.EqualTo(ScreenshotHeight));
|
||||||
|
Assert.That(applicationAlbumEntry.TitleId, Is.EqualTo(0x0100000000001000));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SaveScreenShotCreatesUniqueFileNamesForRepeatedSaves()
|
||||||
|
{
|
||||||
|
using TempSdCard tempSdCard = new();
|
||||||
|
|
||||||
|
CaptureManager captureManager = CreateCaptureManager(tempSdCard.Path);
|
||||||
|
byte[] screenshotData = CreateTestPattern(ScreenshotDataSize);
|
||||||
|
|
||||||
|
ResultCode firstResult = captureManager.SaveScreenShot(
|
||||||
|
screenshotData,
|
||||||
|
appletResourceUserId: 0,
|
||||||
|
titleId: 0x0100000000001000,
|
||||||
|
out _);
|
||||||
|
|
||||||
|
ResultCode secondResult = captureManager.SaveScreenShot(
|
||||||
|
screenshotData,
|
||||||
|
appletResourceUserId: 0,
|
||||||
|
titleId: 0x0100000000001000,
|
||||||
|
out _);
|
||||||
|
|
||||||
|
string[] files = Directory.GetFiles(
|
||||||
|
Path.Combine(tempSdCard.Path, "Nintendo", "Album"),
|
||||||
|
"*.jpg",
|
||||||
|
SearchOption.AllDirectories);
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(firstResult, Is.EqualTo(ResultCode.Success));
|
||||||
|
Assert.That(secondResult, Is.EqualTo(ResultCode.Success));
|
||||||
|
Assert.That(files, Has.Length.EqualTo(2));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CaptureManager CreateCaptureManager(string sdCardPath)
|
||||||
|
{
|
||||||
|
CaptureManager captureManager = (CaptureManager)RuntimeHelpers.GetUninitializedObject(typeof(CaptureManager));
|
||||||
|
|
||||||
|
typeof(CaptureManager)
|
||||||
|
.GetField("_sdCardPath", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||||
|
.SetValue(captureManager, sdCardPath);
|
||||||
|
|
||||||
|
return captureManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetSingleAlbumFile(string sdCardPath)
|
||||||
|
{
|
||||||
|
string albumPath = Path.Combine(sdCardPath, "Nintendo", "Album");
|
||||||
|
|
||||||
|
string[] files = Directory.GetFiles(albumPath, "*.jpg", SearchOption.AllDirectories);
|
||||||
|
|
||||||
|
Assert.That(files, Has.Length.EqualTo(1));
|
||||||
|
|
||||||
|
return files.Single();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] CreateTestPattern(int size)
|
||||||
|
{
|
||||||
|
byte[] data = new byte[size];
|
||||||
|
|
||||||
|
int pixelCount = size / BytesPerPixel;
|
||||||
|
|
||||||
|
for (int i = 0; i < pixelCount; i++)
|
||||||
|
{
|
||||||
|
int x = i % ScreenshotWidth;
|
||||||
|
int y = i / ScreenshotWidth;
|
||||||
|
|
||||||
|
data[(i * 4) + 0] = (byte)(x & 0xff);
|
||||||
|
data[(i * 4) + 1] = (byte)(y & 0xff);
|
||||||
|
data[(i * 4) + 2] = 0x80;
|
||||||
|
data[(i * 4) + 3] = 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TempSdCard : IDisposable
|
||||||
|
{
|
||||||
|
public string Path { get; } = System.IO.Path.Combine(
|
||||||
|
TestContext.CurrentContext.WorkDirectory,
|
||||||
|
"sdcard-" + Guid.NewGuid());
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (Directory.Exists(Path))
|
||||||
|
{
|
||||||
|
Directory.Delete(Path, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
122
src/Ryujinx.Tests/HLE/MiiDatabaseTests.cs
Normal file
122
src/Ryujinx.Tests/HLE/MiiDatabaseTests.cs
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
using Ryujinx.Cpu;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Mii;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Mii.StaticService;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Mii.Types;
|
||||||
|
|
||||||
|
namespace Ryujinx.Tests.HLE
|
||||||
|
{
|
||||||
|
public class MiiDatabaseTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void UpdateLatestReturnsStoredCharInfo()
|
||||||
|
{
|
||||||
|
DatabaseImpl database = new();
|
||||||
|
StoreData storedData = StoreData.BuildDefault(new UtilityImpl(new TickSource(19200000)), 0);
|
||||||
|
MiiDatabaseManager databaseManager = GetDatabaseManager(database);
|
||||||
|
|
||||||
|
NintendoFigurineDatabase figurineDatabase = new();
|
||||||
|
figurineDatabase.Format();
|
||||||
|
figurineDatabase.Add(storedData);
|
||||||
|
SetFigurineDatabase(databaseManager, figurineDatabase);
|
||||||
|
|
||||||
|
TestDatabaseService service = new(database);
|
||||||
|
|
||||||
|
CharInfo oldCharInfo = new();
|
||||||
|
oldCharInfo.SetFromStoreData(storedData);
|
||||||
|
oldCharInfo.Height--;
|
||||||
|
|
||||||
|
ResultCode result = service.UpdateLatestForTest(oldCharInfo, SourceFlag.Database, out CharInfo newCharInfo);
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(result, Is.EqualTo(ResultCode.Success));
|
||||||
|
Assert.That(newCharInfo.CreateId, Is.EqualTo(oldCharInfo.CreateId));
|
||||||
|
Assert.That(newCharInfo.Height, Is.EqualTo(storedData.CoreData.Height));
|
||||||
|
Assert.That(newCharInfo.IsValid(), Is.True);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void AppendAddsRegularCharInfoToDatabase()
|
||||||
|
{
|
||||||
|
DatabaseImpl database = new();
|
||||||
|
UtilityImpl utilityImpl = new(new TickSource(19200000));
|
||||||
|
SetUtilityImpl(database, utilityImpl);
|
||||||
|
MiiDatabaseManager databaseManager = GetDatabaseManager(database);
|
||||||
|
SetFigurineDatabase(databaseManager, CreateFormattedDatabase());
|
||||||
|
|
||||||
|
StoreData defaultStoreData = StoreData.BuildDefault(utilityImpl, 0);
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(defaultStoreData.CoreData.IsValid(), Is.True);
|
||||||
|
Assert.That(defaultStoreData.IsValidDataCrc(), Is.True);
|
||||||
|
Assert.That(defaultStoreData.IsValidDeviceCrc(), Is.True);
|
||||||
|
Assert.That(defaultStoreData.IsValid(), Is.True);
|
||||||
|
});
|
||||||
|
|
||||||
|
CharInfo charInfo = new();
|
||||||
|
charInfo.SetFromStoreData(defaultStoreData);
|
||||||
|
|
||||||
|
DatabaseSessionMetadata metadata = database.CreateSessionMetadata(new SpecialMiiKeyCode());
|
||||||
|
|
||||||
|
ResultCode result = databaseManager.Append(metadata, utilityImpl, charInfo);
|
||||||
|
|
||||||
|
int count = databaseManager.GetCount(metadata);
|
||||||
|
databaseManager.Get(metadata, 0, out StoreData storedData);
|
||||||
|
|
||||||
|
CoreData expectedCoreData = new();
|
||||||
|
expectedCoreData.SetFromCharInfo(charInfo);
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(result, Is.EqualTo(ResultCode.Success));
|
||||||
|
Assert.That(count, Is.EqualTo(1));
|
||||||
|
Assert.That(storedData.IsValid(), Is.True);
|
||||||
|
Assert.That(storedData.CreateId, Is.Not.EqualTo(charInfo.CreateId));
|
||||||
|
Assert.That(storedData.CoreData, Is.EqualTo(expectedCoreData));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TestDatabaseService(DatabaseImpl database) : DatabaseServiceImpl(database, true, new SpecialMiiKeyCode())
|
||||||
|
{
|
||||||
|
public ResultCode UpdateLatestForTest(CharInfo oldCharInfo, SourceFlag flag, out CharInfo newCharInfo)
|
||||||
|
{
|
||||||
|
return UpdateLatest(oldCharInfo, flag, out newCharInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MiiDatabaseManager GetDatabaseManager(DatabaseImpl database)
|
||||||
|
{
|
||||||
|
return (MiiDatabaseManager)typeof(DatabaseImpl)
|
||||||
|
.GetField("_miiDatabase", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||||
|
.GetValue(database);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetFigurineDatabase(MiiDatabaseManager databaseManager, NintendoFigurineDatabase figurineDatabase)
|
||||||
|
{
|
||||||
|
typeof(MiiDatabaseManager)
|
||||||
|
.GetField("_database", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||||
|
.SetValue(databaseManager, figurineDatabase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static NintendoFigurineDatabase CreateFormattedDatabase()
|
||||||
|
{
|
||||||
|
NintendoFigurineDatabase figurineDatabase = new();
|
||||||
|
figurineDatabase.Format();
|
||||||
|
|
||||||
|
return figurineDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetUtilityImpl(DatabaseImpl database, UtilityImpl utilityImpl)
|
||||||
|
{
|
||||||
|
typeof(DatabaseImpl)
|
||||||
|
.GetField("_utilityImpl", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||||
|
.SetValue(database, utilityImpl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
src/Ryujinx/Assets/UIImages/Logo_Forgejo.png
Normal file
BIN
src/Ryujinx/Assets/UIImages/Logo_Forgejo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.1 KiB |
@@ -24,7 +24,7 @@ using System.Text.Json;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
|
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
|
||||||
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
|
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
|
||||||
using PhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
using Key = Ryujinx.Common.Configuration.Hid.Key;
|
||||||
|
|
||||||
namespace Ryujinx.Headless
|
namespace Ryujinx.Headless
|
||||||
{
|
{
|
||||||
@@ -105,48 +105,48 @@ namespace Ryujinx.Headless
|
|||||||
Backend = InputBackendType.WindowKeyboard,
|
Backend = InputBackendType.WindowKeyboard,
|
||||||
Id = null,
|
Id = null,
|
||||||
ControllerType = ControllerType.JoyconPair,
|
ControllerType = ControllerType.JoyconPair,
|
||||||
LeftJoycon = new LeftJoyconCommonConfig<PhysicalKey>
|
LeftJoycon = new LeftJoyconCommonConfig<Key>
|
||||||
{
|
{
|
||||||
DpadUp = PhysicalKey.Up,
|
DpadUp = Key.Up,
|
||||||
DpadDown = PhysicalKey.Down,
|
DpadDown = Key.Down,
|
||||||
DpadLeft = PhysicalKey.Left,
|
DpadLeft = Key.Left,
|
||||||
DpadRight = PhysicalKey.Right,
|
DpadRight = Key.Right,
|
||||||
ButtonMinus = PhysicalKey.Minus,
|
ButtonMinus = Key.Minus,
|
||||||
ButtonL = PhysicalKey.E,
|
ButtonL = Key.E,
|
||||||
ButtonZl = PhysicalKey.Q,
|
ButtonZl = Key.Q,
|
||||||
ButtonSl = PhysicalKey.Unbound,
|
ButtonSl = Key.Unbound,
|
||||||
ButtonSr = PhysicalKey.Unbound,
|
ButtonSr = Key.Unbound,
|
||||||
},
|
},
|
||||||
|
|
||||||
LeftJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||||
{
|
{
|
||||||
StickUp = PhysicalKey.W,
|
StickUp = Key.W,
|
||||||
StickDown = PhysicalKey.S,
|
StickDown = Key.S,
|
||||||
StickLeft = PhysicalKey.A,
|
StickLeft = Key.A,
|
||||||
StickRight = PhysicalKey.D,
|
StickRight = Key.D,
|
||||||
StickButton = PhysicalKey.F,
|
StickButton = Key.F,
|
||||||
},
|
},
|
||||||
|
|
||||||
RightJoycon = new RightJoyconCommonConfig<PhysicalKey>
|
RightJoycon = new RightJoyconCommonConfig<Key>
|
||||||
{
|
{
|
||||||
ButtonA = PhysicalKey.Z,
|
ButtonA = Key.Z,
|
||||||
ButtonB = PhysicalKey.X,
|
ButtonB = Key.X,
|
||||||
ButtonX = PhysicalKey.C,
|
ButtonX = Key.C,
|
||||||
ButtonY = PhysicalKey.V,
|
ButtonY = Key.V,
|
||||||
ButtonPlus = PhysicalKey.Plus,
|
ButtonPlus = Key.Plus,
|
||||||
ButtonR = PhysicalKey.U,
|
ButtonR = Key.U,
|
||||||
ButtonZr = PhysicalKey.O,
|
ButtonZr = Key.O,
|
||||||
ButtonSl = PhysicalKey.Unbound,
|
ButtonSl = Key.Unbound,
|
||||||
ButtonSr = PhysicalKey.Unbound,
|
ButtonSr = Key.Unbound,
|
||||||
},
|
},
|
||||||
|
|
||||||
RightJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||||
{
|
{
|
||||||
StickUp = PhysicalKey.I,
|
StickUp = Key.I,
|
||||||
StickDown = PhysicalKey.K,
|
StickDown = Key.K,
|
||||||
StickLeft = PhysicalKey.J,
|
StickLeft = Key.J,
|
||||||
StickRight = PhysicalKey.L,
|
StickRight = Key.L,
|
||||||
StickButton = PhysicalKey.H,
|
StickButton = Key.H,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -254,6 +254,7 @@ namespace Ryujinx.Headless
|
|||||||
Logger.SetEnable(LogLevel.Trace, option.LoggingEnableTrace);
|
Logger.SetEnable(LogLevel.Trace, option.LoggingEnableTrace);
|
||||||
Logger.SetEnable(LogLevel.Guest, !option.LoggingDisableGuest);
|
Logger.SetEnable(LogLevel.Guest, !option.LoggingDisableGuest);
|
||||||
Logger.SetEnable(LogLevel.AccessLog, option.LoggingEnableFsAccessLog);
|
Logger.SetEnable(LogLevel.AccessLog, option.LoggingEnableFsAccessLog);
|
||||||
|
Logger.SetEnable(LogLevel.NetLog, option.LoggingEnableFsAccessLog);
|
||||||
|
|
||||||
if (!option.DisableFileLog)
|
if (!option.DisableFileLog)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -108,6 +108,9 @@ namespace Ryujinx.Headless
|
|||||||
|
|
||||||
if (NeedsOverride(nameof(LoggingEnableFsAccessLog)))
|
if (NeedsOverride(nameof(LoggingEnableFsAccessLog)))
|
||||||
LoggingEnableFsAccessLog = configurationState.Logger.EnableFsAccessLog;
|
LoggingEnableFsAccessLog = configurationState.Logger.EnableFsAccessLog;
|
||||||
|
|
||||||
|
if (NeedsOverride(nameof(LoggingEnableNetLog)))
|
||||||
|
LoggingEnableNetLog = configurationState.Logger.EnableNetLog;
|
||||||
|
|
||||||
if (NeedsOverride(nameof(LoggingGraphicsDebugLevel)))
|
if (NeedsOverride(nameof(LoggingGraphicsDebugLevel)))
|
||||||
LoggingGraphicsDebugLevel = configurationState.Logger.GraphicsDebugLevel;
|
LoggingGraphicsDebugLevel = configurationState.Logger.GraphicsDebugLevel;
|
||||||
@@ -370,6 +373,9 @@ namespace Ryujinx.Headless
|
|||||||
|
|
||||||
[Option("enable-fs-access-logs", Required = false, Default = false, HelpText = "Enables printing FS access log messages.")]
|
[Option("enable-fs-access-logs", Required = false, Default = false, HelpText = "Enables printing FS access log messages.")]
|
||||||
public bool LoggingEnableFsAccessLog { get; set; }
|
public bool LoggingEnableFsAccessLog { get; set; }
|
||||||
|
|
||||||
|
[Option("enable-net-logs", Required = false, Default = false, HelpText = "Enables printing net log messages.")]
|
||||||
|
public bool LoggingEnableNetLog { get; set; }
|
||||||
|
|
||||||
[Option("graphics-debug-level", Required = false, Default = GraphicsDebugLevel.None, HelpText = "Change Graphics API debug log level.")]
|
[Option("graphics-debug-level", Required = false, Default = GraphicsDebugLevel.None, HelpText = "Change Graphics API debug log level.")]
|
||||||
public GraphicsDebugLevel LoggingGraphicsDebugLevel { get; set; }
|
public GraphicsDebugLevel LoggingGraphicsDebugLevel { get; set; }
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using ConfigKey = Ryujinx.Common.Configuration.Hid.Key;
|
||||||
using Key = Ryujinx.Input.Key;
|
using Key = Ryujinx.Input.Key;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Input
|
namespace Ryujinx.Ava.Input
|
||||||
{
|
{
|
||||||
internal class AvaloniaKeyboard : IKeyboard
|
internal class AvaloniaKeyboard : IKeyboard
|
||||||
{
|
{
|
||||||
private readonly List<KeyboardInputMappingHelper.KeyboardButtonMapping> _buttonsUserMapping;
|
private readonly List<ButtonMappingEntry> _buttonsUserMapping;
|
||||||
private readonly AvaloniaKeyboardDriver _driver;
|
private readonly AvaloniaKeyboardDriver _driver;
|
||||||
private readonly KeyboardInputMode _mode;
|
|
||||||
private StandardKeyboardInputConfig _configuration;
|
private StandardKeyboardInputConfig _configuration;
|
||||||
|
|
||||||
private readonly Lock _userMappingLock = new();
|
private readonly Lock _userMappingLock = new();
|
||||||
@@ -24,12 +24,18 @@ namespace Ryujinx.Ava.Input
|
|||||||
|
|
||||||
public bool IsConnected => true;
|
public bool IsConnected => true;
|
||||||
public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None;
|
public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None;
|
||||||
public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name, KeyboardInputMode mode)
|
|
||||||
|
private class ButtonMappingEntry(GamepadButtonInputId to, Key from)
|
||||||
|
{
|
||||||
|
public readonly GamepadButtonInputId To = to;
|
||||||
|
public readonly Key From = from;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name)
|
||||||
{
|
{
|
||||||
_buttonsUserMapping = [];
|
_buttonsUserMapping = [];
|
||||||
|
|
||||||
_driver = driver;
|
_driver = driver;
|
||||||
_mode = mode;
|
|
||||||
Id = id;
|
Id = id;
|
||||||
Name = name;
|
Name = name;
|
||||||
}
|
}
|
||||||
@@ -51,18 +57,22 @@ namespace Ryujinx.Ava.Input
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (KeyboardInputMappingHelper.KeyboardButtonMapping entry in _buttonsUserMapping)
|
foreach (ButtonMappingEntry entry in _buttonsUserMapping)
|
||||||
{
|
{
|
||||||
if (!entry.IsValid || result.IsPressed(entry.To))
|
if (entry.From == Key.Unknown || entry.From == Key.Unbound || entry.To == GamepadButtonInputId.Unbound)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
result.SetPressed(entry.To, rawState.IsPressed(entry.From));
|
// NOTE: Do not touch state of the button already pressed.
|
||||||
|
if (!result.IsPressed(entry.To))
|
||||||
|
{
|
||||||
|
result.SetPressed(entry.To, rawState.IsPressed(entry.From));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(short leftStickX, short leftStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.LeftJoyconStick);
|
(short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick);
|
||||||
(short rightStickX, short rightStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.RightJoyconStick);
|
(short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick);
|
||||||
|
|
||||||
result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
|
result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
|
||||||
result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY));
|
result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY));
|
||||||
@@ -90,7 +100,7 @@ namespace Ryujinx.Ava.Input
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return _driver.IsPressed(key, _mode);
|
return _driver.IsPressed(key);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -98,19 +108,6 @@ namespace Ryujinx.Ava.Input
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryConsumePressedKey(out Key key)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return _driver.TryConsumePressedKey(_mode, out key);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
key = Key.Unknown;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetConfiguration(InputConfig configuration)
|
public void SetConfiguration(InputConfig configuration)
|
||||||
{
|
{
|
||||||
lock (_userMappingLock)
|
lock (_userMappingLock)
|
||||||
@@ -119,13 +116,37 @@ namespace Ryujinx.Ava.Input
|
|||||||
|
|
||||||
_buttonsUserMapping.Clear();
|
_buttonsUserMapping.Clear();
|
||||||
|
|
||||||
_buttonsUserMapping.AddRange(KeyboardInputMappingHelper.BuildButtonMappings(_configuration));
|
#pragma warning disable IDE0055 // Disable formatting
|
||||||
|
// Left JoyCon
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl));
|
||||||
|
|
||||||
|
// Right JoyCon
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
|
||||||
|
#pragma warning restore IDE0055
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetLed(uint packedRgb)
|
public void SetLed(uint packedRgb)
|
||||||
{
|
{
|
||||||
Logger.Debug?.Print(LogClass.UI, "SetLed called on an AvaloniaKeyboard");
|
Logger.Info?.Print(LogClass.UI, "SetLed called on an AvaloniaKeyboard");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetTriggerThreshold(float triggerThreshold) { }
|
public void SetTriggerThreshold(float triggerThreshold) { }
|
||||||
@@ -141,9 +162,41 @@ namespace Ryujinx.Ava.Input
|
|||||||
return value * ConvertRate;
|
return value * ConvertRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick<ConfigKey> stickConfig)
|
||||||
|
{
|
||||||
|
short stickX = 0;
|
||||||
|
short stickY = 0;
|
||||||
|
|
||||||
|
if (snapshot.IsPressed((Key)stickConfig.StickUp))
|
||||||
|
{
|
||||||
|
stickY += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.IsPressed((Key)stickConfig.StickDown))
|
||||||
|
{
|
||||||
|
stickY -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.IsPressed((Key)stickConfig.StickRight))
|
||||||
|
{
|
||||||
|
stickX += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.IsPressed((Key)stickConfig.StickLeft))
|
||||||
|
{
|
||||||
|
stickX -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 stick = new(stickX, stickY);
|
||||||
|
|
||||||
|
stick = Vector2.Normalize(stick);
|
||||||
|
|
||||||
|
return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
|
||||||
|
}
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
_driver?.Clear(_mode);
|
_driver?.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() { }
|
public void Dispose() { }
|
||||||
|
|||||||
@@ -1,37 +1,19 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.Systems.Configuration;
|
|
||||||
using Ryujinx.Common.Logging;
|
|
||||||
using Ryujinx.Input;
|
using Ryujinx.Input;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using AvaKey = Avalonia.Input.Key;
|
||||||
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
|
||||||
using Key = Ryujinx.Input.Key;
|
using Key = Ryujinx.Input.Key;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Input
|
namespace Ryujinx.Ava.Input
|
||||||
{
|
{
|
||||||
internal class AvaloniaKeyboardDriver : IKeyboardModeDriver
|
internal class AvaloniaKeyboardDriver : IGamepadDriver
|
||||||
{
|
{
|
||||||
private enum PhysicalKeySource
|
|
||||||
{
|
|
||||||
Direct,
|
|
||||||
ObservedFallback,
|
|
||||||
Unknown,
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly string[] _keyboardIdentifers = ["0"];
|
private static readonly string[] _keyboardIdentifers = ["0"];
|
||||||
private readonly Control _control;
|
private readonly Control _control;
|
||||||
private readonly Window _window;
|
private readonly HashSet<AvaKey> _pressedKeys;
|
||||||
private readonly HashSet<Key> _semanticPressedKeys;
|
|
||||||
private readonly HashSet<ConfigPhysicalKey> _physicalPressedKeys;
|
|
||||||
private readonly Dictionary<Key, ConfigPhysicalKey> _observedPhysicalKeysBySemanticKey;
|
|
||||||
private readonly Queue<Key> _semanticPressedKeyQueue;
|
|
||||||
private readonly Queue<Key> _physicalPressedKeyQueue;
|
|
||||||
private readonly Lock _pressedKeyQueueLock;
|
|
||||||
private readonly KeyboardInputMode _defaultMode;
|
|
||||||
|
|
||||||
public event EventHandler<KeyEventArgs> KeyPressed;
|
public event EventHandler<KeyEventArgs> KeyPressed;
|
||||||
public event EventHandler<KeyEventArgs> KeyRelease;
|
public event EventHandler<KeyEventArgs> KeyRelease;
|
||||||
@@ -40,30 +22,14 @@ namespace Ryujinx.Ava.Input
|
|||||||
public string DriverName => "AvaloniaKeyboardDriver";
|
public string DriverName => "AvaloniaKeyboardDriver";
|
||||||
public ReadOnlySpan<string> GamepadsIds => _keyboardIdentifers;
|
public ReadOnlySpan<string> GamepadsIds => _keyboardIdentifers;
|
||||||
|
|
||||||
public AvaloniaKeyboardDriver(Control control, KeyboardInputMode defaultMode = KeyboardInputMode.Semantic)
|
public AvaloniaKeyboardDriver(Control control)
|
||||||
{
|
{
|
||||||
_control = control;
|
_control = control;
|
||||||
_window = control as Window ?? TopLevel.GetTopLevel(control) as Window;
|
_pressedKeys = [];
|
||||||
_semanticPressedKeys = [];
|
|
||||||
_physicalPressedKeys = [];
|
|
||||||
_observedPhysicalKeysBySemanticKey = [];
|
|
||||||
_semanticPressedKeyQueue = [];
|
|
||||||
_physicalPressedKeyQueue = [];
|
|
||||||
_pressedKeyQueueLock = new();
|
|
||||||
_defaultMode = defaultMode;
|
|
||||||
|
|
||||||
// Use routed handlers so keys consumed earlier in the Avalonia pipeline
|
_control.KeyDown += OnKeyPress;
|
||||||
// can still be observed by the input driver. This is needed for keys like
|
_control.KeyUp += OnKeyRelease;
|
||||||
// Caps Lock on macOS, which may not reach the plain CLR event path.
|
|
||||||
_control.AddHandler(InputElement.KeyDownEvent, OnKeyPress, RoutingStrategies.Tunnel, true);
|
|
||||||
_control.AddHandler(InputElement.KeyUpEvent, OnKeyRelease, RoutingStrategies.Tunnel, true);
|
|
||||||
_control.TextInput += Control_TextInput;
|
_control.TextInput += Control_TextInput;
|
||||||
_window?.Deactivated += Window_Deactivated;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Window_Deactivated(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Control_TextInput(object sender, TextInputEventArgs e)
|
private void Control_TextInput(object sender, TextInputEventArgs e)
|
||||||
@@ -84,18 +50,13 @@ namespace Ryujinx.Ava.Input
|
|||||||
}
|
}
|
||||||
|
|
||||||
public IGamepad GetGamepad(string id)
|
public IGamepad GetGamepad(string id)
|
||||||
{
|
|
||||||
return GetKeyboard(id, _defaultMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IKeyboard GetKeyboard(string id, KeyboardInputMode mode)
|
|
||||||
{
|
{
|
||||||
if (!_keyboardIdentifers[0].Equals(id))
|
if (!_keyboardIdentifers[0].Equals(id))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AvaloniaKeyboard(this, _keyboardIdentifers[0], LocaleManager.Instance[LocaleKeys.KeyboardLayout_KeyboardInputMode], mode);
|
return new AvaloniaKeyboard(this, _keyboardIdentifers[0], LocaleManager.Instance[LocaleKeys.AllKeyboards]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<IGamepad> GetGamepads() => [GetGamepad("0")];
|
public IEnumerable<IGamepad> GetGamepads() => [GetGamepad("0")];
|
||||||
@@ -104,189 +65,40 @@ namespace Ryujinx.Ava.Input
|
|||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
_control.RemoveHandler(InputElement.KeyDownEvent, OnKeyPress);
|
_control.KeyUp -= OnKeyPress;
|
||||||
_control.RemoveHandler(InputElement.KeyUpEvent, OnKeyRelease);
|
_control.KeyDown -= OnKeyRelease;
|
||||||
_control.TextInput -= Control_TextInput;
|
|
||||||
if (_window != null)
|
|
||||||
{
|
|
||||||
_window.Deactivated -= Window_Deactivated;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void OnKeyPress(object sender, KeyEventArgs args)
|
protected void OnKeyPress(object sender, KeyEventArgs args)
|
||||||
{
|
{
|
||||||
UpdateKeyStates(args, isPressed: true);
|
_pressedKeys.Add(args.Key);
|
||||||
|
|
||||||
KeyPressed?.Invoke(this, args);
|
KeyPressed?.Invoke(this, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void OnKeyRelease(object sender, KeyEventArgs args)
|
protected void OnKeyRelease(object sender, KeyEventArgs args)
|
||||||
{
|
{
|
||||||
UpdateKeyStates(args, isPressed: false);
|
_pressedKeys.Remove(args.Key);
|
||||||
|
|
||||||
KeyRelease?.Invoke(this, args);
|
KeyRelease?.Invoke(this, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool IsPressed(Key key, KeyboardInputMode mode)
|
internal bool IsPressed(Key key)
|
||||||
{
|
{
|
||||||
if (key is Key.Unbound or Key.Unknown)
|
if (key is Key.Unbound or Key.Unknown)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return mode == KeyboardInputMode.Physical
|
AvaloniaKeyboardMappingHelper.TryGetAvaKey(key, out AvaKey nativeKey);
|
||||||
? _physicalPressedKeys.Contains((ConfigPhysicalKey)(int)key)
|
|
||||||
: _semanticPressedKeys.Contains(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void Clear(KeyboardInputMode mode)
|
return _pressedKeys.Contains(nativeKey);
|
||||||
{
|
|
||||||
lock (_pressedKeyQueueLock)
|
|
||||||
{
|
|
||||||
if (mode == KeyboardInputMode.Physical)
|
|
||||||
{
|
|
||||||
_physicalPressedKeys.Clear();
|
|
||||||
_physicalPressedKeyQueue.Clear();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_semanticPressedKeys.Clear();
|
|
||||||
_semanticPressedKeyQueue.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
lock (_pressedKeyQueueLock)
|
_pressedKeys.Clear();
|
||||||
{
|
|
||||||
_semanticPressedKeys.Clear();
|
|
||||||
_physicalPressedKeys.Clear();
|
|
||||||
_semanticPressedKeyQueue.Clear();
|
|
||||||
_physicalPressedKeyQueue.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal bool TryConsumePressedKey(KeyboardInputMode mode, out Key key)
|
|
||||||
{
|
|
||||||
lock (_pressedKeyQueueLock)
|
|
||||||
{
|
|
||||||
Queue<Key> queue = mode == KeyboardInputMode.Physical ? _physicalPressedKeyQueue : _semanticPressedKeyQueue;
|
|
||||||
|
|
||||||
if (queue.TryDequeue(out key))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
key = Key.Unknown;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void UpdateKeyState(HashSet<Key> pressedKeys, Key key, bool isPressed)
|
|
||||||
{
|
|
||||||
if (key is Key.Unknown or Key.Unbound)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPressed)
|
|
||||||
{
|
|
||||||
pressedKeys.Add(key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pressedKeys.Remove(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void UpdateKeyState(HashSet<ConfigPhysicalKey> pressedKeys, ConfigPhysicalKey key, bool isPressed)
|
|
||||||
{
|
|
||||||
if (key is ConfigPhysicalKey.Unknown or ConfigPhysicalKey.Unbound)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPressed)
|
|
||||||
{
|
|
||||||
pressedKeys.Add(key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pressedKeys.Remove(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateKeyStates(KeyEventArgs args, bool isPressed)
|
|
||||||
{
|
|
||||||
Key semanticKey = AvaloniaKeyboardMappingHelper.ToInputKey(args.Key);
|
|
||||||
Key resolvedSemanticKey = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey, args.Key);
|
|
||||||
ConfigPhysicalKey physicalKey = GetPhysicalInputKey(args, semanticKey, out PhysicalKeySource physicalKeySource);
|
|
||||||
bool semanticWasPressed = _semanticPressedKeys.Contains(resolvedSemanticKey);
|
|
||||||
bool physicalWasPressed = _physicalPressedKeys.Contains(physicalKey);
|
|
||||||
bool semanticStateChanged = resolvedSemanticKey is not Key.Unknown and not Key.Unbound && semanticWasPressed != isPressed;
|
|
||||||
bool physicalStateChanged = physicalKey is not ConfigPhysicalKey.Unknown and not ConfigPhysicalKey.Unbound && physicalWasPressed != isPressed;
|
|
||||||
bool bufferedSemanticPress = false;
|
|
||||||
bool bufferedPhysicalPress = false;
|
|
||||||
|
|
||||||
UpdateKeyState(_semanticPressedKeys, resolvedSemanticKey, isPressed);
|
|
||||||
UpdateKeyState(_physicalPressedKeys, physicalKey, isPressed);
|
|
||||||
|
|
||||||
if (isPressed)
|
|
||||||
{
|
|
||||||
lock (_pressedKeyQueueLock)
|
|
||||||
{
|
|
||||||
if (!semanticWasPressed && resolvedSemanticKey is not Key.Unknown and not Key.Unbound)
|
|
||||||
{
|
|
||||||
_semanticPressedKeyQueue.Enqueue(resolvedSemanticKey);
|
|
||||||
bufferedSemanticPress = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!physicalWasPressed && physicalKey is not ConfigPhysicalKey.Unknown and not ConfigPhysicalKey.Unbound)
|
|
||||||
{
|
|
||||||
_physicalPressedKeyQueue.Enqueue((Key)(int)physicalKey);
|
|
||||||
bufferedPhysicalPress = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPressed &&
|
|
||||||
semanticKey is not Key.Unknown and not Key.Unbound &&
|
|
||||||
physicalKey is not ConfigPhysicalKey.Unknown and not ConfigPhysicalKey.Unbound)
|
|
||||||
{
|
|
||||||
_observedPhysicalKeysBySemanticKey[semanticKey] = physicalKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ConfigurationState.Instance.Logger.EnableAvaloniaLog &&
|
|
||||||
(semanticStateChanged || physicalStateChanged))
|
|
||||||
{
|
|
||||||
Logger.Info?.Print(
|
|
||||||
LogClass.UI,
|
|
||||||
$"Keyboard {(isPressed ? "down" : "up")}: avaloniaKey={args.Key}, avaloniaPhysical={args.PhysicalKey}, keySymbol={FormatKeySymbol(args.KeySymbol)}, modifiers={args.KeyModifiers}, semantic={semanticKey}, resolvedSemantic={resolvedSemanticKey}, physical={physicalKey}, physicalSource={physicalKeySource}, bufferedSemantic={bufferedSemanticPress}, bufferedPhysical={bufferedPhysicalPress}, semanticPressed={_semanticPressedKeys.Count}, physicalPressed={_physicalPressedKeys.Count}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ConfigPhysicalKey GetPhysicalInputKey(KeyEventArgs args, Key semanticKey, out PhysicalKeySource source)
|
|
||||||
{
|
|
||||||
Key key = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey);
|
|
||||||
|
|
||||||
if (key is >= Key.Unknown and < Key.Count)
|
|
||||||
{
|
|
||||||
source = PhysicalKeySource.Direct;
|
|
||||||
return (ConfigPhysicalKey)(int)key;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (semanticKey is not Key.Unknown and not Key.Unbound &&
|
|
||||||
_observedPhysicalKeysBySemanticKey.TryGetValue(semanticKey, out ConfigPhysicalKey observedPhysicalKey))
|
|
||||||
{
|
|
||||||
source = PhysicalKeySource.ObservedFallback;
|
|
||||||
return observedPhysicalKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
source = PhysicalKeySource.Unknown;
|
|
||||||
return ConfigPhysicalKey.Unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string FormatKeySymbol(string keySymbol)
|
|
||||||
{
|
|
||||||
return string.IsNullOrEmpty(keySymbol) ? "<none>" : keySymbol;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using Ryujinx.Input;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AvaKey = Avalonia.Input.Key;
|
using AvaKey = Avalonia.Input.Key;
|
||||||
using AvaPhysicalKey = Avalonia.Input.PhysicalKey;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Input
|
namespace Ryujinx.Ava.Input
|
||||||
{
|
{
|
||||||
@@ -133,8 +132,7 @@ namespace Ryujinx.Ava.Input
|
|||||||
AvaKey.D8,
|
AvaKey.D8,
|
||||||
AvaKey.D9,
|
AvaKey.D9,
|
||||||
AvaKey.OemTilde,
|
AvaKey.OemTilde,
|
||||||
AvaKey.Oem102,
|
AvaKey.OemTilde,AvaKey.OemMinus,
|
||||||
AvaKey.OemMinus,
|
|
||||||
AvaKey.OemPlus,
|
AvaKey.OemPlus,
|
||||||
AvaKey.OemOpenBrackets,
|
AvaKey.OemOpenBrackets,
|
||||||
AvaKey.OemCloseBrackets,
|
AvaKey.OemCloseBrackets,
|
||||||
@@ -149,149 +147,7 @@ namespace Ryujinx.Ava.Input
|
|||||||
AvaKey.None
|
AvaKey.None
|
||||||
];
|
];
|
||||||
|
|
||||||
private static readonly AvaPhysicalKey[] _physicalKeyMapping =
|
|
||||||
[
|
|
||||||
// NOTE: Invalid
|
|
||||||
AvaPhysicalKey.None,
|
|
||||||
|
|
||||||
AvaPhysicalKey.ShiftLeft,
|
|
||||||
AvaPhysicalKey.ShiftRight,
|
|
||||||
AvaPhysicalKey.ControlLeft,
|
|
||||||
AvaPhysicalKey.ControlRight,
|
|
||||||
AvaPhysicalKey.AltLeft,
|
|
||||||
AvaPhysicalKey.AltRight,
|
|
||||||
AvaPhysicalKey.MetaLeft,
|
|
||||||
AvaPhysicalKey.MetaRight,
|
|
||||||
AvaPhysicalKey.ContextMenu,
|
|
||||||
AvaPhysicalKey.F1,
|
|
||||||
AvaPhysicalKey.F2,
|
|
||||||
AvaPhysicalKey.F3,
|
|
||||||
AvaPhysicalKey.F4,
|
|
||||||
AvaPhysicalKey.F5,
|
|
||||||
AvaPhysicalKey.F6,
|
|
||||||
AvaPhysicalKey.F7,
|
|
||||||
AvaPhysicalKey.F8,
|
|
||||||
AvaPhysicalKey.F9,
|
|
||||||
AvaPhysicalKey.F10,
|
|
||||||
AvaPhysicalKey.F11,
|
|
||||||
AvaPhysicalKey.F12,
|
|
||||||
AvaPhysicalKey.F13,
|
|
||||||
AvaPhysicalKey.F14,
|
|
||||||
AvaPhysicalKey.F15,
|
|
||||||
AvaPhysicalKey.F16,
|
|
||||||
AvaPhysicalKey.F17,
|
|
||||||
AvaPhysicalKey.F18,
|
|
||||||
AvaPhysicalKey.F19,
|
|
||||||
AvaPhysicalKey.F20,
|
|
||||||
AvaPhysicalKey.F21,
|
|
||||||
AvaPhysicalKey.F22,
|
|
||||||
AvaPhysicalKey.F23,
|
|
||||||
AvaPhysicalKey.F24,
|
|
||||||
|
|
||||||
AvaPhysicalKey.None,
|
|
||||||
AvaPhysicalKey.None,
|
|
||||||
AvaPhysicalKey.None,
|
|
||||||
AvaPhysicalKey.None,
|
|
||||||
AvaPhysicalKey.None,
|
|
||||||
AvaPhysicalKey.None,
|
|
||||||
AvaPhysicalKey.None,
|
|
||||||
AvaPhysicalKey.None,
|
|
||||||
AvaPhysicalKey.None,
|
|
||||||
AvaPhysicalKey.None,
|
|
||||||
AvaPhysicalKey.None,
|
|
||||||
|
|
||||||
AvaPhysicalKey.ArrowUp,
|
|
||||||
AvaPhysicalKey.ArrowDown,
|
|
||||||
AvaPhysicalKey.ArrowLeft,
|
|
||||||
AvaPhysicalKey.ArrowRight,
|
|
||||||
AvaPhysicalKey.Enter,
|
|
||||||
AvaPhysicalKey.Escape,
|
|
||||||
AvaPhysicalKey.Space,
|
|
||||||
AvaPhysicalKey.Tab,
|
|
||||||
AvaPhysicalKey.Backspace,
|
|
||||||
AvaPhysicalKey.Insert,
|
|
||||||
AvaPhysicalKey.Delete,
|
|
||||||
AvaPhysicalKey.PageUp,
|
|
||||||
AvaPhysicalKey.PageDown,
|
|
||||||
AvaPhysicalKey.Home,
|
|
||||||
AvaPhysicalKey.End,
|
|
||||||
AvaPhysicalKey.CapsLock,
|
|
||||||
AvaPhysicalKey.ScrollLock,
|
|
||||||
AvaPhysicalKey.PrintScreen,
|
|
||||||
AvaPhysicalKey.Pause,
|
|
||||||
AvaPhysicalKey.NumLock,
|
|
||||||
AvaPhysicalKey.NumPadClear,
|
|
||||||
AvaPhysicalKey.NumPad0,
|
|
||||||
AvaPhysicalKey.NumPad1,
|
|
||||||
AvaPhysicalKey.NumPad2,
|
|
||||||
AvaPhysicalKey.NumPad3,
|
|
||||||
AvaPhysicalKey.NumPad4,
|
|
||||||
AvaPhysicalKey.NumPad5,
|
|
||||||
AvaPhysicalKey.NumPad6,
|
|
||||||
AvaPhysicalKey.NumPad7,
|
|
||||||
AvaPhysicalKey.NumPad8,
|
|
||||||
AvaPhysicalKey.NumPad9,
|
|
||||||
AvaPhysicalKey.NumPadDivide,
|
|
||||||
AvaPhysicalKey.NumPadMultiply,
|
|
||||||
AvaPhysicalKey.NumPadSubtract,
|
|
||||||
AvaPhysicalKey.NumPadAdd,
|
|
||||||
AvaPhysicalKey.NumPadDecimal,
|
|
||||||
AvaPhysicalKey.NumPadEnter,
|
|
||||||
AvaPhysicalKey.A,
|
|
||||||
AvaPhysicalKey.B,
|
|
||||||
AvaPhysicalKey.C,
|
|
||||||
AvaPhysicalKey.D,
|
|
||||||
AvaPhysicalKey.E,
|
|
||||||
AvaPhysicalKey.F,
|
|
||||||
AvaPhysicalKey.G,
|
|
||||||
AvaPhysicalKey.H,
|
|
||||||
AvaPhysicalKey.I,
|
|
||||||
AvaPhysicalKey.J,
|
|
||||||
AvaPhysicalKey.K,
|
|
||||||
AvaPhysicalKey.L,
|
|
||||||
AvaPhysicalKey.M,
|
|
||||||
AvaPhysicalKey.N,
|
|
||||||
AvaPhysicalKey.O,
|
|
||||||
AvaPhysicalKey.P,
|
|
||||||
AvaPhysicalKey.Q,
|
|
||||||
AvaPhysicalKey.R,
|
|
||||||
AvaPhysicalKey.S,
|
|
||||||
AvaPhysicalKey.T,
|
|
||||||
AvaPhysicalKey.U,
|
|
||||||
AvaPhysicalKey.V,
|
|
||||||
AvaPhysicalKey.W,
|
|
||||||
AvaPhysicalKey.X,
|
|
||||||
AvaPhysicalKey.Y,
|
|
||||||
AvaPhysicalKey.Z,
|
|
||||||
AvaPhysicalKey.Digit0,
|
|
||||||
AvaPhysicalKey.Digit1,
|
|
||||||
AvaPhysicalKey.Digit2,
|
|
||||||
AvaPhysicalKey.Digit3,
|
|
||||||
AvaPhysicalKey.Digit4,
|
|
||||||
AvaPhysicalKey.Digit5,
|
|
||||||
AvaPhysicalKey.Digit6,
|
|
||||||
AvaPhysicalKey.Digit7,
|
|
||||||
AvaPhysicalKey.Digit8,
|
|
||||||
AvaPhysicalKey.Digit9,
|
|
||||||
AvaPhysicalKey.Backquote,
|
|
||||||
AvaPhysicalKey.IntlBackslash,
|
|
||||||
AvaPhysicalKey.Minus,
|
|
||||||
AvaPhysicalKey.Equal,
|
|
||||||
AvaPhysicalKey.BracketLeft,
|
|
||||||
AvaPhysicalKey.BracketRight,
|
|
||||||
AvaPhysicalKey.Semicolon,
|
|
||||||
AvaPhysicalKey.Quote,
|
|
||||||
AvaPhysicalKey.Comma,
|
|
||||||
AvaPhysicalKey.Period,
|
|
||||||
AvaPhysicalKey.Slash,
|
|
||||||
AvaPhysicalKey.Backslash,
|
|
||||||
|
|
||||||
// NOTE: invalid
|
|
||||||
AvaPhysicalKey.None
|
|
||||||
];
|
|
||||||
|
|
||||||
private static readonly Dictionary<AvaKey, Key> _avaKeyMapping;
|
private static readonly Dictionary<AvaKey, Key> _avaKeyMapping;
|
||||||
private static readonly Dictionary<AvaPhysicalKey, Key> _avaPhysicalKeyMapping;
|
|
||||||
|
|
||||||
static AvaloniaKeyboardMappingHelper()
|
static AvaloniaKeyboardMappingHelper()
|
||||||
{
|
{
|
||||||
@@ -299,42 +155,21 @@ namespace Ryujinx.Ava.Input
|
|||||||
|
|
||||||
// NOTE: Avalonia.Input.Key is not contiguous and quite large, so use a dictionary instead of an array.
|
// NOTE: Avalonia.Input.Key is not contiguous and quite large, so use a dictionary instead of an array.
|
||||||
_avaKeyMapping = new Dictionary<AvaKey, Key>();
|
_avaKeyMapping = new Dictionary<AvaKey, Key>();
|
||||||
_avaPhysicalKeyMapping = new Dictionary<AvaPhysicalKey, Key>();
|
|
||||||
|
|
||||||
foreach (Key key in inputKeys)
|
foreach (Key key in inputKeys)
|
||||||
{
|
{
|
||||||
if (TryGetAvaKey(key, out AvaKey avaKey))
|
if (TryGetAvaKey(key, out AvaKey index))
|
||||||
{
|
{
|
||||||
_avaKeyMapping[avaKey] = key;
|
_avaKeyMapping[index] = key;
|
||||||
}
|
|
||||||
|
|
||||||
if (TryGetAvaPhysicalKey(key, out AvaPhysicalKey avaPhysicalKey))
|
|
||||||
{
|
|
||||||
_avaPhysicalKeyMapping[avaPhysicalKey] = key;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alias additional Avalonia key values to improve non-US layout support.
|
|
||||||
_avaKeyMapping[AvaKey.Oem1] = Key.Semicolon;
|
|
||||||
_avaKeyMapping[AvaKey.Oem2] = Key.Slash;
|
|
||||||
_avaKeyMapping[AvaKey.Oem3] = Key.Tilde;
|
|
||||||
_avaKeyMapping[AvaKey.Oem4] = Key.BracketLeft;
|
|
||||||
_avaKeyMapping[AvaKey.Oem5] = Key.BackSlash;
|
|
||||||
_avaKeyMapping[AvaKey.Oem6] = Key.BracketRight;
|
|
||||||
_avaKeyMapping[AvaKey.Oem7] = Key.Quote;
|
|
||||||
_avaKeyMapping[AvaKey.OemBackslash] = Key.Grave;
|
|
||||||
_avaKeyMapping[AvaKey.Oem102] = Key.Grave;
|
|
||||||
|
|
||||||
// Common alternates for non-US/JIS physical keys.
|
|
||||||
_avaPhysicalKeyMapping[AvaPhysicalKey.IntlRo] = Key.BackSlash;
|
|
||||||
_avaPhysicalKeyMapping[AvaPhysicalKey.IntlYen] = Key.BackSlash;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryGetAvaKey(Key key, out AvaKey avaKey)
|
public static bool TryGetAvaKey(Key key, out AvaKey avaKey)
|
||||||
{
|
{
|
||||||
avaKey = AvaKey.None;
|
avaKey = AvaKey.None;
|
||||||
|
|
||||||
bool keyExist = key < Key.Count && (int)key < _keyMapping.Length;
|
bool keyExist = (int)key < _keyMapping.Length;
|
||||||
if (keyExist)
|
if (keyExist)
|
||||||
{
|
{
|
||||||
avaKey = _keyMapping[(int)key];
|
avaKey = _keyMapping[(int)key];
|
||||||
@@ -343,34 +178,9 @@ namespace Ryujinx.Ava.Input
|
|||||||
return keyExist;
|
return keyExist;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryGetAvaPhysicalKey(Key key, out AvaPhysicalKey avaPhysicalKey)
|
|
||||||
{
|
|
||||||
avaPhysicalKey = AvaPhysicalKey.None;
|
|
||||||
|
|
||||||
bool keyExist = key < Key.Count && (int)key < _physicalKeyMapping.Length;
|
|
||||||
if (keyExist)
|
|
||||||
{
|
|
||||||
avaPhysicalKey = _physicalKeyMapping[(int)key];
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyExist;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Key ToInputKey(AvaKey key)
|
public static Key ToInputKey(AvaKey key)
|
||||||
{
|
{
|
||||||
return _avaKeyMapping.GetValueOrDefault(key, Key.Unknown);
|
return _avaKeyMapping.GetValueOrDefault(key, Key.Unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Key ToInputKey(AvaPhysicalKey key)
|
|
||||||
{
|
|
||||||
return _avaPhysicalKeyMapping.GetValueOrDefault(key, Key.Unknown);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Key ToInputKey(AvaPhysicalKey physicalKey, AvaKey key)
|
|
||||||
{
|
|
||||||
Key inputKey = ToInputKey(key);
|
|
||||||
|
|
||||||
return inputKey != Key.Unknown ? inputKey : ToInputKey(physicalKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ namespace Ryujinx.Ava
|
|||||||
CoreDumpArg = coreDumpArg;
|
CoreDumpArg = coreDumpArg;
|
||||||
|
|
||||||
// TODO: Ryujinx causes core dumps on Linux when it exits "uncleanly", eg. through an unhandled exception.
|
// TODO: Ryujinx causes core dumps on Linux when it exits "uncleanly", eg. through an unhandled exception.
|
||||||
// This is undesirable and causes very odd behavior during development (the process stops responding,
|
// This is undesirable and causes very odd behavior during development (the process stops responding,
|
||||||
// the .NET debugger freezes or suddenly detaches, /tmp/ gets filled etc.), unless explicitly requested by the user.
|
// the .NET debugger freezes or suddenly detaches, /tmp/ gets filled etc.), unless explicitly requested by the user.
|
||||||
// This needs to be investigated, but calling prctl() is better than modifying system-wide settings or leaving this be.
|
// This needs to be investigated, but calling prctl() is better than modifying system-wide settings or leaving this be.
|
||||||
if (!coreDumpArg)
|
if (!coreDumpArg)
|
||||||
@@ -242,7 +242,7 @@ namespace Ryujinx.Ava
|
|||||||
ConfigurationPath = appDataConfigurationPath;
|
ConfigurationPath = appDataConfigurationPath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ConfigurationPath == null)
|
if (ConfigurationPath == null)
|
||||||
{
|
{
|
||||||
// No configuration, we load the default values and save it to disk
|
// No configuration, we load the default values and save it to disk
|
||||||
@@ -313,28 +313,28 @@ namespace Ryujinx.Ava
|
|||||||
_ => ConfigurationState.Instance.HideCursor,
|
_ => ConfigurationState.Instance.HideCursor,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if memoryManagerMode was overridden.
|
// Check if memoryManagerMode was overridden.
|
||||||
if (CommandLineState.OverrideMemoryManagerMode is not null)
|
if (CommandLineState.OverrideMemoryManagerMode is not null)
|
||||||
if (Enum.TryParse(CommandLineState.OverrideMemoryManagerMode, true, out MemoryManagerMode result))
|
if (Enum.TryParse(CommandLineState.OverrideMemoryManagerMode, true, out MemoryManagerMode result))
|
||||||
{
|
{
|
||||||
ConfigurationState.Instance.System.MemoryManagerMode.Value = result;
|
ConfigurationState.Instance.System.MemoryManagerMode.Value = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if PPTC was overridden.
|
// Check if PPTC was overridden.
|
||||||
if (CommandLineState.OverridePPTC is not null)
|
if (CommandLineState.OverridePPTC is not null)
|
||||||
if (Enum.TryParse(CommandLineState.OverridePPTC, true, out bool result))
|
if (Enum.TryParse(CommandLineState.OverridePPTC, true, out bool result))
|
||||||
{
|
{
|
||||||
ConfigurationState.Instance.System.EnablePtc.Value = result;
|
ConfigurationState.Instance.System.EnablePtc.Value = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if region was overridden.
|
// Check if region was overridden.
|
||||||
if (CommandLineState.OverrideSystemRegion is not null)
|
if (CommandLineState.OverrideSystemRegion is not null)
|
||||||
if (Enum.TryParse(CommandLineState.OverrideSystemRegion, true, out Region result))
|
if (Enum.TryParse(CommandLineState.OverrideSystemRegion, true, out Region result))
|
||||||
{
|
{
|
||||||
ConfigurationState.Instance.System.Region.Value = result;
|
ConfigurationState.Instance.System.Region.Value = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check if language was overridden.
|
//Check if language was overridden.
|
||||||
if (CommandLineState.OverrideSystemLanguage is not null)
|
if (CommandLineState.OverrideSystemLanguage is not null)
|
||||||
if (Enum.TryParse(CommandLineState.OverrideSystemLanguage, true, out Language result))
|
if (Enum.TryParse(CommandLineState.OverrideSystemLanguage, true, out Language result))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -46,8 +46,12 @@
|
|||||||
<PackageReference Include="Avalonia.Diagnostics" Condition="'$(Configuration)'=='Debug'" />
|
<PackageReference Include="Avalonia.Diagnostics" Condition="'$(Configuration)'=='Debug'" />
|
||||||
<PackageReference Include="Avalonia.Controls.DataGrid" />
|
<PackageReference Include="Avalonia.Controls.DataGrid" />
|
||||||
<PackageReference Include="Avalonia.Markup.Xaml.Loader" />
|
<PackageReference Include="Avalonia.Markup.Xaml.Loader" />
|
||||||
|
<PackageReference Include="SharpCompress" />
|
||||||
<PackageReference Include="Svg.Controls.Avalonia" />
|
<PackageReference Include="Svg.Controls.Avalonia" />
|
||||||
<PackageReference Include="Svg.Controls.Skia.Avalonia" />
|
<PackageReference Include="Svg.Controls.Skia.Avalonia" />
|
||||||
|
<PackageReference Include="SkiaSharp.NativeAssets.Win32" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
|
||||||
|
<PackageReference Include="SkiaSharp.NativeAssets.macOS" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'win-arm64'" />
|
||||||
|
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Condition="'$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'win-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
|
||||||
<PackageReference Include="DynamicData" />
|
<PackageReference Include="DynamicData" />
|
||||||
<PackageReference Include="FluentAvaloniaUI" />
|
<PackageReference Include="FluentAvaloniaUI" />
|
||||||
<PackageReference Include="CommandLineParser" />
|
<PackageReference Include="CommandLineParser" />
|
||||||
@@ -57,7 +61,7 @@
|
|||||||
<PackageReference Include="Projektanker.Icons.Avalonia.FontAwesome" />
|
<PackageReference Include="Projektanker.Icons.Avalonia.FontAwesome" />
|
||||||
<PackageReference Include="Projektanker.Icons.Avalonia.MaterialDesign" />
|
<PackageReference Include="Projektanker.Icons.Avalonia.MaterialDesign" />
|
||||||
<PackageReference Include="OpenTK.Core" />
|
<PackageReference Include="OpenTK.Core" />
|
||||||
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
|
<PackageReference Include="Ryujinx.Audio.OpenAL" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
|
||||||
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" />
|
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" />
|
||||||
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'win-arm64'" />
|
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'win-arm64'" />
|
||||||
<PackageReference Include="Ryujinx.UpdateClient" />
|
<PackageReference Include="Ryujinx.UpdateClient" />
|
||||||
@@ -68,7 +72,6 @@
|
|||||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
|
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
|
||||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
|
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
|
||||||
<PackageReference Include="SPB" />
|
<PackageReference Include="SPB" />
|
||||||
<PackageReference Include="SharpZipLib" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -169,9 +172,8 @@
|
|||||||
<EmbeddedResource Include="Assets\UIImages\Logo_Amiibo.png" />
|
<EmbeddedResource Include="Assets\UIImages\Logo_Amiibo.png" />
|
||||||
<EmbeddedResource Include="Assets\UIImages\Logo_Discord_Dark.png" />
|
<EmbeddedResource Include="Assets\UIImages\Logo_Discord_Dark.png" />
|
||||||
<EmbeddedResource Include="Assets\UIImages\Logo_Discord_Light.png" />
|
<EmbeddedResource Include="Assets\UIImages\Logo_Discord_Light.png" />
|
||||||
<EmbeddedResource Include="Assets\UIImages\Logo_GitLab_Dark.png" />
|
|
||||||
<EmbeddedResource Include="Assets\UIImages\Logo_GitLab_Light.png" />
|
|
||||||
<EmbeddedResource Include="Assets\UIImages\Logo_Ryujinx.png" />
|
<EmbeddedResource Include="Assets\UIImages\Logo_Ryujinx.png" />
|
||||||
|
<EmbeddedResource Include="Assets\UIImages\Logo_Forgejo.png" />
|
||||||
<EmbeddedResource Include="Assets\UIImages\Logo_Ryujinx_AntiAlias.png" />
|
<EmbeddedResource Include="Assets\UIImages\Logo_Ryujinx_AntiAlias.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ using VSyncMode = Ryujinx.Common.Configuration.VSyncMode;
|
|||||||
|
|
||||||
namespace Ryujinx.Ava.Systems
|
namespace Ryujinx.Ava.Systems
|
||||||
{
|
{
|
||||||
internal class AppHost
|
internal class AppHost : IDisposable
|
||||||
{
|
{
|
||||||
private const int CursorHideIdleTime = 5; // Hide Cursor seconds.
|
private const int CursorHideIdleTime = 5; // Hide Cursor seconds.
|
||||||
private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping.
|
private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping.
|
||||||
@@ -438,7 +438,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
|
|
||||||
SaveBitmapAsPng(bitmapToSave, path);
|
SaveBitmapAsPng(bitmapToSave, path);
|
||||||
|
|
||||||
Logger.Notice.Print(LogClass.Application, $"Screenshot saved to {path}", "Screenshot");
|
Logger.Notice.Print(LogClass.Application, $"Screenshot saved to '{path}'.", "Screenshot");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -611,27 +611,40 @@ namespace Ryujinx.Ava.Systems
|
|||||||
|
|
||||||
_isActive = false;
|
_isActive = false;
|
||||||
|
|
||||||
// NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose.
|
|
||||||
// We only need to wait for all commands submitted during the main gpu loop to be processed.
|
|
||||||
_gpuDoneEvent.WaitOne();
|
|
||||||
_gpuDoneEvent.Dispose();
|
|
||||||
|
|
||||||
DisplaySleep.Restore();
|
DisplaySleep.Restore();
|
||||||
|
|
||||||
NpadManager.Dispose();
|
NpadManager.Dispose();
|
||||||
TouchScreenManager.Dispose();
|
TouchScreenManager.Dispose();
|
||||||
Device.Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
|
// NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose.
|
||||||
|
// We only need to wait for all commands submitted during the main gpu loop to be processed.
|
||||||
|
// If the GPU has no work and is cancelled, we need to handle that as well.
|
||||||
|
|
||||||
|
WaitHandle.WaitAny(new[] { _gpuDoneEvent, _gpuCancellationTokenSource.Token.WaitHandle });
|
||||||
|
|
||||||
|
if (_renderingStarted)
|
||||||
|
{
|
||||||
|
// Waiting for work to be finished before we dispose.
|
||||||
|
Device.Gpu.WaitUntilGpuReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
_gpuDoneEvent.Dispose();
|
||||||
|
_gpuCancellationTokenSource.Dispose();
|
||||||
|
|
||||||
DisposeGpu();
|
DisposeGpu();
|
||||||
|
|
||||||
AppExit?.Invoke(this, EventArgs.Empty);
|
AppExit?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Dispose()
|
// MUST be public to inherit from IDisposable
|
||||||
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (Device.Processes != null)
|
if (Device.Processes != null)
|
||||||
MainWindowViewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText, _playTimer.Elapsed);
|
{
|
||||||
|
MainWindowViewModel.UpdateGameMetadata(Device.Processes.ActiveApplication?.ProgramIdText,
|
||||||
|
_playTimer.Elapsed);
|
||||||
|
}
|
||||||
|
|
||||||
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
|
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
|
||||||
ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState;
|
ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState;
|
||||||
ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState;
|
ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState;
|
||||||
@@ -646,7 +659,6 @@ namespace Ryujinx.Ava.Systems
|
|||||||
_topLevel.PointerExited -= TopLevel_PointerExited;
|
_topLevel.PointerExited -= TopLevel_PointerExited;
|
||||||
|
|
||||||
_gpuCancellationTokenSource.Cancel();
|
_gpuCancellationTokenSource.Cancel();
|
||||||
_gpuCancellationTokenSource.Dispose();
|
|
||||||
|
|
||||||
_chrono.Stop();
|
_chrono.Stop();
|
||||||
_playTimer.Stop();
|
_playTimer.Stop();
|
||||||
@@ -672,6 +684,12 @@ namespace Ryujinx.Ava.Systems
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// No use waiting on something that never started work
|
||||||
|
if (_renderingStarted)
|
||||||
|
{
|
||||||
|
Device.Gpu.WaitUntilGpuReady();
|
||||||
|
}
|
||||||
|
|
||||||
Device.DisposeGpu();
|
Device.DisposeGpu();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -686,7 +704,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
_cursorState = CursorStates.ForceChangeCursor;
|
_cursorState = CursorStates.ForceChangeCursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> LoadGuestApplication(BlitStruct<ApplicationControlProperty>? customNacpData = null)
|
public async Task LoadGuestApplication(CancellationTokenSource cts, BlitStruct<ApplicationControlProperty>? customNacpData = null)
|
||||||
{
|
{
|
||||||
DiscordIntegrationModule.GuestAppStartedAt = Timestamps.Now;
|
DiscordIntegrationModule.GuestAppStartedAt = Timestamps.Now;
|
||||||
|
|
||||||
@@ -715,7 +733,8 @@ namespace Ryujinx.Ava.Systems
|
|||||||
await UserErrorDialog.ShowUserErrorDialog(userError);
|
await UserErrorDialog.ShowUserErrorDialog(userError);
|
||||||
Device.Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
return false;
|
cts.Cancel();
|
||||||
|
throw new OperationCanceledException(cts.Token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -724,10 +743,11 @@ namespace Ryujinx.Ava.Systems
|
|||||||
await UserErrorDialog.ShowUserErrorDialog(userError);
|
await UserErrorDialog.ShowUserErrorDialog(userError);
|
||||||
Device.Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
return false;
|
cts.Cancel();
|
||||||
|
throw new OperationCanceledException(cts.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tell the user that we installed a firmware for them.
|
// Tell the user that we installed firmware for them.
|
||||||
if (userError is UserError.NoFirmware)
|
if (userError is UserError.NoFirmware)
|
||||||
{
|
{
|
||||||
firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
|
firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
|
||||||
@@ -747,7 +767,8 @@ namespace Ryujinx.Ava.Systems
|
|||||||
await UserErrorDialog.ShowUserErrorDialog(userError);
|
await UserErrorDialog.ShowUserErrorDialog(userError);
|
||||||
Device.Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
return false;
|
cts.Cancel();
|
||||||
|
throw new OperationCanceledException(cts.Token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -762,7 +783,8 @@ namespace Ryujinx.Ava.Systems
|
|||||||
{
|
{
|
||||||
Device.Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
return false;
|
cts.Cancel();
|
||||||
|
throw new OperationCanceledException(cts.Token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (Directory.Exists(ApplicationPath))
|
else if (Directory.Exists(ApplicationPath))
|
||||||
@@ -782,20 +804,24 @@ namespace Ryujinx.Ava.Systems
|
|||||||
|
|
||||||
if (!Device.LoadCart(ApplicationPath, romFsFiles[0]))
|
if (!Device.LoadCart(ApplicationPath, romFsFiles[0]))
|
||||||
{
|
{
|
||||||
|
await ContentDialogHelper.CreateErrorDialog(
|
||||||
|
"Please specify an unpacked game directory with a valid exefs or NSO/NRO.");
|
||||||
Device.Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
return false;
|
cts.Cancel();
|
||||||
|
throw new OperationCanceledException(cts.Token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
|
Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
|
||||||
|
|
||||||
if (!Device.LoadCart(ApplicationPath))
|
if (!Device.LoadCart(ApplicationPath))
|
||||||
{
|
{
|
||||||
|
await ContentDialogHelper.CreateErrorDialog(
|
||||||
|
"Please specify an unpacked game directory with a valid exefs or NSO/NRO.");
|
||||||
Device.Dispose();
|
Device.Dispose();
|
||||||
|
cts.Cancel();
|
||||||
return false;
|
throw new OperationCanceledException(cts.Token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -813,7 +839,8 @@ namespace Ryujinx.Ava.Systems
|
|||||||
{
|
{
|
||||||
Device.Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
return false;
|
cts.Cancel();
|
||||||
|
throw new OperationCanceledException(cts.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -826,7 +853,8 @@ namespace Ryujinx.Ava.Systems
|
|||||||
{
|
{
|
||||||
Device.Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
return false;
|
cts.Cancel();
|
||||||
|
throw new OperationCanceledException(cts.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -840,7 +868,8 @@ namespace Ryujinx.Ava.Systems
|
|||||||
{
|
{
|
||||||
Device.Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
return false;
|
cts.Cancel();
|
||||||
|
throw new OperationCanceledException(cts.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -855,7 +884,8 @@ namespace Ryujinx.Ava.Systems
|
|||||||
{
|
{
|
||||||
Device.Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
return false;
|
cts.Cancel();
|
||||||
|
throw new OperationCanceledException(cts.Token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (ArgumentOutOfRangeException)
|
catch (ArgumentOutOfRangeException)
|
||||||
@@ -864,7 +894,8 @@ namespace Ryujinx.Ava.Systems
|
|||||||
|
|
||||||
Device.Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
return false;
|
cts.Cancel();
|
||||||
|
throw new OperationCanceledException(cts.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -873,19 +904,18 @@ namespace Ryujinx.Ava.Systems
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
|
Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NSO/NRO file.");
|
||||||
|
|
||||||
Device.Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
return false;
|
cts.Cancel();
|
||||||
|
throw new OperationCanceledException(cts.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText,
|
ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText,
|
||||||
appMetadata => appMetadata.UpdatePreGame()
|
appMetadata => appMetadata.UpdatePreGame()
|
||||||
);
|
);
|
||||||
_playTimer.Start();
|
_playTimer.Start();
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Resume()
|
internal void Resume()
|
||||||
@@ -895,7 +925,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
_viewModel.IsPaused = false;
|
_viewModel.IsPaused = false;
|
||||||
_playTimer.Start();
|
_playTimer.Start();
|
||||||
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI);
|
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI);
|
||||||
Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed");
|
Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Pause()
|
internal void Pause()
|
||||||
@@ -905,7 +935,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
_viewModel.IsPaused = true;
|
_viewModel.IsPaused = true;
|
||||||
_playTimer.Stop();
|
_playTimer.Stop();
|
||||||
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI, LocaleManager.Instance[LocaleKeys.Paused]);
|
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI, LocaleManager.Instance[LocaleKeys.Paused]);
|
||||||
Logger.Info?.Print(LogClass.Emulation, "Emulation was paused");
|
Logger.Info?.Print(LogClass.Emulation, "Emulation was paused.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitEmulatedSwitch()
|
private void InitEmulatedSwitch()
|
||||||
@@ -1065,49 +1095,56 @@ namespace Ryujinx.Ava.Systems
|
|||||||
|
|
||||||
Device.Gpu.Renderer.RunLoop(() =>
|
Device.Gpu.Renderer.RunLoop(() =>
|
||||||
{
|
{
|
||||||
Device.Gpu.SetGpuThread();
|
try
|
||||||
Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
|
|
||||||
|
|
||||||
_renderer.Window.ChangeVSyncMode(Device.VSyncMode);
|
|
||||||
|
|
||||||
while (_isActive)
|
|
||||||
{
|
{
|
||||||
_ticks += _chrono.ElapsedTicks;
|
Device.Gpu.SetGpuThread();
|
||||||
|
Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
|
||||||
|
|
||||||
_chrono.Restart();
|
_renderer.Window.ChangeVSyncMode(Device.VSyncMode);
|
||||||
|
|
||||||
if (Device.WaitFifo())
|
while (_isActive)
|
||||||
{
|
{
|
||||||
Device.Statistics.RecordFifoStart();
|
_ticks += _chrono.ElapsedTicks;
|
||||||
Device.ProcessFrame();
|
|
||||||
Device.Statistics.RecordFifoEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
while (Device.ConsumeFrameAvailable())
|
_chrono.Restart();
|
||||||
{
|
|
||||||
if (!_renderingStarted)
|
if (Device.WaitFifo())
|
||||||
{
|
{
|
||||||
_renderingStarted = true;
|
Device.Statistics.RecordFifoStart();
|
||||||
_viewModel.SwitchToRenderer(false);
|
Device.ProcessFrame();
|
||||||
InitStatus();
|
Device.Statistics.RecordFifoEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
Device.PresentFrame(() => (RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.SwapBuffers());
|
while (Device.ConsumeFrameAvailable())
|
||||||
}
|
{
|
||||||
|
if (!_renderingStarted)
|
||||||
|
{
|
||||||
|
_renderingStarted = true;
|
||||||
|
_viewModel.SwitchToRenderer(false);
|
||||||
|
InitStatus();
|
||||||
|
}
|
||||||
|
|
||||||
if (_ticks >= _ticksPerFrame)
|
Device.PresentFrame(() =>
|
||||||
{
|
(RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.SwapBuffers());
|
||||||
UpdateStatus();
|
}
|
||||||
|
|
||||||
|
if (_ticks >= _ticksPerFrame)
|
||||||
|
{
|
||||||
|
UpdateStatus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
// Make sure all commands in the run loop are fully executed before leaving the loop.
|
|
||||||
if (Device.Gpu.Renderer is ThreadedRenderer threaded)
|
|
||||||
{
|
{
|
||||||
threaded.FlushThreadedCommands();
|
// Make sure all commands in the run loop are fully executed before leaving the loop.
|
||||||
|
if (Device.Gpu.Renderer is ThreadedRenderer threaded)
|
||||||
|
{
|
||||||
|
Logger.Info?.PrintMsg(LogClass.Gpu, "Flushing threaded commands...");
|
||||||
|
threaded.FlushThreadedCommands();
|
||||||
|
Logger.Info?.PrintMsg(LogClass.Gpu, "Flushed!");
|
||||||
|
}
|
||||||
|
_gpuDoneEvent.Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
_gpuDoneEvent.Set();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
(RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(true);
|
(RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(true);
|
||||||
@@ -1234,17 +1271,9 @@ namespace Ryujinx.Ava.Systems
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasModalFocusLoss = _viewModel.Window is MainWindow mainWindow &&
|
|
||||||
mainWindow.SettingsWindow?.IsActive == true;
|
|
||||||
|
|
||||||
if (!_viewModel.IsActive || hasModalFocusLoss)
|
|
||||||
{
|
|
||||||
_inputManager.KeyboardDriver.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
|
NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
|
||||||
|
|
||||||
if (_viewModel.IsActive && !hasModalFocusLoss)
|
if (_viewModel.IsActive)
|
||||||
{
|
{
|
||||||
bool isCursorVisible = true;
|
bool isCursorVisible = true;
|
||||||
|
|
||||||
@@ -1377,7 +1406,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
// Touchscreen.
|
// Touchscreen.
|
||||||
bool hasTouch = false;
|
bool hasTouch = false;
|
||||||
|
|
||||||
if (_viewModel.IsActive && !hasModalFocusLoss && !ConfigurationState.Instance.Hid.EnableMouse.Value)
|
if (_viewModel.IsActive && !ConfigurationState.Instance.Hid.EnableMouse.Value)
|
||||||
{
|
{
|
||||||
hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as AvaloniaMouseDriver).IsButtonPressed(MouseButton.Button1), ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
|
hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as AvaloniaMouseDriver).IsButtonPressed(MouseButton.Button1), ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -849,7 +849,8 @@ namespace Ryujinx.Ava.Systems.AppLibrary
|
|||||||
|
|
||||||
foreach (ApplicationData installedApplication in Applications.Items)
|
foreach (ApplicationData installedApplication in Applications.Items)
|
||||||
{
|
{
|
||||||
temporary += LoadAndSaveMetaData(installedApplication.IdString).TimePlayed;
|
// this should always exist... should...
|
||||||
|
temporary += LoadAndSaveMetaData(installedApplication.IdString).Value.TimePlayed;
|
||||||
}
|
}
|
||||||
|
|
||||||
TotalTimePlayed = temporary;
|
TotalTimePlayed = temporary;
|
||||||
@@ -1159,15 +1160,22 @@ namespace Ryujinx.Ava.Systems.AppLibrary
|
|||||||
ApplicationCountUpdated?.Invoke(null, e);
|
ApplicationCountUpdated?.Invoke(null, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ApplicationMetadata LoadAndSaveMetaData(string titleId, Action<ApplicationMetadata> modifyFunction = null)
|
public static Gommon.Optional<ApplicationMetadata> LoadAndSaveMetaData(string titleId, Action<ApplicationMetadata> modifyFunction = null)
|
||||||
{
|
{
|
||||||
|
if (titleId is null)
|
||||||
|
{
|
||||||
|
Logger.Warning?.PrintMsg(LogClass.Application, "Cannot save metadata because title ID is invalid.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
string metadataFolder = Path.Combine(AppDataManager.GamesDirPath, titleId, "gui");
|
string metadataFolder = Path.Combine(AppDataManager.GamesDirPath, titleId, "gui");
|
||||||
string metadataFile = Path.Combine(metadataFolder, "metadata.json");
|
string metadataFile = Path.Combine(metadataFolder, "metadata.json");
|
||||||
|
|
||||||
ApplicationMetadata appMetadata;
|
ApplicationMetadata appMetadata;
|
||||||
|
|
||||||
if (!File.Exists(metadataFile))
|
if (!File.Exists(metadataFile))
|
||||||
{
|
{
|
||||||
|
Logger.Info?.Print(LogClass.Application, $"Metadata file does not exist. Creating metadata for {titleId}...");
|
||||||
Directory.CreateDirectory(metadataFolder);
|
Directory.CreateDirectory(metadataFolder);
|
||||||
|
|
||||||
appMetadata = new ApplicationMetadata();
|
appMetadata = new ApplicationMetadata();
|
||||||
@@ -1177,12 +1185,12 @@ namespace Ryujinx.Ava.Systems.AppLibrary
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
Logger.Debug?.Print(LogClass.Application, $"Deserializing metadata for {titleId}...");
|
||||||
appMetadata = JsonHelper.DeserializeFromFile(metadataFile, _serializerContext.ApplicationMetadata);
|
appMetadata = JsonHelper.DeserializeFromFile(metadataFile, _serializerContext.ApplicationMetadata);
|
||||||
}
|
}
|
||||||
catch (JsonException)
|
catch (JsonException)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Application, $"Failed to parse metadata json for {titleId}. Loading defaults.");
|
Logger.Warning?.Print(LogClass.Application, $"Failed to parse metadata json for {titleId}. Loading defaults.");
|
||||||
|
|
||||||
appMetadata = new ApplicationMetadata();
|
appMetadata = new ApplicationMetadata();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current version of the file format
|
/// The current version of the file format
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int CurrentVersion = 71;
|
public const int CurrentVersion = 72;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Version of the configuration file format
|
/// Version of the configuration file format
|
||||||
@@ -113,6 +113,11 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
/// Enables printing FS access log messages
|
/// Enables printing FS access log messages
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool LoggingEnableFsAccessLog { get; set; }
|
public bool LoggingEnableFsAccessLog { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables printing network log messages
|
||||||
|
/// </summary>
|
||||||
|
public bool LoggingEnableNetLog { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enables log messages from Avalonia
|
/// Enables log messages from Avalonia
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Gommon;
|
using Gommon;
|
||||||
using Ryujinx.Ava.Systems.Configuration.System;
|
using Ryujinx.Ava.Systems.Configuration.System;
|
||||||
using Ryujinx.Ava.Systems.Configuration.UI;
|
using Ryujinx.Ava.Systems.Configuration.UI;
|
||||||
@@ -13,8 +13,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Key = Ryujinx.Common.Configuration.Hid.Key;
|
|
||||||
using PhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
|
||||||
using RyuLogger = Ryujinx.Common.Logging.Logger;
|
using RyuLogger = Ryujinx.Common.Logging.Logger;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Systems.Configuration
|
namespace Ryujinx.Ava.Systems.Configuration
|
||||||
@@ -70,6 +68,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
Logger.EnableTrace.Value = cff.LoggingEnableTrace;
|
Logger.EnableTrace.Value = cff.LoggingEnableTrace;
|
||||||
Logger.EnableGuest.Value = cff.LoggingEnableGuest;
|
Logger.EnableGuest.Value = cff.LoggingEnableGuest;
|
||||||
Logger.EnableFsAccessLog.Value = cff.LoggingEnableFsAccessLog;
|
Logger.EnableFsAccessLog.Value = cff.LoggingEnableFsAccessLog;
|
||||||
|
Logger.EnableNetLog.Value = cff.LoggingEnableNetLog;
|
||||||
Logger.FilteredClasses.Value = cff.LoggingFilteredClasses;
|
Logger.FilteredClasses.Value = cff.LoggingFilteredClasses;
|
||||||
Logger.GraphicsDebugLevel.Value = cff.LoggingGraphicsDebugLevel;
|
Logger.GraphicsDebugLevel.Value = cff.LoggingGraphicsDebugLevel;
|
||||||
|
|
||||||
@@ -271,45 +270,45 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
Id = "0",
|
Id = "0",
|
||||||
PlayerIndex = PlayerIndex.Player1,
|
PlayerIndex = PlayerIndex.Player1,
|
||||||
ControllerType = ControllerType.ProController,
|
ControllerType = ControllerType.ProController,
|
||||||
LeftJoycon = new LeftJoyconCommonConfig<PhysicalKey>
|
LeftJoycon = new LeftJoyconCommonConfig<Key>
|
||||||
{
|
{
|
||||||
DpadUp = PhysicalKey.Up,
|
DpadUp = Key.Up,
|
||||||
DpadDown = PhysicalKey.Down,
|
DpadDown = Key.Down,
|
||||||
DpadLeft = PhysicalKey.Left,
|
DpadLeft = Key.Left,
|
||||||
DpadRight = PhysicalKey.Right,
|
DpadRight = Key.Right,
|
||||||
ButtonMinus = PhysicalKey.Minus,
|
ButtonMinus = Key.Minus,
|
||||||
ButtonL = PhysicalKey.E,
|
ButtonL = Key.E,
|
||||||
ButtonZl = PhysicalKey.Q,
|
ButtonZl = Key.Q,
|
||||||
ButtonSl = PhysicalKey.Unbound,
|
ButtonSl = Key.Unbound,
|
||||||
ButtonSr = PhysicalKey.Unbound,
|
ButtonSr = Key.Unbound,
|
||||||
},
|
},
|
||||||
LeftJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||||
{
|
{
|
||||||
StickUp = PhysicalKey.W,
|
StickUp = Key.W,
|
||||||
StickDown = PhysicalKey.S,
|
StickDown = Key.S,
|
||||||
StickLeft = PhysicalKey.A,
|
StickLeft = Key.A,
|
||||||
StickRight = PhysicalKey.D,
|
StickRight = Key.D,
|
||||||
StickButton = PhysicalKey.F,
|
StickButton = Key.F,
|
||||||
},
|
},
|
||||||
RightJoycon = new RightJoyconCommonConfig<PhysicalKey>
|
RightJoycon = new RightJoyconCommonConfig<Key>
|
||||||
{
|
{
|
||||||
ButtonA = PhysicalKey.Z,
|
ButtonA = Key.Z,
|
||||||
ButtonB = PhysicalKey.X,
|
ButtonB = Key.X,
|
||||||
ButtonX = PhysicalKey.C,
|
ButtonX = Key.C,
|
||||||
ButtonY = PhysicalKey.V,
|
ButtonY = Key.V,
|
||||||
ButtonPlus = PhysicalKey.Plus,
|
ButtonPlus = Key.Plus,
|
||||||
ButtonR = PhysicalKey.U,
|
ButtonR = Key.U,
|
||||||
ButtonZr = PhysicalKey.O,
|
ButtonZr = Key.O,
|
||||||
ButtonSl = PhysicalKey.Unbound,
|
ButtonSl = Key.Unbound,
|
||||||
ButtonSr = PhysicalKey.Unbound,
|
ButtonSr = Key.Unbound,
|
||||||
},
|
},
|
||||||
RightJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||||
{
|
{
|
||||||
StickUp = PhysicalKey.I,
|
StickUp = Key.I,
|
||||||
StickDown = PhysicalKey.K,
|
StickDown = Key.K,
|
||||||
StickLeft = PhysicalKey.J,
|
StickLeft = Key.J,
|
||||||
StickRight = PhysicalKey.L,
|
StickRight = Key.L,
|
||||||
StickButton = PhysicalKey.H,
|
StickButton = Key.H,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -257,6 +257,11 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
/// Enables printing FS access log messages
|
/// Enables printing FS access log messages
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ReactiveObject<bool> EnableFsAccessLog { get; private set; }
|
public ReactiveObject<bool> EnableFsAccessLog { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables printing network log messages
|
||||||
|
/// </summary>
|
||||||
|
public ReactiveObject<bool> EnableNetLog { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enables log messages from Avalonia
|
/// Enables log messages from Avalonia
|
||||||
@@ -289,6 +294,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
EnableTrace = new ReactiveObject<bool>();
|
EnableTrace = new ReactiveObject<bool>();
|
||||||
EnableGuest = new ReactiveObject<bool>();
|
EnableGuest = new ReactiveObject<bool>();
|
||||||
EnableFsAccessLog = new ReactiveObject<bool>();
|
EnableFsAccessLog = new ReactiveObject<bool>();
|
||||||
|
EnableNetLog = new ReactiveObject<bool>();
|
||||||
EnableAvaloniaLog = new ReactiveObject<bool>();
|
EnableAvaloniaLog = new ReactiveObject<bool>();
|
||||||
FilteredClasses = new ReactiveObject<LogClass[]>();
|
FilteredClasses = new ReactiveObject<LogClass[]>();
|
||||||
EnableFileLog = new ReactiveObject<bool>();
|
EnableFileLog = new ReactiveObject<bool>();
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ using Ryujinx.Graphics.Vulkan;
|
|||||||
using Ryujinx.HLE;
|
using Ryujinx.HLE;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Key = Ryujinx.Common.Configuration.Hid.Key;
|
|
||||||
using PhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Systems.Configuration
|
namespace Ryujinx.Ava.Systems.Configuration
|
||||||
{
|
{
|
||||||
@@ -49,6 +47,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
LoggingEnableTrace = Logger.EnableTrace,
|
LoggingEnableTrace = Logger.EnableTrace,
|
||||||
LoggingEnableGuest = Logger.EnableGuest,
|
LoggingEnableGuest = Logger.EnableGuest,
|
||||||
LoggingEnableFsAccessLog = Logger.EnableFsAccessLog,
|
LoggingEnableFsAccessLog = Logger.EnableFsAccessLog,
|
||||||
|
LoggingEnableNetLog = Logger.EnableNetLog,
|
||||||
LoggingEnableAvalonia = Logger.EnableAvaloniaLog,
|
LoggingEnableAvalonia = Logger.EnableAvaloniaLog,
|
||||||
LoggingFilteredClasses = Logger.FilteredClasses,
|
LoggingFilteredClasses = Logger.FilteredClasses,
|
||||||
LoggingGraphicsDebugLevel = Logger.GraphicsDebugLevel,
|
LoggingGraphicsDebugLevel = Logger.GraphicsDebugLevel,
|
||||||
@@ -178,6 +177,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
Logger.EnableTrace.Value = false;
|
Logger.EnableTrace.Value = false;
|
||||||
Logger.EnableGuest.Value = true;
|
Logger.EnableGuest.Value = true;
|
||||||
Logger.EnableFsAccessLog.Value = false;
|
Logger.EnableFsAccessLog.Value = false;
|
||||||
|
Logger.EnableNetLog.Value = false;
|
||||||
Logger.EnableAvaloniaLog.Value = false;
|
Logger.EnableAvaloniaLog.Value = false;
|
||||||
Logger.FilteredClasses.Value = [];
|
Logger.FilteredClasses.Value = [];
|
||||||
Logger.GraphicsDebugLevel.Value = GraphicsDebugLevel.None;
|
Logger.GraphicsDebugLevel.Value = GraphicsDebugLevel.None;
|
||||||
@@ -287,45 +287,45 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
Name = "Keyboard",
|
Name = "Keyboard",
|
||||||
PlayerIndex = PlayerIndex.Player1,
|
PlayerIndex = PlayerIndex.Player1,
|
||||||
ControllerType = ControllerType.ProController,
|
ControllerType = ControllerType.ProController,
|
||||||
LeftJoycon = new LeftJoyconCommonConfig<PhysicalKey>
|
LeftJoycon = new LeftJoyconCommonConfig<Key>
|
||||||
{
|
{
|
||||||
DpadUp = PhysicalKey.Up,
|
DpadUp = Key.Up,
|
||||||
DpadDown = PhysicalKey.Down,
|
DpadDown = Key.Down,
|
||||||
DpadLeft = PhysicalKey.Left,
|
DpadLeft = Key.Left,
|
||||||
DpadRight = PhysicalKey.Right,
|
DpadRight = Key.Right,
|
||||||
ButtonMinus = PhysicalKey.Minus,
|
ButtonMinus = Key.Minus,
|
||||||
ButtonL = PhysicalKey.E,
|
ButtonL = Key.E,
|
||||||
ButtonZl = PhysicalKey.Q,
|
ButtonZl = Key.Q,
|
||||||
ButtonSl = PhysicalKey.Unbound,
|
ButtonSl = Key.Unbound,
|
||||||
ButtonSr = PhysicalKey.Unbound,
|
ButtonSr = Key.Unbound,
|
||||||
},
|
},
|
||||||
LeftJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||||
{
|
{
|
||||||
StickUp = PhysicalKey.W,
|
StickUp = Key.W,
|
||||||
StickDown = PhysicalKey.S,
|
StickDown = Key.S,
|
||||||
StickLeft = PhysicalKey.A,
|
StickLeft = Key.A,
|
||||||
StickRight = PhysicalKey.D,
|
StickRight = Key.D,
|
||||||
StickButton = PhysicalKey.F,
|
StickButton = Key.F,
|
||||||
},
|
},
|
||||||
RightJoycon = new RightJoyconCommonConfig<PhysicalKey>
|
RightJoycon = new RightJoyconCommonConfig<Key>
|
||||||
{
|
{
|
||||||
ButtonA = PhysicalKey.Z,
|
ButtonA = Key.Z,
|
||||||
ButtonB = PhysicalKey.X,
|
ButtonB = Key.X,
|
||||||
ButtonX = PhysicalKey.C,
|
ButtonX = Key.C,
|
||||||
ButtonY = PhysicalKey.V,
|
ButtonY = Key.V,
|
||||||
ButtonPlus = PhysicalKey.Plus,
|
ButtonPlus = Key.Plus,
|
||||||
ButtonR = PhysicalKey.U,
|
ButtonR = Key.U,
|
||||||
ButtonZr = PhysicalKey.O,
|
ButtonZr = Key.O,
|
||||||
ButtonSl = PhysicalKey.Unbound,
|
ButtonSl = Key.Unbound,
|
||||||
ButtonSr = PhysicalKey.Unbound,
|
ButtonSr = Key.Unbound,
|
||||||
},
|
},
|
||||||
RightJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||||
{
|
{
|
||||||
StickUp = PhysicalKey.I,
|
StickUp = Key.I,
|
||||||
StickDown = PhysicalKey.K,
|
StickDown = Key.K,
|
||||||
StickLeft = PhysicalKey.J,
|
StickLeft = Key.J,
|
||||||
StickRight = PhysicalKey.L,
|
StickRight = Key.L,
|
||||||
StickButton = PhysicalKey.H,
|
StickButton = Key.H,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
(_, e) => Logger.SetEnable(LogLevel.Guest, e.NewValue);
|
(_, e) => Logger.SetEnable(LogLevel.Guest, e.NewValue);
|
||||||
ConfigurationState.Instance.Logger.EnableFsAccessLog.Event +=
|
ConfigurationState.Instance.Logger.EnableFsAccessLog.Event +=
|
||||||
(_, e) => Logger.SetEnable(LogLevel.AccessLog, e.NewValue);
|
(_, e) => Logger.SetEnable(LogLevel.AccessLog, e.NewValue);
|
||||||
|
ConfigurationState.Instance.Logger.EnableNetLog.Event +=
|
||||||
|
(_, e) => Logger.SetEnable(LogLevel.NetLog, e.NewValue);
|
||||||
|
|
||||||
ConfigurationState.Instance.Logger.FilteredClasses.Event += (_, e) =>
|
ConfigurationState.Instance.Logger.FilteredClasses.Event += (_, e) =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
|
|
||||||
public static void Use(Optional<string> titleId)
|
public static void Use(Optional<string> titleId)
|
||||||
{
|
{
|
||||||
if (titleId.TryGet(out string tid))
|
if (titleId.TryGet(out string tid) && Switch.Shared.Processes.ActiveApplication is not null)
|
||||||
SwitchToPlayingState(
|
SwitchToPlayingState(
|
||||||
ApplicationLibrary.LoadAndSaveMetaData(tid),
|
ApplicationLibrary.LoadAndSaveMetaData(tid),
|
||||||
Switch.Shared.Processes.ActiveApplication
|
Switch.Shared.Processes.ActiveApplication
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If build URL not found, assume no new update is available.
|
// If build URL not found, assume no new update is available.
|
||||||
if (_versionResponse.ArtifactUrl is null or "")
|
if (string.IsNullOrEmpty(_versionResponse.ArtifactUrl))
|
||||||
{
|
{
|
||||||
if (showVersionUpToDate)
|
if (showVersionUpToDate)
|
||||||
{
|
{
|
||||||
@@ -123,6 +123,8 @@ namespace Ryujinx.Ava.Systems
|
|||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_connectionCount = (int)_versionResponse.MaxConcurrency;
|
||||||
|
|
||||||
return (currentVersion, newVersion);
|
return (currentVersion, newVersion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
using Gommon;
|
using Gommon;
|
||||||
using ICSharpCode.SharpZipLib.GZip;
|
|
||||||
using ICSharpCode.SharpZipLib.Tar;
|
|
||||||
using ICSharpCode.SharpZipLib.Zip;
|
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.Utilities;
|
using Ryujinx.Ava.Utilities;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Helper;
|
using Ryujinx.Common.Helper;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using SharpCompress.Archives;
|
||||||
|
using SharpCompress.Compressors.Xz;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Formats.Tar;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
@@ -21,7 +22,6 @@ using System.Net.NetworkInformation;
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -32,8 +32,8 @@ namespace Ryujinx.Ava.Systems
|
|||||||
private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory;
|
private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||||
private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
|
private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
|
||||||
private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish");
|
private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish");
|
||||||
private const int ConnectionCount = 4;
|
|
||||||
|
|
||||||
|
private static int _connectionCount = 1;
|
||||||
private static long _buildSize;
|
private static long _buildSize;
|
||||||
private static bool _updateSuccessful;
|
private static bool _updateSuccessful;
|
||||||
private static bool _running;
|
private static bool _running;
|
||||||
@@ -73,27 +73,6 @@ namespace Ryujinx.Ava.Systems
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch build size information to learn chunk sizes.
|
|
||||||
using HttpClient buildSizeClient = ConstructHttpClient();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0");
|
|
||||||
|
|
||||||
// GitLab instance is located in Ukraine. Connection times will vary across the world.
|
|
||||||
buildSizeClient.Timeout = TimeSpan.FromSeconds(10);
|
|
||||||
|
|
||||||
HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_versionResponse.ArtifactUrl), HttpCompletionOption.ResponseHeadersRead);
|
|
||||||
|
|
||||||
_buildSize = message.Content.Headers.ContentRange.Length.Value;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Application, ex.Message);
|
|
||||||
Logger.Warning?.Print(LogClass.Application, "Couldn't determine build size for update, using single-threaded updater");
|
|
||||||
|
|
||||||
_buildSize = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
await Dispatcher.UIThread.InvokeAsync(async () =>
|
await Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
{
|
{
|
||||||
string newVersionString = ReleaseInformation.IsCanaryBuild
|
string newVersionString = ReleaseInformation.IsCanaryBuild
|
||||||
@@ -143,6 +122,14 @@ namespace Ryujinx.Ava.Systems
|
|||||||
|
|
||||||
Directory.CreateDirectory(_updateDir);
|
Directory.CreateDirectory(_updateDir);
|
||||||
|
|
||||||
|
// If we get a .zip url switch it to the preferred .7z file instead
|
||||||
|
// The update server still returns the .zip url by default for legacy support
|
||||||
|
downloadUrl = downloadUrl.Replace(".zip", ".7z");
|
||||||
|
|
||||||
|
// If we get a .tar.gz url switch it to the preferred .tar.xz file instead
|
||||||
|
// The update server still returns the .tar.gz url by default for legacy support
|
||||||
|
downloadUrl = downloadUrl.Replace(".tar.gz", ".tar.xz");
|
||||||
|
|
||||||
string updateFile = Path.Combine(_updateDir, "update.bin");
|
string updateFile = Path.Combine(_updateDir, "update.bin");
|
||||||
|
|
||||||
TaskDialog taskDialog = new()
|
TaskDialog taskDialog = new()
|
||||||
@@ -153,6 +140,27 @@ namespace Ryujinx.Ava.Systems
|
|||||||
ShowProgressBar = true,
|
ShowProgressBar = true,
|
||||||
XamlRoot = RyujinxApp.MainWindow,
|
XamlRoot = RyujinxApp.MainWindow,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Fetch build size information to learn chunk sizes.
|
||||||
|
using HttpClient buildSizeClient = ConstructHttpClient();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0");
|
||||||
|
|
||||||
|
// Forgejo instance is located in Ukraine. Connection times will vary across the world.
|
||||||
|
buildSizeClient.Timeout = TimeSpan.FromSeconds(10);
|
||||||
|
|
||||||
|
HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_versionResponse.ArtifactUrl), HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
_buildSize = message.Content.Headers.ContentRange.Length.Value;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, ex.Message);
|
||||||
|
Logger.Warning?.Print(LogClass.Application, "Couldn't determine build size for update, using single-threaded updater");
|
||||||
|
|
||||||
|
_buildSize = -1;
|
||||||
|
}
|
||||||
|
|
||||||
taskDialog.Opened += (s, e) =>
|
taskDialog.Opened += (s, e) =>
|
||||||
{
|
{
|
||||||
@@ -234,22 +242,22 @@ namespace Ryujinx.Ava.Systems
|
|||||||
private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
||||||
{
|
{
|
||||||
// Multi-Threaded Updater
|
// Multi-Threaded Updater
|
||||||
long chunkSize = _buildSize / ConnectionCount;
|
long chunkSize = _buildSize / _connectionCount;
|
||||||
long remainderChunk = _buildSize % ConnectionCount;
|
long remainderChunk = _buildSize % _connectionCount;
|
||||||
|
|
||||||
int completedRequests = 0;
|
int completedRequests = 0;
|
||||||
int totalProgressPercentage = 0;
|
int totalProgressPercentage = 0;
|
||||||
int[] progressPercentage = new int[ConnectionCount];
|
int[] progressPercentage = new int[_connectionCount];
|
||||||
|
|
||||||
List<byte[]> list = new(ConnectionCount);
|
List<byte[]> list = new(_connectionCount);
|
||||||
List<WebClient> webClients = new(ConnectionCount);
|
List<WebClient> webClients = new(_connectionCount);
|
||||||
|
|
||||||
for (int i = 0; i < ConnectionCount; i++)
|
for (int i = 0; i < _connectionCount; i++)
|
||||||
{
|
{
|
||||||
list.Add([]);
|
list.Add([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < ConnectionCount; i++)
|
for (int i = 0; i < _connectionCount; i++)
|
||||||
{
|
{
|
||||||
#pragma warning disable SYSLIB0014
|
#pragma warning disable SYSLIB0014
|
||||||
// TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient.
|
// TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient.
|
||||||
@@ -258,7 +266,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
|
|
||||||
webClients.Add(client);
|
webClients.Add(client);
|
||||||
|
|
||||||
if (i == ConnectionCount - 1)
|
if (i == _connectionCount - 1)
|
||||||
{
|
{
|
||||||
client.Headers.Add("Range", $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}");
|
client.Headers.Add("Range", $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}");
|
||||||
}
|
}
|
||||||
@@ -275,7 +283,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage);
|
Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage);
|
||||||
Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage);
|
Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage);
|
||||||
|
|
||||||
taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal);
|
taskDialog.SetProgressBarState(totalProgressPercentage / _connectionCount, TaskDialogProgressState.Normal);
|
||||||
};
|
};
|
||||||
|
|
||||||
client.DownloadDataCompleted += (_, args) =>
|
client.DownloadDataCompleted += (_, args) =>
|
||||||
@@ -294,10 +302,10 @@ namespace Ryujinx.Ava.Systems
|
|||||||
list[index] = args.Result;
|
list[index] = args.Result;
|
||||||
Interlocked.Increment(ref completedRequests);
|
Interlocked.Increment(ref completedRequests);
|
||||||
|
|
||||||
if (Equals(completedRequests, ConnectionCount))
|
if (Equals(completedRequests, _connectionCount))
|
||||||
{
|
{
|
||||||
byte[] mergedFileBytes = new byte[_buildSize];
|
byte[] mergedFileBytes = new byte[_buildSize];
|
||||||
for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < ConnectionCount; connectionIndex++)
|
for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < _connectionCount; connectionIndex++)
|
||||||
{
|
{
|
||||||
Array.Copy(list[connectionIndex], 0, mergedFileBytes, destinationOffset, list[connectionIndex].Length);
|
Array.Copy(list[connectionIndex], 0, mergedFileBytes, destinationOffset, list[connectionIndex].Length);
|
||||||
destinationOffset += list[connectionIndex].Length;
|
destinationOffset += list[connectionIndex].Length;
|
||||||
@@ -402,73 +410,33 @@ namespace Ryujinx.Ava.Systems
|
|||||||
|
|
||||||
[SupportedOSPlatform("linux")]
|
[SupportedOSPlatform("linux")]
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
private static void ExtractTarGzipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
|
private static void ExtractTarGzipFile(string archivePath, string outputDirectoryPath)
|
||||||
{
|
{
|
||||||
using FileStream inStream = File.OpenRead(archivePath);
|
using FileStream inStream = File.OpenRead(archivePath);
|
||||||
using GZipInputStream gzipStream = new(inStream);
|
using GZipStream gzipStream = new(inStream, CompressionMode.Decompress);
|
||||||
using TarInputStream tarStream = new(gzipStream, Encoding.ASCII);
|
|
||||||
|
TarFile.ExtractToDirectory(gzipStream, outputDirectoryPath, true);
|
||||||
TarEntry tarEntry;
|
}
|
||||||
|
|
||||||
while ((tarEntry = tarStream.GetNextEntry()) is not null)
|
[SupportedOSPlatform("linux")]
|
||||||
{
|
[SupportedOSPlatform("macos")]
|
||||||
if (tarEntry.IsDirectory)
|
private static void ExtractTarXzipFile(string archivePath, string outputDirectoryPath)
|
||||||
{
|
{
|
||||||
continue;
|
using FileStream inStream = File.OpenRead(archivePath);
|
||||||
}
|
using XZStream gzipStream = new(inStream);
|
||||||
|
|
||||||
string outPath = Path.Combine(outputDirectoryPath, tarEntry.Name);
|
TarFile.ExtractToDirectory(gzipStream, outputDirectoryPath, true);
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
|
||||||
|
|
||||||
using FileStream outStream = File.OpenWrite(outPath);
|
|
||||||
tarStream.CopyEntryContents(outStream);
|
|
||||||
|
|
||||||
File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode);
|
|
||||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
|
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
|
||||||
{
|
|
||||||
if (tarEntry is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
taskDialog.SetProgressBarState(GetPercentage(tarEntry.Size, inStream.Length), TaskDialogProgressState.Normal);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ExtractZipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
|
private static void ExtractZipFile(string archivePath, string outputDirectoryPath)
|
||||||
{
|
{
|
||||||
using Stream inStream = File.OpenRead(archivePath);
|
ZipFile.ExtractToDirectory(archivePath, outputDirectoryPath);
|
||||||
using ZipFile zipFile = new(inStream);
|
}
|
||||||
|
|
||||||
double count = 0;
|
private static void Extract7ZipFile(string archivePath, string outputDirectoryPath)
|
||||||
foreach (ZipEntry zipEntry in zipFile)
|
{
|
||||||
{
|
IArchive archive = ArchiveFactory.OpenArchive(archivePath);
|
||||||
count++;
|
archive.WriteToDirectory(outputDirectoryPath);
|
||||||
if (zipEntry.IsDirectory)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
string outPath = Path.Combine(outputDirectoryPath, zipEntry.Name);
|
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
|
||||||
|
|
||||||
using Stream zipStream = zipFile.GetInputStream(zipEntry);
|
|
||||||
using FileStream outStream = File.OpenWrite(outPath);
|
|
||||||
|
|
||||||
zipStream.CopyTo(outStream);
|
|
||||||
|
|
||||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
|
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
|
||||||
{
|
|
||||||
taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void InstallUpdate(TaskDialog taskDialog, string updateFile)
|
private static void InstallUpdate(TaskDialog taskDialog, string updateFile)
|
||||||
@@ -479,16 +447,20 @@ namespace Ryujinx.Ava.Systems
|
|||||||
|
|
||||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||||
{
|
{
|
||||||
ExtractTarGzipFile(taskDialog, updateFile, _updateDir);
|
ExtractTarXzipFile(updateFile, _updateDir);
|
||||||
}
|
}
|
||||||
else if (OperatingSystem.IsWindows())
|
else if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
ExtractZipFile(taskDialog, updateFile, _updateDir);
|
Extract7ZipFile(updateFile, _updateDir);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The new decompression implementations don't have a way to show progress
|
||||||
|
// so the progressbar is just set to 100% after the decompression is done
|
||||||
|
taskDialog.SetProgressBarState(100, TaskDialogProgressState.Normal);
|
||||||
|
|
||||||
// Delete downloaded zip
|
// Delete downloaded zip
|
||||||
File.Delete(updateFile);
|
File.Delete(updateFile);
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
|
|
||||||
private void AvaloniaDynamicTextInputHandler_KeyRelease(object sender, KeyEventArgs e)
|
private void AvaloniaDynamicTextInputHandler_KeyRelease(object sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
HidKey key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.PhysicalKey, e.Key);
|
HidKey key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key);
|
||||||
|
|
||||||
if (!(KeyReleasedEvent?.Invoke(key)).GetValueOrDefault(true))
|
if (!(KeyReleasedEvent?.Invoke(key)).GetValueOrDefault(true))
|
||||||
{
|
{
|
||||||
@@ -85,7 +85,7 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
|
|
||||||
private void AvaloniaDynamicTextInputHandler_KeyPressed(object sender, KeyEventArgs e)
|
private void AvaloniaDynamicTextInputHandler_KeyPressed(object sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
HidKey key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.PhysicalKey, e.Key);
|
HidKey key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key);
|
||||||
|
|
||||||
if (!(KeyPressedEvent?.Invoke(key)).GetValueOrDefault(true))
|
if (!(KeyPressedEvent?.Invoke(key)).GetValueOrDefault(true))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Avalonia.Controls.Primitives;
|
using Avalonia.Controls.Primitives;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Ryujinx.Ava.Input;
|
|
||||||
using Ryujinx.Input;
|
using Ryujinx.Input;
|
||||||
using Ryujinx.Input.Assigner;
|
using Ryujinx.Input.Assigner;
|
||||||
using System;
|
using System;
|
||||||
@@ -26,7 +25,6 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
|
|
||||||
private bool _isWaitingForInput;
|
private bool _isWaitingForInput;
|
||||||
private bool _shouldUnbind;
|
private bool _shouldUnbind;
|
||||||
private IKeyboard _keyboard;
|
|
||||||
public event EventHandler<ButtonAssignedEventArgs> ButtonAssigned;
|
public event EventHandler<ButtonAssignedEventArgs> ButtonAssigned;
|
||||||
|
|
||||||
public ButtonKeyAssigner(ToggleButton toggleButton)
|
public ButtonKeyAssigner(ToggleButton toggleButton)
|
||||||
@@ -36,9 +34,6 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
|
|
||||||
public async void GetInputAndAssign(IButtonAssigner assigner, IKeyboard keyboard = null)
|
public async void GetInputAndAssign(IButtonAssigner assigner, IKeyboard keyboard = null)
|
||||||
{
|
{
|
||||||
_keyboard = keyboard;
|
|
||||||
ClearKeyboardState(_keyboard);
|
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() =>
|
||||||
{
|
{
|
||||||
ToggledButton.IsChecked = true;
|
ToggledButton.IsChecked = true;
|
||||||
@@ -87,7 +82,6 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
_isWaitingForInput = false;
|
_isWaitingForInput = false;
|
||||||
|
|
||||||
ToggledButton.IsChecked = false;
|
ToggledButton.IsChecked = false;
|
||||||
ClearKeyboardState(_keyboard);
|
|
||||||
|
|
||||||
if (pressedButton.HasValue && pressedButton.Value.AsHidType<Key>() == Key.BackSpace)
|
if (pressedButton.HasValue && pressedButton.Value.AsHidType<Key>() == Key.BackSpace)
|
||||||
{
|
{
|
||||||
@@ -104,15 +98,6 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
_isWaitingForInput = false;
|
_isWaitingForInput = false;
|
||||||
ToggledButton.IsChecked = false;
|
ToggledButton.IsChecked = false;
|
||||||
_shouldUnbind = shouldUnbind;
|
_shouldUnbind = shouldUnbind;
|
||||||
ClearKeyboardState(_keyboard);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ClearKeyboardState(IKeyboard keyboard)
|
|
||||||
{
|
|
||||||
if (keyboard is AvaloniaKeyboard avaloniaKeyboard)
|
|
||||||
{
|
|
||||||
avaloniaKeyboard.Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using Ryujinx.Common.Configuration.Hid.Controller;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Key = Ryujinx.Input.Key;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Helpers
|
namespace Ryujinx.Ava.UI.Helpers
|
||||||
{
|
{
|
||||||
@@ -13,6 +12,79 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
{
|
{
|
||||||
public static readonly KeyValueConverter Instance = new();
|
public static readonly KeyValueConverter Instance = new();
|
||||||
|
|
||||||
|
private static readonly Dictionary<Key, LocaleKeys> _keysMap = new()
|
||||||
|
{
|
||||||
|
{ Key.Unknown, LocaleKeys.KeyUnknown },
|
||||||
|
{ Key.ShiftLeft, LocaleKeys.KeyShiftLeft },
|
||||||
|
{ Key.ShiftRight, LocaleKeys.KeyShiftRight },
|
||||||
|
{ Key.ControlLeft, LocaleKeys.KeyControlLeft },
|
||||||
|
{ Key.ControlRight, LocaleKeys.KeyControlRight },
|
||||||
|
{ Key.AltLeft, LocaleKeys.KeyAltLeft },
|
||||||
|
{ Key.AltRight, LocaleKeys.KeyAltRight },
|
||||||
|
{ Key.WinLeft, LocaleKeys.KeyWinLeft },
|
||||||
|
{ Key.WinRight, LocaleKeys.KeyWinRight },
|
||||||
|
{ Key.Up, LocaleKeys.KeyUp },
|
||||||
|
{ Key.Down, LocaleKeys.KeyDown },
|
||||||
|
{ Key.Left, LocaleKeys.KeyLeft },
|
||||||
|
{ Key.Right, LocaleKeys.KeyRight },
|
||||||
|
{ Key.Enter, LocaleKeys.KeyEnter },
|
||||||
|
{ Key.Escape, LocaleKeys.KeyEscape },
|
||||||
|
{ Key.Space, LocaleKeys.KeySpace },
|
||||||
|
{ Key.Tab, LocaleKeys.KeyTab },
|
||||||
|
{ Key.BackSpace, LocaleKeys.KeyBackSpace },
|
||||||
|
{ Key.Insert, LocaleKeys.KeyInsert },
|
||||||
|
{ Key.Delete, LocaleKeys.KeyDelete },
|
||||||
|
{ Key.PageUp, LocaleKeys.KeyPageUp },
|
||||||
|
{ Key.PageDown, LocaleKeys.KeyPageDown },
|
||||||
|
{ Key.Home, LocaleKeys.KeyHome },
|
||||||
|
{ Key.End, LocaleKeys.KeyEnd },
|
||||||
|
{ Key.CapsLock, LocaleKeys.KeyCapsLock },
|
||||||
|
{ Key.ScrollLock, LocaleKeys.KeyScrollLock },
|
||||||
|
{ Key.PrintScreen, LocaleKeys.KeyPrintScreen },
|
||||||
|
{ Key.Pause, LocaleKeys.KeyPause },
|
||||||
|
{ Key.NumLock, LocaleKeys.KeyNumLock },
|
||||||
|
{ Key.Clear, LocaleKeys.KeyClear },
|
||||||
|
{ Key.Keypad0, LocaleKeys.KeyKeypad0 },
|
||||||
|
{ Key.Keypad1, LocaleKeys.KeyKeypad1 },
|
||||||
|
{ Key.Keypad2, LocaleKeys.KeyKeypad2 },
|
||||||
|
{ Key.Keypad3, LocaleKeys.KeyKeypad3 },
|
||||||
|
{ Key.Keypad4, LocaleKeys.KeyKeypad4 },
|
||||||
|
{ Key.Keypad5, LocaleKeys.KeyKeypad5 },
|
||||||
|
{ Key.Keypad6, LocaleKeys.KeyKeypad6 },
|
||||||
|
{ Key.Keypad7, LocaleKeys.KeyKeypad7 },
|
||||||
|
{ Key.Keypad8, LocaleKeys.KeyKeypad8 },
|
||||||
|
{ Key.Keypad9, LocaleKeys.KeyKeypad9 },
|
||||||
|
{ Key.KeypadDivide, LocaleKeys.KeyKeypadDivide },
|
||||||
|
{ Key.KeypadMultiply, LocaleKeys.KeyKeypadMultiply },
|
||||||
|
{ Key.KeypadSubtract, LocaleKeys.KeyKeypadSubtract },
|
||||||
|
{ Key.KeypadAdd, LocaleKeys.KeyKeypadAdd },
|
||||||
|
{ Key.KeypadDecimal, LocaleKeys.KeyKeypadDecimal },
|
||||||
|
{ Key.KeypadEnter, LocaleKeys.KeyKeypadEnter },
|
||||||
|
{ Key.Number0, LocaleKeys.KeyNumber0 },
|
||||||
|
{ Key.Number1, LocaleKeys.KeyNumber1 },
|
||||||
|
{ Key.Number2, LocaleKeys.KeyNumber2 },
|
||||||
|
{ Key.Number3, LocaleKeys.KeyNumber3 },
|
||||||
|
{ Key.Number4, LocaleKeys.KeyNumber4 },
|
||||||
|
{ Key.Number5, LocaleKeys.KeyNumber5 },
|
||||||
|
{ Key.Number6, LocaleKeys.KeyNumber6 },
|
||||||
|
{ Key.Number7, LocaleKeys.KeyNumber7 },
|
||||||
|
{ Key.Number8, LocaleKeys.KeyNumber8 },
|
||||||
|
{ Key.Number9, LocaleKeys.KeyNumber9 },
|
||||||
|
{ Key.Tilde, LocaleKeys.KeyTilde },
|
||||||
|
{ Key.Grave, LocaleKeys.KeyGrave },
|
||||||
|
{ Key.Minus, LocaleKeys.KeyMinus },
|
||||||
|
{ Key.Plus, LocaleKeys.KeyPlus },
|
||||||
|
{ Key.BracketLeft, LocaleKeys.KeyBracketLeft },
|
||||||
|
{ Key.BracketRight, LocaleKeys.KeyBracketRight },
|
||||||
|
{ Key.Semicolon, LocaleKeys.KeySemicolon },
|
||||||
|
{ Key.Quote, LocaleKeys.KeyQuote },
|
||||||
|
{ Key.Comma, LocaleKeys.KeyComma },
|
||||||
|
{ Key.Period, LocaleKeys.KeyPeriod },
|
||||||
|
{ Key.Slash, LocaleKeys.KeySlash },
|
||||||
|
{ Key.BackSlash, LocaleKeys.KeyBackSlash },
|
||||||
|
{ Key.Unbound, LocaleKeys.KeyUnbound },
|
||||||
|
};
|
||||||
|
|
||||||
private static readonly Dictionary<GamepadInputId, LocaleKeys> _gamepadInputIdMap = new()
|
private static readonly Dictionary<GamepadInputId, LocaleKeys> _gamepadInputIdMap = new()
|
||||||
{
|
{
|
||||||
{ GamepadInputId.LeftStick, LocaleKeys.GamepadLeftStick },
|
{ GamepadInputId.LeftStick, LocaleKeys.GamepadLeftStick },
|
||||||
@@ -38,38 +110,49 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
{ GamepadInputId.SingleRightTrigger0, LocaleKeys.GamepadSingleRightTrigger0},
|
{ GamepadInputId.SingleRightTrigger0, LocaleKeys.GamepadSingleRightTrigger0},
|
||||||
{ GamepadInputId.SingleLeftTrigger1, LocaleKeys.GamepadSingleLeftTrigger1},
|
{ GamepadInputId.SingleLeftTrigger1, LocaleKeys.GamepadSingleLeftTrigger1},
|
||||||
{ GamepadInputId.SingleRightTrigger1, LocaleKeys.GamepadSingleRightTrigger1},
|
{ GamepadInputId.SingleRightTrigger1, LocaleKeys.GamepadSingleRightTrigger1},
|
||||||
{ GamepadInputId.Unbound, LocaleKeys.KeyboardLayout_KeyUnbound},
|
{ GamepadInputId.Unbound, LocaleKeys.KeyUnbound},
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly Dictionary<StickInputId, LocaleKeys> _stickInputIdMap = new()
|
private static readonly Dictionary<StickInputId, LocaleKeys> _stickInputIdMap = new()
|
||||||
{
|
{
|
||||||
{ StickInputId.Left, LocaleKeys.StickLeft},
|
{ StickInputId.Left, LocaleKeys.StickLeft},
|
||||||
{ StickInputId.Right, LocaleKeys.StickRight},
|
{ StickInputId.Right, LocaleKeys.StickRight},
|
||||||
{ StickInputId.Unbound, LocaleKeys.KeyboardLayout_KeyUnbound},
|
{ StickInputId.Unbound, LocaleKeys.KeyUnbound},
|
||||||
};
|
};
|
||||||
|
|
||||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
{
|
{
|
||||||
string keyString = string.Empty;
|
string keyString = string.Empty;
|
||||||
|
LocaleKeys localeKey;
|
||||||
|
|
||||||
switch (value)
|
switch (value)
|
||||||
{
|
{
|
||||||
case Key key:
|
case Key key:
|
||||||
if (KeyboardLayoutLocaleHelper.TryGetSemanticLabel(key, out string localizedKeyLabel))
|
if (_keysMap.TryGetValue(key, out localeKey))
|
||||||
{
|
{
|
||||||
keyString = localizedKeyLabel;
|
if (OperatingSystem.IsMacOS())
|
||||||
|
{
|
||||||
|
localeKey = localeKey switch
|
||||||
|
{
|
||||||
|
LocaleKeys.KeyControlLeft => LocaleKeys.KeyMacControlLeft,
|
||||||
|
LocaleKeys.KeyControlRight => LocaleKeys.KeyMacControlRight,
|
||||||
|
LocaleKeys.KeyAltLeft => LocaleKeys.KeyMacAltLeft,
|
||||||
|
LocaleKeys.KeyAltRight => LocaleKeys.KeyMacAltRight,
|
||||||
|
LocaleKeys.KeyWinLeft => LocaleKeys.KeyMacWinLeft,
|
||||||
|
LocaleKeys.KeyWinRight => LocaleKeys.KeyMacWinRight,
|
||||||
|
_ => localeKey
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
keyString = LocaleManager.Instance[localeKey];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
keyString = key.ToString();
|
keyString = key.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
|
||||||
case PhysicalKey physicalKey:
|
|
||||||
keyString = PhysicalKeyLabelHelper.GetDisplayString(physicalKey);
|
|
||||||
break;
|
break;
|
||||||
case GamepadInputId gamepadInputId:
|
case GamepadInputId gamepadInputId:
|
||||||
LocaleKeys localeKey;
|
|
||||||
if (_gamepadInputIdMap.TryGetValue(gamepadInputId, out localeKey))
|
if (_gamepadInputIdMap.TryGetValue(gamepadInputId, out localeKey))
|
||||||
{
|
{
|
||||||
keyString = LocaleManager.Instance[localeKey];
|
keyString = LocaleManager.Instance[localeKey];
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user