Compare commits

..

109 Commits

Author SHA1 Message Date
Neo
ecd503cf20 Merge branch ryujinx:master into ui-userprofiles-and-misc 2026-02-10 02:13:32 -06:00
Neo
36d9466427 Merge branch ryujinx:master into ui-userprofiles-and-misc 2026-02-01 03:06:19 -06:00
Neo
6282db2cb0 Merge branch ryujinx:master into ui-userprofiles-and-misc 2026-01-31 05:10:59 -06:00
Neo
6ea4ce3404 Merge branch ryujinx:master into ui-userprofiles-and-misc 2026-01-30 06:18:03 -06:00
Neo
72c88b9b50 Merge branch ryujinx:master into ui-userprofiles-and-misc 2026-01-29 09:01:47 -06:00
Neo
0baf3b84ed Merge branch ryujinx:master into ui-userprofiles-and-misc 2026-01-22 05:19:01 -06:00
Neo
1552d31e01 Merge branch ryujinx:master into ui-userprofiles-and-misc 2026-01-21 04:07:47 -06:00
_Neo_
5d0fc6d456 Minor cleanup
Going over changed files and adjusting structure and looks
2026-01-17 17:21:37 +02:00
_Neo_
5993c78344 Another entry to UserProfiles.json 2026-01-16 19:45:51 +02:00
_Neo_
e1b01f2e70 Move another entry to UserProfiles.json 2026-01-16 19:29:31 +02:00
_Neo_
e3bdda7afa Remove duplicate locale of "Choose Avatar" 2026-01-16 19:27:23 +02:00
_Neo_
0d5503c014 Add back the file picker options 2026-01-16 19:00:21 +02:00
_Neo_
b26375cc4b Some more adjustments 2026-01-06 13:08:48 +02:00
_Neo_
85dad79581 Reverts & Some Fixes 2026-01-06 13:01:29 +02:00
_Neo_
2941951f4d Fractured locales minor cleanup 2026-01-06 11:47:32 +02:00
_Neo_
925ae1652b Some fixes
Alignment, sizing, and selection indicator
2026-01-05 17:30:16 +02:00
_Neo_
af59454e19 Implement Fractured Locales for User Profiles
Initial implementation before cleanup
2026-01-04 17:40:09 +02:00
Neo
beb7dfe7a6 Merge branch ryujinx:master into ui-userprofiles-and-misc 2026-01-03 12:42:07 -06:00
Neo
85e62c3ad2 Merge branch ryujinx:master into ui-userprofiles-and-misc 2026-01-01 04:59:59 -06:00
_Neo_
082a2a2051 Quick fix 2025-12-29 21:05:13 +02:00
_Neo_
2b2159f330 Proper UserSelector & ProfileSelector Alignment 2025-12-29 21:02:44 +02:00
_Neo_
3620c76cc1 Initial cleanup 2025-12-29 12:36:37 +02:00
Neo
5a8f6fa46d Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-12-28 07:05:46 -06:00
_Neo_
813057acbf Further adjustments to FirmwareAvatarSelector 2025-12-26 17:54:01 +02:00
_Neo_
620eba5fcb Removals and cleanup 2025-12-26 17:23:23 +02:00
Neo
829a5561ab Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-12-26 02:54:13 -06:00
_Neo_
0aec32f437 Adding an observable property 2025-12-25 19:39:01 +02:00
_Neo_
6619de59ab Testing new stuff 2025-12-25 19:37:29 +02:00
_Neo_
746dc2cd22 Margins + Locale adjustments Pt.2 2025-12-24 21:16:13 +02:00
_Neo_
17ab09119e Margins + Locale adjustments 2025-12-24 18:55:08 +02:00
Neo
46bae1c40a Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-12-23 02:27:12 -06:00
Neo
5536bba1fa Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-12-22 02:44:23 -06:00
_Neo_
8c6b642129 Center listbox elements in ProfileSelectorDialog 2025-12-20 16:36:51 +02:00
Neo
76dd747811 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-12-19 04:58:26 -06:00
Neo
1c073ebc63 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-12-18 03:52:22 -06:00
Neo
cebe423df2 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-12-13 05:08:36 -06:00
Neo
93f53b24b8 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-12-07 03:33:08 -06:00
Neo
cb5c9ce585 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-12-05 08:06:27 -06:00
Neo
167b41354b Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-12-05 04:07:13 -06:00
Neo
e52fa0b9d1 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-12-03 07:17:01 -06:00
Neo
6ed92dd9b7 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-11-20 02:51:42 -06:00
Neo
d9846faa5f Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-11-18 02:21:28 -06:00
_Neo_
928a189d99 Fix "No Profiles To Recover" not updating in certain instances
"No Profiles To Recover" text did not show up after saving the recovered profile, as the user is still on the "Profile Recovery" window. This now updates this.
2025-11-17 13:43:23 +02:00
_Neo_
de11115971 Remove "Profile" from "Set Profile Image" + Others 2025-11-17 13:32:57 +02:00
Neo
c093b34767 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-11-17 02:42:27 -06:00
Neo
13036dcd5b Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-11-16 02:22:28 -06:00
Neo
51bec1f4a2 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-11-15 03:23:23 -06:00
Neo
00cb9e42f8 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-11-14 05:54:46 -06:00
Neo
5be56d0ccf Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-11-12 08:57:43 -06:00
Neo
a9bb932491 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-11-11 13:26:11 -06:00
Neo
e1f215de46 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-11-11 13:03:34 -06:00
Neo
b6eb78598c Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-11-11 02:37:12 -06:00
Neo
1392fcfbc5 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-11-10 03:14:11 -06:00
Neo
9b82e8452f Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-11-08 10:01:40 -06:00
Neo
379ce9e7aa Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-11-08 03:13:13 -06:00
Neo
fc89c17037 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-11-07 03:17:31 -06:00
Neo
222db1a736 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-11-05 03:47:22 -06:00
_Neo_
89c6c490a3 Merge branch 'ui-userprofiles-and-misc' of https://git.ryujinx.app/neo/ryujinx into ui-userprofiles-and-misc 2025-11-01 13:33:54 +02:00
_Neo_
4d3a98e71d Fixing ProfileSelectorDialog and removing unnecessary style which literally does nothing visually 2025-11-01 13:33:33 +02:00
Neo
18233cf7e6 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-11-01 05:21:36 -05:00
_Neo_
c0cc54cc56 Reverting "I want to reset my settings" and fixing merge conflicts 2025-11-01 12:21:25 +02:00
Neo
d9ab68b1e9 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-10-30 03:28:45 -05:00
Neo
e7e0d4d877 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-10-29 02:32:24 -05:00
_Neo_
a2fa346cfd Remove unnecessary Close in User Profiles 2025-10-28 15:49:11 +02:00
_Neo_
6ae279300c Remove unused line 2025-10-28 12:27:16 +02:00
Neo
82e392604d Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-10-28 04:45:57 -05:00
Neo
860112c910 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-10-27 05:46:55 -05:00
Neo
0ecef83316 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-10-26 14:31:54 -05:00
_Neo_
93256afd24 Dynamic Window Titles + Clean Up Locales 2025-10-26 17:48:55 +02:00
Neo
5d3f22ac57 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-10-26 04:46:37 -05:00
_Neo_
5b63aabe8b Update "Lost Accounts" window title with new message 2025-10-25 19:26:54 +03:00
_Neo_
9b6dfab66e Quick fix for French and Spanish "Lost Accounts" 2025-10-25 17:22:18 +03:00
_Neo_
9e1ee169d9 Image Selector Updates 2025-10-25 17:12:45 +03:00
_Neo_
0958796a29 New Edits + Potential Fixes 2025-10-25 16:50:30 +03:00
_Neo_
f257481cdb Reverting more new stuff... 2025-10-25 15:37:22 +03:00
_Neo_
6533270499 Another revert. 2025-10-25 15:13:05 +03:00
_Neo_
c3c6f36fea Merge branch 'ui-userprofiles-and-misc' of https://git.ryujinx.app/neo/ryujinx into ui-userprofiles-and-misc 2025-10-25 15:11:33 +03:00
_Neo_
df153efadf Reverting... 2025-10-25 15:11:12 +03:00
Neo
4b2362f18b Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-10-25 07:06:47 -05:00
_Neo_
c2fc1a8582 Fixing... 2025-10-25 15:06:22 +03:00
_Neo_
598d6076ee Fixing Pt.6 2025-10-25 15:05:27 +03:00
_Neo_
69e2ea2894 Fixing Pt.5 2025-10-25 15:02:12 +03:00
_Neo_
93fe2d36aa Fixing... pt.4 2025-10-25 15:01:25 +03:00
_Neo_
0d2f280303 Fixing pt.3 2025-10-25 14:57:35 +03:00
_Neo_
24ac55f4d6 Fixing pt.2 2025-10-25 14:55:59 +03:00
_Neo_
af5d9a90b7 Fixing the MR due to errors in merge 2025-10-25 14:52:58 +03:00
_Neo_
d52415b535 Fix Recover Account window title behaviour 2025-10-23 21:57:39 +03:00
Neo
4d56f4dcd3 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-10-23 10:11:31 -05:00
Neo
57c91089f7 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-10-22 02:54:18 -05:00
_Neo_
15c9d50815 Recover Button Fix 2025-10-21 22:31:21 +03:00
Neo
4012fecc25 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-10-21 05:58:41 -05:00
_Neo_
80df6e2336 Remove Name Nesting in User Selector View 2025-10-20 16:28:20 +03:00
_Neo_
9e2837d885 Fixed Styling & (Potential) Fix "No Profiles To Recover" 2025-10-20 15:50:47 +03:00
_Neo_
96028daff1 Merge branch 'ui-userprofiles-and-misc' of https://git.ryujinx.app/neo/ryujinx into ui-userprofiles-and-misc 2025-10-20 14:38:21 +03:00
_Neo_
46fa8c1426 Reverting no periods and other translations 2025-10-20 14:37:53 +03:00
Neo
35aacdb289 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-10-20 06:36:37 -05:00
_Neo_
7a2802d870 Fixing MR So Nothing Breaks Pt.1 2025-10-20 14:35:58 +03:00
_Neo_
8548d35620 UserSelector Max Width Back at 90 2025-10-18 22:39:35 +03:00
_Neo_
07ef8e9c9a Potential Size Fix. 2025-10-18 21:28:07 +03:00
_Neo_
5963b425d1 One more. 2025-10-18 18:56:53 +03:00
_Neo_
7f5a67434e Additional fix. 2025-10-18 18:47:40 +03:00
Neo
3abfeebd58 Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-10-18 03:20:24 -05:00
Neo
8b438c69db Merge branch ryujinx:master into ui-userprofiles-and-misc 2025-10-17 04:23:13 -05:00
_Neo_
1d86653c9d Other Misc Updates 2025-10-15 23:08:21 +03:00
_Neo_
79f3ea5cfa Adjusting some minor values 2025-10-15 20:51:43 +03:00
_Neo_
ba656e560b Lowercase "shader" in some lines. 2025-10-15 19:09:43 +03:00
_Neo_
5a6d476490 Other quick adjustments. 2025-10-15 19:07:20 +03:00
_Neo_
16c35344d8 Quick Radius fix. 2025-10-15 19:05:00 +03:00
_Neo_
9f2ab7aa8f Update User Profile Stuff + Misc. 2025-10-15 18:59:13 +03:00
117 changed files with 2538 additions and 3891 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'
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'
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'
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

@@ -10,7 +10,7 @@ jobs:
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v9
- uses: actions/github-script@v6
with:
script: |
const {owner, repo} = context.repo;

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'
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,27 +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: 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
@@ -159,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'
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
@@ -193,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
./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'
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: |

View File

@@ -3,63 +3,60 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia" Version="11.3.14" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.14" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.14" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.14" />
<PackageVersion Include="SharpCompress" Version="0.47.4" />
<PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.9.5" />
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.9.5" />
<PackageVersion Include="Avalonia" Version="11.3.6" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.6" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.6" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.6" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.6" />
<PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.6.2" />
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.6.2" />
<PackageVersion Include="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.NoAnim" Version="2.4.0-build3" />
<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.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.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.LibHac" Version="0.21.0-alpha.126" />
<PackageVersion Include="Ryujinx.UpdateClient" Version="1.0.44" />
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="1.0.44" />
<PackageVersion Include="Gommon" Version="2.8.0.1" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="Sep" Version="0.13.0" />
<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.22.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.22.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.22.0" />
<PackageVersion Include="SkiaSharp" Version="2.88.9" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.9" />
<PackageVersion Include="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="System.Management" Version="9.0.2" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
</ItemGroup>
</Project>
</Project>

View File

@@ -7,8 +7,8 @@
# 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 href="https://github.com/Ryubing/forgejo" target="_blank">modified</a> <a href="https://forgejo.org/" target="_blank">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

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

