Compare commits

..

108 Commits

Author SHA1 Message Date
Babib3l
0a59f8d154 Clear stuck keyboard input when windows lose focus 2026-04-08 19:48:37 +02:00
Babib3l
031cd90048 Log keyboard UI events only on key state changes 2026-04-08 18:19:19 +02:00
Babib3l
8d5adfed14 Gate keyboard event logs behind the Avalonia UI log setting 2026-04-08 17:59:16 +02:00
Babib3l
5aab5f205d Merge branch ryujinx:master into keyboard-localisation-fracture-share 2026-04-02 15:54:30 +02:00
Babib3l
bfff9ff780 Merge branch ryujinx:master into keyboard-localisation-fracture-share 2026-04-01 18:38:59 +02:00
Babib3l
2a28dbde83 Add non functional reset keybinds button and refresh labels 2026-04-01 15:34:08 +02:00
Babib3l
70207cd374 Fix macOS Caps Lock capture in Avalonia keyboard driver 2026-04-01 15:14:28 +02:00
Babib3l
23b9a47d08 Refresh input modified state only when keybinds actually change 2026-03-30 22:04:23 +02:00
Babib3l
fa0696ca27 Rename and privatize input device refresh helper 2026-03-30 21:40:32 +02:00
Babib3l
087655972d Restore input device default reset behavior 2026-03-30 21:36:53 +02:00
Babib3l
25306c221d Add UI keyboard trace logs for key state and rebinding 2026-03-30 20:07:22 +02:00
_Neo_
cd1ce67f89 Improve 1 locale ID 2026-03-30 20:25:41 +03:00
_Neo_
4c38af62ae Revert "Update Locale ID's for better readability"
Reverting the "readability" updates to the locale IDs due to helpful feedback from LotP. If these go get changed, then only in a similar MR.
2026-03-30 20:22:03 +03:00
Babib3l
903cb3f22f Merge branch ryujinx:master into keyboard-localisation-fracture-share 2026-03-29 13:23:49 +02:00
Babib3l
d3f6460fdf Fix macOS Caps Lock rebinding by buffering one-shot key-down events during keyboard assignment 2026-03-24 22:45:37 +01:00
Babib3l
c1845aac4d Copilot key fix 2026-03-24 12:49:07 +01:00
Babib3l
943d69e93d Sh0inx line review Fixes 2026-03-24 12:45:25 +01:00
_Neo_
ce07a2be68 Update Locale ID's for better readability 2026-03-22 22:37:27 +02:00
_Neo_
a8c18a9853 Update all Keyboard Labels
Moves keyboard labels that were updated in the UI input MR, to this MR, due to it fitting the purpose better and to avoid double work.
2026-03-22 21:59:15 +02:00
Babib3l
c8367335d4 Fix input settings device selection state 2026-03-22 18:15:12 +01:00
Babib3l
ca015200f1 Persist observed physical key labels 2026-03-22 17:29:55 +01:00
Babib3l
5809661664 fix for caps lock 2026-03-22 12:43:52 +01:00
Babib3l
9aeb0c8c8c Fallback to keyboard on controller disconnect 2026-03-22 01:05:17 +01:00
Babib3l
d1464eb5f2 Simplify keyboard input cleanup paths 2026-03-22 00:40:45 +01:00
Babib3l
e4b920002f Fix input device refresh button 2026-03-21 23:44:39 +01:00
Babib3l
0b02e71a66 fixed KeyDown events latching gameplay keys 2026-03-21 23:32:46 +01:00
Babib3l
7becde9d8e Fix settings keyboard input focus loss 2026-03-21 22:45:19 +01:00
Babib3l
9a5e4c06af Merge branch 'keyboard-localisation-fracture-share' of https://git.ryujinx.app/babib3l/Ryujinx into keyboard-localisation-fracture-share 2026-03-21 21:33:56 +01:00
Babib3l
cd4aa41a8f Guard input profile loading when config is missing 2026-03-21 17:55:04 +01:00
_Neo_
b3d18f7845 Initial Prep for Keyboard Label Transfer
Preparing to transfer updated keyboard labels from another MR into this one.
2026-03-20 22:55:47 +02:00
Babib3l
3cbe372b18 Simplify gameplay keyboard physical-key paths 2026-03-19 20:45:48 +01:00
Babib3l
327f90b420 Refresh keyboard labels when layout changes 2026-03-19 20:25:01 +01:00
Babib3l
818399ecfc add .dotnet-home/ to .gitignore 2026-03-18 22:12:24 +01:00
Babib3l
1e5c4fedbd Fix Build error 2026-03-18 21:55:50 +01:00
Babib3l
3401c29b81 Overall Code Cleanup 2026-03-18 21:34:13 +01:00
Babib3l
39f58e453b Track keyboard labels from host layout events 2026-03-18 21:19:35 +01:00
Babib3l
84f3ce2ca5 Update keyboard localisation refactor snapshot 2026-03-18 21:10:28 +01:00
Babib3l
2fe5e8c40d Cache Avalonia keyboard LED state 2026-03-18 20:21:24 +01:00
Babib3l
ebd8cc4f4a Added better support for the different keyboards. 2026-03-18 18:17:06 +01:00
Babib3l
13c8b57063 Fix AltGr key assignment and silence keyboard SetLed logs 2026-03-18 17:13:43 +01:00
Babib3l
32f603d7ad Merge branch ryujinx:master into keyboard-localisation-fracture 2026-03-18 16:42:11 +01:00
Babib3l
7df5299d5a Split keyboard localisation into dedicated locale file 2026-03-18 16:38:31 +01:00
Babib3l
1b7ffbe723 Merge branch ryujinx:master into master 2026-03-14 22:28:08 +01:00
Babib3l
d23b2c162b Merge branch ryujinx:master into master 2026-03-02 17:36:53 +01:00
Babib3l
128e16b9d3 Merge branch ryujinx:master into master 2026-03-01 13:04:54 +01:00
Babib3l
0fff818fdf Merge branch ryujinx:master into master 2026-02-11 17:12:12 +01:00
Babib3l
5eb5eeb285 Merge branch ryujinx:master into master 2026-02-03 09:53:56 +01:00
Babib3l
aabbb3c5d2 Merge branch ryujinx:master into master 2026-01-28 14:12:30 +01:00
Babib3l
b8d5744fd3 Merge branch ryujinx:master into master 2026-01-28 13:54:25 +01:00
Babib3l
5954f8f3b7 Merge branch ryujinx:master into master 2026-01-05 22:38:20 +01:00
Babib3l3l
041c088d61 french and spanish translations 2026-01-03 13:36:36 +01:00
Babib3l
ddd9ba8aba Merge branch ryujinx:master into master 2026-01-03 13:17:06 +01:00
Babib3l
f788e1211d Merge branch ryujinx:master into master 2025-11-18 12:43:50 +01:00
Babib3l
d34aa0e549 Merge branch ryujinx:master into master 2025-11-15 15:26:12 +01:00
Babib3l
fb881986ce Merge branch ryujinx:master into master 2025-11-12 15:32:58 +01:00
Babib3l
f5d87f3bb7 Merge branch ryujinx:master into master 2025-11-11 00:59:25 +01:00
Babib3l
8ddb0c16c3 Merge branch ryujinx:master into master 2025-11-10 23:46:28 +01:00
Babib3l
54f08acf2c Merge branch ryujinx:master into master 2025-11-10 15:37:20 +01:00
Babib3l
3c550deeb7 Merge branch ryujinx:master into master 2025-11-08 16:31:23 +01:00
Babib3l
f2e2e93ea2 Merge branch ryujinx:master into master 2025-11-07 18:38:10 +01:00
Babib3l
3361ad933f Merge branch ryujinx:master into master 2025-11-05 17:41:48 +01:00
Babib3l
27c3231433 nullification of a french translation 2025-10-31 18:00:21 +01:00
Babib3l
3d25b9940e Merge branch ryujinx:master into master 2025-10-31 17:27:32 +01:00
Babib3l
b5f6e68e55 Merge branch ryujinx:master into master 2025-10-30 11:01:10 +01:00
Babib3l
69ec2ef1be Merge branch ryujinx:master into master 2025-10-29 20:30:26 +01:00
Babib3l
07491eeaf4 Merge branch ryujinx:master into master 2025-10-28 13:57:59 +01:00
Babib3l
d9d9c69a15 Merge branch ryujinx:master into master 2025-10-27 13:31:12 +01:00
Babib3l
5327853f72 Update file locales.json 2025-10-26 22:10:25 +01:00
Babib3l
1ab78040aa more general fixes 2025-10-26 22:07:17 +01:00
Babib3l
726491d0ba Fix Debug being Deboguage in some fr_FR translations for consistency 2025-10-26 21:59:20 +01:00
Babib3l
b1bd469897 Update file locales.json 2025-10-26 21:53:23 +01:00
Babib3l
2c53c5bb06 Merge branch ryujinx:master into master 2025-10-26 21:52:09 +01:00
Babib3l
2a74d2284d Capitalisation fix 2025-10-26 11:12:44 +01:00
Babib3l
c980dc00aa Update file locales.json 2025-10-26 11:10:15 +01:00
Babib3l
c7c8086f9f Merge branch ryujinx:master into master 2025-10-26 11:01:11 +01:00
Babib3l
0c8c1be821 Merge branch ryujinx:master into master 2025-10-25 13:56:44 +02:00
Babib3l
1c3ed0d168 Merge branch ryujinx:master into master 2025-10-24 18:37:51 +02:00
Babib3l
a605f7fafc Merge branch ryujinx:master into master 2025-10-24 15:21:19 +02:00
Babib3l
03ee34e016 Merge branch ryujinx:master into master 2025-10-23 12:17:56 +02:00
Babib3l
8dff5a2556 Merge branch ryujinx:master into master 2025-10-22 20:06:58 +02:00
Babib3l
75faee906d Merge branch ryujinx:master into master 2025-10-21 14:47:14 +02:00
Babib3l
9beb4efb56 Merge branch ryujinx:master into master 2025-10-20 10:55:22 +02:00
Babib3l
914d4c8a79 Update file locales.json 2025-10-18 19:05:40 +02:00
Babib3l
2a999912ea Merge branch ryujinx:master into master 2025-10-18 18:41:46 +02:00
Babib3l
c02263abd7 Update file locales.json 2025-10-18 18:41:35 +02:00
Babib3l
02c7d0706a Merge branch ryujinx:master into master 2025-10-16 13:22:54 +02:00
Babib3l
028425982c Merge branch ryujinx:master into master 2025-10-14 19:15:24 +02:00
Babib3l
a2bb436e40 Merge branch ryujinx:master into master 2025-10-12 13:10:27 +02:00
Babib3l
9e1f6db406 Merge branch ryujinx:master into master 2025-09-30 17:03:59 +02:00
Babib3l
f84ee55307 Merge branch ryujinx:master into master 2025-09-05 13:07:45 +02:00
Babib3l
f045f4acd4 Merge branch ryujinx:master into master 2025-09-01 19:26:03 +02:00
Babib3l
f389415b0a Merge branch ryujinx:master into master 2025-08-31 15:36:29 +02:00
Babib3l
b4bde4ccb8 Merge branch ryujinx:master into master 2025-08-28 21:46:19 +02:00
Babib3l
c76eda2c1a Update file locales.json 2025-08-28 15:54:51 +02:00
Babib3l
59eba8f38b Update file locales.json 2025-08-28 15:48:41 +02:00
Babib3l
fc62ae41ae Update file locales.json 2025-08-28 14:52:22 +02:00
Babib3l
127d0c7ac1 Update file locales.json 2025-08-28 14:10:51 +02:00
Babib3l
15b44cfea6 Merge branch ryujinx:master into master 2025-08-28 14:08:37 +02:00
Babib3l
07eddefc95 Merge branch ryujinx:master into master 2025-08-27 13:03:36 +02:00
Babib3l
e3ea13bc45 Update file locales.json 2025-08-25 12:48:13 +02:00
Babib3l
0684d60c8c Merge branch ryujinx:master into master 2025-08-25 12:43:20 +02:00
Babib3l
6bf57c5ffb Merge branch ryujinx:master into master 2025-08-24 18:35:45 +02:00
Babib3l
e4a927f7a1 Merge branch ryujinx:master into master 2025-08-02 13:31:33 +02:00
Babib3l
e1e4c111d1 Merge branch ryujinx:master into master 2025-07-28 13:38:11 +02:00
Babib3l
0b790469a8 Merge branch ryujinx:master into master 2025-07-17 04:43:37 +02:00
Babib3l
385e9c869f Update file locales.json 2025-07-16 15:20:04 +02:00
Babib3l
c5528d59a0 Update file locales.json 2025-07-15 15:46:52 +02:00
Babib3l
6f113c4175 Update file locales.json 2025-07-15 15:43:12 +02:00
161 changed files with 5261 additions and 6106 deletions

View File

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

View File

@@ -1,35 +0,0 @@
{
"$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"
}
]
}

View File

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

86
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,86 @@
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 Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,10 +10,6 @@ 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/**'
@@ -22,17 +18,17 @@ input:
- 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.LocaleGenerator/**']
- any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.UI.Common/**', 'src/Ryujinx.UI.LocaleGenerator/**']
'horizon/hle':
horizon:
- changed-files:
- 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/**']
- any-glob-to-any-file: ['src/Ryujinx.HLE/**', 'src/Ryujinx.Horizon/**']
kernel:
- changed-files:
@@ -40,7 +36,7 @@ kernel:
infra:
- changed-files:
- any-glob-to-any-file: ['.forgejo/**', 'distribution/**', 'Directory.Packages.props', 'src/Ryujinx.BuildValidationTasks/**']
- any-glob-to-any-file: ['.github/**', 'distribution/**', 'Directory.Packages.props', 'src/Ryujinx.BuildValidationTasks/**']
documentation:
- changed-files:
@@ -48,4 +44,4 @@ documentation:
ldn:
- changed-files:
- any-glob-to-any-file: ['src/Ryujinx.HLE/HOS/Services/Ldn/**', 'src/Ryujinx/UI/Windows/LdnGamesListWindow.*', 'src/Ryujinx/UI/ViewModels/LdnGamesListViewModel.cs']
- any-glob-to-any-file: 'src/Ryujinx.HLE/HOS/Services/Ldn/**'

168
.github/workflows/build.yml vendored Normal file
View File

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

View File

@@ -6,7 +6,7 @@ on:
push:
branches: [ master ]
paths-ignore:
- '.forgejo/**'
- '.github/**'
- 'docs/**'
- 'assets/**'
- '*.yml'
@@ -25,41 +25,44 @@ env:
jobs:
release:
name: Release for ${{ matrix.platform.name }}
runs-on: docker
container:
image: ${{ matrix.platform.os }}
runs-on: ${{ matrix.platform.os }}
strategy:
matrix:
platform:
- { 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 }
- { 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 }
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v5
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: Overwrite csc problem matcher
run: echo "::add-matcher::.forgejo/csc.json"
- name: Install GLI
uses: actions/setup-gli@v1
with:
token: ${{ secrets.SETUP_GLI_TOKEN }}
run: echo "::add-matcher::.github/csc.json"
- name: Install 7zip
run: |
sudo apt update && sudo apt install -y 7zip
sudo apt install -y 7zip
- name: Install gli
run: |
mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.31
chmod +x gli
mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Get version info
id: version_info
run: |
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
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
shell: bash
- name: Configure for release
@@ -84,9 +87,12 @@ 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')
@@ -95,8 +101,9 @@ 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)
@@ -105,7 +112,7 @@ jobs:
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
PLATFORM_NAME="${{ matrix.platform.name }}"
sudo apt update && sudo apt install -y zsync desktop-file-utils appstream libfuse2t64
sudo apt install -y zsync desktop-file-utils appstream
mkdir -p tools
export PATH="$PATH:$(readlink -f tools)"
@@ -132,28 +139,17 @@ 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: docker
container:
image: ghcr.io/catthehacker/ubuntu:act-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v5
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
@@ -163,24 +159,33 @@ jobs:
chmod +x llvm.sh
sudo ./llvm.sh 17
- name: Install GLI
uses: actions/setup-gli@v1
with:
token: ${{ secrets.SETUP_GLI_TOKEN }}
- name: Install gli
run: |
mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.30
chmod +x gli
mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install rcodesign
run: |
gli ghr -R indygreg/apple-platform-rs -p apple-codesign-*-x86_64-unknown-linux-musl.tar.gz -O apple-codesign.tar.gz
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 /usr/bin/rcodesign
mv rcodesign $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Get version info
id: version_info
run: |
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
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
shell: bash
- name: Configure for release
@@ -196,53 +201,46 @@ 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
- 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
create_gitlab_release:
name: Create GitLab Release
runs-on: ubuntu-24.04
needs:
- macos_release
- release
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Install GLI
uses: actions/setup-gli@v1
with:
token: ${{ secrets.SETUP_GLI_TOKEN }}
- name: Install gli
run: |
mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.30
chmod +x gli
mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Get version info
id: version_info
run: |
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
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
shell: bash
- name: Create tag
run: |
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 }}
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 }})"
- name: Send notification webhook
run: |
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
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
- name: Notify update server of new builds
run: |

25
.github/workflows/checks.yml vendored Normal file
View File

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

View File

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

View File

@@ -5,22 +5,24 @@ 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@v6
uses: actions/checkout@v4
with:
# Ensure we pin the source origin as pull_request_target run under forks.
fetch-depth: 0
repository: projects/Ryubing
repository: GreemDev/Ryujinx
ref: master
- name: Update labels based on changes
uses: actions/labeler@v6
uses: actions/labeler@v5
with:
repo-token: ${{ secrets.LABELER_TOKEN }}
configuration-path: .forgejo/labeler.yml
sync-labels: true
dot: true

View File

@@ -19,20 +19,18 @@ env:
jobs:
release:
name: Release for ${{ matrix.platform.name }}
runs-on: docker
container:
image: ${{ matrix.platform.os }}
runs-on: ${{ matrix.platform.os }}
strategy:
matrix:
platform:
- { 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 }
- { 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 }
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v5
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
@@ -43,21 +41,26 @@ jobs:
run: |
sudo apt install -y 7zip
- name: Install GLI
uses: actions/setup-gli@v1
with:
token: ${{ secrets.SETUP_GLI_TOKEN }}
- name: Install gli
run: |
mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.31
chmod +x gli
mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Get version info
id: version_info
run: |
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $FORGEJO_OUTPUT
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $GITHUB_OUTPUT
else
echo "build_version=$(gli get-next-version -c Stable -R)" >> $FORGEJO_OUTPUT
echo "build_version=$(gli get-next-version -c Stable -R)" >> $GITHUB_OUTPUT
fi
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
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
shell: bash
- name: Configure for release
@@ -81,9 +84,12 @@ 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')
@@ -92,9 +98,12 @@ 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')
@@ -129,25 +138,17 @@ jobs:
pushd publish_appimage
mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
popd
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/**
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
macos_release:
name: Release MacOS universal
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v5
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
@@ -157,28 +158,37 @@ jobs:
chmod +x llvm.sh
sudo ./llvm.sh 17
- name: Install GLI
uses: actions/setup-gli@v1
with:
token: ${{ secrets.SETUP_GLI_TOKEN }}
- name: Install gli
run: |
mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.30
chmod +x gli
mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install rcodesign
run: |
gli ghr -R indygreg/apple-platform-rs -p apple-codesign-*-x86_64-unknown-linux-musl.tar.gz -O apple-codesign.tar.gz
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 /usr/bin/rcodesign
mv rcodesign $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_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)" >> $FORGEJO_OUTPUT
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $GITHUB_OUTPUT
else
echo "build_version=$(gli get-next-version -c Stable -R)" >> $FORGEJO_OUTPUT
echo "build_version=$(gli get-next-version -c Stable -R)" >> $GITHUB_OUTPUT
fi
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
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
shell: bash
- name: Configure for release
@@ -191,47 +201,49 @@ jobs:
- name: Publish macOS Ryujinx
run: |
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
./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
needs:
- macos_release
- release
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Install GLI
uses: actions/setup-gli@v1
with:
token: ${{ secrets.SETUP_GLI_TOKEN }}
- name: Install gli
run: |
mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.30
chmod +x gli
mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Get version info
id: version_info
run: |
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $FORGEJO_OUTPUT
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $GITHUB_OUTPUT
else
echo "build_version=$(gli get-next-version -c Stable -R)" >> $FORGEJO_OUTPUT
echo "build_version=$(gli get-next-version -c Stable -R)" >> $GITHUB_OUTPUT
fi
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
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
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.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
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
- name: Notify update server of new builds
run: |

3
.gitignore vendored
View File

@@ -72,6 +72,9 @@ ipch/
_ReSharper*/
*.[Rr]e[Ss]harper
#.NET
.dotnet-home/
# TeamCity is a build add-in
_TeamCity*

View File

