mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-05-07 22:05:47 +00:00
Compare commits
97 Commits
Canary-1.3
...
d2b2d65061
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
ecd1c1240c | ||
|
|
3ad4d4a692 | ||
|
|
6fe7fb8dcb | ||
|
|
fc357d3ba4 | ||
|
|
32ee806070 | ||
|
|
4e81a4c2f4 | ||
|
|
9cae62096a | ||
|
|
648b609ebb | ||
|
|
5ae86fc493 | ||
|
|
6f90e47a73 | ||
|
|
ac5f9857e2 | ||
|
|
4b42087bd4 | ||
|
|
80cbf5d1fc | ||
|
|
cc6d2dc162 | ||
|
|
4ebc318da5 | ||
|
|
00dad0a5e2 | ||
|
|
b70e2e44cb | ||
|
|
012d1d6886 | ||
|
|
d1205dc95d | ||
|
|
6f95172bb6 | ||
|
|
8208d43d9e | ||
|
|
1260f93aaf | ||
|
|
1b3bf1473d | ||
|
|
081cdcab0c | ||
|
|
922775664c | ||
|
|
478b66fd49 | ||
|
|
a16a072155 | ||
|
|
a4a0fcd4da | ||
|
|
cc5b60bbca | ||
|
|
5ed94c365b | ||
|
|
fef93a453a | ||
|
|
82074eb191 | ||
|
|
bd388cf4f9 | ||
|
|
d271abe19a | ||
|
|
c154f66f26 | ||
|
|
f556e8b8fb | ||
|
|
99feaafbe6 |
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:
|
||||
- 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':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'src/Ryujinx.Graphics.OpenGL/**'
|
||||
@@ -18,17 +22,17 @@ gpu:
|
||||
- changed-files:
|
||||
- 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:
|
||||
- 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:
|
||||
- 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:
|
||||
- changed-files:
|
||||
@@ -36,7 +40,7 @@ kernel:
|
||||
|
||||
infra:
|
||||
- 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:
|
||||
- changed-files:
|
||||
@@ -44,4 +48,4 @@ documentation:
|
||||
|
||||
ldn:
|
||||
- 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
244
.forgejo/workflows/build.yml
Normal file
244
.forgejo/workflows/build.yml
Normal file
@@ -0,0 +1,244 @@
|
||||
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/catthehacker/ubuntu:act-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
|
||||
|
||||
- uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Install GLI
|
||||
uses: actions/setup-gli@v1
|
||||
with:
|
||||
token: ${{ secrets.SETUP_GLI_TOKEN }}
|
||||
|
||||
- name: Install 7zip
|
||||
run: |
|
||||
sudo apt update && sudo apt install -y 7zip
|
||||
|
||||
- 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 }}"
|
||||
|
||||
sudo apt update && sudo apt install -y zsync desktop-file-utils appstream libfuse2t64
|
||||
|
||||
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
|
||||
|
||||
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: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
matrix:
|
||||
configuration: [ Release ]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-dotnet@v5
|
||||
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 GLI
|
||||
uses: actions/setup-gli@v1
|
||||
with:
|
||||
token: ${{ secrets.SETUP_GLI_TOKEN }}
|
||||
|
||||
- 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'
|
||||
|
||||
post_comment:
|
||||
name: Post comment linking uploaded artifacts
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
- build_macos
|
||||
steps:
|
||||
- uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const forgejo = getOctokit(process.env.FORGEJO_TOKEN, {
|
||||
baseUrl: 'https://git.ryujinx.app/api/v1'
|
||||
});
|
||||
|
||||
const {owner, repo} = context.repo;
|
||||
const run_id = ${{ forgejo.run_number }};
|
||||
|
||||
const issue_number = ${{ forgejo.event.pull_request.number }};
|
||||
core.info(`Using pull request ${issue_number}`);
|
||||
|
||||
const {data: {artifacts}} = await forgejo.rest.actions.listWorkflowRunArtifacts({owner, repo, run_id});
|
||||
if (artifacts == undefined || !artifacts.length) {
|
||||
return core.error(`No artifacts found`);
|
||||
}
|
||||
let body = `Download the artifacts for this pull request:\n`;
|
||||
for (const art of artifacts) {
|
||||
const url = `https://git.ryujinx.app/api/v1/repos/${owner}/${repo}/actions/artifacts/${art.id}/zip`;
|
||||
body += `\n* [${art.name}](${url})`;
|
||||
}
|
||||
|
||||
const {data: comments} = await forgejo.rest.issues.listComments({repo, owner, issue_number});
|
||||
const existing_comment = comments.find((c) => c.user.login === 'forgejo-actions');
|
||||
if (existing_comment) {
|
||||
core.info(`Updating comment ${existing_comment.id}`);
|
||||
await forgejo.rest.issues.updateComment({repo, owner, comment_id: existing_comment.id, body});
|
||||
} else {
|
||||
core.info(`Creating a comment`);
|
||||
await forgejo.rest.issues.createComment({repo, owner, issue_number, body});
|
||||
}
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/**'
|
||||
- '.forgejo/**'
|
||||
- 'docs/**'
|
||||
- 'assets/**'
|
||||
- '*.yml'
|
||||
@@ -25,44 +25,41 @@ env:
|
||||
jobs:
|
||||
release:
|
||||
name: Release for ${{ matrix.platform.name }}
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
runs-on: docker
|
||||
container:
|
||||
image: ${{ matrix.platform.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- { name: win-x64, os: ubuntu-latest, zip_os_name: win_x64 }
|
||||
#- { name: win-arm64, os: ubuntu-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: win-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_x64 }
|
||||
#- { name: win-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_arm64 }
|
||||
- { name: linux-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_x64 }
|
||||
- { name: linux-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_arm64 }
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-dotnet@v4
|
||||
- uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Overwrite csc problem matcher
|
||||
run: echo "::add-matcher::.github/csc.json"
|
||||
run: echo "::add-matcher::.forgejo/csc.json"
|
||||
|
||||
- name: Install GLI
|
||||
uses: actions/setup-gli@v1
|
||||
with:
|
||||
token: ${{ secrets.SETUP_GLI_TOKEN }}
|
||||
|
||||
- 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'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
sudo apt update && sudo apt install -y 7zip
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=$(gli get-next-version -c Canary -R)" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $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)" >> $FORGEJO_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Configure for release
|
||||
@@ -87,12 +84,9 @@ jobs:
|
||||
pushd publish
|
||||
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 }}.7z ../publish
|
||||
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
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Packing Linux builds
|
||||
if: contains(matrix.platform.name, 'linux')
|
||||
@@ -101,9 +95,8 @@ jobs:
|
||||
rm libarmeilleure-jitsupport.dylib
|
||||
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 -cJvf ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.xz ../publish
|
||||
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
|
||||
|
||||
- name: Build AppImage (Linux)
|
||||
@@ -112,7 +105,7 @@ jobs:
|
||||
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
|
||||
PLATFORM_NAME="${{ matrix.platform.name }}"
|
||||
|
||||
sudo apt install -y zsync desktop-file-utils appstream
|
||||
sudo apt update && sudo apt install -y zsync desktop-file-utils appstream libfuse2t64
|
||||
|
||||
mkdir -p tools
|
||||
export PATH="$PATH:$(readlink -f tools)"
|
||||
@@ -139,17 +132,28 @@ jobs:
|
||||
pushd publish_appimage
|
||||
mv Ryujinx.AppImage ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||
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
|
||||
|
||||
- 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:
|
||||
name: Release MacOS universal
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: docker
|
||||
container:
|
||||
image: ghcr.io/catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-dotnet@v4
|
||||
- uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
@@ -159,33 +163,24 @@ jobs:
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 17
|
||||
|
||||
- name: Install gli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Install GLI
|
||||
uses: actions/setup-gli@v1
|
||||
with:
|
||||
token: ${{ secrets.SETUP_GLI_TOKEN }}
|
||||
|
||||
- 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'
|
||||
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
|
||||
mv rcodesign $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
mv rcodesign /usr/bin/rcodesign
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=$(gli get-next-version -c Canary -R)" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $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)" >> $FORGEJO_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Configure for release
|
||||
@@ -201,46 +196,53 @@ jobs:
|
||||
- name: Publish macOS Ryujinx
|
||||
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
|
||||
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 GitLab Release
|
||||
runs-on: ubuntu-24.04
|
||||
- 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: |-
|
||||
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/catthehacker/ubuntu:act-latest
|
||||
needs:
|
||||
- macos_release
|
||||
- release
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install gli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Install GLI
|
||||
uses: actions/setup-gli@v1
|
||||
with:
|
||||
token: ${{ secrets.SETUP_GLI_TOKEN }}
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=$(gli get-next-version -c Canary -R)" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $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)" >> $FORGEJO_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Create tag
|
||||
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 }}
|
||||
|
||||
- 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 }})"
|
||||
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: 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
|
||||
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
|
||||
run: |
|
||||
@@ -5,24 +5,22 @@ on:
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
# Grab sources to get latest labeler.yml
|
||||
- name: Fetch sources
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
# Ensure we pin the source origin as pull_request_target run under forks.
|
||||
fetch-depth: 0
|
||||
repository: GreemDev/Ryujinx
|
||||
repository: projects/Ryubing
|
||||
ref: master
|
||||
|
||||
- name: Update labels based on changes
|
||||
uses: actions/labeler@v5
|
||||
uses: actions/labeler@v6
|
||||
with:
|
||||
repo-token: ${{ secrets.LABELER_TOKEN }}
|
||||
configuration-path: .forgejo/labeler.yml
|
||||
sync-labels: true
|
||||
dot: true
|
||||
@@ -19,18 +19,20 @@ env:
|
||||
jobs:
|
||||
release:
|
||||
name: Release for ${{ matrix.platform.name }}
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
runs-on: docker
|
||||
container:
|
||||
image: ${{ matrix.platform.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- { name: win-x64, os: ubuntu-latest, zip_os_name: win_x64 }
|
||||
#- { name: win-arm64, os: ubuntu-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: win-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_x64 }
|
||||
#- { name: win-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_arm64 }
|
||||
- { name: linux-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_x64 }
|
||||
- { name: linux-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_arm64 }
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-dotnet@v4
|
||||
- uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
@@ -41,26 +43,21 @@ jobs:
|
||||
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'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Install GLI
|
||||
uses: actions/setup-gli@v1
|
||||
with:
|
||||
token: ${{ secrets.SETUP_GLI_TOKEN }}
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
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
|
||||
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
|
||||
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $FORGEJO_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Configure for release
|
||||
@@ -84,12 +81,9 @@ jobs:
|
||||
pushd publish
|
||||
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 }}.7z ../publish
|
||||
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
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Packing Linux builds
|
||||
if: contains(matrix.platform.name, 'linux')
|
||||
@@ -98,12 +92,9 @@ jobs:
|
||||
rm libarmeilleure-jitsupport.dylib
|
||||
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 -cJvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.xz ../publish
|
||||
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
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build AppImage (Linux)
|
||||
if: contains(matrix.platform.name, 'linux')
|
||||
@@ -138,17 +129,25 @@ jobs:
|
||||
pushd publish_appimage
|
||||
mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||
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
|
||||
|
||||
- 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:
|
||||
name: Release MacOS universal
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-dotnet@v4
|
||||
- uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
@@ -158,37 +157,28 @@ jobs:
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 17
|
||||
|
||||
- name: Install gli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Install GLI
|
||||
uses: actions/setup-gli@v1
|
||||
with:
|
||||
token: ${{ secrets.SETUP_GLI_TOKEN }}
|
||||
|
||||
- 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'
|
||||
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
|
||||
mv rcodesign $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
mv rcodesign /usr/bin/rcodesign
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
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
|
||||
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
|
||||
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $FORGEJO_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Configure for release
|
||||
@@ -201,49 +191,47 @@ jobs:
|
||||
|
||||
- name: Publish macOS Ryujinx
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
create_gitlab_release:
|
||||
name: Create GitLab Release
|
||||
runs-on: ubuntu-24.04
|
||||
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
|
||||
|
||||
- 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: |-
|
||||
publish_ava/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz
|
||||
|
||||
post_ci:
|
||||
name: Post-CI Steps
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- macos_release
|
||||
- release
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install gli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Install GLI
|
||||
uses: actions/setup-gli@v1
|
||||
with:
|
||||
token: ${{ secrets.SETUP_GLI_TOKEN }}
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
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
|
||||
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
|
||||
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
echo "commit_message=$(git log -1 --pretty=%B)" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $FORGEJO_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
|
||||
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
|
||||
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
|
||||
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,60 +3,65 @@
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia" Version="11.3.6" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.6" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.6" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.6" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.6" />
|
||||
<PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.6.2" />
|
||||
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.6.2" />
|
||||
<PackageVersion Include="Avalonia" Version="11.3.14" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.14" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.14" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.14" />
|
||||
<PackageVersion Include="SharpCompress" Version="0.47.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.Utilities.Core" Version="17.12.6" />
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.12.50" />
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageVersion Include="Projektanker.Icons.Avalonia" 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="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="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.2" />
|
||||
<PackageVersion Include="Concentus" Version="2.2.2" />
|
||||
<PackageVersion Include="DiscordRichPresence" Version="1.6.1.70" />
|
||||
<PackageVersion Include="DynamicData" Version="9.4.1" />
|
||||
<PackageVersion Include="FluentAvaloniaUI.NoAnim" Version="2.4.0-build3" />
|
||||
<PackageVersion Include="DynamicData" Version="9.4.31" />
|
||||
<PackageVersion Include="FluentAvaloniaUI" Version="2.5.1" />
|
||||
<PackageVersion Include="Humanizer" Version="2.14.1" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.3.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.18.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
|
||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||
<PackageVersion Include="NetCoreServer" Version="8.0.7" />
|
||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||
<PackageVersion Include="OpenTK.Core" Version="4.8.2" />
|
||||
<PackageVersion Include="OpenTK.Graphics" Version="4.8.2" />
|
||||
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.2" />
|
||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.2" />
|
||||
<PackageVersion Include="NUnit" Version="3.14.0" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageVersion Include="OpenTK.Core" Version="4.9.4" />
|
||||
<PackageVersion Include="OpenTK.Graphics" Version="4.9.4" />
|
||||
<!-- OpenTk.Audio.OpenAL has moved to OpenTk.Audio -->
|
||||
<!--<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="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" Version="6.1.2-build3" />
|
||||
<!-- Ryujinx.Audio.OpenAL.Dependencies is from the original project, last updated 12/30/20 -->
|
||||
<!--<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.LibHac" Version="0.21.0-alpha.126" />
|
||||
<PackageVersion Include="Ryujinx.UpdateClient" Version="1.0.44" />
|
||||
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="1.0.44" />
|
||||
<PackageVersion Include="Gommon" Version="2.8.0.1" />
|
||||
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.133" />
|
||||
<PackageVersion Include="Ryujinx.UpdateClient" Version="2.0.6" />
|
||||
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="2.0.6" />
|
||||
<PackageVersion Include="Gommon" Version="2.8.1.2" />
|
||||
<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="SharpZipLib" Version="1.4.2" />
|
||||
<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.KHR" Version="2.22.0" />
|
||||
<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="System.IO.Hashing" Version="9.0.2" />
|
||||
<PackageVersion Include="System.Management" Version="9.0.2" />
|
||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||
<PackageVersion Include="System.IO.Hashing" Version="9.0.15" />
|
||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.1.3" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
26
README.md
26
README.md
@@ -5,10 +5,10 @@
|
||||
</td>
|
||||
<td align="center" width="75%">
|
||||
|
||||
# Ryujinx
|
||||
<h1 class="ryu-gradient-text">Ryujinx</h1>
|
||||
|
||||
[](https://update.ryujinx.app/latest/stable)
|
||||
[](https://update.ryujinx.app/latest/canary)
|
||||
[](https://update.ryujinx.app/latest/stable)
|
||||
[](https://update.ryujinx.app/latest/canary)
|
||||
<br>
|
||||
<a href="https://discord.gg/PEuzjrFXUA">
|
||||
<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#.
|
||||
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.
|
||||
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 />
|
||||
</p>
|
||||
<p align="center">
|
||||
@@ -31,11 +31,11 @@
|
||||
<br>
|
||||
This is not a Ryujinx revival project. This is not a Phoenix project.
|
||||
<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 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>
|
||||
|
||||
## 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**.
|
||||
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.
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
@@ -105,13 +105,13 @@ If you are planning to contribute or just want to learn more about this project
|
||||
|
||||
## 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.
|
||||
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
|
||||
|
||||
- [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.
|
||||
- [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.
|
||||
|
||||
20
Ryujinx.sln
20
Ryujinx.sln
@@ -47,6 +47,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Vic", "src
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Video", "src\Ryujinx.Graphics.Video\Ryujinx.Graphics.Video.csproj", "{FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.Apple", "src\Ryujinx.Audio.Backends.Apple\Ryujinx.Audio.Backends.Apple.csproj", "{AC26EFF0-8593-4184-9A09-98E37EFFB32E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.SDL3", "src\Ryujinx.Audio.Backends.SDL3\Ryujinx.Audio.Backends.SDL3.csproj", "{988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.OpenAL", "src\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj", "{0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}"
|
||||
@@ -84,11 +86,11 @@ EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
.github\workflows\build.yml = .github\workflows\build.yml
|
||||
.github\workflows\canary.yml = .github\workflows\canary.yml
|
||||
.forgejo\workflows\build.yml = .forgejo\workflows\build.yml
|
||||
.forgejo\workflows\canary.yml = .forgejo\workflows\canary.yml
|
||||
Directory.Packages.props = Directory.Packages.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
|
||||
EndProjectSection
|
||||
EndProject
|
||||
@@ -569,6 +571,18 @@ Global
|
||||
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x64.Build.0 = Release|Any CPU
|
||||
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x86.ActiveCfg = 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.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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
"ID": "MenuBarActions_StartCapture",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "RenderDoc Frame-Aufnahme starten",
|
||||
"el_GR": "",
|
||||
"en_US": "Start RenderDoc Frame Capture",
|
||||
"es_ES": "",
|
||||
"fr_FR": "",
|
||||
"es_ES": "Iniciar una captura de fotograma de RenderDoc",
|
||||
"fr_FR": "Démarrer une capture de trame RenderDoc",
|
||||
"he_IL": "",
|
||||
"it_IT": "",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"ko_KR": "RenderDoc 프레임 캡처 시작",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
@@ -21,23 +21,23 @@
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
"zh_CN": "启动 RenderDoc 帧捕获",
|
||||
"zh_TW": "啟動 RenderDoc 畫格擷取"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "MenuBarActions_EndCapture",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "RenderDoc Frame-Aufnahme beenden",
|
||||
"el_GR": "",
|
||||
"en_US": "End RenderDoc Frame Capture",
|
||||
"es_ES": "",
|
||||
"fr_FR": "",
|
||||
"es_ES": "Detener la captura de fotograma de RenderDoc",
|
||||
"fr_FR": "Arrêter la capture de trame RenderDoc",
|
||||
"he_IL": "",
|
||||
"it_IT": "",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"ko_KR": "RenderDoc 프레임 캡처 종료",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
@@ -46,23 +46,23 @@
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
"zh_CN": "结束 RenderDoc 帧捕获",
|
||||
"zh_TW": "停止 RenderDoc 畫格擷取"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "MenuBarActions_DiscardCapture",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "RenderDoc Frame-Aufnahme verwerfen",
|
||||
"el_GR": "",
|
||||
"en_US": "Discard RenderDoc Frame Capture",
|
||||
"es_ES": "",
|
||||
"fr_FR": "",
|
||||
"es_ES": "Descartar la captura de fotograma de RenderDoc",
|
||||
"fr_FR": "Supprimer la capture de trame RenderDoc",
|
||||
"he_IL": "",
|
||||
"it_IT": "",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"ko_KR": "RenderDoc 프레임 캡처 폐기",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
@@ -71,23 +71,23 @@
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
"zh_CN": "丢弃 RenderDoc 帧捕获",
|
||||
"zh_TW": "捨棄 RenderDoc 畫格擷取"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "MenuBarActions_DiscardCapture_ToolTip",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Beendet die jetzige RenderDoc Frame-Aufnahme, verwirft sofort das Ergebnis.",
|
||||
"el_GR": "",
|
||||
"en_US": "Ends the currently active RenderDoc Frame Capture, immediately discarding its result.",
|
||||
"es_ES": "",
|
||||
"fr_FR": "",
|
||||
"es_ES": "Finaliza la captura de fotograma de RenderDoc actualmente activa y descarta inmediatamente su resultado.",
|
||||
"fr_FR": "Met fin à la capture de trame RenderDoc en cours, en supprimant immédiatement son résultat.",
|
||||
"he_IL": "",
|
||||
"it_IT": "",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"ko_KR": "현재 활성화된 RenderDoc 프레임 캡처를 종료하고 결과를 즉시 폐기합니다.",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
@@ -96,8 +96,8 @@
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
"zh_CN": "结束当前正在进行的 RenderDoc 帧捕获,并立即丢弃其结果。",
|
||||
"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"
|
||||
|
||||
appimagetool -n --comp zstd --mksquashfs-opt -Xcompression-level --mksquashfs-opt 21 \
|
||||
AppDir "$OUTDIR"/Ryujinx.AppImage
|
||||
appimagetool --appimage-extract-and-run -n --comp zstd --mksquashfs-opt -Xcompression-level --mksquashfs-opt 21 \
|
||||
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"
|
||||
@@ -2050,7 +2050,9 @@
|
||||
010003C00B868000,"Ninjin: Clash of Carrots",online-broken,playable,2024-07-10 05:12:26
|
||||
0100746010E4C000,"NinNinDays",,playable,2022-11-20 15:17:29
|
||||
0100C9A00ECE6000,"Nintendo 64™ – Nintendo Switch Online",gpu;vulkan,ingame,2024-04-23 20:21:07
|
||||
010057D00ECE4000,"Nintendo 64™ – Nintendo Switch Online",gpu;vulkan,ingame,2024-04-23 20:21:07
|
||||
0100e0601c632000,"Nintendo 64™ – Nintendo Switch Online: MATURE 17+",,ingame,2025-02-03 22:27:00
|
||||
010037A0170D2000,"NINTENDO 64™ – Nintendo Switch Online 18+",,ingame,2025-02-03 22:27:00
|
||||
0100D870045B6000,"Nintendo Entertainment System™ - Nintendo Switch Online",online,playable,2022-07-01 15:45:06
|
||||
0100C4B0034B2000,"Nintendo Labo Toy-Con 01 Variety Kit",gpu,ingame,2022-08-07 12:56:07
|
||||
01001E9003502000,"Nintendo Labo Toy-Con 03 Vehicle Kit",services;crash,menus,2022-08-03 17:20:11
|
||||
@@ -2638,6 +2640,7 @@
|
||||
0100B16009C10000,"SINNER: Sacrifice for Redemption",nvdec;UE4;vulkan-backend-bug,playable,2022-08-12 20:37:33
|
||||
0100E9201410E000,"Sir Lovelot",,playable,2021-04-05 16:21:46
|
||||
0100134011E32000,"Skate City",,playable,2022-11-04 11:37:39
|
||||
0100a8501b66e000,"Skateboard Drifting with Maxwell Cat: The Game Simulator",,playable,2026-02-17 19:05:00
|
||||
0100B2F008BD8000,"Skee-Ball",,playable,2020-11-16 04:44:07
|
||||
01001A900F862000,"Skelattack",,playable,2021-06-09 15:26:26
|
||||
01008E700F952000,"Skelittle: A Giant Party!",,playable,2021-06-09 19:08:34
|
||||
@@ -3307,6 +3310,7 @@
|
||||
0100AFA011068000,"Voxel Pirates",,playable,2022-09-28 22:55:02
|
||||
0100BFB00D1F4000,"Voxel Sword",,playable,2022-08-30 14:57:27
|
||||
01004E90028A2000,"Vroom in the night sky",Needs Update;vulkan-backend-bug,playable,2023-02-20 02:32:29
|
||||
0100BFC01D976000,"Virtual Boy – Nintendo Classics",services,nothing,2026-02-17 11:26:59
|
||||
0100C7C00AE6C000,"VSR: Void Space Racing",,playable,2021-01-27 14:08:59
|
||||
0100B130119D0000,"Waifu Uncovered",crash,ingame,2023-02-27 01:17:46
|
||||
0100E29010A4A000,"Wanba Warriors",,playable,2020-10-04 17:56:22
|
||||
|
||||
|
@@ -5,8 +5,7 @@
|
||||
<clear />
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||
<!-- Only needed when using pre-release versions of Ryujinx.LibHac. -->
|
||||
<add key="LibHacAlpha" value="https://git.ryujinx.app/api/v4/projects/17/packages/nuget/index.json" />
|
||||
<add key="Ryujinx.UpdateClient" value="https://git.ryujinx.app/api/v4/projects/71/packages/nuget/index.json" />
|
||||
<add key="LibHacAlpha" value="https://git.ryujinx.app/api/packages/projects/nuget/index.json" />
|
||||
</packageSources>
|
||||
<packageSourceMapping>
|
||||
<!-- key value for <packageSource> should match key values from <packageSources> element -->
|
||||
@@ -14,10 +13,6 @@
|
||||
<packageSource key="nuget.org">
|
||||
<package pattern="*" />
|
||||
</packageSource>
|
||||
<packageSource key="Ryujinx.UpdateClient">
|
||||
<package pattern="Ryujinx.UpdateClient" />
|
||||
<package pattern="Ryujinx.Systems.Update.Common" />
|
||||
</packageSource>
|
||||
<packageSource key="LibHacAlpha">
|
||||
<package pattern="Ryujinx.LibHac" />
|
||||
</packageSource>
|
||||
|
||||
@@ -168,7 +168,7 @@ namespace ARMeilleure.Common
|
||||
{
|
||||
_allocated.Dispose();
|
||||
|
||||
foreach (IntPtr page in _pages.Values)
|
||||
foreach (nint page in _pages.Values)
|
||||
{
|
||||
NativeAllocator.Instance.Free((void*)page);
|
||||
}
|
||||
|
||||
16
src/Ryujinx.Audio.Backends.Apple/AppleAudioBuffer.cs
Normal file
16
src/Ryujinx.Audio.Backends.Apple/AppleAudioBuffer.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace Ryujinx.Audio.Backends.Apple
|
||||
{
|
||||
class AppleAudioBuffer
|
||||
{
|
||||
public readonly ulong DriverIdentifier;
|
||||
public readonly ulong SampleCount;
|
||||
public ulong SamplePlayed;
|
||||
|
||||
public AppleAudioBuffer(ulong driverIdentifier, ulong sampleCount)
|
||||
{
|
||||
DriverIdentifier = driverIdentifier;
|
||||
SampleCount = sampleCount;
|
||||
SamplePlayed = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
196
src/Ryujinx.Audio.Backends.Apple/AppleHardwareDeviceDriver.cs
Normal file
196
src/Ryujinx.Audio.Backends.Apple/AppleHardwareDeviceDriver.cs
Normal file
@@ -0,0 +1,196 @@
|
||||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Runtime.Versioning;
|
||||
using Ryujinx.Audio.Backends.Apple.Native;
|
||||
using static Ryujinx.Audio.Backends.Apple.Native.AudioToolbox;
|
||||
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.Apple
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
[SupportedOSPlatform("ios")]
|
||||
public sealed class AppleHardwareDeviceDriver : IHardwareDeviceDriver
|
||||
{
|
||||
private readonly ManualResetEvent _updateRequiredEvent;
|
||||
private readonly ManualResetEvent _pauseEvent;
|
||||
private readonly ConcurrentDictionary<AppleHardwareDeviceSession, byte> _sessions;
|
||||
private readonly bool _supportSurroundConfiguration;
|
||||
|
||||
public float Volume { get; set; }
|
||||
|
||||
public AppleHardwareDeviceDriver()
|
||||
{
|
||||
_updateRequiredEvent = new ManualResetEvent(false);
|
||||
_pauseEvent = new ManualResetEvent(true);
|
||||
_sessions = new ConcurrentDictionary<AppleHardwareDeviceSession, byte>();
|
||||
|
||||
_supportSurroundConfiguration = TestSurroundSupport();
|
||||
|
||||
Volume = 1f;
|
||||
}
|
||||
|
||||
private bool TestSurroundSupport()
|
||||
{
|
||||
try
|
||||
{
|
||||
AudioStreamBasicDescription format =
|
||||
GetAudioFormat(SampleFormat.PcmFloat, Constants.TargetSampleRate, 6);
|
||||
|
||||
int result = AudioQueueNewOutput(
|
||||
ref format,
|
||||
nint.Zero,
|
||||
nint.Zero,
|
||||
nint.Zero,
|
||||
nint.Zero,
|
||||
0,
|
||||
out nint testQueue);
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
AudioChannelLayout layout = new AudioChannelLayout
|
||||
{
|
||||
AudioChannelLayoutTag = kAudioChannelLayoutTag_MPEG_5_1_A,
|
||||
AudioChannelBitmap = 0,
|
||||
NumberChannelDescriptions = 0
|
||||
};
|
||||
|
||||
int layoutResult = AudioQueueSetProperty(
|
||||
testQueue,
|
||||
kAudioQueueProperty_ChannelLayout,
|
||||
ref layout,
|
||||
(uint)Marshal.SizeOf<AudioChannelLayout>());
|
||||
|
||||
if (layoutResult == 0)
|
||||
{
|
||||
AudioQueueDispose(testQueue, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioQueueDispose(testQueue, true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsSupported => OperatingSystem.IsMacOSVersionAtLeast(10, 5);
|
||||
|
||||
public ManualResetEvent GetUpdateRequiredEvent()
|
||||
=> _updateRequiredEvent;
|
||||
|
||||
public ManualResetEvent GetPauseEvent()
|
||||
=> _pauseEvent;
|
||||
|
||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager,
|
||||
SampleFormat sampleFormat, uint sampleRate, uint channelCount)
|
||||
{
|
||||
if (channelCount == 0)
|
||||
{
|
||||
channelCount = 2;
|
||||
}
|
||||
|
||||
if (sampleRate == 0)
|
||||
{
|
||||
sampleRate = Constants.TargetSampleRate;
|
||||
}
|
||||
|
||||
if (direction != Direction.Output)
|
||||
{
|
||||
throw new NotImplementedException("Input direction is currently not implemented on Apple backend!");
|
||||
}
|
||||
|
||||
AppleHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
|
||||
|
||||
_sessions.TryAdd(session, 0);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
internal bool Unregister(AppleHardwareDeviceSession session)
|
||||
=> _sessions.TryRemove(session, out _);
|
||||
|
||||
internal static AudioStreamBasicDescription GetAudioFormat(SampleFormat sampleFormat, uint sampleRate,
|
||||
uint channelCount)
|
||||
{
|
||||
uint formatFlags;
|
||||
uint bitsPerChannel;
|
||||
|
||||
switch (sampleFormat)
|
||||
{
|
||||
case SampleFormat.PcmInt8:
|
||||
formatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
|
||||
bitsPerChannel = 8;
|
||||
break;
|
||||
case SampleFormat.PcmInt16:
|
||||
formatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
|
||||
bitsPerChannel = 16;
|
||||
break;
|
||||
case SampleFormat.PcmInt32:
|
||||
formatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
|
||||
bitsPerChannel = 32;
|
||||
break;
|
||||
case SampleFormat.PcmFloat:
|
||||
formatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked;
|
||||
bitsPerChannel = 32;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported sample format {sampleFormat}");
|
||||
}
|
||||
|
||||
uint bytesPerFrame = (bitsPerChannel / 8) * channelCount;
|
||||
|
||||
return new AudioStreamBasicDescription
|
||||
{
|
||||
SampleRate = sampleRate,
|
||||
FormatID = kAudioFormatLinearPCM,
|
||||
FormatFlags = formatFlags,
|
||||
BytesPerPacket = bytesPerFrame,
|
||||
FramesPerPacket = 1,
|
||||
BytesPerFrame = bytesPerFrame,
|
||||
ChannelsPerFrame = channelCount,
|
||||
BitsPerChannel = bitsPerChannel,
|
||||
Reserved = 0
|
||||
};
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
foreach (AppleHardwareDeviceSession session in _sessions.Keys)
|
||||
{
|
||||
session.Dispose();
|
||||
}
|
||||
|
||||
_pauseEvent.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public bool SupportsDirection(Direction direction)
|
||||
=> direction != Direction.Input;
|
||||
|
||||
public bool SupportsSampleRate(uint sampleRate) => true;
|
||||
|
||||
public bool SupportsSampleFormat(SampleFormat sampleFormat)
|
||||
=> sampleFormat != SampleFormat.PcmInt24;
|
||||
|
||||
public bool SupportsChannelCount(uint channelCount)
|
||||
=> channelCount != 6 || _supportSurroundConfiguration;
|
||||
}
|
||||
}
|
||||
285
src/Ryujinx.Audio.Backends.Apple/AppleHardwareDeviceSession.cs
Normal file
285
src/Ryujinx.Audio.Backends.Apple/AppleHardwareDeviceSession.cs
Normal file
@@ -0,0 +1,285 @@
|
||||
using Ryujinx.Audio.Backends.Common;
|
||||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Runtime.Versioning;
|
||||
using static Ryujinx.Audio.Backends.Apple.Native.AudioToolbox;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.Apple
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
[SupportedOSPlatform("ios")]
|
||||
class AppleHardwareDeviceSession : HardwareDeviceSessionOutputBase
|
||||
{
|
||||
private const int NumBuffers = 3;
|
||||
|
||||
private readonly AppleHardwareDeviceDriver _driver;
|
||||
private readonly ConcurrentQueue<AppleAudioBuffer> _queuedBuffers = new();
|
||||
private readonly DynamicRingBuffer _ringBuffer = new();
|
||||
private readonly ManualResetEvent _updateRequiredEvent;
|
||||
|
||||
private readonly AudioQueueOutputCallback _callbackDelegate;
|
||||
private readonly GCHandle _gcHandle;
|
||||
|
||||
private nint _audioQueue;
|
||||
private readonly nint[] _audioQueueBuffers = new nint[NumBuffers];
|
||||
private readonly int[] _bufferBytesFilled = new int[NumBuffers];
|
||||
|
||||
private readonly int _bytesPerFrame;
|
||||
|
||||
private ulong _playedSampleCount;
|
||||
private bool _started;
|
||||
private float _volume = 1f;
|
||||
|
||||
private readonly object _lock = new();
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate void AudioQueueOutputCallback(
|
||||
nint userData,
|
||||
nint audioQueue,
|
||||
nint buffer);
|
||||
|
||||
public AppleHardwareDeviceSession(
|
||||
AppleHardwareDeviceDriver driver,
|
||||
IVirtualMemoryManager memoryManager,
|
||||
SampleFormat requestedSampleFormat,
|
||||
uint requestedSampleRate,
|
||||
uint requestedChannelCount)
|
||||
: base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
|
||||
{
|
||||
_driver = driver;
|
||||
_updateRequiredEvent = driver.GetUpdateRequiredEvent();
|
||||
_callbackDelegate = OutputCallback;
|
||||
_bytesPerFrame = BackendHelper.GetSampleSize(requestedSampleFormat) * (int)requestedChannelCount;
|
||||
|
||||
_gcHandle = GCHandle.Alloc(this, GCHandleType.Normal);
|
||||
|
||||
SetupAudioQueue();
|
||||
}
|
||||
|
||||
private void SetupAudioQueue()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
AudioStreamBasicDescription format = AppleHardwareDeviceDriver.GetAudioFormat(
|
||||
RequestedSampleFormat,
|
||||
RequestedSampleRate,
|
||||
RequestedChannelCount);
|
||||
|
||||
nint callbackPtr = Marshal.GetFunctionPointerForDelegate(_callbackDelegate);
|
||||
nint userData = GCHandle.ToIntPtr(_gcHandle);
|
||||
|
||||
int result = AudioQueueNewOutput(
|
||||
ref format,
|
||||
callbackPtr,
|
||||
userData,
|
||||
nint.Zero,
|
||||
nint.Zero,
|
||||
0,
|
||||
out _audioQueue);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
throw new InvalidOperationException($"AudioQueueNewOutput failed: {result}");
|
||||
}
|
||||
|
||||
uint framesPerBuffer = RequestedSampleRate / 100;
|
||||
uint bufferSize = framesPerBuffer * (uint)_bytesPerFrame;
|
||||
|
||||
for (int i = 0; i < NumBuffers; i++)
|
||||
{
|
||||
AudioQueueAllocateBuffer(_audioQueue, bufferSize, out _audioQueueBuffers[i]);
|
||||
_bufferBytesFilled[i] = 0;
|
||||
|
||||
PrimeBuffer(_audioQueueBuffers[i], i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void PrimeBuffer(nint bufferPtr, int bufferIndex)
|
||||
{
|
||||
AudioQueueBuffer* buffer = (AudioQueueBuffer*)bufferPtr;
|
||||
|
||||
int capacityBytes = (int)buffer->AudioDataBytesCapacity;
|
||||
int framesPerBuffer = capacityBytes / _bytesPerFrame;
|
||||
|
||||
int availableFrames = _ringBuffer.Length / _bytesPerFrame;
|
||||
int framesToRead = Math.Min(availableFrames, framesPerBuffer);
|
||||
int bytesToRead = framesToRead * _bytesPerFrame;
|
||||
|
||||
Span<byte> dst = new((void*)buffer->AudioData, capacityBytes);
|
||||
dst.Clear();
|
||||
|
||||
if (bytesToRead > 0)
|
||||
{
|
||||
Span<byte> audio = dst.Slice(0, bytesToRead);
|
||||
_ringBuffer.Read(audio, 0, bytesToRead);
|
||||
ApplyVolume(buffer->AudioData, bytesToRead);
|
||||
}
|
||||
|
||||
buffer->AudioDataByteSize = (uint)capacityBytes;
|
||||
_bufferBytesFilled[bufferIndex] = bytesToRead;
|
||||
|
||||
AudioQueueEnqueueBuffer(_audioQueue, bufferPtr, 0, nint.Zero);
|
||||
}
|
||||
|
||||
private void OutputCallback(nint userData, nint audioQueue, nint bufferPtr)
|
||||
{
|
||||
if (!_started || bufferPtr == nint.Zero)
|
||||
return;
|
||||
|
||||
int bufferIndex = Array.IndexOf(_audioQueueBuffers, bufferPtr);
|
||||
if (bufferIndex < 0)
|
||||
return;
|
||||
|
||||
int bytesPlayed = _bufferBytesFilled[bufferIndex];
|
||||
if (bytesPlayed > 0)
|
||||
{
|
||||
ProcessPlayedSamples(bytesPlayed);
|
||||
}
|
||||
|
||||
PrimeBuffer(bufferPtr, bufferIndex);
|
||||
}
|
||||
|
||||
private void ProcessPlayedSamples(int bytesPlayed)
|
||||
{
|
||||
ulong samplesPlayed = GetSampleCount(bytesPlayed);
|
||||
ulong remaining = samplesPlayed;
|
||||
bool needUpdate = false;
|
||||
|
||||
while (remaining > 0 && _queuedBuffers.TryPeek(out AppleAudioBuffer buffer))
|
||||
{
|
||||
ulong needed = buffer.SampleCount - Interlocked.Read(ref buffer.SamplePlayed);
|
||||
ulong take = Math.Min(needed, remaining);
|
||||
|
||||
ulong played = Interlocked.Add(ref buffer.SamplePlayed, take);
|
||||
remaining -= take;
|
||||
|
||||
if (played == buffer.SampleCount)
|
||||
{
|
||||
_queuedBuffers.TryDequeue(out _);
|
||||
needUpdate = true;
|
||||
}
|
||||
|
||||
Interlocked.Add(ref _playedSampleCount, take);
|
||||
}
|
||||
|
||||
if (needUpdate)
|
||||
{
|
||||
_updateRequiredEvent.Set();
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void ApplyVolume(nint dataPtr, int byteSize)
|
||||
{
|
||||
float volume = Math.Clamp(_volume * _driver.Volume, 0f, 1f);
|
||||
if (volume >= 0.999f)
|
||||
return;
|
||||
|
||||
int sampleCount = byteSize / BackendHelper.GetSampleSize(RequestedSampleFormat);
|
||||
|
||||
switch (RequestedSampleFormat)
|
||||
{
|
||||
case SampleFormat.PcmInt16:
|
||||
short* s16 = (short*)dataPtr;
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
s16[i] = (short)(s16[i] * volume);
|
||||
break;
|
||||
|
||||
case SampleFormat.PcmFloat:
|
||||
float* f32 = (float*)dataPtr;
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
f32[i] *= volume;
|
||||
break;
|
||||
|
||||
case SampleFormat.PcmInt32:
|
||||
int* s32 = (int*)dataPtr;
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
s32[i] = (int)(s32[i] * volume);
|
||||
break;
|
||||
|
||||
case SampleFormat.PcmInt8:
|
||||
sbyte* s8 = (sbyte*)dataPtr;
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
s8[i] = (sbyte)(s8[i] * volume);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void QueueBuffer(AudioBuffer buffer)
|
||||
{
|
||||
_ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
|
||||
_queuedBuffers.Enqueue(new AppleAudioBuffer(buffer.DataPointer, GetSampleCount(buffer)));
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_started)
|
||||
return;
|
||||
|
||||
_started = true;
|
||||
AudioQueueStart(_audioQueue, nint.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Stop()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (!_started)
|
||||
return;
|
||||
|
||||
_started = false;
|
||||
AudioQueuePause(_audioQueue);
|
||||
}
|
||||
}
|
||||
|
||||
public override ulong GetPlayedSampleCount()
|
||||
=> Interlocked.Read(ref _playedSampleCount);
|
||||
|
||||
public override float GetVolume() => _volume;
|
||||
public override void SetVolume(float volume) => _volume = volume;
|
||||
|
||||
public override bool WasBufferFullyConsumed(AudioBuffer buffer)
|
||||
{
|
||||
if (!_queuedBuffers.TryPeek(out AppleAudioBuffer driverBuffer))
|
||||
return true;
|
||||
|
||||
return driverBuffer.DriverIdentifier != buffer.DataPointer;
|
||||
}
|
||||
|
||||
public override void PrepareToClose() { }
|
||||
public override void UnregisterBuffer(AudioBuffer buffer) { }
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Stop();
|
||||
|
||||
if (_audioQueue != nint.Zero)
|
||||
{
|
||||
AudioQueueStop(_audioQueue, true);
|
||||
AudioQueueDispose(_audioQueue, true);
|
||||
_audioQueue = nint.Zero;
|
||||
}
|
||||
|
||||
if (_gcHandle.IsAllocated)
|
||||
{
|
||||
_gcHandle.Free();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
102
src/Ryujinx.Audio.Backends.Apple/Native/AudioToolbox.cs
Normal file
102
src/Ryujinx.Audio.Backends.Apple/Native/AudioToolbox.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using System.Runtime.InteropServices;
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace Ryujinx.Audio.Backends.Apple.Native
|
||||
{
|
||||
public static partial class AudioToolbox
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AudioStreamBasicDescription
|
||||
{
|
||||
public double SampleRate;
|
||||
public uint FormatID;
|
||||
public uint FormatFlags;
|
||||
public uint BytesPerPacket;
|
||||
public uint FramesPerPacket;
|
||||
public uint BytesPerFrame;
|
||||
public uint ChannelsPerFrame;
|
||||
public uint BitsPerChannel;
|
||||
public uint Reserved;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AudioChannelLayout
|
||||
{
|
||||
public uint AudioChannelLayoutTag;
|
||||
public uint AudioChannelBitmap;
|
||||
public uint NumberChannelDescriptions;
|
||||
}
|
||||
|
||||
internal const uint kAudioFormatLinearPCM = 0x6C70636D;
|
||||
internal const uint kAudioQueueProperty_ChannelLayout = 0x6171636c;
|
||||
internal const uint kAudioChannelLayoutTag_MPEG_5_1_A = 0x650006;
|
||||
internal const uint kAudioFormatFlagIsFloat = (1 << 0);
|
||||
internal const uint kAudioFormatFlagIsSignedInteger = (1 << 2);
|
||||
internal const uint kAudioFormatFlagIsPacked = (1 << 3);
|
||||
internal const uint kAudioFormatFlagIsBigEndian = (1 << 1);
|
||||
internal const uint kAudioFormatFlagIsAlignedHigh = (1 << 4);
|
||||
internal const uint kAudioFormatFlagIsNonInterleaved = (1 << 5);
|
||||
|
||||
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
|
||||
internal static partial int AudioQueueNewOutput(
|
||||
ref AudioStreamBasicDescription format,
|
||||
nint callback,
|
||||
nint userData,
|
||||
nint callbackRunLoop,
|
||||
nint callbackRunLoopMode,
|
||||
uint flags,
|
||||
out nint audioQueue);
|
||||
|
||||
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
|
||||
internal static partial int AudioQueueSetProperty(
|
||||
nint audioQueue,
|
||||
uint propertyID,
|
||||
ref AudioChannelLayout layout,
|
||||
uint layoutSize);
|
||||
|
||||
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
|
||||
internal static partial int AudioQueueDispose(nint audioQueue, [MarshalAs(UnmanagedType.I1)] bool immediate);
|
||||
|
||||
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
|
||||
internal static partial int AudioQueueAllocateBuffer(
|
||||
nint audioQueue,
|
||||
uint bufferByteSize,
|
||||
out nint buffer);
|
||||
|
||||
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
|
||||
internal static partial int AudioQueueStart(nint audioQueue, nint startTime);
|
||||
|
||||
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
|
||||
internal static partial int AudioQueuePause(nint audioQueue);
|
||||
|
||||
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
|
||||
internal static partial int AudioQueueStop(nint audioQueue, [MarshalAs(UnmanagedType.I1)] bool immediate);
|
||||
|
||||
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
|
||||
internal static partial int AudioQueueSetParameter(
|
||||
nint audioQueue,
|
||||
uint parameterID,
|
||||
float value);
|
||||
|
||||
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
|
||||
internal static partial int AudioQueueEnqueueBuffer(
|
||||
nint audioQueue,
|
||||
nint buffer,
|
||||
uint numPacketDescs,
|
||||
nint packetDescs);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AudioQueueBuffer
|
||||
{
|
||||
public uint AudioDataBytesCapacity;
|
||||
public nint AudioData;
|
||||
public uint AudioDataByteSize;
|
||||
public nint UserData;
|
||||
public uint PacketDescriptionCapacity;
|
||||
public nint PacketDescriptions;
|
||||
public uint PacketDescriptionCount;
|
||||
}
|
||||
|
||||
internal const uint kAudioQueueParam_Volume = 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -10,7 +10,8 @@ using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.OpenAL
|
||||
{
|
||||
public class OpenALHardwareDeviceDriver : IHardwareDeviceDriver
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public sealed class OpenALHardwareDeviceDriver : IHardwareDeviceDriver
|
||||
{
|
||||
private readonly ALDevice _device;
|
||||
private readonly ALContext _context;
|
||||
@@ -148,7 +149,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
|
||||
@@ -9,7 +9,8 @@ using System.Threading;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.OpenAL
|
||||
{
|
||||
class OpenALHardwareDeviceSession : HardwareDeviceSessionOutputBase
|
||||
// ReSharper disable once InconsistentNaming
|
||||
sealed class OpenALHardwareDeviceSession : HardwareDeviceSessionOutputBase
|
||||
{
|
||||
private readonly OpenALHardwareDeviceDriver _driver;
|
||||
private readonly int _sourceId;
|
||||
@@ -190,7 +191,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && _driver.Unregister(this))
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Ryujinx.Audio.Backends.SDL3
|
||||
|
||||
using unsafe SDL_AudioStreamCallbackPointer = delegate* unmanaged[Cdecl]<nint, SDL_AudioStream*, int, int, void>;
|
||||
|
||||
public class SDL3HardwareDeviceDriver : IHardwareDeviceDriver
|
||||
public sealed class SDL3HardwareDeviceDriver : IHardwareDeviceDriver
|
||||
{
|
||||
private readonly ManualResetEvent _updateRequiredEvent;
|
||||
private readonly ManualResetEvent _pauseEvent;
|
||||
@@ -162,7 +162,7 @@ namespace Ryujinx.Audio.Backends.SDL3
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
|
||||
@@ -12,10 +12,7 @@ using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.SDL3
|
||||
{
|
||||
|
||||
|
||||
|
||||
unsafe class SDL3HardwareDeviceSession : HardwareDeviceSessionOutputBase
|
||||
sealed unsafe class SDL3HardwareDeviceSession : HardwareDeviceSessionOutputBase
|
||||
{
|
||||
private readonly SDL3HardwareDeviceDriver _driver;
|
||||
private readonly ConcurrentQueue<SDL3AudioBuffer> _queuedBuffers;
|
||||
@@ -226,7 +223,7 @@ namespace Ryujinx.Audio.Backends.SDL3
|
||||
return driverBuffer.DriverIdentifier != buffer.DataPointer;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && _driver.Unregister(this))
|
||||
{
|
||||
|
||||
@@ -130,7 +130,7 @@ namespace Ryujinx.Audio.Backends.SoundIo.Native
|
||||
unsafe
|
||||
{
|
||||
int* frameCountPtr = &nativeFrameCount;
|
||||
IntPtr* arenasPtr = &arenas;
|
||||
nint* arenasPtr = &arenas;
|
||||
CheckError(soundio_outstream_begin_write(_context, (nint)arenasPtr, (nint)frameCountPtr));
|
||||
|
||||
frameCount = *frameCountPtr;
|
||||
|
||||
@@ -10,7 +10,7 @@ using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.SoundIo
|
||||
{
|
||||
public class SoundIoHardwareDeviceDriver : IHardwareDeviceDriver
|
||||
public sealed class SoundIoHardwareDeviceDriver : IHardwareDeviceDriver
|
||||
{
|
||||
private readonly SoundIoContext _audioContext;
|
||||
private readonly SoundIoDeviceContext _audioDevice;
|
||||
@@ -227,7 +227,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.SoundIo
|
||||
{
|
||||
class SoundIoHardwareDeviceSession : HardwareDeviceSessionOutputBase
|
||||
sealed class SoundIoHardwareDeviceSession : HardwareDeviceSessionOutputBase
|
||||
{
|
||||
private readonly SoundIoHardwareDeviceDriver _driver;
|
||||
private readonly ConcurrentQueue<SoundIoAudioBuffer> _queuedBuffers;
|
||||
@@ -428,7 +428,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && _driver.Unregister(this))
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Ryujinx.Audio.Renderer.Common
|
||||
public uint MixesSize;
|
||||
public uint SinksSize;
|
||||
public uint PerformanceBufferSize;
|
||||
public uint Unknown24;
|
||||
public uint SplitterSize;
|
||||
public uint RenderInfoSize;
|
||||
|
||||
#pragma warning disable IDE0051, CS0169 // Remove unused field
|
||||
|
||||
@@ -433,8 +433,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
|
||||
public ResultCode UpdateSplitter(SplitterContext context)
|
||||
{
|
||||
long initialInputConsumed = _inputReader.Consumed;
|
||||
|
||||
if (context.Update(ref _inputReader))
|
||||
{
|
||||
_inputReader.SetConsumed(initialInputConsumed + _inputHeader.SplitterSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
|
||||
@@ -107,12 +107,12 @@ namespace Ryujinx.BuildValidationTasks
|
||||
{
|
||||
locale.Translations[langCode] = string.Empty;
|
||||
Console.WriteLine(
|
||||
$"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'! Resetting it...");
|
||||
$"Language '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'! Resetting it...");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'!");
|
||||
$"Language '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,20 +12,12 @@ namespace Ryujinx.Common.Helper
|
||||
private static partial nint GetConsoleWindow();
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[LibraryImport("user32")]
|
||||
[LibraryImport("kernel32", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static partial bool ShowWindow(nint hWnd, int nCmdShow);
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[LibraryImport("user32")]
|
||||
private static partial nint GetForegroundWindow();
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[LibraryImport("user32")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static partial bool SetForegroundWindow(nint hWnd);
|
||||
private static partial bool FreeConsole();
|
||||
|
||||
public static bool SetConsoleWindowStateSupported => OperatingSystem.IsWindows();
|
||||
public static bool HasConsoleWindow => OperatingSystem.IsWindows() && GetConsoleWindow() != nint.Zero;
|
||||
|
||||
public static void SetConsoleWindowState(bool show)
|
||||
{
|
||||
@@ -42,22 +34,31 @@ namespace Ryujinx.Common.Helper
|
||||
[SupportedOSPlatform("windows")]
|
||||
private static void SetConsoleWindowStateWindows(bool show)
|
||||
{
|
||||
const int SW_HIDE = 0;
|
||||
const int SW_SHOW = 5;
|
||||
|
||||
nint hWnd = GetConsoleWindow();
|
||||
|
||||
if (hWnd == nint.Zero)
|
||||
if (show)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,11 +136,7 @@ namespace Ryujinx.Common.Logging
|
||||
|
||||
_time = Stopwatch.StartNew();
|
||||
|
||||
// Logger should log to console by default
|
||||
AddTarget(new AsyncLogTargetWrapper(
|
||||
new ConsoleLogTarget("console"),
|
||||
1000,
|
||||
AsyncLogTargetOverflowAction.Discard));
|
||||
SetConsoleTargetEnabled(true);
|
||||
|
||||
Notice = new Log(LogLevel.Notice);
|
||||
|
||||
@@ -173,6 +169,21 @@ namespace Ryujinx.Common.Logging
|
||||
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)
|
||||
{
|
||||
ILogTarget logTarget = GetTarget(target);
|
||||
|
||||
@@ -29,8 +29,8 @@ namespace Ryujinx.Common
|
||||
|
||||
public static string GetChangelogUrl(Version currentVersion, Version newVersion) =>
|
||||
IsCanaryBuild
|
||||
? $"https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-{currentVersion}...Canary-{newVersion}"
|
||||
: $"https://git.ryujinx.app/ryubing/ryujinx/-/releases/{newVersion}";
|
||||
? $"https://git.ryujinx.app/projects/Ryubing/compare/Canary-{currentVersion}...Canary-{newVersion}"
|
||||
: $"https://git.ryujinx.app/projects/Ryubing/releases/tag/{newVersion}";
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" />
|
||||
<PackageReference Include="MsgPack.Cli" />
|
||||
<PackageReference Include="System.Management" />
|
||||
<PackageReference Include="Humanizer" />
|
||||
<PackageReference Include="Gommon" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -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 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 =
|
||||
"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 =
|
||||
"https://git.ryujinx.app/ryubing/ryujinx/-/wikis/Multiplayer-(LDN-Local-Wireless)-Guide";
|
||||
"https://git.ryujinx.app/projects/Ryubing/wiki/Multiplayer-(LDN-Local-Wireless)-Guide";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,6 +184,7 @@ namespace Ryujinx.Common
|
||||
"01001b300b9be000", // Diablo III: Eternal Collection
|
||||
"010027400cdc6000", // Divinity Original 2 - Definitive Edition
|
||||
"01008c8012920000", // Dying Light Platinum Edition
|
||||
"0100d11013e6a000", // Eschatos
|
||||
"01001cc01b2d4000", // Goat Simulator 3
|
||||
"01003620068ea000", // Hand of Fate 2
|
||||
"0100f7e00c70e000", // Hogwarts Legacy
|
||||
@@ -193,9 +194,15 @@ namespace Ryujinx.Common
|
||||
"0100d71004694000", // Minecraft
|
||||
"01007430037f6000", // Monopoly
|
||||
"0100853015e86000", // No Man's Sky
|
||||
"0100f85014ed0000", // No More Heroes
|
||||
"0100463014ed4000", // No More Heroes 2
|
||||
"0100e570094e8000", // Owlboy
|
||||
"01007bb017812000", // Portal
|
||||
"0100abd01785c000", // Portal 2
|
||||
"01009f100bc52000", // Psikyo Collection 1
|
||||
"01009d400c4a8000", // Psikyo Collection 2
|
||||
"01008e200c5c2000", // Muse Dash
|
||||
"01005ff002e2a000", // Rayman Legends
|
||||
"01007820196a6000", // Red Dead Redemption
|
||||
"0100e8300a67a000", // Risk
|
||||
"01002f7013224000", // Rune Factory 5
|
||||
|
||||
@@ -22,10 +22,11 @@ namespace Ryujinx.Common.Utilities
|
||||
}
|
||||
|
||||
// "dumpable" attribute of the calling process
|
||||
private const int PR_GET_DUMPABLE = 3;
|
||||
private const int PR_SET_DUMPABLE = 4;
|
||||
|
||||
[DllImport("libc", SetLastError = true)]
|
||||
private static extern int prctl(int option, int arg2);
|
||||
[LibraryImport("libc", SetLastError = true)]
|
||||
private static partial int prctl(int option, int arg2);
|
||||
|
||||
public static void SetCoreDumpable(bool dumpable)
|
||||
{
|
||||
@@ -36,5 +37,13 @@ namespace Ryujinx.Common.Utilities
|
||||
Debug.Assert(result == 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Use the below line to display dumpable status in the console:
|
||||
// Console.WriteLine($"{OsUtils.IsCoreDumpable()}");
|
||||
public static bool IsCoreDumpable()
|
||||
{
|
||||
int result = prctl(PR_GET_DUMPABLE, 0);
|
||||
return result == 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,9 +30,9 @@ namespace ARMeilleure.Common
|
||||
/// <summary>
|
||||
/// Base address for the page.
|
||||
/// </summary>
|
||||
public readonly IntPtr Address;
|
||||
public readonly nint Address;
|
||||
|
||||
public AddressTablePage(bool isSparse, IntPtr address)
|
||||
public AddressTablePage(bool isSparse, nint address)
|
||||
{
|
||||
IsSparse = isSparse;
|
||||
Address = address;
|
||||
@@ -47,20 +47,20 @@ namespace ARMeilleure.Common
|
||||
public readonly SparseMemoryBlock Block;
|
||||
private readonly TrackingEventDelegate _trackingEvent;
|
||||
|
||||
public TableSparseBlock(ulong size, Action<IntPtr> ensureMapped, PageInitDelegate pageInit)
|
||||
public TableSparseBlock(ulong size, Action<nint> ensureMapped, PageInitDelegate pageInit)
|
||||
{
|
||||
SparseMemoryBlock block = new(size, pageInit, null);
|
||||
|
||||
_trackingEvent = (address, size, write) =>
|
||||
{
|
||||
ulong pointer = (ulong)block.Block.Pointer + address;
|
||||
ensureMapped((IntPtr)pointer);
|
||||
ensureMapped((nint)pointer);
|
||||
return pointer;
|
||||
};
|
||||
|
||||
bool added = NativeSignalHandler.AddTrackedRegion(
|
||||
(nuint)block.Block.Pointer,
|
||||
(nuint)(block.Block.Pointer + (IntPtr)block.Block.Size),
|
||||
(nuint)(block.Block.Pointer + (nint)block.Block.Size),
|
||||
Marshal.GetFunctionPointerForDelegate(_trackingEvent));
|
||||
|
||||
if (!added)
|
||||
@@ -116,7 +116,7 @@ namespace ARMeilleure.Common
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr Base
|
||||
public nint Base
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -124,7 +124,7 @@ namespace ARMeilleure.Common
|
||||
|
||||
lock (_pages)
|
||||
{
|
||||
return (IntPtr)GetRootPage();
|
||||
return (nint)GetRootPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -240,7 +240,7 @@ namespace ARMeilleure.Common
|
||||
|
||||
long index = Levels[^1].GetValue(address);
|
||||
|
||||
EnsureMapped((IntPtr)(page + index));
|
||||
EnsureMapped((nint)(page + index));
|
||||
|
||||
return ref page[index];
|
||||
}
|
||||
@@ -284,7 +284,7 @@ namespace ARMeilleure.Common
|
||||
/// Ensure the given pointer is mapped in any overlapping sparse reservations.
|
||||
/// </summary>
|
||||
/// <param name="ptr">Pointer to be mapped</param>
|
||||
private void EnsureMapped(IntPtr ptr)
|
||||
private void EnsureMapped(nint ptr)
|
||||
{
|
||||
if (Sparse)
|
||||
{
|
||||
@@ -299,7 +299,7 @@ namespace ARMeilleure.Common
|
||||
{
|
||||
SparseMemoryBlock sparse = reserved.Block;
|
||||
|
||||
if (ptr >= sparse.Block.Pointer && ptr < sparse.Block.Pointer + (IntPtr)sparse.Block.Size)
|
||||
if (ptr >= sparse.Block.Pointer && ptr < sparse.Block.Pointer + (nint)sparse.Block.Size)
|
||||
{
|
||||
sparse.EnsureMapped((ulong)(ptr - sparse.Block.Pointer));
|
||||
|
||||
@@ -319,15 +319,15 @@ namespace ARMeilleure.Common
|
||||
/// </summary>
|
||||
/// <param name="level">Level to get the fill value for</param>
|
||||
/// <returns>The fill value</returns>
|
||||
private IntPtr GetFillValue(int level)
|
||||
private nint GetFillValue(int level)
|
||||
{
|
||||
if (_fillBottomLevel != null && level == Levels.Length - 2)
|
||||
{
|
||||
return (IntPtr)_fillBottomLevelPtr;
|
||||
return (nint)_fillBottomLevelPtr;
|
||||
}
|
||||
else
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
return nint.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,7 +379,7 @@ namespace ARMeilleure.Common
|
||||
/// <param name="fill">Fill value</param>
|
||||
/// <param name="leaf"><see langword="true"/> if leaf; otherwise <see langword="false"/></param>
|
||||
/// <returns>Allocated block</returns>
|
||||
private IntPtr Allocate<T>(int length, T fill, bool leaf) where T : unmanaged
|
||||
private nint Allocate<T>(int length, T fill, bool leaf) where T : unmanaged
|
||||
{
|
||||
int size = sizeof(T) * length;
|
||||
|
||||
@@ -405,7 +405,7 @@ namespace ARMeilleure.Common
|
||||
}
|
||||
}
|
||||
|
||||
page = new AddressTablePage(true, block.Block.Pointer + (IntPtr)_sparseReservedOffset);
|
||||
page = new AddressTablePage(true, block.Block.Pointer + (nint)_sparseReservedOffset);
|
||||
|
||||
_sparseReservedOffset += (ulong)size;
|
||||
|
||||
@@ -413,7 +413,7 @@ namespace ARMeilleure.Common
|
||||
}
|
||||
else
|
||||
{
|
||||
IntPtr address = (IntPtr)NativeAllocator.Instance.Allocate((uint)size);
|
||||
nint address = (nint)NativeAllocator.Instance.Allocate((uint)size);
|
||||
page = new AddressTablePage(false, address);
|
||||
|
||||
Span<T> span = new((void*)page.Address, length);
|
||||
|
||||
@@ -17,7 +17,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
private static readonly int _pageMask = _pageSize - 1;
|
||||
|
||||
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;
|
||||
|
||||
@@ -33,6 +34,14 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
[SupportedOSPlatform("windows")]
|
||||
[LibraryImport("kernel32.dll", SetLastError = true)]
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -658,7 +658,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
bool canImport = Storage.Info.IsLinear && Storage.Info.Stride >= Storage.Info.Width * Storage.Info.FormatInfo.BytesPerPixel;
|
||||
|
||||
IntPtr hostPointer = canImport ? _physicalMemory.GetHostPointer(Storage.Range) : 0;
|
||||
nint hostPointer = canImport ? _physicalMemory.GetHostPointer(Storage.Range) : 0;
|
||||
|
||||
if (hostPointer != 0 && _context.Renderer.PrepareHostMapping(hostPointer, Storage.Size))
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
@@ -114,7 +115,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
cbs.AddDependant(this);
|
||||
|
||||
// We need to add a dependency on the command buffer to all objects this object
|
||||
// references aswell.
|
||||
// references as well.
|
||||
if (_referencedObjs != null)
|
||||
{
|
||||
for (int i = 0; i < _referencedObjs.Length; i++)
|
||||
@@ -176,6 +177,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
// This can somehow become -1.
|
||||
// Logger.Info?.PrintMsg(LogClass.Gpu, $"_referenceCount: {_referenceCount}");
|
||||
Debug.Assert(_referenceCount >= 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Ryujinx.Graphics.Vulkan.MoltenVK
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
IntPtr configSize = (nint)Marshal.SizeOf<MVKConfiguration>();
|
||||
nint configSize = (nint)Marshal.SizeOf<MVKConfiguration>();
|
||||
|
||||
vkGetMoltenVKConfigurationMVK(nint.Zero, out MVKConfiguration config, configSize);
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
enabledExtensions = enabledExtensions.Append(ExtDebugUtils.ExtensionName).ToArray();
|
||||
}
|
||||
|
||||
IntPtr appName = Marshal.StringToHGlobalAnsi(AppName);
|
||||
nint appName = Marshal.StringToHGlobalAnsi(AppName);
|
||||
|
||||
ApplicationInfo applicationInfo = new()
|
||||
{
|
||||
@@ -166,7 +166,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
internal static DeviceInfo[] GetSuitablePhysicalDevices(Vk api)
|
||||
{
|
||||
IntPtr appName = Marshal.StringToHGlobalAnsi(AppName);
|
||||
nint appName = Marshal.StringToHGlobalAnsi(AppName);
|
||||
|
||||
ApplicationInfo applicationInfo = new()
|
||||
{
|
||||
|
||||
@@ -488,6 +488,8 @@ namespace Ryujinx.HLE.FileSystem
|
||||
if (keyPaths.Length is 0)
|
||||
throw new FileNotFoundException($"Directory '{keysSource}' contained no '.keys' files.");
|
||||
|
||||
List<string> failedFiles = new();
|
||||
|
||||
foreach (string filePath in keyPaths)
|
||||
{
|
||||
try
|
||||
@@ -497,17 +499,20 @@ namespace Ryujinx.HLE.FileSystem
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, e.Message);
|
||||
failedFiles.Add(Path.GetFileName(filePath));
|
||||
continue;
|
||||
}
|
||||
|
||||
string destPath = Path.Combine(installDirectory, Path.GetFileName(filePath));
|
||||
|
||||
if (File.Exists(destPath))
|
||||
File.Delete(destPath);
|
||||
|
||||
File.Copy(filePath, destPath, true);
|
||||
}
|
||||
|
||||
if (failedFiles.Count > 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to install the following key files: {string.Join(", ", failedFiles)}");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -518,8 +523,6 @@ namespace Ryujinx.HLE.FileSystem
|
||||
|
||||
FileInfo info = new(keysSource);
|
||||
|
||||
using FileStream file = File.OpenRead(keysSource);
|
||||
|
||||
if (info.Extension is not ".keys")
|
||||
throw new InvalidFirmwarePackageException("Input file extension is not .keys");
|
||||
|
||||
@@ -534,10 +537,6 @@ namespace Ryujinx.HLE.FileSystem
|
||||
|
||||
string dest = Path.Combine(installDirectory, info.Name);
|
||||
|
||||
if (File.Exists(dest))
|
||||
File.Delete(dest);
|
||||
|
||||
// overwrite: true seems to not work on its own? https://github.com/Ryubing/Issues/issues/189
|
||||
File.Copy(keysSource, dest, true);
|
||||
}
|
||||
|
||||
@@ -1059,7 +1058,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||
}
|
||||
}
|
||||
|
||||
public static bool AreKeysAlredyPresent(string pathToCheck)
|
||||
public static bool AreKeysAlreadyPresent(string pathToCheck)
|
||||
{
|
||||
string[] fileNames = ["prod.keys", "title.keys", "console.keys", "dev.keys"];
|
||||
foreach (string file in fileNames)
|
||||
|
||||
@@ -20,6 +20,7 @@ using Ryujinx.HLE.HOS.Services.Mii;
|
||||
using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption;
|
||||
using Ryujinx.HLE.HOS.Services.Nfc.Nfp;
|
||||
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
|
||||
using Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager;
|
||||
using Ryujinx.HLE.HOS.Services.Nv;
|
||||
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl;
|
||||
using Ryujinx.HLE.HOS.Services.Pcv.Bpc;
|
||||
@@ -66,6 +67,8 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
internal List<NfpDevice> NfpDevices { get; private set; }
|
||||
|
||||
internal List<NfcDevice> NfcDevices { get; private set; }
|
||||
|
||||
internal SmRegistry SmRegistry { get; private set; }
|
||||
|
||||
internal ServerBase SmServer { get; private set; }
|
||||
@@ -132,6 +135,7 @@ namespace Ryujinx.HLE.HOS
|
||||
PerformanceState = new PerformanceState();
|
||||
|
||||
NfpDevices = [];
|
||||
NfcDevices = [];
|
||||
|
||||
// Note: This is not really correct, but with HLE of services, the only memory
|
||||
// region used that is used is Application, so we can use the other ones for anything.
|
||||
@@ -242,21 +246,21 @@ namespace Ryujinx.HLE.HOS
|
||||
public void InitializeServices()
|
||||
{
|
||||
SmRegistry = new SmRegistry();
|
||||
SmServer = new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext, SmRegistry));
|
||||
SmServer = new ServerBase(KernelContext, "Sm", () => new IUserInterface(KernelContext, SmRegistry));
|
||||
|
||||
// Wait until SM server thread is done with initialization,
|
||||
// only then doing connections to SM is safe.
|
||||
SmServer.InitDone.WaitOne();
|
||||
|
||||
BsdServer = new ServerBase(KernelContext, "BsdServer");
|
||||
FsServer = new ServerBase(KernelContext, "FsServer");
|
||||
HidServer = new ServerBase(KernelContext, "HidServer");
|
||||
NvDrvServer = new ServerBase(KernelContext, "NvservicesServer");
|
||||
TimeServer = new ServerBase(KernelContext, "TimeServer");
|
||||
ViServer = new ServerBase(KernelContext, "ViServerU");
|
||||
ViServerM = new ServerBase(KernelContext, "ViServerM");
|
||||
ViServerS = new ServerBase(KernelContext, "ViServerS");
|
||||
LdnServer = new ServerBase(KernelContext, "LdnServer");
|
||||
BsdServer = new ServerBase(KernelContext, "Bsd");
|
||||
FsServer = new ServerBase(KernelContext, "Fs");
|
||||
HidServer = new ServerBase(KernelContext, "Hid");
|
||||
NvDrvServer = new ServerBase(KernelContext, "Nv");
|
||||
TimeServer = new ServerBase(KernelContext, "Time");
|
||||
ViServer = new ServerBase(KernelContext, "Vi:u");
|
||||
ViServerM = new ServerBase(KernelContext, "Vi:m");
|
||||
ViServerS = new ServerBase(KernelContext, "Vi:s");
|
||||
LdnServer = new ServerBase(KernelContext, "Ldn");
|
||||
|
||||
StartNewServices();
|
||||
}
|
||||
@@ -282,7 +286,7 @@ namespace Ryujinx.HLE.HOS
|
||||
ProcessCreationFlags.Is64Bit |
|
||||
ProcessCreationFlags.PoolPartitionSystem;
|
||||
|
||||
ProcessCreationInfo creationInfo = new("Service", 1, 0, 0x8000000, 1, Flags, 0, 0);
|
||||
ProcessCreationInfo creationInfo = new(service.Name, 1, 0, 0x8000000, 1, Flags, 0, 0);
|
||||
|
||||
uint[] defaultCapabilities =
|
||||
[
|
||||
@@ -372,6 +376,15 @@ namespace Ryujinx.HLE.HOS
|
||||
}
|
||||
}
|
||||
|
||||
public void ScanSkylander(int nfcDeviceId, byte[] data)
|
||||
{
|
||||
if (NfcDevices[nfcDeviceId].State == NfcDeviceState.SearchingForTag)
|
||||
{
|
||||
NfcDevices[nfcDeviceId].State = NfcDeviceState.TagFound;
|
||||
NfcDevices[nfcDeviceId].Data = data;
|
||||
}
|
||||
}
|
||||
|
||||
public bool SearchingForAmiibo(out int nfpDeviceId)
|
||||
{
|
||||
nfpDeviceId = default;
|
||||
@@ -389,6 +402,53 @@ namespace Ryujinx.HLE.HOS
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool SearchingForSkylander(out int nfcDeviceId)
|
||||
{
|
||||
nfcDeviceId = default;
|
||||
|
||||
for (int i = 0; i < NfcDevices.Count; i++)
|
||||
{
|
||||
if (NfcDevices[i].State == NfcDeviceState.SearchingForTag)
|
||||
{
|
||||
nfcDeviceId = i;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool HasSkylander(out int nfcDeviceId)
|
||||
{
|
||||
nfcDeviceId = default;
|
||||
|
||||
for (int i = 0; i < NfcDevices.Count; i++)
|
||||
{
|
||||
if (NfcDevices[i].State == NfcDeviceState.TagFound)
|
||||
{
|
||||
nfcDeviceId = i;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void RemoveSkylander()
|
||||
{
|
||||
for (int i = 0; i < NfcDevices.Count; i++)
|
||||
{
|
||||
if (NfcDevices[i].State == NfcDeviceState.TagFound)
|
||||
{
|
||||
NfcDevices[i].State = NfcDeviceState.Initialized;
|
||||
NfcDevices[i].SignalDeactivate();
|
||||
Thread.Sleep(100); // NOTE: Simulate skylander scanning delay.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SignalDisplayResolutionChange()
|
||||
{
|
||||
DisplayResolutionChangeEvent.ReadableEvent.Signal();
|
||||
|
||||
@@ -118,8 +118,11 @@ namespace Ryujinx.HLE.HOS.Services.Caps
|
||||
}
|
||||
|
||||
// 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 SKBitmap bitmap = new(new SKImageInfo(1280, 720, SKColorType.Rgba8888, SKAlphaType.Premul));
|
||||
int dataLen = screenshotData.Length > bitmap.ByteCount ? bitmap.ByteCount : screenshotData.Length;
|
||||
|
||||
Marshal.Copy(screenshotData, 0, bitmap.GetPixels(), dataLen);
|
||||
|
||||
using SKData data = bitmap.Encode(SKEncodedImageFormat.Jpeg, 80);
|
||||
using FileStream file = File.OpenWrite(filePath);
|
||||
data.SaveTo(file);
|
||||
|
||||
@@ -56,6 +56,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
_activeCount = 0;
|
||||
|
||||
JoyHold = NpadJoyHoldType.Vertical;
|
||||
SixAxisActive = false;
|
||||
}
|
||||
|
||||
internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player)
|
||||
@@ -580,6 +581,29 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
|
||||
return needUpdateRight;
|
||||
}
|
||||
|
||||
public bool isAtRest(int playerNumber)
|
||||
{
|
||||
ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[playerNumber].InternalState;
|
||||
|
||||
if (currentNpad.StyleSet == NpadStyleTag.None)
|
||||
{
|
||||
return true; // it will always be at rest because it cannot move.
|
||||
}
|
||||
|
||||
ref SixAxisSensorState storage = ref GetSixAxisSensorLifo(ref currentNpad, false).GetCurrentEntryRef();
|
||||
|
||||
float acceleration = Math.Abs(storage.Acceleration.X)
|
||||
+ Math.Abs(storage.Acceleration.Y)
|
||||
+ Math.Abs(storage.Acceleration.Z);
|
||||
|
||||
float angularVelocity = Math.Abs(storage.AngularVelocity.X)
|
||||
+ Math.Abs(storage.AngularVelocity.Y)
|
||||
+ Math.Abs(storage.AngularVelocity.Z);
|
||||
|
||||
// TODO: check against config deadzone and add sensitivity setting
|
||||
return ((acceleration <= 1.0F) && (angularVelocity <= 1.0F));
|
||||
}
|
||||
|
||||
private void UpdateDisconnectedInputSixAxis(PlayerIndex index)
|
||||
{
|
||||
|
||||
@@ -602,19 +602,33 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
}
|
||||
|
||||
[CommandCmif(82)]
|
||||
// IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAsRest
|
||||
// IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAtRest
|
||||
public ResultCode IsSixAxisSensorAtRest(ServiceCtx context)
|
||||
{
|
||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||
|
||||
// 4 byte struct w/ 4-byte alignment
|
||||
|
||||
// uint typeValue = (uint) sixAxisSensorHandle; // 0x0 0x4 TypeValue
|
||||
// uint npadStyleIndex = (uint) sixAxisSensorHandle & 0xff; // 0x0 0x1 NpadStyleIndex
|
||||
int playerNumber = (sixAxisSensorHandle << 8) & 0xff; // 0x1 0x1 PlayerNumber
|
||||
// uint deviceIdx= ((uint) sixAxisSensorHandle << 16) & 0xff; // 0x2 0x1 DeviceIdx
|
||||
// uint unknown = ((uint) sixAxisSensorHandle << 24) & 0xff;
|
||||
|
||||
// 32bit sign extension padding -> if = 0, + offset, else - offset
|
||||
|
||||
// npadStyleIndex = ((npadStyleIndex & 0x8000) == 0) ? npadStyleIndex | 0xFFFF0000 : npadStyleIndex & 0xFFFF0000;
|
||||
// playerNumber = ((playerNumber & 0x8000) == 0) ? playerNumber | 0xFFFF0000 : playerNumber & 0xFFFF0000;
|
||||
// deviceIdx = ((deviceIdx & 0x8000) == 0) ? deviceIdx | 0xFFFF0000 : deviceIdx & 0xFFFF0000;
|
||||
// unknown = ((unknown & 0x8000) == 0) ? unknown | 0xFFFF0000 : unknown & 0xFFFF0000;
|
||||
|
||||
context.RequestData.BaseStream.Position += 4; // Padding
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
|
||||
bool isAtRest = true;
|
||||
|
||||
context.ResponseData.Write(isAtRest);
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, isAtRest });
|
||||
|
||||
|
||||
// TODO: link to context.Device.Hid.Npads.SixAxisActive when properly implemented
|
||||
// We currently do not support stopping or starting SixAxisTracking.
|
||||
|
||||
context.ResponseData.Write(context.Device.Hid.Npads.isAtRest(playerNumber));
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
@@ -629,7 +643,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
context.ResponseData.Write(_isFirmwareUpdateAvailableForSixAxisSensor);
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _isFirmwareUpdateAvailableForSixAxisSensor });
|
||||
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.Exceptions;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
@@ -14,6 +15,7 @@ using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
@@ -487,6 +489,23 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(106)] // 20.0.0+
|
||||
// SetProtocol
|
||||
public ResultCode SetProtocol(ServiceCtx context)
|
||||
{
|
||||
uint protocolValue = context.RequestData.ReadUInt32();
|
||||
|
||||
// On NX only input value 1 or 3 is allowed, with an error being thrown otherwise.
|
||||
|
||||
if (protocolValue != 1 && protocolValue != 3)
|
||||
{
|
||||
throw new ArgumentException($"{GetType().FullName}: Protocol value is not 1 or 3!! Protocol value: {protocolValue}");
|
||||
}
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceLdn, $"Protocol value: {protocolValue}");
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(200)]
|
||||
// OpenAccessPoint()
|
||||
public ResultCode OpenAccessPoint(ServiceCtx context)
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
using Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare
|
||||
{
|
||||
[Service("nfc:mf:u")]
|
||||
class IUserManager : IpcService
|
||||
{
|
||||
public IUserManager(ServiceCtx context) { }
|
||||
|
||||
[CommandCmif(0)]
|
||||
// CreateUserInterface() -> object<nn::nfc::mf::IUser>
|
||||
public ResultCode CreateUserInterface(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IMifare());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
477
src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/IMifare.cs
Normal file
477
src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/IMifare.cs
Normal file
@@ -0,0 +1,477 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.Hid;
|
||||
using Ryujinx.HLE.HOS.Services.Hid.HidServer;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||
{
|
||||
class IMifare : IpcService
|
||||
{
|
||||
private State _state;
|
||||
|
||||
private KEvent _availabilityChangeEvent;
|
||||
|
||||
private CancellationTokenSource _cancelTokenSource;
|
||||
|
||||
public IMifare()
|
||||
{
|
||||
_state = State.NonInitialized;
|
||||
}
|
||||
|
||||
[CommandCmif(0)]
|
||||
public ResultCode Initialize(ServiceCtx context)
|
||||
{
|
||||
_state = State.Initialized;
|
||||
|
||||
NfcDevice devicePlayer1 = new()
|
||||
{
|
||||
NpadIdType = NpadIdType.Player1,
|
||||
Handle = HidUtils.GetIndexFromNpadIdType(NpadIdType.Player1),
|
||||
State = NfcDeviceState.Initialized,
|
||||
};
|
||||
|
||||
context.Device.System.NfcDevices.Add(devicePlayer1);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1)]
|
||||
public ResultCode Finalize(ServiceCtx context)
|
||||
{
|
||||
if (_state == State.Initialized)
|
||||
{
|
||||
_cancelTokenSource?.Cancel();
|
||||
|
||||
// NOTE: All events are destroyed here.
|
||||
context.Device.System.NfcDevices.Clear();
|
||||
|
||||
_state = State.NonInitialized;
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(2)]
|
||||
public ResultCode GetListDevices(ServiceCtx context)
|
||||
{
|
||||
if (context.Request.RecvListBuff.Count == 0)
|
||||
{
|
||||
return ResultCode.WrongArgument;
|
||||
}
|
||||
|
||||
ulong outputPosition = context.Request.RecvListBuff[0].Position;
|
||||
ulong outputSize = context.Request.RecvListBuff[0].Size;
|
||||
|
||||
if (context.Device.System.NfcDevices.Count == 0)
|
||||
{
|
||||
return ResultCode.DeviceNotFound;
|
||||
}
|
||||
|
||||
MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
|
||||
|
||||
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
|
||||
{
|
||||
context.Memory.Write(outputPosition + ((uint)i * sizeof(long)), (uint)context.Device.System.NfcDevices[i].Handle);
|
||||
}
|
||||
|
||||
context.ResponseData.Write(context.Device.System.NfcDevices.Count);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(3)]
|
||||
public ResultCode StartDetection(ServiceCtx context)
|
||||
{
|
||||
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
|
||||
|
||||
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
|
||||
{
|
||||
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
|
||||
{
|
||||
context.Device.System.NfcDevices[i].State = NfcDeviceState.SearchingForTag;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_cancelTokenSource = new CancellationTokenSource();
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (_cancelTokenSource.Token.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
|
||||
{
|
||||
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagFound)
|
||||
{
|
||||
context.Device.System.NfcDevices[i].SignalActivate();
|
||||
Thread.Sleep(125); // NOTE: Simulate skylander scanning delay.
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, _cancelTokenSource.Token);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(4)]
|
||||
public ResultCode StopDetection(ServiceCtx context)
|
||||
{
|
||||
_cancelTokenSource?.Cancel();
|
||||
|
||||
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
|
||||
|
||||
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
|
||||
{
|
||||
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
|
||||
{
|
||||
context.Device.System.NfcDevices[i].State = NfcDeviceState.Initialized;
|
||||
Array.Clear(context.Device.System.NfcDevices[i].Data);
|
||||
context.Device.System.NfcDevices[i].SignalDeactivate();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(5)]
|
||||
public ResultCode ReadMifare(ServiceCtx context)
|
||||
{
|
||||
if (context.Request.ReceiveBuff.Count == 0 || context.Request.SendBuff.Count == 0)
|
||||
{
|
||||
return ResultCode.WrongArgument;
|
||||
}
|
||||
|
||||
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
|
||||
|
||||
if (context.Device.System.NfcDevices.Count == 0)
|
||||
{
|
||||
return ResultCode.DeviceNotFound;
|
||||
}
|
||||
|
||||
ulong outputPosition = context.Request.ReceiveBuff[0].Position;
|
||||
ulong outputSize = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
|
||||
|
||||
ulong inputPosition = context.Request.SendBuff[0].Position;
|
||||
ulong inputSize = context.Request.SendBuff[0].Size;
|
||||
|
||||
byte[] readBlockParameter = new byte[inputSize];
|
||||
|
||||
context.Memory.Read(inputPosition, readBlockParameter);
|
||||
|
||||
var span = MemoryMarshal.Cast<byte, NfcMifareReadBlockParameter>(readBlockParameter);
|
||||
var list = new List<NfcMifareReadBlockParameter>(span.Length);
|
||||
|
||||
foreach (var item in span)
|
||||
list.Add(item);
|
||||
|
||||
Thread.Sleep(125 * list.Count); // NOTE: Simulate skylander scanning delay.
|
||||
|
||||
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
|
||||
{
|
||||
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
|
||||
{
|
||||
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagRemoved)
|
||||
{
|
||||
return ResultCode.TagNotFound;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int p = 0; p < list.Count; p++)
|
||||
{
|
||||
NfcMifareReadBlockData blockData = new()
|
||||
{
|
||||
SectorNumber = list[p].SectorNumber,
|
||||
Reserved = new Array7<byte>(),
|
||||
};
|
||||
byte[] data = new byte[16];
|
||||
|
||||
switch (list[p].SectorKey.MifareCommand)
|
||||
{
|
||||
case NfcMifareCommand.NfcMifareCommand_Read:
|
||||
case NfcMifareCommand.NfcMifareCommand_AuthA:
|
||||
if (IsCurrentBlockKeyBlock(list[p].SectorNumber))
|
||||
{
|
||||
Array.Copy(context.Device.System.NfcDevices[i].Data, (16 * list[p].SectorNumber) + 6, data, 6, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
Array.Copy(context.Device.System.NfcDevices[i].Data, 16 * list[p].SectorNumber, data, 0, 16);
|
||||
}
|
||||
data.CopyTo(blockData.Data.AsSpan());
|
||||
context.Memory.Write(outputPosition + ((uint)(p * Unsafe.SizeOf<NfcMifareReadBlockData>())), blockData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(6)]
|
||||
public ResultCode WriteMifare(ServiceCtx context)
|
||||
{
|
||||
if (context.Request.SendBuff.Count == 0)
|
||||
{
|
||||
return ResultCode.WrongArgument;
|
||||
}
|
||||
|
||||
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
|
||||
|
||||
if (context.Device.System.NfcDevices.Count == 0)
|
||||
{
|
||||
return ResultCode.DeviceNotFound;
|
||||
}
|
||||
|
||||
ulong inputPosition = context.Request.SendBuff[0].Position;
|
||||
ulong inputSize = context.Request.SendBuff[0].Size;
|
||||
|
||||
byte[] writeBlockParameter = new byte[inputSize];
|
||||
|
||||
context.Memory.Read(inputPosition, writeBlockParameter);
|
||||
|
||||
var span = MemoryMarshal.Cast<byte, NfcMifareWriteBlockParameter>(writeBlockParameter);
|
||||
var list = new List<NfcMifareWriteBlockParameter>(span.Length);
|
||||
|
||||
foreach (var item in span)
|
||||
list.Add(item);
|
||||
|
||||
Thread.Sleep(125 * list.Count); // NOTE: Simulate skylander scanning delay.
|
||||
|
||||
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
|
||||
{
|
||||
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
|
||||
{
|
||||
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagRemoved)
|
||||
{
|
||||
return ResultCode.TagNotFound;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int p = 0; p < list.Count; p++)
|
||||
{
|
||||
switch (list[p].SectorKey.MifareCommand)
|
||||
{
|
||||
case NfcMifareCommand.NfcMifareCommand_Write:
|
||||
case NfcMifareCommand.NfcMifareCommand_AuthA:
|
||||
list[p].Data.AsSpan().CopyTo(context.Device.System.NfcDevices[i].Data.AsSpan(list[p].SectorNumber * 16, 16));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(7)]
|
||||
public ResultCode GetTagInfo(ServiceCtx context)
|
||||
{
|
||||
ResultCode resultCode = ResultCode.Success;
|
||||
|
||||
if (context.Request.RecvListBuff.Count == 0)
|
||||
{
|
||||
return ResultCode.WrongArgument;
|
||||
}
|
||||
|
||||
ulong outputPosition = context.Request.RecvListBuff[0].Position;
|
||||
|
||||
context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf<TagInfo>());
|
||||
|
||||
MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf<TagInfo>());
|
||||
|
||||
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
|
||||
|
||||
if (context.Device.System.NfcDevices.Count == 0)
|
||||
{
|
||||
return ResultCode.DeviceNotFound;
|
||||
}
|
||||
|
||||
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
|
||||
{
|
||||
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
|
||||
{
|
||||
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagRemoved)
|
||||
{
|
||||
resultCode = ResultCode.TagNotFound;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagMounted || context.Device.System.NfcDevices[i].State == NfcDeviceState.TagFound)
|
||||
{
|
||||
TagInfo tagInfo = new()
|
||||
{
|
||||
UuidLength = 4,
|
||||
Reserved1 = new Array21<byte>(),
|
||||
Protocol = (uint)NfcProtocol.NfcProtocol_TypeA, // Type A Protocol
|
||||
TagType = (uint)NfcTagType.NfcTagType_Mifare, // Mifare Type
|
||||
Reserved2 = new Array6<byte>(),
|
||||
};
|
||||
|
||||
byte[] uuid = new byte[4];
|
||||
|
||||
Array.Copy(context.Device.System.NfcDevices[i].Data, 0, uuid, 0, 4);
|
||||
|
||||
uuid.CopyTo(tagInfo.Uuid.AsSpan());
|
||||
|
||||
context.Memory.Write(outputPosition, tagInfo);
|
||||
|
||||
resultCode = ResultCode.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
resultCode = ResultCode.WrongDeviceState;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
[CommandCmif(8)]
|
||||
public ResultCode AttachActivateEvent(ServiceCtx context)
|
||||
{
|
||||
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
|
||||
|
||||
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
|
||||
{
|
||||
if ((uint)context.Device.System.NfcDevices[i].Handle == deviceHandle)
|
||||
{
|
||||
context.Device.System.NfcDevices[i].ActivateEvent = new KEvent(context.Device.System.KernelContext);
|
||||
|
||||
if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfcDevices[i].ActivateEvent.ReadableEvent, out int activateEventHandle) != Result.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(activateEventHandle);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode.DeviceNotFound;
|
||||
}
|
||||
|
||||
[CommandCmif(9)]
|
||||
public ResultCode AttachDeactivateEvent(ServiceCtx context)
|
||||
{
|
||||
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
|
||||
|
||||
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
|
||||
{
|
||||
if ((uint)context.Device.System.NfcDevices[i].Handle == deviceHandle)
|
||||
{
|
||||
context.Device.System.NfcDevices[i].DeactivateEvent = new KEvent(context.Device.System.KernelContext);
|
||||
|
||||
if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfcDevices[i].DeactivateEvent.ReadableEvent, out int deactivateEventHandle) != Result.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(deactivateEventHandle);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode.DeviceNotFound;
|
||||
}
|
||||
|
||||
[CommandCmif(10)]
|
||||
public ResultCode GetState(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write((int)_state);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(11)]
|
||||
public ResultCode GetDeviceState(ServiceCtx context)
|
||||
{
|
||||
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
|
||||
|
||||
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
|
||||
{
|
||||
if ((uint)context.Device.System.NfcDevices[i].Handle == deviceHandle)
|
||||
{
|
||||
if (context.Device.System.NfcDevices[i].State > NfcDeviceState.Finalized)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(context.Device.System.NfcDevices)} contains an invalid state for device {i}: {context.Device.System.NfcDevices[i].State}");
|
||||
}
|
||||
context.ResponseData.Write((uint)context.Device.System.NfcDevices[i].State);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
|
||||
context.ResponseData.Write((uint)NfcDeviceState.Unavailable);
|
||||
|
||||
return ResultCode.DeviceNotFound;
|
||||
}
|
||||
|
||||
[CommandCmif(12)]
|
||||
public ResultCode GetNpadId(ServiceCtx context)
|
||||
{
|
||||
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
|
||||
|
||||
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
|
||||
{
|
||||
if ((uint)context.Device.System.NfcDevices[i].Handle == deviceHandle)
|
||||
{
|
||||
context.ResponseData.Write((uint)HidUtils.GetNpadIdTypeFromIndex(context.Device.System.NfcDevices[i].Handle));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode.DeviceNotFound;
|
||||
}
|
||||
|
||||
[CommandCmif(13)]
|
||||
public ResultCode AttachAvailabilityChangeEvent(ServiceCtx context)
|
||||
{
|
||||
_availabilityChangeEvent = new KEvent(context.Device.System.KernelContext);
|
||||
|
||||
if (context.Process.HandleTable.GenerateHandle(_availabilityChangeEvent.ReadableEvent, out int availabilityChangeEventHandle) != Result.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(availabilityChangeEventHandle);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
private bool IsCurrentBlockKeyBlock(byte block)
|
||||
{
|
||||
return ((block + 1) % 4) == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.Hid;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||
{
|
||||
class NfcDevice
|
||||
{
|
||||
public KEvent ActivateEvent;
|
||||
public KEvent DeactivateEvent;
|
||||
|
||||
public void SignalActivate() => ActivateEvent.ReadableEvent.Signal();
|
||||
public void SignalDeactivate() => DeactivateEvent.ReadableEvent.Signal();
|
||||
|
||||
public NfcDeviceState State = NfcDeviceState.Unavailable;
|
||||
|
||||
public PlayerIndex Handle;
|
||||
public NpadIdType NpadIdType;
|
||||
|
||||
public byte[] Data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||
{
|
||||
enum NfcMifareCommand : byte
|
||||
{
|
||||
NfcMifareCommand_Read = 0x30,
|
||||
NfcMifareCommand_AuthA = 0x60,
|
||||
NfcMifareCommand_AuthB = 0x61,
|
||||
NfcMifareCommand_Write = 0xA0,
|
||||
NfcMifareCommand_Transfer = 0xB0,
|
||||
NfcMifareCommand_Decrement = 0xC0,
|
||||
NfcMifareCommand_Increment = 0xC1,
|
||||
NfcMifareCommand_Store = 0xC2,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x18)]
|
||||
struct NfcMifareReadBlockData
|
||||
{
|
||||
public Array16<byte> Data;
|
||||
public byte SectorNumber;
|
||||
public Array7<byte> Reserved;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x18)]
|
||||
struct NfcMifareReadBlockParameter
|
||||
{
|
||||
public byte SectorNumber;
|
||||
public Array7<byte> Reserved;
|
||||
public NfcSectorKey SectorKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x28)]
|
||||
struct NfcMifareWriteBlockParameter
|
||||
{
|
||||
public Array16<byte> Data;
|
||||
public byte SectorNumber;
|
||||
public Array7<byte> Reserved;
|
||||
public NfcSectorKey SectorKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||
{
|
||||
enum NfcProtocol : byte
|
||||
{
|
||||
NfcProtocol_None = 0b_0000_0000,
|
||||
NfcProtocol_TypeA = 0b_0000_0001, ///< ISO14443A
|
||||
NfcProtocol_TypeB = 0b_0000_0010, ///< ISO14443B
|
||||
NfcProtocol_TypeF = 0b_0000_0100, ///< Sony FeliCa
|
||||
NfcProtocol_All = 0xFF,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
|
||||
struct NfcSectorKey
|
||||
{
|
||||
public NfcMifareCommand MifareCommand;
|
||||
public byte Unknown;
|
||||
public Array6<byte> Reserved1;
|
||||
public Array6<byte> SectorKey;
|
||||
public Array2<byte> Reserved2;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||
{
|
||||
enum NfcTagType : byte
|
||||
{
|
||||
NfcTagType_None = 0b_0000_0000,
|
||||
NfcTagType_Type1 = 0b_0000_0001, ///< ISO14443A RW. Topaz
|
||||
NfcTagType_Type2 = 0b_0000_0010, ///< ISO14443A RW. Ultralight, NTAGX, ST25TN
|
||||
NfcTagType_Type3 = 0b_0000_0100, ///< ISO14443A RW/RO. Sony FeliCa
|
||||
NfcTagType_Type4A = 0b_0000_1000, ///< ISO14443A RW/RO. DESFire
|
||||
NfcTagType_Type4B = 0b_0001_0000, ///< ISO14443B RW/RO. DESFire
|
||||
NfcTagType_Type5 = 0b_0010_0000, ///< ISO15693 RW/RO. SLI, SLIX, ST25TV
|
||||
NfcTagType_Mifare = 0b_0100_0000, ///< Mifare clasic. Skylanders
|
||||
NfcTagType_All = 0xFF,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||
{
|
||||
enum NfcDeviceState : byte
|
||||
{
|
||||
Initialized = 0,
|
||||
SearchingForTag = 1,
|
||||
TagFound = 2,
|
||||
TagRemoved = 3,
|
||||
TagMounted = 4,
|
||||
Unavailable = 5,
|
||||
Finalized = 6,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||
{
|
||||
enum State
|
||||
{
|
||||
NonInitialized,
|
||||
Initialized,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x58)]
|
||||
struct TagInfo
|
||||
{
|
||||
public Array10<byte> Uuid;
|
||||
public byte UuidLength;
|
||||
public Array21<byte> Reserved1;
|
||||
public uint Protocol;
|
||||
public uint TagType;
|
||||
public Array6<byte> Reserved2;
|
||||
}
|
||||
}
|
||||
17
src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/ResultCode.cs
Normal file
17
src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/ResultCode.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare
|
||||
{
|
||||
public enum ResultCode
|
||||
{
|
||||
ModuleId = 161,
|
||||
ErrorCodeShift = 9,
|
||||
|
||||
Success = 0,
|
||||
|
||||
DeviceNotFound = (64 << ErrorCodeShift) | ModuleId, // 0x80A1
|
||||
WrongArgument = (65 << ErrorCodeShift) | ModuleId, // 0x82A1
|
||||
WrongDeviceState = (73 << ErrorCodeShift) | ModuleId, // 0x92A1
|
||||
NfcDisabled = (80 << ErrorCodeShift) | ModuleId, // 0xA0A1
|
||||
TagNotFound = (97 << ErrorCodeShift) | ModuleId, // 0xC2A1
|
||||
MifareAccessError = (288 << ErrorCodeShift) | ModuleId, // 0x240a1
|
||||
}
|
||||
}
|
||||
@@ -79,9 +79,15 @@ namespace Ryujinx.HLE.HOS.Services
|
||||
ProcessCreationFlags.Is64Bit |
|
||||
ProcessCreationFlags.PoolPartitionSystem;
|
||||
|
||||
ProcessCreationInfo creationInfo = new("Service", 1, 0, 0x8000000, 1, Flags, 0, 0);
|
||||
ProcessCreationInfo creationInfo = new(Name, 1, 0, 0x8000000, 1, Flags, 0, 0);
|
||||
|
||||
KernelStatic.StartInitialProcess(context, creationInfo, DefaultCapabilities, 44, Main);
|
||||
KernelStatic.StartInitialProcess(context, creationInfo, DefaultCapabilities, 44, () =>
|
||||
{
|
||||
var currentThread = KernelStatic.GetCurrentThread();
|
||||
currentThread.HostThread.Name = $"{{{Name}}}";
|
||||
|
||||
Main();
|
||||
});
|
||||
}
|
||||
|
||||
private void AddPort(int serverPortHandle, Func<IpcService> objectFactory)
|
||||
|
||||
@@ -17,13 +17,12 @@ namespace Ryujinx.HLE.HOS.Services.Sm
|
||||
private static readonly Dictionary<string, Type> _services;
|
||||
|
||||
private readonly SmRegistry _registry;
|
||||
private readonly ServerBase _commonServer;
|
||||
private ServerBase _commonServer;
|
||||
|
||||
private bool _isInitialized;
|
||||
|
||||
public IUserInterface(KernelContext context, SmRegistry registry) : base(registerTipc: true)
|
||||
{
|
||||
_commonServer = new ServerBase(context, "CommonServer");
|
||||
_registry = registry;
|
||||
}
|
||||
|
||||
@@ -97,6 +96,11 @@ namespace Ryujinx.HLE.HOS.Services.Sm
|
||||
|
||||
IpcService service = GetServiceInstance(type, context, serviceAttribute.Parameter);
|
||||
|
||||
if (_commonServer is null)
|
||||
{
|
||||
_commonServer = new ServerBase(context.Device.System.KernelContext, "Common");
|
||||
}
|
||||
|
||||
service.TrySetServer(_commonServer);
|
||||
service.Server.AddSessionObj(session.ServerSession, service);
|
||||
}
|
||||
@@ -253,7 +257,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm
|
||||
|
||||
public override void DestroyAtExit()
|
||||
{
|
||||
_commonServer.Dispose();
|
||||
_commonServer?.Dispose();
|
||||
|
||||
base.DestroyAtExit();
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
||||
|
||||
public bool Blocking { get => Socket.Blocking; set => Socket.Blocking = value; }
|
||||
|
||||
public nint Handle => IntPtr.Zero;
|
||||
public nint Handle => nint.Zero;
|
||||
|
||||
public IPEndPoint RemoteEndPoint => Socket.RemoteEndPoint as IPEndPoint;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Ssl
|
||||
public ISslService(ServiceCtx context) { }
|
||||
|
||||
[CommandCmif(0)]
|
||||
// CreateContext(nn::ssl::sf::SslVersion, u64, pid) -> object<nn::ssl::sf::ISslContext>
|
||||
// CreateContext(nn::ssl::sf::SslVersion, u64 pid_placeholder, pid) -> object<nn::ssl::sf::ISslContext>
|
||||
public ResultCode CreateContext(ServiceCtx context)
|
||||
{
|
||||
SslVersion sslVersion = (SslVersion)context.RequestData.ReadUInt32();
|
||||
@@ -126,14 +126,18 @@ namespace Ryujinx.HLE.HOS.Services.Ssl
|
||||
}
|
||||
|
||||
[CommandCmif(100)]
|
||||
// CreateContextForSystem(u64 pid, nn::ssl::sf::SslVersion, u64)
|
||||
// CreateContextForSystem(nn::ssl::sf::SslVersion, u64 pid_placeholder, pid) -> object<nn::ssl::sf::ISslContextForSystem>
|
||||
public ResultCode CreateContextForSystem(ServiceCtx context)
|
||||
{
|
||||
ulong pid = context.RequestData.ReadUInt64();
|
||||
SslVersion sslVersion = (SslVersion)context.RequestData.ReadUInt32();
|
||||
#pragma warning disable IDE0059 // Remove unnecessary value assignment
|
||||
ulong pidPlaceholder = context.RequestData.ReadUInt64();
|
||||
#pragma warning restore IDE0059
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { pid, sslVersion, pidPlaceholder });
|
||||
// Note: We use ISslContext here instead of ISslContextForSystem class because Ryujinx implements both in one class.
|
||||
MakeObject(context, new ISslContext(context.Request.HandleDesc.PId, sslVersion));
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { sslVersion });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
@@ -20,5 +20,7 @@ namespace Ryujinx.HLE.HOS.SystemState
|
||||
SimplifiedChinese,
|
||||
TraditionalChinese,
|
||||
BrazilianPortuguese,
|
||||
Polish,
|
||||
Thai,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,9 @@ namespace Ryujinx.HLE.HOS.SystemState
|
||||
"es-419",
|
||||
"zh-Hans",
|
||||
"zh-Hant",
|
||||
"pt-BR"
|
||||
"pt-BR",
|
||||
"pl",
|
||||
"th"
|
||||
];
|
||||
|
||||
internal long DesiredKeyboardLayout { get; private set; }
|
||||
|
||||
@@ -18,5 +18,7 @@ namespace Ryujinx.HLE.HOS.SystemState
|
||||
TraditionalChinese,
|
||||
SimplifiedChinese,
|
||||
BrazilianPortuguese,
|
||||
Polish,
|
||||
Thai,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ using Ryujinx.HLE.Loaders.Executables;
|
||||
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
@@ -27,10 +28,15 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||
|
||||
private ulong _latestPid;
|
||||
|
||||
public ProcessResult ActiveApplication
|
||||
public ProcessResult? ActiveApplication
|
||||
{
|
||||
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))
|
||||
throw new RyujinxException(
|
||||
$"The HLE Process map did not have a process with ID {_latestPid}. Are you missing firmware?");
|
||||
@@ -144,7 +150,7 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||
public bool LoadUnpackedNca(string exeFsDirPath, string romFsPath = null)
|
||||
{
|
||||
ProcessResult processResult = new LocalFileSystem(exeFsDirPath).Load(_device, romFsPath);
|
||||
|
||||
|
||||
if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult))
|
||||
{
|
||||
if (processResult.Start(_device))
|
||||
|
||||
@@ -4,7 +4,6 @@ using LibHac.Ns;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.HOS.SystemState;
|
||||
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||
using Ryujinx.Horizon.Common;
|
||||
|
||||
namespace Ryujinx.HLE.Loaders.Processes
|
||||
@@ -52,6 +51,7 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||
|
||||
if (metaLoader is not null)
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application,$"metaLoader: {metaLoader}");
|
||||
ulong programId = metaLoader.ProgramId;
|
||||
|
||||
Name = ApplicationControlProperties.Title[(int)titleLanguage].NameString.ToString();
|
||||
@@ -71,8 +71,15 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||
ProgramId = programId;
|
||||
ProgramIdText = $"{programId:x16}";
|
||||
Is64Bit = metaLoader.IsProgram64Bit;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application,$"metaLoader is null !!!");
|
||||
ProcessId = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
DiskCacheEnabled = diskCacheEnabled;
|
||||
AllowCodeMemoryForJit = allowCodeMemoryForJit;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,9 @@
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
|
||||
<PackageReference Include="MsgPack.Cli" />
|
||||
<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="Open.NAT.Core" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -33,7 +33,20 @@ namespace Ryujinx.Horizon.Prepo.Ipc
|
||||
[CmifCommand(10100)] // 1.0.0-5.1.0
|
||||
[CmifCommand(10102)] // 6.0.0-9.2.0
|
||||
[CmifCommand(10104)] // 10.0.0+
|
||||
public Result SaveReport([Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid)
|
||||
public Result SaveReportOld([Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid)
|
||||
{
|
||||
if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0)
|
||||
{
|
||||
return PrepoResult.PermissionDenied;
|
||||
}
|
||||
|
||||
ProcessPlayReport(PlayReportKind.Normal, gameRoomBuffer, reportBuffer, pid, Uid.Null);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(10106)] // 21.0.0+
|
||||
public Result SaveReport([Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid, bool optInCheckEnabled)
|
||||
{
|
||||
if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0)
|
||||
{
|
||||
@@ -48,7 +61,20 @@ namespace Ryujinx.Horizon.Prepo.Ipc
|
||||
[CmifCommand(10101)] // 1.0.0-5.1.0
|
||||
[CmifCommand(10103)] // 6.0.0-9.2.0
|
||||
[CmifCommand(10105)] // 10.0.0+
|
||||
public Result SaveReportWithUser(Uid userId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid)
|
||||
public Result SaveReportWithUserOld(Uid userId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid)
|
||||
{
|
||||
if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0)
|
||||
{
|
||||
return PrepoResult.PermissionDenied;
|
||||
}
|
||||
|
||||
ProcessPlayReport(PlayReportKind.Normal, gameRoomBuffer, reportBuffer, pid, userId, true);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(10107)] // 21.0.0+
|
||||
public Result SaveReportWithUser(Uid userId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid, bool optInCheckEnabled)
|
||||
{
|
||||
if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0)
|
||||
{
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Ns
|
||||
{
|
||||
public struct ApplicationControlProperty
|
||||
{
|
||||
public Array16<ApplicationTitle> Title;
|
||||
/// <summary>
|
||||
/// Use <see cref="Title"/> to access titles instead of accessing them directly.
|
||||
/// </summary>
|
||||
public Array16<ApplicationTitle> TitleBlock;
|
||||
public Array37<byte> Isbn;
|
||||
public StartupUserAccountValue StartupUserAccount;
|
||||
public UserAccountSwitchLockValue UserAccountSwitchLock;
|
||||
@@ -58,7 +65,10 @@ namespace Ryujinx.Horizon.Sdk.Ns
|
||||
public RepairFlagValue RepairFlag;
|
||||
public byte ProgramIndex;
|
||||
public RequiredNetworkServiceLicenseOnLaunchValue RequiredNetworkServiceLicenseOnLaunchFlag;
|
||||
public Array4<byte> Reserved3214;
|
||||
public byte ApplicationErrorCodePrefix;
|
||||
public TitleCompressionValue TitleCompression;
|
||||
public byte AcdIndex;
|
||||
public byte ApparentPlatform;
|
||||
public ApplicationNeighborDetectionClientConfiguration NeighborDetectionClientConfiguration;
|
||||
public ApplicationJitConfiguration JitConfiguration;
|
||||
public RequiredAddOnContentsSetBinaryDescriptor RequiredAddOnContentsSetBinaryDescriptors;
|
||||
@@ -74,6 +84,47 @@ namespace Ryujinx.Horizon.Sdk.Ns
|
||||
public readonly string DisplayVersionString => Encoding.UTF8.GetString(DisplayVersion.AsSpan()).TrimEnd('\0');
|
||||
public readonly string ApplicationErrorCodeCategoryString => Encoding.UTF8.GetString(ApplicationErrorCodeCategory.AsSpan()).TrimEnd('\0');
|
||||
public readonly string BcatPassphraseString => Encoding.UTF8.GetString(BcatPassphrase.AsSpan()).TrimEnd('\0');
|
||||
|
||||
private const int TitleCount = 32;
|
||||
private const int TitleEntrySize = 0x300;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the resolved title entries. When <see cref="TitleCompression"/> is
|
||||
/// <see cref="TitleCompressionValue.Enable"/>, the raw <see cref="TitleBlock"/> bytes are
|
||||
/// decompressed (raw deflate) from 0x3000 into 0x6000 bytes yielding up to 32 entries.
|
||||
/// Otherwise the 16 uncompressed entries from <see cref="TitleBlock"/> are returned directly.
|
||||
/// </summary>
|
||||
public readonly ApplicationTitle[] Title
|
||||
{
|
||||
get
|
||||
{
|
||||
var titles = new ApplicationTitle[TitleCount];
|
||||
|
||||
if (TitleCompression != TitleCompressionValue.Enable)
|
||||
{
|
||||
TitleBlock.AsSpan().CopyTo(titles);
|
||||
|
||||
return titles;
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> titleBytes = MemoryMarshal.AsBytes(TitleBlock.AsSpan());
|
||||
ushort compressedBlobSize = BinaryPrimitives.ReadUInt16LittleEndian(titleBytes);
|
||||
ReadOnlySpan<byte> compressedBlob = titleBytes.Slice(2, compressedBlobSize);
|
||||
|
||||
byte[] decompressed = new byte[TitleCount * TitleEntrySize];
|
||||
|
||||
using (var compressedStream = new MemoryStream(compressedBlob.ToArray()))
|
||||
using (var deflateStream = new DeflateStream(compressedStream, CompressionMode.Decompress))
|
||||
{
|
||||
deflateStream.ReadExactly(decompressed, 0, decompressed.Length);
|
||||
}
|
||||
|
||||
MemoryMarshal.Cast<byte, ApplicationTitle>(decompressed).CopyTo(titles);
|
||||
|
||||
return titles;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ApplicationTitle
|
||||
{
|
||||
@@ -130,6 +181,8 @@ namespace Ryujinx.Horizon.Sdk.Ns
|
||||
TraditionalChinese = 13,
|
||||
SimplifiedChinese = 14,
|
||||
BrazilianPortuguese = 15,
|
||||
Polish = 16,
|
||||
Thai = 17,
|
||||
}
|
||||
|
||||
public enum Organization
|
||||
@@ -302,5 +355,11 @@ namespace Ryujinx.Horizon.Sdk.Ns
|
||||
Deny = 0,
|
||||
Allow = 1,
|
||||
}
|
||||
|
||||
public enum TitleCompressionValue : byte
|
||||
{
|
||||
Disable = 0,
|
||||
Enable = 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,10 @@ namespace Ryujinx.Horizon.Sdk.Prepo
|
||||
{
|
||||
interface IPrepoService : IServiceObject
|
||||
{
|
||||
Result SaveReport(ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid);
|
||||
Result SaveReportWithUser(Uid userId, ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid);
|
||||
Result SaveReportOld(ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid);
|
||||
Result SaveReport(ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid, bool optInCheckEnabled);
|
||||
Result SaveReportWithUserOld(Uid userId, ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid);
|
||||
Result SaveReportWithUser(Uid userId, ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid, bool optInCheckEnabled);
|
||||
Result RequestImmediateTransmission();
|
||||
Result GetTransmissionStatus(out int status);
|
||||
Result GetSystemSessionId(out ulong systemSessionId);
|
||||
|
||||
@@ -9,12 +9,14 @@ namespace Ryujinx.Horizon
|
||||
private readonly Action<ServiceTable> _entrypoint;
|
||||
private readonly ServiceTable _serviceTable;
|
||||
private readonly HorizonOptions _options;
|
||||
public readonly string Name;
|
||||
|
||||
internal ServiceEntry(Action<ServiceTable> entrypoint, ServiceTable serviceTable, HorizonOptions options)
|
||||
internal ServiceEntry(Action<ServiceTable> entrypoint, ServiceTable serviceTable, HorizonOptions options, string name)
|
||||
{
|
||||
_entrypoint = entrypoint;
|
||||
_serviceTable = serviceTable;
|
||||
_options = options;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public void Start(ISyscallApi syscallApi, IVirtualMemoryManager addressSpace, IThreadContext threadContext)
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Ryujinx.Horizon
|
||||
|
||||
void RegisterService<T>() where T : IService
|
||||
{
|
||||
entries.Add(new ServiceEntry(T.Main, this, options));
|
||||
entries.Add(new ServiceEntry(T.Main, this, options, typeof(T).Name));
|
||||
}
|
||||
|
||||
RegisterService<ArpMain>();
|
||||
|
||||
@@ -151,7 +151,9 @@ namespace Ryujinx.Input.SDL3
|
||||
result |= GamepadFeaturesFlag.Led;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -9,10 +9,20 @@ using static SDL.SDL3;
|
||||
|
||||
namespace Ryujinx.Input.SDL3
|
||||
{
|
||||
|
||||
|
||||
public unsafe class SDL3GamepadDriver : IGamepadDriver
|
||||
{
|
||||
private readonly Dictionary<SDL_JoystickID, string> _gamepadsInstanceIdsMapping;
|
||||
private readonly Dictionary<SDL_JoystickID, string> _gamepadsIds;
|
||||
/// <summary>
|
||||
/// Unlinked joy-cons
|
||||
/// </summary>
|
||||
private readonly Dictionary<SDL_JoystickID, string> _joyConsIds;
|
||||
/// <summary>
|
||||
/// Linked joy-cons, remove dual joy-con from <c>_gamepadsIds</c> when a linked joy-con is removed
|
||||
/// </summary>
|
||||
private readonly Dictionary<SDL_JoystickID,string> _linkedJoyConsIds;
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
public ReadOnlySpan<string> GamepadsIds
|
||||
@@ -21,7 +31,11 @@ namespace Ryujinx.Input.SDL3
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _gamepadsIds.Values.ToArray();
|
||||
List<string> temp = [];
|
||||
temp.AddRange(_gamepadsIds.Values);
|
||||
temp.AddRange(_joyConsIds.Values);
|
||||
temp.AddRange(_linkedJoyConsIds.Values);
|
||||
return temp.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +49,8 @@ namespace Ryujinx.Input.SDL3
|
||||
{
|
||||
_gamepadsInstanceIdsMapping = new Dictionary<SDL_JoystickID, string>();
|
||||
_gamepadsIds = [];
|
||||
_joyConsIds = [];
|
||||
_linkedJoyConsIds = [];
|
||||
|
||||
SDL3Driver.Instance.Initialize();
|
||||
SDL3Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected;
|
||||
@@ -92,7 +108,7 @@ namespace Ryujinx.Input.SDL3
|
||||
int guidIndex = 0;
|
||||
id = guidIndex + "-" + guidString;
|
||||
|
||||
while (_gamepadsIds.ContainsValue(id))
|
||||
while (_gamepadsIds.ContainsValue(id) || _joyConsIds.ContainsValue(id) || _linkedJoyConsIds.ContainsValue(id))
|
||||
{
|
||||
id = (++guidIndex) + "-" + guidString;
|
||||
}
|
||||
@@ -104,16 +120,47 @@ namespace Ryujinx.Input.SDL3
|
||||
private void HandleJoyStickDisconnected(SDL_JoystickID joystickInstanceId)
|
||||
{
|
||||
bool joyConPairDisconnected = false;
|
||||
string fakeId = null;
|
||||
|
||||
if (!_gamepadsInstanceIdsMapping.Remove(joystickInstanceId, out string id))
|
||||
return;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_gamepadsIds.Remove(joystickInstanceId);
|
||||
if (!SDL3JoyConPair.IsCombinable(_gamepadsIds))
|
||||
if (!_linkedJoyConsIds.ContainsKey(joystickInstanceId))
|
||||
{
|
||||
_gamepadsIds.Remove(GetInstanceIdFromId(SDL3JoyConPair.Id));
|
||||
if (!_joyConsIds.Remove(joystickInstanceId))
|
||||
{
|
||||
_gamepadsIds.Remove(joystickInstanceId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (string matchId in _gamepadsIds.Values)
|
||||
{
|
||||
if (matchId.Contains(id))
|
||||
{
|
||||
fakeId = matchId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
string leftId = fakeId!.Split('_')[0];
|
||||
string rightId = fakeId!.Split('_')[1];
|
||||
|
||||
if (leftId == id)
|
||||
{
|
||||
_linkedJoyConsIds.Remove(GetInstanceIdFromId(rightId));
|
||||
_joyConsIds.Add(GetInstanceIdFromId(rightId), rightId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_linkedJoyConsIds.Remove(GetInstanceIdFromId(leftId));
|
||||
_joyConsIds.Add(GetInstanceIdFromId(leftId), leftId);
|
||||
}
|
||||
|
||||
_linkedJoyConsIds.Remove(joystickInstanceId);
|
||||
_gamepadsIds.Remove(GetInstanceIdFromId(fakeId));
|
||||
joyConPairDisconnected = true;
|
||||
}
|
||||
}
|
||||
@@ -121,13 +168,14 @@ namespace Ryujinx.Input.SDL3
|
||||
OnGamepadDisconnected?.Invoke(id);
|
||||
if (joyConPairDisconnected)
|
||||
{
|
||||
OnGamepadDisconnected?.Invoke(SDL3JoyConPair.Id);
|
||||
OnGamepadDisconnected?.Invoke(fakeId);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleJoyStickConnected(SDL_JoystickID joystickInstanceId)
|
||||
{
|
||||
bool joyConPairConnected = false;
|
||||
string fakeId = null;
|
||||
|
||||
if (SDL_IsGamepad(joystickInstanceId))
|
||||
{
|
||||
@@ -149,27 +197,40 @@ namespace Ryujinx.Input.SDL3
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
|
||||
_gamepadsIds.Add(joystickInstanceId, id);
|
||||
|
||||
if (SDL3JoyConPair.IsCombinable(_gamepadsIds))
|
||||
if (!SDL3JoyCon.IsJoyCon(joystickInstanceId))
|
||||
{
|
||||
// TODO - It appears that you can only have one joy con pair connected at a time?
|
||||
// This was also the behavior before SDL3
|
||||
_gamepadsIds.Remove(GetInstanceIdFromId(SDL3JoyConPair.Id));
|
||||
uint fakeInstanceID = uint.MaxValue;
|
||||
while (!_gamepadsIds.TryAdd((SDL_JoystickID)fakeInstanceID, SDL3JoyConPair.Id))
|
||||
_gamepadsIds.Add(joystickInstanceId, id);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SDL3JoyConPair.IsCombinable(joystickInstanceId, _joyConsIds, out SDL_JoystickID match))
|
||||
{
|
||||
fakeInstanceID--;
|
||||
_joyConsIds.Remove(match, out string matchId);
|
||||
_linkedJoyConsIds.Add(joystickInstanceId, id);
|
||||
_linkedJoyConsIds.Add(match, matchId);
|
||||
|
||||
uint fakeInstanceId = uint.MaxValue;
|
||||
fakeId = SDL3JoyCon.IsLeftJoyCon(joystickInstanceId)
|
||||
? $"{id}_{matchId}"
|
||||
: $"{matchId}_{id}";
|
||||
while (!_gamepadsIds.TryAdd((SDL_JoystickID)fakeInstanceId, fakeId))
|
||||
{
|
||||
fakeInstanceId--;
|
||||
}
|
||||
_gamepadsInstanceIdsMapping.Add((SDL_JoystickID)fakeInstanceId, fakeId);
|
||||
joyConPairConnected = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_joyConsIds.Add(joystickInstanceId, id);
|
||||
}
|
||||
joyConPairConnected = true;
|
||||
}
|
||||
}
|
||||
|
||||
OnGamepadConnected?.Invoke(id);
|
||||
if (joyConPairConnected)
|
||||
{
|
||||
OnGamepadConnected?.Invoke(SDL3JoyConPair.Id);
|
||||
OnGamepadConnected?.Invoke(fakeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,10 +254,22 @@ namespace Ryujinx.Input.SDL3
|
||||
{
|
||||
OnGamepadDisconnected?.Invoke(gamepad.Value);
|
||||
}
|
||||
|
||||
foreach (var gamepad in _joyConsIds)
|
||||
{
|
||||
OnGamepadDisconnected?.Invoke(gamepad.Value);
|
||||
}
|
||||
|
||||
foreach (var gamepad in _linkedJoyConsIds)
|
||||
{
|
||||
OnGamepadDisconnected?.Invoke(gamepad.Value);
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_gamepadsIds.Clear();
|
||||
_joyConsIds.Clear();
|
||||
_linkedJoyConsIds.Clear();
|
||||
}
|
||||
|
||||
SDL3Driver.Instance.Dispose();
|
||||
@@ -215,11 +288,27 @@ namespace Ryujinx.Input.SDL3
|
||||
|
||||
public IGamepad GetGamepad(string id)
|
||||
{
|
||||
if (id == SDL3JoyConPair.Id)
|
||||
// joy-con pair ids is the combined ids of its parts which are split using a '_'
|
||||
if (id.Contains('_'))
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return SDL3JoyConPair.GetGamepad(_gamepadsIds);
|
||||
string leftId = id.Split('_')[0];
|
||||
string rightId = id.Split('_')[1];
|
||||
|
||||
SDL_JoystickID leftInstanceId = GetInstanceIdFromId(leftId);
|
||||
SDL_JoystickID rightInstanceId = GetInstanceIdFromId(rightId);
|
||||
|
||||
SDL_Gamepad* leftGamepadHandle = SDL_OpenGamepad(leftInstanceId);
|
||||
SDL_Gamepad* rightGamepadHandle = SDL_OpenGamepad(rightInstanceId);
|
||||
|
||||
if (leftGamepadHandle == null || rightGamepadHandle == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SDL3JoyConPair(new SDL3JoyCon(leftGamepadHandle, leftId),
|
||||
new SDL3JoyCon(rightGamepadHandle, rightId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,7 +321,7 @@ namespace Ryujinx.Input.SDL3
|
||||
return null;
|
||||
}
|
||||
|
||||
if (SDL_GetGamepadName(gamepadHandle).StartsWith(SDL3JoyCon.Prefix))
|
||||
if (SDL3JoyCon.IsJoyCon(instanceId))
|
||||
{
|
||||
return new SDL3JoyCon(gamepadHandle, id);
|
||||
}
|
||||
@@ -242,12 +331,18 @@ namespace Ryujinx.Input.SDL3
|
||||
|
||||
public IEnumerable<IGamepad> GetGamepads()
|
||||
{
|
||||
lock (_gamepadsIds)
|
||||
string[] ids;
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (var gamepad in _gamepadsIds)
|
||||
{
|
||||
yield return GetGamepad(gamepad.Value);
|
||||
}
|
||||
ids = _gamepadsIds.Values
|
||||
.Concat(_joyConsIds.Values)
|
||||
.Concat(_linkedJoyConsIds.Values)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
foreach (string id in ids)
|
||||
{
|
||||
yield return GetGamepad(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,10 @@ namespace Ryujinx.Input.SDL3
|
||||
private readonly Dictionary<GamepadButtonInputId, SDL_GamepadButton> _leftButtonsDriverMapping = new()
|
||||
{
|
||||
{GamepadButtonInputId.LeftStick, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_STICK},
|
||||
{GamepadButtonInputId.DpadUp, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_NORTH},
|
||||
{GamepadButtonInputId.DpadDown, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_SOUTH},
|
||||
{GamepadButtonInputId.DpadLeft, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_EAST},
|
||||
{GamepadButtonInputId.DpadRight, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_WEST},
|
||||
{GamepadButtonInputId.DpadUp, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_WEST},
|
||||
{GamepadButtonInputId.DpadDown, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_EAST},
|
||||
{GamepadButtonInputId.DpadLeft, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_SOUTH},
|
||||
{GamepadButtonInputId.DpadRight, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_NORTH},
|
||||
{GamepadButtonInputId.Minus, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_START},
|
||||
{GamepadButtonInputId.LeftShoulder, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_PADDLE1},
|
||||
{GamepadButtonInputId.LeftTrigger, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_PADDLE2},
|
||||
@@ -37,10 +37,10 @@ namespace Ryujinx.Input.SDL3
|
||||
private readonly Dictionary<GamepadButtonInputId, SDL_GamepadButton> _rightButtonsDriverMapping = new()
|
||||
{
|
||||
{GamepadButtonInputId.RightStick, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_STICK},
|
||||
{GamepadButtonInputId.A, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_EAST},
|
||||
{GamepadButtonInputId.B, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_NORTH},
|
||||
{GamepadButtonInputId.X, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_SOUTH},
|
||||
{GamepadButtonInputId.Y, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_WEST},
|
||||
{GamepadButtonInputId.A, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_SOUTH},
|
||||
{GamepadButtonInputId.B, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_WEST},
|
||||
{GamepadButtonInputId.X, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_EAST},
|
||||
{GamepadButtonInputId.Y, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_NORTH},
|
||||
{GamepadButtonInputId.Plus, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_START},
|
||||
{GamepadButtonInputId.RightShoulder, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1},
|
||||
{GamepadButtonInputId.RightTrigger, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2},
|
||||
@@ -398,5 +398,15 @@ namespace Ryujinx.Input.SDL3
|
||||
|
||||
return SDL_GetGamepadButton(_gamepadHandle, button);
|
||||
}
|
||||
|
||||
public static bool IsJoyCon(SDL_JoystickID gamepadsId)
|
||||
{
|
||||
return SDL_GetGamepadNameForID(gamepadsId) is LeftName or RightName;
|
||||
}
|
||||
|
||||
public static bool IsLeftJoyCon(SDL_JoystickID gamepadsId)
|
||||
{
|
||||
return SDL_GetGamepadNameForID(gamepadsId) is LeftName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Ryujinx.Input.SDL3
|
||||
public const string Id = "JoyConPair";
|
||||
string IGamepad.Id => Id;
|
||||
|
||||
public string Name => "* Nintendo Switch Joy-Con (L/R)";
|
||||
public string Name => "Nintendo Switch Dual Joy-Con (L/R)";
|
||||
public bool IsConnected => left is { IsConnected: true } && right is { IsConnected: true };
|
||||
|
||||
public void Dispose()
|
||||
@@ -96,44 +96,23 @@ namespace Ryujinx.Input.SDL3
|
||||
right.SetTriggerThreshold(triggerThreshold);
|
||||
}
|
||||
|
||||
public static bool IsCombinable(Dictionary<SDL_JoystickID, string> gamepadsIds)
|
||||
public static bool IsCombinable(SDL_JoystickID joyCon1, Dictionary<SDL_JoystickID, string> joyConIds, out SDL_JoystickID match)
|
||||
{
|
||||
(int leftIndex, int rightIndex) = DetectJoyConPair(gamepadsIds);
|
||||
return leftIndex >= 0 && rightIndex >= 0;
|
||||
}
|
||||
bool isLeft = SDL3JoyCon.IsLeftJoyCon(joyCon1);
|
||||
string matchName = isLeft ? SDL3JoyCon.RightName : SDL3JoyCon.LeftName;
|
||||
match = 0;
|
||||
|
||||
private static (int leftIndex, int rightIndex) DetectJoyConPair(Dictionary<SDL_JoystickID, string> gamepadsIds)
|
||||
{
|
||||
Dictionary<string, SDL_JoystickID> gamepadNames = gamepadsIds
|
||||
.Where(gamepadId => gamepadId.Value != Id && SDL_GetGamepadNameForID(gamepadId.Key) is SDL3JoyCon.LeftName or SDL3JoyCon.RightName)
|
||||
.Select(gamepad => (SDL_GetGamepadNameForID(gamepad.Key), gamepad.Key))
|
||||
.ToDictionary();
|
||||
SDL_JoystickID idx;
|
||||
int leftIndex = gamepadNames.TryGetValue(SDL3JoyCon.LeftName, out idx) ? (int)idx : -1;
|
||||
int rightIndex = gamepadNames.TryGetValue(SDL3JoyCon.RightName, out idx) ? (int)idx : -1;
|
||||
|
||||
return (leftIndex, rightIndex);
|
||||
}
|
||||
|
||||
public unsafe static IGamepad GetGamepad(Dictionary<SDL_JoystickID, string> gamepadsIds)
|
||||
{
|
||||
(int leftIndex, int rightIndex) = DetectJoyConPair(gamepadsIds);
|
||||
|
||||
if (leftIndex <= 0 || rightIndex <= 0)
|
||||
foreach (var joyConId in joyConIds.Keys)
|
||||
{
|
||||
return null;
|
||||
if (SDL_GetGamepadNameForID(joyConId) == matchName)
|
||||
{
|
||||
match = joyConId;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Gamepad* leftGamepadHandle = SDL_OpenGamepad((SDL_JoystickID)leftIndex);
|
||||
SDL_Gamepad* rightGamepadHandle = SDL_OpenGamepad((SDL_JoystickID)rightIndex);
|
||||
|
||||
if (leftGamepadHandle == null || rightGamepadHandle == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SDL3JoyConPair(new SDL3JoyCon(leftGamepadHandle, gamepadsIds[(SDL_JoystickID)leftIndex]),
|
||||
new SDL3JoyCon(rightGamepadHandle, gamepadsIds[(SDL_JoystickID)rightIndex]));
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,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 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} " +
|
||||
$"L.low.amp={leftVibrationValue.AmplitudeLow}, " +
|
||||
|
||||
@@ -159,7 +159,7 @@ namespace Ryujinx.Memory.WindowsShared
|
||||
{
|
||||
SplitForMap((ulong)location, (ulong)size, srcOffset);
|
||||
|
||||
IntPtr ptr = WindowsApi.MapViewOfFile3(
|
||||
nint ptr = WindowsApi.MapViewOfFile3(
|
||||
sharedMemory,
|
||||
WindowsApi.CurrentProcessHandle,
|
||||
location,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.SDL3-CS" />
|
||||
<PackageReference Include="Ryujinx.SDL3-CS" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect
|
||||
public void EnsureTypeSize()
|
||||
{
|
||||
Assert.AreEqual(0x18, Unsafe.SizeOf<BiquadFilterEffectParameter1>());
|
||||
Assert.AreEqual(0x24, Unsafe.SizeOf<BiquadFilterEffectParameter2>());
|
||||
Assert.AreEqual(0x28, Unsafe.SizeOf<BiquadFilterEffectParameter2>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ namespace Ryujinx.Tests.Memory
|
||||
|
||||
// Create some info to be used for managing the native writing loop.
|
||||
int stateSize = Unsafe.SizeOf<NativeWriteLoopState>();
|
||||
IntPtr statePtr = Marshal.AllocHGlobal(stateSize);
|
||||
nint statePtr = Marshal.AllocHGlobal(stateSize);
|
||||
Unsafe.InitBlockUnaligned((void*)statePtr, 0, (uint)stateSize);
|
||||
|
||||
ref NativeWriteLoopState writeLoopState = ref Unsafe.AsRef<NativeWriteLoopState>((void*)statePtr);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user