@@ -0,0 +1,854 @@
{
"Locales": [
{
"ID": "MenuBarOptions_OpenUserProfiles",
"Translations": {
"ar_SA": "_ملفات المستخدمين",
"de_DE": "_Benutzerprofile",
"el_GR": "_Προφίλ Χρηστών",
"en_US": "_User Profiles",
"es_ES": "_Perfiles de Usuario",
"fr_FR": "_Profils d'Utilisateurs",
"he_IL": "_פרופילי משתמש",
"it_IT": "_Profili utent",
"ja_JP": "ユーザプロファイル(_M)",
"ko_KR": "사용자 프로필(_M)",
"no_NO": "_Brukerprofiler",
"pl_PL": "_Profile użytkowników",
"pt_BR": "_Perfis de usuário",
"ru_RU": "_Учётные записи",
"sv_SE": "_Användarprofiler",
"th_TH": "_โปรไฟล์ผู้ใช้งาน",
"tr_TR": "_Kullanıcı Profilleri",
"uk_UA": "_Профілі користувачів",
"zh_CN": "用户配置文件(_M)",
"zh_TW": "使用者設定檔(_M)"
}
},
{
"ID": "WindowTitle",
"Translations": {
"ar_SA": "ملفات المستخدمين",
"de_DE": "Benutzerprofile",
"el_GR": "Προφίλ Χρηστών",
"en_US": "User Profiles",
"es_ES": "Perfiles de Usuario",
"fr_FR": "Profils d'Utilisateurs",
"he_IL": "פרופילי משתמש",
"it_IT": "Profili utent",
"ja_JP": "ユーザプロファイル",
"ko_KR": "사용자 프로필",
"no_NO": "Brukerprofiler",
"pl_PL": "Profile użytkowników",
"pt_BR": "Perfis de usuário",
"ru_RU": "Учётные записи",
"sv_SE": "Användarprofiler",
"th_TH": "โปรไฟล์ผู้ใช้งาน",
"tr_TR": "Kullanıcı Profilleri",
"uk_UA": "Профілі користувачів",
"zh_CN": "用户配置文件",
"zh_TW": "使用者設定檔"
}
},
{
"ID": "ManageSaves",
"Translations": {
"ar_SA": "عمليات الحفظ",
"de_DE": "Speicherstände",
"el_GR": "Αποθηκεύσεις",
"en_US": "Saves",
"es_ES": "Partidas",
"fr_FR": "Sauvegardes",
"he_IL": "שמירות",
"it_IT": "Salvataggi",
"ja_JP": "セーブデータ",
"ko_KR": "저장",
"no_NO": "Lagringer",
"pl_PL": "Zapisy",
"pt_BR": "Salvamentos",
"ru_RU": "Сохранения",
"sv_SE": "Sparningar",
"th_TH": "บันทึก",
"tr_TR": "Kayıtlar",
"uk_UA": "Збереження",
"zh_CN": "存档",
"zh_TW": "存檔"
}
},
{
"ID": "DeleteSaveNote",
"Translations": {
"ar_SA": "هل حذف بيانات حفظ المستخدم لهذه اللعبة؟",
"de_DE": "Löschen Sie die gespeicherten Spielstände dieses Spiels?",
"el_GR": "Διαγραφή των δεδομένων αποθήκευσης αυτού του παιχνιδιού;",
"en_US": "Delete this game's save data?",
"es_ES": "¿Eliminar los datos de guardado de este juego?",
"fr_FR": "Supprimer les données de sauvegarde de ce jeu ?",
"he_IL": "האם למחוק את נתוני השמירה של המשחק הזה?",
"it_IT": "Eliminare i dati di salvataggio di questo gioco?",
"ja_JP": "このゲームのセーブデータを削除しますか?",
"ko_KR": "이 게임의 저장 데이터를 삭제하시겠습니까?",
"no_NO": "Slette lagrede data for dette spillet?",
"pl_PL": "Usunąć dane zapisu dla tej gry?",
"pt_BR": "Excluir os dados salvos deste jogo?",
"ru_RU": "Удалить данные сохранений для этой игры?",
"sv_SE": "Ta bort sparad data för detta spel?",
"th_TH": "ลบข้อมูลบันทึกของเกมนี้หรือไม่?",
"tr_TR": "Bu oyun için kaydedilen veriyi silmek?",
"uk_UA": "Видалити збереження даних для цієї гри?",
"zh_CN": "删除此游戏的存档数据?",
"zh_TW": "刪除此遊戲的存檔資料?"
}
},
{
"ID": "SaveManagerTitle",
"Translations": {
"ar_SA": "حفظات {0}",
"de_DE": "{0}s Speicherstände",
"el_GR": "Αποθηκεύσεις του {0}",
"en_US": "{0}'s Saves",
"es_ES": "Guardados de {0}",
"fr_FR": "Sauvegardes de {0}",
"he_IL": "שמירות של {0}",
"it_IT": "Salvataggi di {0}",
"ja_JP": "{0} のセーブデータ",
"ko_KR": "{0} 의 저장",
"no_NO": "Lagringer til {0}",
"pl_PL": "Zapisy {0}",
"pt_BR": "Salvamentos de {0}",
"ru_RU": "Сохранения {0}",
"sv_SE": "{0}s Sparningar",
"th_TH": "ข้อมูลที่บันทึกไว้ของ {0}",
"tr_TR": "{0}nin Kayıtları",
"uk_UA": "Збереження {0}",
"zh_CN": "{0} 的存档",
"zh_TW": "{0} 的存檔"
}
},
{
"ID": "RecoverLostProfiles",
"Translations": {
"ar_SA": "الملفات الشخصية المفقودة",
"de_DE": "Verlorene Profile",
"el_GR": "Χαμένα προφίλ",
"en_US": "Lost Profiles",
"es_ES": "Perfiles Perdidos",
"fr_FR": "Profils Perdus",
"he_IL": "פרופילים אבודים",
"it_IT": "Profili persi",
"ja_JP": "失われたプロフィール",
"ko_KR": "분실된 프로필",
"no_NO": "Tapte profiler",
"pl_PL": "Utracone profile",
"pt_BR": "Perfis perdidos",
"ru_RU": "Потерянные учёные записи",
"sv_SE": "Förlorade profiler",
"th_TH": "โปรไฟล์ที่สูญหาย",
"tr_TR": "Kayıp profiller",
"uk_UA": "Втрачені профілі",
"zh_CN": "丢失的个人资料",
"zh_TW": "遺失的個人資料"
}
},
{
"ID": "RecoverLostProfiles_ToolTip",
"Translations": {
"ar_SA": "يستعيد الملفات الشخصية التي لم تُحذف يدويًا والتي تحتوي على حفظات.",
"de_DE": "Stellt nicht manuell gelöschte Profile mit Speicherständen wieder.",
"el_GR": "Ανακτά προφίλ που δεν διαγράφηκαν χειροκίνητα και έχουν αποθηκεύσεις.",
"en_US": "Recovers non-manually-deleted profiles that have saves.",
"es_ES": "Recupera perfiles no eliminados manualmente que tienen guardados.",
"fr_FR": "Récupère les profils non supprimés manuellement ayant des sauvegardes.",
"he_IL": "שחזור פרופילים שלא נמחקו ידנית ויש להם שמירות.",
"it_IT": "Recupera profili non eliminati manualmente che hanno salvataggi.",
"ja_JP": "手動で削除されていない、保存されたプロフィールを回復します。",
"ko_KR": "수동으로 삭제되지 않은 저장된 프로필을 복구합니다.",
"no_NO": "Gjenoppretter profiler som ikke er manuelt slettet og som har lagringer.",
"pl_PL": "Odzyskuje profile, które nie zostały usunięte ręcznie, a które mają zapisy.",
"pt_BR": "Recupera perfis não deletados manualmente que possuem saves.",
"ru_RU": "Восстанавливает учётные записи, не удалённые вручную и имеющие сохранения.",
"sv_SE": "Återställer profiler som inte har raderats manuellt och har sparade data.",
"th_TH": "กู้คืนโปรไฟล์ที่ไม่ได้ลบด้วยตนเองและมีการบันทึก",
"tr_TR": "Manuel olarak silinmemiş ve kayıtlara sahip profilleri kurtarır.",
"uk_UA": "Відновлює учётні записи, які не були видалені вручну і мають збереження.",
"zh_CN": "恢复未手动删除且有存档的个人资料。",
"zh_TW": "恢復未手動刪除且有存檔的個人資料。"
}
},
{
"ID": "RecoverProfile",
"Translations": {
"ar_SA": "استعادة",
"de_DE": "Wiederherstellen",
"el_GR": "Ανάκτηση",
"en_US": "Recover",
"es_ES": "Recuperar",
"fr_FR": "Récupérer",
"he_IL": "שחזר",
"it_IT": "Recupera",
"ja_JP": "復旧",
"ko_KR": "복구",
"no_NO": "Gjenopprett",
"pl_PL": "Odzyskaj",
"pt_BR": "Recuperar",
"ru_RU": "Восстановить",
"sv_SE": "Återskapa",
"th_TH": "กู้คืน",
"tr_TR": "Kurtar",
"uk_UA": "Відновити",
"zh_CN": "恢复",
"zh_TW": "復原"
}
},
{
"ID": "RecoverProfile_EmptyList",
"Translations": {
"ar_SA": "لا توجد ملفات شخصية لاستردادها",
"de_DE": "Keine Profile zum Wiederherstellen",
"el_GR": "Δεν υπάρχουν προφίλ για ανάκτηση",
"en_US": "No Profiles To Recover",
"es_ES": "No hay perfiles a recuperar",
"fr_FR": "Aucun profil à restaurer",
"he_IL": "אין פרופילים לשחזור",
"it_IT": "Nessun profilo da recuperare",
"ja_JP": "復元するプロファイルはありません",
"ko_KR": "복구할 프로필 없음",
"no_NO": "Ingen profiler å gjenopprette",
"pl_PL": "Brak profili do odzyskania",
"pt_BR": "Nenhum perfil para recuperar",
"ru_RU": "Нет учётных записей для восстановления",
"sv_SE": "Inga profiler att återskapa",
"th_TH": "ไม่มีโปรไฟล์ที่สามารถกู้คืนได้",
"tr_TR": "Kurtarılacak profil bulunamadı",
"uk_UA": "Немає профілів для відновлення",
"zh_CN": "没有可以恢复的用户数据",
"zh_TW": "無設定檔可復原"
}
},
{
"ID": "ManageSaves_SortByName",
"Translations": {
"ar_SA": "الاسم",
"de_DE": "",
"el_GR": "Όνομα",
"en_US": "Name",
"es_ES": "Nombre",
"fr_FR": "Nom",
"he_IL": "שם",
"it_IT": "Nome",
"ja_JP": "名称",
"ko_KR": "이름",
"no_NO": "Navn",
"pl_PL": "Nazwa",
"pt_BR": "Nome",
"ru_RU": "Название",
"sv_SE": "Namn",
"th_TH": "ชื่อ",
"tr_TR": "İsim",
"uk_UA": "Назва",
"zh_CN": "名称",
"zh_TW": "名稱"
}
},
{
"ID": "ManageSaves_SortBySize",
"Translations": {
"ar_SA": "الحجم",
"de_DE": "Größe",
"el_GR": "Μέγεθος",
"en_US": "Size",
"es_ES": "Tamaño",
"fr_FR": "Taille",
"he_IL": "גודל",
"it_IT": "Dimensione",
"ja_JP": "サイズ",
"ko_KR": "크기",
"no_NO": "Størrelse",
"pl_PL": "Rozmiar",
"pt_BR": "Tamanho",
"ru_RU": "Размер",
"sv_SE": "Storlek",
"th_TH": "ขนาด",
"tr_TR": "Boyut",
"uk_UA": "Розмір",
"zh_CN": "大小",
"zh_TW": "大小"
}
},
{
"ID": "ManageSaves_SortOrderAscending",
"Translations": {
"ar_SA": "تصاعدي",
"de_DE": "Aufsteigend",
"el_GR": "Αύξουσα",
"en_US": "Ascending",
"es_ES": "Ascendente",
"fr_FR": "Croissant",
"he_IL": "סדר עולה",
"it_IT": "Crescente",
"ja_JP": "昇順",
"ko_KR": "오름차순",
"no_NO": "Stigende",
"pl_PL": "Rosnąco",
"pt_BR": "Ascendente",
"ru_RU": "По Возрастанию",
"sv_SE": "Stigande",
"th_TH": "จากน้อยไปมาก",
"tr_TR": "Artan",
"uk_UA": "За зростанням",
"zh_CN": "升序",
"zh_TW": "從小到大"
}
},
{
"ID": "ManageSaves_SortOrderDescending",
"Translations": {
"ar_SA": "تنازلي",
"de_DE": "Absteigend",
"el_GR": "Φθίνουσα",
"en_US": "Descending",
"es_ES": "Descendente",
"fr_FR": "Décroissant",
"he_IL": "סדר יורד",
"it_IT": "Decrescente",
"ja_JP": "降順",
"ko_KR": "내림차순",
"no_NO": "Synkende",
"pl_PL": "Malejąco",
"pt_BR": "Descendente",
"ru_RU": "По Убыванию",
"sv_SE": "Fallande",
"th_TH": "จากมากไปน้อย",
"tr_TR": "Azalan",
"uk_UA": "За спаданням",
"zh_CN": "降序",
"zh_TW": "從大到小"
}
},
{
"ID": "ManageSaves_Search",
"Translations": {
"ar_SA": "بحث",
"de_DE": "Suche",
"el_GR": "Αναζήτηση",
"en_US": "Search",
"es_ES": "Buscar",
"fr_FR": "Rechercher",
"he_IL": "חפש",
"it_IT": "Cerca",
"ja_JP": "検索",
"ko_KR": "찾기",
"no_NO": "Søk",
"pl_PL": "Wyszukaj",
"pt_BR": "Buscar",
"ru_RU": "Поиск",
"sv_SE": "Sök",
"th_TH": "ค้นหา",
"tr_TR": "Ara",
"uk_UA": "Пошук",
"zh_CN": "搜索",
"zh_TW": "搜尋"
}
},
{
"ID": "IrreversibleActionNote",
"Translations": {
"ar_SA": "هذا الإجراء لا يمكن التراجع عنه.",
"de_DE": "Diese Option kann nicht rückgängig gemacht werden.",
"el_GR": "Αυτή η ενέργεια είναι μη αναστρέψιμη.",
"en_US": "This action is not reversible.",
"es_ES": "Esta acción no es reversible.",
"fr_FR": "Cette action n'est pas réversible.",
"he_IL": "הפעולה הזו בלתי הפיכה.",
"it_IT": "Questa azione non è reversibile.",
"ja_JP": "この操作は元に戻せません.",
"ko_KR": "이 작업은 되돌릴 수 없습니다.",
"no_NO": "Denne handlingen er ikke reverserbar.",
"pl_PL": "Ta czynność nie jest odwracalna.",
"pt_BR": "Esta ação não é reversível.",
"ru_RU": "Данное действие является необратимым.",
"sv_SE": "Denna åtgärd går inte att ångra.",
"th_TH": "การดำเนินการนี้ไม่สามารถย้อนกลับได้",
"tr_TR": "Bu eylem geri alınamaz.",
"uk_UA": "Цю дію не можна скасувати.",
"zh_CN": "删除后不可恢复。",
"zh_TW": "此動作將無法復原。"
}
},
{
"ID": "ButtonClose",
"Translations": {
"ar_SA": "إغلاق",
"de_DE": "Schließen",
"el_GR": "Κλείσιμο",
"en_US": "Close",
"es_ES": "Cerrar",
"fr_FR": "Fermer",
"he_IL": "סגירה",
"it_IT": "Chiudi",
"ja_JP": "閉じる",
"ko_KR": "닫기",
"no_NO": "Lukk",
"pl_PL": "Zamknij",
"pt_BR": "Fechar",
"ru_RU": "Закрыть",
"sv_SE": "Stäng",
"th_TH": "ปิด",
"tr_TR": "Kapat",
"uk_UA": "Закрити",
"zh_CN": "关闭",
"zh_TW": "關閉"
}
},
{
"ID": "NameLabel",
"Translations": {
"ar_SA": "الاسم:",
"de_DE": null,
"el_GR": "Όνομα:",
"en_US": "Name:",
"es_ES": "Nombre:",
"fr_FR": "Nom :",
"he_IL": "שם:",
"it_IT": "Nome:",
"ja_JP": "名称:",
"ko_KR": "이름 :",
"no_NO": "Navn:",
"pl_PL": "Nazwa:",
"pt_BR": "Nome:",
"ru_RU": "Имя:",
"sv_SE": "Namn:",
"th_TH": "ชื่อ:",
"tr_TR": "İsim:",
"uk_UA": "Імʼя",
"zh_CN": "名称:",
"zh_TW": "名稱:"
}
},
{
"ID": "ProfileNameSelectionWatermark",
"Translations": {
"ar_SA": "اختر اسم الملف الشخصي",
"de_DE": "Wähle einen Profilnamen",
"el_GR": "Επιλέξτε όνομα προφίλ",
"en_US": "Choose a Profile Name",
"es_ES": "Escoge un Nombre de Perfil",
"fr_FR": "Choisir un Nom de Profil",
"he_IL": "בחרו שם פרופיל",
"it_IT": "Scegli un Nome Profilo",
"ja_JP": "プロフィール名を選択",
"ko_KR": "프로필 이름 선택",
"no_NO": "Velg et Profilnavn",
"pl_PL": "Wybierz nazwę profilu",
"pt_BR": "Escolha um Nome de Perfil",
"ru_RU": "Выберите имя профиля",
"sv_SE": "Välj ett Profilnamn",
"th_TH": "เลือก ชื่อโปรไฟล์",
"tr_TR": "Profil Adı Seç",
"uk_UA": "Оберіть ім'я профілю",
"zh_CN": "选择个人资料名称",
"zh_TW": "選擇個人資料名稱"
}
},
{
"ID": "UserIdLabel",
"Translations": {
"ar_SA": "معرف المستخدم:",
"de_DE": "Benutzer-ID:",
"el_GR": "Ταυτότητα Χρήστη:",
"en_US": "User ID:",
"es_ES": "ID de Usuario:",
"fr_FR": "Identifiant Utilisateur :",
"he_IL": "מזהה משתמש:",
"it_IT": "ID utente:",
"ja_JP": "ユーザID:",
"ko_KR": "사용자 ID :",
"no_NO": "Bruker ID:",
"pl_PL": "ID Użytkownika:",
"pt_BR": "ID de Usuário:",
"ru_RU": "ID пользователя:",
"sv_SE": "Användar-id:",
"th_TH": "รหัสผู้ใช้:",
"tr_TR": "Kullanıcı ID:",
"uk_UA": "ID користувача:",
"zh_CN": "用户 ID",
"zh_TW": "使用者 ID:"
}
},
{
"ID": "ProfileImage_Import",
"Translations": {
"ar_SA": "استيراد الصورة",
"de_DE": "Bild importieren",
"el_GR": "Εισαγωγή Εικόνας",
"en_US": "Import Image",
"es_ES": "Importar Imagen",
"fr_FR": "Importer une image",
"he_IL": "ייבוא תמונה",
"it_IT": "Importa immagine",
"ja_JP": "画像をインポート",
"ko_KR": "이미지 가져오기",
"no_NO": "Importer bilde",
"pl_PL": "Importuj obraz",
"pt_BR": "Importar Imagem",
"ru_RU": "Импорт изображения",
"sv_SE": "Importera bild",
"th_TH": "นำเข้าภาพ",
"tr_TR": "Resim İçeri Aktar",
"uk_UA": "Імпорт зображення",
"zh_CN": "导入图像",
"zh_TW": "匯入圖像"
}
},
{
"ID": "ProfileImage_SelectAvatar",
"Translations": {
"ar_SA": "حدد صورة الأفاتار من البرنامج الثابت",
"de_DE": "Firmware-Avatar auswählen",
"el_GR": "Επιλέξτε Avatar από Firmware",
"en_US": "Select Firmware Avatar",
"es_ES": "Seleccionar Avatar del Firmware",
"fr_FR": "Choisir un Avatar du Firmware",
"he_IL": "בחרו אוואטר קושחה",
"it_IT": "Seleziona avatar dal firmware",
"ja_JP": "ファームウェア内のアバターを選択",
"ko_KR": "펌웨어 아바타 선택",
"no_NO": "Velg firmware-avatar",
"pl_PL": "Wybierz avatar z oprogramowania",
"pt_BR": "Selecionar Avatar do Firmware",
"ru_RU": "Выбрать аватар прошивки",
"sv_SE": "Välj avatar från firmware",
"th_TH": "เลือกอวาต้าจากระบบ",
"tr_TR": "Yazılım Avatarı Seç",
"uk_UA": "Виберіть аватар прошивки",
"zh_CN": "选择固件头像",
"zh_TW": "選取韌體大頭貼"
}
},
{
"ID": "SupportedImageFormatDialogTitle",
"Translations": {
"ar_SA": "اختر إما JPG أو JPEG أو PNG أو BMP",
"de_DE": "Wählen Sie entweder ein JPG, JPEG, PNG oder BMP",
"el_GR": "Επιλέξτε είτε JPG, JPEG, PNG ή BMP",
"en_US": "Choose either a JPG, JPEG, PNG, or BMP",
"es_ES": "Elige ya sea JPG, JPEG, PNG o BMP",
"fr_FR": "Choisissez soit un JPG, JPEG, PNG ou BMP",
"he_IL": "בחר את JPG, JPEG, PNG או BMP",
"it_IT": "Scegli tra JPG, JPEG, PNG o BMP",
"ja_JP": "JPG、JPEG、PNG、またはBMPのいずれかを選択してください",
"ko_KR": "JPG, JPEG, PNG 또는 BMP 중에서 선택하세요",
"no_NO": "Velg enten et JPG, JPEG, PNG eller BMP",
"pl_PL": "Wybierz JPG, JPEG, PNG lub BMP",
"pt_BR": "Escolha JPG, JPEG, PNG ou BMP",
"ru_RU": "Выберите либо JPG, JPEG, PNG, или BMP",
"sv_SE": "Välj antingen ett JPG, JPEG, PNG eller BMP",
"th_TH": "เลือก JPG, JPEG, PNG หรือ BMP",
"tr_TR": "JPG, JPEG, PNG veya BMP seçin",
"uk_UA": "Виберіть або JPG, JPEG, PNG, або BMP",
"zh_CN": "选择 JPG、JPEG、PNG 或 BMP",
"zh_TW": "選擇 JPG、JPEG、PNG 或 BMP"
}
},
{
"ID": "SelectAvatarTitle",
"Translations": {
"ar_SA": "تحديد أفاتار البرنامج الثابت",
"de_DE": "Firmware-Avatar auswählen",
"el_GR": "Επιλογή Avatar Firmware",
"en_US": "Select Firmware Avatar",
"es_ES": "Seleccionar Avatar del Firmware",
"fr_FR": "Sélection dun Avatar du Firmware",
"he_IL": "בחירת אוואטר קושחה",
"it_IT": "Selezione Avatar Firmware",
"ja_JP": "ファームウェアアバター選択",
"ko_KR": "펌웨어 아바타 선택",
"no_NO": "Velg firmware-avatar",
"pl_PL": "Wybór awatara oprogramowania",
"pt_BR": "Selecionar Avatar do Firmware",
"ru_RU": "Выбор аватара прошивки",
"sv_SE": "Välj firmware-avatar",
"th_TH": "การเลือกอวตารเฟิร์มแวร์",
"tr_TR": "Firmware Avatar Seçimi",
"uk_UA": "Вибір аватара прошивки",
"zh_CN": "选择固件头像",
"zh_TW": "選取韌體頭像"
}
},
{
"ID": "ButtonChooseAvatar",
"Translations": {
"ar_SA": "اختر الأفاتار",
"de_DE": "Wähle Avatar",
"el_GR": "Επιλέξτε Avatar",
"en_US": "Choose Avatar",
"es_ES": "Elegir Avatar",
"fr_FR": "Choisir un Avatar",
"he_IL": "בחרו אוואטר",
"it_IT": "Scegli Avatar",
"ja_JP": "アバターを選択",
"ko_KR": "아바타 선택",
"no_NO": "Velg avatar",
"pl_PL": "Wybierz awatar",
"pt_BR": "Escolher Avatar",
"ru_RU": "Выбрать аватар",
"sv_SE": "Välj avatar",
"th_TH": "เลือกอวาต้าของคุณ",
"tr_TR": "Avatar Seç",
"uk_UA": "Вибрати аватар",
"zh_CN": "选择头像",
"zh_TW": "選擇大頭貼"
}
},
{
"ID": "DialogUserProfileUnsavedChangesMessage",
"Translations": {
"ar_SA": "لقد قمت بإجراء تغييرات غير محفوظة على هذا الملف الشخصي.",
"de_DE": "Sie haben nicht gespeicherte Änderungen an diesem Profil.",
"el_GR": "Έχετε μη αποθηκευμένες αλλαγές σε αυτό το προφίλ.",
"en_US": "You have unsaved changes to this profile.",
"es_ES": "Tienes cambios no guardados en este perfil.",
"fr_FR": "Vous avez des modifications non enregistrées sur ce profil.",
"he_IL": "ביצעת שינויים לא שמורים בפרופיל זה.",
"it_IT": "Hai modifiche non salvate su questo profilo.",
"ja_JP": "このプロファイルには保存されていない変更があります.",
"ko_KR": "이 프로필에는 저장되지 않은 변경 사항이 있습니다.",
"no_NO": "Du har usparende endringer på denne profilen.",
"pl_PL": "Masz niezapisane zmiany w tym profilu.",
"pt_BR": "Você tem alterações não salvas neste perfil.",
"ru_RU": "У вас есть несохраненные изменения в этом профиле.",
"sv_SE": "Du har osparade ändringar i den här profilen.",
"th_TH": "คุณมีการเปลี่ยนแปลงที่ยังไม่ได้บันทึกในโปรไฟล์นี้",
"tr_TR": "Bu profilde kaydedilmemiş değişiklikleriniz var.",
"uk_UA": "У вас є незбережені зміни в цьому профілі.",
"zh_CN": "您对该账户有未保存的更改。",
"zh_TW": "您對該使用者設定檔有未儲存的變更。"
}
},
{
"ID": "DialogUserProfileUnsavedChangesSubMessage",
"Translations": {
"ar_SA": "هل تريد تجاهل التغييرات؟",
"de_DE": "Verwerfen Sie die Änderungen?",
"el_GR": "Θέλετε να απορρίψετε τις αλλαγές?",
"en_US": "Discard changes?",
"es_ES": "¿Descartar los cambios?",
"fr_FR": "Annuler les modifications ?",
"he_IL": "האם ברצונך להתעלם מהשינויים?",
"it_IT": "Scartare le modifiche?",
"ja_JP": "変更を破棄しますか?",
"ko_KR": "변경 사항을 취소하시겠습니까?",
"no_NO": "Vil du forkaste endringene?",
"pl_PL": "Czy chcesz odrzucić zmiany?",
"pt_BR": "Deseja descartar as alterações?",
"ru_RU": "Отменить изменения?",
"sv_SE": "Vill du förkasta ändringarna?",
"th_TH": "คุณต้องการทิ้งการเปลี่ยนแปลงหรือไม่?",
"tr_TR": "Değişiklikleri iptal et?",
"uk_UA": "Бажаєте скасувати зміни?",
"zh_CN": "确定要放弃更改吗?",
"zh_TW": "您確定要放棄變更嗎?"
}
},
{
"ID": "DialogUserProfileUnsavedChangesTitle",
"Translations": {
"ar_SA": "تحذير - التغييرات غير محفوظة",
"de_DE": "WARNUNG - Nicht gespeicherte Änderungen",
"el_GR": "ΠΡΟΣΟΧΗ - Μην Αποθηκευμένες Αλλαγές.",
"en_US": "WARNING - Unsaved Changes",
"es_ES": "ADVERTENCIA - Cambios Sin Guardar",
"fr_FR": "AVERTISSEMENT - Modifications Non Enregistrées",
"he_IL": "אזהרה - שינויים לא שמורים",
"it_IT": "ATTENZIONE - Modifiche non salvate",
"ja_JP": "警告 - 保存されていない変更",
"ko_KR": "경고 - 저장되지 않은 변경 사항",
"no_NO": "ADVARSEL - Ulagrede endringer",
"pl_PL": "UWAGA - Niezapisane zmiany",
"pt_BR": "ALERTA - Alterações não salvas",
"ru_RU": "ВНИМАНИЕ - Несохраненные изменения",
"sv_SE": "VARNING - Ej sparade ändringar",
"th_TH": "คำเตือน - มีการเปลี่ยนแปลงที่ไม่ได้บันทึก",
"tr_TR": "UYARI - Kaydedilmemiş Değişiklikler",
"uk_UA": "УВАГА — Незбережені зміни",
"zh_CN": "警告 - 有未保存的更改",
"zh_TW": "警告 - 未儲存的變更"
}
},
{
"ID": "DialogUserProfileDeletionConfirmMessage",
"Translations": {
"ar_SA": "هل حذف الملف الشخصي المحدد؟",
"de_DE": "Löschen Sie das ausgewählte Profil?",
"el_GR": "Διαγραφή του επιλεγμένου προφίλ;",
"en_US": "Delete the selected profile?",
"es_ES": "¿Eliminar el perfil seleccionado?",
"fr_FR": "Supprimer le profil sélectionné ?",
"he_IL": "האם למחוק את הפרופיל שנבחר?",
"it_IT": "Eliminare il profilo selezionato?",
"ja_JP": "選択されたプロファイルを削除しますか?",
"ko_KR": "선택한 프로필을 삭제하시겠습니까?",
"no_NO": "Slette den valgte profilen?",
"pl_PL": "Usunąć wybrany profil?",
"pt_BR": "Excluir o perfil selecionado?",
"ru_RU": "Удалить выбранный профиль?",
"sv_SE": "Ta bort den valda profilen?",
"th_TH": "ลบโปรไฟล์ที่เลือก?",
"tr_TR": "Seçilen profili silmek?",
"uk_UA": "Видалити вибраний профіль?",
"zh_CN": "删除所选账户?",
"zh_TW": "刪除所選設定檔?"
}
},
{
"ID": "DialogUserProfileDeletionWarningMessage",
"Translations": {
"ar_SA": "لن تكون هناك ملفات الشخصية أخرى لفتحها إذا تم حذف الملف الشخصي المحدد.",
"de_DE": "Es können keine anderen Profile geöffnet werden, wenn das ausgewählte Profil gelöscht wird.",
"el_GR": "Δεν θα υπάρχουν άλλα προφίλ εάν διαγραφεί το επιλεγμένο.",
"en_US": "There would be no other profiles to be opened if selected profile is deleted.",
"es_ES": "Si eliminas el perfil seleccionado no quedará ningún otro perfil.",
"fr_FR": "Il n'y aurait aucun autre profil à ouvrir si le profil sélectionné est supprimé.",
"he_IL": "לא יהיו פרופילים אחרים שייפתחו אם הפרופיל שנבחר יימחק.",
"it_IT": "Non ci sarebbero altri profili da aprire se il profilo selezionato venisse cancellato.",
"ja_JP": "選択されたプロファイルを削除すると,プロファイルがひとつも存在しなくなります.",
"ko_KR": "선택한 프로필을 삭제하면 다른 프로필을 열 수 없음.",
"no_NO": "Det vil ikke være noen profiler å åpnes hvis valgt profil blir slettet.",
"pl_PL": "Nie będzie innych profili do otwarcia, jeśli wybrany profil zostanie usunięty.",
"pt_BR": "Não haveria nenhum perfil selecionado se o perfil atual fosse deletado.",
"ru_RU": "Если выбранный профиль будет удален, другие профили не будут открываться.",
"sv_SE": "Det skulle inte finnas några andra profiler att öppnas om angiven profil tas bort.",
"th_TH": "จะไม่มีโปรไฟล์อื่นให้เปิดหากโปรไฟล์ที่เลือกถูกลบ",
"tr_TR": "Seçilen profil silinirse kullanılabilen başka profil kalmayacak.",
"uk_UA": "Якщо вибраний профіль буде видалено, інші профілі не відкриватимуться.",
"zh_CN": "删除后将没有可用的账户。",
"zh_TW": "如果刪除選取的設定檔,將無法開啟其他設定檔。"
}
},
{
"ID": "ButtonDelete",
"Translations": {
"ar_SA": "حذف",
"de_DE": "Löschen",
"el_GR": "Διαγράφω",
"en_US": "Delete",
"es_ES": "Eliminar",
"fr_FR": "Supprimer",
"he_IL": "מחיקה",
"it_IT": "Elimina",
"ja_JP": "削除",
"ko_KR": "삭제",
"no_NO": "Slett",
"pl_PL": "Usuń",
"pt_BR": "Apagar",
"ru_RU": "Удалить",
"sv_SE": "Ta bort",
"th_TH": "ลบ",
"tr_TR": "Sil",
"uk_UA": "Видалити",
"zh_CN": "删除",
"zh_TW": "刪除"
}
},
{
"ID": "ButtonSave",
"Translations": {
"ar_SA": "حفظ",
"de_DE": "Speichern",
"el_GR": "Αποθήκευση",
"en_US": "Save",
"es_ES": "Guardar",
"fr_FR": "Enregistrer",
"he_IL": "שמור",
"it_IT": "Salva",
"ja_JP": "セーブ",
"ko_KR": "저장",
"no_NO": "Lagre",
"pl_PL": "Zapisz",
"pt_BR": "Salvar",
"ru_RU": "Сохранить",
"sv_SE": "Spara",
"th_TH": "บันทึก",
"tr_TR": "Kaydet",
"uk_UA": "Зберегти",
"zh_CN": "保存",
"zh_TW": "儲存"
}
},
{
"ID": "UserEditorTitle",
"Translations": {
"ar_SA": "جارٍ تعديل {0}",
"de_DE": "{0} wird bearbeitet",
"el_GR": "Επεξεργασία {0}",
"en_US": "Editing {0}",
"es_ES": "Editando {0}",
"fr_FR": "Modification de {0}",
"he_IL": "עריכת {0}",
"it_IT": "Modifica di {0}",
"ja_JP": "{0} を編集中",
"ko_KR": "{0} 편집 중",
"no_NO": "Redigerer {0}",
"pl_PL": "Edycja {0}",
"pt_BR": "Editando {0}",
"ru_RU": "Редактирование {0}",
"sv_SE": "Redigerar {0}",
"th_TH": "กำลังกำลังแก้ไข {0}",
"tr_TR": "{0} düzenleniyor",
"uk_UA": "Редагування {0}",
"zh_CN": "正在编辑 {0}",
"zh_TW": "正在編輯 {0}"
}
},
{
"ID": "UserEditorTitleNewUser",
"Translations": {
"ar_SA": "مستخدم جديد",
"de_DE": "Neuer Nutzer",
"el_GR": "Νέος Χρήστης",
"en_US": "New User",
"es_ES": "Nuevo Usuario",
"fr_FR": "Nouvel Utilisateur",
"he_IL": "משתמש חדש",
"it_IT": "Nuovo utente",
"ja_JP": "新しいユーザー",
"ko_KR": "새 사용자",
"no_NO": "Ny bruker",
"pl_PL": "Nowy użytkownik",
"pt_BR": "Novo usuário",
"ru_RU": "Новый пользователь",
"sv_SE": "Ny användare",
"th_TH": "ผู้ใช้ใหม่",
"tr_TR": "Yeni kullanıcı",
"uk_UA": "Новий користувач",
"zh_CN": "新用户",
"zh_TW": "新使用者"
}
},
{
"ID": "EmptyNameError",
"Translations": {
"ar_SA": "الاسم مطلوب",
"de_DE": "Name ist erforderlich",
"el_GR": "Απαιτείται όνομα",
"en_US": "Name is required",
"es_ES": "El nombre es obligatorio",
"fr_FR": "Le nom est requis",
"he_IL": "נדרש שם",
"it_IT": "Il nome è obbligatorio",
"ja_JP": "名称が必要です",
"ko_KR": "이름 필수 입력",
"no_NO": "Navn er påkrevd",
"pl_PL": "Nazwa jest wymagana",
"pt_BR": "Nome é obrigatório",
"ru_RU": "Необходимо ввести имя",
"sv_SE": "Namn krävs",
"th_TH": "จำเป็นต้องระบุชื่อ",
"tr_TR": "İsim gerekli",
"uk_UA": "Імʼя обовʼязкове",
"zh_CN": "必须输入名称",
"zh_TW": "名稱為必填"
}
}
]
}

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

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