@@ -3,66 +3,59 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia" Version="11.3.15" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.15" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.15" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.15" />
<PackageVersion Include="SharpCompress" Version="0.48.1" />
<PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.9.5" />
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.9.5" />
<PackageVersion Include="Avalonia" Version="11.3.12" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.12" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.12" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.12" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.12" />
<PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.9.4" />
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.9.4" />
<PackageVersion Include="Microsoft.Build.Framework" Version="17.11.4" />
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.12.50" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.12.6" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<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="Ryujinx.SDL3-CS" Version="2026.501.0" />
<PackageVersion Include="ppy.SDL3-CS" Version="2025.920.0" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageVersion Include="Concentus" Version="2.2.2" />
<PackageVersion Include="DiscordRichPresence" Version="1.6.1.70" />
<PackageVersion Include="DynamicData" Version="9.4.31" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.5.1" />
<PackageVersion Include="DynamicData" Version="9.4.1" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.5.0" />
<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.18.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.3.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<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.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="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="Open.NAT.Core" Version="2.1.0.5" />
<!-- 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.MoltenVK" Version="1.4.2-ryujinx.3" />
<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="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" Version="6.1.2-build3" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.129" />
<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="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="Sep" Version="0.14.1" />
<PackageVersion Include="Sep" Version="0.11.1" />
<PackageVersion Include="shaderc.net" Version="0.1.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.23.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.23.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.23.0" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.22.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.22.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.22.0" />
<PackageVersion Include="SkiaSharp" 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="SkiaSharp.NativeAssets.Linux" Version="2.88.9" />
<PackageVersion Include="SPB" Version="0.0.4-build32" />
<PackageVersion Include="System.IO.Hashing" Version="9.0.15" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.1.3" />
<PackageVersion Include="System.IO.Hashing" Version="9.0.2" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
</ItemGroup>
</Project>

View File

@@ -5,10 +5,10 @@
</td>
<td align="center" width="75%">
<h1 class="ryu-gradient-text">Ryujinx</h1>
# Ryujinx
[![Latest release](https://git.ryujinx.app/projects/Ryubing/badges/release.svg?label=stable&color=32cd32)](https://update.ryujinx.app/latest/stable)
[![Latest canary release](https://git.ryujinx.app/Ryubing/Canary/badges/release.svg?label=canary&color=FF4500)](https://update.ryujinx.app/latest/canary)
[![Latest release](https://img.shields.io/gitlab/v/release/ryubing%2Fryujinx?gitlab_url=https%3A%2F%2Fgit.ryujinx.app&label=stable&color=32cd32)](https://update.ryujinx.app/latest/stable)
[![Latest canary release](https://img.shields.io/gitlab/v/release/ryubing%2Fcanary?gitlab_url=https%3A%2F%2Fgit.ryujinx.app&label=canary&color=FF4500)](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 <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>.
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>.
<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/projects/Ryubing/wiki/Home">Wiki tab</a>.
Guides and documentation can be found on the <a href="https://git.ryujinx.app/groups/ryubing/-/wikis/home">Wiki tab</a>.
</p>
<p align="center">
<img src="https://git.ryujinx.app/projects/Ryubing/raw/branch/master/docs/shell.png" alt="Ryujinx example">
<img src="https://git.ryujinx.app/ryubing/ryujinx/-/raw/master/docs/shell.png?ref_type=heads&inline=false" 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/projects/Ryubing/releases).
You can find the stable releases [here](https://git.ryujinx.app/ryubing/ryujinx/-/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](https://git.ryujinx.app/projects/Ryubing/src/branch/master/docs/README.md).
If you are planning to contribute or just want to learn more about this project please read through our [documentation](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](https://git.ryujinx.app/projects/Ryubing/src/branch/master/LICENSE.txt).
This software is licensed under the terms of the [MIT license](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](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.
See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](distribution/legal/THIRDPARTY.md) for more details.
## Credits
- [LibHac](https://git.ryujinx.app/projects/LibHac) is used for our file-system.
- [LibHac](https://git.ryujinx.app/ryubing/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.

View File

@@ -86,11 +86,11 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.forgejo\workflows\build.yml = .forgejo\workflows\build.yml
.forgejo\workflows\canary.yml = .forgejo\workflows\canary.yml
.github\workflows\build.yml = .github\workflows\build.yml
.github\workflows\canary.yml = .github\workflows\canary.yml
Directory.Packages.props = Directory.Packages.props
Directory.Build.props = Directory.Build.props
.forgejo\workflows\release.yml = .forgejo\workflows\release.yml
.github\workflows\release.yml = .github\workflows\release.yml
nuget.config = nuget.config
EndProjectSection
EndProject
@@ -573,16 +573,6 @@ Global
{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

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
"ID": "MenuBarActions_StartCapture",
"Translations": {
"ar_SA": "",
"de_DE": "RenderDoc Frame-Aufnahme starten",
"de_DE": "",
"el_GR": "",
"en_US": "Start RenderDoc Frame Capture",
"es_ES": "Iniciar una captura de fotograma de RenderDoc",
@@ -22,14 +22,14 @@
"tr_TR": "",
"uk_UA": "",
"zh_CN": "启动 RenderDoc 帧捕获",
"zh_TW": "啟動 RenderDoc 畫格擷取"
"zh_TW": ""
}
},
{
"ID": "MenuBarActions_EndCapture",
"Translations": {
"ar_SA": "",
"de_DE": "RenderDoc Frame-Aufnahme beenden",
"de_DE": "",
"el_GR": "",
"en_US": "End RenderDoc Frame Capture",
"es_ES": "Detener la captura de fotograma de RenderDoc",
@@ -47,14 +47,14 @@
"tr_TR": "",
"uk_UA": "",
"zh_CN": "结束 RenderDoc 帧捕获",
"zh_TW": "停止 RenderDoc 畫格擷取"
"zh_TW": ""
}
},
{
"ID": "MenuBarActions_DiscardCapture",
"Translations": {
"ar_SA": "",
"de_DE": "RenderDoc Frame-Aufnahme verwerfen",
"de_DE": "",
"el_GR": "",
"en_US": "Discard RenderDoc Frame Capture",
"es_ES": "Descartar la captura de fotograma de RenderDoc",
@@ -72,14 +72,14 @@
"tr_TR": "",
"uk_UA": "",
"zh_CN": "丢弃 RenderDoc 帧捕获",
"zh_TW": "捨棄 RenderDoc 畫格擷取"
"zh_TW": ""
}
},
{
"ID": "MenuBarActions_DiscardCapture_ToolTip",
"Translations": {
"ar_SA": "",
"de_DE": "Beendet die jetzige RenderDoc Frame-Aufnahme, verwirft sofort das Ergebnis.",
"de_DE": "",
"el_GR": "",
"en_US": "Ends the currently active RenderDoc Frame Capture, immediately discarding its result.",
"es_ES": "Finaliza la captura de fotograma de RenderDoc actualmente activa y descarta inmediatamente su resultado.",
@@ -97,7 +97,7 @@
"tr_TR": "",
"uk_UA": "",
"zh_CN": "结束当前正在进行的 RenderDoc 帧捕获,并立即丢弃其结果。",
"zh_TW": "停止正在執行的 RenderDoc 畫格擷取,且立即捨棄其結果。"
"zh_TW": ""
}
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -22,5 +22,5 @@ chmod +x AppDir/AppRun AppDir/usr/bin/Ryujinx*
mkdir -p "$OUTDIR"
appimagetool --appimage-extract-and-run -n --comp zstd --mksquashfs-opt -Xcompression-level --mksquashfs-opt 21 \
AppDir "$OUTDIR"/Ryujinx.AppImage
appimagetool -n --comp zstd --mksquashfs-opt -Xcompression-level --mksquashfs-opt 21 \
AppDir "$OUTDIR"/Ryujinx.AppImage

View File

@@ -47,12 +47,14 @@ def get_new_name(
input_component = str(input_dylib_path).replace(str(input_directory), "")[1:]
return Path(os.path.join(output_directory, input_component))
def get_archs(dylib_path: Path) -> list[str]:
res = subprocess.check_output([LIPO, "-info", str(dylib_path)]).decode("utf-8")
if res.startswith("Non-fat file"):
return [res.split(":")[-1].strip()]
else:
return res.split("are:")[-1].strip().split()
def is_fat_file(dylib_path: Path) -> str:
res = subprocess.check_output([LIPO, "-info", str(dylib_path.absolute())]).decode(
"utf-8"
)
return not res.split("\n")[0].startswith("Non-fat file")
def construct_universal_dylib(
arm64_input_dylib_path: Path, x86_64_input_dylib_path: Path, output_dylib_path: Path
@@ -67,12 +69,11 @@ def construct_universal_dylib(
os.path.basename(arm64_input_dylib_path.resolve()), output_dylib_path
)
else:
arm64_archs = get_archs(arm64_input_dylib_path)
x86_64_archs = get_archs(x86_64_input_dylib_path) if x86_64_input_dylib_path.exists() else []
if "arm64" in arm64_archs and "x86_64" in arm64_archs:
shutil.copy2(arm64_input_dylib_path, output_dylib_path)
elif x86_64_archs:
if is_fat_file(arm64_input_dylib_path) or not x86_64_input_dylib_path.exists():
with open(output_dylib_path, "wb") as dst:
with open(arm64_input_dylib_path, "rb") as src:
dst.write(src.read())
else:
subprocess.check_call(
[
LIPO,

View File

@@ -1,120 +0,0 @@
#!/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"

View File

@@ -1061,7 +1061,6 @@
0100BCA016636000,"eBaseball Powerful Pro Yakyuu 2022",gpu;services-horizon;crash,nothing,2024-05-26 23:07:19
01001F20100B8000,"Eclipse: Edge of Light",,playable,2020-08-11 23:06:29
0100E0A0110F4000,"eCrossminton",,playable,2020-07-11 18:24:27
010054601D54C000,"Emio The Smiling Man: Famicom Detective Club (DEMO)",demo,playable,2026-05-13 18:32:12
0100ABE00DB4E000,"Edna & Harvey: Harvey's New Eyes",nvdec,playable,2021-01-26 14:36:08
01004F000B716000,"Edna & Harvey: The Breakout Anniversary Edition",crash;nvdec,ingame,2022-08-01 16:59:56
01002550129F0000,"Effie",,playable,2022-10-27 14:36:39
@@ -1205,7 +1204,7 @@
01003B200E440000,"Five Nights at Freddy's: Sister Location",,playable,2023-10-06 09:00:58
010038200E088000,"Flan",crash;regression,ingame,2021-11-17 07:39:28
01000A0004C50000,"FLASHBACK™",nvdec,playable,2020-05-14 13:57:29
0100C53004C52000,"Flat Heroes",,playable,2026-02-27 17:00:00
0100C53004C52000,"Flat Heroes",gpu,ingame,2022-07-26 19:37:37
0100B54012798000,"Flatland: Prologue",,playable,2020-12-11 20:41:12
0100307004B4C000,"Flinthook",online,playable,2021-03-25 20:42:29
010095A004040000,"Flip Wars",services;ldn-untested,ingame,2022-05-02 15:39:18
@@ -1395,7 +1394,6 @@
0100c3c012718000,"Grand Theft Auto: III The Definitive Edition",gpu;UE4,ingame,2022-10-31 20:13:52
0100182014022000,"Grand Theft Auto: Vice City The Definitive Edition",gpu;UE4,ingame,2022-10-31 20:13:52
010065a014024000,"Grand Theft Auto: San Andreas The Definitive Edition",gpu;UE4,ingame,2022-10-31 20:13:52
0100EB500D92E000,"GROOVE COASTER WAI WAI PARTY!!!!",gpu,ingame,2026-05-13 18:32:12
0100822012D76000,"HAAK",gpu,ingame,2023-02-19 14:31:05
01007E100EFA8000,"Habroxia",,playable,2020-06-16 23:04:42
0100535012974000,"Hades",vulkan,playable,2022-10-05 10:45:21
@@ -1834,7 +1832,6 @@
010055200E87E000,"Metamorphosis",UE4;audout;gpu;nvdec,ingame,2021-06-16 16:18:11
0100D4900E82C000,"Metro 2033 Redux",gpu,ingame,2022-11-09 10:53:13
0100F0400E850000,"Metro: Last Light Redux",slow;nvdec;vulkan-backend-bug,ingame,2023-11-01 11:53:52
010019A01E2F2000,"Metroid Prime 4: Beyond",,ingame,2026-05-13 18:32:12
010012101468C000,"Metroid Prime™ Remastered",gpu;Needs Update;vulkan-backend-bug;opengl-backend-bug,ingame,2024-05-07 22:48:15
010093801237C000,"Metroid™ Dread",,playable,2023-11-13 04:02:36
0100A1200F20C000,"Midnight Evil",,playable,2022-10-18 22:55:19
@@ -1948,7 +1945,6 @@
0100C3E00ACAA000,"Mutant Football League: Dynasty Edition",online-broken,playable,2022-08-05 17:01:51
01004BE004A86000,"Mutant Mudds Collection",,playable,2022-08-05 17:11:38
0100E6B00DEA4000,"Mutant Year Zero: Road to Eden - Deluxe Edition",nvdec;UE4,playable,2022-09-10 13:31:10
010037501F864000,"Mute Crimson DX",,ingame,2026-05-13 18:32:12
0100161009E5C000,"MX Nitro: Unleashed",,playable,2022-09-27 22:34:33
0100218011E7E000,"MX vs ATV All Out",nvdec;UE4;vulkan-backend-bug,playable,2022-10-25 19:51:46
0100D940063A0000,"MXGP3 - The Official Motocross Videogame",UE4;gpu;nvdec,ingame,2020-12-16 14:00:20
@@ -2272,7 +2268,6 @@
010086F0064CE000,"Poi: Explorer Edition",nvdec,playable,2021-01-21 19:32:00
0100EB6012FD2000,"Poison Control",,playable,2021-05-16 14:01:54
010072400E04A000,"Pokémon Café ReMix",,playable,2021-08-17 20:00:04
01005B7008C52800,"Pokémon Champions",Needs Update;services;online-broke,menus,2026-05-13 18:32:12
010008c01e742000,"Pokémon Friends",crash;services,menus,2025-07-24 13:32:00
01003D200BAA2000,"Pokémon Mystery Dungeon™: Rescue Team DX",mac-bug,playable,2024-01-21 00:16:32
01008DB008C2C000,"Pokémon Shield + Pokémon Shield Expansion Pass",deadlock;crash;online-broken;ldn-works;LAN,ingame,2024-08-12 07:20:22
@@ -2280,8 +2275,6 @@
01009AD008C4C000,"Pokémon: Let's Go, Pikachu! demo",slow;demo,playable,2023-11-26 11:23:20
0100000011D90000,"Pokémon™ Brilliant Diamond",gpu;ldn-works,ingame,2024-08-28 13:26:35
010018E011D92000,"Pokémon™ Shining Pearl",gpu;ldn-works,ingame,2024-08-28 13:26:35
100554023408000,"Pokémon FireRed Version",crashes,nothing,2026-05-13 18:32:12
010034D02340E000,"Pokémon LeafGreen Version",crashes,nothing,2026-05-13 18:32:12
010015F008C54000,"Pokémon™ HOME",Needs Update;crash;services,menus,2020-12-06 06:01:51
01001F5010DFA000,"Pokémon™ Legends: Arceus",gpu;Needs Update;ldn-works,ingame,2024-09-19 10:02:02
0100F43008C44000,"Pokémon™ Legends: Z-A",gpu;crash;ldn-works,ingame,2025-11-16 00:30:00
@@ -2873,7 +2866,7 @@
0100277011F1A000,"Super Mario Bros.™ 35",online-broken,menus,2022-08-07 16:27:25
010015100B514000,"Super Mario Bros.™ Wonder",amd-vendor-bug,playable,2024-09-06 13:21:21
01009B90006DC000,"Super Mario Maker™ 2",online-broken;ldn-broken,playable,2024-08-25 11:05:19
0100000000010000,"Super Mario Odyssey™",nvdec;intel-vendor-bug;mac-bug;amd-vendor-bug,playable,2026-05-13 18:32:12
0100000000010000,"Super Mario Odyssey™",nvdec;intel-vendor-bug;mac-bug,playable,2024-08-25 01:32:34
010036B0034E4000,"Super Mario Party™",gpu;Needs Update;ldn-works,ingame,2024-06-21 05:10:16
0100965017338000,"Super Mario Party Jamboree",mac-bug;gpu,ingame,2025-02-17 02:09:20
0100BC0018138000,"Super Mario RPG™",gpu;audio;nvdec,ingame,2024-06-19 17:43:42
@@ -3170,8 +3163,6 @@
0100E2E00CB14000,"Tokyo School Life",,playable,2022-09-16 20:25:54
010024601BB16000,"Tomb Raider I-III Remastered Starring Lara Croft",gpu;opengl,ingame,2024-09-27 12:32:04
0100D7F01E49C000,"Tomba! Special Edition",services-horizon,nothing,2024-09-15 21:59:54
010051F0207B2000,"Tomodachi Life: Living the Dream",amd-vendor-bug;gpu;intel-vendor-bug;ldn-broken,ingame,2026-05-13 18:32:12
0100CA502552A000,"Tomodachi Life: Living the Dream Welcome Edtion",amd-vendor-bug;demo,playable,2026-05-13 18:32:12
0100D400100F8000,"Tonight We Riot",,playable,2021-02-26 15:55:09
0100CC00102B4000,"Tony Hawk's™ Pro Skater™ 1 + 2",gpu;Needs Update,ingame,2024-09-24 08:18:14
010093F00E818000,"Tools Up!",crash,ingame,2020-07-21 12:58:17
1 title_id game_name labels status last_updated
1061 0100BCA016636000 eBaseball Powerful Pro Yakyuu 2022 gpu;services-horizon;crash nothing 2024-05-26 23:07:19
1062 01001F20100B8000 Eclipse: Edge of Light playable 2020-08-11 23:06:29
1063 0100E0A0110F4000 eCrossminton playable 2020-07-11 18:24:27
010054601D54C000 Emio – The Smiling Man: Famicom Detective Club (DEMO) demo playable 2026-05-13 18:32:12
1064 0100ABE00DB4E000 Edna & Harvey: Harvey's New Eyes nvdec playable 2021-01-26 14:36:08
1065 01004F000B716000 Edna & Harvey: The Breakout – Anniversary Edition crash;nvdec ingame 2022-08-01 16:59:56
1066 01002550129F0000 Effie playable 2022-10-27 14:36:39
1204 01003B200E440000 Five Nights at Freddy's: Sister Location playable 2023-10-06 09:00:58
1205 010038200E088000 Flan crash;regression ingame 2021-11-17 07:39:28
1206 01000A0004C50000 FLASHBACK™ nvdec playable 2020-05-14 13:57:29
1207 0100C53004C52000 Flat Heroes gpu playable ingame 2026-02-27 17:00:00 2022-07-26 19:37:37
1208 0100B54012798000 Flatland: Prologue playable 2020-12-11 20:41:12
1209 0100307004B4C000 Flinthook online playable 2021-03-25 20:42:29
1210 010095A004040000 Flip Wars services;ldn-untested ingame 2022-05-02 15:39:18
1394 0100c3c012718000 Grand Theft Auto: III – The Definitive Edition gpu;UE4 ingame 2022-10-31 20:13:52
1395 0100182014022000 Grand Theft Auto: Vice City – The Definitive Edition gpu;UE4 ingame 2022-10-31 20:13:52
1396 010065a014024000 Grand Theft Auto: San Andreas – The Definitive Edition gpu;UE4 ingame 2022-10-31 20:13:52
0100EB500D92E000 GROOVE COASTER WAI WAI PARTY!!!! gpu ingame 2026-05-13 18:32:12
1397 0100822012D76000 HAAK gpu ingame 2023-02-19 14:31:05
1398 01007E100EFA8000 Habroxia playable 2020-06-16 23:04:42
1399 0100535012974000 Hades vulkan playable 2022-10-05 10:45:21
1832 010055200E87E000 Metamorphosis UE4;audout;gpu;nvdec ingame 2021-06-16 16:18:11
1833 0100D4900E82C000 Metro 2033 Redux gpu ingame 2022-11-09 10:53:13
1834 0100F0400E850000 Metro: Last Light Redux slow;nvdec;vulkan-backend-bug ingame 2023-11-01 11:53:52
010019A01E2F2000 Metroid Prime 4: Beyond ingame 2026-05-13 18:32:12
1835 010012101468C000 Metroid Prime™ Remastered gpu;Needs Update;vulkan-backend-bug;opengl-backend-bug ingame 2024-05-07 22:48:15
1836 010093801237C000 Metroid™ Dread playable 2023-11-13 04:02:36
1837 0100A1200F20C000 Midnight Evil playable 2022-10-18 22:55:19
1945 0100C3E00ACAA000 Mutant Football League: Dynasty Edition online-broken playable 2022-08-05 17:01:51
1946 01004BE004A86000 Mutant Mudds Collection playable 2022-08-05 17:11:38
1947 0100E6B00DEA4000 Mutant Year Zero: Road to Eden - Deluxe Edition nvdec;UE4 playable 2022-09-10 13:31:10
010037501F864000 Mute Crimson DX ingame 2026-05-13 18:32:12
1948 0100161009E5C000 MX Nitro: Unleashed playable 2022-09-27 22:34:33
1949 0100218011E7E000 MX vs ATV All Out nvdec;UE4;vulkan-backend-bug playable 2022-10-25 19:51:46
1950 0100D940063A0000 MXGP3 - The Official Motocross Videogame UE4;gpu;nvdec ingame 2020-12-16 14:00:20
2268 010086F0064CE000 Poi: Explorer Edition nvdec playable 2021-01-21 19:32:00
2269 0100EB6012FD2000 Poison Control playable 2021-05-16 14:01:54
2270 010072400E04A000 Pokémon Café ReMix playable 2021-08-17 20:00:04
01005B7008C52800 Pokémon Champions Needs Update;services;online-broke menus 2026-05-13 18:32:12
2271 010008c01e742000 Pokémon Friends crash;services menus 2025-07-24 13:32:00
2272 01003D200BAA2000 Pokémon Mystery Dungeon™: Rescue Team DX mac-bug playable 2024-01-21 00:16:32
2273 01008DB008C2C000 Pokémon Shield + Pokémon Shield Expansion Pass deadlock;crash;online-broken;ldn-works;LAN ingame 2024-08-12 07:20:22
2275 01009AD008C4C000 Pokémon: Let's Go, Pikachu! demo slow;demo playable 2023-11-26 11:23:20
2276 0100000011D90000 Pokémon™ Brilliant Diamond gpu;ldn-works ingame 2024-08-28 13:26:35
2277 010018E011D92000 Pokémon™ Shining Pearl gpu;ldn-works ingame 2024-08-28 13:26:35
100554023408000 Pokémon FireRed Version crashes nothing 2026-05-13 18:32:12
010034D02340E000 Pokémon LeafGreen Version crashes nothing 2026-05-13 18:32:12
2278 010015F008C54000 Pokémon™ HOME Needs Update;crash;services menus 2020-12-06 06:01:51
2279 01001F5010DFA000 Pokémon™ Legends: Arceus gpu;Needs Update;ldn-works ingame 2024-09-19 10:02:02
2280 0100F43008C44000 Pokémon™ Legends: Z-A gpu;crash;ldn-works ingame 2025-11-16 00:30:00
2866 0100277011F1A000 Super Mario Bros.™ 35 online-broken menus 2022-08-07 16:27:25
2867 010015100B514000 Super Mario Bros.™ Wonder amd-vendor-bug playable 2024-09-06 13:21:21
2868 01009B90006DC000 Super Mario Maker™ 2 online-broken;ldn-broken playable 2024-08-25 11:05:19
2869 0100000000010000 Super Mario Odyssey™ nvdec;intel-vendor-bug;mac-bug;amd-vendor-bug nvdec;intel-vendor-bug;mac-bug playable 2026-05-13 18:32:12 2024-08-25 01:32:34
2870 010036B0034E4000 Super Mario Party™ gpu;Needs Update;ldn-works ingame 2024-06-21 05:10:16
2871 0100965017338000 Super Mario Party Jamboree mac-bug;gpu ingame 2025-02-17 02:09:20
2872 0100BC0018138000 Super Mario RPG™ gpu;audio;nvdec ingame 2024-06-19 17:43:42
3163 0100E2E00CB14000 Tokyo School Life playable 2022-09-16 20:25:54
3164 010024601BB16000 Tomb Raider I-III Remastered Starring Lara Croft gpu;opengl ingame 2024-09-27 12:32:04
3165 0100D7F01E49C000 Tomba! Special Edition services-horizon nothing 2024-09-15 21:59:54
010051F0207B2000 Tomodachi Life: Living the Dream amd-vendor-bug;gpu;intel-vendor-bug;ldn-broken ingame 2026-05-13 18:32:12
0100CA502552A000 Tomodachi Life: Living the Dream – Welcome Edtion amd-vendor-bug;demo playable 2026-05-13 18:32:12
3166 0100D400100F8000 Tonight We Riot playable 2021-02-26 15:55:09
3167 0100CC00102B4000 Tony Hawk's™ Pro Skater™ 1 + 2 gpu;Needs Update ingame 2024-09-24 08:18:14
3168 010093F00E818000 Tools Up! crash ingame 2020-07-21 12:58:17

View File

@@ -5,7 +5,8 @@
<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/packages/projects/nuget/index.json" />
<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" />
</packageSources>
<packageSourceMapping>
<!-- key value for <packageSource> should match key values from <packageSources> element -->
@@ -13,6 +14,10 @@
<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>

View File

@@ -16,10 +16,5 @@ namespace Ryujinx.Common.Configuration.Hid.Controller
/// Enable Rumble
/// </summary>
public bool EnableRumble { get; set; }
/// <summary>
/// Enable HD Rumble support
/// </summary
public bool UseHDRumble { get; set; }
}
}

View File

@@ -1,4 +1,4 @@
namespace Ryujinx.Common.Configuration.Hid.Keyboard
{
public class StandardKeyboardInputConfig : GenericKeyboardInputConfig<Key> { }
public class StandardKeyboardInputConfig : GenericKeyboardInputConfig<PhysicalKey> { }
}

View File

@@ -0,0 +1,142 @@
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid
{
[JsonConverter(typeof(JsonStringEnumConverter<PhysicalKey>))]
public enum PhysicalKey
{
Unknown,
ShiftLeft,
ShiftRight,
ControlLeft,
ControlRight,
AltLeft,
AltRight,
WinLeft,
WinRight,
Menu,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
F13,
F14,
F15,
F16,
F17,
F18,
F19,
F20,
F21,
F22,
F23,
F24,
F25,
F26,
F27,
F28,
F29,
F30,
F31,
F32,
F33,
F34,
F35,
Up,
Down,
Left,
Right,
Enter,
Escape,
Space,
Tab,
BackSpace,
Insert,
Delete,
PageUp,
PageDown,
Home,
End,
CapsLock,
ScrollLock,
PrintScreen,
Pause,
NumLock,
Clear,
Keypad0,
Keypad1,
Keypad2,
Keypad3,
Keypad4,
Keypad5,
Keypad6,
Keypad7,
Keypad8,
Keypad9,
KeypadDivide,
KeypadMultiply,
KeypadSubtract,
KeypadAdd,
KeypadDecimal,
KeypadEnter,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
Number0,
Number1,
Number2,
Number3,
Number4,
Number5,
Number6,
Number7,
Number8,
Number9,
Tilde,
Grave,
Minus,
Plus,
BracketLeft,
BracketRight,
Semicolon,
Quote,
Comma,
Period,
Slash,
BackSlash,
Unbound,
Count,
}
}

View File

@@ -12,12 +12,20 @@ namespace Ryujinx.Common.Helper
private static partial nint GetConsoleWindow();
[SupportedOSPlatform("windows")]
[LibraryImport("kernel32", SetLastError = true)]
[LibraryImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool FreeConsole();
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);
public static bool SetConsoleWindowStateSupported => OperatingSystem.IsWindows();
public static bool HasConsoleWindow => OperatingSystem.IsWindows() && GetConsoleWindow() != nint.Zero;
public static void SetConsoleWindowState(bool show)
{
@@ -34,31 +42,22 @@ namespace Ryujinx.Common.Helper
[SupportedOSPlatform("windows")]
private static void SetConsoleWindowStateWindows(bool show)
{
if (show)
const int SW_HIDE = 0;
const int SW_SHOW = 5;
nint hWnd = GetConsoleWindow();
if (hWnd == nint.Zero)
{
if (GetConsoleWindow() != nint.Zero)
{
Logger.SetConsoleTargetEnabled(true);
}
Logger.Warning?.Print(LogClass.Application, "Attempted to show/hide console window but console window does not exist");
return;
}
Logger.SetConsoleTargetEnabled(false);
DetachConsole();
}
SetForegroundWindow(hWnd);
[SupportedOSPlatform("windows")]
private static void DetachConsole()
{
if (GetConsoleWindow() == nint.Zero)
{
return;
}
hWnd = GetForegroundWindow();
if (!FreeConsole())
{
Logger.Warning?.Print(LogClass.Application, "Attempted to detach console window but the operation failed");
}
ShowWindow(hWnd, show ? SW_SHOW : SW_HIDE);
}
}
}

View File

@@ -51,7 +51,6 @@ namespace Ryujinx.Common.Logging
ServiceNgct,
ServiceNifm,
ServiceNim,
ServiceNotification,
ServiceNs,
ServiceNsd,
ServiceNtc,

View File

@@ -12,7 +12,6 @@ namespace Ryujinx.Common.Logging
Error,
Guest,
AccessLog,
NetLog,
Notice,
Trace,
}

View File

@@ -119,7 +119,6 @@ namespace Ryujinx.Common.Logging
public static Log? Error { get; private set; }
public static Log? Guest { get; private set; }
public static Log? AccessLog { get; private set; }
public static Log? NetLog { get; private set; }
public static Log? Stub { get; private set; }
public static Log? Trace { get; private set; }
public static Log Notice { get; } // Always enabled
@@ -137,7 +136,11 @@ namespace Ryujinx.Common.Logging
_time = Stopwatch.StartNew();
SetConsoleTargetEnabled(true);
// Logger should log to console by default
AddTarget(new AsyncLogTargetWrapper(
new ConsoleLogTarget("console"),
1000,
AsyncLogTargetOverflowAction.Discard));
Notice = new Log(LogLevel.Notice);
@@ -170,21 +173,6 @@ 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);
@@ -248,7 +236,6 @@ namespace Ryujinx.Common.Logging
case LogLevel.Error : Error = enabled ? new Log(LogLevel.Error) : null; break;
case LogLevel.Guest : Guest = enabled ? new Log(LogLevel.Guest) : null; break;
case LogLevel.AccessLog : AccessLog = enabled ? new Log(LogLevel.AccessLog) : null; break;
case LogLevel.NetLog : NetLog = enabled ? new Log(LogLevel.NetLog) : null; break;
case LogLevel.Stub : Stub = enabled ? new Log(LogLevel.Stub) : null; break;
case LogLevel.Trace : Trace = enabled ? new Log(LogLevel.Trace) : null; break;
case LogLevel.Notice : break;

View File

@@ -29,8 +29,8 @@ namespace Ryujinx.Common
public static string GetChangelogUrl(Version currentVersion, Version newVersion) =>
IsCanaryBuild
? $"https://git.ryujinx.app/projects/Ryubing/compare/Canary-{currentVersion}...Canary-{newVersion}"
: $"https://git.ryujinx.app/projects/Ryubing/releases/tag/{newVersion}";
? $"https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-{currentVersion}...Canary-{newVersion}"
: $"https://git.ryujinx.app/ryubing/ryujinx/-/releases/{newVersion}";
}

View File

@@ -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/projects/Ryubing/wiki/FAQ-%26-Troubleshooting";
public const string FaqWikiUrl = "https://git.ryujinx.app/ryubing/ryujinx/-/wikis/FAQ-&-Troubleshooting";
public const string SetupGuideWikiUrl =
"https://git.ryujinx.app/projects/Ryubing/wiki/Setup-%26-Configuration-Guide";
"https://git.ryujinx.app/ryubing/ryujinx/-/wikis/Setup-&-Configuration-Guide";
public const string MultiplayerWikiUrl =
"https://git.ryujinx.app/projects/Ryubing/wiki/Multiplayer-(LDN-Local-Wireless)-Guide";
"https://git.ryujinx.app/ryubing/ryujinx/-/wikis/Multiplayer-(LDN-Local-Wireless)-Guide";
}
}

View File

@@ -59,7 +59,6 @@ namespace Ryujinx.Common
//Mario Franchise
"010021d00812a000", // Arcade Archives VS. SUPER MARIO BROS.
"01007fe0221d8000", // Hello, Mario!
"01006d0017f7a000", // Mario & Luigi: Brothership
"010003000e146000", // Mario & Sonic at the Olympic Games Tokyo 2020
"010067300059a000", // Mario + Rabbids: Kingdom Battle
@@ -71,9 +70,6 @@ namespace Ryujinx.Common
"0100bde00862a000", // Mario Tennis Aces
"0100b99019412000", // Mario vs. Donkey Kong
"010049900f546000", // Super Mario 3D All-Stars
"010049900f546001", // Super Mario 3D All-Stars | Super Mario 64
"010049900f546002", // Super Mario 3D All-Stars | Super Mario Sunshine
"010049900f546003", // Super Mario 3D All-Stars | Super Mario Galaxy
"010028600ebda000", // Super Mario 3D World + Bowser's Fury
"010049900F546001", // Super Mario 64
"0100ea80032ea000", // Super Mario Bros. U Deluxe
@@ -111,11 +107,6 @@ namespace Ryujinx.Common
"0100187003a36000", // Pokémon: Let's Go Eevee!
"010003f003a34000", // Pokémon: Let's Go Pikachu!
"0100f43008c44000", // Pokémon Legends: Z-A
"0100554023408000", // Pokémon FireRed Version (EN)
"01006fa0233f8000", // Pokémon FireRed Version (JP)
"0100fd6023430000", // Pokémon LeafGreen Version (DE)
"0100f1e0233fa000", // Pokémon LeafGreen Version (JP)
"01005b7008c52000", // Pokémon Champions
//Splatoon Franchise
"0100f8f0000a2000", // Splatoon 2 (EU)
@@ -125,14 +116,13 @@ namespace Ryujinx.Common
"0100ba0018500000", // Splatoon 3: Splatfest World Premiere
//NSO Membership games
"0100d870045b6000", // NES - Nintendo Switch Online
"01008d300c50c000", // SNES - Nintendo Switch Online
"0100c62011050000", // GB - Nintendo Switch Online
"010012f017576000", // GBA - Nintendo Switch Online
"0100c9a00ece6000", // N64 - Nintendo Switch Online
"0100e0601c632000", // N64 - Nintendo Switch Online 18+
"0100d870045b6000", // NES - Nintendo Switch Online
"0100b3c014bda000", // SEGA Genesis - Nintendo Switch Online
"0100bfc01d976000", // Virtual Boy - Nintendo Switch Online
"01008d300c50c000", // SNES - Nintendo Switch Online
"0100ccf019c8c000", // F-ZERO 99
"0100ad9012510000", // PAC-MAN 99
"010040600c5ce000", // Tetris 99
@@ -151,17 +141,12 @@ namespace Ryujinx.Common
"0100704000B3A000", // Snipperclips
"01006a800016e000", // Super Smash Bros. Ultimate
"0100a9400c9c2000", // Tokyo Mirage Sessions #FE Encore
"0100ca502552a000", // Tomodachi Life: Living the Dream - Welcome Edition
"010051f0207b2000", // Tomodachi Life: Living the Dream
//Bayonetta Franchise
"010076f0049a2000", // Bayonetta
"01007960049a0000", // Bayonetta 2
"01004a4010fea000", // Bayonetta 3
"0100cf5010fec000", // Bayonetta Origins: Cereza and the Lost Demon
// Famicom Detective Club Franchise
"010054601d54c000", // Emio - The Smiling Man: Famicom Detective Series (DEMO)
//Persona Franchise
"0100dcd01525a000", // Persona 3 Portable
@@ -186,9 +171,7 @@ namespace Ryujinx.Common
"0100453019aa8000", // Xenoblade Chronicles: X Definitive Edition
//Misc Games
"01003670066de000", // 36 Fragments of Midnight
"010056e00853a000", // A Hat in Time
"0100c9f00aaee000", // Ascendence
"0100fd1014726000", // Baldurs Gate: Dark Alliance
"01008c2019598000", // Bluey: The Video Game
"010096f00ff22000", // Borderlands 2: Game of the Year Edition
@@ -202,10 +185,8 @@ namespace Ryujinx.Common
"010027400cdc6000", // Divinity Original 2 - Definitive Edition
"01008c8012920000", // Dying Light Platinum Edition
"0100d11013e6a000", // Eschatos
"01000490067ae000", // Frederic 2: Evil Strikes Back
"01001cc01b2d4000", // Goat Simulator 3
"01003620068ea000", // Hand of Fate 2
"01007ac00e012000", // HEXAGRAVITY
"0100f7e00c70e000", // Hogwarts Legacy
"010013c00e930000", // Hollow Knight: Silksong
"010085500130a000", // Lego City: Undercover
@@ -215,7 +196,6 @@ namespace Ryujinx.Common
"0100853015e86000", // No Man's Sky
"0100f85014ed0000", // No More Heroes
"0100463014ed4000", // No More Heroes 2
"0100f7d00a1bc000", // NO THING
"0100e570094e8000", // Owlboy
"01007bb017812000", // Portal
"0100abd01785c000", // Portal 2
@@ -224,14 +204,11 @@ namespace Ryujinx.Common
"01008e200c5c2000", // Muse Dash
"01005ff002e2a000", // Rayman Legends
"01007820196a6000", // Red Dead Redemption
"01007a800d520000", // REFUNCT
"0100e8300a67a000", // Risk
"01002f7013224000", // Rune Factory 5
"01008d100d43e000", // Saints Row IV
"0100de600beee000", // Saints Row: The Third - The Full Package
"01001180021fa000", // Shovel Knight: Specter of Torment
"010079f00671c000", // Sparkle 2: Evo
"010077b00e046000", // Spyro: Reignited Trilogy
"0100e1D01eb2e000", // Squeakross: Home Squeak Home
"0100e65002bb8000", // Stardew Valley
"0100d7a01b7a2000", // Star Wars: Bounty Hunter

View File

@@ -17,8 +17,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
private static readonly int _pageMask = _pageSize - 1;
private const int CodeAlignment = 4; // Bytes.
// 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 const int CacheSize = 256 * 1024 * 1024;
private static JitCacheInvalidation _jitCacheInvalidator;
@@ -34,14 +33,6 @@ 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)
{

View File

@@ -42,7 +42,6 @@ namespace Ryujinx.Graphics.GAL
public readonly bool SupportsShaderBallot;
public readonly bool SupportsShaderBarrierDivergence;
public readonly bool SupportsShaderFloat64;
public readonly bool SupportsShaderNonUniformIndexing;
public readonly bool SupportsTextureGatherOffsets;
public readonly bool SupportsTextureShadowLod;
public readonly bool SupportsVertexStoreAndAtomics;
@@ -111,7 +110,6 @@ namespace Ryujinx.Graphics.GAL
bool supportsShaderBallot,
bool supportsShaderBarrierDivergence,
bool supportsShaderFloat64,
bool supportsShaderNonUniformIndexing,
bool supportsTextureGatherOffsets,
bool supportsTextureShadowLod,
bool supportsVertexStoreAndAtomics,
@@ -174,7 +172,6 @@ namespace Ryujinx.Graphics.GAL
SupportsShaderBallot = supportsShaderBallot;
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
SupportsShaderFloat64 = supportsShaderFloat64;
SupportsShaderNonUniformIndexing = supportsShaderNonUniformIndexing;
SupportsTextureGatherOffsets = supportsTextureGatherOffsets;
SupportsTextureShadowLod = supportsTextureShadowLod;
SupportsVertexStoreAndAtomics = supportsVertexStoreAndAtomics;

View File

@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
private const uint CodeGenVersion = 7354;
private const uint CodeGenVersion = 7353;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";

View File

@@ -231,8 +231,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
public bool QueryHostSupportsShaderFloat64() => _context.Capabilities.SupportsShaderFloat64;
public bool QueryHostSupportsShaderNonUniformIndexing() => _context.Capabilities.SupportsShaderNonUniformIndexing;
public bool QueryHostSupportsSnormBufferTextureFormat() => _context.Capabilities.SupportsSnormBufferTextureFormat;
public bool QueryHostSupportsTextureGatherOffsets() => _context.Capabilities.SupportsTextureGatherOffsets;

View File

@@ -551,7 +551,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
level,
x,
width,
(InternalFormat) format.PixelFormat,
format.PixelFormat,
mipSize,
data);
}
@@ -579,7 +579,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
layer,
width,
1,
(InternalFormat) format.PixelFormat,
format.PixelFormat,
mipSize,
data);
}
@@ -609,7 +609,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
y,
width,
height,
(InternalFormat) format.PixelFormat,
format.PixelFormat,
mipSize,
data);
}
@@ -643,7 +643,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
width,
height,
1,
(InternalFormat) format.PixelFormat,
format.PixelFormat,
mipSize,
data);
}
@@ -675,7 +675,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
y,
width,
height,
(InternalFormat) format.PixelFormat,
format.PixelFormat,
mipSize,
data);
}
@@ -744,7 +744,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
level,
0,
width,
(InternalFormat) format.PixelFormat,
format.PixelFormat,
mipSize,
data);
}
@@ -773,7 +773,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
0,
width,
height,
(InternalFormat) format.PixelFormat,
format.PixelFormat,
mipSize,
data);
}
@@ -807,7 +807,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
width,
height,
depth,
(InternalFormat) format.PixelFormat,
format.PixelFormat,
mipSize,
data);
}
@@ -843,7 +843,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
0,
width,
height,
(InternalFormat) format.PixelFormat,
format.PixelFormat,
mipSize / 6,
data + faceOffset);
}