@@ -2050,9 +2050,7 @@
010003C00B868000,"Ninjin: Clash of Carrots",online-broken,playable,2024-07-10 05:12:26
0100746010E4C000,"NinNinDays",,playable,2022-11-20 15:17:29
0100C9A00ECE6000,"Nintendo 64™ Nintendo Switch Online",gpu;vulkan,ingame,2024-04-23 20:21:07
010057D00ECE4000,"Nintendo 64™ Nintendo Switch Online",gpu;vulkan,ingame,2024-04-23 20:21:07
0100e0601c632000,"Nintendo 64™ Nintendo Switch Online: MATURE 17+",,ingame,2025-02-03 22:27:00
010037A0170D2000,"NINTENDO 64™ Nintendo Switch Online 18+",,ingame,2025-02-03 22:27:00
0100D870045B6000,"Nintendo Entertainment System™ - Nintendo Switch Online",online,playable,2022-07-01 15:45:06
0100C4B0034B2000,"Nintendo Labo Toy-Con 01 Variety Kit",gpu,ingame,2022-08-07 12:56:07
01001E9003502000,"Nintendo Labo Toy-Con 03 Vehicle Kit",services;crash,menus,2022-08-03 17:20:11
@@ -2640,7 +2638,6 @@
0100B16009C10000,"SINNER: Sacrifice for Redemption",nvdec;UE4;vulkan-backend-bug,playable,2022-08-12 20:37:33
0100E9201410E000,"Sir Lovelot",,playable,2021-04-05 16:21:46
0100134011E32000,"Skate City",,playable,2022-11-04 11:37:39
0100a8501b66e000,"Skateboard Drifting with Maxwell Cat: The Game Simulator",,playable,2026-02-17 19:05:00
0100B2F008BD8000,"Skee-Ball",,playable,2020-11-16 04:44:07
01001A900F862000,"Skelattack",,playable,2021-06-09 15:26:26
01008E700F952000,"Skelittle: A Giant Party!",,playable,2021-06-09 19:08:34
@@ -3310,7 +3307,6 @@
0100AFA011068000,"Voxel Pirates",,playable,2022-09-28 22:55:02
0100BFB00D1F4000,"Voxel Sword",,playable,2022-08-30 14:57:27
01004E90028A2000,"Vroom in the night sky",Needs Update;vulkan-backend-bug,playable,2023-02-20 02:32:29
0100BFC01D976000,"Virtual Boy Nintendo Classics",services,nothing,2026-02-17 11:26:59
0100C7C00AE6C000,"VSR: Void Space Racing",,playable,2021-01-27 14:08:59
0100B130119D0000,"Waifu Uncovered",crash,ingame,2023-02-27 01:17:46
0100E29010A4A000,"Wanba Warriors",,playable,2020-10-04 17:56:22
1 title_id game_name labels status last_updated
2050 010003C00B868000 Ninjin: Clash of Carrots online-broken playable 2024-07-10 05:12:26
2051 0100746010E4C000 NinNinDays playable 2022-11-20 15:17:29
2052 0100C9A00ECE6000 Nintendo 64™ – Nintendo Switch Online gpu;vulkan ingame 2024-04-23 20:21:07
010057D00ECE4000 Nintendo 64™ – Nintendo Switch Online gpu;vulkan ingame 2024-04-23 20:21:07
2053 0100e0601c632000 Nintendo 64™ – Nintendo Switch Online: MATURE 17+ ingame 2025-02-03 22:27:00
010037A0170D2000 NINTENDO 64™ – Nintendo Switch Online 18+ ingame 2025-02-03 22:27:00
2054 0100D870045B6000 Nintendo Entertainment System™ - Nintendo Switch Online online playable 2022-07-01 15:45:06
2055 0100C4B0034B2000 Nintendo Labo Toy-Con 01 Variety Kit gpu ingame 2022-08-07 12:56:07
2056 01001E9003502000 Nintendo Labo Toy-Con 03 Vehicle Kit services;crash menus 2022-08-03 17:20:11
2638 0100B16009C10000 SINNER: Sacrifice for Redemption nvdec;UE4;vulkan-backend-bug playable 2022-08-12 20:37:33
2639 0100E9201410E000 Sir Lovelot playable 2021-04-05 16:21:46
2640 0100134011E32000 Skate City playable 2022-11-04 11:37:39
0100a8501b66e000 Skateboard Drifting with Maxwell Cat: The Game Simulator playable 2026-02-17 19:05:00
2641 0100B2F008BD8000 Skee-Ball playable 2020-11-16 04:44:07
2642 01001A900F862000 Skelattack playable 2021-06-09 15:26:26
2643 01008E700F952000 Skelittle: A Giant Party! playable 2021-06-09 19:08:34
3307 0100AFA011068000 Voxel Pirates playable 2022-09-28 22:55:02
3308 0100BFB00D1F4000 Voxel Sword playable 2022-08-30 14:57:27
3309 01004E90028A2000 Vroom in the night sky Needs Update;vulkan-backend-bug playable 2023-02-20 02:32:29
0100BFC01D976000 Virtual Boy – Nintendo Classics services nothing 2026-02-17 11:26:59
3310 0100C7C00AE6C000 VSR: Void Space Racing playable 2021-01-27 14:08:59
3311 0100B130119D0000 Waifu Uncovered crash ingame 2023-02-27 01:17:46
3312 0100E29010A4A000 Wanba Warriors playable 2020-10-04 17:56:22

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

@@ -107,12 +107,12 @@ namespace Ryujinx.BuildValidationTasks
{
locale.Translations[langCode] = string.Empty;
Console.WriteLine(
$"Language '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'! Resetting it...");
$"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'! Resetting it...");
}
else
{
Console.WriteLine(
$"Language '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'!");
$"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'!");
}
}

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

@@ -10,6 +10,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" />
<PackageReference Include="MsgPack.Cli" />
<PackageReference Include="System.Management" />
<PackageReference Include="Humanizer" />
<PackageReference Include="Gommon" />
</ItemGroup>

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

@@ -184,7 +184,6 @@ namespace Ryujinx.Common
"01001b300b9be000", // Diablo III: Eternal Collection
"010027400cdc6000", // Divinity Original 2 - Definitive Edition
"01008c8012920000", // Dying Light Platinum Edition
"0100d11013e6a000", // Eschatos
"01001cc01b2d4000", // Goat Simulator 3
"01003620068ea000", // Hand of Fate 2
"0100f7e00c70e000", // Hogwarts Legacy
@@ -194,15 +193,9 @@ namespace Ryujinx.Common
"0100d71004694000", // Minecraft
"01007430037f6000", // Monopoly
"0100853015e86000", // No Man's Sky
"0100f85014ed0000", // No More Heroes
"0100463014ed4000", // No More Heroes 2
"0100e570094e8000", // Owlboy
"01007bb017812000", // Portal
"0100abd01785c000", // Portal 2
"01009f100bc52000", // Psikyo Collection 1
"01009d400c4a8000", // Psikyo Collection 2
"01008e200c5c2000", // Muse Dash
"01005ff002e2a000", // Rayman Legends
"01007820196a6000", // Red Dead Redemption
"0100e8300a67a000", // Risk
"01002f7013224000", // Rune Factory 5

View File

@@ -22,11 +22,10 @@ namespace Ryujinx.Common.Utilities
}
// "dumpable" attribute of the calling process
private const int PR_GET_DUMPABLE = 3;
private const int PR_SET_DUMPABLE = 4;
[LibraryImport("libc", SetLastError = true)]
private static partial int prctl(int option, int arg2);
[DllImport("libc", SetLastError = true)]
private static extern int prctl(int option, int arg2);
public static void SetCoreDumpable(bool dumpable)
{
@@ -37,13 +36,5 @@ namespace Ryujinx.Common.Utilities
Debug.Assert(result == 0);
}
}
// Use the below line to display dumpable status in the console:
// Console.WriteLine($"{OsUtils.IsCoreDumpable()}");
public static bool IsCoreDumpable()
{
int result = prctl(PR_GET_DUMPABLE, 0);
return result == 1;
}
}
}

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

@@ -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++)
@@ -177,8 +176,6 @@ namespace Ryujinx.Graphics.Vulkan
}
}
// This can somehow become -1.
// Logger.Info?.PrintMsg(LogClass.Gpu, $"_referenceCount: {_referenceCount}");
Debug.Assert(_referenceCount >= 0);
}

View File

@@ -488,8 +488,6 @@ namespace Ryujinx.HLE.FileSystem
if (keyPaths.Length is 0)
throw new FileNotFoundException($"Directory '{keysSource}' contained no '.keys' files.");
List<string> failedFiles = new();
foreach (string filePath in keyPaths)
{
try
@@ -499,18 +497,15 @@ namespace Ryujinx.HLE.FileSystem
catch (Exception e)
{
Logger.Error?.Print(LogClass.Application, e.Message);
failedFiles.Add(Path.GetFileName(filePath));
continue;
}
string destPath = Path.Combine(installDirectory, Path.GetFileName(filePath));
File.Copy(filePath, destPath, true);
}
if (File.Exists(destPath))
File.Delete(destPath);
if (failedFiles.Count > 0)
{
throw new InvalidOperationException($"Failed to install the following key files: {string.Join(", ", failedFiles)}");
File.Copy(filePath, destPath, true);
}
return;
@@ -523,6 +518,8 @@ namespace Ryujinx.HLE.FileSystem
FileInfo info = new(keysSource);
using FileStream file = File.OpenRead(keysSource);
if (info.Extension is not ".keys")
throw new InvalidFirmwarePackageException("Input file extension is not .keys");
@@ -537,6 +534,10 @@ namespace Ryujinx.HLE.FileSystem
string dest = Path.Combine(installDirectory, info.Name);
if (File.Exists(dest))
File.Delete(dest);
// overwrite: true seems to not work on its own? https://github.com/Ryubing/Issues/issues/189
File.Copy(keysSource, dest, true);
}
@@ -1058,7 +1059,7 @@ namespace Ryujinx.HLE.FileSystem
}
}
public static bool AreKeysAlreadyPresent(string pathToCheck)
public static bool AreKeysAlredyPresent(string pathToCheck)
{
string[] fileNames = ["prod.keys", "title.keys", "console.keys", "dev.keys"];
foreach (string file in fileNames)

View File

@@ -20,7 +20,6 @@ using Ryujinx.HLE.HOS.Services.Mii;
using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption;
using Ryujinx.HLE.HOS.Services.Nfc.Nfp;
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
using Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager;
using Ryujinx.HLE.HOS.Services.Nv;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl;
using Ryujinx.HLE.HOS.Services.Pcv.Bpc;
@@ -67,8 +66,6 @@ namespace Ryujinx.HLE.HOS
internal List<NfpDevice> NfpDevices { get; private set; }
internal List<NfcDevice> NfcDevices { get; private set; }
internal SmRegistry SmRegistry { get; private set; }
internal ServerBase SmServer { get; private set; }
@@ -135,7 +132,6 @@ namespace Ryujinx.HLE.HOS
PerformanceState = new PerformanceState();
NfpDevices = [];
NfcDevices = [];
// Note: This is not really correct, but with HLE of services, the only memory
// region used that is used is Application, so we can use the other ones for anything.
@@ -246,21 +242,21 @@ namespace Ryujinx.HLE.HOS
public void InitializeServices()
{
SmRegistry = new SmRegistry();
SmServer = new ServerBase(KernelContext, "Sm", () => new IUserInterface(KernelContext, SmRegistry));
SmServer = new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext, SmRegistry));
// Wait until SM server thread is done with initialization,
// only then doing connections to SM is safe.
SmServer.InitDone.WaitOne();
BsdServer = new ServerBase(KernelContext, "Bsd");
FsServer = new ServerBase(KernelContext, "Fs");
HidServer = new ServerBase(KernelContext, "Hid");
NvDrvServer = new ServerBase(KernelContext, "Nv");
TimeServer = new ServerBase(KernelContext, "Time");
ViServer = new ServerBase(KernelContext, "Vi:u");
ViServerM = new ServerBase(KernelContext, "Vi:m");
ViServerS = new ServerBase(KernelContext, "Vi:s");
LdnServer = new ServerBase(KernelContext, "Ldn");
BsdServer = new ServerBase(KernelContext, "BsdServer");
FsServer = new ServerBase(KernelContext, "FsServer");
HidServer = new ServerBase(KernelContext, "HidServer");
NvDrvServer = new ServerBase(KernelContext, "NvservicesServer");
TimeServer = new ServerBase(KernelContext, "TimeServer");
ViServer = new ServerBase(KernelContext, "ViServerU");
ViServerM = new ServerBase(KernelContext, "ViServerM");
ViServerS = new ServerBase(KernelContext, "ViServerS");
LdnServer = new ServerBase(KernelContext, "LdnServer");
StartNewServices();
}
@@ -286,7 +282,7 @@ namespace Ryujinx.HLE.HOS
ProcessCreationFlags.Is64Bit |
ProcessCreationFlags.PoolPartitionSystem;
ProcessCreationInfo creationInfo = new(service.Name, 1, 0, 0x8000000, 1, Flags, 0, 0);
ProcessCreationInfo creationInfo = new("Service", 1, 0, 0x8000000, 1, Flags, 0, 0);
uint[] defaultCapabilities =
[
@@ -376,15 +372,6 @@ namespace Ryujinx.HLE.HOS
}
}
public void ScanSkylander(int nfcDeviceId, byte[] data)
{
if (NfcDevices[nfcDeviceId].State == NfcDeviceState.SearchingForTag)
{
NfcDevices[nfcDeviceId].State = NfcDeviceState.TagFound;
NfcDevices[nfcDeviceId].Data = data;
}
}
public bool SearchingForAmiibo(out int nfpDeviceId)
{
nfpDeviceId = default;
@@ -402,53 +389,6 @@ namespace Ryujinx.HLE.HOS
return false;
}
public bool SearchingForSkylander(out int nfcDeviceId)
{
nfcDeviceId = default;
for (int i = 0; i < NfcDevices.Count; i++)
{
if (NfcDevices[i].State == NfcDeviceState.SearchingForTag)
{
nfcDeviceId = i;
return true;
}
}
return false;
}
public bool HasSkylander(out int nfcDeviceId)
{
nfcDeviceId = default;
for (int i = 0; i < NfcDevices.Count; i++)
{
if (NfcDevices[i].State == NfcDeviceState.TagFound)
{
nfcDeviceId = i;
return true;
}
}
return false;
}
public void RemoveSkylander()
{
for (int i = 0; i < NfcDevices.Count; i++)
{
if (NfcDevices[i].State == NfcDeviceState.TagFound)
{
NfcDevices[i].State = NfcDeviceState.Initialized;
NfcDevices[i].SignalDeactivate();
Thread.Sleep(100); // NOTE: Simulate skylander scanning delay.
}
}
}
public void SignalDisplayResolutionChange()
{
DisplayResolutionChangeEvent.ReadableEvent.Signal();

View File

@@ -118,11 +118,8 @@ namespace Ryujinx.HLE.HOS.Services.Caps
}
// NOTE: The saved JPEG file doesn't have the limitation in the extra EXIF data.
using SKBitmap bitmap = new(new SKImageInfo(1280, 720, SKColorType.Rgba8888, SKAlphaType.Premul));
int dataLen = screenshotData.Length > bitmap.ByteCount ? bitmap.ByteCount : screenshotData.Length;
Marshal.Copy(screenshotData, 0, bitmap.GetPixels(), dataLen);
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);

View File

@@ -56,7 +56,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid
_activeCount = 0;
JoyHold = NpadJoyHoldType.Vertical;
SixAxisActive = false;
}
internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player)
@@ -581,29 +580,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid
return needUpdateRight;
}
public bool isAtRest(int playerNumber)
{
ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[playerNumber].InternalState;
if (currentNpad.StyleSet == NpadStyleTag.None)
{
return true; // it will always be at rest because it cannot move.
}
ref SixAxisSensorState storage = ref GetSixAxisSensorLifo(ref currentNpad, false).GetCurrentEntryRef();
float acceleration = Math.Abs(storage.Acceleration.X)
+ Math.Abs(storage.Acceleration.Y)
+ Math.Abs(storage.Acceleration.Z);
float angularVelocity = Math.Abs(storage.AngularVelocity.X)
+ Math.Abs(storage.AngularVelocity.Y)
+ Math.Abs(storage.AngularVelocity.Z);
// TODO: check against config deadzone and add sensitivity setting
return ((acceleration <= 1.0F) && (angularVelocity <= 1.0F));
}
private void UpdateDisconnectedInputSixAxis(PlayerIndex index)
{

View File

@@ -602,33 +602,19 @@ namespace Ryujinx.HLE.HOS.Services.Hid
}
[CommandCmif(82)]
// IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAtRest
// IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAsRest
public ResultCode IsSixAxisSensorAtRest(ServiceCtx context)
{
int sixAxisSensorHandle = context.RequestData.ReadInt32();
// 4 byte struct w/ 4-byte alignment
// uint typeValue = (uint) sixAxisSensorHandle; // 0x0 0x4 TypeValue
// uint npadStyleIndex = (uint) sixAxisSensorHandle & 0xff; // 0x0 0x1 NpadStyleIndex
int playerNumber = (sixAxisSensorHandle << 8) & 0xff; // 0x1 0x1 PlayerNumber
// uint deviceIdx= ((uint) sixAxisSensorHandle << 16) & 0xff; // 0x2 0x1 DeviceIdx
// uint unknown = ((uint) sixAxisSensorHandle << 24) & 0xff;
// 32bit sign extension padding -> if = 0, + offset, else - offset
// npadStyleIndex = ((npadStyleIndex & 0x8000) == 0) ? npadStyleIndex | 0xFFFF0000 : npadStyleIndex & 0xFFFF0000;
// playerNumber = ((playerNumber & 0x8000) == 0) ? playerNumber | 0xFFFF0000 : playerNumber & 0xFFFF0000;
// deviceIdx = ((deviceIdx & 0x8000) == 0) ? deviceIdx | 0xFFFF0000 : deviceIdx & 0xFFFF0000;
// unknown = ((unknown & 0x8000) == 0) ? unknown | 0xFFFF0000 : unknown & 0xFFFF0000;
context.RequestData.BaseStream.Position += 4; // Padding
long appletResourceUserId = context.RequestData.ReadInt64();
// TODO: link to context.Device.Hid.Npads.SixAxisActive when properly implemented
// We currently do not support stopping or starting SixAxisTracking.
context.ResponseData.Write(context.Device.Hid.Npads.isAtRest(playerNumber));
bool isAtRest = true;
context.ResponseData.Write(isAtRest);
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, isAtRest });
return ResultCode.Success;
}
@@ -643,7 +629,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
context.ResponseData.Write(_isFirmwareUpdateAvailableForSixAxisSensor);
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _isFirmwareUpdateAvailableForSixAxisSensor });
return ResultCode.Success;
}

View File

@@ -1,19 +1,8 @@
using Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager;
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare
{
[Service("nfc:mf:u")]
class IUserManager : IpcService
{
public IUserManager(ServiceCtx context) { }
[CommandCmif(0)]
// CreateUserInterface() -> object<nn::nfc::mf::IUser>
public ResultCode CreateUserInterface(ServiceCtx context)
{
MakeObject(context, new IMifare());
return ResultCode.Success;
}
}
}

View File