View File

@@ -184,7 +184,6 @@ namespace Ryujinx.Graphics.OpenGL
supportsShaderBallot: HwCapabilities.SupportsShaderBallot,
supportsShaderBarrierDivergence: !(intelWindows || intelUnix),
supportsShaderFloat64: true,
supportsShaderNonUniformIndexing: false,
supportsTextureGatherOffsets: true,
supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
supportsVertexStoreAndAtomics: true,

View File

@@ -27,7 +27,7 @@ namespace Ryujinx.Graphics.OpenGL
public void Map(BufferHandle handle, int size)
{
GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle);
nint ptr = GL.MapBufferRange(BufferTarget.CopyWriteBuffer, nint.Zero, size, MapBufferAccessMask.MapReadBit | MapBufferAccessMask.MapPersistentBit);
nint ptr = GL.MapBufferRange(BufferTarget.CopyWriteBuffer, nint.Zero, size, BufferAccessMask.MapReadBit | BufferAccessMask.MapPersistentBit);
_maps[handle] = ptr;
}
@@ -75,7 +75,7 @@ namespace Ryujinx.Graphics.OpenGL
GL.BindBuffer(BufferTarget.CopyWriteBuffer, _copyBufferHandle);
GL.BufferStorage(BufferTarget.CopyWriteBuffer, requiredSize, nint.Zero, BufferStorageFlags.MapReadBit | BufferStorageFlags.MapPersistentBit);
_bufferMap = GL.MapBufferRange(BufferTarget.CopyWriteBuffer, nint.Zero, requiredSize, MapBufferAccessMask.MapReadBit | MapBufferAccessMask.MapPersistentBit);
_bufferMap = GL.MapBufferRange(BufferTarget.CopyWriteBuffer, nint.Zero, requiredSize, BufferAccessMask.MapReadBit | BufferAccessMask.MapPersistentBit);
}
}