@@ -1,477 +0,0 @@
using Ryujinx.Common.Memory;
using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Hid;
using Ryujinx.HLE.HOS.Services.Hid.HidServer;
using Ryujinx.Horizon.Common;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
class IMifare : IpcService
{
private State _state;
private KEvent _availabilityChangeEvent;
private CancellationTokenSource _cancelTokenSource;
public IMifare()
{
_state = State.NonInitialized;
}
[CommandCmif(0)]
public ResultCode Initialize(ServiceCtx context)
{
_state = State.Initialized;
NfcDevice devicePlayer1 = new()
{
NpadIdType = NpadIdType.Player1,
Handle = HidUtils.GetIndexFromNpadIdType(NpadIdType.Player1),
State = NfcDeviceState.Initialized,
};
context.Device.System.NfcDevices.Add(devicePlayer1);
return ResultCode.Success;
}
[CommandCmif(1)]
public ResultCode Finalize(ServiceCtx context)
{
if (_state == State.Initialized)
{
_cancelTokenSource?.Cancel();
// NOTE: All events are destroyed here.
context.Device.System.NfcDevices.Clear();
_state = State.NonInitialized;
}
return ResultCode.Success;
}
[CommandCmif(2)]
public ResultCode GetListDevices(ServiceCtx context)
{
if (context.Request.RecvListBuff.Count == 0)
{
return ResultCode.WrongArgument;
}
ulong outputPosition = context.Request.RecvListBuff[0].Position;
ulong outputSize = context.Request.RecvListBuff[0].Size;
if (context.Device.System.NfcDevices.Count == 0)
{
return ResultCode.DeviceNotFound;
}
MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
context.Memory.Write(outputPosition + ((uint)i * sizeof(long)), (uint)context.Device.System.NfcDevices[i].Handle);
}
context.ResponseData.Write(context.Device.System.NfcDevices.Count);
return ResultCode.Success;
}
[CommandCmif(3)]
public ResultCode StartDetection(ServiceCtx context)
{
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
{
context.Device.System.NfcDevices[i].State = NfcDeviceState.SearchingForTag;
break;
}
}
_cancelTokenSource = new CancellationTokenSource();
Task.Run(() =>
{
while (true)
{
if (_cancelTokenSource.Token.IsCancellationRequested)
{
break;
}
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagFound)
{
context.Device.System.NfcDevices[i].SignalActivate();
Thread.Sleep(125); // NOTE: Simulate skylander scanning delay.
break;
}
}
}
}, _cancelTokenSource.Token);
return ResultCode.Success;
}
[CommandCmif(4)]
public ResultCode StopDetection(ServiceCtx context)
{
_cancelTokenSource?.Cancel();
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
{
context.Device.System.NfcDevices[i].State = NfcDeviceState.Initialized;
Array.Clear(context.Device.System.NfcDevices[i].Data);
context.Device.System.NfcDevices[i].SignalDeactivate();
break;
}
}
return ResultCode.Success;
}
[CommandCmif(5)]
public ResultCode ReadMifare(ServiceCtx context)
{
if (context.Request.ReceiveBuff.Count == 0 || context.Request.SendBuff.Count == 0)
{
return ResultCode.WrongArgument;
}
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
if (context.Device.System.NfcDevices.Count == 0)
{
return ResultCode.DeviceNotFound;
}
ulong outputPosition = context.Request.ReceiveBuff[0].Position;
ulong outputSize = context.Request.ReceiveBuff[0].Size;
MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
ulong inputPosition = context.Request.SendBuff[0].Position;
ulong inputSize = context.Request.SendBuff[0].Size;
byte[] readBlockParameter = new byte[inputSize];
context.Memory.Read(inputPosition, readBlockParameter);
var span = MemoryMarshal.Cast<byte, NfcMifareReadBlockParameter>(readBlockParameter);
var list = new List<NfcMifareReadBlockParameter>(span.Length);
foreach (var item in span)
list.Add(item);
Thread.Sleep(125 * list.Count); // NOTE: Simulate skylander scanning delay.
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
{
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagRemoved)
{
return ResultCode.TagNotFound;
}
else
{
for (int p = 0; p < list.Count; p++)
{
NfcMifareReadBlockData blockData = new()
{
SectorNumber = list[p].SectorNumber,
Reserved = new Array7<byte>(),
};
byte[] data = new byte[16];
switch (list[p].SectorKey.MifareCommand)
{
case NfcMifareCommand.NfcMifareCommand_Read:
case NfcMifareCommand.NfcMifareCommand_AuthA:
if (IsCurrentBlockKeyBlock(list[p].SectorNumber))
{
Array.Copy(context.Device.System.NfcDevices[i].Data, (16 * list[p].SectorNumber) + 6, data, 6, 4);
}
else
{
Array.Copy(context.Device.System.NfcDevices[i].Data, 16 * list[p].SectorNumber, data, 0, 16);
}
data.CopyTo(blockData.Data.AsSpan());
context.Memory.Write(outputPosition + ((uint)(p * Unsafe.SizeOf<NfcMifareReadBlockData>())), blockData);
break;
}
}
}
}
}
return ResultCode.Success;
}
[CommandCmif(6)]
public ResultCode WriteMifare(ServiceCtx context)
{
if (context.Request.SendBuff.Count == 0)
{
return ResultCode.WrongArgument;
}
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
if (context.Device.System.NfcDevices.Count == 0)
{
return ResultCode.DeviceNotFound;
}
ulong inputPosition = context.Request.SendBuff[0].Position;
ulong inputSize = context.Request.SendBuff[0].Size;
byte[] writeBlockParameter = new byte[inputSize];
context.Memory.Read(inputPosition, writeBlockParameter);
var span = MemoryMarshal.Cast<byte, NfcMifareWriteBlockParameter>(writeBlockParameter);
var list = new List<NfcMifareWriteBlockParameter>(span.Length);
foreach (var item in span)
list.Add(item);
Thread.Sleep(125 * list.Count); // NOTE: Simulate skylander scanning delay.
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
{
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagRemoved)
{
return ResultCode.TagNotFound;
}
else
{
for (int p = 0; p < list.Count; p++)
{
switch (list[p].SectorKey.MifareCommand)
{
case NfcMifareCommand.NfcMifareCommand_Write:
case NfcMifareCommand.NfcMifareCommand_AuthA:
list[p].Data.AsSpan().CopyTo(context.Device.System.NfcDevices[i].Data.AsSpan(list[p].SectorNumber * 16, 16));
break;
}
}
}
}
}
return ResultCode.Success;
}
[CommandCmif(7)]
public ResultCode GetTagInfo(ServiceCtx context)
{
ResultCode resultCode = ResultCode.Success;
if (context.Request.RecvListBuff.Count == 0)
{
return ResultCode.WrongArgument;
}
ulong outputPosition = context.Request.RecvListBuff[0].Position;
context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf<TagInfo>());
MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf<TagInfo>());
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
if (context.Device.System.NfcDevices.Count == 0)
{
return ResultCode.DeviceNotFound;
}
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
{
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagRemoved)
{
resultCode = ResultCode.TagNotFound;
}
else
{
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagMounted || context.Device.System.NfcDevices[i].State == NfcDeviceState.TagFound)
{
TagInfo tagInfo = new()
{
UuidLength = 4,
Reserved1 = new Array21<byte>(),
Protocol = (uint)NfcProtocol.NfcProtocol_TypeA, // Type A Protocol
TagType = (uint)NfcTagType.NfcTagType_Mifare, // Mifare Type
Reserved2 = new Array6<byte>(),
};
byte[] uuid = new byte[4];
Array.Copy(context.Device.System.NfcDevices[i].Data, 0, uuid, 0, 4);
uuid.CopyTo(tagInfo.Uuid.AsSpan());
context.Memory.Write(outputPosition, tagInfo);
resultCode = ResultCode.Success;
}
else
{
resultCode = ResultCode.WrongDeviceState;
}
}
break;
}
}
return resultCode;
}
[CommandCmif(8)]
public ResultCode AttachActivateEvent(ServiceCtx context)
{
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if ((uint)context.Device.System.NfcDevices[i].Handle == deviceHandle)
{
context.Device.System.NfcDevices[i].ActivateEvent = new KEvent(context.Device.System.KernelContext);
if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfcDevices[i].ActivateEvent.ReadableEvent, out int activateEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(activateEventHandle);
return ResultCode.Success;
}
}
return ResultCode.DeviceNotFound;
}
[CommandCmif(9)]
public ResultCode AttachDeactivateEvent(ServiceCtx context)
{
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if ((uint)context.Device.System.NfcDevices[i].Handle == deviceHandle)
{
context.Device.System.NfcDevices[i].DeactivateEvent = new KEvent(context.Device.System.KernelContext);
if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfcDevices[i].DeactivateEvent.ReadableEvent, out int deactivateEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(deactivateEventHandle);
return ResultCode.Success;
}
}
return ResultCode.DeviceNotFound;
}
[CommandCmif(10)]
public ResultCode GetState(ServiceCtx context)
{
context.ResponseData.Write((int)_state);
return ResultCode.Success;
}
[CommandCmif(11)]
public ResultCode GetDeviceState(ServiceCtx context)
{
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if ((uint)context.Device.System.NfcDevices[i].Handle == deviceHandle)
{
if (context.Device.System.NfcDevices[i].State > NfcDeviceState.Finalized)
{
throw new InvalidOperationException($"{nameof(context.Device.System.NfcDevices)} contains an invalid state for device {i}: {context.Device.System.NfcDevices[i].State}");
}
context.ResponseData.Write((uint)context.Device.System.NfcDevices[i].State);
return ResultCode.Success;
}
}
context.ResponseData.Write((uint)NfcDeviceState.Unavailable);
return ResultCode.DeviceNotFound;
}
[CommandCmif(12)]
public ResultCode GetNpadId(ServiceCtx context)
{
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if ((uint)context.Device.System.NfcDevices[i].Handle == deviceHandle)
{
context.ResponseData.Write((uint)HidUtils.GetNpadIdTypeFromIndex(context.Device.System.NfcDevices[i].Handle));
return ResultCode.Success;
}
}
return ResultCode.DeviceNotFound;
}
[CommandCmif(13)]
public ResultCode AttachAvailabilityChangeEvent(ServiceCtx context)
{
_availabilityChangeEvent = new KEvent(context.Device.System.KernelContext);
if (context.Process.HandleTable.GenerateHandle(_availabilityChangeEvent.ReadableEvent, out int availabilityChangeEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(availabilityChangeEventHandle);
return ResultCode.Success;
}
private bool IsCurrentBlockKeyBlock(byte block)
{
return ((block + 1) % 4) == 0;
}
}
}

View File

@@ -1,21 +0,0 @@
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Hid;
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
class NfcDevice
{
public KEvent ActivateEvent;
public KEvent DeactivateEvent;
public void SignalActivate() => ActivateEvent.ReadableEvent.Signal();
public void SignalDeactivate() => DeactivateEvent.ReadableEvent.Signal();
public NfcDeviceState State = NfcDeviceState.Unavailable;
public PlayerIndex Handle;
public NpadIdType NpadIdType;
public byte[] Data;
}
}

View File

@@ -1,14 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
enum NfcMifareCommand : byte
{
NfcMifareCommand_Read = 0x30,
NfcMifareCommand_AuthA = 0x60,
NfcMifareCommand_AuthB = 0x61,
NfcMifareCommand_Write = 0xA0,
NfcMifareCommand_Transfer = 0xB0,
NfcMifareCommand_Decrement = 0xC0,
NfcMifareCommand_Increment = 0xC1,
NfcMifareCommand_Store = 0xC2,
}
}

View File

@@ -1,13 +0,0 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
[StructLayout(LayoutKind.Sequential, Size = 0x18)]
struct NfcMifareReadBlockData
{
public Array16<byte> Data;
public byte SectorNumber;
public Array7<byte> Reserved;
}
}

View File

@@ -1,13 +0,0 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
[StructLayout(LayoutKind.Sequential, Size = 0x18)]
struct NfcMifareReadBlockParameter
{
public byte SectorNumber;
public Array7<byte> Reserved;
public NfcSectorKey SectorKey;
}
}

View File

@@ -1,14 +0,0 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
[StructLayout(LayoutKind.Sequential, Size = 0x28)]
struct NfcMifareWriteBlockParameter
{
public Array16<byte> Data;
public byte SectorNumber;
public Array7<byte> Reserved;
public NfcSectorKey SectorKey;
}
}

View File

@@ -1,11 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
enum NfcProtocol : byte
{
NfcProtocol_None = 0b_0000_0000,
NfcProtocol_TypeA = 0b_0000_0001, ///< ISO14443A
NfcProtocol_TypeB = 0b_0000_0010, ///< ISO14443B
NfcProtocol_TypeF = 0b_0000_0100, ///< Sony FeliCa
NfcProtocol_All = 0xFF,
}
}

View File

@@ -1,16 +0,0 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
struct NfcSectorKey
{
public NfcMifareCommand MifareCommand;
public byte Unknown;
public Array6<byte> Reserved1;
public Array6<byte> SectorKey;
public Array2<byte> Reserved2;
}
}

View File

@@ -1,15 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
enum NfcTagType : byte
{
NfcTagType_None = 0b_0000_0000,
NfcTagType_Type1 = 0b_0000_0001, ///< ISO14443A RW. Topaz
NfcTagType_Type2 = 0b_0000_0010, ///< ISO14443A RW. Ultralight, NTAGX, ST25TN
NfcTagType_Type3 = 0b_0000_0100, ///< ISO14443A RW/RO. Sony FeliCa
NfcTagType_Type4A = 0b_0000_1000, ///< ISO14443A RW/RO. DESFire
NfcTagType_Type4B = 0b_0001_0000, ///< ISO14443B RW/RO. DESFire
NfcTagType_Type5 = 0b_0010_0000, ///< ISO15693 RW/RO. SLI, SLIX, ST25TV
NfcTagType_Mifare = 0b_0100_0000, ///< Mifare clasic. Skylanders
NfcTagType_All = 0xFF,
}
}

View File

@@ -1,13 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
enum NfcDeviceState : byte
{
Initialized = 0,
SearchingForTag = 1,
TagFound = 2,
TagRemoved = 3,
TagMounted = 4,
Unavailable = 5,
Finalized = 6,
}
}

View File

@@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
enum State
{
NonInitialized,
Initialized,
}
}

View File

@@ -1,16 +0,0 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
[StructLayout(LayoutKind.Sequential, Size = 0x58)]
struct TagInfo
{
public Array10<byte> Uuid;
public byte UuidLength;
public Array21<byte> Reserved1;
public uint Protocol;
public uint TagType;
public Array6<byte> Reserved2;
}
}

View File

@@ -1,17 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare
{
public enum ResultCode
{
ModuleId = 161,
ErrorCodeShift = 9,
Success = 0,
DeviceNotFound = (64 << ErrorCodeShift) | ModuleId, // 0x80A1
WrongArgument = (65 << ErrorCodeShift) | ModuleId, // 0x82A1
WrongDeviceState = (73 << ErrorCodeShift) | ModuleId, // 0x92A1
NfcDisabled = (80 << ErrorCodeShift) | ModuleId, // 0xA0A1
TagNotFound = (97 << ErrorCodeShift) | ModuleId, // 0xC2A1
MifareAccessError = (288 << ErrorCodeShift) | ModuleId, // 0x240a1
}
}

View File

@@ -79,15 +79,9 @@ namespace Ryujinx.HLE.HOS.Services
ProcessCreationFlags.Is64Bit |
ProcessCreationFlags.PoolPartitionSystem;
ProcessCreationInfo creationInfo = new(Name, 1, 0, 0x8000000, 1, Flags, 0, 0);
ProcessCreationInfo creationInfo = new("Service", 1, 0, 0x8000000, 1, Flags, 0, 0);
KernelStatic.StartInitialProcess(context, creationInfo, DefaultCapabilities, 44, () =>
{
var currentThread = KernelStatic.GetCurrentThread();
currentThread.HostThread.Name = $"{{{Name}}}";
Main();
});
KernelStatic.StartInitialProcess(context, creationInfo, DefaultCapabilities, 44, Main);
}
private void AddPort(int serverPortHandle, Func<IpcService> objectFactory)

View File

@@ -17,12 +17,13 @@ namespace Ryujinx.HLE.HOS.Services.Sm
private static readonly Dictionary<string, Type> _services;
private readonly SmRegistry _registry;
private ServerBase _commonServer;
private readonly ServerBase _commonServer;
private bool _isInitialized;
public IUserInterface(KernelContext context, SmRegistry registry) : base(registerTipc: true)
{
_commonServer = new ServerBase(context, "CommonServer");
_registry = registry;
}
@@ -96,11 +97,6 @@ namespace Ryujinx.HLE.HOS.Services.Sm
IpcService service = GetServiceInstance(type, context, serviceAttribute.Parameter);
if (_commonServer is null)
{
_commonServer = new ServerBase(context.Device.System.KernelContext, "Common");
}
service.TrySetServer(_commonServer);
service.Server.AddSessionObj(session.ServerSession, service);
}
@@ -257,7 +253,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm
public override void DestroyAtExit()
{
_commonServer?.Dispose();
_commonServer.Dispose();
base.DestroyAtExit();
}

View File

@@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Ssl
public ISslService(ServiceCtx context) { }
[CommandCmif(0)]
// CreateContext(nn::ssl::sf::SslVersion, u64 pid_placeholder, pid) -> object<nn::ssl::sf::ISslContext>
// CreateContext(nn::ssl::sf::SslVersion, u64, pid) -> object<nn::ssl::sf::ISslContext>
public ResultCode CreateContext(ServiceCtx context)
{
SslVersion sslVersion = (SslVersion)context.RequestData.ReadUInt32();
@@ -126,18 +126,14 @@ namespace Ryujinx.HLE.HOS.Services.Ssl
}
[CommandCmif(100)]
// CreateContextForSystem(nn::ssl::sf::SslVersion, u64 pid_placeholder, pid) -> object<nn::ssl::sf::ISslContextForSystem>
// CreateContextForSystem(u64 pid, nn::ssl::sf::SslVersion, u64)
public ResultCode CreateContextForSystem(ServiceCtx context)
{
ulong pid = context.RequestData.ReadUInt64();
SslVersion sslVersion = (SslVersion)context.RequestData.ReadUInt32();
#pragma warning disable IDE0059 // Remove unnecessary value assignment
ulong pidPlaceholder = context.RequestData.ReadUInt64();
#pragma warning restore IDE0059
// Note: We use ISslContext here instead of ISslContextForSystem class because Ryujinx implements both in one class.
MakeObject(context, new ISslContext(context.Request.HandleDesc.PId, sslVersion));
Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { sslVersion });
Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { pid, sslVersion, pidPlaceholder });
return ResultCode.Success;
}

View File

@@ -20,7 +20,5 @@ namespace Ryujinx.HLE.HOS.SystemState
SimplifiedChinese,
TraditionalChinese,
BrazilianPortuguese,
Polish,
Thai,
}
}

View File

@@ -23,9 +23,7 @@ namespace Ryujinx.HLE.HOS.SystemState
"es-419",
"zh-Hans",
"zh-Hant",
"pt-BR",
"pl",
"th"
"pt-BR"
];
internal long DesiredKeyboardLayout { get; private set; }

View File