View File

@@ -924,8 +924,8 @@ namespace Ryujinx.Graphics.OpenGL
GL.Disable(EnableCap.CullFace);
return;
}
GL.CullFace((TriangleFace) face.Convert());
GL.CullFace(face.Convert());
GL.Enable(EnableCap.CullFace);
}
@@ -1085,12 +1085,12 @@ namespace Ryujinx.Graphics.OpenGL
{
if (frontMode == backMode)
{
GL.PolygonMode((TriangleFace) MaterialFace.FrontAndBack, frontMode.Convert());
GL.PolygonMode(MaterialFace.FrontAndBack, frontMode.Convert());
}
else
{
GL.PolygonMode((TriangleFace) MaterialFace.Front, frontMode.Convert());
GL.PolygonMode((TriangleFace) MaterialFace.Back, backMode.Convert());
GL.PolygonMode(MaterialFace.Front, frontMode.Convert());
GL.PolygonMode(MaterialFace.Back, backMode.Convert());
}
}

View File

@@ -59,7 +59,7 @@ namespace Ryujinx.Graphics.OpenGL
GL.CompileShader(shaderHandle);
break;
case TargetLanguage.Spirv:
GL.ShaderBinary(1, ref shaderHandle, ShaderBinaryFormat.ShaderBinaryFormatSpirV, shader.BinaryCode, shader.BinaryCode.Length);
GL.ShaderBinary(1, ref shaderHandle, (BinaryFormat)All.ShaderBinaryFormatSpirVArb, shader.BinaryCode, shader.BinaryCode.Length);
GL.SpecializeShader(shaderHandle, "main", 0, (int[])null, (int[])null);
break;
}

View File

@@ -32,7 +32,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries
GL.BufferStorage(BufferTarget.QueryBuffer, sizeof(long), (nint)(&defaultValue), BufferStorageFlags.MapReadBit | BufferStorageFlags.MapWriteBit | BufferStorageFlags.MapPersistentBit);
}
_bufferMap = GL.MapBufferRange(BufferTarget.QueryBuffer, nint.Zero, sizeof(long), MapBufferAccessMask.MapReadBit | MapBufferAccessMask.MapWriteBit | MapBufferAccessMask.MapPersistentBit);
_bufferMap = GL.MapBufferRange(BufferTarget.QueryBuffer, nint.Zero, sizeof(long), BufferAccessMask.MapReadBit | BufferAccessMask.MapWriteBit | BufferAccessMask.MapPersistentBit);
}
public void Reset()

View File

@@ -82,7 +82,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public bool IsMainFunction { get; private set; }
public bool MayHaveReturned { get; set; }
public bool WasNonUniformAccessDeclared { get; set; }
public CodeGenContext(
StructuredProgramInfo info,
@@ -90,8 +89,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
GeneratorPool<Instruction> instPool,
GeneratorPool<LiteralInteger> integerPool) : base(SpirvVersionPacked, instPool, integerPool)
{
WasNonUniformAccessDeclared = false;
Info = info;
AttributeUsage = parameters.AttributeUsage;
Definitions = parameters.Definitions;

View File

@@ -587,23 +587,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return OperationResult.Invalid;
}
private static void MarkNonUniform(CodeGenContext context, SpvInstruction inst)
{
if (context.HostCapabilities.SupportsShaderNonUniformIndexing)
{
if (!context.WasNonUniformAccessDeclared)
{
context.AddExtension("SPV_EXT_descriptor_indexing");
context.AddCapability(Capability.ShaderNonUniform);
context.AddCapability(Capability.SampledImageArrayNonUniformIndexing);
context.AddCapability(Capability.StorageImageArrayNonUniformIndexing);
}
context.Decorate(inst, Decoration.NonUniform);
context.WasNonUniformAccessDeclared = true;
}
}
private static OperationResult GenerateImageAtomic(CodeGenContext context, AstOperation operation)
{
AstTextureOperation texOp = (AstTextureOperation)operation;
@@ -630,7 +613,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
SpvInstruction textureIndex = Src(AggregateType.S32);
image = context.AccessChain(imagePointerType, image, textureIndex);
MarkNonUniform(context, image);
}
int coordsCount = texOp.Type.Dimensions;
@@ -701,21 +683,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
ImageDeclaration declaration = context.Images[texOp.GetTextureSetAndBinding()];
SpvInstruction image = declaration.Image;
bool isIndexed = declaration.IsIndexed;
if (isIndexed)
if (declaration.IsIndexed)
{
SpvInstruction textureIndex = Src(AggregateType.S32);
image = context.AccessChain(declaration.ImagePointerType, image, textureIndex);
MarkNonUniform(context, image);
}
image = context.Load(declaration.ImageType, image);
if (isIndexed)
{
MarkNonUniform(context, image);
}
int coordsCount = texOp.Type.Dimensions;
@@ -764,21 +740,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
ImageDeclaration declaration = context.Images[texOp.GetTextureSetAndBinding()];
SpvInstruction image = declaration.Image;
bool isIndexed = declaration.IsIndexed;
if (isIndexed)
if (declaration.IsIndexed)
{
SpvInstruction textureIndex = Src(AggregateType.S32);
image = context.AccessChain(declaration.ImagePointerType, image, textureIndex);
MarkNonUniform(context, image);
}
image = context.Load(declaration.ImageType, image);
if (isIndexed)
{
MarkNonUniform(context, image);
}
int coordsCount = texOp.Type.Dimensions;
@@ -1908,56 +1878,35 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
private static SpvInstruction GenerateSampledImageLoad(CodeGenContext context, AstTextureOperation texOp, SamplerDeclaration declaration, ref int srcIndex)
{
SpvInstruction image = declaration.Image;
bool imageIndexed = declaration.IsIndexed;
if (imageIndexed)
if (declaration.IsIndexed)
{
SpvInstruction textureIndex = context.Get(AggregateType.S32, texOp.GetSource(srcIndex++));
image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex);
MarkNonUniform(context, image);
}
if (texOp.IsSeparate)
{
image = context.Load(declaration.ImageType, image);
if (imageIndexed)
{
MarkNonUniform(context, image);
}
SamplerDeclaration samplerDeclaration = context.Samplers[texOp.GetSamplerSetAndBinding()];
SpvInstruction sampler = samplerDeclaration.Image;
bool samplerIndexed = samplerDeclaration.IsIndexed;
if (samplerIndexed)
if (samplerDeclaration.IsIndexed)
{
SpvInstruction samplerIndex = context.Get(AggregateType.S32, texOp.GetSource(srcIndex++));
sampler = context.AccessChain(samplerDeclaration.SampledImagePointerType, sampler, samplerIndex);
MarkNonUniform(context, sampler);
}
sampler = context.Load(samplerDeclaration.ImageType, sampler);
if (samplerIndexed)
{
MarkNonUniform(context, sampler);
}
image = context.SampledImage(declaration.SampledImageType, image, sampler);
if (imageIndexed || samplerIndexed)
{
MarkNonUniform(context, image);
}
}
else
{
image = context.Load(declaration.SampledImageType, image);
if (imageIndexed)
{
MarkNonUniform(context, image);
}
}
return image;

View File

@@ -336,10 +336,6 @@ namespace Ryujinx.Graphics.Shader
{
return true;
}
bool QueryHostSupportsShaderNonUniformIndexing()
{
return false;
}
/// <summary>
/// Queries host GPU support for signed normalized buffer texture formats.

View File

@@ -9,7 +9,6 @@ namespace Ryujinx.Graphics.Shader.Translation
public readonly bool SupportsShaderBallot;
public readonly bool SupportsShaderBarrierDivergence;
public readonly bool SupportsShaderFloat64;
public readonly bool SupportsShaderNonUniformIndexing;
public readonly bool SupportsTextureShadowLod;
public readonly bool SupportsViewportMask;
@@ -21,7 +20,6 @@ namespace Ryujinx.Graphics.Shader.Translation
bool supportsShaderBallot,
bool supportsShaderBarrierDivergence,
bool supportsShaderFloat64,
bool supportsShaderNonUniformIndexing,
bool supportsTextureShadowLod,
bool supportsViewportMask)
{
@@ -32,7 +30,6 @@ namespace Ryujinx.Graphics.Shader.Translation
SupportsShaderBallot = supportsShaderBallot;
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
SupportsShaderFloat64 = supportsShaderFloat64;
SupportsShaderNonUniformIndexing = supportsShaderNonUniformIndexing;
SupportsTextureShadowLod = supportsTextureShadowLod;
SupportsViewportMask = supportsViewportMask;
}

View File

@@ -364,7 +364,6 @@ namespace Ryujinx.Graphics.Shader.Translation
GpuAccessor.QueryHostSupportsShaderBallot(),
GpuAccessor.QueryHostSupportsShaderBarrierDivergence(),
GpuAccessor.QueryHostSupportsShaderFloat64(),
GpuAccessor.QueryHostSupportsShaderNonUniformIndexing(),
GpuAccessor.QueryHostSupportsTextureShadowLod(),
GpuAccessor.QueryHostSupportsViewportMask());

View File

@@ -1,4 +1,3 @@
using Ryujinx.Common.Logging;
using System;
using System.Diagnostics;
using System.Threading;
@@ -115,7 +114,7 @@ namespace Ryujinx.Graphics.Vulkan
cbs.AddDependant(this);
// We need to add a dependency on the command buffer to all objects this object
// references as well.
// references aswell.
if (_referencedObjs != null)
{
for (int i = 0; i < _referencedObjs.Length; i++)
@@ -176,7 +175,7 @@ namespace Ryujinx.Graphics.Vulkan
}
}
}
Debug.Assert(_referenceCount >= 0);
}

View File

@@ -46,14 +46,7 @@ namespace Ryujinx.Graphics.Vulkan
public static (AccessFlags Access, PipelineStageFlags Stages) GetSubpassAccessSuperset(VulkanRenderer gd)
{
AccessFlags access = BufferAccess |
AccessFlags.ShaderReadBit |
AccessFlags.ShaderWriteBit |
AccessFlags.ColorAttachmentReadBit |
AccessFlags.ColorAttachmentWriteBit |
AccessFlags.DepthStencilAttachmentReadBit |
AccessFlags.DepthStencilAttachmentWriteBit;
AccessFlags access = BufferAccess;
PipelineStageFlags stages = PipelineStageFlags.AllGraphicsBit;
if (gd.TransformFeedbackApi != null)

View File

@@ -16,15 +16,15 @@ namespace Ryujinx.Graphics.Vulkan
{
DescriptorSetLayout[] layouts = new DescriptorSetLayout[setDescriptors.Count];
bool[] updateAfterBindFlags = new bool[setDescriptors.Count];
bool isMoltenVk = gd.IsMoltenVk;
for (int setIndex = 0; setIndex < setDescriptors.Count; setIndex++)
{
ResourceDescriptorCollection rdc = setDescriptors[setIndex];
ResourceStages activeStages = ResourceStages.None;
if (isMoltenVk)
{
for (int descIndex = 0; descIndex < rdc.Descriptors.Count; descIndex++)
@@ -42,13 +42,12 @@ namespace Ryujinx.Graphics.Vulkan
ResourceDescriptor descriptor = rdc.Descriptors[descIndex];
ResourceStages stages = descriptor.Stages;
if (descriptor.Type == ResourceType.StorageBuffer && gd.IsMoltenVk)
if (descriptor.Type == ResourceType.StorageBuffer && isMoltenVk)
{
// There's a bug in MoltenVK where using the same buffer across different stages
// There's a bug on MoltenVK where using the same buffer across different stages
// causes invalid resource errors, allow the binding on all active stages as workaround.
// https://github.com/KhronosGroup/MoltenVK/issues/1870
stages = activeStages;
}
}
layoutBindings[descIndex] = new DescriptorSetLayoutBinding
{

View File

@@ -15,8 +15,6 @@ namespace Ryujinx.Graphics.Vulkan
Image dstImage,
TextureCreateInfo srcInfo,
TextureCreateInfo dstInfo,
TextureCreateInfo srcStorageInfo,
TextureCreateInfo dstStorageInfo,
Extents2D srcRegion,
Extents2D dstRegion,
int srcLayer,
@@ -42,13 +40,6 @@ namespace Ryujinx.Graphics.Vulkan
return (xy1, xy2);
}
static (Offset3D, Offset3D) ClampOffsetsToMip(Offset3D xy1, Offset3D xy2, int mipW, int mipH)
{
return (
new Offset3D(Math.Min(xy1.X, mipW), Math.Min(xy1.Y, mipH), xy1.Z),
new Offset3D(Math.Min(xy2.X, mipW), Math.Min(xy2.Y, mipH), xy2.Z));
}
if (srcAspectFlags == 0)
{
srcAspectFlags = srcInfo.Format.ConvertAspectFlags();
@@ -89,14 +80,6 @@ namespace Ryujinx.Graphics.Vulkan
(srcOffsets.Element0, srcOffsets.Element1) = ExtentsToOffset3D(srcRegion, srcInfo.Width, srcInfo.Height, level);
(dstOffsets.Element0, dstOffsets.Element1) = ExtentsToOffset3D(dstRegion, dstInfo.Width, dstInfo.Height, level);
int srcMipW = Math.Max(1, srcStorageInfo.Width >> (int)copySrcLevel);
int srcMipH = Math.Max(1, srcStorageInfo.Height >> (int)copySrcLevel);
int dstMipW = Math.Max(1, dstStorageInfo.Width >> (int)copyDstLevel);
int dstMipH = Math.Max(1, dstStorageInfo.Height >> (int)copyDstLevel);
(srcOffsets.Element0, srcOffsets.Element1) = ClampOffsetsToMip(srcOffsets.Element0, srcOffsets.Element1, srcMipW, srcMipH);
(dstOffsets.Element0, dstOffsets.Element1) = ClampOffsetsToMip(dstOffsets.Element0, dstOffsets.Element1, dstMipW, dstMipH);
ImageBlit region = new()
{
SrcSubresource = srcSl,
@@ -138,8 +121,6 @@ namespace Ryujinx.Graphics.Vulkan
Image dstImage,
TextureCreateInfo srcInfo,
TextureCreateInfo dstInfo,
TextureCreateInfo srcStorageInfo,
TextureCreateInfo dstStorageInfo,
int srcViewLayer,
int dstViewLayer,
int srcViewLevel,
@@ -170,8 +151,6 @@ namespace Ryujinx.Graphics.Vulkan
dstImage,
srcInfo,
dstInfo,
srcStorageInfo,
dstStorageInfo,
srcViewLayer,
dstViewLayer,
srcViewLevel,
@@ -207,8 +186,6 @@ namespace Ryujinx.Graphics.Vulkan
Image dstImage,
TextureCreateInfo srcInfo,
TextureCreateInfo dstInfo,
TextureCreateInfo srcStorageInfo,
TextureCreateInfo dstStorageInfo,
int srcViewLayer,
int dstViewLayer,
int srcViewLevel,
@@ -337,14 +314,6 @@ namespace Ryujinx.Graphics.Vulkan
int copyWidth = sizeInBlocks ? BitUtils.DivRoundUp(width, blockWidth) : width;
int copyHeight = sizeInBlocks ? BitUtils.DivRoundUp(height, blockHeight) : height;
int srcMipW = Math.Max(1, srcStorageInfo.Width >> (srcViewLevel + srcLevel + level));
int srcMipH = Math.Max(1, srcStorageInfo.Height >> (srcViewLevel + srcLevel + level));
int dstMipW = Math.Max(1, dstStorageInfo.Width >> (dstViewLevel + dstLevel + level));
int dstMipH = Math.Max(1, dstStorageInfo.Height >> (dstViewLevel + dstLevel + level));
copyWidth = Math.Min(copyWidth, Math.Min(srcMipW, dstMipW));
copyHeight = Math.Min(copyHeight, Math.Min(srcMipH, dstMipH));
Extent3D extent = new((uint)copyWidth, (uint)copyHeight, (uint)srcDepth);
if (srcInfo.Samples > 1 && srcInfo.Samples != dstInfo.Samples)

View File

@@ -67,8 +67,6 @@ namespace Ryujinx.Graphics.Vulkan
public VkFormat VkFormat { get; }
public ImageUsageFlags UsageFlags { get; }
public unsafe TextureStorage(
VulkanRenderer gd,
Device device,
@@ -95,8 +93,7 @@ namespace Ryujinx.Graphics.Vulkan
SampleCountFlags sampleCountFlags = ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)info.Samples);
ImageUsageFlags usage = GetImageUsage(info.Format, gd.Capabilities, isMsImageStorageSupported);
UsageFlags = usage;
ImageUsageFlags usage = GetImageUsage(info.Format, gd.Capabilities, isMsImageStorageSupported, true);
ImageCreateFlags flags = ImageCreateFlags.CreateMutableFormatBit | ImageCreateFlags.CreateExtendedUsageBit;
@@ -162,7 +159,7 @@ namespace Ryujinx.Graphics.Vulkan
_imageAuto = new Auto<DisposableImage>(new DisposableImage(_gd.Api, device, _image));
InitialTransition(ImageLayout.Undefined, ImageLayout.General);
InitialTransition(ImageLayout.Preinitialized, ImageLayout.General);
}
_slices = new TextureSliceInfo[levels * _depthOrLayers];
@@ -310,7 +307,7 @@ namespace Ryujinx.Graphics.Vulkan
}
}
public static ImageUsageFlags GetImageUsage(Format format, in HardwareCapabilities capabilities, bool isMsImageStorageSupported)
public static ImageUsageFlags GetImageUsage(Format format, in HardwareCapabilities capabilities, bool isMsImageStorageSupported, bool extendedUsage)
{
ImageUsageFlags usage = DefaultUsageFlags;
@@ -323,7 +320,7 @@ namespace Ryujinx.Graphics.Vulkan
usage |= ImageUsageFlags.ColorAttachmentBit;
}
if (format.IsImageCompatible && isMsImageStorageSupported)
if ((format.IsImageCompatible && isMsImageStorageSupported) || extendedUsage)
{
usage |= ImageUsageFlags.StorageBit;
}

View File

@@ -64,7 +64,7 @@ namespace Ryujinx.Graphics.Vulkan
bool isMsImageStorageSupported = gd.Capabilities.SupportsShaderStorageImageMultisample || !info.Target.IsMultisample;
VkFormat format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format, isMsImageStorageSupported);
ImageUsageFlags usage = TextureStorage.GetImageUsage(info.Format, gd.Capabilities, isMsImageStorageSupported) & storage.UsageFlags;
ImageUsageFlags usage = TextureStorage.GetImageUsage(info.Format, gd.Capabilities, isMsImageStorageSupported, false);
uint levels = (uint)info.Levels;
uint layers = (uint)info.GetLayers();
@@ -133,8 +133,6 @@ namespace Ryujinx.Graphics.Vulkan
shaderUsage |= ImageUsageFlags.StorageBit;
}
shaderUsage &= storage.UsageFlags;
_imageView = CreateImageView(componentMapping, subresourceRange, type, shaderUsage);
// Framebuffer attachments and storage images requires a identity component mapping.
@@ -259,8 +257,6 @@ namespace Ryujinx.Graphics.Vulkan
dstImage,
src.Info,
dst.Info,
src.Storage.Info,
dst.Storage.Info,
src.FirstLayer,
dst.FirstLayer,
src.FirstLevel,
@@ -314,8 +310,6 @@ namespace Ryujinx.Graphics.Vulkan
dstImage,
src.Info,
dst.Info,
src.Storage.Info,
dst.Storage.Info,
src.FirstLayer,
dst.FirstLayer,
src.FirstLevel,
@@ -391,8 +385,6 @@ namespace Ryujinx.Graphics.Vulkan
dst.GetImage().Get(cbs).Value,
src.Info,
dst.Info,
src.Storage.Info,
dst.Storage.Info,
src.FirstLayer,
dst.FirstLayer,
src.FirstLevel,
@@ -418,8 +410,6 @@ namespace Ryujinx.Graphics.Vulkan
dst.GetImage().Get(cbs).Value,
src.Info,
dst.Info,
src.Storage.Info,
dst.Storage.Info,
srcRegion,
dstRegion,
src.FirstLayer,
@@ -473,8 +463,6 @@ namespace Ryujinx.Graphics.Vulkan
dstImage.Get(cbs).Value,
src.Info,
dst.Info,
src.Storage.Info,
dst.Storage.Info,
srcRegion,
dstRegion,
src.FirstLayer,

View File

@@ -68,7 +68,9 @@ namespace Ryujinx.Graphics.Vulkan
int stride = (_stride + (alignment - 1)) & -alignment;
int newSize = (_size / _stride) * stride;
updater.BindVertexBuffer(cbs, binding, autoBuffer, 0, newSize, (ulong)stride);
Buffer buffer = autoBuffer.Get(cbs, 0, newSize).Value;
updater.BindVertexBuffer(cbs, binding, buffer, 0, (ulong)newSize, (ulong)stride);
_buffer = autoBuffer;
@@ -91,7 +93,11 @@ namespace Ryujinx.Graphics.Vulkan
if (autoBuffer != null)
{
updater.BindVertexBuffer(cbs, binding, autoBuffer, _offset, _size, (ulong)_stride);
int offset = _offset;
bool mirrorable = _size <= VertexBufferMaxMirrorable;
Buffer buffer = mirrorable ? autoBuffer.GetMirrorable(cbs, ref offset, _size, out _).Value : autoBuffer.Get(cbs, offset, _size).Value;
updater.BindVertexBuffer(cbs, binding, buffer, (ulong)offset, (ulong)_size, (ulong)_stride);
}
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Buffers;
using VkBuffer = Silk.NET.Vulkan.Buffer;
namespace Ryujinx.Graphics.Vulkan
@@ -16,10 +15,6 @@ namespace Ryujinx.Graphics.Vulkan
private readonly NativeArray<ulong> _sizes;
private readonly NativeArray<ulong> _strides;
private readonly Auto<DisposableBuffer>[] _bufferAutos;
private readonly int[] _bufferOffsetsForGet;
private readonly int[] _bufferSizesForGet;
public VertexBufferUpdater(VulkanRenderer gd)
{
_gd = gd;
@@ -28,13 +23,9 @@ namespace Ryujinx.Graphics.Vulkan
_offsets = new NativeArray<ulong>(Constants.MaxVertexBuffers);
_sizes = new NativeArray<ulong>(Constants.MaxVertexBuffers);
_strides = new NativeArray<ulong>(Constants.MaxVertexBuffers);
_bufferAutos = new Auto<DisposableBuffer>[Constants.MaxVertexBuffers];
_bufferOffsetsForGet = new int[Constants.MaxVertexBuffers];
_bufferSizesForGet = new int[Constants.MaxVertexBuffers];
}
public void BindVertexBuffer(CommandBufferScoped cbs, uint binding, Auto<DisposableBuffer> autoBuffer, int offset, int size, ulong stride)
public void BindVertexBuffer(CommandBufferScoped cbs, uint binding, VkBuffer buffer, ulong offset, ulong size, ulong stride)
{
if (_count == 0)
{
@@ -48,11 +39,9 @@ namespace Ryujinx.Graphics.Vulkan
int index = (int)_count;
_bufferAutos[index] = autoBuffer;
_bufferOffsetsForGet[index] = offset;
_bufferSizesForGet[index] = size;
_offsets[index] = (ulong)offset;
_sizes[index] = (ulong)size;
_buffers[index] = buffer;
_offsets[index] = offset;
_sizes[index] = size;
_strides[index] = stride;
_count++;
@@ -62,65 +51,23 @@ namespace Ryujinx.Graphics.Vulkan
{
if (_count != 0)
{
int count = (int)_count;
uint baseBinding = _baseBinding;
if (_gd.Capabilities.SupportsExtendedDynamicState)
{
_gd.ExtendedDynamicStateApi.CmdBindVertexBuffers2(
cbs.CommandBuffer,
_baseBinding,
_count,
_buffers.Pointer,
_offsets.Pointer,
_sizes.Pointer,
_strides.Pointer);
}
else
{
_gd.Api.CmdBindVertexBuffers(cbs.CommandBuffer, _baseBinding, _count, _buffers.Pointer, _offsets.Pointer);
}
_count = 0;
Auto<DisposableBuffer>[] autos = ArrayPool<Auto<DisposableBuffer>>.Shared.Rent(count);
Span<int> getOffsets = stackalloc int[Constants.MaxVertexBuffers];
Span<int> getSizes = stackalloc int[Constants.MaxVertexBuffers];
Span<ulong> offsets = stackalloc ulong[Constants.MaxVertexBuffers];
Span<ulong> sizes = stackalloc ulong[Constants.MaxVertexBuffers];
Span<ulong> strides = stackalloc ulong[Constants.MaxVertexBuffers];
Span<VkBuffer> buffers = stackalloc VkBuffer[Constants.MaxVertexBuffers];
for (int i = 0; i < count; i++)
{
autos[i] = _bufferAutos[i];
_bufferAutos[i] = null;
getOffsets[i] = _bufferOffsetsForGet[i];
getSizes[i] = _bufferSizesForGet[i];
offsets[i] = _offsets[i];
sizes[i] = _sizes[i];
strides[i] = _strides[i];
}
try
{
for (int i = 0; i < count; i++)
{
buffers[i] = autos[i].Get(cbs, getOffsets[i], getSizes[i]).Value;
autos[i] = null;
}
for (int i = 0; i < count; i++)
{
_buffers[i] = buffers[i];
_offsets[i] = offsets[i];
_sizes[i] = sizes[i];
_strides[i] = strides[i];
}
if (_gd.Capabilities.SupportsExtendedDynamicState)
{
_gd.ExtendedDynamicStateApi.CmdBindVertexBuffers2(
cbs.CommandBuffer,
baseBinding,
(uint)count,
_buffers.Pointer,
_offsets.Pointer,
_sizes.Pointer,
_strides.Pointer);
}
else
{
_gd.Api.CmdBindVertexBuffers(cbs.CommandBuffer, baseBinding, (uint)count, _buffers.Pointer, _offsets.Pointer);
}
}
finally
{
ArrayPool<Auto<DisposableBuffer>>.Shared.Return(autos, clearArray: true);
}
}
}

View File

@@ -494,8 +494,6 @@ namespace Ryujinx.Graphics.Vulkan
UniformBufferStandardLayout = supportedPhysicalDeviceVulkan12Features.UniformBufferStandardLayout,
UniformAndStorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.UniformAndStorageBuffer8BitAccess,
StorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.StorageBuffer8BitAccess,
ShaderSampledImageArrayNonUniformIndexing = supportedPhysicalDeviceVulkan12Features.ShaderSampledImageArrayNonUniformIndexing,
ShaderStorageImageArrayNonUniformIndexing = supportedPhysicalDeviceVulkan12Features.ShaderStorageImageArrayNonUniformIndexing,
};
pExtendedFeatures = &featuresVk12;

View File

@@ -435,8 +435,8 @@ namespace Ryujinx.Graphics.Vulkan
_physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName),
features2.Features.MultiViewport && !(IsMoltenVk && Vendor == Vendor.Amd), // Workaround for AMD on MoltenVK issue
featuresRobustness2.NullDescriptor || IsMoltenVk,
supportsPushDescriptors,
IsMoltenVk ? 16 : propertiesPushDescriptor.MaxPushDescriptors, // In case an old version of MoltenVK is used, apply a limit to prevent vertex explosions.
supportsPushDescriptors && !IsMoltenVk,
propertiesPushDescriptor.MaxPushDescriptors,
featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart,
featuresPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart,
supportsTransformFeedback,
@@ -775,11 +775,7 @@ namespace Ryujinx.Graphics.Vulkan
supportsShaderBallot: false,
supportsShaderBarrierDivergence: Vendor != Vendor.Intel,
supportsShaderFloat64: Capabilities.SupportsShaderFloat64,
supportsShaderNonUniformIndexing:
featuresVk12.ShaderSampledImageArrayNonUniformIndexing &&
featuresVk12.ShaderStorageImageArrayNonUniformIndexing,
supportsTextureGatherOffsets: features2.Features.ShaderImageGatherExtended,
supportsTextureGatherOffsets: features2.Features.ShaderImageGatherExtended && !IsMoltenVk,
supportsTextureShadowLod: false,
supportsVertexStoreAndAtomics: features2.Features.VertexPipelineStoresAndAtomics,
supportsViewportIndexVertexTessellation: featuresVk12.ShaderOutputViewportIndex,

View File

@@ -391,12 +391,12 @@ namespace Ryujinx.Graphics.Vulkan
{
if (_effect != null)
{
_gd.FlushAllCommands();
_gd.CommandBufferPool.Return(
cbs,
null,
[PipelineStageFlags.ColorAttachmentOutputBit],
null);
_gd.FlushAllCommands();
cbs.GetFence().Wait();
cbs = _gd.CommandBufferPool.Rent();
}
@@ -455,8 +455,6 @@ namespace Ryujinx.Graphics.Vulkan
ImageLayout.General,
ImageLayout.PresentSrcKhr);
_gd.FlushAllCommands();
_gd.CommandBufferPool.Return(
cbs,
[_imageAvailableSemaphores[semaphoreIndex]],

View File

@@ -14,10 +14,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" >
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
</ItemGroup>
</Project>

View File

@@ -1,5 +1,4 @@
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using System;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletProxy
@@ -45,35 +44,6 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
return ResultCode.Success;
}
[CommandCmif(1)]
// PushOutData(object<nn::am::service::IStorage>)
public ResultCode PushOutData(ServiceCtx context)
{
IStorage appletData = GetObject<IStorage>(context, 0);
if (appletData == null || appletData.Data.Length == 0) // is this necessary?
{
return ResultCode.NullObject;
}
_appletStandalone.InputData.Enqueue(appletData.Data);
return ResultCode.Success;
}
[CommandCmif(10)]
// ExitProcessAndReturn -> nn::am::service::LibraryAppletInfo
public ResultCode ExitProcessAndReturn(ServiceCtx context)
{
// Exits the LibraryApplet and returns to running the title which launched this LibraryApplet (qlaunch for example).
// On success, official sw will enter an infinite loop with sleep-thread value 86400000000000.
// Since we don't currently support qlaunch, it's fine to stub it.
Logger.Stub?.PrintStub(LogClass.Service);
return ResultCode.Success;
}
[CommandCmif(11)]
// GetLibraryAppletInfo() -> nn::am::service::LibraryAppletInfo
@@ -97,8 +67,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
AppletIdentifyInfo appletIdentifyInfo = new()
{
AppletId = AppletId.QLaunch,
// 0x4 padding
TitleId = 0x0100000000001000, // qlaunch systemAppletMenu title ID
TitleId = 0x0100000000001000,
};
context.ResponseData.WriteStruct(appletIdentifyInfo);

View File