@@ -18,7 +18,5 @@ namespace Ryujinx.HLE.HOS.SystemState
TraditionalChinese,
SimplifiedChinese,
BrazilianPortuguese,
Polish,
Thai,
}
}

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,15 +27,10 @@ namespace Ryujinx.HLE.Loaders.Processes
private ulong _latestPid;
public ProcessResult? ActiveApplication
public ProcessResult ActiveApplication
{
get
{
return _processesByPid.GetValueOrDefault(_latestPid);
// Using this if statement locks up the UI and prevents a new game from loading.
// Haven't quite deduced why yet.
if (!_processesByPid.TryGetValue(_latestPid, out ProcessResult value))
throw new RyujinxException(
$"The HLE Process map did not have a process with ID {_latestPid}. Are you missing firmware?");
@@ -150,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))

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

@@ -1,19 +1,12 @@
using Ryujinx.Common.Memory;
using System;
using System.Buffers.Binary;
using System.IO;
using System.IO.Compression;
using System.Runtime.InteropServices;
using System.Text;
namespace Ryujinx.Horizon.Sdk.Ns
{
public struct ApplicationControlProperty
{
/// <summary>
/// Use <see cref="Title"/> to access titles instead of accessing them directly.
/// </summary>
public Array16<ApplicationTitle> TitleBlock;
public Array16<ApplicationTitle> Title;
public Array37<byte> Isbn;
public StartupUserAccountValue StartupUserAccount;
public UserAccountSwitchLockValue UserAccountSwitchLock;
@@ -65,10 +58,7 @@ namespace Ryujinx.Horizon.Sdk.Ns
public RepairFlagValue RepairFlag;
public byte ProgramIndex;
public RequiredNetworkServiceLicenseOnLaunchValue RequiredNetworkServiceLicenseOnLaunchFlag;
public byte ApplicationErrorCodePrefix;
public TitleCompressionValue TitleCompression;
public byte AcdIndex;
public byte ApparentPlatform;
public Array4<byte> Reserved3214;
public ApplicationNeighborDetectionClientConfiguration NeighborDetectionClientConfiguration;
public ApplicationJitConfiguration JitConfiguration;
public RequiredAddOnContentsSetBinaryDescriptor RequiredAddOnContentsSetBinaryDescriptors;
@@ -84,47 +74,6 @@ namespace Ryujinx.Horizon.Sdk.Ns
public readonly string DisplayVersionString => Encoding.UTF8.GetString(DisplayVersion.AsSpan()).TrimEnd('\0');
public readonly string ApplicationErrorCodeCategoryString => Encoding.UTF8.GetString(ApplicationErrorCodeCategory.AsSpan()).TrimEnd('\0');
public readonly string BcatPassphraseString => Encoding.UTF8.GetString(BcatPassphrase.AsSpan()).TrimEnd('\0');
private const int TitleCount = 32;
private const int TitleEntrySize = 0x300;
/// <summary>
/// Returns the resolved title entries. When <see cref="TitleCompression"/> is
/// <see cref="TitleCompressionValue.Enable"/>, the raw <see cref="TitleBlock"/> bytes are
/// decompressed (raw deflate) from 0x3000 into 0x6000 bytes yielding up to 32 entries.
/// Otherwise the 16 uncompressed entries from <see cref="TitleBlock"/> are returned directly.
/// </summary>
public readonly ApplicationTitle[] Title
{
get
{
var titles = new ApplicationTitle[TitleCount];
if (TitleCompression != TitleCompressionValue.Enable)
{
TitleBlock.AsSpan().CopyTo(titles);
return titles;
}
ReadOnlySpan<byte> titleBytes = MemoryMarshal.AsBytes(TitleBlock.AsSpan());
ushort compressedBlobSize = BinaryPrimitives.ReadUInt16LittleEndian(titleBytes);
ReadOnlySpan<byte> compressedBlob = titleBytes.Slice(2, compressedBlobSize);
byte[] decompressed = new byte[TitleCount * TitleEntrySize];
using (var compressedStream = new MemoryStream(compressedBlob.ToArray()))
using (var deflateStream = new DeflateStream(compressedStream, CompressionMode.Decompress))
{
deflateStream.ReadExactly(decompressed, 0, decompressed.Length);
}
MemoryMarshal.Cast<byte, ApplicationTitle>(decompressed).CopyTo(titles);
return titles;
}
}
public struct ApplicationTitle
{
@@ -181,8 +130,6 @@ namespace Ryujinx.Horizon.Sdk.Ns
TraditionalChinese = 13,
SimplifiedChinese = 14,
BrazilianPortuguese = 15,
Polish = 16,
Thai = 17,
}
public enum Organization
@@ -355,11 +302,5 @@ namespace Ryujinx.Horizon.Sdk.Ns
Deny = 0,
Allow = 1,
}
public enum TitleCompressionValue : byte
{
Disable = 0,
Enable = 1,
}
}
}

View File

@@ -9,14 +9,12 @@ namespace Ryujinx.Horizon
private readonly Action<ServiceTable> _entrypoint;
private readonly ServiceTable _serviceTable;
private readonly HorizonOptions _options;
public readonly string Name;
internal ServiceEntry(Action<ServiceTable> entrypoint, ServiceTable serviceTable, HorizonOptions options, string name)
internal ServiceEntry(Action<ServiceTable> entrypoint, ServiceTable serviceTable, HorizonOptions options)
{
_entrypoint = entrypoint;
_serviceTable = serviceTable;
_options = options;
Name = name;
}
public void Start(ISyscallApi syscallApi, IVirtualMemoryManager addressSpace, IThreadContext threadContext)

View File

@@ -37,7 +37,7 @@ namespace Ryujinx.Horizon
void RegisterService<T>() where T : IService
{
entries.Add(new ServiceEntry(T.Main, this, options, typeof(T).Name));
entries.Add(new ServiceEntry(T.Main, this, options));
}
RegisterService<ArpMain>();

View File

@@ -151,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;
}

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

@@ -563,7 +563,7 @@ namespace Ryujinx.Input.HLE
float low = Math.Min(1f, (float)((rightVibrationValue.AmplitudeLow * 0.85 + rightVibrationValue.AmplitudeHigh * 0.15) * controllerConfig.Rumble.StrongRumble));
float high = Math.Min(1f, (float)((leftVibrationValue.AmplitudeLow * 0.15 + leftVibrationValue.AmplitudeHigh * 0.85) * controllerConfig.Rumble.WeakRumble));
_gamepad?.Rumble(low, high, uint.MaxValue);
_gamepad.Rumble(low, high, uint.MaxValue);
Logger.Debug?.Print(LogClass.Hid, $"Effect for {controllerConfig.PlayerIndex} " +
$"L.low.amp={leftVibrationValue.AmplitudeLow}, " +

View File

@@ -10,7 +10,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Ryujinx.SDL3-CS" />
<PackageReference Include="ppy.SDL3-CS" />
</ItemGroup>
</Project>

View File

@@ -10,7 +10,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect
public void EnsureTypeSize()
{
Assert.AreEqual(0x18, Unsafe.SizeOf<BiquadFilterEffectParameter1>());
Assert.AreEqual(0x28, Unsafe.SizeOf<BiquadFilterEffectParameter2>());
Assert.AreEqual(0x24, Unsafe.SizeOf<BiquadFilterEffectParameter2>());
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -141,7 +141,7 @@ namespace Ryujinx.Ava.Input
AvaKey.OemComma,
AvaKey.OemPeriod,
AvaKey.OemQuestion,
AvaKey.OemPipe,
AvaKey.OemBackslash,
// NOTE: invalid
AvaKey.None

View File

@@ -42,7 +42,6 @@ namespace Ryujinx.Ava
public static bool PreviewerDetached { get; private set; }
public static bool UseHardwareAcceleration { get; private set; }
public static string BackendThreadingArg { get; private set; }
public static bool CoreDumpArg { get; private set; }
private const uint MbIconwarning = 0x30;
@@ -82,10 +81,8 @@ namespace Ryujinx.Ava
bool noGuiArg = ConsumeCommandLineArgument(ref args, "--no-gui") || ConsumeCommandLineArgument(ref args, "nogui");
bool coreDumpArg = ConsumeCommandLineArgument(ref args, "--core-dumps");
CoreDumpArg = coreDumpArg;
// TODO: Ryujinx causes core dumps on Linux when it exits "uncleanly", eg. through an unhandled exception.
// This is undesirable and causes very odd behavior during development (the process stops responding,
// This is undesirable and causes very odd behavior during development (the process stops responding,
// the .NET debugger freezes or suddenly detaches, /tmp/ gets filled etc.), unless explicitly requested by the user.
// This needs to be investigated, but calling prctl() is better than modifying system-wide settings or leaving this be.
if (!coreDumpArg)
@@ -242,7 +239,7 @@ namespace Ryujinx.Ava
ConfigurationPath = appDataConfigurationPath;
}
}
if (ConfigurationPath == null)
{
// No configuration, we load the default values and save it to disk
@@ -313,28 +310,28 @@ namespace Ryujinx.Ava
_ => ConfigurationState.Instance.HideCursor,
};
// Check if memoryManagerMode was overridden.
// Check if memoryManagerMode was overridden.
if (CommandLineState.OverrideMemoryManagerMode is not null)
if (Enum.TryParse(CommandLineState.OverrideMemoryManagerMode, true, out MemoryManagerMode result))
{
ConfigurationState.Instance.System.MemoryManagerMode.Value = result;
}
// Check if PPTC was overridden.
// Check if PPTC was overridden.
if (CommandLineState.OverridePPTC is not null)
if (Enum.TryParse(CommandLineState.OverridePPTC, true, out bool result))
{
ConfigurationState.Instance.System.EnablePtc.Value = result;
}
// Check if region was overridden.
// Check if region was overridden.
if (CommandLineState.OverrideSystemRegion is not null)
if (Enum.TryParse(CommandLineState.OverrideSystemRegion, true, out Region result))
{
ConfigurationState.Instance.System.Region.Value = result;
}
//Check if language was overridden.
//Check if language was overridden.
if (CommandLineState.OverrideSystemLanguage is not null)
if (Enum.TryParse(CommandLineState.OverrideSystemLanguage, true, out Language result))
{

View File

@@ -28,6 +28,11 @@
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode>
</PropertyGroup>
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'win-arm64'">
<PublishSingleFile>true</PublishSingleFile>
<PublishTrimmed>false</PublishTrimmed>
</PropertyGroup>
<!--
FluentAvalonia, used in the Avalonia UI, requires a workaround for the json serializer used internally when using .NET 8+ System.Text.Json.
@@ -46,11 +51,10 @@
<PackageReference Include="Avalonia.Diagnostics" Condition="'$(Configuration)'=='Debug'" />
<PackageReference Include="Avalonia.Controls.DataGrid" />
<PackageReference Include="Avalonia.Markup.Xaml.Loader" />
<PackageReference Include="SharpCompress" />
<PackageReference Include="Svg.Controls.Avalonia" />
<PackageReference Include="Svg.Controls.Skia.Avalonia" />
<PackageReference Include="DynamicData" />
<PackageReference Include="FluentAvaloniaUI" />
<PackageReference Include="FluentAvaloniaUI.NoAnim" />
<PackageReference Include="CommandLineParser" />
<PackageReference Include="CommunityToolkit.Mvvm" />
<PackageReference Include="DiscordRichPresence" />
@@ -58,7 +62,7 @@
<PackageReference Include="Projektanker.Icons.Avalonia.FontAwesome" />
<PackageReference Include="Projektanker.Icons.Avalonia.MaterialDesign" />
<PackageReference Include="OpenTK.Core" />
<PackageReference Include="Ryujinx.Audio.OpenAL" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" />
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'win-arm64'" />
<PackageReference Include="Ryujinx.UpdateClient" />
@@ -69,6 +73,7 @@
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
<PackageReference Include="SPB" />
<PackageReference Include="SharpZipLib" />
</ItemGroup>
<ItemGroup>
@@ -169,8 +174,9 @@
<EmbeddedResource Include="Assets\UIImages\Logo_Amiibo.png" />
<EmbeddedResource Include="Assets\UIImages\Logo_Discord_Dark.png" />
<EmbeddedResource Include="Assets\UIImages\Logo_Discord_Light.png" />
<EmbeddedResource Include="Assets\UIImages\Logo_GitLab_Dark.png" />
<EmbeddedResource Include="Assets\UIImages\Logo_GitLab_Light.png" />
<EmbeddedResource Include="Assets\UIImages\Logo_Ryujinx.png" />
<EmbeddedResource Include="Assets\UIImages\Logo_Forgejo.png" />
<EmbeddedResource Include="Assets\UIImages\Logo_Ryujinx_AntiAlias.png" />
</ItemGroup>
<ItemGroup>

View File

@@ -62,7 +62,7 @@ using VSyncMode = Ryujinx.Common.Configuration.VSyncMode;
namespace Ryujinx.Ava.Systems
{
internal class AppHost : IDisposable
internal class AppHost
{
private const int CursorHideIdleTime = 5; // Hide Cursor seconds.
private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping.
@@ -438,7 +438,7 @@ namespace Ryujinx.Ava.Systems
SaveBitmapAsPng(bitmapToSave, path);
Logger.Notice.Print(LogClass.Application, $"Screenshot saved to '{path}'.", "Screenshot");
Logger.Notice.Print(LogClass.Application, $"Screenshot saved to {path}", "Screenshot");
}
});
}
@@ -611,40 +611,27 @@ namespace Ryujinx.Ava.Systems
_isActive = false;
// NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose.
// We only need to wait for all commands submitted during the main gpu loop to be processed.
_gpuDoneEvent.WaitOne();
_gpuDoneEvent.Dispose();
DisplaySleep.Restore();
NpadManager.Dispose();
TouchScreenManager.Dispose();
Device.Dispose();
// NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose.
// We only need to wait for all commands submitted during the main gpu loop to be processed.
// If the GPU has no work and is cancelled, we need to handle that as well.
WaitHandle.WaitAny(new[] { _gpuDoneEvent, _gpuCancellationTokenSource.Token.WaitHandle });
_gpuCancellationTokenSource.Dispose();
// Waiting for work to be finished before we dispose.
if (_renderingStarted)
{
Device.Gpu.WaitUntilGpuReady();
}
_gpuDoneEvent.Dispose();
DisposeGpu();
AppExit?.Invoke(this, EventArgs.Empty);
}
// MUST be public to inherit from IDisposable
public void Dispose()
private void Dispose()
{
if (Device.Processes != null)
{
MainWindowViewModel.UpdateGameMetadata(Device.Processes.ActiveApplication?.ProgramIdText,
_playTimer.Elapsed);
}
MainWindowViewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText, _playTimer.Elapsed);
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState;
ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState;
@@ -659,6 +646,7 @@ namespace Ryujinx.Ava.Systems
_topLevel.PointerExited -= TopLevel_PointerExited;
_gpuCancellationTokenSource.Cancel();
_gpuCancellationTokenSource.Dispose();
_chrono.Stop();
_playTimer.Stop();
@@ -684,12 +672,6 @@ namespace Ryujinx.Ava.Systems
}
else
{
// No use waiting on something that never started work
if (_renderingStarted)
{
Device.Gpu.WaitUntilGpuReady();
}
Device.DisposeGpu();
}
}
@@ -704,7 +686,7 @@ namespace Ryujinx.Ava.Systems
_cursorState = CursorStates.ForceChangeCursor;
}
public async Task LoadGuestApplication(CancellationTokenSource cts, BlitStruct<ApplicationControlProperty>? customNacpData = null)
public async Task<bool> LoadGuestApplication(BlitStruct<ApplicationControlProperty>? customNacpData = null)
{
DiscordIntegrationModule.GuestAppStartedAt = Timestamps.Now;
@@ -733,8 +715,7 @@ namespace Ryujinx.Ava.Systems
await UserErrorDialog.ShowUserErrorDialog(userError);
Device.Dispose();
cts.Cancel();
throw new OperationCanceledException(cts.Token);
return false;
}
}
@@ -743,11 +724,10 @@ namespace Ryujinx.Ava.Systems
await UserErrorDialog.ShowUserErrorDialog(userError);
Device.Dispose();
cts.Cancel();
throw new OperationCanceledException(cts.Token);
return false;
}
// Tell the user that we installed firmware for them.
// Tell the user that we installed a firmware for them.
if (userError is UserError.NoFirmware)
{
firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
@@ -767,8 +747,7 @@ namespace Ryujinx.Ava.Systems
await UserErrorDialog.ShowUserErrorDialog(userError);
Device.Dispose();
cts.Cancel();
throw new OperationCanceledException(cts.Token);
return false;
}
}
}
@@ -783,8 +762,7 @@ namespace Ryujinx.Ava.Systems
{
Device.Dispose();
cts.Cancel();
throw new OperationCanceledException(cts.Token);
return false;
}
}
else if (Directory.Exists(ApplicationPath))
@@ -804,24 +782,20 @@ namespace Ryujinx.Ava.Systems
if (!Device.LoadCart(ApplicationPath, romFsFiles[0]))
{
await ContentDialogHelper.CreateErrorDialog(
"Please specify an unpacked game directory with a valid exefs or NSO/NRO.");
Device.Dispose();
cts.Cancel();
throw new OperationCanceledException(cts.Token);
return false;
}
}
else
{
Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
if (!Device.LoadCart(ApplicationPath))
{
await ContentDialogHelper.CreateErrorDialog(
"Please specify an unpacked game directory with a valid exefs or NSO/NRO.");
Device.Dispose();
cts.Cancel();
throw new OperationCanceledException(cts.Token);
return false;
}
}
}
@@ -839,8 +813,7 @@ namespace Ryujinx.Ava.Systems
{
Device.Dispose();
cts.Cancel();
throw new OperationCanceledException(cts.Token);
return false;
}
break;
@@ -853,8 +826,7 @@ namespace Ryujinx.Ava.Systems
{
Device.Dispose();
cts.Cancel();
throw new OperationCanceledException(cts.Token);
return false;
}
break;
@@ -868,8 +840,7 @@ namespace Ryujinx.Ava.Systems
{
Device.Dispose();
cts.Cancel();
throw new OperationCanceledException(cts.Token);
return false;
}
break;
@@ -884,8 +855,7 @@ namespace Ryujinx.Ava.Systems
{
Device.Dispose();
cts.Cancel();
throw new OperationCanceledException(cts.Token);
return false;
}
}
catch (ArgumentOutOfRangeException)
@@ -894,8 +864,7 @@ namespace Ryujinx.Ava.Systems
Device.Dispose();
cts.Cancel();
throw new OperationCanceledException(cts.Token);
return false;
}
break;
@@ -904,18 +873,19 @@ namespace Ryujinx.Ava.Systems
}
else
{
Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NSO/NRO file.");
Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
Device.Dispose();
cts.Cancel();
throw new OperationCanceledException(cts.Token);
return false;
}
ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText,
appMetadata => appMetadata.UpdatePreGame()
);
_playTimer.Start();
return true;
}
internal void Resume()
@@ -925,7 +895,7 @@ namespace Ryujinx.Ava.Systems
_viewModel.IsPaused = false;
_playTimer.Start();
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI);
Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed.");
Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed");
}
internal void Pause()
@@ -935,7 +905,7 @@ namespace Ryujinx.Ava.Systems
_viewModel.IsPaused = true;
_playTimer.Stop();
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI, LocaleManager.Instance[LocaleKeys.Paused]);
Logger.Info?.Print(LogClass.Emulation, "Emulation was paused.");
Logger.Info?.Print(LogClass.Emulation, "Emulation was paused");
}
private void InitEmulatedSwitch()
@@ -1134,9 +1104,7 @@ namespace Ryujinx.Ava.Systems
// Make sure all commands in the run loop are fully executed before leaving the loop.
if (Device.Gpu.Renderer is ThreadedRenderer threaded)
{
Logger.Info?.PrintMsg(LogClass.Gpu, "Flushing threaded commands...");
threaded.FlushThreadedCommands();
Logger.Info?.PrintMsg(LogClass.Gpu, "Flushed!");
}
_gpuDoneEvent.Set();