@@ -1,9 +1,7 @@
using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Services.Caps.Types;
using SkiaSharp;
using System;
using System.Globalization;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -11,20 +9,16 @@ using System.Security.Cryptography;
namespace Ryujinx.HLE.HOS.Services.Caps
{
internal class CaptureManager
class CaptureManager
{
public CaptureManager(Switch device)
{
_ = device;
}
private readonly string _sdCardPath = FileSystem.VirtualFileSystem.GetSdCardPath();
private readonly string _sdCardPath;
private uint _shimLibraryVersion;
private const int ScreenshotWidth = 1280;
private const int ScreenshotHeight = 720;
private const int ScreenshotBytesPerPixel = 4;
private const int ScreenshotDataSize = ScreenshotWidth * ScreenshotHeight * ScreenshotBytesPerPixel; // 0x384000
public CaptureManager(Switch device)
{
_sdCardPath = FileSystem.VirtualFileSystem.GetSdCardPath();
}
public ResultCode SetShimLibraryVersion(ServiceCtx context)
{
@@ -59,94 +53,81 @@ namespace Ryujinx.HLE.HOS.Services.Caps
return resultCode;
}
public ResultCode SaveScreenShot(
byte[] screenshotData,
ulong appletResourceUserId,
ulong titleId,
out ApplicationAlbumEntry applicationAlbumEntry)
public ResultCode SaveScreenShot(byte[] screenshotData, ulong appletResourceUserId, ulong titleId, out ApplicationAlbumEntry applicationAlbumEntry)
{
Logger.Stub?.PrintStub(LogClass.ServiceCaps, new
{
appletResourceUserId,
titleId,
screenshotDataLength = screenshotData?.Length ?? 0,
});
applicationAlbumEntry = default;
if (screenshotData == null || screenshotData.Length == 0)
if (screenshotData.Length == 0)
{
return ResultCode.NullInputBuffer;
}
if (screenshotData.Length < ScreenshotDataSize)
{
Logger.Warning?.PrintMsg(
LogClass.ServiceCaps,
$"Invalid screenshot buffer size 0x{screenshotData.Length:X}; expected at least 0x{ScreenshotDataSize:X}.");
return ResultCode.NullInputBuffer;
}
DateTime currentDateTime = DateTime.Now;
applicationAlbumEntry = new ApplicationAlbumEntry()
{
Size = (ulong)Unsafe.SizeOf<ApplicationAlbumEntry>(),
TitleId = titleId,
AlbumFileDateTime = new AlbumFileDateTime()
{
Year = (ushort)currentDateTime.Year,
Month = (byte)currentDateTime.Month,
Day = (byte)currentDateTime.Day,
Hour = (byte)currentDateTime.Hour,
Minute = (byte)currentDateTime.Minute,
Second = (byte)currentDateTime.Second,
UniqueId = 0,
},
AlbumStorage = AlbumStorage.Sd,
ContentType = ContentType.Screenshot,
Padding = new Array5<byte>(),
Unknown0x1f = 1,
};
// NOTE: The hex hash is a HMAC-SHA256 (first 32 bytes) using a hardcoded secret key over the titleId, we can simulate it by hashing the titleId instead.
string hash = Convert.ToHexString(SHA256.HashData(BitConverter.GetBytes(titleId)))[..0x20];
string folderPath = Path.Combine(
_sdCardPath,
"Nintendo",
"Album",
currentDateTime.Year.ToString("0000", CultureInfo.InvariantCulture),
currentDateTime.Month.ToString("00", CultureInfo.InvariantCulture),
currentDateTime.Day.ToString("00", CultureInfo.InvariantCulture));
string filePath = GenerateFilePath(folderPath, applicationAlbumEntry, currentDateTime, hash);
_ = Directory.CreateDirectory(folderPath);
while (File.Exists(filePath))
{
applicationAlbumEntry.AlbumFileDateTime.UniqueId++;
filePath = GenerateFilePath(folderPath, applicationAlbumEntry, currentDateTime, hash);
}
using SKBitmap bitmap = new(new SKImageInfo(ScreenshotWidth, ScreenshotHeight, SKColorType.Rgba8888));
IntPtr pixels = bitmap.GetPixels();
if (pixels == IntPtr.Zero)
/*
// NOTE: On our current implementation, appletResourceUserId starts at 0, disable it for now.
if (appletResourceUserId == 0)
{
return ResultCode.InvalidArgument;
}
*/
Marshal.Copy(screenshotData, 0, pixels, ScreenshotDataSize);
/*
// Doesn't occur in our case.
if (applicationAlbumEntry == null)
{
return ResultCode.NullOutputBuffer;
}
*/
using SKData data = bitmap.Encode(SKEncodedImageFormat.Jpeg, 80);
using FileStream file = File.OpenWrite(filePath);
data.SaveTo(file);
if (screenshotData.Length >= 0x384000)
{
DateTime currentDateTime = DateTime.Now;
return ResultCode.Success;
applicationAlbumEntry = new ApplicationAlbumEntry()
{
Size = (ulong)Unsafe.SizeOf<ApplicationAlbumEntry>(),
TitleId = titleId,
AlbumFileDateTime = new AlbumFileDateTime()
{
Year = (ushort)currentDateTime.Year,
Month = (byte)currentDateTime.Month,
Day = (byte)currentDateTime.Day,
Hour = (byte)currentDateTime.Hour,
Minute = (byte)currentDateTime.Minute,
Second = (byte)currentDateTime.Second,
UniqueId = 0,
},
AlbumStorage = AlbumStorage.Sd,
ContentType = ContentType.Screenshot,
Padding = new Array5<byte>(),
Unknown0x1f = 1,
};
// NOTE: The hex hash is a HMAC-SHA256 (first 32 bytes) using a hardcoded secret key over the titleId, we can simulate it by hashing the titleId instead.
string hash = Convert.ToHexString(SHA256.HashData(BitConverter.GetBytes(titleId)))[..0x20];
string folderPath = Path.Combine(_sdCardPath, "Nintendo", "Album", currentDateTime.Year.ToString("00"), currentDateTime.Month.ToString("00"), currentDateTime.Day.ToString("00"));
string filePath = GenerateFilePath(folderPath, applicationAlbumEntry, currentDateTime, hash);
// TODO: Handle that using the FS service implementation and return the right error code instead of throwing exceptions.
Directory.CreateDirectory(folderPath);
while (File.Exists(filePath))
{
applicationAlbumEntry.AlbumFileDateTime.UniqueId++;
filePath = GenerateFilePath(folderPath, applicationAlbumEntry, currentDateTime, hash);
}
// NOTE: The saved JPEG file doesn't have the limitation in the extra EXIF data.
using SKBitmap bitmap = new(new SKImageInfo(1280, 720, SKColorType.Rgba8888));
Marshal.Copy(screenshotData, 0, bitmap.GetPixels(), screenshotData.Length);
using SKData data = bitmap.Encode(SKEncodedImageFormat.Jpeg, 80);
using FileStream file = File.OpenWrite(filePath);
data.SaveTo(file);
return ResultCode.Success;
}
return ResultCode.NullInputBuffer;
}
private string GenerateFilePath(string folderPath, ApplicationAlbumEntry applicationAlbumEntry, DateTime currentDateTime, string hash)

View File

@@ -1,19 +1,13 @@
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Caps.Types;
namespace Ryujinx.HLE.HOS.Services.Caps
{
[Service("caps:su")] // 6.0.0+
internal class IScreenShotApplicationService : IpcService
class IScreenShotApplicationService : IpcService
{
private const ulong ScreenshotDataSize = 0x384000;
private const ulong ApplicationDataSize = 0x404;
public IScreenShotApplicationService(ServiceCtx context) { }
public IScreenShotApplicationService(ServiceCtx context)
{
_ = context;
}
[CommandCmif(32)] // 7.0.0+
// SetShimLibraryVersion(pid, u64, nn::applet::AppletResourceUserId)
public ResultCode SetShimLibraryVersion(ServiceCtx context)
@@ -39,15 +33,6 @@ namespace Ryujinx.HLE.HOS.Services.Caps
ulong screenshotDataPosition = context.Request.SendBuff[0].Position;
ulong screenshotDataSize = context.Request.SendBuff[0].Size;
if (screenshotDataSize < ScreenshotDataSize)
{
Logger.Warning?.PrintMsg(
LogClass.ServiceCaps,
$"Invalid screenshot buffer size 0x{screenshotDataSize:X}; expected at least 0x{ScreenshotDataSize:X}.");
return ResultCode.NullInputBuffer;
}
byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray();
ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry);
@@ -75,24 +60,6 @@ namespace Ryujinx.HLE.HOS.Services.Caps
ulong screenshotDataPosition = context.Request.SendBuff[1].Position;
ulong screenshotDataSize = context.Request.SendBuff[1].Size;
if (applicationDataSize != ApplicationDataSize)
{
Logger.Warning?.PrintMsg(
LogClass.ServiceCaps,
$"Invalid ApplicationData size 0x{applicationDataSize:X}; expected 0x{ApplicationDataSize:X}.");
return ResultCode.InvalidArgument;
}
if (screenshotDataSize < ScreenshotDataSize)
{
Logger.Warning?.PrintMsg(
LogClass.ServiceCaps,
$"Invalid screenshot buffer size 0x{screenshotDataSize:X}; expected at least 0x{ScreenshotDataSize:X}.");
return ResultCode.NullInputBuffer;
}
// TODO: Parse the application data: At 0x00 it's UserData (Size of 0x400), at 0x404 it's a uint UserDataSize (Always empty for now).
_ = context.Memory.GetSpan(applicationDataPosition, (int)applicationDataSize).ToArray();
@@ -121,23 +88,6 @@ namespace Ryujinx.HLE.HOS.Services.Caps
ulong screenshotDataPosition = context.Request.SendBuff[1].Position;
ulong screenshotDataSize = context.Request.SendBuff[1].Size;
if (userIdListSize != 0x88)
{
Logger.Warning?.PrintMsg(
LogClass.ServiceCaps,
$"Invalid UserIdList size 0x{userIdListSize:X}; expected 0x88.");
return ResultCode.InvalidArgument;
}
if (screenshotDataSize < ScreenshotDataSize)
{
Logger.Warning?.PrintMsg(
LogClass.ServiceCaps,
$"Invalid screenshot buffer size 0x{screenshotDataSize:X}; expected at least 0x{ScreenshotDataSize:X}.");
return ResultCode.NullInputBuffer;
}
// TODO: Parse the UserIdList.
_ = context.Memory.GetSpan(userIdListPosition, (int)userIdListSize).ToArray();

View File

@@ -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;
@@ -66,11 +68,10 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{
if (localCommunicationId == localCommunicationIdChecked)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,"CheckLocalCommumicationIdPermission: Checked!");
return true;
}
}
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,"CheckLocalCommumicationIdPermission: Check failed!");
return false;
}
@@ -81,7 +82,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
if (_nifmResultCode != ResultCode.Success)
{
context.ResponseData.Write((int)NetworkState.Error);
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,$"GetState: _nifmResultCode = {_nifmResultCode.ToString()}");
return ResultCode.Success;
}
@@ -113,14 +114,12 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
if (_nifmResultCode != ResultCode.Success)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,$"GetNetworkInfo: _nifmResultCode = {_nifmResultCode.ToString()}");
return _nifmResultCode;
}
ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo);
if (resultCode != ResultCode.Success)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,$"GetState: resultCode = {resultCode.ToString()}");
return resultCode;
}
@@ -136,22 +135,18 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
if (_state == NetworkState.StationConnected)
{
networkInfo = _station.NetworkInfo;
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,"GetNetworkInfoImpl: _station");
}
else if (_state == NetworkState.AccessPointCreated)
{
networkInfo = _accessPoint.NetworkInfo;
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,"GetNetworkInfoImpl: _accessPoint");
}
else
{
networkInfo = new NetworkInfo();
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,"GetNetworkInfoImpl: Invalid state!");
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,$"GetNetworkInfoImpl: networkInfo = {networkInfo}");
return ResultCode.InvalidState;
}
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,$"GetNetworkInfoImpl: networkInfo = {networkInfo}");
return ResultCode.Success;
}
@@ -203,7 +198,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
}
else
{
Logger.NetLog?.Print(LogClass.ServiceLdn, $"Console's LDN IP is \"{unicastAddress.Address}\".");
Logger.Info?.Print(LogClass.ServiceLdn, $"Console's LDN IP is \"{unicastAddress.Address}\".");
context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(unicastAddress.Address));
context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(unicastAddress.IPv4Mask));
@@ -211,7 +206,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
}
else
{
Logger.NetLog?.Print(LogClass.ServiceLdn, $"LDN obtained proxy IP.");
Logger.Info?.Print(LogClass.ServiceLdn, $"LDN obtained proxy IP.");
context.ResponseData.Write(config.ProxyIp);
context.ResponseData.Write(config.ProxySubnetMask);
@@ -232,7 +227,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
// NOTE: Returns ResultCode.InvalidArgument if _disconnectReason is null, doesn't occur in our case.
context.ResponseData.Write((short)_disconnectReason);
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetDisconnectReason: {_disconnectReason}");
return ResultCode.Success;
}
@@ -252,14 +247,12 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{
if (_nifmResultCode != ResultCode.Success)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetSecurityParameter: _nifmResultCode = {_nifmResultCode}");
return _nifmResultCode;
}
ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo);
if (resultCode != ResultCode.Success)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetSecurityParameter: resultCode = {resultCode}");
return resultCode;
}
@@ -270,8 +263,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
};
context.ResponseData.WriteStruct(securityParameter);
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetSecurityParameter: securityParameter = {securityParameter}");
return ResultCode.Success;
}
@@ -281,14 +273,12 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{
if (_nifmResultCode != ResultCode.Success)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetNetworkConfig: _nifmResultCode = {_nifmResultCode}");
return _nifmResultCode;
}
ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo);
if (resultCode != ResultCode.Success)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetNetworkConfig: resultCode = {resultCode}");
return resultCode;
}
@@ -302,8 +292,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
};
context.ResponseData.WriteStruct(networkConfig);
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetNetworkConfig: networkConfig = {networkConfig}");
return ResultCode.Success;
}
@@ -334,14 +322,12 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
if (_nifmResultCode != ResultCode.Success)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetNetworkInfoLatestUpdate: _nifmResultCode = {_nifmResultCode}");
return _nifmResultCode;
}
ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo);
if (resultCode != ResultCode.Success)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"GetNetworkInfoLatestUpdate: resultCode = {resultCode}");
return resultCode;
}
@@ -392,7 +378,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
if (_nifmResultCode != ResultCode.Success)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ScanImpl: _nifmResultCode = {_nifmResultCode}");
return _nifmResultCode;
}
@@ -415,7 +400,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{
if (scanFilter.Ssid.Length <= 31)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ScanImpl: resultCode = {resultCode}");
return resultCode;
}
}
@@ -424,13 +408,11 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{
if (scanFilterFlag > ScanFilterFlag.All)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ScanImpl: resultCode = {resultCode}");
return resultCode;
}
if (_state - 3 >= NetworkState.AccessPoint)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "ScanImpl: Invalid state!");
resultCode = ResultCode.InvalidState;
}
else
@@ -455,8 +437,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
}
}
}
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ScanImpl: resultCode = {resultCode}");
return resultCode;
}
@@ -481,7 +462,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
}
}
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ScanInternal: availableGames = {availableGames}");
return ResultCode.Success;
}
@@ -522,8 +502,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
throw new ArgumentException($"{GetType().FullName}: Protocol value is not 1 or 3!! Protocol value: {protocolValue}");
}
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"SetProtocol: protocolValue = {protocolValue}");
Logger.Stub?.PrintStub(LogClass.ServiceLdn, new { protocolValue});
Logger.Stub?.PrintStub(LogClass.ServiceLdn, $"Protocol value: {protocolValue}");
return ResultCode.Success;
}
@@ -533,13 +512,11 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{
if (_nifmResultCode != ResultCode.Success)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"OpenAccessPoint: _nifmResultCode = {_nifmResultCode}");
return _nifmResultCode;
}
if (_state != NetworkState.Initialized)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "OpenAccessPoint: Invalid state!");
return ResultCode.InvalidState;
}
@@ -561,7 +538,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{
if (_nifmResultCode != ResultCode.Success)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"CloseAccessPoint: _nifmResultCode = {_nifmResultCode}");
return _nifmResultCode;
}
@@ -571,7 +547,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
}
else
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "CloseAccessPoint: Invalid state!");
return ResultCode.InvalidState;
}
@@ -621,13 +596,11 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
bool isLocalCommunicationIdValid = CheckLocalCommunicationIdPermission(context, (ulong)networkConfig.IntentId.LocalCommunicationId);
if (!isLocalCommunicationIdValid && NetworkClient.NeedsRealId)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "CreateNetworkImpl: Invalid object!");
return ResultCode.InvalidObject;
}
if (_nifmResultCode != ResultCode.Success)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"CreateNetworkImpl: _nifmResultCode = {_nifmResultCode}");
return _nifmResultCode;
}
@@ -656,22 +629,16 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
AddressList addressList = MemoryMarshal.Cast<byte, AddressList>(addressListBytes)[0];
_accessPoint.CreateNetworkPrivate(securityConfig, securityParameter, userConfig, networkConfig, addressList);
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"CreateNetworkImpl: Created private network! " +
$"| securityConfig = {securityConfig} | securityParameter = {securityParameter} " +
$"| userConfig = {userConfig} | networkConfig = {networkConfig} | addressList = {addressList}");
}
else
{
_accessPoint.CreateNetwork(securityConfig, userConfig, networkConfig);
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"CreateNetworkImpl: Created network! " +
$"| securityConfig = {securityConfig} | userConfig = {userConfig} | networkConfig = {networkConfig}");
}
return ResultCode.Success;
}
else
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "CreateNetworkImpl: Invalid state!");
return ResultCode.InvalidState;
}
}
@@ -693,7 +660,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{
if (_nifmResultCode != ResultCode.Success)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"DestroyNetworkImpl: _nifmResultCode = {_nifmResultCode}");
return _nifmResultCode;
}
@@ -710,11 +676,9 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
CloseAccessPoint();
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "DestroyNetworkImpl: Invalid state!");
return ResultCode.InvalidState;
}
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "DestroyNetworkImpl: Invalid argument!");
return ResultCode.InvalidArgument;
}
@@ -731,17 +695,14 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{
if (_nifmResultCode != ResultCode.Success)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"RejectImpl: _nifmResultCode = {_nifmResultCode}");
return _nifmResultCode;
}
if (_state != NetworkState.AccessPointCreated)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "RejectImpl: Invalid state!");
return ResultCode.InvalidState; // Must be network host to reject nodes.
}
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"RejectImpl: disconnectReason = {disconnectReason} | nodeId = {nodeId}");
return NetworkClient.Reject(disconnectReason, nodeId);
}
@@ -753,13 +714,11 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
if (_nifmResultCode != ResultCode.Success)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"SetAdvertiseData: _nifmResultCode = {_nifmResultCode}");
return _nifmResultCode;
}
if (bufferSize is 0 or > LdnConst.AdvertiseDataSizeMax)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "SetAdvertiseData: Invalid argument!");
return ResultCode.InvalidArgument;
}
@@ -768,12 +727,11 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
byte[] advertiseData = new byte[bufferSize];
context.Memory.Read(bufferPosition, advertiseData);
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"SetAdvertiseData: advertiseData = {advertiseData}");
return _accessPoint.SetAdvertiseData(advertiseData);
}
else
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "SetAdvertiseData: Invalid state!");
return ResultCode.InvalidState;
}
}
@@ -786,24 +744,20 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
if (_nifmResultCode != ResultCode.Success)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"SetStationAcceptPolicy: _nifmResultCode = {_nifmResultCode}");
return _nifmResultCode;
}
if (acceptPolicy > AcceptPolicy.WhiteList)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "SetStationAcceptPolicy: Invalid argument!");
return ResultCode.InvalidArgument;
}
if (_state is NetworkState.AccessPoint or NetworkState.AccessPointCreated)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"SetStationAcceptPolicy: acceptPolicy = {acceptPolicy}");
return _accessPoint.SetStationAcceptPolicy(acceptPolicy);
}
else
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "SetStationAcceptPolicy: Invalid state!");
return ResultCode.InvalidState;
}
}
@@ -814,7 +768,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{
if (_nifmResultCode != ResultCode.Success)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"AddAcceptFilterEntry: _nifmResultCode = {_nifmResultCode}");
return _nifmResultCode;
}
@@ -829,7 +782,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{
if (_nifmResultCode != ResultCode.Success)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ClearAcceptFilter: _nifmResultCode = {_nifmResultCode}");
return _nifmResultCode;
}
@@ -844,13 +796,11 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{
if (_nifmResultCode != ResultCode.Success)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"OpenStation: _nifmResultCode = {_nifmResultCode}");
return _nifmResultCode;
}
if (_state != NetworkState.Initialized)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "OpenStation: Invalid state!");
return ResultCode.InvalidState;
}
@@ -863,8 +813,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
// NOTE: Calls nifm service and returns related result codes.
// Since we use our own implementation we can return ResultCode.Success.
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"OpenStation: _station = {_station}");
return ResultCode.Success;
}
@@ -875,7 +823,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{
if (_nifmResultCode != ResultCode.Success)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"CloseStation: _nifmResultCode = {_nifmResultCode}");
return _nifmResultCode;
}
@@ -885,13 +832,11 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
}
else
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "CloseStation: Invalid state!");
return ResultCode.InvalidState;
}
SetState(NetworkState.Initialized);
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "CloseStation: Closed.");
return ResultCode.Success;
}
@@ -956,13 +901,11 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
bool isLocalCommunicationIdValid = CheckLocalCommunicationIdPermission(context, (ulong)networkInfo.NetworkId.IntentId.LocalCommunicationId);
if (!isLocalCommunicationIdValid && NetworkClient.NeedsRealId)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "ConnectImpl: Invalid object!");
return ResultCode.InvalidObject;
}
if (_nifmResultCode != ResultCode.Success)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ConnectImpl: _nifmResultCode = {_nifmResultCode}");
return _nifmResultCode;
}
@@ -982,7 +925,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{
if (_state != NetworkState.Station)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "ConnectImpl: Invalid state!");
resultCode = ResultCode.InvalidState;
}
else
@@ -990,16 +932,10 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
if (isPrivate)
{
resultCode = _station.ConnectPrivate(securityConfig, securityParameter, userConfig, localCommunicationVersion, optionUnknown, networkConfig);
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ConnectImpl: Private connection established! " +
$"| securityConfig = {securityConfig} | securityParameter = {securityParameter} | userConfig = {userConfig} " +
$"| localCommunicationVersion = {localCommunicationVersion} | optionUnknown = {optionUnknown} | networkConfig = {networkConfig}");
}
else
{
resultCode = _station.Connect(securityConfig, userConfig, localCommunicationVersion, optionUnknown, networkInfo);
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ConnectImpl: Connection established! " +
$"| securityConfig = {securityConfig} | userConfig = {userConfig} " +
$"| localCommunicationVersion = {localCommunicationVersion} | optionUnknown = {optionUnknown} | networkConfig = {networkConfig}");
}
}
}
@@ -1007,8 +943,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
}
}
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"ConnectImpl: resultCode = {resultCode}");
return resultCode;
}
@@ -1023,7 +957,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{
if (_nifmResultCode != ResultCode.Success)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"DisconnectImpl: _nifmResultCode = {_nifmResultCode}");
return _nifmResultCode;
}
@@ -1037,17 +970,14 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
_disconnectReason = disconnectReason;
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"DisconnectImpl: _disconnectReason = {_disconnectReason}");
return ResultCode.Success;
}
CloseStation();
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "DisconnectImpl: Invalid state!");
return ResultCode.InvalidState;
}
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "DisconnectImpl: Invalid argument!");
return ResultCode.InvalidArgument;
}
@@ -1064,7 +994,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{
if (_nifmResultCode != ResultCode.Success)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"Finalize: _disconnectReason = {_disconnectReason}");
return _nifmResultCode;
}
@@ -1081,13 +1010,11 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
_stateChangeEventHandle = 0;
}
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"Finalize: resultCode = {resultCode}");
return resultCode;
}
private ResultCode FinalizeImpl(bool isCausedBySystem)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "FinalizeImpl");
DisconnectReason disconnectReason;
switch (_state)
@@ -1211,6 +1138,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
NetworkClient.SetGameVersion(context.Device.Processes.ActiveApplication.ApplicationControlProperties.DisplayVersion);
resultCode = ResultCode.Success;
_nifmResultCode = resultCode;
SetState(NetworkState.Initialized);
@@ -1224,7 +1152,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
}
}
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"InitializeImpl: resultCode = {resultCode}");
return resultCode;
}

View File

@@ -132,7 +132,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu
protected override void OnConnected()
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"LDN TCP client connected a new session with Id {Id}");
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"LDN TCP client connected a new session with Id {Id}");
UpdatePassphraseIfNeeded();
@@ -141,7 +141,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu
protected override void OnDisconnected()
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"LDN TCP client disconnected a session with Id {Id}");
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"LDN TCP client disconnected a session with Id {Id}");
_passphrase = null;
@@ -174,7 +174,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu
protected override void OnError(SocketError error)
{
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, $"LDN TCP client caught an error with code {error}");
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"LDN TCP client caught an error with code {error}");
_error.Set();
}
@@ -428,7 +428,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu
}
else
{
Logger.NetLog?.Print(LogClass.ServiceLdn, $"Created a wireless P2P network on port {request.ExternalProxyPort}.");
Logger.Info?.Print(LogClass.ServiceLdn, $"Created a wireless P2P network on port {request.ExternalProxyPort}.");
_hostedProxy.Start();
(_, UnicastIPAddressInformation unicastAddress) = NetworkHelpers.GetLocalInterface();

View File

@@ -1,4 +1,3 @@
using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Services.Ldn.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
@@ -37,12 +36,10 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
if (Connected)
{
_parent.SetState(NetworkState.StationConnected);
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,$"NetworkChanged: {NetworkInfo}");
}
else
{
_parent.SetDisconnectReason(e.DisconnectReasonOrDefault(DisconnectReason.DestroyedByUser));
Logger.NetLog?.PrintMsg(LogClass.ServiceLdn,"NetworkChanged: Disconnected (DestroyedByUser)");
}
}
else

View File

@@ -81,10 +81,8 @@ namespace Ryujinx.HLE.HOS.Services.Mii
return ResultCode.Success;
}
public ResultCode UpdateLatest<T>(DatabaseSessionMetadata metadata, T oldMiiData, SourceFlag flag, out T newMiiData) where T : unmanaged, IStoredData<T>
public ResultCode UpdateLatest<T>(DatabaseSessionMetadata metadata, IStoredData<T> oldMiiData, SourceFlag flag, IStoredData<T> newMiiData) where T : unmanaged
{
newMiiData = default;
if (!flag.HasFlag(SourceFlag.Database))
{
return ResultCode.NotFound;
@@ -108,7 +106,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii
newMiiData.SetFromStoreData(storeData);
if (oldMiiData.Equals(newMiiData))
if (oldMiiData == newMiiData)
{
return ResultCode.NotUpdated;
}
@@ -288,18 +286,6 @@ namespace Ryujinx.HLE.HOS.Services.Mii
return result;
}
public ResultCode Append(DatabaseSessionMetadata metadata, CharInfo charInfo)
{
ResultCode result = _miiDatabase.Append(metadata, _utilityImpl, charInfo);
if (result == ResultCode.Success)
{
result = _miiDatabase.SaveDatabase();
}
return result;
}
public ResultCode ConvertCharInfoToCoreData(CharInfo charInfo, out CoreData coreData)
{
coreData = new CoreData();

View File

@@ -449,32 +449,6 @@ namespace Ryujinx.HLE.HOS.Services.Mii
return ResultCode.Success;
}
public ResultCode Append(DatabaseSessionMetadata metadata, UtilityImpl utilityImpl, CharInfo charInfo)
{
if (!charInfo.IsValid())
{
return ResultCode.InvalidCharInfo;
}
if (charInfo.Type == 1)
{
return ResultCode.InvalidOperationOnSpecialMii;
}
CoreData coreData = new();
coreData.SetFromCharInfo(charInfo);
StoreData storeData;
do
{
storeData = StoreData.BuildFromCoreData(utilityImpl, coreData);
}
while (_database.GetIndexByCreatorId(out _, storeData.CreateId));
return AddOrReplace(metadata, storeData);
}
public ResultCode Delete(DatabaseSessionMetadata metadata, CreateId createId)
{
if (!_database.GetIndexByCreatorId(out int index, createId))

View File

@@ -54,7 +54,9 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService
protected override ResultCode UpdateLatest(CharInfo oldCharInfo, SourceFlag flag, out CharInfo newCharInfo)
{
return _database.UpdateLatest(_metadata, oldCharInfo, flag, out newCharInfo);
newCharInfo = default;
return _database.UpdateLatest(_metadata, oldCharInfo, flag, newCharInfo);
}
protected override ResultCode BuildRandom(Age age, Gender gender, Race race, out CharInfo charInfo)
@@ -111,14 +113,14 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService
protected override ResultCode UpdateLatest1(StoreData oldStoreData, SourceFlag flag, out StoreData newStoreData)
{
newStoreData = default;
if (!_isSystem)
{
newStoreData = default;
return ResultCode.PermissionDenied;
}
return _database.UpdateLatest(_metadata, oldStoreData, flag, out newStoreData);
return _database.UpdateLatest(_metadata, oldStoreData, flag, newStoreData);
}
protected override ResultCode FindIndex(CreateId createId, bool isSpecial, out int index)
@@ -260,10 +262,5 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService
{
return _database.ConvertCharInfoToCoreData(charInfo, out coreData);
}
protected override ResultCode Append(CharInfo charInfo)
{
return _database.Append(_metadata, charInfo);
}
}
}

View File

@@ -340,15 +340,6 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService
return result;
}
[CommandCmif(26)] // 10.2.0+
// Append(nn::mii::CharInfo char_info)
public ResultCode Append(ServiceCtx context)
{
CharInfo charInfo = context.RequestData.ReadStruct<CharInfo>();
return Append(charInfo);
}
private Span<byte> CreateByteSpanFromBuffer(ServiceCtx context, IpcBuffDesc ipcBuff, bool isOutput)
{
byte[] rawData;
@@ -430,7 +421,5 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService
protected abstract ResultCode ConvertCoreDataToCharInfo(CoreData coreData, out CharInfo charInfo);
protected abstract ResultCode ConvertCharInfoToCoreData(CharInfo charInfo, out CoreData coreData);
protected abstract ResultCode Append(CharInfo charInfo);
}
}

View File

@@ -1,24 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Notification
{
[Service("notif:s")] // 9.0.0+
class INotificationServices : IpcService
{
public INotificationServices(ServiceCtx context) { }
[CommandCmif(1000)] // 9.0.0+
// GetNotificationCount() -> nn::notification::server::INotificationSystemEventAccessor
public ResultCode GetNotificationCount(ServiceCtx context)
{
MakeObject(context, new INotificationSystemEventAccessor(context));
return ResultCode.Success;
}
[CommandCmif(1040)] // 9.0.0+
// GetNotificationSendingNotifier() -> nn::notification::server::INotificationSystemEventAccessor
public ResultCode GetNotificationSendingNotifier(ServiceCtx context)
{
MakeObject(context, new INotificationSystemEventAccessor(context));
return ResultCode.Success;
}
}
}

View File

@@ -1,33 +1,8 @@
using Ryujinx.Common.Logging;
namespace Ryujinx.HLE.HOS.Services.Notification
{
[Service("notif:a")] // 9.0.0+
class INotificationServicesForApplication : IpcService
{
public INotificationServicesForApplication(ServiceCtx context) { }
// Leaving this here since I can never find it: https://switchbrew.org/wiki/Glue_services
[CommandCmif(520)] // 9.0.0+
// ListAlarmSettings(nn::arp::ApplicationCertificate) -> s32 AlarmSettingsCount
public ResultCode ListAlarmSettings(ServiceCtx context)
{
// TO-DO: Currently just returns 0. Should read in an ApplicationCertificate.
int alarmSettingsCount = 0;
context.ResponseData.Write(alarmSettingsCount);
return ResultCode.Success;
}
[CommandCmif(1000)] // 9.0.0+
// Initialize(PID-descriptor, u64 pid_reserved)
public ResultCode Intialize(ServiceCtx context)
{
ulong pid = context.Request.HandleDesc.PId;
context.RequestData.ReadUInt64(); // pid placeholder, zero
Logger.Stub?.PrintStub(LogClass.ServiceNotification, new { pid });
return ResultCode.Success;
}
}
}

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Notification
{
[Service("notif:s")] // 9.0.0+
class INotificationServicesForSystem : IpcService
{
public INotificationServicesForSystem(ServiceCtx context) { }
}
}