View File

@@ -849,8 +849,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
foreach (ApplicationData installedApplication in Applications.Items)
{
// this should always exist... should...
temporary += LoadAndSaveMetaData(installedApplication.IdString).Value.TimePlayed;
temporary += LoadAndSaveMetaData(installedApplication.IdString).TimePlayed;
}
TotalTimePlayed = temporary;
@@ -1160,22 +1159,15 @@ namespace Ryujinx.Ava.Systems.AppLibrary
ApplicationCountUpdated?.Invoke(null, e);
}
public static Gommon.Optional<ApplicationMetadata> LoadAndSaveMetaData(string titleId, Action<ApplicationMetadata> modifyFunction = null)
public static ApplicationMetadata LoadAndSaveMetaData(string titleId, Action<ApplicationMetadata> modifyFunction = null)
{
if (titleId is null)
{
Logger.Warning?.PrintMsg(LogClass.Application, "Cannot save metadata because title ID is invalid.");
return null;
}
string metadataFolder = Path.Combine(AppDataManager.GamesDirPath, titleId, "gui");
string metadataFile = Path.Combine(metadataFolder, "metadata.json");
ApplicationMetadata appMetadata;
if (!File.Exists(metadataFile))
{
Logger.Info?.Print(LogClass.Application, $"Metadata file does not exist. Creating metadata for {titleId}...");
Directory.CreateDirectory(metadataFolder);
appMetadata = new ApplicationMetadata();
@@ -1185,12 +1177,12 @@ namespace Ryujinx.Ava.Systems.AppLibrary
try
{
Logger.Debug?.Print(LogClass.Application, $"Deserializing metadata for {titleId}...");
appMetadata = JsonHelper.DeserializeFromFile(metadataFile, _serializerContext.ApplicationMetadata);
}
catch (JsonException)
{
Logger.Warning?.Print(LogClass.Application, $"Failed to parse metadata json for {titleId}. Loading defaults.");
appMetadata = new ApplicationMetadata();
}
@@ -1412,7 +1404,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
if (string.IsNullOrWhiteSpace(data.Name))
{
foreach (ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
foreach (ref readonly ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
{
if (!controlTitle.NameString.IsEmpty())
{
@@ -1425,7 +1417,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
if (string.IsNullOrWhiteSpace(data.Developer))
{
foreach (ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
foreach (ref readonly ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
{
if (!controlTitle.PublisherString.IsEmpty())
{

View File

@@ -24,8 +24,6 @@ namespace Ryujinx.Ava.Systems.Configuration.System
SimplifiedChinese,
TraditionalChinese,
BrazilianPortuguese,
Polish,
Thai,
}
public static class LanguageEnumHelper

View File

@@ -82,7 +82,7 @@ namespace Ryujinx.Ava.Systems
public static void Use(Optional<string> titleId)
{
if (titleId.TryGet(out string tid) && Switch.Shared.Processes.ActiveApplication is not null)
if (titleId.TryGet(out string tid))
SwitchToPlayingState(
ApplicationLibrary.LoadAndSaveMetaData(tid),
Switch.Shared.Processes.ActiveApplication

View File

@@ -91,7 +91,7 @@ namespace Ryujinx.Ava.Systems
}
// If build URL not found, assume no new update is available.
if (string.IsNullOrEmpty(_versionResponse.ArtifactUrl))
if (_versionResponse.ArtifactUrl is null or "")
{
if (showVersionUpToDate)
{
@@ -123,8 +123,6 @@ namespace Ryujinx.Ava.Systems
return default;
}
_connectionCount = (int)_versionResponse.MaxConcurrency;
return (currentVersion, newVersion);
}
}

View File

@@ -1,20 +1,19 @@
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using Gommon;
using ICSharpCode.SharpZipLib.GZip;
using ICSharpCode.SharpZipLib.Tar;
using ICSharpCode.SharpZipLib.Zip;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.Utilities;
using Ryujinx.Common;
using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging;
using SharpCompress.Archives;
using SharpCompress.Compressors.Xz;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Formats.Tar;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
@@ -22,6 +21,7 @@ using System.Net.NetworkInformation;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -32,8 +32,8 @@ namespace Ryujinx.Ava.Systems
private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory;
private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish");
private const int ConnectionCount = 4;
private static int _connectionCount = 1;
private static long _buildSize;
private static bool _updateSuccessful;
private static bool _running;
@@ -73,6 +73,27 @@ namespace Ryujinx.Ava.Systems
return;
}
// Fetch build size information to learn chunk sizes.
using HttpClient buildSizeClient = ConstructHttpClient();
try
{
buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0");
// GitLab instance is located in Ukraine. Connection times will vary across the world.
buildSizeClient.Timeout = TimeSpan.FromSeconds(10);
HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_versionResponse.ArtifactUrl), HttpCompletionOption.ResponseHeadersRead);
_buildSize = message.Content.Headers.ContentRange.Length.Value;
}
catch (Exception ex)
{
Logger.Warning?.Print(LogClass.Application, ex.Message);
Logger.Warning?.Print(LogClass.Application, "Couldn't determine build size for update, using single-threaded updater");
_buildSize = -1;
}
await Dispatcher.UIThread.InvokeAsync(async () =>
{
string newVersionString = ReleaseInformation.IsCanaryBuild
@@ -122,14 +143,6 @@ namespace Ryujinx.Ava.Systems
Directory.CreateDirectory(_updateDir);
// If we get a .zip url switch it to the preferred .7z file instead
// The update server still returns the .zip url by default for legacy support
downloadUrl = downloadUrl.Replace(".zip", ".7z");
// If we get a .tar.gz url switch it to the preferred .tar.xz file instead
// The update server still returns the .tar.gz url by default for legacy support
downloadUrl = downloadUrl.Replace(".tar.gz", ".tar.xz");
string updateFile = Path.Combine(_updateDir, "update.bin");
TaskDialog taskDialog = new()
@@ -140,27 +153,6 @@ namespace Ryujinx.Ava.Systems
ShowProgressBar = true,
XamlRoot = RyujinxApp.MainWindow,
};
// Fetch build size information to learn chunk sizes.
using HttpClient buildSizeClient = ConstructHttpClient();
try
{
buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0");
// Forgejo instance is located in Ukraine. Connection times will vary across the world.
buildSizeClient.Timeout = TimeSpan.FromSeconds(10);
HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_versionResponse.ArtifactUrl), HttpCompletionOption.ResponseHeadersRead);
_buildSize = message.Content.Headers.ContentRange.Length.Value;
}
catch (Exception ex)
{
Logger.Warning?.Print(LogClass.Application, ex.Message);
Logger.Warning?.Print(LogClass.Application, "Couldn't determine build size for update, using single-threaded updater");
_buildSize = -1;
}
taskDialog.Opened += (s, e) =>
{
@@ -242,22 +234,22 @@ namespace Ryujinx.Ava.Systems
private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile)
{
// Multi-Threaded Updater
long chunkSize = _buildSize / _connectionCount;
long remainderChunk = _buildSize % _connectionCount;
long chunkSize = _buildSize / ConnectionCount;
long remainderChunk = _buildSize % ConnectionCount;
int completedRequests = 0;
int totalProgressPercentage = 0;
int[] progressPercentage = new int[_connectionCount];
int[] progressPercentage = new int[ConnectionCount];
List<byte[]> list = new(_connectionCount);
List<WebClient> webClients = new(_connectionCount);
List<byte[]> list = new(ConnectionCount);
List<WebClient> webClients = new(ConnectionCount);
for (int i = 0; i < _connectionCount; i++)
for (int i = 0; i < ConnectionCount; i++)
{
list.Add([]);
}
for (int i = 0; i < _connectionCount; i++)
for (int i = 0; i < ConnectionCount; i++)
{
#pragma warning disable SYSLIB0014
// TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient.
@@ -266,7 +258,7 @@ namespace Ryujinx.Ava.Systems
webClients.Add(client);
if (i == _connectionCount - 1)
if (i == ConnectionCount - 1)
{
client.Headers.Add("Range", $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}");
}
@@ -283,7 +275,7 @@ namespace Ryujinx.Ava.Systems
Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage);
Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage);
taskDialog.SetProgressBarState(totalProgressPercentage / _connectionCount, TaskDialogProgressState.Normal);
taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal);
};
client.DownloadDataCompleted += (_, args) =>
@@ -302,10 +294,10 @@ namespace Ryujinx.Ava.Systems
list[index] = args.Result;
Interlocked.Increment(ref completedRequests);
if (Equals(completedRequests, _connectionCount))
if (Equals(completedRequests, ConnectionCount))
{
byte[] mergedFileBytes = new byte[_buildSize];
for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < _connectionCount; connectionIndex++)
for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < ConnectionCount; connectionIndex++)
{
Array.Copy(list[connectionIndex], 0, mergedFileBytes, destinationOffset, list[connectionIndex].Length);
destinationOffset += list[connectionIndex].Length;
@@ -410,33 +402,73 @@ namespace Ryujinx.Ava.Systems
[SupportedOSPlatform("linux")]
[SupportedOSPlatform("macos")]
private static void ExtractTarGzipFile(string archivePath, string outputDirectoryPath)
private static void ExtractTarGzipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
{
using FileStream inStream = File.OpenRead(archivePath);
using GZipStream gzipStream = new(inStream, CompressionMode.Decompress);
TarFile.ExtractToDirectory(gzipStream, outputDirectoryPath, true);
}
[SupportedOSPlatform("linux")]
[SupportedOSPlatform("macos")]
private static void ExtractTarXzipFile(string archivePath, string outputDirectoryPath)
{
using FileStream inStream = File.OpenRead(archivePath);
using XZStream gzipStream = new(inStream);
TarFile.ExtractToDirectory(gzipStream, outputDirectoryPath, true);
using GZipInputStream gzipStream = new(inStream);
using TarInputStream tarStream = new(gzipStream, Encoding.ASCII);
TarEntry tarEntry;
while ((tarEntry = tarStream.GetNextEntry()) is not null)
{
if (tarEntry.IsDirectory)
{
continue;
}
string outPath = Path.Combine(outputDirectoryPath, tarEntry.Name);
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
using FileStream outStream = File.OpenWrite(outPath);
tarStream.CopyEntryContents(outStream);
File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode);
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
Dispatcher.UIThread.Post(() =>
{
if (tarEntry is null)
{
return;
}
taskDialog.SetProgressBarState(GetPercentage(tarEntry.Size, inStream.Length), TaskDialogProgressState.Normal);
});
}
}
private static void ExtractZipFile(string archivePath, string outputDirectoryPath)
private static void ExtractZipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
{
ZipFile.ExtractToDirectory(archivePath, outputDirectoryPath);
}
private static void Extract7ZipFile(string archivePath, string outputDirectoryPath)
{
IArchive archive = ArchiveFactory.OpenArchive(archivePath);
archive.WriteToDirectory(outputDirectoryPath);
using Stream inStream = File.OpenRead(archivePath);
using ZipFile zipFile = new(inStream);
double count = 0;
foreach (ZipEntry zipEntry in zipFile)
{
count++;
if (zipEntry.IsDirectory)
{
continue;
}
string outPath = Path.Combine(outputDirectoryPath, zipEntry.Name);
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
using Stream zipStream = zipFile.GetInputStream(zipEntry);
using FileStream outStream = File.OpenWrite(outPath);
zipStream.CopyTo(outStream);
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
Dispatcher.UIThread.Post(() =>
{
taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal);
});
}
}
private static void InstallUpdate(TaskDialog taskDialog, string updateFile)
@@ -447,20 +479,16 @@ namespace Ryujinx.Ava.Systems
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
ExtractTarXzipFile(updateFile, _updateDir);
ExtractTarGzipFile(taskDialog, updateFile, _updateDir);
}
else if (OperatingSystem.IsWindows())
{
Extract7ZipFile(updateFile, _updateDir);
ExtractZipFile(taskDialog, updateFile, _updateDir);
}
else
{
throw new NotSupportedException();
}
// The new decompression implementations don't have a way to show progress
// so the progressbar is just set to 100% after the decompression is done
taskDialog.SetProgressBarState(100, TaskDialogProgressState.Normal);
// Delete downloaded zip
File.Delete(updateFile);

View File

@@ -16,41 +16,31 @@
<Design.DataContext>
<viewModels:ProfileSelectorDialogViewModel />
</Design.DataContext>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RowDefinitions="*,Auto">
<Border
CornerRadius="5"
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
BorderThickness="1">
<Border Padding="-3" BorderThickness="0">
<ListBox
MaxHeight="300"
HorizontalAlignment="Stretch"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="Transparent"
ItemsSource="{Binding Profiles}"
SelectionChanged="ProfilesList_SelectionChanged">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel
HorizontalAlignment="Left"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal" />
Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Margin" Value="5 5 0 5" />
<Setter Property="Margin" Value="0" />
<Setter Property="CornerRadius" Value="5" />
</Style>
<Style Selector="Rectangle#SelectionIndicator">
<Setter Property="Opacity" Value="0" />
</Style>
</ListBox.Styles>
<ListBox.DataTemplates>
<DataTemplate
DataType="models:UserProfile">
@@ -58,6 +48,7 @@
PointerEntered="Grid_PointerEntered"
PointerExited="Grid_OnPointerExited">
<Border
Margin="5"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ClipToBounds="True"
@@ -69,37 +60,26 @@
<Image
Width="96"
Height="96"
Margin="0,0,0,10"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Source="{Binding Image, Converter={x:Static helpers:BitmapArrayValueConverter.Instance}}" />
<TextBlock
HorizontalAlignment="Stretch"
MaxWidth="90"
Text="{Binding Name}"
Height="30"
MaxWidth="90"
TextAlignment="Center"
TextWrapping="Wrap"
HorizontalAlignment="Center"
VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"
MaxLines="2"
Margin="5" />
TextWrapping="Wrap"
MaxLines="2" />
</StackPanel>
</Border>
</Grid>
</DataTemplate>
<DataTemplate
DataType="viewModels:BaseModel">
<Panel
Height="118"
Width="96">
<Panel.Styles>
<Style Selector="Panel">
<Setter Property="Background" Value="{DynamicResource ListBoxBackground}" />
</Style>
</Panel.Styles>
</Panel>
</DataTemplate>
</ListBox.DataTemplates>
</ListBox>
</Border>
</Grid>
</UserControl>

View File

@@ -94,7 +94,7 @@ namespace Ryujinx.Ava.UI.Applet
ContentDialog contentDialog = new()
{
Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle],
Title = LocaleManager.Instance[LocaleKeys.UserProfiles_WindowTitle],
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.Continue],
SecondaryButtonText = string.Empty,
CloseButtonText = LocaleManager.Instance[LocaleKeys.Cancel],

View File

@@ -9,11 +9,11 @@
Command="{Binding RunApplication}"
CommandParameter="{Binding}"
Header="{ext:Locale GameListContextMenuRunApplication}"
Icon="{ext:Icon fa-solid fa-play}"/>
Icon="{ext:Icon fa-solid fa-play}" />
<MenuItem
Command="{Binding ToggleFavorite}"
CommandParameter="{Binding}"
Header="{ext:Locale GameListContextMenuToggleFavorite}"
Header="{Binding FavoriteStatusText}"
Icon="{ext:Icon fa-solid fa-star}" />
<MenuItem
Command="{Binding CreateApplicationShortcut}"
@@ -30,15 +30,15 @@
ToolTip.Tip="{ext:Locale EditCustomConfigurationToolTip}" />
<MenuItem
Command="{Binding EditGameConfiguration}"
CommandParameter="{Binding}"
IsVisible="{Binding !SelectedApplication.HasIndependentConfiguration}"
Header="{ext:Locale GameListContextMenuCreateCustomConfiguration}"
Icon="{ext:Icon fa-solid fa-gear}"
ToolTip.Tip="{ext:Locale CreateCustomConfigurationToolTip}" />
CommandParameter="{Binding}"
IsVisible="{Binding !SelectedApplication.HasIndependentConfiguration}"
Header="{ext:Locale GameListContextMenuCreateCustomConfiguration}"
Icon="{ext:Icon fa-solid fa-gear}"
ToolTip.Tip="{ext:Locale CreateCustomConfigurationToolTip}" />
<MenuItem
IsVisible="{Binding HasCompatibilityEntry}"
Command="{Binding OpenApplicationCompatibility}"
CommandParameter="{Binding}"
IsVisible="{Binding HasCompatibilityEntry}"
Header="{ext:Locale GameListContextMenuShowCompatEntry}"
Icon="{ext:Icon fa-solid fa-database}"
ToolTip.Tip="{ext:Locale GameListContextMenuShowCompatEntryToolTip}"/>
@@ -104,8 +104,8 @@
Command="{Binding TrimXci}"
CommandParameter="{Binding}"
Header="{ext:Locale GameListContextMenuTrimXCI}"
IsEnabled="{Binding TrimXCIEnabled}"
Icon="{ext:Icon fa-solid fa-scissors}"
IsEnabled="{Binding TrimXCIEnabled}"
ToolTip.Tip="{ext:Locale GameListContextMenuTrimXCIToolTip}" />
<MenuItem Header="{ext:Locale GameListContextMenuCacheManagement}" Icon="{ext:Icon fa-solid fa-memory}">
<MenuItem
@@ -151,9 +151,9 @@
Header="{ext:Locale GameListContextMenuExtractDataRomFS}"
ToolTip.Tip="{ext:Locale GameListContextMenuExtractDataRomFSToolTip}" />
<MenuItem
IsVisible="{Binding HasDlc}"
Command="{Binding ExtractApplicationAocRomFs}"
CommandParameter="{Binding}"
IsVisible="{Binding HasDlc}"
Header="{ext:Locale GameListContextMenuExtractDataAocRomFS}"
ToolTip.Tip="{ext:Locale GameListContextMenuExtractDataAocRomFSToolTip}" />
<MenuItem

View File

@@ -71,7 +71,7 @@ namespace Ryujinx.Ava.UI.Controls
NavigationDialogHost content = new(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem, ownerHorizonClient);
ContentDialog contentDialog = new()
{
Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle],
Title = LocaleManager.Instance[LocaleKeys.UserProfiles_WindowTitle],
PrimaryButtonText = string.Empty,
SecondaryButtonText = string.Empty,
CloseButtonText = string.Empty,
@@ -156,7 +156,7 @@ namespace Ryujinx.Ava.UI.Controls
{
_ = Dispatcher.UIThread.InvokeAsync(async ()
=> await ContentDialogHelper.CreateErrorDialog(
LocaleManager.Instance[LocaleKeys.DialogUserProfileDeletionWarningMessage]));
LocaleManager.Instance[LocaleKeys.UserProfiles_DialogUserProfileDeletionWarningMessage]));
return;
}
@@ -165,8 +165,8 @@ namespace Ryujinx.Ava.UI.Controls
}
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogUserProfileDeletionConfirmMessage],
string.Empty,
LocaleManager.Instance[LocaleKeys.UserProfiles_DialogUserProfileDeletionConfirmMessage],
LocaleManager.Instance[LocaleKeys.IrreversibleActionNote],
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
string.Empty);