View File

@@ -1,32 +0,0 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common;
using System;
namespace Ryujinx.HLE.HOS.Services.Notification
{
class INotificationSystemEventAccessor : IpcService
{
private readonly KEvent _getNotificationSendingNotifierEvent;
private int _getNotificationSendingNotifierEventHandle;
public INotificationSystemEventAccessor(ServiceCtx context) { }
[CommandCmif(0)] // 9.0.0+
// GetNotificationSendingNotifier() -> nn::notification::server::INotificationSystemEventAccessor
public ResultCode GetSystemEvent(ServiceCtx context)
{
if (_getNotificationSendingNotifierEventHandle == 0)
{
if (context.Process.HandleTable.GenerateHandle(_getNotificationSendingNotifierEvent.ReadableEvent, out _getNotificationSendingNotifierEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_getNotificationSendingNotifierEventHandle);
return ResultCode.Success;
}
}
}

View File

@@ -14,7 +14,6 @@ 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;
@@ -28,64 +27,17 @@ namespace Ryujinx.HLE.Loaders.Processes
private ulong _latestPid;
private readonly object _pidLock = new();
#nullable enable
public ProcessResult? ActiveApplication
public ProcessResult ActiveApplication
{
get
{
lock (_pidLock)
{
// Check if _latestPid is still valid
if (_latestPid == 0)
{
return null;
}
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?");
// Verify process still exists in kernel (authoritative source)
if (!_device.System.KernelContext.Processes.TryGetValue(_latestPid, out HOS.Kernel.Process.KProcess? kernelProcess))
{
// Process no longer exists in kernel, clear stale state
Logger.Warning?.Print(LogClass.Loader,
$"ActiveApplication PID {_latestPid} no longer exists in kernel, clearing stale state");
_processesByPid.TryRemove(_latestPid, out _);
_latestPid = 0;
TitleIDs.CurrentApplication.Value = null;
return null;
}
// Verify process still exists in ProcessLoader's dictionary
if (_processesByPid.TryGetValue(_latestPid, out ProcessResult? processResult))
{
// Additional check: verify process state
if (kernelProcess.State == HOS.Kernel.Process.ProcessState.Exited ||
kernelProcess.State == HOS.Kernel.Process.ProcessState.Exiting)
{
Logger.Warning?.Print(LogClass.Loader,
$"ActiveApplication PID {_latestPid} is in state {kernelProcess.State}, clearing");
_processesByPid.TryRemove(_latestPid, out _);
_latestPid = 0;
TitleIDs.CurrentApplication.Value = null;
return null;
}
return processResult;
}
// Fallback: clear stale PID if not in our dictionary
Logger.Warning?.Print(LogClass.Loader,
$"ActiveApplication PID {_latestPid} not in ProcessLoader dictionary, clearing");
_latestPid = 0;
return null;
}
return value;
}
}
#nullable disable
public ProcessLoader(Switch device)
{
@@ -192,7 +144,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))
@@ -325,39 +277,5 @@ namespace Ryujinx.HLE.Loaders.Processes
return false;
}
/// <summary>
/// Clears a specific process from the ProcessLoader's tracking.
/// This should be called when a process exits or is terminated.
/// </summary>
/// <param name="pid">The process ID to clear</param>
public void ClearProcess(ulong pid)
{
lock (_pidLock)
{
if (_processesByPid.TryRemove(pid, out _))
{
if (_latestPid == pid)
{
_latestPid = 0;
TitleIDs.CurrentApplication.Value = null;
}
}
}
}
/// <summary>
/// Clears all processes from the ProcessLoader's tracking.
/// This should be called during system shutdown.
/// </summary>
public void ClearAllProcesses()
{
lock (_pidLock)
{
_processesByPid.Clear();
_latestPid = 0;
TitleIDs.CurrentApplication.Value = null;
}
}
}
}

View File

@@ -4,6 +4,7 @@ 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
@@ -51,7 +52,6 @@ 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,15 +71,8 @@ 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;
}

View File

@@ -27,9 +27,7 @@
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
<PackageReference Include="MsgPack.Cli" />
<PackageReference Include="SkiaSharp" />
<PackageReference Include="SkiaSharp.NativeAssets.Win32" />
<PackageReference Include="SkiaSharp.NativeAssets.macOS" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux" />
<PackageReference Include="NetCoreServer" />
<PackageReference Include="Open.NAT.Core" />
</ItemGroup>

View File

@@ -183,7 +183,6 @@ namespace Ryujinx.HLE
{
if (disposing)
{
Processes.ClearAllProcesses();
System.Dispose();
AudioDeviceDriver.Dispose();
FileSystem.Dispose();

View File

@@ -1,203 +0,0 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Hid;
using SDL;
using static SDL.SDL3;
using System;
namespace Ryujinx.Input.SDL3
{
/// <summary>
/// Manages a HID handle of a gamepad to encode and write HD rumble commands for Nin controllers.
/// </summary>
public unsafe class NpadHdRumble : IDisposable
{
private readonly SDL_hid_device* _hidHandle;
private int _globalCount;
private ulong _lastWriteTicks;
private NpadHdRumble(SDL_hid_device* hidHandle)
{
_hidHandle = hidHandle;
}
public static NpadHdRumble Create(SDL_Gamepad* gamepadHandle)
{
ushort vendor = SDL_GetGamepadVendor(gamepadHandle);
if (vendor != 0x057e)
{
return null;
}
ushort product = SDL_GetGamepadProduct(gamepadHandle);
if (!Enum.IsDefined(typeof(HDRumbleSupported), product))
{
return null;
}
return new NpadHdRumble(SDL_hid_open(vendor, product, 0));
}
// Some of the code was translated from https://github.com/MIZUSHIKI/JoyShockLibrary-plus-HDRumble
private bool WriteHdRumble(
int encLeftLowFreq, int encLeftLowAmp,
int encLeftHighFreq, int encLeftHighAmp,
int encRightLowFreq, int encRightLowAmp,
int encRightHighFreq, int encRightHighAmp)
{
byte[] buf = new byte[10];
buf[0] = 0x10;
buf[1] = (byte)((++_globalCount) & 0xF);
buf[2] = (byte)(encLeftHighFreq & 0xFF);
buf[3] = (byte)(encLeftHighAmp + ((encLeftHighFreq >> 8) & 0xFF));
buf[4] = (byte)(encLeftLowFreq + ((encLeftLowAmp >> 8) & 0xFF));
buf[5] = (byte)(encLeftLowAmp & 0xFF);
buf[6] = (byte)(encRightHighFreq & 0xFF);
buf[7] = (byte)(encRightHighAmp + ((encRightHighFreq >> 8) & 0xFF));
buf[8] = (byte)(encRightLowFreq + ((encRightLowAmp >> 8) & 0xFF));
buf[9] = (byte)(encRightLowAmp & 0xFF);
if (_globalCount > 0xF)
{
_globalCount = 0x0;
}
fixed (byte* ptr = buf)
{
if (SendHDRumble(ptr, (nuint)buf.Length) >= 0)
{
return true;
}
if (!String.IsNullOrEmpty(SDL_GetError()))
{
Logger.Error?.PrintMsg(LogClass.Hid, SDL_GetError());
SDL_ClearError();
}
return false;
}
}
private static int EncodeLowFreq(float lowFreq)
{
float lf = Math.Clamp(lowFreq, 40.875885f, 626.286133f);
return (int) Math.Round(32 * Math.Log2(lf * 0.1f) - 0x40);
}
private static int EncodeHighFreq(float highFreq)
{
float hf = Math.Clamp(highFreq, 81.75177f, 1252.572266f);
return (int) Math.Round((32 * Math.Log2(hf * 0.1f) - 0x60) * 4);
}
private static int EncodeLowAmp(float rawAmp)
{
double encodedAmp = 0;
if (rawAmp is > 0 and < 0.012f)
{
encodedAmp = 1;
}
else if (rawAmp is >= 0.012f and < 0.112f)
{
encodedAmp = 4 * Math.Log2(rawAmp * 110f);
}
else if (rawAmp is >= 0.112f and < 0.225f)
{
encodedAmp = 16 * Math.Log2(rawAmp * 17f);
}
else if (rawAmp is >= 0.225f and <= 1f)
{
encodedAmp = 32 * Math.Log2(rawAmp * 8.7f);
}
return (int)Math.Floor(encodedAmp / 2.0) + 64;
}
private static int EncodeHighAmp(float rawAmp)
{
double encodedAmp = 0;
if (rawAmp is > 0 and < 0.012f)
{
encodedAmp = 1;
}
else if (rawAmp is >= 0.012f and < 0.112f)
{
encodedAmp = 4 * Math.Log2(rawAmp * 110f);
}
else if (rawAmp is >= 0.112f and < 0.225f)
{
encodedAmp = 16 * Math.Log2(rawAmp * 17f);
}
else if (rawAmp is >= 0.225f and <= 1f)
{
encodedAmp = 32 * Math.Log2(rawAmp * 8.7f);
}
return (int) Math.Round(encodedAmp * 2);
}
public bool HdRumble(VibrationValue left, VibrationValue right)
{
return WriteHdRumble(EncodeLowFreq(left.FrequencyLow),
EncodeLowAmp(left.AmplitudeLow),
EncodeHighFreq(left.FrequencyHigh),
EncodeHighAmp(left.AmplitudeHigh),
EncodeLowFreq(right.FrequencyLow),
EncodeLowAmp(right.AmplitudeLow),
EncodeHighFreq(right.FrequencyHigh),
EncodeHighAmp(right.AmplitudeHigh));
}
private int SendHDRumble(byte* data, nuint length)
{
int result = 0;
ulong currentTicks = SDL_GetTicks();
// Ditch rumble if we haven't hit the poll-rate yet.
// TODO: figure out a better way to do this
// While the polling check makes the rumble accurate, it also causes it to miss signals.
if ((currentTicks - _lastWriteTicks) < 8) // https://docs.handheldlegend.com/s/progcc-3/doc/lag-comparison-aAR1mV3JLX
{
return result;
}
SDL_LockJoysticks();
{
// Fun fact: Mario Kart 8 Deluxe sends rumble packets
// where the amplitude is zero, but the frequency isn't.
result = SDL_hid_write(_hidHandle, data, length);
if (result >= 0)
{
_lastWriteTicks = currentTicks;
}
}
SDL_UnlockJoysticks();
return result;
}
public void Dispose()
{
SDL_hid_close(_hidHandle);
}
}
public enum HDRumbleSupported : ushort
{
JoyConLeft = 0x2006,
JoyConRight = 0x2007,
JoyconPair = 0x2008,
ProController = 0x2009,
JoyconGrip = 0x200e,
Joycon2Right = 0x2066,
Joycon2Left = 0x2067,
Joycon2Pair = 0x2068,
Switch2ProController = 0x2069,
GamecubeController = 0x2073
}
}

View File

@@ -2,7 +2,6 @@ using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.Services.Hid;
using System;
using System.Collections.Generic;
using System.Numerics;
@@ -77,14 +76,11 @@ namespace Ryujinx.Input.SDL3
private SDL_Gamepad* _gamepadHandle;
private NpadHdRumble _hdRumble;
private float _triggerThreshold;
public SDL3Gamepad(SDL_Gamepad* gamepadHandle, string driverId)
{
_gamepadHandle = gamepadHandle;
_hdRumble = NpadHdRumble.Create(gamepadHandle);
_buttonsUserMapping = new List<ButtonMappingEntry>(20);
Name = SDL_GetGamepadName(_gamepadHandle);
@@ -155,9 +151,7 @@ namespace Ryujinx.Input.SDL3
result |= GamepadFeaturesFlag.Led;
}
SDL_UnlockProperties(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).
SDL_DestroyProperties(propID);
return result;
}
@@ -169,10 +163,6 @@ namespace Ryujinx.Input.SDL3
protected virtual void Dispose(bool disposing)
{
if (disposing && _hdRumble != null)
{
_hdRumble.Dispose();
}
if (disposing && _gamepadHandle != null)
{
SDL_CloseGamepad(_gamepadHandle);
@@ -192,17 +182,10 @@ namespace Ryujinx.Input.SDL3
_triggerThreshold = triggerThreshold;
}
public bool HDRumble(VibrationValue left, VibrationValue right)
{
return _hdRumble?.HdRumble(left, right) ?? false;
}
public bool Rumble(float lowFrequency, float highFrequency, uint durationMs)
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
{
if ((Features & GamepadFeaturesFlag.Rumble) == 0)
{
return false;
}
return;
ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue);
ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue);
@@ -221,15 +204,6 @@ namespace Ryujinx.Input.SDL3
if (!SDL_RumbleGamepad(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs))
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
}
if (!String.IsNullOrEmpty(SDL_GetError()))
{
Logger.Error?.PrintMsg(LogClass.Hid, SDL_GetError());
SDL_ClearError();
return false;
}
return true;
}
public Vector3 GetMotionData(MotionInputId inputId)

View File

@@ -331,18 +331,28 @@ namespace Ryujinx.Input.SDL3
public IEnumerable<IGamepad> GetGamepads()
{
string[] ids;
lock (_lock)
lock (_gamepadsIds)
{
ids = _gamepadsIds.Values
.Concat(_joyConsIds.Values)
.Concat(_linkedJoyConsIds.Values)
.ToArray();
foreach (var gamepad in _gamepadsIds)
{
yield return GetGamepad(gamepad.Value);
}
}
foreach (string id in ids)
lock (_joyConsIds)
{
yield return GetGamepad(id);
foreach (var gamepad in _joyConsIds)
{
yield return GetGamepad(gamepad.Value);
}
}
lock (_linkedJoyConsIds)
{
foreach (var gamepad in _linkedJoyConsIds)
{
yield return GetGamepad(gamepad.Value);
}
}
}
}

View File

@@ -1,7 +1,6 @@
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Hid;
using System;
using System.Collections.Generic;
using System.Numerics;
@@ -62,8 +61,6 @@ namespace Ryujinx.Input.SDL3
public GamepadFeaturesFlag Features { get; }
private SDL_Gamepad* _gamepadHandle;
private NpadHdRumble _hdRumble;
private enum JoyConType
{
@@ -79,7 +76,6 @@ namespace Ryujinx.Input.SDL3
public SDL3JoyCon(SDL_Gamepad* gamepadHandle, string driverId)
{
_gamepadHandle = gamepadHandle;
_hdRumble = NpadHdRumble.Create(gamepadHandle);
_buttonsUserMapping = new List<ButtonMappingEntry>(10);
Name = SDL_GetGamepadName(_gamepadHandle);
@@ -143,10 +139,6 @@ namespace Ryujinx.Input.SDL3
protected virtual void Dispose(bool disposing)
{
if (disposing && _hdRumble != null)
{
_hdRumble.Dispose();
}
if (disposing && _gamepadHandle != null)
{
SDL_CloseGamepad(_gamepadHandle);
@@ -162,20 +154,13 @@ namespace Ryujinx.Input.SDL3
public void SetTriggerThreshold(float triggerThreshold)
{
// No operations
}
public bool HDRumble(VibrationValue left, VibrationValue right)
{
return _hdRumble?.HdRumble(left, right) ?? false;
}
public bool Rumble(float lowFrequency, float highFrequency, uint durationMs)
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
{
if ((Features & GamepadFeaturesFlag.Rumble) == 0)
{
return false;
}
return;
ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue);
ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue);
@@ -194,15 +179,6 @@ namespace Ryujinx.Input.SDL3
if (!SDL_RumbleGamepad(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs))
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
}
if (!String.IsNullOrEmpty(SDL_GetError()))
{
Logger.Error?.PrintMsg(LogClass.Hid, SDL_GetError());
SDL_ClearError();
return false;
}
return true;
}
public Vector3 GetMotionData(MotionInputId inputId)

View File

@@ -1,7 +1,4 @@
using Gommon;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Hid;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
@@ -64,14 +61,7 @@ namespace Ryujinx.Input.SDL3
return left.IsPressed(inputId) || right.IsPressed(inputId);
}
public bool HDRumble(VibrationValue left, VibrationValue right)
{
// return _hdRumble?.HdRumble(left, right) ?? false;
// TODO: Track rumble and motion on both controllers
return false;
}
public bool Rumble(float lowFrequency, float highFrequency, uint durationMs)
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
{
if (lowFrequency != 0)
{
@@ -88,15 +78,6 @@ namespace Ryujinx.Input.SDL3
left.Rumble(0, 0, durationMs);
right.Rumble(0, 0, durationMs);
}
if (!SDL_GetError().IsNullOrEmpty())
{
Logger.Error?.PrintMsg(LogClass.Hid, SDL_GetError());
SDL_ClearError();
return false;
}
return true;
}
public void SetConfiguration(InputConfig configuration)

View File

@@ -1,7 +1,6 @@
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Hid;
using System;
using System.Collections.Generic;
using System.Numerics;
@@ -9,25 +8,15 @@ using System.Runtime.CompilerServices;
using System.Threading;
using SDL;
using static SDL.SDL3;
using ConfigKey = Ryujinx.Common.Configuration.Hid.Key;
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
namespace Ryujinx.Input.SDL3
{
class SDL3Keyboard : IKeyboard
{
private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, Key From)
{
public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not Key.Unbound;
}
private readonly Lock _userMappingLock = new();
#pragma warning disable IDE0052 // Remove unread private member
private readonly SDL3KeyboardDriver _driver;
#pragma warning restore IDE0052
private StandardKeyboardInputConfig _configuration;
private readonly List<ButtonMappingEntry> _buttonsUserMapping;
private readonly List<KeyboardInputMappingHelper.KeyboardButtonMapping> _buttonsUserMapping;
private static readonly SDL_Keycode[] _keysDriverMapping =
@@ -172,9 +161,8 @@ namespace Ryujinx.Input.SDL3
SDL_Keycode.SDLK_0
];
public SDL3Keyboard(SDL3KeyboardDriver driver, string id, string name)
public SDL3Keyboard(string id, string name)
{
_driver = driver;
Id = id;
Name = name;
_buttonsUserMapping = [];
@@ -196,9 +184,9 @@ namespace Ryujinx.Input.SDL3
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe static int ToSDL3Scancode(Key key)
private unsafe static int ToSDL3Scancode(ConfigPhysicalKey key)
{
if (key is >= Key.Unknown and <= Key.Menu)
if (key is >= ConfigPhysicalKey.Unknown and <= ConfigPhysicalKey.Menu)
{
return -1;
}
@@ -206,18 +194,18 @@ namespace Ryujinx.Input.SDL3
return (int)SDL_GetScancodeFromKey(_keysDriverMapping[(int)key], null);
}
private static SDL_Keymod GetKeyboardModifierMask(Key key)
private static SDL_Keymod GetKeyboardModifierMask(ConfigPhysicalKey key)
{
return key switch
{
Key.ShiftLeft => SDL_Keymod.SDL_KMOD_LSHIFT,
Key.ShiftRight => SDL_Keymod.SDL_KMOD_RSHIFT,
Key.ControlLeft => SDL_Keymod.SDL_KMOD_LCTRL,
Key.ControlRight => SDL_Keymod.SDL_KMOD_RCTRL,
Key.AltLeft => SDL_Keymod.SDL_KMOD_LALT,
Key.AltRight => SDL_Keymod.SDL_KMOD_RALT,
Key.WinLeft => SDL_Keymod.SDL_KMOD_LGUI,
Key.WinRight => SDL_Keymod.SDL_KMOD_RGUI,
ConfigPhysicalKey.ShiftLeft => SDL_Keymod.SDL_KMOD_LSHIFT,
ConfigPhysicalKey.ShiftRight => SDL_Keymod.SDL_KMOD_RSHIFT,
ConfigPhysicalKey.ControlLeft => SDL_Keymod.SDL_KMOD_LCTRL,
ConfigPhysicalKey.ControlRight => SDL_Keymod.SDL_KMOD_RCTRL,
ConfigPhysicalKey.AltLeft => SDL_Keymod.SDL_KMOD_LALT,
ConfigPhysicalKey.AltRight => SDL_Keymod.SDL_KMOD_RALT,
ConfigPhysicalKey.WinLeft => SDL_Keymod.SDL_KMOD_LGUI,
ConfigPhysicalKey.WinRight => SDL_Keymod.SDL_KMOD_RGUI,
// NOTE: Menu key isn't supported by SDL3.
_ => SDL_Keymod.SDL_KMOD_NONE
};
@@ -233,9 +221,9 @@ namespace Ryujinx.Input.SDL3
rawKeyboardState = SDL_GetKeyboardState(null);
}
bool[] keysState = new bool[(int)Key.Count];
bool[] keysState = new bool[(int)ConfigPhysicalKey.Count];
for (Key key = 0; key < Key.Count; key++)
for (ConfigPhysicalKey key = 0; key < ConfigPhysicalKey.Count; key++)
{
int index = ToSDL3Scancode(key);
if (index == -1)
@@ -265,36 +253,6 @@ namespace Ryujinx.Input.SDL3
return value * ConvertRate;
}
private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick<ConfigKey> stickConfig)
{
short stickX = 0;
short stickY = 0;
if (snapshot.IsPressed((Key)stickConfig.StickUp))
{
stickY += 1;
}
if (snapshot.IsPressed((Key)stickConfig.StickDown))
{
stickY -= 1;
}
if (snapshot.IsPressed((Key)stickConfig.StickRight))
{
stickX += 1;
}
if (snapshot.IsPressed((Key)stickConfig.StickLeft))
{
stickX -= 1;
}
Vector2 stick = Vector2.Normalize(new Vector2(stickX, stickY));
return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
}
public GamepadStateSnapshot GetMappedStateSnapshot()
{
KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot();
@@ -307,9 +265,9 @@ namespace Ryujinx.Input.SDL3
return result;
}
foreach (ButtonMappingEntry entry in _buttonsUserMapping)
foreach (KeyboardInputMappingHelper.KeyboardButtonMapping entry in _buttonsUserMapping)
{
if (entry.From == Key.Unknown || entry.From == Key.Unbound || entry.To == GamepadButtonInputId.Unbound)
if (!entry.IsValid)
{
continue;
}
@@ -321,8 +279,8 @@ namespace Ryujinx.Input.SDL3
}
}
(short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick);
(short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick);
(short leftStickX, short leftStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.LeftJoyconStick);
(short rightStickX, short rightStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.RightJoyconStick);
result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY));
@@ -358,38 +316,15 @@ namespace Ryujinx.Input.SDL3
{
_configuration = (StandardKeyboardInputConfig)configuration;
// First clear the buttons mapping
_buttonsUserMapping.Clear();
// Then configure left joycon
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl));
// Finally configure right joycon
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
_buttonsUserMapping.AddRange(KeyboardInputMappingHelper.BuildButtonMappings(_configuration));
}
}
public void SetLed(uint packedRgb)
{
Logger.Info?.Print(LogClass.UI, "SetLed called on an SDL3Keyboard");
Logger.Debug?.Print(LogClass.UI, "SetLed called on an SDL3Keyboard");
}
public void SetTriggerThreshold(float triggerThreshold)
@@ -397,14 +332,9 @@ namespace Ryujinx.Input.SDL3
// No operations
}
public bool HDRumble(VibrationValue left, VibrationValue right)
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
{
return false;
}
public bool Rumble(float lowFrequency, float highFrequency, uint durationMs)
{
return false;
// No operations
}
public Vector3 GetMotionData(MotionInputId inputId)