View File

@@ -57,15 +57,8 @@ namespace Ryujinx.Ava.UI.Models
}
else
{
Gommon.Optional<ApplicationMetadata> appMetadata = ApplicationLibrary.LoadAndSaveMetaData(TitleIdString);
if (appMetadata != null)
{
Title = appMetadata.Value.Title ?? TitleIdString;
}
else
{
Title = "<INVALID>";
}
ApplicationMetadata appMetadata = ApplicationLibrary.LoadAndSaveMetaData(TitleIdString);
Title = appMetadata.Title ?? TitleIdString;
}
Task.Run(() =>

View File

@@ -10,6 +10,9 @@ namespace Ryujinx.Ava.UI.Models
[ObservableProperty]
public partial byte[] Image { get; set; }
[ObservableProperty]
public partial bool FirmwareFound { get; set; }
[ObservableProperty]
public partial string Name { get; set; } = string.Empty;

View File

@@ -5,7 +5,6 @@ using Avalonia.Markup.Xaml;
using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.Threading;
using FluentAvalonia.Core;
using FluentAvalonia.UI.Windowing;
using Gommon;
using Ryujinx.Ava.Common.Locale;
@@ -54,9 +53,6 @@ namespace Ryujinx.Ava
{
Name = FormatTitle();
// Disable menu animations
FAUISettings.SetAnimationsEnabledAtAppLevel(false);
AvaloniaXamlLoader.Load(this);
if (OperatingSystem.IsMacOS())

View File

@@ -11,7 +11,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
public partial class AboutWindowViewModel : BaseModel, IDisposable
{
[ObservableProperty] public partial Bitmap ForgejoLogo { get; set; }
[ObservableProperty] public partial Bitmap GitLabLogo { get; set; }
[ObservableProperty] public partial Bitmap DiscordLogo { get; set; }
@@ -37,7 +37,6 @@ namespace Ryujinx.Ava.UI.ViewModels
}
private const string LogoPathFormat = "resm:Ryujinx.Assets.UIImages.Logo_{0}_{1}.png?assembly=Ryujinx";
private const string UnthemedLogoPathFormat = "resm:Ryujinx.Assets.UIImages.Logo_{0}.png?assembly=Ryujinx";
private void UpdateLogoTheme(string theme)
{
@@ -47,7 +46,7 @@ namespace Ryujinx.Ava.UI.ViewModels
string themeName = isDarkTheme ? "Dark" : "Light";
DiscordLogo = LoadBitmap(LogoPathFormat.Format("Discord", themeName));
ForgejoLogo = LoadBitmap(UnthemedLogoPathFormat.Format("Forgejo"));
GitLabLogo = LoadBitmap(LogoPathFormat.Format("GitLab", themeName));
}
private static Bitmap LoadBitmap(string uri) => new(Avalonia.Platform.AssetLoader.Open(new Uri(uri)));
@@ -56,7 +55,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
RyujinxApp.ThemeChanged -= Ryujinx_ThemeChanged;
ForgejoLogo.Dispose();
GitLabLogo.Dispose();
DiscordLogo.Dispose();
GC.SuppressFinalize(this);

View File

@@ -174,7 +174,6 @@ namespace Ryujinx.Ava.UI.ViewModels
private string _screenshotKey = "F8";
private float _volume;
private ApplicationData _currentApplicationData;
private bool _pendingRestart;
private readonly AutoResetEvent _rendererWaitEvent;
private int _customVSyncInterval;
private int _customVSyncIntervalPercentageProxy;
@@ -371,39 +370,6 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool CanScanAmiiboBinaries => AmiiboBinReader.HasAmiiboKeyFile;
public bool IsSkylanderRequested
{
get => field && _isGameRunning;
set
{
field = value;
OnPropertyChanged();
}
}
public bool HasSkylander
{
get => field && _isGameRunning;
set
{
field = value;
OnPropertyChanged();
}
}
public bool ShowSkylanderActions
{
get => field && _isGameRunning;
set
{
field = value;
OnPropertyChanged();
}
}
public bool ShowLoadProgress
{
get;
@@ -1063,7 +1029,7 @@ namespace Ryujinx.Ava.UI.ViewModels
string dialogMessage =
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallMessage);
if (ContentManager.AreKeysAlreadyPresent(systemDirectory))
if (ContentManager.AreKeysAlredyPresent(systemDirectory))
{
dialogMessage +=
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys
@@ -1251,14 +1217,6 @@ namespace Ryujinx.Ava.UI.ViewModels
await LoadApplication(_currentApplicationData);
}
else if (_pendingRestart)
{
_pendingRestart = false;
Logger.Info?.Print(LogClass.Application, $"Restarting emulation for '{_currentApplicationData.Name}'");
await LoadApplication(_currentApplicationData);
}
else
{
// Otherwise, clear state.
@@ -1267,21 +1225,6 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public void RestartEmulation()
{
if (AppHost is null || _currentApplicationData is null)
{
Logger.Warning?.Print(LogClass.Application, "RestartEmulation called but no application is running.");
return;
}
Logger.Info?.Print(LogClass.Application, $"Restart requested for '{_currentApplicationData.Name}'");
_pendingRestart = true;
AppHost.Stop();
}
private void Update_StatusBar(object sender, StatusUpdatedEventArgs args)
{
if (ShowMenuAndStatusBar && !ShowLoadProgress)
@@ -1760,6 +1703,11 @@ namespace Ryujinx.Ava.UI.ViewModels
Logger.RestartTime();
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(application.Path,
ConfigurationState.Instance.System.Language, application.Id);
PrepareLoadScreen();
RendererHostControl = new RendererHost();
AppHost = new AppHost(
@@ -1773,34 +1721,18 @@ namespace Ryujinx.Ava.UI.ViewModels
UserChannelPersistence,
this,
TopLevel);
CancellationTokenSource cts = new CancellationTokenSource();
try
if (!await AppHost.LoadGuestApplication(customNacpData))
{
await AppHost.LoadGuestApplication(cts, customNacpData);
}
catch (OperationCanceledException exception)
{
Logger.Info?.Print(LogClass.Application,
"LoadGuestApplication was interrupted !!! " + exception.Message);
AppHost.DisposeContext();
AppHost = null;
return;
}
finally
{
cts.Dispose();
}
CanUpdate = false;
application.Name ??= AppHost.Device.Processes.ActiveApplication.Name;
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(application.Path,
ConfigurationState.Instance.System.Language, application.Id);
PrepareLoadScreen();
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, application.Name);
@@ -1822,9 +1754,9 @@ namespace Ryujinx.Ava.UI.ViewModels
RendererHostControl.Focus();
});
public static void UpdateGameMetadata(string titleId, TimeSpan playTime)
public static void UpdateGameMetadata(string titleId, TimeSpan playTime)
=> ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => appMetadata.UpdatePostGame(playTime));
public void RefreshFirmwareStatus()
{
SystemVersion version = null;
@@ -1932,46 +1864,6 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
}
public async Task OpenSkylanderWindow()
{
if (AppHost.Device.System.SearchingForSkylander(out int deviceId))
{
Optional<IStorageFile> result = await StorageProvider.OpenSingleFilePickerAsync(
new FilePickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle],
FileTypeFilter = new List<FilePickerFileType>
{
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
{
Patterns = ["*.sky", "*.bin", "*.dmp", "*.dump"],
},
},
});
if (result.HasValue)
{
// Open reading stream from the first file.
await using var stream = await result.Value.OpenReadAsync();
using var streamReader = new BinaryReader(stream);
// Reads all the content of file as a text.
byte[] data = new byte[1024];
var count = streamReader.Read(data, 0, 1024);
if (count < 1024)
{
return;
}
else
{
AppHost.Device.System.ScanSkylander(deviceId, data);
}
}
}
}
public async Task RemoveSkylander()
{
AppHost.Device.System.RemoveSkylander();
}
public void ReloadRenderDocApi()
{
@@ -2225,6 +2117,8 @@ namespace Ryujinx.Ava.UI.ViewModels
}
);
public string FavoriteStatusText => SelectedApplication?.Favorite == false ? LocaleManager.Instance[LocaleKeys.GameListContextMenuAddToFavorites] : LocaleManager.Instance[LocaleKeys.GameListContextMenuRemoveFromFavorites];
public static RelayCommand<MainWindowViewModel> CreateApplicationShortcut { get; } =
Commands.CreateConditional<MainWindowViewModel>(vm => vm?.SelectedApplication != null,
viewModel => ShortcutHelper.CreateAppShortcut(

View File

@@ -28,6 +28,9 @@ namespace Ryujinx.Ava.UI.ViewModels
[ObservableProperty]
public partial ObservableCollection<ProfileImageModel> Images { get; set; }
[ObservableProperty]
public partial bool FirmwareFound { get; set; }
[ObservableProperty]
public partial Color BackgroundColor { get; set; } = Colors.White;
@@ -43,17 +46,15 @@ namespace Ryujinx.Ava.UI.ViewModels
};
}
private int _selectedIndex = -1;
public int SelectedIndex
{
get;
get => _selectedIndex;
set
{
field = value;
SelectedImage = field == -1
? null
: Images[field].Data;
_selectedIndex = value;
SelectedImage = value == -1 ? null : Images[value].Data;
OnPropertyChanged();
OnPropertyChanged(nameof(SelectedImage));
}

View File

@@ -1,10 +0,0 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace Ryujinx.Ava.UI.ViewModels
{
public partial class UserProfileImageSelectorViewModel : BaseModel
{
[ObservableProperty]
public partial bool FirmwareFound { get; set; }
}
}

View File

@@ -1,7 +1,7 @@
using Ryujinx.Ava.UI.Models;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Collections.Specialized;
namespace Ryujinx.Ava.UI.ViewModels
{
@@ -9,20 +9,35 @@ namespace Ryujinx.Ava.UI.ViewModels
{
public UserProfileViewModel()
{
Profiles = [];
LostProfiles = [];
IsEmpty = !LostProfiles.Any();
Profiles = new ObservableCollection<BaseModel>();
LostProfiles = new ObservableCollection<UserProfile>();
LostProfiles.CollectionChanged += LostProfilesChanged;
}
public ObservableCollection<BaseModel> Profiles { get; set; }
public ObservableCollection<BaseModel> Profiles { get; }
public ObservableCollection<UserProfile> LostProfiles { get; set; }
public ObservableCollection<UserProfile> LostProfiles { get; }
public bool IsEmpty { get; set; }
public bool IsEmpty => LostProfiles.Count == 0;
public void Dispose()
{
GC.SuppressFinalize(this);
LostProfiles.CollectionChanged -= LostProfilesChanged;
}
private void LostProfilesChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(nameof(IsEmpty));
}
public void UpdateLostProfiles(ObservableCollection<UserProfile> newProfiles)
{
LostProfiles.Clear();
foreach (var profile in newProfiles)
LostProfiles.Add(profile);
OnPropertyChanged(nameof(IsEmpty));
}
}
}

View File

@@ -27,7 +27,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private readonly AccountManager _accountManager;
public string SaveManagerHeading => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SaveManagerHeading, _accountManager.LastOpenedUser.Name, _accountManager.LastOpenedUser.UserId);
public string SaveManagerTitle => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UserProfiles_SaveManagerTitle, _accountManager.LastOpenedUser.Name);
public UserSaveManagerViewModel(AccountManager accountManager)
{

View File

@@ -122,8 +122,8 @@
Click="Button_OnClick"
CornerRadius="15"
Tag="https://src.ryujinx.app"
ToolTip.Tip="{ext:Locale AboutForgejoUrlTooltipMessage}">
<Image Source="{Binding ForgejoLogo}" />
ToolTip.Tip="{ext:Locale AboutGitLabUrlTooltipMessage}">
<Image Source="{Binding GitLabLogo}" />
</Button>
<Button
MinWidth="30"

View File

@@ -27,7 +27,7 @@ namespace Ryujinx.Ava.UI.Views.Dialog
{
PrimaryButtonText = string.Empty,
SecondaryButtonText = string.Empty,
CloseButtonText = LocaleManager.Instance[LocaleKeys.UserProfilesClose],
CloseButtonText = LocaleManager.Instance[LocaleKeys.SettingsButtonClose],
Content = new AboutView { ViewModel = viewModel }
};

View File

@@ -47,13 +47,18 @@
</StackPanel>
<StackPanel Orientation="Horizontal" IsEnabled="{Binding ShowLedColorPicker}">
<TextBlock MinWidth="75" MaxWidth="200" Text="{ext:Locale ControllerSettingsLedColor}" />
<ColorPicker
<ui:ColorPickerButton
Margin="5"
IsMoreButtonVisible="False"
UseColorPalette="False"
UseColorTriangle="False"
UseColorWheel="False"
ShowAcceptDismissButtons="False"
IsAlphaEnabled="False"
AttachedToVisualTree="ColorPicker_OnAttachedToVisualTree"
ColorChanged="ColorPicker_OnColorChanged"
AttachedToVisualTree="ColorPickerButton_OnAttachedToVisualTree"
ColorChanged="ColorPickerButton_OnColorChanged"
Color="{Binding LedColor, Mode=TwoWay}">
</ColorPicker>
</ui:ColorPickerButton>
</StackPanel>
</StackPanel>
</UserControl>

View File

@@ -1,5 +1,4 @@
using Avalonia;
using Avalonia.Controls;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
@@ -31,17 +30,19 @@ namespace Ryujinx.UI.Views.Input
InitializeComponent();
}
private void ColorPicker_OnColorChanged(object sender, ColorChangedEventArgs args)
private void ColorPickerButton_OnColorChanged(ColorPickerButton sender, ColorButtonColorChangedEventArgs args)
{
if (!args.NewColor.HasValue)
return;
if (!ViewModel.EnableLedChanging)
return;
if (ViewModel.TurnOffLed)
return;
ViewModel.ParentModel.SelectedGamepad.SetLed(args.NewColor.ToUInt32());
ViewModel.ParentModel.SelectedGamepad.SetLed(args.NewColor.Value.ToUInt32());
}
private void ColorPicker_OnAttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
private void ColorPickerButton_OnAttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
{
if (!ViewModel.EnableLedChanging)
return;

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