View File

@@ -1,6 +1,5 @@
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Hid;
using System;
using System.Drawing;
using System.Numerics;
@@ -68,12 +67,7 @@ namespace Ryujinx.Input.SDL3
throw new NotImplementedException();
}
public bool HDRumble(VibrationValue left, VibrationValue right)
{
throw new NotImplementedException();
}
public bool Rumble(float lowFrequency, float highFrequency, uint durationMs)
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
{
throw new NotImplementedException();
}

View File

@@ -50,7 +50,7 @@ namespace Ryujinx.Input.SDL3
return null;
}
return new SDL3Keyboard(this, _keyboardIdentifers[0], "All keyboards");
return new SDL3Keyboard(_keyboardIdentifers[0], "All keyboards");
}
public IEnumerable<IGamepad> GetGamepads()

View File

@@ -1,3 +1,5 @@
using Ryujinx.Common.Logging;
namespace Ryujinx.Input.Assigner
{
/// <summary>
@@ -8,22 +10,42 @@ namespace Ryujinx.Input.Assigner
private readonly IKeyboard _keyboard;
private KeyboardStateSnapshot _keyboardState;
private Button? _pressedButton;
public KeyboardKeyAssigner(IKeyboard keyboard)
{
_keyboard = keyboard;
}
public void Initialize() { }
public void Initialize()
{
_pressedButton = null;
}
public void ReadInput()
{
_keyboardState = _keyboard.GetKeyboardStateSnapshot();
if (_pressedButton is not null)
{
return;
}
Button? buttonFromState = GetPressedButtonFromState();
Button? buttonFromBufferedPress = buttonFromState is null ? GetPressedButtonFromBufferedPress() : null;
_pressedButton = buttonFromState ?? buttonFromBufferedPress;
if (_pressedButton is not null)
{
string source = buttonFromState is not null ? "state" : "buffered-press";
Logger.Debug?.Print(LogClass.UI, $"Keyboard assigner registered key={_pressedButton.Value.AsHidType<Key>()}, source={source}, cancelPressed={ShouldCancel()}");
}
}
public bool IsAnyButtonPressed()
{
return GetPressedButton() is not null;
return _pressedButton is not null;
}
public bool ShouldCancel()
@@ -33,18 +55,53 @@ namespace Ryujinx.Input.Assigner
public Button? GetPressedButton()
{
Button? keyPressed = null;
return !ShouldCancel() ? _pressedButton : null;
}
private Button? GetPressedButtonFromState()
{
Key aliasedKey = GetAliasedPressedKey();
if (aliasedKey != Key.Unknown)
{
return new Button(aliasedKey);
}
for (Key key = Key.Unknown; key < Key.Count; key++)
{
if (_keyboardState.IsPressed(key))
{
keyPressed = new(key);
break;
return new Button(key);
}
}
return !ShouldCancel() ? keyPressed : null;
return null;
}
private Button? GetPressedButtonFromBufferedPress()
{
return _keyboard.TryConsumePressedKey(out Key key) ? new Button(key) : null;
}
private Key GetAliasedPressedKey()
{
// On some layouts (for example AltGr on Windows), Right Alt is reported as Ctrl+Alt.
// Prefer AltRight in that case so the binding reflects the physical key used.
if (_keyboardState.IsPressed(Key.ControlLeft) && _keyboardState.IsPressed(Key.AltRight))
{
return Key.AltRight;
}
// On some Copilot keyboards, the key in the right-control position is reported as
// ShiftLeft+Win+F23. Prefer ControlRight so the binding reflects that physical key.
if (_keyboardState.IsPressed(Key.ShiftLeft) &&
_keyboardState.IsPressed(Key.F23) &&
(_keyboardState.IsPressed(Key.WinLeft) || _keyboardState.IsPressed(Key.WinRight)))
{
return Key.ControlRight;
}
return Key.Unknown;
}
}
}

View File

@@ -2,9 +2,11 @@ using Ryujinx.Common;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Hid;
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Numerics;
using System.Runtime.CompilerServices;
@@ -233,7 +235,9 @@ namespace Ryujinx.Input.HLE
_gamepad?.Dispose();
Id = config.Id;
_gamepad = GamepadDriver.GetGamepad(Id);
_gamepad = config is StandardKeyboardInputConfig && GamepadDriver is IKeyboardModeDriver keyboardModeDriver
? keyboardModeDriver.GetKeyboard(Id, KeyboardInputMode.Physical)
: GamepadDriver.GetGamepad(Id);
UpdateUserConfiguration(config);
@@ -554,37 +558,23 @@ namespace Ryujinx.Input.HLE
{
if (queue.TryDequeue(out (VibrationValue, VibrationValue) dualVibrationValue))
{
if (_config is not StandardControllerInputConfig controllerConfig ||
!controllerConfig.Rumble.EnableRumble)
if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Rumble.EnableRumble)
{
return;
VibrationValue leftVibrationValue = dualVibrationValue.Item1;
VibrationValue rightVibrationValue = dualVibrationValue.Item2;
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);
Logger.Debug?.Print(LogClass.Hid, $"Effect for {controllerConfig.PlayerIndex} " +
$"L.low.amp={leftVibrationValue.AmplitudeLow}, " +
$"L.high.amp={leftVibrationValue.AmplitudeHigh}, " +
$"R.low.amp={rightVibrationValue.AmplitudeLow}, " +
$"R.high.amp={rightVibrationValue.AmplitudeHigh} " +
$"--> ({low}, {high})");
}
VibrationValue leftVibrationValue = dualVibrationValue.Item1;
VibrationValue rightVibrationValue = dualVibrationValue.Item2;
leftVibrationValue.AmplitudeLow *= controllerConfig.Rumble.WeakRumble;
leftVibrationValue.AmplitudeHigh *= controllerConfig.Rumble.StrongRumble;
rightVibrationValue.AmplitudeLow *= controllerConfig.Rumble.WeakRumble;
rightVibrationValue.AmplitudeHigh *= controllerConfig.Rumble.StrongRumble;
if (!controllerConfig.Rumble.UseHDRumble || _gamepad?.HDRumble(leftVibrationValue, rightVibrationValue) == false)
{
float low = Math.Min(1f, (float)((rightVibrationValue.AmplitudeLow * 0.85 + rightVibrationValue.AmplitudeHigh * 0.15)));
float high = Math.Min(1f, (float)((leftVibrationValue.AmplitudeLow * 0.15 + leftVibrationValue.AmplitudeHigh * 0.85)));
_gamepad?.Rumble(low, high, 0xFFFFFFFF);
}
Logger.Debug?.Print(LogClass.Hid, $"Effect for {controllerConfig.PlayerIndex} " +
// Value=value/multiplier * multiplier (result)
$"L.low.amp={leftVibrationValue.AmplitudeLow / controllerConfig.Rumble.WeakRumble} * {controllerConfig.Rumble.WeakRumble} ({leftVibrationValue.AmplitudeLow}), " +
$"L.high.amp={leftVibrationValue.AmplitudeHigh / controllerConfig.Rumble.WeakRumble} * {controllerConfig.Rumble.WeakRumble} ({leftVibrationValue.AmplitudeHigh}), " +
$"L.low.freq={leftVibrationValue.FrequencyLow / controllerConfig.Rumble.WeakRumble} * {controllerConfig.Rumble.WeakRumble} ({leftVibrationValue.FrequencyLow}), " +
$"L.high.freq={leftVibrationValue.FrequencyHigh / controllerConfig.Rumble.WeakRumble} * {controllerConfig.Rumble.WeakRumble} ({leftVibrationValue.FrequencyHigh}), " +
$"R.low.amp={rightVibrationValue.AmplitudeLow / controllerConfig.Rumble.StrongRumble} * {controllerConfig.Rumble.StrongRumble} ({rightVibrationValue.AmplitudeLow}), " +
$"R.high.amp={rightVibrationValue.AmplitudeHigh / controllerConfig.Rumble.StrongRumble} * {controllerConfig.Rumble.StrongRumble} ({rightVibrationValue.AmplitudeHigh}), " +
$"R.low.freq={rightVibrationValue.FrequencyLow / controllerConfig.Rumble.StrongRumble} * {controllerConfig.Rumble.StrongRumble} ({rightVibrationValue.FrequencyLow}), " +
$"R.high.freq={rightVibrationValue.FrequencyHigh / controllerConfig.Rumble.StrongRumble} * {controllerConfig.Rumble.StrongRumble} ({rightVibrationValue.FrequencyHigh})");
}
}
}

View File

@@ -36,6 +36,7 @@ namespace Ryujinx.Input.HLE
private bool _isDisposed;
private List<InputConfig> _inputConfig;
private List<InputConfig> _requestedInputConfig;
private bool _enableKeyboard;
private bool _enableMouse;
private Switch _device;
@@ -52,6 +53,7 @@ namespace Ryujinx.Input.HLE
_gamepadDriver = gamepadDriver;
_mouseDriver = mouseDriver;
_inputConfig = [];
_requestedInputConfig = [];
_gamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
_gamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
@@ -89,14 +91,14 @@ namespace Ryujinx.Input.HLE
}
}
ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse);
ReloadConfiguration(_requestedInputConfig, _enableKeyboard, _enableMouse);
}
}
private void HandleOnGamepadConnected(string id)
{
// Force input reload
ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse);
ReloadConfiguration(_requestedInputConfig, _enableKeyboard, _enableMouse);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -127,11 +129,13 @@ namespace Ryujinx.Input.HLE
{
lock (_lock)
{
_requestedInputConfig = inputConfig?.ToList() ?? [];
NpadController[] oldControllers = _controllers.ToArray();
List<InputConfig> validInputs = [];
foreach (InputConfig inputConfigEntry in inputConfig)
foreach (InputConfig inputConfigEntry in _requestedInputConfig)
{
NpadController controller;
int index = (int)inputConfigEntry.PlayerIndex;
@@ -147,7 +151,17 @@ namespace Ryujinx.Input.HLE
controller = new(_cemuHookClient);
}
bool isValid = DriverConfigurationUpdate(ref controller, inputConfigEntry);
InputConfig activeConfig = inputConfigEntry;
bool isValid = DriverConfigurationUpdate(ref controller, activeConfig);
if (!isValid &&
enableKeyboard &&
inputConfigEntry is StandardControllerInputConfig &&
TryGetKeyboardFallback(inputConfigEntry, out StandardKeyboardInputConfig fallbackConfig))
{
activeConfig = fallbackConfig;
isValid = DriverConfigurationUpdate(ref controller, activeConfig);
}
if (!isValid)
{
@@ -157,7 +171,7 @@ namespace Ryujinx.Input.HLE
else
{
_controllers[index] = controller;
validInputs.Add(inputConfigEntry);
validInputs.Add(activeConfig);
}
}
@@ -169,7 +183,7 @@ namespace Ryujinx.Input.HLE
oldControllers[i] = null;
}
_inputConfig = inputConfig;
_inputConfig = validInputs;
_enableKeyboard = enableKeyboard;
_enableMouse = enableMouse;
@@ -177,6 +191,79 @@ namespace Ryujinx.Input.HLE
}
}
private bool TryGetKeyboardFallback(InputConfig inputConfig, out StandardKeyboardInputConfig fallbackConfig)
{
fallbackConfig = null;
ReadOnlySpan<string> keyboardIds = _keyboardDriver.GamepadsIds;
if (keyboardIds.IsEmpty)
{
return false;
}
string keyboardId = keyboardIds[0];
using IGamepad keyboard = _keyboardDriver.GetGamepad(keyboardId);
if (keyboard == null)
{
return false;
}
fallbackConfig = new StandardKeyboardInputConfig
{
Version = InputConfig.CurrentVersion,
Backend = InputBackendType.WindowKeyboard,
Id = keyboardId,
Name = keyboard.Name,
PlayerIndex = inputConfig.PlayerIndex,
ControllerType = inputConfig.ControllerType,
LeftJoycon = new LeftJoyconCommonConfig<PhysicalKey>
{
DpadUp = PhysicalKey.Up,
DpadDown = PhysicalKey.Down,
DpadLeft = PhysicalKey.Left,
DpadRight = PhysicalKey.Right,
ButtonMinus = PhysicalKey.Minus,
ButtonL = PhysicalKey.E,
ButtonZl = PhysicalKey.Q,
ButtonSl = PhysicalKey.Unbound,
ButtonSr = PhysicalKey.Unbound,
},
LeftJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
{
StickUp = PhysicalKey.W,
StickDown = PhysicalKey.S,
StickLeft = PhysicalKey.A,
StickRight = PhysicalKey.D,
StickButton = PhysicalKey.F,
},
RightJoycon = new RightJoyconCommonConfig<PhysicalKey>
{
ButtonA = PhysicalKey.Z,
ButtonB = PhysicalKey.X,
ButtonX = PhysicalKey.C,
ButtonY = PhysicalKey.V,
ButtonPlus = PhysicalKey.Plus,
ButtonR = PhysicalKey.U,
ButtonZr = PhysicalKey.O,
ButtonSl = PhysicalKey.Unbound,
ButtonSr = PhysicalKey.Unbound,
},
RightJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
{
StickUp = PhysicalKey.I,
StickDown = PhysicalKey.K,
StickLeft = PhysicalKey.J,
StickRight = PhysicalKey.L,
StickButton = PhysicalKey.H,
},
};
return true;
}
public void UnblockInputUpdates()
{
lock (_lock)
@@ -334,7 +421,7 @@ namespace Ryujinx.Input.HLE
}
}
internal InputConfig GetPlayerInputConfigByIndex(int index)
public InputConfig GetPlayerInputConfigByIndex(int index)
{
lock (_lock)
{

View File

@@ -1,6 +1,5 @@
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Services.Hid;
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
@@ -75,23 +74,16 @@ namespace Ryujinx.Input
public void ClearLed() => SetLed(0);
/// <summary>
/// Starts an HD vibration effect on the gamepad if available.
/// </summary>
/// <param name="left">The vibration data for the left side</param>
/// <param name="right">The vibration data for the right side</param>
bool HDRumble(VibrationValue left, VibrationValue right);
/// <summary>
/// Starts a rumble effect on the gamepad.
/// </summary>
/// <param name="lowFrequency">The intensity of the low frequency from 0.0f to 1.0f</param>
/// <param name="highFrequency">The intensity of the high frequency from 0.0f to 1.0f</param>
/// <param name="durationMs">The duration of the rumble effect in milliseconds.</param>
bool Rumble(float lowFrequency, float highFrequency, uint durationMs);
void Rumble(float lowFrequency, float highFrequency, uint durationMs);
/// <summary>
/// Get a snaphost of the state of the gamepad that is remapped with the information from the <see cref="InputConfig"/> set via <see cref="SetConfiguration(InputConfig)"/>.
/// Get a snaphost of the state of the gamepad that is remapped with the informations from the <see cref="InputConfig"/> set via <see cref="SetConfiguration(InputConfig)"/>.
/// </summary>
/// <returns>A remapped snaphost of the state of the gamepad.</returns>
GamepadStateSnapshot GetMappedStateSnapshot();

View File

@@ -1,5 +1,6 @@
using System.Buffers;
using System.Runtime.CompilerServices;
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
namespace Ryujinx.Input
{
@@ -33,15 +34,26 @@ namespace Ryujinx.Input
{
if (_keyState is null)
{
_keyState = new bool[(int)Key.Count];
_keyState = new bool[(int)ConfigPhysicalKey.Count];
}
for (Key key = 0; key < Key.Count; key++)
for (ConfigPhysicalKey key = 0; key < ConfigPhysicalKey.Count; key++)
{
_keyState[(int)key] = keyboard.IsPressed(key);
_keyState[(int)key] = keyboard.IsPressed((Key)(int)key);
}
return new KeyboardStateSnapshot(_keyState);
}
/// <summary>
/// Try to consume a recently pressed key.
/// </summary>
/// <param name="key">The pressed key, if available.</param>
/// <returns>True if a key press was consumed.</returns>
bool TryConsumePressedKey(out Key key)
{
key = Key.Unknown;
return false;
}
}
}

View File

@@ -0,0 +1,7 @@
namespace Ryujinx.Input
{
public interface IKeyboardModeDriver : IGamepadDriver
{
IKeyboard GetKeyboard(string id, KeyboardInputMode mode);
}
}

View File

@@ -0,0 +1,78 @@
using Ryujinx.Common.Configuration.Hid.Keyboard;
using System.Collections.Generic;
using System.Numerics;
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
namespace Ryujinx.Input
{
public static class KeyboardInputMappingHelper
{
public readonly record struct KeyboardButtonMapping(GamepadButtonInputId To, ConfigPhysicalKey From)
{
public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not ConfigPhysicalKey.Unknown and not ConfigPhysicalKey.Unbound;
}
public static KeyboardButtonMapping[] BuildButtonMappings(StandardKeyboardInputConfig configuration) =>
[
// Left JoyCon
new(GamepadButtonInputId.LeftStick, configuration.LeftJoyconStick.StickButton),
new(GamepadButtonInputId.DpadUp, configuration.LeftJoycon.DpadUp),
new(GamepadButtonInputId.DpadDown, configuration.LeftJoycon.DpadDown),
new(GamepadButtonInputId.DpadLeft, configuration.LeftJoycon.DpadLeft),
new(GamepadButtonInputId.DpadRight, configuration.LeftJoycon.DpadRight),
new(GamepadButtonInputId.Minus, configuration.LeftJoycon.ButtonMinus),
new(GamepadButtonInputId.LeftShoulder, configuration.LeftJoycon.ButtonL),
new(GamepadButtonInputId.LeftTrigger, configuration.LeftJoycon.ButtonZl),
new(GamepadButtonInputId.SingleRightTrigger0, configuration.LeftJoycon.ButtonSr),
new(GamepadButtonInputId.SingleLeftTrigger0, configuration.LeftJoycon.ButtonSl),
// Right JoyCon
new(GamepadButtonInputId.RightStick, configuration.RightJoyconStick.StickButton),
new(GamepadButtonInputId.A, configuration.RightJoycon.ButtonA),
new(GamepadButtonInputId.B, configuration.RightJoycon.ButtonB),
new(GamepadButtonInputId.X, configuration.RightJoycon.ButtonX),
new(GamepadButtonInputId.Y, configuration.RightJoycon.ButtonY),
new(GamepadButtonInputId.Plus, configuration.RightJoycon.ButtonPlus),
new(GamepadButtonInputId.RightShoulder, configuration.RightJoycon.ButtonR),
new(GamepadButtonInputId.RightTrigger, configuration.RightJoycon.ButtonZr),
new(GamepadButtonInputId.SingleRightTrigger1, configuration.RightJoycon.ButtonSr),
new(GamepadButtonInputId.SingleLeftTrigger1, configuration.RightJoycon.ButtonSl),
];
public static (short X, short Y) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick<ConfigPhysicalKey> stickConfig)
{
short stickX = 0;
short stickY = 0;
if (snapshot.IsPressed(stickConfig.StickUp))
{
stickY += 1;
}
if (snapshot.IsPressed(stickConfig.StickDown))
{
stickY -= 1;
}
if (snapshot.IsPressed(stickConfig.StickRight))
{
stickX += 1;
}
if (snapshot.IsPressed(stickConfig.StickLeft))
{
stickX -= 1;
}
if (stickX == 0 && stickY == 0)
{
return (0, 0);
}
Vector2 stick = Vector2.Normalize(new Vector2(stickX, stickY));
return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
}
}
}

Some files were not shown because too many files have changed in this diff Show More