Compare commits

...

65 Commits

Author SHA1 Message Date
GreemDev
b7dd718d6f use lambda-based config modifier insstead of manually setting a bool to true 2025-12-22 00:13:53 -06:00
GreemDev
6ee7957574 use a helper to get key path instead of checking mode & userpath existing every time 2025-12-19 23:15:23 -06:00
GreemDev
bf62531802 unused property 2025-12-19 23:15:23 -06:00
GreemDev
17be50ea80 Update title with page 2025-12-19 23:15:23 -06:00
GreemDev
ec50a1ec3e rename markup imported xml namespace to ext to match the rest of the codebase 2025-12-19 23:15:23 -06:00
GreemDev
5a20047e5e forgot to string.Format gamedir page desc 2025-12-19 23:15:23 -06:00
GreemDev
f9fed4cf4d make page desc smaller 2025-12-19 23:15:23 -06:00
GreemDev
2970dcd3c7 Localize all (I think...) previously hardcoded english strings in the setup wizard 2025-12-19 23:15:23 -06:00
GreemDev
4be6cb2fa1 oops 2025-12-19 23:15:23 -06:00
GreemDev
c90d2af9cd game dir setup
known bugs are a missing prod.keys popup after setup (how), as well as the dialog for autoload kinda cluttering up the screen after you hit next on the game dir page
2025-12-19 23:15:23 -06:00
GreemDev
13ff9cb162 Separate firmware avatar loading from the selector view model
This is going to be used for the setup wizard (probably)
2025-12-19 23:15:23 -06:00
GreemDev
b35ba58831 rewrite EmbeddedAvaloniaResources 2025-12-19 23:15:23 -06:00
GreemDev
e12a77d4a3 add a setup finished screen
added the ability to hide the help button (basically just for the finish screen, because it has a bigger discord button in the same place)
holding shift while opening the setup wizard now opens it in passive mode, aka it will install only what you need. this is mostly for testing and likely will be nuked before this code as a whole is made part of the official emulator, but it might not
2025-12-19 23:15:23 -06:00
GreemDev
804a4e0bcb reduce logo crunching 2025-12-19 23:15:23 -06:00
GreemDev
94870eafaa further simplify pagebuilding by embedding the desired title locale key in the context base type 2025-12-19 23:15:23 -06:00
GreemDev
7e6cc31866 cleanup usings 2025-12-19 23:15:23 -06:00
GreemDev
3b25c43abf reorganize RyujinxSetupWizard
additionally, the CreateHelpContent method is now no longer locked to returning an Avalonia `Control`.
the WithHelpContent method also has logic to handle it returning a string directly, and will wrap it in a textblock with h1 style and size 20 font. otherwise it's up to the ContentPresenter for the help content to choose how to display it if it's none of the above mentioned types.
2025-12-19 23:15:23 -06:00
GreemDev
1804dd031b oops
i left in my very professional logger debugging
2025-12-19 23:15:23 -06:00
GreemDev
211498e060 Overhaul setup wizard help pages
the context can now override a virtual method named `CreateHelpContent` which the setup wizard system will automatically try to use when you use the generic overload taking a generic context type. If the return is null, it skips setting entirely (the default impl is null)

additionally made the discord join link a button with code copied from the about window, and made it centered at the bottom.
2025-12-19 23:15:23 -06:00
GreemDev
4bdee89288 small cleanup 2025-12-19 23:15:23 -06:00
GreemDev
d8a6364cca rename NotificationHelper to RyujinxNotificationManager,
rename instance method names.
Additionally clarified what the math is in the notification manager margin parameter.
2025-12-19 23:15:23 -06:00
GreemDev
2f794794c6 use the margin to force it to show bottom center
(boy i sure do hope this doesnt have any adverse effects on anything but my specific resolution & scaling configuration!)
2025-12-19 23:15:23 -06:00
GreemDev
1d6c2426df OOPS broke the setup wizard :3 2025-12-19 23:15:23 -06:00
GreemDev
6cd03f15fa cleanup 2025-12-19 23:15:23 -06:00
GreemDev
3fe7600382 add "overwrite mode" for the setup wizard, basically this just ignores the precondition of having whatever the page configures before showing it.
i.e. if you had keys installed, previously it'd skip right to firmware.

additionally added more customization to the now instance-based NotificationHelper
2025-12-19 23:15:23 -06:00
GreemDev
dc2aa837b3 Setup Wizard restructuring
- Remove polymorphic base, this only existed because TKMM has a desktop/switch setup prodecure difference and has 2 implementations of the setup wizard. We only need one.
- Remove Systems/UI file split, they're all in Ryujinx.Ava.UI now
- made NotificationHelper instance-based to allow you to encapsulate notifications to a window that magically disappear when the window is closed, instead of switching to showing on the main window.
2025-12-19 23:15:22 -06:00
GreemDev
133ac41425 Bake setup step logic into the view models themselves instead of being in the setup wizard implementation
renamed view models to contexts (like TKMM), however the contexts here are actually of a unique base type; containing aforementioned setup step logic. if the return value is of an error state result, it will prompt a retry of the page.
2025-12-19 23:15:22 -06:00
GreemDev
fd2ecee479 fix "could not find part of path" error when installing firmware 2025-12-19 23:15:22 -06:00
GreemDev
8f529d17a8 combine SetupWizardPage and the builder type since the builder mutated an instance of the built type anyways 2025-12-19 23:15:22 -06:00
GreemDev
884d0f526c treat configuration load fail as first start (so you're prompted to set the game/autoload dirs, when that step is implemented) 2025-12-19 23:15:22 -06:00
GreemDev
c5b325bde2 add a setup wizard opener in the help dropdown in the menu bar, that also respects CanShowSetupWizard 2025-12-19 23:15:22 -06:00
GreemDev
8ab851ead8 move more of the setup wizard logic into the setup wizard itself instead of having some critical logic in a random lambda in MainWindow.axaml.cs 2025-12-19 23:15:22 -06:00
GreemDev
5a060cf451 fixup namespaces (again) 2025-12-19 23:15:22 -06:00
GreemDev
9b0fa3bf6d content & viewmodel object creation helper with out param, touch up firmware install step 2025-12-19 23:15:22 -06:00
GreemDev
325e13a490 fix: require valid key installations before moving onto firmware setup step 2025-12-19 23:15:22 -06:00
GreemDev
e202cccc6e firmware stage 2025-12-19 23:15:22 -06:00
GreemDev
e0ed8f56ea cleanup 2025-12-19 23:15:22 -06:00
GreemDev
46b2fb92d7 more namespace fixes 2025-12-19 23:15:22 -06:00
GreemDev
8563e7d4dc use a custom key install function with notifications instead of the normal one with dialogs 2025-12-19 23:15:22 -06:00
GreemDev
ee10cbf735 cleanup 2025-12-19 23:15:22 -06:00
GreemDev
b033adbde7 Initial work on a setup wizard
Setup wizard abstraction & architecture from TKMM
2025-12-19 23:15:22 -06:00
GreemDev
66f339d265 CI 2.0 (ryubing/ryujinx!237)
See merge request ryubing/ryujinx!237
2025-12-18 22:56:50 -06:00
GreemDev
6cdbdfd329 [ci skip] Pin GitLabCli to 1.4.1 in CI scripts so I can test v2.0 2025-12-18 03:27:43 -06:00
GreemDev
9f817d60d5 oops 2025-12-18 03:05:42 -06:00
GreemDev
5cffc95be6 Make all OSes build on Linux (7zip has a linux version) 2025-12-18 03:01:22 -06:00
LotP
2c0977f6b3 fix pre-action crash (ryubing/ryujinx!236)
See merge request ryubing/ryujinx!236
2025-12-12 14:28:54 -06:00
LotP
3a593b6084 Fix kaddressarbiter crash (ryubing/ryujinx!235)
See merge request ryubing/ryujinx!235
2025-12-06 20:16:43 -06:00
LotP
c3155fcadb Memory Changes 3.2 (ryubing/ryujinx!234)
See merge request ryubing/ryujinx!234
2025-12-06 17:19:19 -06:00
LotP
fd7554425a Update BiquadFilterEffectParameter2.cs (ryubing/ryujinx!233)
See merge request ryubing/ryujinx!233
2025-12-05 07:53:09 -06:00
Princess Piplup
52700f71dc Fix SaveCurrentScreenshot (ryubing/ryujinx!230)
See merge request ryubing/ryujinx!230
2025-12-04 17:35:17 -06:00
Alula
b018a44ff0 Disable coredumps by default on Linux + other minor fixes (ryubing/ryujinx!204)
See merge request ryubing/ryujinx!204
2025-12-04 17:32:26 -06:00
GreemDev
d522bfef62 fix: Force the key install helper to delete key files before copying (not sure why the overwrite boolean does nothing for File.Copy) 2025-12-02 21:23:06 -06:00
KeatonTheBot
39f55b2af3 cpu: Protect against stack overflow caused by deep recursion (ryubing/ryujinx!111)
See merge request ryubing/ryujinx!111
2025-11-19 20:50:23 -06:00
Goodfeat
6126e3dc1e fix: UI: Custom setting was reset to global when changed during gameplay. (ryubing/ryujinx!164)
See merge request ryubing/ryujinx!164
2025-11-19 20:33:35 -06:00
GreemDev
e1c829f91d We no longer offer a dedicated headless build. This script could have been deleted like a year ago lol 2025-11-17 22:47:23 -06:00
Babib3l
6c7dc7646b Translation updates (ryubing/ryujinx!221)
See merge request ryubing/ryujinx!221
2025-11-17 22:34:54 -06:00
GreemDev
862a686c5e UI: Improve "Show Changelog" button in the updater
Now it no longer causes the dialog to disappear (which then promptly re-appears so you can click yes/no to accept/deny the update)
2025-11-17 00:15:58 -06:00
GreemDev
e8751e1c40 more C# 14 partial properties 2025-11-16 19:14:26 -06:00
GreemDev
09748b140a Use the new C# 14 null propagation setter 2025-11-16 19:14:26 -06:00
Coxxs
456db46065 Stub IWriterForApplication: 0 (CreateContextRegistrar) (ryubing/ryujinx!183)
See merge request ryubing/ryujinx!183
2025-11-16 18:24:41 -06:00
GreemDev
7b39ff36c0 docs: compat: ZA LDN works and has for a while 2025-11-16 00:30:37 -06:00
Maki
10476771d3 fix: detect face button layout for gamepads (ryubing/ryujinx!219)
See merge request ryubing/ryujinx!219
2025-11-14 12:51:53 -06:00
Maki
c5082ac85a fix: make controller GUIDs match old SDL2 GUIDs (ryubing/ryujinx!218)
See merge request ryubing/ryujinx!218
2025-11-14 11:52:42 -06:00
GreemDev
5e86ad83cc feature: macOS Liquid Glass dynamic icon
No more squircle jail! Thanks @transistor.exe in the Ryubing discord for dropping the asset files for this to happen.
2025-11-13 20:47:05 -06:00
Coxxs
1baaa1c365 Preserve and rename the configuration file when it is deemed invalid (ryubing/ryujinx!216)
See merge request ryubing/ryujinx!216
2025-11-12 08:23:05 -06:00
146 changed files with 4191 additions and 1811 deletions

View File

@@ -1,4 +1,4 @@
name: Canary release job
name: Canary CI
on:
workflow_dispatch:
@@ -19,7 +19,6 @@ concurrency: release
env:
POWERSHELL_TELEMETRY_OPTOUT: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1
RYUJINX_BASE_VERSION: "1.3"
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "canary"
RELEASE: 1
@@ -30,8 +29,8 @@ jobs:
strategy:
matrix:
platform:
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
#- { name: win-arm64, os: windows-latest, zip_os_name: win_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:
@@ -44,12 +43,27 @@ jobs:
- name: Overwrite csc problem matcher
run: echo "::add-matcher::.github/csc.json"
- name: Install 7zip
run: |
sudo apt install -y 7zip
- name: Install gli
run: |
mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
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=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_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
echo "commit_message=$(git log -1 --pretty=%B)" >> $GITHUB_OUTPUT
shell: bash
- name: Configure for release
@@ -69,33 +83,20 @@ jobs:
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained
- name: Packing Windows builds
if: matrix.platform.os == 'windows-latest'
if: contains(matrix.platform.name, 'win')
run: |
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
popd
gh release download -R GreemDev/GLI -O gli.exe -p 'GitLabCli-win_x64.exe'
./gli.exe --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip"
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -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: Install GitLabCli
if: matrix.platform.os == 'ubuntu-latest'
run: |
mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
chmod +x gli
mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Packing Linux builds
if: matrix.platform.os == 'ubuntu-latest'
if: contains(matrix.platform.name, 'linux')
run: |
pushd publish
rm libarmeilleure-jitsupport.dylib
@@ -103,11 +104,11 @@ jobs:
tar -czvf ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
popd
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz"
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -p release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz
shell: bash
- name: Build AppImage (Linux)
if: matrix.platform.os == 'ubuntu-latest'
if: contains(matrix.platform.name, 'linux')
run: |
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
PLATFORM_NAME="${{ matrix.platform.name }}"
@@ -139,8 +140,8 @@ jobs:
pushd publish_appimage
mv Ryujinx.AppImage ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage
popd
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage"
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -p release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage
shell: bash
macos_release:
@@ -159,10 +160,10 @@ jobs:
chmod +x llvm.sh
sudo ./llvm.sh 17
- name: Install GitLabCli
- name: Install gli
run: |
mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
chmod +x gli
mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH
@@ -183,9 +184,10 @@ jobs:
- name: Get version info
id: version_info
run: |
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_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
run: |
@@ -200,7 +202,7 @@ 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 --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|publish_ava/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz"
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -p publish_ava/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz
create_gitlab_release:
name: Create GitLab Release
@@ -210,37 +212,42 @@ jobs:
- release
steps:
- uses: actions/checkout@v4
- name: Get version info
id: version_info
run: |
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
shell: bash
- name: Install GitLabCli
- name: Install gli
run: |
mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
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)" >> $GITHUB_OUTPUT
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $GITHUB_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
echo "commit_message=$(git log -1 --pretty=%B)" >> $GITHUB_OUTPUT
shell: bash
- name: Create tag
run: |
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=CreateTag "Canary-${{ steps.version_info.outputs.build_version }}|${{ steps.version_info.outputs.git_short_hash }}"
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 }} -c "${{ steps.version_info.outputs.commit_message }}"
- name: Create release
run: |
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=CreateReleaseFromGenericPackageFiles "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|main|Canary ${{ steps.version_info.outputs.build_version }}|**Full Changelog:** [${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}](https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }})"
gli create-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 --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=SendUpdateMessage "${{ steps.version_info.outputs.build_version }}|FF4500|${{ secrets.CANARY_DISCORD_WEBHOOK }}|https://avatars.githubusercontent.com/u/192939710?s=200&v=4|false"
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: |
curl 'https://update.ryujinx.app/api/v1/admin/refresh_cache?rc=canary' -X PATCH -H 'accept: */*' -H 'Authorization: ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }}'
gli refresh-version-cache -T ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }} -c Canary
- name: Advance to the next version
run: |
gli increment-version -T ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }} -c Canary

View File

@@ -1,224 +0,0 @@
name: Release job (Debug)
on:
workflow_dispatch:
inputs: {}
concurrency: release
env:
POWERSHELL_TELEMETRY_OPTOUT: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1
RYUJINX_BASE_VERSION: "1.3"
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "release"
RELEASE: 1
jobs:
release:
name: Release for ${{ matrix.platform.name }}
runs-on: ${{ matrix.platform.os }}
strategy:
matrix:
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 }
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 version info
id: version_info
run: |
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} + 10))" >> $GITHUB_OUTPUT
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
shell: bash
- name: Configure for release
run: |
sed -r --in-place 's/\%\%RYUJINX_BUILD_VERSION\%\%/${{ steps.version_info.outputs.build_version }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
- name: Create output dir
run: "mkdir release_output"
- name: Publish
run: |
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained
- name: Packing Windows builds
if: matrix.platform.os == 'windows-latest'
run: |
pushd publish
rm libarmeilleure-jitsupport.dylib
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
popd
gh release download -R GreemDev/GLI -O gli.exe -p 'GitLabCli-win_x64.exe'
./gli.exe --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip"
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install GitLabCli
if: matrix.platform.os == 'ubuntu-latest'
run: |
mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
chmod +x gli
mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Packing Linux builds
if: matrix.platform.os == 'ubuntu-latest'
run: |
pushd publish
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
popd
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz"
shell: bash
- name: Build AppImage (Linux)
if: matrix.platform.os == 'ubuntu-latest'
run: |
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
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
pushd publish_appimage
mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
mv Ryujinx.AppImage.zsync ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
popd
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage"
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync"
shell: bash
macos_release:
name: Release MacOS universal
runs-on: ubuntu-24.04
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 GitLabCli
run: |
mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
chmod +x gli
mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install rcodesign
run: |
mkdir -p $HOME/.bin
gh release download -R indygreg/apple-platform-rs -O apple-codesign.tar.gz -p 'apple-codesign-*-x86_64-unknown-linux-musl.tar.gz'
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 version info
id: version_info
run: |
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} + 10))" >> $GITHUB_OUTPUT
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
- name: Configure for release
run: |
sed -r --in-place 's/\%\%RYUJINX_BUILD_VERSION\%\%/${{ steps.version_info.outputs.build_version }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
- name: Publish macOS Ryujinx
run: |
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|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@v4
- name: Get version info
id: version_info
run: |
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} + 10))" >> $GITHUB_OUTPUT
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
shell: bash
- name: Install GitLabCli
run: |
mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
chmod +x gli
mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create release
run: |
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=CreateReleaseFromGenericPackageFiles "Ryubing|${{ steps.version_info.outputs.build_version }}|${{ steps.version_info.outputs.git_short_hash }}|test|THIS IS NOT INTENDED FOR END USER USAGE"

View File

@@ -1,15 +1,18 @@
name: Release job
name: Stable CI
on:
workflow_dispatch:
inputs: {}
inputs:
is_bugfix_release:
description: "Bug fix release: If checked, this will increment the third number for only Stable, and leave the Major version alone for both Stable and Canary."
required: true
type: boolean
concurrency: release
env:
POWERSHELL_TELEMETRY_OPTOUT: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1
RYUJINX_BASE_VERSION: "1.3"
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "release"
RELEASE: 1
@@ -20,8 +23,8 @@ jobs:
strategy:
matrix:
platform:
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
#- { name: win-arm64, os: windows-latest, zip_os_name: win_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:
@@ -33,12 +36,30 @@ jobs:
- name: Overwrite csc problem matcher
run: echo "::add-matcher::.github/csc.json"
- name: Install 7zip
run: |
sudo apt install -y 7zip
- name: Install gli
run: |
mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
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=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $GITHUB_OUTPUT
else
echo "build_version=$(gli get-next-version -c Stable -R)" >> $GITHUB_OUTPUT
fi
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $GITHUB_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
shell: bash
@@ -58,47 +79,34 @@ jobs:
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained
- name: Packing Windows builds
if: matrix.platform.os == 'windows-latest'
if: contains(matrix.platform.name, 'win')
run: |
pushd publish
rm libarmeilleure-jitsupport.dylib
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
popd
gh release download -R GreemDev/GLI -O gli.exe -p 'GitLabCli-win_x64.exe'
./gli.exe --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip"
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -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: Install GitLabCli
if: matrix.platform.os == 'ubuntu-latest'
run: |
mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
chmod +x gli
mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Packing Linux builds
if: matrix.platform.os == 'ubuntu-latest'
if: contains(matrix.platform.name, 'linux')
run: |
pushd publish
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
popd
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz"
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -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: matrix.platform.os == 'ubuntu-latest'
if: contains(matrix.platform.name, 'linux')
run: |
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
PLATFORM_NAME="${{ matrix.platform.name }}"
@@ -131,7 +139,7 @@ jobs:
mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
popd
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage"
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -p release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
shell: bash
macos_release:
@@ -150,10 +158,10 @@ jobs:
chmod +x llvm.sh
sudo ./llvm.sh 17
- name: Install GitLabCli
- name: Install gli
run: |
mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
chmod +x gli
mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH
@@ -174,9 +182,14 @@ jobs:
- name: Get version info
id: version_info
run: |
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $GITHUB_OUTPUT
else
echo "build_version=$(gli get-next-version -c Stable -R)" >> $GITHUB_OUTPUT
fi
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $GITHUB_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
shell: bash
- name: Configure for release
run: |
@@ -189,7 +202,8 @@ jobs:
- name: Publish macOS Ryujinx
run: |
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|publish/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz"
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -p publish/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz
create_gitlab_release:
name: Create GitLab Release
@@ -200,32 +214,45 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Get version info
id: version_info
run: |
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
shell: bash
- name: Install GitLabCli
- name: Install gli
run: |
mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
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)" >> $GITHUB_OUTPUT
else
echo "build_version=$(gli get-next-version -c Stable -R)" >> $GITHUB_OUTPUT
fi
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $GITHUB_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
echo "commit_message=$(git log -1 --pretty=%B)" >> $GITHUB_OUTPUT
shell: bash
- name: Create release
run: |
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=CreateReleaseFromGenericPackageFiles "Ryubing|${{ steps.version_info.outputs.build_version }}|${{ steps.version_info.outputs.git_short_hash }}|${{ steps.version_info.outputs.build_version }}|msd:${{ steps.version_info.outputs.build_version }}"
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 --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=SendUpdateMessage "${{ steps.version_info.outputs.build_version }}|32cd32|${{ secrets.STABLE_DISCORD_WEBHOOK }}|https://avatars.githubusercontent.com/u/192939710?s=200&v=4|false"
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: |
curl 'https://update.ryujinx.app/api/v1/admin/refresh_cache?rc=stable' -X PATCH -H 'accept: */*' -H 'Authorization: ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }}'
gli refresh-version-cache -T ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }} -c Stable
- name: Advance to the next version
run: |
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
gli advance-version -T ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }}
else
gli increment-version -T ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }} -c Stable
fi

3
.gitignore vendored
View File

@@ -18,6 +18,8 @@ build/
[Oo]bj/
AppDir/
publish_appimage/
publish_ava/
publish_tmp_ava/
# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
!packages/*/build/
@@ -98,6 +100,7 @@ DocProject/Help/html
# Click-Once directory
publish/
RyubingMaintainerTools/
# Publish Web Output
*.Publish.xml

View File

@@ -44,7 +44,7 @@
<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="Gommon" Version="2.8.0.2" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="Sep" Version="0.11.1" />
<PackageVersion Include="shaderc.net" Version="0.1.0" />
@@ -59,4 +59,4 @@
<PackageVersion Include="System.Management" Version="9.0.2" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
</ItemGroup>
</Project>
</Project>

View File

@@ -225,7 +225,7 @@
"el_GR": "Χωρίς Ελέγχους (γρηγορότερο, μη ασφαλές)",
"en_US": "Host Unchecked (Fastest, Unsafe)",
"es_ES": "Host Sin Verificación (Más rápido, Inseguro)",
"fr_FR": "Hôte Non Vérifié (plus rapide, non sécurisé)",
"fr_FR": "Hôte Non Vérifié (Plus Rapide, Non Sécurisé)",
"he_IL": "מארח לא מבוקר (המהיר ביותר, לא בטוח)",
"it_IT": "Host senza verifica (più veloce, non sicura)",
"ja_JP": "ホスト, チェックなし (最高速, 安全でない)",
@@ -3350,7 +3350,7 @@
"el_GR": "Ερώτημα",
"en_US": "Prompt",
"es_ES": "Al Inicio",
"fr_FR": "Demande",
"fr_FR": "Demander",
"he_IL": "הודעה",
"it_IT": "Domanda",
"ja_JP": "プロンプト",
@@ -4800,7 +4800,7 @@
"el_GR": "Backend Ήχου:",
"en_US": "Audio Backend:",
"es_ES": "Motor de Audio:",
"fr_FR": "Bibliothèque Audio :",
"fr_FR": "Moteur Audio :",
"he_IL": "אחראי שמע:",
"it_IT": "Backend audio:",
"ja_JP": "音声バックエンド:",
@@ -5700,7 +5700,7 @@
"el_GR": "Έκταση σε όλο το παράθυρο",
"en_US": "Stretch to Fit Window",
"es_ES": "Estirar a la Ventana",
"fr_FR": "Ajuster à la Taille de la Fenêtre",
"fr_FR": "Adapter à la Taille de la Fenêtre",
"he_IL": "מתח לגודל חלון",
"it_IT": "Adatta alla finestra",
"ja_JP": "ウインドウサイズに合わせる",
@@ -5750,7 +5750,7 @@
"el_GR": "Τοποθεσία Shaders Γραφικών:",
"en_US": "Graphics Shader Dump Path:",
"es_ES": "Directorio de Volcado de Sombreadores:",
"fr_FR": "Chemin du Dossier de Copie des Shaders :",
"fr_FR": "Chemin du dump des shaders graphiques :",
"he_IL": "",
"it_IT": "Percorso di dump degli shader:",
"ja_JP": "グラフィックス シェーダー ダンプパス:",
@@ -15000,7 +15000,7 @@
"el_GR": "Πολυνηματική Επεξεργασία Γραφικών:",
"en_US": "Graphics Backend Multithreading:",
"es_ES": "Multihilado del Motor Gráfico:",
"fr_FR": "Interface graphique multithread :",
"fr_FR": "Interface Graphique Multithread :",
"he_IL": "אחראי גרפיקה רב-תהליכי:",
"it_IT": "Multithreading del backend grafico:",
"ja_JP": "グラフィックスバックエンドのマルチスレッド実行:",
@@ -16598,9 +16598,9 @@
"ar_SA": "",
"de_DE": "Diese Option überspringt den Dialog 'Benutzerprofile verwalten' während des Spiels und verwendet ein voreingestelltes Profil.\n\nDie Profilumschaltung finden Sie unter 'Einstellungen' - 'Benutzerprofile verwalten'. Wählen Sie das gewünschte Profil aus, bevor Sie das Spiel laden.",
"el_GR": "Αυτή η επιλογή παρακάμπτει το παράθυρο διαλόγου 'Διαχειριστής Προφίλ Χρήστη' κατά τη διάρκεια του παιχνιδιού, χρησιμοποιώντας ένα προεπιλεγμένο προφίλ.\n\nΗ εναλλαγή προφίλ βρίσκεται στις 'Ρυθμίσεις' - 'Διαχειριστής Προφίλ Χρήστη'. Επιλέξτε το επιθυμητό προφίλ πριν φορτώσετε το παιχνίδι.",
"en_US": "This option skips the 'Manage User Profiles' dialog during gameplay, using a pre-selected profile.\n\nProfile switching is found in 'Settings' - 'Manager User Profiles'. Select the desired profile before loading the game.",
"es_ES": "Esta opción omite el diálogo de 'Gestionar perfiles de usuario' durante el juego, utilizando un perfil preseleccionado.\n\nEl cambio de perfil se encuentra en 'Configuración' - 'Gestionar perfiles de usuario'. Seleccione el perfil deseado antes de cargar el juego.",
"fr_FR": "Cette option permet d'éviter le dialogue du 'Gérer les profils d'utilisateurs' pendant le jeu, en utilisant un profil pré-sélectionné.\n\nLa sélection du profil se trouve dans 'Paramètres' - 'Gérer les profils d'utilisateurs'. Sélectionnez le profil souhaité avant de lancer le jeu.",
"en_US": "This option skips the 'Manage User Profiles' dialog during gameplay, using a pre-selected profile.\n\nProfile switching is found in 'Options' - 'User Profiles'. Select the desired profile before loading the game.",
"es_ES": "Esta opción omite el diálogo de 'Gestionar perfiles de usuario' durante el juego, utilizando un perfil preseleccionado.\n\nEl cambio de perfil se encuentra en 'Opciones' - 'Perfiles de Usuario'. Seleccione el perfil deseado antes de cargar el juego.",
"fr_FR": "Cette option permet de passer le dialogue 'Gérer les profils d'utilisateurs' pendant le jeu, en utilisant un profil pré-sélectionné.\n\nLa sélection du profil se trouve dans 'Options' - 'Profils d'Utilisateurs'. Sélectionnez le profil souhaité avant de lancer le jeu.",
"he_IL": "",
"it_IT": "Questa opzione salta la finestra di dialogo 'Gestisci i profili utente' durante il gioco, utilizzando un profilo pre-selezionato.\n\nIl cambio del profilo si trova in 'Impostazioni' - 'Gestisci i profili utente'. Seleziona il profilo desiderato prima di caricare il gioco.",
"ja_JP": "このオプションは、ゲームプレイ中に「ユーザプロファイルを管理」ダイアログをスキップし、事前に選択されたプロファイルを使用します。\n\nプロファイルの切り替えは、「設定」-「ユーザプロファイルを管理」で見つけることができます。ゲームのロード前に目的のプロファイルをを選択してください。",
@@ -22520,26 +22520,26 @@
{
"ID": "MultiplayerModeTooltip",
"Translations": {
"ar_SA": "تغيير وضع LDN متعدد اللاعبين.\n\nسوف يقوم LdnMitm بتعديل وظيفة اللعب المحلية/اللاسلكية المحلية في الألعاب لتعمل كما لو كانت شبكة LAN، مما يسمح باتصالات الشبكة المحلية نفسها مع محاكيات ريوجينكس الأخرى وأجهزة نينتندو سويتش المخترقة التي تم تثبيت وحدة ldn_mitm عليها.\n\nيتطلب وضع اللاعبين المتعددين أن يكون جميع اللاعبين على نفس إصدار اللعبة (على سبيل المثال، يتعذر على الإصدار 13.0.1 من سوبر سماش برذرز ألتميت الاتصال بالإصدار 13.0.0).\n\nاتركه معطلا إذا لم تكن متأكدا.",
"de_DE": "Ändert den LDN-Mehrspielermodus.\n\nLdnMitm ändert die lokale drahtlose/lokale Spielfunktionalität in Spielen so, dass sie wie ein LAN funktioniert und lokale, netzwerkgleiche Verbindungen mit anderen Ryujinx-Instanzen und gehackten Nintendo Switch-Konsolen ermöglicht, auf denen das ldn_mitm-Modul installiert ist.\n\nMultiplayer erfordert, dass alle Spieler die gleiche Spielversion verwenden (d.h. Super Smash Bros. Ultimate v13.0.1 kann sich nicht mit v13.0.0 verbinden).\n\nIm Zweifelsfall auf DISABLED lassen.",
"ar_SA": "تغيير وضع LDN متعدد اللاعبين.\n\nسوف يقوم ldn_mitm بتعديل وظيفة اللعب المحلية/اللاسلكية المحلية في الألعاب لتعمل كما لو كانت شبكة LAN، مما يسمح باتصالات الشبكة المحلية نفسها مع محاكيات ريوجينكس الأخرى وأجهزة نينتندو سويتش المخترقة التي تم تثبيت وحدة ldn_mitm عليها.\n\nيتطلب وضع اللاعبين المتعددين أن يكون جميع اللاعبين على نفس إصدار اللعبة (على سبيل المثال، يتعذر على الإصدار 13.0.1 من سوبر سماش برذرز ألتميت الاتصال بالإصدار 13.0.0).\n\nاتركه معطلا إذا لم تكن متأكدا.",
"de_DE": "Ändert den LDN-Mehrspielermodus.\n\nldn_mitm ändert die lokale drahtlose/lokale Spielfunktionalität in Spielen so, dass sie wie ein LAN funktioniert und lokale, netzwerkgleiche Verbindungen mit anderen Ryujinx-Instanzen und gehackten Nintendo Switch-Konsolen ermöglicht, auf denen das ldn_mitm-Modul installiert ist.\n\nMultiplayer erfordert, dass alle Spieler die gleiche Spielversion verwenden (d.h. Super Smash Bros. Ultimate v13.0.1 kann sich nicht mit v13.0.0 verbinden).\n\nIm Zweifelsfall auf DISABLED lassen.",
"el_GR": "",
"en_US": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.",
"es_ES": "Cambiar modo LDN multijugador.\n\nLdnMitm modificará la funcionalidad local de juego inalámbrico para funcionar como si fuera LAN, permitiendo locales conexiones de la misma red con otras instancias de Ryujinx y consolas hackeadas de Nintendo Switch que tienen instalado el módulo ldn_mitm.\n\nMultijugador requiere que todos los jugadores estén en la misma versión del juego (por ejemplo, Super Smash Bros. Ultimate v13.0.1 no se puede conectar a v13.0.0).\n\nDejar DESACTIVADO si no está seguro.",
"fr_FR": "Change le mode multijoueur LDN.\n\nLdnMitm modifiera la fonctionnalité de jeu sans fil local/jeu local dans les jeux pour fonctionner comme s'il s'agissait d'un LAN, permettant des connexions locales sur le même réseau avec d'autres instances de Ryujinx et des consoles Nintendo Switch piratées ayant le module ldn_mitm installé.\n\nLe multijoueur nécessite que tous les joueurs soient sur la même version du jeu (par exemple, Super Smash Bros. Ultimate v13.0.1 ne peut pas se connecter à v13.0.0).\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.",
"en_US": "Change LDN multiplayer mode.\n\nldn_mitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.",
"es_ES": "Cambiar modo LDN multijugador.\n\nldn_mitm modificará la funcionalidad local de juego inalámbrico para funcionar como si fuera LAN, permitiendo locales conexiones de la misma red con otras instancias de Ryujinx y consolas hackeadas de Nintendo Switch que tienen instalado el módulo ldn_mitm.\n\nMultijugador requiere que todos los jugadores estén en la misma versión del juego (por ejemplo, Super Smash Bros. Ultimate v13.0.1 no se puede conectar a v13.0.0).\n\nDejar DESACTIVADO si no está seguro.",
"fr_FR": "Change le mode multijoueur LDN.\n\nldn_mitm modifiera la fonctionnalité de jeu sans fil local/jeu local dans les jeux pour fonctionner comme s'il s'agissait d'un LAN, permettant des connexions locales sur le même réseau avec d'autres instances de Ryujinx et des consoles Nintendo Switch piratées ayant le module ldn_mitm installé.\n\nLe multijoueur nécessite que tous les joueurs soient sur la même version du jeu (par exemple, Super Smash Bros. Ultimate v13.0.1 ne peut pas se connecter à v13.0.0).\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.",
"he_IL": "",
"it_IT": "Cambia la modalità multigiocatore LDN.\n\nLdnMitm modificherà la funzionalità locale wireless/local play nei giochi per funzionare come se fosse in modalità LAN, consentendo connessioni locali sulla stessa rete con altre istanze di Ryujinx e console Nintendo Switch modificate che hanno il modulo ldn_mitm installato.\n\nLa modalità multigiocatore richiede che tutti i giocatori usino la stessa versione del gioco (es. Super Smash Bros. Ultimate v13.0.1 non può connettersi con la v13.0.0).\n\nNel dubbio, lascia l'opzione su Disabilitato.",
"it_IT": "Cambia la modalità multigiocatore LDN.\n\nldn_mitm modificherà la funzionalità locale wireless/local play nei giochi per funzionare come se fosse in modalità LAN, consentendo connessioni locali sulla stessa rete con altre istanze di Ryujinx e console Nintendo Switch modificate che hanno il modulo ldn_mitm installato.\n\nLa modalità multigiocatore richiede che tutti i giocatori usino la stessa versione del gioco (es. Super Smash Bros. Ultimate v13.0.1 non può connettersi con la v13.0.0).\n\nNel dubbio, lascia l'opzione su Disabilitato.",
"ja_JP": "LDNマルチプレイヤーモードを変更します.\n\nldn_mitmモジュールがインストールされた, 他のRyujinxインスタンスや,ハックされたNintendo Switchコンソールとのローカル/同一ネットワーク接続を可能にします.\n\nマルチプレイでは, すべてのプレイヤーが同じゲームバージョンである必要がありますSuper Smash Bros. Ultimate v13.0.1はv13.0.0に接続できません).\n\n不明な場合は「無効」のままにしてください.",
"ko_KR": "LDN 멀티플레이어 모드를 변경합니다.\n\nLdnMitm은 게임의 로컬 무선/로컬 플레이 기능을 LAN처럼 작동하도록 수정하여 다른 Ryujinx 인스턴스나 ldn_mitm 모듈이 설치된 해킹된 Nintendo Switch 콘솔과 로컬, 동일 네트워크 연결이 가능합니다.\n\n멀티플레이어는 모든 플레이어가 동일한 게임 버전을 사용해야 합니다(예 : 슈퍼 스매시브라더스 얼티밋 v13.0.1은 v13.0.0에 연결할 수 없음).\n\n모르면 비활성화 상태로 두세요.",
"no_NO": "Endre LDN flerspillermodus.\n\nLdnMitm vil endre lokal trådløst/lokal spillfunksjonalitet i spill som skal fungere som om den var LAN, noe som tillater lokal, samme nettverk forbindelser med andre Ryujinx instanser og hacket Nintendo Switch konsoller som har installert ldn_mitm-modulen.\n\nFlerspiller krever at alle spillerne er på samme versjon (dvs. Super Smash Bros. Ultimat v13.0.1 kan ikke koble til v13.0.0).\n\nForlat DEAKTIVERT hvis usikker.",
"ko_KR": "LDN 멀티플레이어 모드를 변경합니다.\n\nldn_mitm은 게임의 로컬 무선/로컬 플레이 기능을 LAN처럼 작동하도록 수정하여 다른 Ryujinx 인스턴스나 ldn_mitm 모듈이 설치된 해킹된 Nintendo Switch 콘솔과 로컬, 동일 네트워크 연결이 가능합니다.\n\n멀티플레이어는 모든 플레이어가 동일한 게임 버전을 사용해야 합니다(예 : 슈퍼 스매시브라더스 얼티밋 v13.0.1은 v13.0.0에 연결할 수 없음).\n\n모르면 비활성화 상태로 두세요.",
"no_NO": "Endre LDN flerspillermodus.\n\nldn_mitm vil endre lokal trådløst/lokal spillfunksjonalitet i spill som skal fungere som om den var LAN, noe som tillater lokal, samme nettverk forbindelser med andre Ryujinx instanser og hacket Nintendo Switch konsoller som har installert ldn_mitm-modulen.\n\nFlerspiller krever at alle spillerne er på samme versjon (dvs. Super Smash Bros. Ultimat v13.0.1 kan ikke koble til v13.0.0).\n\nForlat DEAKTIVERT hvis usikker.",
"pl_PL": "",
"pt_BR": "Alterar o modo multiplayer LDN.\n\nLdnMitm modificará a funcionalidade de jogo sem fio/local nos jogos para funcionar como se fosse LAN, permitindo conexões locais, na mesma rede, com outras instâncias do Ryujinx e consoles Nintendo Switch hackeados que possuem o módulo ldn_mitm instalado.\n\nO multiplayer exige que todos os jogadores estejam na mesma versão do jogo (ex.: Super Smash Bros. Ultimate v13.0.1 não consegue se conectar à v13.0.0).\n\nDeixe DESATIVADO se estiver em dúvida.",
"ru_RU": "Меняет многопользовательский режим LDN.\n\nLdnMitm модифицирует функциональность локальной беспроводной игры на одном устройстве в играх, позволяя играть с другими пользователями Ryujinx или взломанными консолями Nintendo Switch с установленным модулем ldn_mitm, находящимися в одной локальной сети друг с другом.\n\nМногопользовательская игра требует наличия у всех игроков одной и той же версии игры (т.е. Super Smash Bros. Ultimate v13.0.1 не может подключиться к v13.0.0).\n\nРекомендуется оставить выключенным.",
"sv_SE": "Ändra LDN-flerspelarläge\n\nLdnMitm kommer att ändra lokal funktionalitet för trådlös/lokalt spel att fungera som om det vore ett LAN, vilket ger stöd för anslutningar med local och same-network med andra Ryujinx-instanser och hackade Nintendo Switch-konsoller som har modulen ldn_mitm installerad.\n\nFlerspelare kräver att alla spelare har samma spelversion (t.ex. Super Smash Bros. Ultimate v13.0.1 kan inte ansluta till v13.0.0).\n\nLämna INAKTIVERAD om du är osäker.",
"th_TH": "เปลี่ยนโหมดผู้เล่นหลายคนของ LDN\n\nLdnMitm จะปรับเปลี่ยนฟังก์ชันการเล่นแบบไร้สาย/ภายใน จะให้เกมทำงานเหมือนกับว่าเป็น LAN ช่วยให้สามารถเชื่อมต่อภายในเครือข่ายเดียวกันกับอินสแตนซ์ Ryujinx อื่น ๆ และคอนโซล Nintendo Switch ที่ถูกแฮ็กซึ่งมีโมดูล ldn_mitm ติดตั้งอยู่\n\nผู้เล่นหลายคนต้องการให้ผู้เล่นทุกคนอยู่ในเกมเวอร์ชันเดียวกัน (เช่น Super Smash Bros. Ultimate v13.0.1 ไม่สามารถเชื่อมต่อกับ v13.0.0)\n\nปล่อยให้ปิดการใช้งานหากไม่แน่ใจ",
"pt_BR": "Alterar o modo multiplayer LDN.\n\nldn_mitm modificará a funcionalidade de jogo sem fio/local nos jogos para funcionar como se fosse LAN, permitindo conexões locais, na mesma rede, com outras instâncias do Ryujinx e consoles Nintendo Switch hackeados que possuem o módulo ldn_mitm instalado.\n\nO multiplayer exige que todos os jogadores estejam na mesma versão do jogo (ex.: Super Smash Bros. Ultimate v13.0.1 não consegue se conectar à v13.0.0).\n\nDeixe DESATIVADO se estiver em dúvida.",
"ru_RU": "Меняет многопользовательский режим LDN.\n\nldn_mitm модифицирует функциональность локальной беспроводной игры на одном устройстве в играх, позволяя играть с другими пользователями Ryujinx или взломанными консолями Nintendo Switch с установленным модулем ldn_mitm, находящимися в одной локальной сети друг с другом.\n\nМногопользовательская игра требует наличия у всех игроков одной и той же версии игры (т.е. Super Smash Bros. Ultimate v13.0.1 не может подключиться к v13.0.0).\n\nРекомендуется оставить выключенным.",
"sv_SE": "Ändra LDN-flerspelarläge\n\nldn_mitm kommer att ändra lokal funktionalitet för trådlös/lokalt spel att fungera som om det vore ett LAN, vilket ger stöd för anslutningar med local och same-network med andra Ryujinx-instanser och hackade Nintendo Switch-konsoller som har modulen ldn_mitm installerad.\n\nFlerspelare kräver att alla spelare har samma spelversion (t.ex. Super Smash Bros. Ultimate v13.0.1 kan inte ansluta till v13.0.0).\n\nLämna INAKTIVERAD om du är osäker.",
"th_TH": "เปลี่ยนโหมดผู้เล่นหลายคนของ LDN\n\nldn_mitm จะปรับเปลี่ยนฟังก์ชันการเล่นแบบไร้สาย/ภายใน จะให้เกมทำงานเหมือนกับว่าเป็น LAN ช่วยให้สามารถเชื่อมต่อภายในเครือข่ายเดียวกันกับอินสแตนซ์ Ryujinx อื่น ๆ และคอนโซล Nintendo Switch ที่ถูกแฮ็กซึ่งมีโมดูล ldn_mitm ติดตั้งอยู่\n\nผู้เล่นหลายคนต้องการให้ผู้เล่นทุกคนอยู่ในเกมเวอร์ชันเดียวกัน (เช่น Super Smash Bros. Ultimate v13.0.1 ไม่สามารถเชื่อมต่อกับ v13.0.0)\n\nปล่อยให้ปิดการใช้งานหากไม่แน่ใจ",
"tr_TR": "",
"uk_UA": "Змінити LDN мультиплеєру.\n\nLdnMitm змінить функціонал бездротової/локальної гри в іграх, щоб вони працювали так, ніби це LAN, що дозволяє локальні підключення в тій самій мережі з іншими екземплярами Ryujinx та хакнутими консолями Nintendo Switch, які мають встановлений модуль ldn_mitm.\n\nМультиплеєр вимагає, щоб усі гравці були на одній і тій же версії гри (наприклад Super Smash Bros. Ultimate v13.0.1 не зможе під'єднатися до v13.0.0).\n\nЗалиште на \"Вимкнено\", якщо не впевнені.",
"uk_UA": "Змінити LDN мультиплеєру.\n\nldn_mitm змінить функціонал бездротової/локальної гри в іграх, щоб вони працювали так, ніби це LAN, що дозволяє локальні підключення в тій самій мережі з іншими екземплярами Ryujinx та хакнутими консолями Nintendo Switch, які мають встановлений модуль ldn_mitm.\n\nМультиплеєр вимагає, щоб усі гравці були на одній і тій же версії гри (наприклад Super Smash Bros. Ultimate v13.0.1 не зможе під'єднатися до v13.0.0).\n\nЗалиште на \"Вимкнено\", якщо не впевнені.",
"zh_CN": "修改 LDN 多人联机游玩模式。\n\nldn_mitm 联机插件将修改游戏中的本地无线和本地游玩功能,使其表现得像局域网一样,允许和其他安装了 ldn_mitm 插件的 Ryujinx 模拟器和破解的任天堂 Switch 主机在同一网络下进行本地连接,实现多人联机游玩。\n\n多人联机游玩要求所有玩家必须运行相同的游戏版本例如游戏版本 v13.0.1 无法与 v13.0.0 联机)。\n\n如果不确定请保持为“禁用”。",
"zh_TW": "變更 LDN 多人遊戲模式。\n\nLdnMitm 將修改遊戲中的本機無線/本機遊戲功能,使其如同區域網路一樣執行,允許與其他安裝了 ldn_mitm 模組的 Ryujinx 實例和已破解的 Nintendo Switch 遊戲機進行本機同網路連線。\n\n多人遊戲要求所有玩家使用相同的遊戲版本 (例如Super Smash Bros. Ultimate v13.0.1 無法連接 v13.0.0)。\n\n如果不確定請保持 Disabled (停用) 狀態。"
"zh_TW": "變更 LDN 多人遊戲模式。\n\nldn_mitm 將修改遊戲中的本機無線/本機遊戲功能,使其如同區域網路一樣執行,允許與其他安裝了 ldn_mitm 模組的 Ryujinx 實例和已破解的 Nintendo Switch 遊戲機進行本機同網路連線。\n\n多人遊戲要求所有玩家使用相同的遊戲版本 (例如Super Smash Bros. Ultimate v13.0.1 無法連接 v13.0.0)。\n\n如果不確定請保持 Disabled (停用) 狀態。"
}
},
{
@@ -22800,7 +22800,7 @@
"el_GR": "",
"en_US": "Generates a new passphrase, which can be shared with other players.",
"es_ES": "Genera una nueva frase de contraseña, que puede ser compartida con otros jugadores.",
"fr_FR": "Génére un nouveau mot de passe, qui peut être partagé avec les autres.",
"fr_FR": "Génère un nouveau mot de passe, qui peut être partagé avec les autres.",
"he_IL": "",
"it_IT": "Genera una nuova passphrase, che può essere condivisa con altri giocatori.",
"ja_JP": "",
@@ -24841,6 +24841,781 @@
"zh_CN": "如果您在设置中有某些 LDN 口令则可加入此游戏。",
"zh_TW": "你只能加入與 LDN 網路密碼片語 (passphrase) 設定相同的遊戲。"
}
},
{
"ID": "SetupWizardOpen",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Setup Wizard",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardActionBack",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Back",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardActionNext",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Next",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardFirstPageTitle",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Welcome to Ryubing!",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardFirstPageContent",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Ryubing is a fork of the discontinued Nintendo Switch emulator, Ryujinx.\n\nThis setup wizard will guide you through the necessary steps needed for Ryubing to play your Switch games on PC.",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardFirstPageAction",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Start Setup",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardKeysPageTitle",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Key Files",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardKeysPageDescription",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Please select the folder containing your prod/title .keys files:",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardKeysPageFolderPopupTitle",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Please select the folder containing your prod/title .keys files",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardKeysPageHelpText",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Not sure how to get your keys?",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardKeysPageSkipText",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Skipped setting up keys as you already have a valid key installation and did not choose a folder to install from.\nClick '{0}' if you wish to reinstall your keys.",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardFirmwarePageTitle",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Switch Firmware",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardFirmwarePageDescription",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Please select the folder or .zip/.xci containing your dumped Nintendo Switch firmware:",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardFirmwarePageFolderPopupTitle",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Please select the folder containing your dumped & extracted Switch firmware.",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardFirmwarePageFilePopupTitle",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Please select the file containing your dumped Switch firmware.",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardFirmwarePageFileBrowse",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Select .zip or .xci",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardFirmwarePageFolderBrowse",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Select Extracted Folder",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardFirmwarePageInstallSuccessNotificationTitle",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Firmware installed",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardFirmwarePageInstallSuccessNotificationText",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Installed firmware version {0}.",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardFirmwarePageInstallFailNotificationTitle",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Firmware not installed",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardFirmwarePageInstallFailNotificationText",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "It seems some error occurred when trying to install the firmware at path '{0}'.\nDid that folder contain a firmware dump?",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardFirmwarePageHelpText",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Not sure how to get your firmware off of your Switch?",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardFirmwarePageSkipText",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Skipped setting up firmware as you already have a valid firmware installation and did not choose a folder or file to install from.\nClick '{0}' if you wish to overwrite your firmware.",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardGameDirsPageTitle",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Game, Update, and DLC Paths",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardGameDirsPageDescription",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "{0} can be pointed at any number of folders to look for your games, updates, and DLC content.\nAt least one folder must be specified in game directories before continuing.",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardGameDirsPageNoFoldersSelectedError",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "At least one folder for games must be selected; otherwise the UI will be empty.",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardGameDirsPageHelpText",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Not sure how to get your games, updates, and/or DLC onto your PC?",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardFinalPageTitle",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Setup Complete",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardFinalPageDescription",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Your installation of Ryubing (aka Ryujinx) has been completed.\n\nIf you require assistance, feel free to join our Discord server and ask for help,\nafter verifying your possession of a modded Nintendo Switch.",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardFinalPageAction",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Finish",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SetupWizardHelpLinkButton",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Click here to view a guide.",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
}
]
}
}

Binary file not shown.

View File

@@ -10,6 +10,8 @@
<string>Ryujinx</string>
<key>CFBundleIconFile</key>
<string>Ryujinx.icns</string>
<key>CFBundleIconName</key>
<string>Ryujinx</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
@@ -40,7 +42,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.2</string>
<string>1.3</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>

View File

@@ -25,6 +25,7 @@ cp "$PUBLISH_DIRECTORY"/*.dylib "$APP_BUNDLE_DIRECTORY/Contents/Frameworks"
cp Info.plist "$APP_BUNDLE_DIRECTORY/Contents"
cp Ryujinx.icns "$APP_BUNDLE_DIRECTORY/Contents/Resources/Ryujinx.icns"
cp updater.sh "$APP_BUNDLE_DIRECTORY/Contents/Resources/updater.sh"
cp Assets.car "$APP_BUNDLE_DIRECTORY/Contents/Resources/Assets.car"
cp -r "$PUBLISH_DIRECTORY/THIRDPARTY.md" "$APP_BUNDLE_DIRECTORY/Contents/Resources"
echo -n "APPL????" > "$APP_BUNDLE_DIRECTORY/Contents/PkgInfo"

View File

@@ -1,124 +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> <CANARY>"
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
CANARY=$8
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
if [ "$CANARY" == "1" ]; then
RELEASE_TAR_FILE_NAME=nogui-ryujinx-canary-$VERSION-macos_universal.tar
elif [ "$VERSION" == "1.1.0" ]; then
RELEASE_TAR_FILE_NAME=nogui-ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.tar
else
RELEASE_TAR_FILE_NAME=nogui-ryujinx-$VERSION-macos_universal.tar
fi
ARM64_OUTPUT="$TEMP_DIRECTORY/publish_arm64"
X64_OUTPUT="$TEMP_DIRECTORY/publish_x64"
UNIVERSAL_OUTPUT="$OUTPUT_DIRECTORY/publish"
EXECUTABLE_SUB_PATH=Ryujinx.Headless.SDL3
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.Headless.SDL3
dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Headless.SDL3
dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Headless.SDL3
# 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"
rm -rf "$OUTPUT_DIRECTORY"
mkdir -p "$OUTPUT_DIRECTORY"
# Let's copy one of the two different outputs and remove the executable
cp -R "$ARM64_OUTPUT/" "$UNIVERSAL_OUTPUT"
rm "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH"
# Make its libraries universal
python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_OUTPUT" "$X64_OUTPUT" "$UNIVERSAL_OUTPUT" "**/*.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_OUTPUT/$EXECUTABLE_SUB_PATH" "$X64_OUTPUT/$EXECUTABLE_SUB_PATH" -output "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH" -create
# 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"
for FILE in "$UNIVERSAL_OUTPUT"/*; do
if [[ $(file "$FILE") == *"Mach-O"* ]]; then
rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$FILE"
fi
done
else
echo "Using codesign for ad-hoc signing"
for FILE in "$UNIVERSAL_OUTPUT"/*; do
if [[ $(file "$FILE") == *"Mach-O"* ]]; then
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f -s - "$FILE"
fi
done
fi
echo "Creating archive"
pushd "$OUTPUT_DIRECTORY"
tar --exclude "publish/Ryujinx.Headless.SDL3" -cvf "$RELEASE_TAR_FILE_NAME" publish 1> /dev/null
python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "publish/Ryujinx.Headless.SDL3" "publish/Ryujinx.Headless.SDL3"
gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz"
rm "$RELEASE_TAR_FILE_NAME"
popd
echo "Done"

View File

@@ -2275,12 +2275,12 @@
010018E011D92000,"Pokémon™ Shining Pearl",gpu;ldn-works,ingame,2024-08-28 13:26:35
010015F008C54000,"Pokémon™ HOME",Needs Update;crash;services,menus,2020-12-06 06:01:51
01001F5010DFA000,"Pokémon™ Legends: Arceus",gpu;Needs Update;ldn-works,ingame,2024-09-19 10:02:02
0100F43008C44000,"Pokémon™ Legends: Z-A",gpu;crash;ldn-works,ingame,2025-11-16 00:30:00
01005D100807A000,"Pokémon™ Quest",,playable,2022-02-22 16:12:32
0100A3D008C5C000,"Pokémon™ Scarlet",gpu;nvdec;ldn-works;amd-vendor-bug,ingame,2023-12-14 13:18:29
01008F6008C5E000,"Pokémon™ Violet",gpu;nvdec;ldn-works;amd-vendor-bug;mac-bug,ingame,2024-07-30 02:51:48
0100187003A36000,"Pokémon™: Lets Go, Eevee!",crash;nvdec;online-broken;ldn-broken,ingame,2024-06-01 15:03:04
010003F003A34000,"Pokémon™: Lets Go, Pikachu!",crash;nvdec;online-broken;ldn-broken,ingame,2024-03-15 07:55:41
0100F43008C44000,"Pokémon Legends: Z-A",gpu;crash;ldn-broken,ingame,2025-10-16 19:13:00
0100B3F000BE2000,"Pokkén Tournament™ DX",nvdec;ldn-works;opengl-backend-bug;LAN;amd-vendor-bug;intel-vendor-bug,playable,2024-07-18 23:11:08
010030D005AE6000,"Pokkén Tournament™ DX Demo",demo;opengl-backend-bug,playable,2022-08-10 12:03:19
0100A3500B4EC000,"Polandball: Can Into Space",,playable,2020-06-25 15:13:26
1 title_id game_name labels status last_updated
2275 010018E011D92000 Pokémon™ Shining Pearl gpu;ldn-works ingame 2024-08-28 13:26:35
2276 010015F008C54000 Pokémon™ HOME Needs Update;crash;services menus 2020-12-06 06:01:51
2277 01001F5010DFA000 Pokémon™ Legends: Arceus gpu;Needs Update;ldn-works ingame 2024-09-19 10:02:02
2278 0100F43008C44000 Pokémon™ Legends: Z-A gpu;crash;ldn-works ingame 2025-11-16 00:30:00
2279 01005D100807A000 Pokémon™ Quest playable 2022-02-22 16:12:32
2280 0100A3D008C5C000 Pokémon™ Scarlet gpu;nvdec;ldn-works;amd-vendor-bug ingame 2023-12-14 13:18:29
2281 01008F6008C5E000 Pokémon™ Violet gpu;nvdec;ldn-works;amd-vendor-bug;mac-bug ingame 2024-07-30 02:51:48
2282 0100187003A36000 Pokémon™: Let’s Go, Eevee! crash;nvdec;online-broken;ldn-broken ingame 2024-06-01 15:03:04
2283 010003F003A34000 Pokémon™: Let’s Go, Pikachu! crash;nvdec;online-broken;ldn-broken ingame 2024-03-15 07:55:41
0100F43008C44000 Pokémon Legends: Z-A gpu;crash;ldn-broken ingame 2025-10-16 19:13:00
2284 0100B3F000BE2000 Pokkén Tournament™ DX nvdec;ldn-works;opengl-backend-bug;LAN;amd-vendor-bug;intel-vendor-bug playable 2024-07-18 23:11:08
2285 010030D005AE6000 Pokkén Tournament™ DX Demo demo;opengl-backend-bug playable 2022-08-10 12:03:19
2286 0100A3500B4EC000 Polandball: Can Into Space playable 2020-06-25 15:13:26

View File

@@ -19,7 +19,7 @@ namespace ARMeilleure.Instructions
context.LoadFromContext();
context.Return(Const(op.Address));
InstEmitFlowHelper.EmitReturn(context, Const(op.Address));
}
public static void Svc(ArmEmitterContext context)
@@ -49,7 +49,7 @@ namespace ARMeilleure.Instructions
context.LoadFromContext();
context.Return(Const(op.Address));
InstEmitFlowHelper.EmitReturn(context, Const(op.Address));
}
}
}

View File

@@ -33,7 +33,7 @@ namespace ARMeilleure.Instructions
context.LoadFromContext();
context.Return(Const(context.CurrOp.Address));
InstEmitFlowHelper.EmitReturn(context, Const(context.CurrOp.Address));
}
}
}

View File

@@ -66,7 +66,7 @@ namespace ARMeilleure.Instructions
{
OpCodeBReg op = (OpCodeBReg)context.CurrOp;
context.Return(GetIntOrZR(context, op.Rn));
EmitReturn(context, GetIntOrZR(context, op.Rn));
}
public static void Tbnz(ArmEmitterContext context) => EmitTb(context, onNotZero: true);

View File

@@ -13,6 +13,10 @@ namespace ARMeilleure.Instructions
{
static class InstEmitFlowHelper
{
// How many calls we can have in our call stack before we give up and return to the dispatcher.
// This prevents stack overflows caused by deep recursive calls.
private const int MaxCallDepth = 200;
public static void EmitCondBranch(ArmEmitterContext context, Operand target, Condition cond)
{
if (cond != Condition.Al)
@@ -182,12 +186,7 @@ namespace ARMeilleure.Instructions
{
if (isReturn || context.IsSingleStep)
{
if (target.Type == OperandType.I32)
{
target = context.ZeroExtend32(OperandType.I64, target);
}
context.Return(target);
EmitReturn(context, target);
}
else
{
@@ -195,6 +194,19 @@ namespace ARMeilleure.Instructions
}
}
public static void EmitReturn(ArmEmitterContext context, Operand target)
{
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
DecreaseCallDepth(context, nativeContext);
if (target.Type == OperandType.I32)
{
target = context.ZeroExtend32(OperandType.I64, target);
}
context.Return(target);
}
private static void EmitTableBranch(ArmEmitterContext context, Operand guestAddress, bool isJump)
{
context.StoreToContext();
@@ -257,6 +269,8 @@ namespace ARMeilleure.Instructions
if (isJump)
{
DecreaseCallDepth(context, nativeContext);
context.Tailcall(hostAddress, nativeContext);
}
else
@@ -278,8 +292,42 @@ namespace ARMeilleure.Instructions
Operand lblContinue = context.GetLabel(nextAddr.Value);
context.BranchIf(lblContinue, returnAddress, nextAddr, Comparison.Equal, BasicBlockFrequency.Cold);
DecreaseCallDepth(context, nativeContext);
context.Return(returnAddress);
}
}
public static void EmitCallDepthCheckAndIncrement(EmitterContext context, Operand guestAddress)
{
if (!Optimizations.EnableDeepCallRecursionProtection)
{
return;
}
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
Operand callDepthAddr = context.Add(nativeContext, Const((ulong)NativeContext.GetCallDepthOffset()));
Operand currentCallDepth = context.Load(OperandType.I32, callDepthAddr);
Operand lblDoCall = Label();
context.BranchIf(lblDoCall, currentCallDepth, Const(MaxCallDepth), Comparison.LessUI);
context.Store(callDepthAddr, context.Subtract(currentCallDepth, Const(1)));
context.Return(guestAddress);
context.MarkLabel(lblDoCall);
context.Store(callDepthAddr, context.Add(currentCallDepth, Const(1)));
}
private static void DecreaseCallDepth(EmitterContext context, Operand nativeContext)
{
if (!Optimizations.EnableDeepCallRecursionProtection)
{
return;
}
Operand callDepthAddr = context.Add(nativeContext, Const((ulong)NativeContext.GetCallDepthOffset()));
Operand currentCallDepth = context.Load(OperandType.I32, callDepthAddr);
context.Store(callDepthAddr, context.Subtract(currentCallDepth, Const(1)));
}
}
}

View File

@@ -13,6 +13,7 @@ namespace ARMeilleure
public static bool AllowLcqInFunctionTable { get; set; } = true;
public static bool UseUnmanagedDispatchLoop { get; set; } = true;
public static bool EnableDebugging { get; set; } = false;
public static bool EnableDeepCallRecursionProtection { get; set; } = true;
public static bool UseAdvSimdIfAvailable { get; set; } = true;
public static bool UseArm64AesIfAvailable { get; set; } = true;

View File

@@ -134,6 +134,11 @@ namespace ARMeilleure.State
public bool GetFPstateFlag(FPState flag) => _nativeContext.GetFPStateFlag(flag);
public void SetFPstateFlag(FPState flag, bool value) => _nativeContext.SetFPStateFlag(flag, value);
internal void ResetCallDepth()
{
_nativeContext.ResetCallDepth();
}
internal void CheckInterrupt()
{
if (Interrupted)

View File

@@ -22,6 +22,7 @@ namespace ARMeilleure.State
public ulong ExclusiveValueHigh;
public int Running;
public long Tpidr2El0;
public int CallDepth;
/// <summary>
/// Precise PC value used for debugging.
@@ -199,6 +200,8 @@ namespace ARMeilleure.State
public bool GetRunning() => GetStorage().Running != 0;
public void SetRunning(bool value) => GetStorage().Running = value ? 1 : 0;
public void ResetCallDepth() => GetStorage().CallDepth = 0;
public unsafe static int GetRegisterOffset(Register reg)
{
if (reg.Type == RegisterType.Integer)
@@ -284,6 +287,11 @@ namespace ARMeilleure.State
return StorageOffset(ref _dummyStorage, ref _dummyStorage.DebugPrecisePc);
}
public static int GetCallDepthOffset()
{
return StorageOffset(ref _dummyStorage, ref _dummyStorage.CallDepth);
}
private static int StorageOffset<T>(ref NativeCtxStorage storage, ref T target)
{
return (int)Unsafe.ByteOffset(ref Unsafe.As<NativeCtxStorage, T>(ref storage), ref target);

View File

@@ -361,10 +361,7 @@ namespace ARMeilleure.Translation
IntervalTreeNode<TK, TV> tmp = LeftOf(replacementNode) ?? RightOf(replacementNode);
if (tmp != null)
{
tmp.Parent = ParentOf(replacementNode);
}
tmp?.Parent = ParentOf(replacementNode);
if (ParentOf(replacementNode) == null)
{
@@ -582,10 +579,7 @@ namespace ARMeilleure.Translation
{
IntervalTreeNode<TK, TV> right = RightOf(node);
node.Right = LeftOf(right);
if (node.Right != null)
{
node.Right.Parent = node;
}
node.Right?.Parent = node;
IntervalTreeNode<TK, TV> nodeParent = ParentOf(node);
right.Parent = nodeParent;
@@ -615,10 +609,7 @@ namespace ARMeilleure.Translation
{
IntervalTreeNode<TK, TV> left = LeftOf(node);
node.Left = RightOf(left);
if (node.Left != null)
{
node.Left.Parent = node;
}
node.Left?.Parent = node;
IntervalTreeNode<TK, TV> nodeParent = ParentOf(node);
left.Parent = nodeParent;
@@ -667,10 +658,7 @@ namespace ARMeilleure.Translation
/// <param name="color">Color (Boolean)</param>
private static void SetColor(IntervalTreeNode<TK, TV> node, bool color)
{
if (node != null)
{
node.Color = color;
}
node?.Color = color;
}
/// <summary>

View File

@@ -33,7 +33,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\0\0";
private const uint InternalVersion = 7009; //! To be incremented manually for each change to the ARMeilleure project.
private const uint InternalVersion = 7010; //! To be incremented manually for each change to the ARMeilleure project.
private const string ActualDir = "0";
private const string BackupDir = "1";

View File

@@ -186,6 +186,7 @@ namespace ARMeilleure.Translation
Statistics.StartTimer();
context.ResetCallDepth();
ulong nextAddr = func.Execute(Stubs.ContextWrapper, context);
Statistics.StopTimer(address);
@@ -260,6 +261,7 @@ namespace ARMeilleure.Translation
Logger.StartPass(PassName.Translation);
InstEmitFlowHelper.EmitCallDepthCheckAndIncrement(context, Const(address));
EmitSynchronization(context);
if (blocks[0].Address != address)

View File

@@ -262,10 +262,18 @@ namespace ARMeilleure.Translation
Operand runningAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRunningOffset()));
Operand dispatchAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset()));
Operand callDepthAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetCallDepthOffset()));
EmitSyncFpContext(context, nativeContext, true);
context.MarkLabel(beginLbl);
if (Optimizations.EnableDeepCallRecursionProtection)
{
// Reset the call depth counter, since this is our first guest function call.
context.Store(callDepthAddress, Const(0));
}
context.Store(dispatchAddress, guestAddress);
context.Copy(guestAddress, context.Call(Const((ulong)DispatchStub), OperandType.I64, nativeContext));
context.BranchIfFalse(endLbl, guestAddress);

View File

@@ -19,6 +19,11 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
/// The output channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
public Array6<byte> Output;
/// <summary>
/// Reserved/unused.
/// </summary>
private readonly uint _padding;
/// <summary>
/// Biquad filter numerator (b0, b1, b2).

View File

@@ -91,7 +91,11 @@ namespace Ryujinx.Common
public void Dispose()
{
_queue.CompleteAdding();
try
{
_queue.CompleteAdding();
} catch (ObjectDisposedException) {}
_cts.Cancel();
_workerThread.Join();

View File

@@ -386,10 +386,7 @@ namespace Ryujinx.Common.Collections
IntervalTreeNode<TKey, TValue> tmp = LeftOf(replacementNode) ?? RightOf(replacementNode);
if (tmp != null)
{
tmp.Parent = ParentOf(replacementNode);
}
tmp?.Parent = ParentOf(replacementNode);
if (ParentOf(replacementNode) == null)
{

View File

@@ -235,10 +235,7 @@ namespace Ryujinx.Common.Collections
parent = ParentOf(element);
color = ColorOf(element);
if (child != null)
{
child.Parent = parent;
}
child?.Parent = parent;
if (parent == null)
{
@@ -258,8 +255,7 @@ namespace Ryujinx.Common.Collections
element.Right = old.Right;
element.Parent = old.Parent;
element.Predecessor = old.Predecessor;
if (element.Predecessor != null)
element.Predecessor.Successor = element;
element.Predecessor?.Successor = element;
if (ParentOf(old) == null)
{
@@ -292,10 +288,7 @@ namespace Ryujinx.Common.Collections
parent = ParentOf(nodeToDelete);
color = ColorOf(nodeToDelete);
if (child != null)
{
child.Parent = parent;
}
child?.Parent = parent;
if (parent == null)
{
@@ -314,11 +307,9 @@ namespace Ryujinx.Common.Collections
{
RestoreBalanceAfterRemoval(child);
}
if (old.Successor != null)
old.Successor.Predecessor = old.Predecessor;
if (old.Predecessor != null)
old.Predecessor.Successor = old.Successor;
old.Successor?.Predecessor = old.Predecessor;
old.Predecessor?.Successor = old.Successor;
return old;
}

View File

@@ -250,10 +250,7 @@ namespace Ryujinx.Common.Collections
{
T right = RightOf(node);
node.Right = LeftOf(right);
if (node.Right != null)
{
node.Right.Parent = node;
}
node.Right?.Parent = node;
T nodeParent = ParentOf(node);
right.Parent = nodeParent;
@@ -281,10 +278,7 @@ namespace Ryujinx.Common.Collections
{
T left = LeftOf(node);
node.Left = RightOf(left);
if (node.Left != null)
{
node.Left.Parent = node;
}
node.Left?.Parent = node;
T nodeParent = ParentOf(node);
left.Parent = nodeParent;
@@ -329,10 +323,7 @@ namespace Ryujinx.Common.Collections
/// <param name="color">Color (Boolean)</param>
protected static void SetColor(T node, bool color)
{
if (node != null)
{
node.Color = color;
}
node?.Color = color;
}
/// <summary>

View File

@@ -328,10 +328,7 @@ namespace Ryujinx.Common.Collections
Node<TKey, TValue> tmp = LeftOf(replacementNode) ?? RightOf(replacementNode);
if (tmp != null)
{
tmp.Parent = ParentOf(replacementNode);
}
tmp?.Parent = ParentOf(replacementNode);
if (ParentOf(replacementNode) == null)
{

View File

@@ -31,6 +31,11 @@ namespace Ryujinx.Common.Configuration
public static string KeysDirPath { get; private set; }
public static string KeysDirPathUser { get; }
public static string GetKeysDir() =>
Mode is LaunchMode.UserProfile && Directory.Exists(KeysDirPathUser)
? KeysDirPathUser
: KeysDirPath;
public static string LogsDirPath { get; private set; }
public const string DefaultNandDir = "bis";

View File

@@ -35,6 +35,7 @@ namespace Ryujinx.Common.Logging
ServiceBsd,
ServiceBtm,
ServiceCaps,
ServiceEctx,
ServiceFatal,
ServiceFriend,
ServiceFs,

View File

@@ -7,6 +7,9 @@ namespace Ryujinx.Common.Memory
{
private static readonly RecyclableMemoryStreamManager _shared = new();
private static readonly ObjectPool<RecyclableMemoryStream> _streamPool =
new(() => new RecyclableMemoryStream(_shared, Guid.NewGuid(), null, 0));
/// <summary>
/// We don't expose the <c>RecyclableMemoryStreamManager</c> directly because version 2.x
/// returns them as <c>MemoryStream</c>. This Shared class is here to a) offer only the GetStream() versions we use
@@ -19,7 +22,12 @@ namespace Ryujinx.Common.Memory
/// </summary>
/// <returns>A <c>RecyclableMemoryStream</c></returns>
public static RecyclableMemoryStream GetStream()
=> new(_shared);
{
RecyclableMemoryStream stream = _streamPool.Allocate();
stream.SetLength(0);
return stream;
}
/// <summary>
/// Retrieve a new <c>MemoryStream</c> object with the contents copied from the provided
@@ -55,7 +63,8 @@ namespace Ryujinx.Common.Memory
RecyclableMemoryStream stream = null;
try
{
stream = new RecyclableMemoryStream(_shared, id, tag, buffer.Length);
stream = _streamPool.Allocate();
stream.SetLength(0);
stream.Write(buffer);
stream.Position = 0;
return stream;
@@ -83,7 +92,8 @@ namespace Ryujinx.Common.Memory
RecyclableMemoryStream stream = null;
try
{
stream = new RecyclableMemoryStream(_shared, id, tag, count);
stream = _streamPool.Allocate();
stream.SetLength(0);
stream.Write(buffer, offset, count);
stream.Position = 0;
return stream;
@@ -94,6 +104,11 @@ namespace Ryujinx.Common.Memory
throw;
}
}
public static void ReleaseStream(RecyclableMemoryStream stream)
{
_streamPool.Release(stream);
}
}
}
}

View File

@@ -13,6 +13,15 @@ namespace Ryujinx.Common
public const string SetupGuideWikiUrl =
"https://git.ryujinx.app/ryubing/ryujinx/-/wikis/Setup-&-Configuration-Guide";
public const string DumpKeysWikiUrl =
"https://git.ryujinx.app/ryubing/ryujinx/-/wikis/Dumping/Keys";
public const string DumpFirmwareWikiUrl =
"https://git.ryujinx.app/ryubing/ryujinx/-/wikis/Dumping/Firmware";
public const string DumpContentWikiUrl =
"https://git.ryujinx.app/ryubing/ryujinx/-/wikis/Dumping/Games,-Updates-&-DLC";
public const string MultiplayerWikiUrl =
"https://git.ryujinx.app/ryubing/ryujinx/-/wikis/Multiplayer-(LDN-Local-Wireless)-Guide";
}

View File

@@ -20,5 +20,21 @@ namespace Ryujinx.Common.Utilities
Debug.Assert(res != -1);
}
}
// "dumpable" attribute of the calling process
private const int PR_SET_DUMPABLE = 4;
[DllImport("libc", SetLastError = true)]
private static extern int prctl(int option, int arg2);
public static void SetCoreDumpable(bool dumpable)
{
if (OperatingSystem.IsLinux())
{
int dumpableInt = dumpable ? 1 : 0;
int result = prctl(PR_SET_DUMPABLE, dumpableInt);
Debug.Assert(result == 0);
}
}
}
}

View File

@@ -82,7 +82,7 @@ namespace Ryujinx.Graphics.GAL
void SetRasterizerDiscard(bool discard);
void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask);
void SetRenderTargets(ITexture[] colors, ITexture depthStencil);
void SetRenderTargets(Span<ITexture> colors, ITexture depthStencil);
void SetScissors(ReadOnlySpan<Rectangle<int>> regions);

View File

@@ -1,5 +1,6 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
using System;
using System.Buffers;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
@@ -8,11 +9,13 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{
public static readonly ArrayPool<ITexture> ArrayPool = ArrayPool<ITexture>.Create(512, 50);
public readonly CommandType CommandType => CommandType.SetRenderTargets;
private int _colorsCount;
private TableRef<ITexture[]> _colors;
private TableRef<ITexture> _depthStencil;
public void Set(TableRef<ITexture[]> colors, TableRef<ITexture> depthStencil)
public void Set(int colorsCount, TableRef<ITexture[]> colors, TableRef<ITexture> depthStencil)
{
_colorsCount = colorsCount;
_colors = colors;
_depthStencil = depthStencil;
}
@@ -20,16 +23,15 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
public static void Run(ref SetRenderTargetsCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
ITexture[] colors = command._colors.Get(threaded);
ITexture[] colorsCopy = ArrayPool.Rent(colors.Length);
Span<ITexture> colorsSpan = colors.AsSpan(0, command._colorsCount);
for (int i = 0; i < colors.Length; i++)
for (int i = 0; i < colorsSpan.Length; i++)
{
colorsCopy[i] = ((ThreadedTexture)colors[i])?.Base;
colorsSpan[i] = ((ThreadedTexture)colorsSpan[i])?.Base;
}
renderer.Pipeline.SetRenderTargets(colorsCopy, command._depthStencil.GetAs<ThreadedTexture>(threaded)?.Base);
renderer.Pipeline.SetRenderTargets(colorsSpan, command._depthStencil.GetAs<ThreadedTexture>(threaded)?.Base);
ArrayPool.Return(colorsCopy);
ArrayPool.Return(colors);
}
}

View File

@@ -267,12 +267,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand();
}
public unsafe void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
public unsafe void SetRenderTargets(Span<ITexture> colors, ITexture depthStencil)
{
ITexture[] colorsCopy = SetRenderTargetsCommand.ArrayPool.Rent(colors.Length);
colors.CopyTo(colorsCopy, 0);
colors.CopyTo(colorsCopy.AsSpan());
_renderer.New<SetRenderTargetsCommand>()->Set(Ref(colorsCopy), Ref(depthStencil));
_renderer.New<SetRenderTargetsCommand>()->Set(colors.Length, Ref(colorsCopy), Ref(depthStencil));
_renderer.QueueCommand();
}

View File

@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
/// </summary>
public class ThreadedRenderer : IRenderer
{
private const int SpanPoolBytes = 4 * 1024 * 1024;
private const int SpanPoolBytes = 8 * 1024 * 1024;
private const int MaxRefsPerCommand = 2;
private const int QueueCount = 10000;

View File

@@ -404,9 +404,12 @@ namespace Ryujinx.Graphics.Gpu
if (force || _pendingSync || (syncPoint && SyncpointActions.Count > 0))
{
foreach (ISyncActionHandler action in SyncActions)
for (int i = 0; i < SyncActions.Count; i++)
{
action.SyncPreAction(syncPoint);
if (SyncActions[i].SyncPreAction(syncPoint))
{
SyncActions.RemoveAt(i--);
}
}
foreach (ISyncActionHandler action in SyncpointActions)

View File

@@ -411,7 +411,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// flushes often enough, which is determined by the flush balance.
/// </summary>
/// <inheritdoc/>
public void SyncPreAction(bool syncpoint)
public bool SyncPreAction(bool syncpoint)
{
if (syncpoint || NextSyncCopies())
{
@@ -421,6 +421,8 @@ namespace Ryujinx.Graphics.Gpu.Image
_registeredBufferSync = _modifiedSync;
}
}
return true;
}
/// <summary>

View File

@@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary>
/// Buffer, used to store vertex and index data, uniform and storage buffers, and others.
/// </summary>
class Buffer : INonOverlappingRange, ISyncActionHandler, IDisposable
class Buffer : INonOverlappingRange<Buffer>, ISyncActionHandler, IDisposable
{
private const ulong GranularBufferThreshold = 4096;
@@ -41,6 +41,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// End address of the buffer in guest memory.
/// </summary>
public ulong EndAddress => Address + Size;
public Buffer Next { get; set; }
public Buffer Previous { get; set; }
/// <summary>
/// Increments when the buffer is (partially) unmapped or disposed.
@@ -87,6 +90,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
private readonly bool _useGranular;
private bool _syncActionRegistered;
private bool _bufferInherited;
private int _referenceCount = 1;
@@ -113,7 +117,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong size,
BufferStage stage,
bool sparseCompatible,
RangeItem<Buffer>[] baseBuffers)
Buffer[] baseBuffers)
{
_context = context;
_physicalMemory = physicalMemory;
@@ -134,15 +138,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (baseBuffers.Length != 0)
{
baseHandles = new List<IRegionHandle>();
foreach (RangeItem<Buffer> item in baseBuffers)
foreach (Buffer item in baseBuffers)
{
if (item.Value._useGranular)
if (item._useGranular)
{
baseHandles.AddRange(item.Value._memoryTrackingGranular.Handles);
baseHandles.AddRange(item._memoryTrackingGranular.Handles);
}
else
{
baseHandles.Add(item.Value._memoryTracking);
baseHandles.Add(item._memoryTracking);
}
}
}
@@ -247,14 +251,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Checks if a given range overlaps with the buffer.
/// </summary>
/// <param name="address">Start address of the range</param>
/// <param name="size">Size in bytes of the range</param>
/// <param name="endAddress">End address of the range</param>
/// <returns>True if the range overlaps, false otherwise</returns>
public bool OverlapsWith(ulong address, ulong size)
public bool OverlapsWith(ulong address, ulong endAddress)
{
return Address < address + size && address < EndAddress;
return Address < endAddress && address < EndAddress;
}
public INonOverlappingRange Split(ulong splitAddress)
public INonOverlappingRange<Buffer> Split(ulong splitAddress)
{
throw new NotImplementedException();
}
@@ -389,11 +393,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// This will copy any buffer ranges designated for pre-flushing.
/// </summary>
/// <param name="syncpoint">True if the action is a guest syncpoint</param>
public void SyncPreAction(bool syncpoint)
public bool SyncPreAction(bool syncpoint)
{
if (_bufferInherited)
{
return true;
}
if (_referenceCount == 0)
{
return;
return false;
}
if (BackingState.ShouldChangeBacking())
@@ -410,6 +419,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
_modifiedRanges?.GetRangesAtSync(Address, Size, _context.SyncNumber, _syncPreRangeAction);
}
}
return false;
}
void SyncPreRangeAction(ulong address, ulong size)
@@ -426,10 +437,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
_syncActionRegistered = false;
if (_bufferInherited)
{
return true;
}
if (_useGranular)
{
_modifiedRanges?.GetRanges(Address, Size, _syncRangeAction);
}
else
@@ -453,6 +467,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="from">The buffer to inherit from</param>
public void InheritModifiedRanges(Buffer from)
{
from._bufferInherited = true;
if (from._modifiedRanges is { HasRanges: true })
{
if (from._syncActionRegistered && !_syncActionRegistered)

View File

@@ -56,7 +56,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="parent">Parent buffer</param>
/// <param name="stage">Initial buffer stage</param>
/// <param name="baseBuffers">Buffers to inherit state from</param>
public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, RangeItem<Buffer>[] baseBuffers)
public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, Buffer[] baseBuffers)
{
_size = (int)parent.Size;
_systemMemoryType = context.Capabilities.MemoryType;
@@ -102,9 +102,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (baseBuffers.Length != 0)
{
foreach (RangeItem<Buffer> item in baseBuffers)
foreach (Buffer item in baseBuffers)
{
CombineState(item.Value.BackingState);
CombineState(item.BackingState);
}
}
}

View File

@@ -79,16 +79,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int index = 0; index < range.Count; index++)
{
MemoryRange subRange = range.GetSubRange(index);
_buffers.Lock.EnterReadLock();
Span<RangeItem<Buffer>> overlaps = _buffers.FindOverlapsAsSpan(subRange.Address, subRange.Size);
ReadOnlySpan<Buffer> overlaps = _buffers.FindOverlapsAsSpan(subRange.Address, subRange.Size);
for (int i = 0; i < overlaps.Length; i++)
{
overlaps[i].Value.Unmapped(subRange.Address, subRange.Size);
overlaps[i].Unmapped(subRange.Address, subRange.Size);
}
_buffers.Lock.ExitReadLock();
}
}
@@ -328,7 +325,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask;
ulong alignedSize = alignedEndAddress - alignedAddress;
Buffer buffer = _buffers.FindOverlap(alignedAddress, alignedSize).Value;
Buffer buffer = _buffers.FindOverlap(alignedAddress, alignedSize);
BufferRange bufferRange = buffer.GetRange(alignedAddress, alignedSize, false);
alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize);
@@ -395,7 +392,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (subRange.Address != MemoryManager.PteUnmapped)
{
Buffer buffer = _buffers.FindOverlap(subRange.Address, subRange.Size).Value;
Buffer buffer = _buffers.FindOverlap(subRange.Address, subRange.Size);
virtualBuffer.AddPhysicalDependency(buffer, subRange.Address, dstOffset, subRange.Size);
physicalBuffers.Add(buffer);
@@ -487,10 +484,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="stage">The type of usage that created the buffer</param>
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage)
{
Buffer newBuffer = null;
_buffers.Lock.EnterWriteLock();
Span<RangeItem<Buffer>> overlaps = _buffers.FindOverlapsAsSpan(address, size);
ReadOnlySpan<Buffer> overlaps = _buffers.FindOverlapsAsSpan(address, size);
if (overlaps.Length != 0)
{
@@ -521,7 +515,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
// Try to grow the buffer by 1.5x of its current size.
// This improves performance in the cases where the buffer is resized often by small amounts.
ulong existingSize = overlaps[0].Value.Size;
ulong existingSize = overlaps[0].Size;
ulong growthSize = (existingSize + Math.Min(existingSize >> 1, MaxDynamicGrowthSize)) & ~BufferAlignmentMask;
size = Math.Max(size, growthSize);
@@ -535,39 +529,22 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int i = 0; i < overlaps.Length; i++)
{
anySparseCompatible |= overlaps[i].Value.SparseCompatible;
anySparseCompatible |= overlaps[i].SparseCompatible;
}
RangeItem<Buffer>[] overlapsArray = overlaps.ToArray();
Buffer[] overlapsArray = overlaps.ToArray();
_buffers.RemoveRange(overlaps[0], overlaps[^1]);
_buffers.Lock.ExitWriteLock();
ulong newSize = endAddress - address;
newBuffer = CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlapsArray);
}
else
{
_buffers.Lock.ExitWriteLock();
_buffers.Add(CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlapsArray));
}
}
else
{
_buffers.Lock.ExitWriteLock();
// No overlap, just create a new buffer.
newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, []);
}
if (newBuffer is not null)
{
_buffers.Lock.EnterWriteLock();
_buffers.Add(newBuffer);
_buffers.Lock.ExitWriteLock();
_buffers.Add(new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, []));
}
}
@@ -583,10 +560,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, ulong alignment)
{
bool sparseAligned = alignment >= SparseBufferAlignmentSize;
Buffer newBuffer = null;
_buffers.Lock.EnterWriteLock();
Span<RangeItem<Buffer>> overlaps = _buffers.FindOverlapsAsSpan(address, size);
ReadOnlySpan<Buffer> overlaps = _buffers.FindOverlapsAsSpan(address, size);
if (overlaps.Length != 0)
{
@@ -598,7 +573,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (overlaps[0].Address > address ||
overlaps[0].EndAddress < endAddress ||
(overlaps[0].Address & (alignment - 1)) != 0 ||
(!overlaps[0].Value.SparseCompatible && sparseAligned))
(!overlaps[0].SparseCompatible && sparseAligned))
{
// We need to make sure the new buffer is properly aligned.
// However, after the range is aligned, it is possible that it
@@ -622,35 +597,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong newSize = endAddress - address;
RangeItem<Buffer>[] overlapsArray = overlaps.ToArray();
Buffer[] overlapsArray = overlaps.ToArray();
_buffers.RemoveRange(overlaps[0], overlaps[^1]);
_buffers.Lock.ExitWriteLock();
newBuffer = CreateBufferAligned(address, newSize, stage, sparseAligned, overlapsArray);
}
else
{
_buffers.Lock.ExitWriteLock();
_buffers.Add(CreateBufferAligned(address, newSize, stage, sparseAligned, overlapsArray));
}
}
else
{
_buffers.Lock.ExitWriteLock();
// No overlap, just create a new buffer.
newBuffer = new(_context, _physicalMemory, address, size, stage, sparseAligned, []);
}
if (newBuffer is not null)
{
_buffers.Lock.EnterWriteLock();
_buffers.Add(newBuffer);
_buffers.Lock.ExitWriteLock();
}
_buffers.Add(new(_context, _physicalMemory, address, size, stage, sparseAligned, []));
}
}
/// <summary>
@@ -663,13 +621,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="stage">The type of usage that created the buffer</param>
/// <param name="sparseCompatible">Indicates if the buffer can be used in a sparse buffer mapping</param>
/// <param name="overlaps">Buffers overlapping the range</param>
private Buffer CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, RangeItem<Buffer>[] overlaps)
private Buffer CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, Buffer[] overlaps)
{
Buffer newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible, overlaps);
for (int index = 0; index < overlaps.Length; index++)
{
Buffer buffer = overlaps[index].Value;
Buffer buffer = overlaps[index];
int dstOffset = (int)(buffer.Address - newBuffer.Address);
@@ -897,7 +855,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
MemoryRange subRange = range.GetSubRange(i);
Buffer subBuffer = _buffers.FindOverlap(subRange.Address, subRange.Size).Value;
Buffer subBuffer = _buffers.FindOverlap(subRange.Address, subRange.Size);
subBuffer.SynchronizeMemory(subRange.Address, subRange.Size);
@@ -945,7 +903,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (size != 0)
{
buffer = _buffers.FindOverlap(address, size).Value;
buffer = _buffers.FindOverlap(address, size);
buffer.CopyFromDependantVirtualBuffers();
buffer.SynchronizeMemory(address, size);
@@ -957,7 +915,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
else
{
buffer = _buffers.FindOverlapFast(address, 1).Value;
buffer = _buffers.FindOverlapFast(address, 1);
}
return buffer;
@@ -995,7 +953,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
if (size != 0)
{
Buffer buffer = _buffers.FindOverlap(address, size).Value;
Buffer buffer = _buffers.FindOverlap(address, size);
if (copyBackVirtual)
{

View File

@@ -8,7 +8,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary>
/// A range within a buffer that has been modified by the GPU.
/// </summary>
class BufferModifiedRange : INonOverlappingRange
class BufferModifiedRange : INonOverlappingRange<BufferModifiedRange>
{
/// <summary>
/// Start address of the range in guest memory.
@@ -24,6 +24,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// End address of the range in guest memory.
/// </summary>
public ulong EndAddress => Address + Size;
public BufferModifiedRange Next { get; set; }
public BufferModifiedRange Previous { get; set; }
/// <summary>
/// The GPU sync number at the time of the last modification.
@@ -54,14 +57,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Checks if a given range overlaps with the modified range.
/// </summary>
/// <param name="address">Start address of the range</param>
/// <param name="size">Size in bytes of the range</param>
/// <param name="endAddress">End address of the range</param>
/// <returns>True if the range overlaps, false otherwise</returns>
public bool OverlapsWith(ulong address, ulong size)
public bool OverlapsWith(ulong address, ulong endAddress)
{
return Address < address + size && address < EndAddress;
return Address < endAddress && address < EndAddress;
}
public INonOverlappingRange Split(ulong splitAddress)
public INonOverlappingRange<BufferModifiedRange> Split(ulong splitAddress)
{
throw new NotImplementedException();
}
@@ -119,11 +122,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
// Slices a given region using the modified regions in the list. Calls the action for the new slices.
Lock.EnterReadLock();
Span<RangeItem<BufferModifiedRange>> overlaps = FindOverlapsAsSpan(address, size);
ReadOnlySpan<BufferModifiedRange> overlaps = FindOverlapsAsSpan(address, size);
for (int i = 0; i < overlaps.Length; i++)
{
BufferModifiedRange overlap = overlaps[i].Value;
BufferModifiedRange overlap = overlaps[i];
if (overlap.Address > address)
{
@@ -157,7 +160,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong syncNumber = _context.SyncNumber;
// We may overlap with some existing modified regions. They must be cut into by the new entry.
Lock.EnterWriteLock();
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlapsAsNodes(address, size);
(BufferModifiedRange first, BufferModifiedRange last) = FindOverlapsAsNodes(address, size);
if (first is null)
{
@@ -170,34 +173,39 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
if (first.Address == address && first.EndAddress == endAddress)
{
first.Value.SyncNumber = syncNumber;
first.Value.Parent = this;
first.SyncNumber = syncNumber;
first.Parent = this;
Lock.ExitWriteLock();
return;
}
if (first.Address < address)
{
first.Value.Size = address - first.Address;
Update(first);
if (first.EndAddress > endAddress)
{
Add(new BufferModifiedRange(endAddress, first.EndAddress - endAddress,
first.Value.SyncNumber, first.Value.Parent));
first.SyncNumber, first.Parent));
}
first.Size = address - first.Address;
}
else
{
if (first.EndAddress > endAddress)
{
first.Value.Size = first.EndAddress - endAddress;
first.Value.Address = endAddress;
Update(first);
first.Size = first.EndAddress - endAddress;
first.Address = endAddress;
}
else
{
Remove(first.Value);
first.Address = address;
first.Size = size;
first.SyncNumber = syncNumber;
first.Parent = this;
Lock.ExitWriteLock();
return;
}
}
@@ -207,38 +215,39 @@ namespace Ryujinx.Graphics.Gpu.Memory
return;
}
BufferModifiedRange buffPre = null;
BufferModifiedRange buffPost = null;
bool extendsPost = false;
bool extendsPre = false;
if (first.Address < address)
{
buffPre = new BufferModifiedRange(first.Address, address - first.Address,
first.Value.SyncNumber, first.Value.Parent);
extendsPre = true;
first.Size = address - first.Address;
first = first.Next;
}
if (last.EndAddress > endAddress)
{
buffPost = new BufferModifiedRange(endAddress, last.EndAddress - endAddress,
last.Value.SyncNumber, last.Value.Parent);
extendsPost = true;
last.Size = last.EndAddress - endAddress;
last.Address = endAddress;
last = last.Previous;
}
RemoveRange(first, last);
if (extendsPre)
if (first.Address < last.Address)
{
Add(buffPre);
RemoveRange(first.Next, last);
first.Address = address;
first.Size = size;
first.SyncNumber = syncNumber;
first.Parent = this;
}
if (extendsPost)
else if (first.Address == last.Address)
{
Add(buffPost);
first.Address = address;
first.Size = size;
first.SyncNumber = syncNumber;
first.Parent = this;
}
Add(new BufferModifiedRange(address, size, syncNumber, this));
else
{
Add(new BufferModifiedRange(address, size, syncNumber, this));
}
Lock.ExitWriteLock();
}
@@ -252,11 +261,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
public void GetRangesAtSync(ulong address, ulong size, ulong syncNumber, Action<ulong, ulong> rangeAction)
{
Lock.EnterReadLock();
Span<RangeItem<BufferModifiedRange>> overlaps = FindOverlapsAsSpan(address, size);
ReadOnlySpan<BufferModifiedRange> overlaps = FindOverlapsAsSpan(address, size);
for (int i = 0; i < overlaps.Length; i++)
{
BufferModifiedRange overlap = overlaps[i].Value;
BufferModifiedRange overlap = overlaps[i];
if (overlap.SyncNumber == syncNumber)
{
@@ -277,18 +286,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
// We use the non-span method here because keeping the lock will cause a deadlock.
Lock.EnterReadLock();
RangeItem<BufferModifiedRange>[] overlaps = FindOverlapsAsArray(address, size, out int length);
BufferModifiedRange[] overlaps = FindOverlapsAsArray(address, size, out int length);
Lock.ExitReadLock();
if (length != 0)
{
for (int i = 0; i < length; i++)
{
BufferModifiedRange overlap = overlaps[i].Value;
BufferModifiedRange overlap = overlaps[i];
rangeAction(overlap.Address, overlap.Size);
}
ArrayPool<RangeItem<BufferModifiedRange>>.Shared.Return(overlaps);
ArrayPool<BufferModifiedRange>.Shared.Return(overlaps);
}
}
@@ -301,7 +310,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
public bool HasRange(ulong address, ulong size)
{
Lock.EnterReadLock();
RangeItem<BufferModifiedRange> first = FindOverlapFast(address, size);
BufferModifiedRange first = FindOverlapFast(address, size);
bool result = first is not null;
Lock.ExitReadLock();
return result;
@@ -336,7 +345,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="address">The start address of the flush range</param>
/// <param name="endAddress">The end address of the flush range</param>
private void RemoveRangesAndFlush(
RangeItem<BufferModifiedRange>[] overlaps,
BufferModifiedRange[] overlaps,
int rangeCount,
long highestDiff,
ulong currentSync,
@@ -349,7 +358,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int i = 0; i < rangeCount; i++)
{
BufferModifiedRange overlap = overlaps[i].Value;
BufferModifiedRange overlap = overlaps[i];
long diff = (long)(overlap.SyncNumber - currentSync);
@@ -358,7 +367,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong clampAddress = Math.Max(address, overlap.Address);
ulong clampEnd = Math.Min(endAddress, overlap.EndAddress);
ClearPart(overlap, clampAddress, clampEnd);
if (i == 0 || i == rangeCount - 1)
{
ClearPart(overlap, clampAddress, clampEnd);
}
else
{
Remove(overlap);
}
RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, _flushAction);
}
@@ -398,7 +414,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
Lock.EnterWriteLock();
// We use the non-span method here because the array is partially modified by the code, which would invalidate a span.
RangeItem<BufferModifiedRange>[] overlaps = FindOverlapsAsArray(address, size, out int rangeCount);
BufferModifiedRange[] overlaps = FindOverlapsAsArray(address, size, out int rangeCount);
if (rangeCount == 0)
{
@@ -414,7 +430,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int i = 0; i < rangeCount; i++)
{
BufferModifiedRange overlap = overlaps![i].Value;
BufferModifiedRange overlap = overlaps![i];
long diff = (long)(overlap.SyncNumber - currentSync);
@@ -436,7 +452,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress);
ArrayPool<RangeItem<BufferModifiedRange>>.Shared.Return(overlaps!);
ArrayPool<BufferModifiedRange>.Shared.Return(overlaps!);
Lock.ExitWriteLock();
}
@@ -452,7 +468,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
public void InheritRanges(BufferModifiedRangeList ranges, Action<ulong, ulong> registerRangeAction)
{
ranges.Lock.EnterReadLock();
BufferModifiedRange[] inheritRanges = ranges.ToArray();
int rangesCount = ranges.Count;
BufferModifiedRange[] inheritRanges = ArrayPool<BufferModifiedRange>.Shared.Rent(ranges.Count);
ranges.Items.AsSpan(0, ranges.Count).CopyTo(inheritRanges);
ranges.Lock.ExitReadLock();
// Copy over the migration from the previous range list
@@ -478,22 +496,26 @@ namespace Ryujinx.Graphics.Gpu.Memory
ranges._migrationTarget = this;
Lock.EnterWriteLock();
foreach (BufferModifiedRange range in inheritRanges)
for (int i = 0; i < rangesCount; i++)
{
BufferModifiedRange range = inheritRanges[i];
Add(range);
}
Lock.ExitWriteLock();
ulong currentSync = _context.SyncNumber;
foreach (BufferModifiedRange range in inheritRanges)
for (int i = 0; i < rangesCount; i++)
{
BufferModifiedRange range = inheritRanges[i];
if (range.SyncNumber != currentSync)
{
registerRangeAction(range.Address, range.Size);
}
}
ArrayPool<BufferModifiedRange>.Shared.Return(inheritRanges);
}
/// <summary>
@@ -534,18 +556,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
private void ClearPart(BufferModifiedRange overlap, ulong address, ulong endAddress)
{
Remove(overlap);
// If the overlap extends outside of the clear range, make sure those parts still exist.
if (overlap.Address < address)
{
Add(new BufferModifiedRange(overlap.Address, address - overlap.Address, overlap.SyncNumber, overlap.Parent));
if (overlap.EndAddress > endAddress)
{
Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber, overlap.Parent));
}
overlap.Size = address - overlap.Address;
}
if (overlap.EndAddress > endAddress)
else if (overlap.EndAddress > endAddress)
{
Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber, overlap.Parent));
overlap.Size = overlap.EndAddress - endAddress;
overlap.Address = endAddress;
}
else
{
Remove(overlap);
}
}
@@ -558,7 +587,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
ulong endAddress = address + size;
Lock.EnterWriteLock();
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlapsAsNodes(address, size);
(BufferModifiedRange first, BufferModifiedRange last) = FindOverlapsAsNodes(address, size);
if (first is null)
{
@@ -570,26 +599,24 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
if (first.Address < address)
{
first.Value.Size = address - first.Address;
Update(first);
first.Size = address - first.Address;
if (first.EndAddress > endAddress)
{
Add(new BufferModifiedRange(endAddress, first.EndAddress - endAddress,
first.Value.SyncNumber, first.Value.Parent));
first.SyncNumber, first.Parent));
}
}
else
{
if (first.EndAddress > endAddress)
{
first.Value.Size = first.EndAddress - endAddress;
first.Value.Address = endAddress;
Update(first);
first.Size = first.EndAddress - endAddress;
first.Address = endAddress;
}
else
{
Remove(first.Value);
Remove(first);
}
}
@@ -605,14 +632,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (first.Address < address)
{
buffPre = new BufferModifiedRange(first.Address, address - first.Address,
first.Value.SyncNumber, first.Value.Parent);
first.SyncNumber, first.Parent);
extendsPre = true;
}
if (last.EndAddress > endAddress)
{
buffPost = new BufferModifiedRange(endAddress, last.EndAddress - endAddress,
last.Value.SyncNumber, last.Value.Parent);
last.SyncNumber, last.Parent);
extendsPost = true;
}

View File

@@ -84,10 +84,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int i = 0; i < count; i++)
{
ICounterEvent evt = _items[index + i].Event;
if (evt != null)
{
evt.Invalid = true;
}
evt?.Invalid = true;
}
_items.RemoveRange(index, count);

View File

@@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary>
/// Represents a GPU virtual memory range.
/// </summary>
private class VirtualRange : INonOverlappingRange
private class VirtualRange : INonOverlappingRange<VirtualRange>
{
/// <summary>
/// GPU virtual address where the range starts.
@@ -32,6 +32,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary>
public ulong EndAddress => Address + Size;
public VirtualRange Next { get; set; }
public VirtualRange Previous { get; set; }
/// <summary>
/// Physical regions where the GPU virtual region is mapped.
/// </summary>
@@ -54,14 +57,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Checks if a given range overlaps with the buffer.
/// </summary>
/// <param name="address">Start address of the range</param>
/// <param name="size">Size in bytes of the range</param>
/// <param name="endAddress">End address of the range</param>
/// <returns>True if the range overlaps, false otherwise</returns>
public bool OverlapsWith(ulong address, ulong size)
public bool OverlapsWith(ulong address, ulong endAddress)
{
return Address < address + size && address < EndAddress;
return Address < endAddress && address < EndAddress;
}
public INonOverlappingRange Split(ulong splitAddress)
public INonOverlappingRange<VirtualRange> Split(ulong splitAddress)
{
throw new NotImplementedException();
}
@@ -122,7 +125,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong originalVa = gpuVa;
_virtualRanges.Lock.EnterWriteLock();
(RangeItem<VirtualRange> first, RangeItem<VirtualRange> last) = _virtualRanges.FindOverlapsAsNodes(gpuVa, size);
(VirtualRange first, VirtualRange last) = _virtualRanges.FindOverlapsAsNodes(gpuVa, size);
if (first is not null)
{
@@ -147,8 +150,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
else
{
found = first.Value.Range.Count == 1 || IsSparseAligned(first.Value.Range);
range = first.Value.Range.Slice(gpuVa - first.Address, size);
found = first.Range.Count == 1 || IsSparseAligned(first.Range);
range = first.Range.Slice(gpuVa - first.Address, size);
}
}
else

View File

@@ -17,6 +17,6 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
/// Action to be performed immediately before sync is created.
/// </summary>
/// <param name="syncpoint">True if the action is a guest syncpoint</param>
void SyncPreAction(bool syncpoint) { }
bool SyncPreAction(bool syncpoint) { return true; }
}
}

View File

@@ -1166,7 +1166,7 @@ namespace Ryujinx.Graphics.OpenGL
}
}
public void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
public void SetRenderTargets(Span<ITexture> colors, ITexture depthStencil)
{
EnsureFramebuffer();

View File

@@ -26,12 +26,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
// - Both branches are jumping to the same location.
// In this case, the branch on the current block can be removed,
// as the next block is going to jump to the same place anyway.
if (nextBlock == null)
{
return false;
}
if (nextBlock.Operations.First?.Value is not Operation next)
if (nextBlock?.Operations.First?.Value is not Operation next)
{
return false;
}

View File

@@ -71,17 +71,31 @@ namespace Ryujinx.Graphics.Vulkan
HasDepthStencil = isDepthStencil;
}
public FramebufferParams(Device device, ITexture[] colors, ITexture depthStencil)
public FramebufferParams(Device device, ReadOnlySpan<ITexture> colors, ITexture depthStencil)
{
_device = device;
int colorsCount = colors.Count(IsValidTextureView);
int colorsCount = 0;
_colorsCanonical = new TextureView[colors.Length];
for (int i = 0; i < colors.Length; i++)
{
ITexture color = colors[i];
if (color is TextureView { Valid: true } view)
{
colorsCount++;
_colorsCanonical[i] = view;
}
else
{
_colorsCanonical[i] = null;
}
}
int count = colorsCount + (IsValidTextureView(depthStencil) ? 1 : 0);
_attachments = new Auto<DisposableImageView>[count];
_colors = new TextureView[colorsCount];
_colorsCanonical = colors.Select(color => color is TextureView view && view.Valid ? view : null).ToArray();
AttachmentSamples = new uint[count];
AttachmentFormats = new VkFormat[count];
@@ -165,9 +179,17 @@ namespace Ryujinx.Graphics.Vulkan
_totalCount = colors.Length;
}
public FramebufferParams Update(ITexture[] colors, ITexture depthStencil)
public FramebufferParams Update(ReadOnlySpan<ITexture> colors, ITexture depthStencil)
{
int colorsCount = colors.Count(IsValidTextureView);
int colorsCount = 0;
foreach (ITexture color in colors)
{
if (IsValidTextureView(color))
{
colorsCount++;
}
}
int count = colorsCount + (IsValidTextureView(depthStencil) ? 1 : 0);

View File

@@ -1,7 +1,7 @@
using Ryujinx.Common;
using Ryujinx.Common.Memory;
using Silk.NET.Vulkan;
using System;
using System.Buffers;
namespace Ryujinx.Graphics.Vulkan
{
@@ -10,6 +10,8 @@ namespace Ryujinx.Graphics.Vulkan
/// </summary>
class MultiFenceHolder
{
public static readonly ObjectPool<FenceHolder[]> FencePool = new(() => new FenceHolder[CommandBufferPool.MaxCommandBuffers]);
private const int BufferUsageTrackingGranularity = 4096;
public FenceHolder[] Fences { get; }
@@ -20,7 +22,7 @@ namespace Ryujinx.Graphics.Vulkan
/// </summary>
public MultiFenceHolder()
{
Fences = ArrayPool<FenceHolder>.Shared.Rent(CommandBufferPool.MaxCommandBuffers);
Fences = FencePool.Allocate();
}
/// <summary>
@@ -29,7 +31,7 @@ namespace Ryujinx.Graphics.Vulkan
/// <param name="size">Size of the buffer</param>
public MultiFenceHolder(int size)
{
Fences = ArrayPool<FenceHolder>.Shared.Rent(CommandBufferPool.MaxCommandBuffers);
Fences = FencePool.Allocate();
_bufferUsageBitmap = new BufferUsageBitmap(size, BufferUsageTrackingGranularity);
}

View File

@@ -1035,7 +1035,7 @@ namespace Ryujinx.Graphics.Vulkan
}
}
private void SetRenderTargetsInternal(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked)
private void SetRenderTargetsInternal(Span<ITexture> colors, ITexture depthStencil, bool filterWriteMasked)
{
CreateFramebuffer(colors, depthStencil, filterWriteMasked);
CreateRenderPass();
@@ -1043,7 +1043,7 @@ namespace Ryujinx.Graphics.Vulkan
SignalAttachmentChange();
}
public void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
public void SetRenderTargets(Span<ITexture> colors, ITexture depthStencil)
{
_framebufferUsingColorWriteMask = false;
SetRenderTargetsInternal(colors, depthStencil, Gd.IsTBDR);
@@ -1389,7 +1389,7 @@ namespace Ryujinx.Graphics.Vulkan
_currentPipelineHandle = 0;
}
private void CreateFramebuffer(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked)
private void CreateFramebuffer(Span<ITexture> colors, ITexture depthStencil, bool filterWriteMasked)
{
if (filterWriteMasked)
{
@@ -1399,7 +1399,7 @@ namespace Ryujinx.Graphics.Vulkan
// Just try to remove duplicate attachments.
// Save a copy of the array to rebind when mask changes.
void MaskOut()
void MaskOut(ReadOnlySpan<ITexture> colors)
{
if (!_framebufferUsingColorWriteMask)
{
@@ -1436,12 +1436,12 @@ namespace Ryujinx.Graphics.Vulkan
if (vkBlend.ColorWriteMask == 0)
{
colors[i] = null;
MaskOut();
MaskOut(colors);
}
else if (vkBlend2.ColorWriteMask == 0)
{
colors[j] = null;
MaskOut();
MaskOut(colors);
}
}
}

View File

@@ -1,6 +1,6 @@
using Ryujinx.Common.Logging;
using Silk.NET.Vulkan;
using System.Buffers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@@ -193,7 +193,8 @@ namespace Ryujinx.Graphics.Vulkan
{
_firstHandle = first.ID + 1;
_handles.RemoveAt(0);
ArrayPool<FenceHolder>.Shared.Return(first.Waitable.Fences);
Array.Clear(first.Waitable.Fences);
MultiFenceHolder.FencePool.Release(first.Waitable.Fences);
first.Waitable = null;
}
}

View File

@@ -2,7 +2,7 @@ using System;
namespace Ryujinx.HLE.Exceptions
{
class InvalidFirmwarePackageException : Exception
public class InvalidFirmwarePackageException : Exception
{
public InvalidFirmwarePackageException(string message) : base(message) { }
}

View File

@@ -483,10 +483,29 @@ namespace Ryujinx.HLE.FileSystem
{
if (Directory.Exists(keysSource))
{
foreach (string filePath in Directory.EnumerateFiles(keysSource, "*.keys"))
string[] keyPaths = Directory.EnumerateFiles(keysSource, "*.keys").ToArray();
if (keyPaths.Length is 0)
throw new FileNotFoundException($"Directory '{keysSource}' contained no '.keys' files.");
foreach (string filePath in keyPaths)
{
VerifyKeysFile(filePath);
File.Copy(filePath, Path.Combine(installDirectory, Path.GetFileName(filePath)), true);
try
{
VerifyKeysFile(filePath);
}
catch (Exception e)
{
Logger.Error?.Print(LogClass.Application, e.Message);
continue;
}
string destPath = Path.Combine(installDirectory, Path.GetFileName(filePath));
if (File.Exists(destPath))
File.Delete(destPath);
File.Copy(filePath, destPath, true);
}
return;
@@ -501,13 +520,25 @@ namespace Ryujinx.HLE.FileSystem
using FileStream file = File.OpenRead(keysSource);
if (info.Extension is ".keys")
if (info.Extension is not ".keys")
throw new InvalidFirmwarePackageException("Input file extension is not .keys");
try
{
VerifyKeysFile(keysSource);
File.Copy(keysSource, Path.Combine(installDirectory, info.Name), true);
}
else
}
catch
{
throw new InvalidFirmwarePackageException("Input file is not a valid key package");
}
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);
}
private void FinishInstallation(string temporaryDirectory, string registeredDirectory)
@@ -517,6 +548,9 @@ namespace Ryujinx.HLE.FileSystem
new DirectoryInfo(registeredDirectory).Delete(true);
}
if (!Directory.Exists(temporaryDirectory))
return; // nothing to move
Directory.Move(temporaryDirectory, registeredDirectory);
LoadEntries();
@@ -985,8 +1019,8 @@ namespace Ryujinx.HLE.FileSystem
public static void VerifyKeysFile(string filePath)
{
// Verify the keys file format refers to https://github.com/Thealexbarney/LibHac/blob/master/KEYS.md
string genericPattern = @"^[a-z0-9_]+ = [a-z0-9]+$";
string titlePattern = @"^[a-z0-9]{32} = [a-z0-9]{32}$";
string genericPattern = "^[a-z0-9_]+ = [a-z0-9]+$";
string titlePattern = "^[a-z0-9]{32} = [a-z0-9]{32}$";
if (File.Exists(filePath))
{
@@ -994,24 +1028,13 @@ namespace Ryujinx.HLE.FileSystem
string fileName = Path.GetFileName(filePath);
string[] lines = File.ReadAllLines(filePath);
bool verified;
switch (fileName)
bool verified = fileName switch
{
case "prod.keys":
verified = VerifyKeys(lines, genericPattern);
break;
case "title.keys":
verified = VerifyKeys(lines, titlePattern);
break;
case "console.keys":
verified = VerifyKeys(lines, genericPattern);
break;
case "dev.keys":
verified = VerifyKeys(lines, genericPattern);
break;
default:
throw new FormatException($"Keys file name \"{fileName}\" not supported. Only \"prod.keys\", \"title.keys\", \"console.keys\", \"dev.keys\" are supported.");
}
"prod.keys" or "console.keys" or "dev.keys" => VerifyKeys(lines, genericPattern),
"title.keys" => VerifyKeys(lines, titlePattern),
_ => throw new FormatException(
$"Keys file name \"{fileName}\" not supported. Only \"prod.keys\", \"title.keys\", \"console.keys\", \"dev.keys\" are supported.")
};
if (!verified)
{

View File

@@ -219,6 +219,8 @@ namespace Ryujinx.HLE.FileSystem
FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, fsServerConfig);
}
public bool HasKeySet { get; private set; }
public void ReloadKeySet()
{
KeySet ??= KeySet.CreateDefaultKeySet();
@@ -228,12 +230,19 @@ namespace Ryujinx.HLE.FileSystem
string consoleKeyFile = null;
string devKeyFile = null;
if (AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile)
{
LoadSetAtPath(AppDataManager.KeysDirPathUser);
}
LoadSetAtPath(AppDataManager.GetKeysDir());
LoadSetAtPath(AppDataManager.KeysDirPath);
HasKeySet = (prodKeyFile != null && titleKeyFile != null) || prodKeyFile != null;
ExternalKeyReader.ReadKeyFile(
KeySet,
prodKeyFile,
devKeyFile,
titleKeyFile,
consoleKeyFile);
return;
void LoadSetAtPath(string basePath)
{
@@ -262,8 +271,6 @@ namespace Ryujinx.HLE.FileSystem
devKeyFile = localDevKeyFile;
}
}
ExternalKeyReader.ReadKeyFile(KeySet, prodKeyFile, devKeyFile, titleKeyFile, consoleKeyFile, null);
}
public void ImportTickets(IFileSystem fs)

View File

@@ -891,10 +891,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
result = new NestedName(name, prev);
}
if (context != null)
{
context.FinishWithTemplateArguments = false;
}
context?.FinishWithTemplateArguments = false;
return result;
}
@@ -1074,10 +1071,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
return null;
}
if (context != null)
{
context.CtorDtorConversion = true;
}
context?.CtorDtorConversion = true;
return new ConversionOperatorType(type);
default:
@@ -1349,10 +1343,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
_position++;
if (context != null)
{
context.CtorDtorConversion = true;
}
context?.CtorDtorConversion = true;
if (isInherited && ParseName(context) == null)
{
@@ -1372,10 +1363,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
_position++;
if (context != null)
{
context.CtorDtorConversion = true;
}
context?.CtorDtorConversion = true;
return new CtorDtorNameType(prev, true);
}
@@ -3005,16 +2993,10 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
BaseNode result = null;
CvType cv = new(ParseCvQualifiers(), null);
if (context != null)
{
context.Cv = cv;
}
context?.Cv = cv;
SimpleReferenceType Ref = ParseRefQualifiers();
if (context != null)
{
context.Ref = Ref;
}
context?.Ref = Ref;
if (ConsumeIf("St"))
{
@@ -3060,10 +3042,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
}
result = new NameTypeWithTemplateArguments(result, templateArgument);
if (context != null)
{
context.FinishWithTemplateArguments = true;
}
context?.FinishWithTemplateArguments = true;
_substitutionList.Add(result);
continue;
@@ -3256,10 +3235,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
return null;
}
if (context != null)
{
context.FinishWithTemplateArguments = true;
}
context?.FinishWithTemplateArguments = true;
return new NameTypeWithTemplateArguments(substitution, templateArguments);
}
@@ -3279,10 +3255,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
return null;
}
if (context != null)
{
context.FinishWithTemplateArguments = true;
}
context?.FinishWithTemplateArguments = true;
return new NameTypeWithTemplateArguments(result, templateArguments);
}

View File

@@ -2,6 +2,7 @@ using Microsoft.IO;
using Ryujinx.Common;
using Ryujinx.Common.Memory;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
@@ -37,7 +38,7 @@ namespace Ryujinx.HLE.HOS.Ipc
public IpcMessage(ReadOnlySpan<byte> data, long cmdPtr)
{
using RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data);
RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data);
BinaryReader reader = new(ms);
@@ -123,6 +124,8 @@ namespace Ryujinx.HLE.HOS.Ipc
}
ObjectIds = [];
MemoryStreamManager.Shared.ReleaseStream(ms);
}
public RecyclableMemoryStream GetStream(long cmdPtr, ulong recvListAddr)

View File

@@ -20,6 +20,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
_exchangeBufferDescriptors = new List<KBufferDescriptor>(MaxInternalBuffersCount);
}
public KBufferDescriptorTable Clear()
{
_sendBufferDescriptors.Clear();
_receiveBufferDescriptors.Clear();
_exchangeBufferDescriptors.Clear();
return this;
}
public Result AddSendBuffer(ulong src, ulong dst, ulong size, MemoryState state)
{
return Add(_sendBufferDescriptors, src, dst, size, state);

View File

@@ -1,3 +1,4 @@
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading;
@@ -32,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
KThread currentThread = KernelStatic.GetCurrentThread();
KSessionRequest request = new(currentThread, customCmdBuffAddr, customCmdBuffSize);
KSessionRequest request = _parent.ServerSession.RequestPool.Allocate().Set(currentThread, customCmdBuffAddr, customCmdBuffSize);
KernelContext.CriticalSection.Enter();
@@ -55,7 +56,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
KThread currentThread = KernelStatic.GetCurrentThread();
KSessionRequest request = new(currentThread, customCmdBuffAddr, customCmdBuffSize, asyncEvent);
KSessionRequest request = _parent.ServerSession.RequestPool.Allocate().Set(currentThread, customCmdBuffAddr, customCmdBuffSize, asyncEvent);
KernelContext.CriticalSection.Enter();

View File

@@ -10,6 +10,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
class KServerSession : KSynchronizationObject
{
public readonly ObjectPool<KSessionRequest> RequestPool = new(() => new KSessionRequest());
private static readonly MemoryState[] _ipcMemoryStates =
[
MemoryState.IpcBuffer3,
@@ -274,6 +276,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
KernelContext.CriticalSection.Leave();
WakeClientThread(request, clientResult);
RequestPool.Release(request);
}
if (clientHeader.ReceiveListType < 2 &&
@@ -627,6 +631,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
CloseAllHandles(clientMsg, serverHeader, clientProcess);
FinishRequest(request, clientResult);
RequestPool.Release(request);
}
if (clientHeader.ReceiveListType < 2 &&
@@ -865,6 +871,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
// Unmap buffers from server.
FinishRequest(request, clientResult);
RequestPool.Release(request);
return serverResult;
}
@@ -1098,6 +1106,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
foreach (KSessionRequest request in IterateWithRemovalOfAllRequests())
{
FinishRequest(request, KernelResult.PortRemoteClosed);
RequestPool.Release(request);
}
}
@@ -1117,6 +1127,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
SendResultToAsyncRequestClient(request, KernelResult.PortRemoteClosed);
}
RequestPool.Release(request);
}
WakeServerThreads(KernelResult.PortRemoteClosed);

View File

@@ -5,18 +5,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
class KSessionRequest
{
public KBufferDescriptorTable BufferDescriptorTable { get; }
public KBufferDescriptorTable BufferDescriptorTable { get; private set; }
public KThread ClientThread { get; }
public KThread ClientThread { get; private set; }
public KProcess ServerProcess { get; set; }
public KWritableEvent AsyncEvent { get; }
public KWritableEvent AsyncEvent { get; private set; }
public ulong CustomCmdBuffAddr { get; }
public ulong CustomCmdBuffSize { get; }
public ulong CustomCmdBuffAddr { get; private set; }
public ulong CustomCmdBuffSize { get; private set; }
public KSessionRequest(
public KSessionRequest Set(
KThread clientThread,
ulong customCmdBuffAddr,
ulong customCmdBuffSize,
@@ -27,7 +27,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
CustomCmdBuffSize = customCmdBuffSize;
AsyncEvent = asyncEvent;
BufferDescriptorTable = new KBufferDescriptorTable();
BufferDescriptorTable = BufferDescriptorTable?.Clear() ?? new KBufferDescriptorTable();
return this;
}
}
}

View File

@@ -1,10 +1,8 @@
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.Horizon.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace Ryujinx.HLE.HOS.Kernel.Threading
@@ -12,12 +10,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
class KAddressArbiter
{
private const int HasListenersMask = 0x40000000;
private static readonly ObjectPool<KThread[]> _threadArrayPool = new(() => []);
private readonly KernelContext _context;
private readonly List<KThread> _condVarThreads;
private readonly List<KThread> _arbiterThreads;
private readonly Dictionary<ulong, List<KThread>> _condVarThreads;
private readonly Dictionary<ulong, List<KThread>> _arbiterThreads;
private readonly ByDynamicPriority _byDynamicPriority;
public KAddressArbiter(KernelContext context)
{
@@ -25,6 +23,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
_condVarThreads = [];
_arbiterThreads = [];
_byDynamicPriority = new ByDynamicPriority();
}
public Result ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle)
@@ -140,9 +139,23 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.MutexAddress = mutexAddress;
currentThread.ThreadHandleForUserMutex = threadHandle;
currentThread.CondVarAddress = condVarAddress;
_condVarThreads.Add(currentThread);
if (_condVarThreads.TryGetValue(condVarAddress, out List<KThread> threads))
{
int i = 0;
if (threads.Count > 0)
{
i = threads.BinarySearch(currentThread, _byDynamicPriority);
if (i < 0) i = ~i;
}
threads.Insert(i, currentThread);
}
else
{
_condVarThreads.Add(condVarAddress, [currentThread]);
}
if (timeout != 0)
{
@@ -165,7 +178,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.MutexOwner?.RemoveMutexWaiter(currentThread);
_condVarThreads.Remove(currentThread);
_condVarThreads[condVarAddress].Remove(currentThread);
_context.CriticalSection.Leave();
@@ -200,13 +213,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
_context.CriticalSection.Enter();
static bool SignalProcessWideKeyPredicate(KThread thread, ulong address)
int validThreads = 0;
_condVarThreads.TryGetValue(address, out List<KThread> threads);
if (threads is not null && threads.Count > 0)
{
return thread.CondVarAddress == address;
validThreads = WakeThreads(threads, count, TryAcquireMutex);
}
int validThreads = WakeThreads(_condVarThreads, count, TryAcquireMutex, SignalProcessWideKeyPredicate, address);
if (validThreads == 0)
{
KernelTransfer.KernelToUser(address, 0);
@@ -315,9 +329,24 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.MutexAddress = address;
currentThread.WaitingInArbitration = true;
if (_arbiterThreads.TryGetValue(address, out List<KThread> threads))
{
int i = 0;
_arbiterThreads.Add(currentThread);
if (threads.Count > 0)
{
i = threads.BinarySearch(currentThread, _byDynamicPriority);
if (i < 0) i = ~i;
}
threads.Insert(i, currentThread);
}
else
{
_arbiterThreads.Add(address, [currentThread]);
}
currentThread.Reschedule(ThreadSchedState.Paused);
if (timeout > 0)
@@ -336,7 +365,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
if (currentThread.WaitingInArbitration)
{
_arbiterThreads.Remove(currentThread);
_arbiterThreads[address].Remove(currentThread);
currentThread.WaitingInArbitration = false;
}
@@ -392,9 +421,24 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.MutexAddress = address;
currentThread.WaitingInArbitration = true;
if (_arbiterThreads.TryGetValue(address, out List<KThread> threads))
{
int i = 0;
_arbiterThreads.Add(currentThread);
if (threads.Count > 0)
{
i = threads.BinarySearch(currentThread, _byDynamicPriority);
if (i < 0) i = ~i;
}
threads.Insert(i, currentThread);
}
else
{
_arbiterThreads.Add(address, [currentThread]);
}
currentThread.Reschedule(ThreadSchedState.Paused);
if (timeout > 0)
@@ -413,7 +457,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
if (currentThread.WaitingInArbitration)
{
_arbiterThreads.Remove(currentThread);
_arbiterThreads[address].Remove(currentThread);
currentThread.WaitingInArbitration = false;
}
@@ -486,15 +530,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
// or equal to the Count of threads to be signaled, or Count is zero
// or negative. It is incremented if there are no threads waiting.
int waitingCount = 0;
foreach (KThread thread in _arbiterThreads)
if (_arbiterThreads.TryGetValue(address, out List<KThread> threads))
{
if (thread.MutexAddress == address &&
++waitingCount >= count)
{
break;
}
waitingCount = threads.Count;
}
if (waitingCount > 0)
{
@@ -561,55 +602,38 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
thread.WaitingInArbitration = false;
}
static bool ArbiterThreadPredecate(KThread thread, ulong address)
{
return thread.MutexAddress == address;
}
_arbiterThreads.TryGetValue(address, out List<KThread> threads);
WakeThreads(_arbiterThreads, count, RemoveArbiterThread, ArbiterThreadPredecate, address);
if (threads is not null && threads.Count > 0)
{
WakeThreads(threads, count, RemoveArbiterThread);
}
}
private static int WakeThreads(
List<KThread> threads,
int count,
Action<KThread> removeCallback,
Func<KThread, ulong, bool> predicate,
ulong address = 0)
Action<KThread> removeCallback)
{
KThread[] candidates = _threadArrayPool.Allocate();
if (candidates.Length < threads.Count)
{
Array.Resize(ref candidates, threads.Count);
}
int validCount = 0;
for (int i = 0; i < threads.Count; i++)
{
if (predicate(threads[i], address))
{
candidates[validCount++] = threads[i];
}
}
Span<KThread> candidatesSpan = candidates.AsSpan(..validCount);
candidatesSpan.Sort((x, y) => (x.DynamicPriority.CompareTo(y.DynamicPriority)));
int validCount = count > 0 ? Math.Min(count, threads.Count) : threads.Count;
if (count > 0)
{
candidatesSpan = candidatesSpan[..Math.Min(count, candidatesSpan.Length)];
}
foreach (KThread thread in candidatesSpan)
for (int i = 0; i < validCount; i++)
{
KThread thread = threads[i];
removeCallback(thread);
threads.Remove(thread);
}
_threadArrayPool.Release(candidates);
threads.RemoveRange(0, validCount);
return validCount;
}
private class ByDynamicPriority : IComparer<KThread>
{
public int Compare(KThread x, KThread y)
{
return x!.DynamicPriority.CompareTo(y!.DynamicPriority);
}
}
}
}

View File

@@ -174,10 +174,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
if (previousThread != nextThread)
{
if (previousThread != null)
{
previousThread.LastScheduledTime = PerformanceCounter.ElapsedTicks;
}
previousThread?.LastScheduledTime = PerformanceCounter.ElapsedTicks;
_state.SelectedThread = nextThread;
_state.NeedsScheduling = true;

View File

@@ -61,8 +61,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
public KSynchronizationObject SignaledObj { get; set; }
public ulong CondVarAddress { get; set; }
private ulong _entrypoint;
private ThreadStart _customThreadStart;
private bool _forcedUnschedulable;

View File

@@ -416,7 +416,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
return ResultCode.InvalidParameters;
}
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { albumReportOption });
context.Device.UIHandler.TakeScreenshot();
return ResultCode.Success;
}

View File

@@ -0,0 +1,32 @@
using System;
using Ryujinx.Common.Logging;
using Ryujinx.Horizon.Common;
namespace Ryujinx.HLE.HOS.Services.Ectx
{
class IContextRegistrar : DisposableIpcService
{
public IContextRegistrar(ServiceCtx context) { }
[CommandCmif(0)] // 11.0.0+
// Complete(nn::Result result, buffer<bytes, 5> raw_context) -> (i32 context_descriptor)
public ResultCode Complete(ServiceCtx context)
{
Result result = new(context.RequestData.ReadInt32());
ulong rawContextPosition = context.Request.SendBuff[0].Position;
ulong rawContextSize = context.Request.SendBuff[0].Size;
byte[] rawContext = new byte[rawContextSize];
context.Memory.Read(rawContextPosition, rawContext);
context.ResponseData.Write(0); // TODO: return context_descriptor
Logger.Stub?.PrintStub(LogClass.ServiceEctx, $"Result: {result}, rawContext: {Convert.ToHexString(rawContext)}" );
return ResultCode.Success;
}
protected override void Dispose(bool isDisposing) { }
}
}

View File

@@ -4,5 +4,14 @@ namespace Ryujinx.HLE.HOS.Services.Ectx
class IWriterForApplication : IpcService
{
public IWriterForApplication(ServiceCtx context) { }
[CommandCmif(0)]
// CreateContextRegistrar() -> object<nn::err::context::IContextRegistrar>
public ResultCode CreateContextRegistrar(ServiceCtx context)
{
MakeObject(context, new IContextRegistrar(context));
return ResultCode.Success;
}
}
}

View File

@@ -23,6 +23,9 @@ namespace Ryujinx.HLE.HOS.Services
private int _selfId;
private bool _isDomain;
// cache array so we don't recreate it all the time
private object[] _parameters = [null];
public IpcService(ServerBase server = null, bool registerTipc = false)
{
Stopwatch sw = Stopwatch.StartNew();
@@ -146,7 +149,9 @@ namespace Ryujinx.HLE.HOS.Services
{
Logger.Trace?.Print(LogClass.KernelIpc, $"{service.GetType().Name}: {processRequest.Name}");
result = (ResultCode)processRequest.Invoke(service, [context]);
_parameters[0] = context;
result = (ResultCode)processRequest.Invoke(service, _parameters);
}
else
{
@@ -196,7 +201,9 @@ namespace Ryujinx.HLE.HOS.Services
{
Logger.Debug?.Print(LogClass.KernelIpc, $"{GetType().Name}: {processRequest.Name}");
result = (ResultCode)processRequest.Invoke(this, [context]);
_parameters[0] = context;
result = (ResultCode)processRequest.Invoke(this, _parameters);
}
else
{

View File

@@ -334,7 +334,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption
private static string GetKeyRetailBinPath()
{
return Path.Combine(AppDataManager.KeysDirPath, "key_retail.bin");
return Path.Combine(AppDataManager.GetKeysDir(), "key_retail.bin");
}
public static bool HasAmiiboKeyFile => File.Exists(GetKeyRetailBinPath());

View File

@@ -59,6 +59,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv
// TODO: This should call set:sys::GetDebugModeFlag
private readonly bool _debugModeEnabled = false;
private byte[] _ioctl2Buffer = [];
private byte[] _ioctlArgumentBuffer = [];
private byte[] _ioctl3Buffer = [];
public INvDrvServices(ServiceCtx context) : base(context.Device.System.NvDrvServer)
{
@@ -128,27 +132,38 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if (!context.Memory.TryReadUnsafe(inputDataPosition, (int)inputDataSize, out arguments))
{
arguments = new byte[inputDataSize];
if (_ioctlArgumentBuffer.Length < (int)inputDataSize)
{
Array.Resize(ref _ioctlArgumentBuffer, (int)inputDataSize);
}
arguments = _ioctlArgumentBuffer.AsSpan(0, (int)inputDataSize);
context.Memory.Read(inputDataPosition, arguments);
}
else
{
arguments = arguments.ToArray();
}
}
else if (isWrite)
{
byte[] outputData = new byte[outputDataSize];
arguments = new Span<byte>(outputData);
if (_ioctlArgumentBuffer.Length < (int)outputDataSize)
{
Array.Resize(ref _ioctlArgumentBuffer, (int)outputDataSize);
}
arguments = _ioctlArgumentBuffer.AsSpan(0, (int)outputDataSize);
}
else
{
byte[] temp = new byte[inputDataSize];
if (!context.Memory.TryReadUnsafe(inputDataPosition, (int)inputDataSize, out arguments))
{
if (_ioctlArgumentBuffer.Length < (int)inputDataSize)
{
Array.Resize(ref _ioctlArgumentBuffer, (int)inputDataSize);
}
arguments = _ioctlArgumentBuffer.AsSpan(0, (int)inputDataSize);
context.Memory.Read(inputDataPosition, temp);
arguments = new Span<byte>(temp);
context.Memory.Read(inputDataPosition, arguments);
}
}
return NvResult.Success;
@@ -270,7 +285,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0)
{
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray());
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments);
}
}
}
@@ -474,13 +489,15 @@ namespace Ryujinx.HLE.HOS.Services.Nv
errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments);
byte[] inlineInBuffer = null;
if (!context.Memory.TryReadUnsafe(inlineInBufferPosition, (int)inlineInBufferSize, out Span<byte> inlineInBufferSpan))
{
inlineInBuffer = _byteArrayPool.Rent((int)inlineInBufferSize);
inlineInBufferSpan = inlineInBuffer;
context.Memory.Read(inlineInBufferPosition, inlineInBufferSpan[..(int)inlineInBufferSize]);
if (_ioctl2Buffer.Length < (int)inlineInBufferSize)
{
Array.Resize(ref _ioctl2Buffer, (int)inlineInBufferSize);
}
inlineInBufferSpan = _ioctl2Buffer.AsSpan(0, (int)inlineInBufferSize);
context.Memory.Read(inlineInBufferPosition, inlineInBufferSpan);
}
if (errorCode == NvResult.Success)
@@ -489,7 +506,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if (errorCode == NvResult.Success)
{
NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBufferSpan[..(int)inlineInBufferSize]);
NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBufferSpan);
if (internalResult == NvInternalResult.NotImplemented)
{
@@ -500,15 +517,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0)
{
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray());
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments);
}
}
}
if (inlineInBuffer is not null)
{
_byteArrayPool.Return(inlineInBuffer);
}
}
context.ResponseData.Write((uint)errorCode);
@@ -531,13 +543,15 @@ namespace Ryujinx.HLE.HOS.Services.Nv
errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments);
byte[] inlineOutBuffer = null;
if (!context.Memory.TryReadUnsafe(inlineOutBufferPosition, (int)inlineOutBufferSize, out Span<byte> inlineOutBufferSpan))
{
inlineOutBuffer = _byteArrayPool.Rent((int)inlineOutBufferSize);
inlineOutBufferSpan = inlineOutBuffer;
context.Memory.Read(inlineOutBufferPosition, inlineOutBufferSpan[..(int)inlineOutBufferSize]);
if (_ioctl3Buffer.Length < (int)inlineOutBufferSize)
{
Array.Resize(ref _ioctl3Buffer, (int)inlineOutBufferSize);
}
inlineOutBufferSpan = _ioctl3Buffer.AsSpan(0, (int)inlineOutBufferSize);
context.Memory.Read(inlineOutBufferPosition, inlineOutBufferSpan);
}
if (errorCode == NvResult.Success)
@@ -546,7 +560,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if (errorCode == NvResult.Success)
{
NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBufferSpan[..(int)inlineOutBufferSize]);
NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBufferSpan);
if (internalResult == NvInternalResult.NotImplemented)
{
@@ -557,16 +571,11 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0)
{
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray());
context.Memory.Write(inlineOutBufferPosition, inlineOutBufferSpan[..(int)inlineOutBufferSize].ToArray());
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments);
context.Memory.Write(inlineOutBufferPosition, inlineOutBufferSpan);
}
}
}
if (inlineOutBuffer is not null)
{
_byteArrayPool.Return(inlineOutBuffer);
}
}
context.ResponseData.Write((uint)errorCode);

View File

@@ -454,8 +454,9 @@ namespace Ryujinx.HLE.HOS.Services
response.RawData = _responseDataStream.ToArray();
using RecyclableMemoryStream responseStream = response.GetStreamTipc();
RecyclableMemoryStream responseStream = response.GetStreamTipc();
_selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence());
MemoryStreamManager.Shared.ReleaseStream(responseStream);
}
else
{
@@ -464,8 +465,9 @@ namespace Ryujinx.HLE.HOS.Services
if (!isTipcCommunication)
{
using RecyclableMemoryStream responseStream = response.GetStream((long)_selfThread.TlsAddress, recvListAddr | ((ulong)PointerBufferSize << 48));
RecyclableMemoryStream responseStream = response.GetStream((long)_selfThread.TlsAddress, recvListAddr | ((ulong)PointerBufferSize << 48));
_selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence());
MemoryStreamManager.Shared.ReleaseStream(responseStream);
}
return shouldReply;

View File

@@ -1169,9 +1169,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
public override void DestroyAtExit()
{
if (_context != null) {
_context.Dispose();
}
_context?.Dispose();
}
}
}

View File

@@ -68,5 +68,10 @@ namespace Ryujinx.HLE.UI
/// Displays the player select dialog and returns the selected profile.
/// </summary>
UserProfile ShowPlayerSelectDialog();
/// <summary>
/// Takes a screenshot from the current renderer and saves it in the screenshots folder.
/// </summary>
void TakeScreenshot();
}
}

View File

@@ -19,6 +19,9 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
private int _waitingThreadHandle;
private MultiWaitHolderBase _signaledHolder;
ObjectPool<int[]> _objectHandlePool = new(() => new int[64]);
ObjectPool<MultiWaitHolderBase[]> _objectPool = new(() => new MultiWaitHolderBase[64]);
public long CurrentTime { get; private set; }
@@ -76,11 +79,15 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
private MultiWaitHolderBase WaitAnyHandleImpl(bool infinite, long timeout)
{
Span<int> objectHandles = new int[64];
int[] objectHandles = _objectHandlePool.Allocate();
Span<int> objectHandlesSpan = objectHandles;
objectHandlesSpan.Clear();
Span<MultiWaitHolderBase> objects = new MultiWaitHolderBase[64];
MultiWaitHolderBase[] objects = _objectPool.Allocate();
Span<MultiWaitHolderBase> objectsSpan = objects;
objectsSpan.Clear();
int count = FillObjectsArray(objectHandles, objects);
int count = FillObjectsArray(objectHandlesSpan, objectsSpan);
long endTime = infinite ? long.MaxValue : PerformanceCounter.ElapsedMilliseconds * 1000000;
@@ -98,7 +105,7 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
}
else
{
index = WaitSynchronization(objectHandles[..count], minTimeout);
index = WaitSynchronization(objectHandlesSpan[..count], minTimeout);
DebugUtil.Assert(index != WaitInvalid);
}
@@ -116,12 +123,18 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
{
_signaledHolder = minTimeoutObject;
_objectHandlePool.Release(objectHandles);
_objectPool.Release(objects);
return _signaledHolder;
}
}
}
else
{
_objectHandlePool.Release(objectHandles);
_objectPool.Release(objects);
return null;
}
@@ -131,6 +144,9 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
{
if (_signaledHolder != null)
{
_objectHandlePool.Release(objectHandles);
_objectPool.Release(objects);
return _signaledHolder;
}
}
@@ -139,8 +155,11 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
default:
lock (_lock)
{
_signaledHolder = objects[index];
_signaledHolder = objectsSpan[index];
_objectHandlePool.Release(objectHandles);
_objectPool.Release(objects);
return _signaledHolder;
}
}

View File

@@ -22,7 +22,7 @@ namespace Ryujinx.Input.SDL3
private StandardControllerInputConfig _configuration;
private static readonly SDL_GamepadButton[] _buttonsDriverMapping =
private readonly SDL_GamepadButton[] _buttonsDriverMapping =
[
// Unbound, ignored.
SDL_GamepadButton.SDL_GAMEPAD_BUTTON_INVALID,
@@ -88,6 +88,20 @@ namespace Ryujinx.Input.SDL3
Features = GetFeaturesFlag();
_triggerThreshold = 0.0f;
// Face button mapping
SDL_GamepadButton[] faceButtons = _buttonsDriverMapping[1..5];
foreach (SDL_GamepadButton btn in faceButtons) {
int mapId = SDL_GetGamepadButtonLabel(_gamepadHandle, btn) switch {
SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_A or SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_CROSS => 1,
SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_B or SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_CIRCLE => 2,
SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_X or SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_SQUARE => 3,
SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_Y or SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE => 4,
_ => -1
};
if (mapId == -1) { continue; }
_buttonsDriverMapping[mapId] = btn;
}
// Enable motion tracking
if ((Features & GamepadFeaturesFlag.Motion) != 0)
{

View File

@@ -65,7 +65,7 @@ namespace Ryujinx.Input.SDL3
string strGuid = new(guidBytes);
return $"{strGuid[0..8]}-{strGuid[8..12]}-{strGuid[12..16]}-{strGuid[16..20]}-{strGuid[20..32]}";
return $"{strGuid[4..6]}{strGuid[6..8]}{strGuid[2..4]}{strGuid[0..2]}-{strGuid[10..12]}{strGuid[8..10]}-{strGuid[12..16]}-{strGuid[16..20]}-{strGuid[20..32]}";
}

View File

@@ -532,8 +532,6 @@ namespace Ryujinx.Input.HLE
hidKeyboard.Modifier |= value << entry.Target;
}
ArrayPool<bool>.Shared.Return(keyboardState.KeysState);
return hidKeyboard;

View File

@@ -20,7 +20,6 @@ namespace Ryujinx.Input.HLE
{
public class NpadManager : IDisposable
{
private static readonly ObjectPool<List<SixAxisInput>> _hleMotionStatesPool = new (() => new List<SixAxisInput>(NpadDevices.MaxControllers));
private readonly CemuHookClient _cemuHookClient;
private readonly Lock _lock = new();
@@ -40,6 +39,9 @@ namespace Ryujinx.Input.HLE
private bool _enableKeyboard;
private bool _enableMouse;
private Switch _device;
private readonly List<GamepadInput> _hleInputStates = [];
private readonly List<SixAxisInput> _hleMotionStates = new(NpadDevices.MaxControllers);
public NpadManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IGamepadDriver mouseDriver)
{
@@ -217,8 +219,8 @@ namespace Ryujinx.Input.HLE
{
lock (_lock)
{
List<GamepadInput> hleInputStates = [];
List<SixAxisInput> hleMotionStates = _hleMotionStatesPool.Allocate();
_hleInputStates.Clear();
_hleMotionStates.Clear();
KeyboardInput? hleKeyboardInput = null;
@@ -260,14 +262,14 @@ namespace Ryujinx.Input.HLE
inputState.PlayerId = playerIndex;
motionState.Item1.PlayerId = playerIndex;
hleInputStates.Add(inputState);
hleMotionStates.Add(motionState.Item1);
_hleInputStates.Add(inputState);
_hleMotionStates.Add(motionState.Item1);
if (isJoyconPair && !motionState.Item2.Equals(default))
{
motionState.Item2.PlayerId = playerIndex;
hleMotionStates.Add(motionState.Item2);
_hleMotionStates.Add(motionState.Item2);
}
}
@@ -276,8 +278,8 @@ namespace Ryujinx.Input.HLE
hleKeyboardInput = NpadController.GetHLEKeyboardInput(_keyboardDriver);
}
_device.Hid.Npads.Update(hleInputStates);
_device.Hid.Npads.UpdateSixAxis(hleMotionStates);
_device.Hid.Npads.Update(_hleInputStates);
_device.Hid.Npads.UpdateSixAxis(_hleMotionStates);
if (hleKeyboardInput.HasValue)
{
@@ -328,10 +330,7 @@ namespace Ryujinx.Input.HLE
_device.Hid.Mouse.Update(0, 0);
}
_device.TamperMachine.UpdateInput(hleInputStates);
hleMotionStates.Clear();
_hleMotionStatesPool.Release(hleMotionStates);
_device.TamperMachine.UpdateInput(_hleInputStates);
}
}

View File

@@ -8,6 +8,8 @@ namespace Ryujinx.Input
/// </summary>
public interface IKeyboard : IGamepad
{
private static bool[] _keyState;
/// <summary>
/// Check if a given key is pressed on the keyboard.
/// </summary>
@@ -29,15 +31,17 @@ namespace Ryujinx.Input
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static KeyboardStateSnapshot GetStateSnapshot(IKeyboard keyboard)
{
if (_keyState is null)
{
_keyState = new bool[(int)Key.Count];
}
bool[] keysState = ArrayPool<bool>.Shared.Rent((int)Key.Count);
for (Key key = 0; key < Key.Count; key++)
{
keysState[(int)key] = keyboard.IsPressed(key);
_keyState[(int)key] = keyboard.IsPressed(key);
}
return new KeyboardStateSnapshot(keysState);
return new KeyboardStateSnapshot(_keyState);
}
}
}

View File

@@ -3,7 +3,7 @@ namespace Ryujinx.Memory.Range
/// <summary>
/// Range of memory that can be split in two.
/// </summary>
public interface INonOverlappingRange : IRange
public interface INonOverlappingRange<T> : IRangeListRange<T> where T : class, IRangeListRange<T>
{
/// <summary>
/// Split this region into two, around the specified address.
@@ -11,6 +11,6 @@ namespace Ryujinx.Memory.Range
/// </summary>
/// <param name="splitAddress">Address to split the region around</param>
/// <returns>The second part of the split region, with start address at the given split.</returns>
public INonOverlappingRange Split(ulong splitAddress);
public INonOverlappingRange<T> Split(ulong splitAddress);
}
}

View File

@@ -24,8 +24,8 @@ namespace Ryujinx.Memory.Range
/// Check if this range overlaps with another.
/// </summary>
/// <param name="address">Base address</param>
/// <param name="size">Size of the range</param>
/// <param name="endAddress">EndAddress of the range</param>
/// <returns>True if overlapping, false otherwise</returns>
bool OverlapsWith(ulong address, ulong size);
bool OverlapsWith(ulong address, ulong endAddress);
}
}

View File

@@ -11,7 +11,7 @@ namespace Ryujinx.Memory.Range
/// A range list that assumes ranges are non-overlapping, with list items that can be split in two to avoid overlaps.
/// </summary>
/// <typeparam name="T">Type of the range.</typeparam>
public unsafe class NonOverlappingRangeList<T> : RangeListBase<T> where T : class, INonOverlappingRange
public class NonOverlappingRangeList<T> : RangeListBase<T> where T : class, INonOverlappingRange<T>
{
public readonly ReaderWriterLockSlim Lock = new();
@@ -32,83 +32,18 @@ namespace Ryujinx.Memory.Range
/// <param name="item">The item to be added</param>
public override void Add(T item)
{
Debug.Assert(item.Address != item.EndAddress);
int index = BinarySearch(item.Address);
if (index < 0)
{
index = ~index;
}
RangeItem<T> rangeItem = _rangeItemPool.Allocate().Set(item);
Insert(index, rangeItem);
}
/// <summary>
/// Updates an item's end address on the list. Address must be the same.
/// </summary>
/// <param name="item">The item to be updated</param>
/// <returns>True if the item was located and updated, false otherwise</returns>
protected override bool Update(T item)
{
int index = BinarySearch(item.Address);
if (index >= 0 && Items[index].Value.Equals(item))
{
RangeItem<T> rangeItem = new(item) { Previous = Items[index].Previous, Next = Items[index].Next };
if (index > 0)
{
Items[index - 1].Next = rangeItem;
}
if (index < Count - 1)
{
Items[index + 1].Previous = rangeItem;
}
Items[index] = rangeItem;
return true;
}
return false;
}
/// <summary>
/// Updates an item's end address on the list. Address must be the same.
/// </summary>
/// <param name="item">The RangeItem to be updated</param>
/// <returns>True if the item was located and updated, false otherwise</returns>
protected override bool Update(RangeItem<T> item)
{
int index = BinarySearch(item.Address);
RangeItem<T> rangeItem = new(item.Value) { Previous = item.Previous, Next = item.Next };
if (index > 0)
{
Items[index - 1].Next = rangeItem;
}
if (index < Count - 1)
{
Items[index + 1].Previous = rangeItem;
}
Items[index] = rangeItem;
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Insert(int index, RangeItem<T> item)
{
Debug.Assert(item.Address != item.EndAddress);
if (Count + 1 > Items.Length)
{
Array.Resize(ref Items, Items.Length + BackingGrowthSize);
Array.Resize(ref Items, (int)(Items.Length * 1.5));
}
if (index >= Count)
@@ -145,8 +80,6 @@ namespace Ryujinx.Memory.Range
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void RemoveAt(int index)
{
_rangeItemPool.Release(Items[index]);
if (index < Count - 1)
{
Items[index + 1].Previous = index > 0 ? Items[index - 1] : null;
@@ -173,7 +106,7 @@ namespace Ryujinx.Memory.Range
{
int index = BinarySearch(item.Address);
if (index >= 0 && Items[index].Value.Equals(item))
if (index >= 0 && Items[index] == item)
{
RemoveAt(index);
@@ -188,7 +121,7 @@ namespace Ryujinx.Memory.Range
/// </summary>
/// <param name="startItem">The first item in the range of items to be removed</param>
/// <param name="endItem">The last item in the range of items to be removed</param>
public override void RemoveRange(RangeItem<T> startItem, RangeItem<T> endItem)
public override void RemoveRange(T startItem, T endItem)
{
if (startItem is null)
{
@@ -197,7 +130,7 @@ namespace Ryujinx.Memory.Range
if (startItem == endItem)
{
Remove(startItem.Value);
Remove(startItem);
return;
}
@@ -229,42 +162,45 @@ namespace Ryujinx.Memory.Range
/// <param name="size">Size of the range</param>
public void RemoveRange(ulong address, ulong size)
{
int startIndex = BinarySearchLeftEdge(address, address + size);
(int startIndex, int endIndex) = BinarySearchEdges(address, address + size);
if (startIndex < 0)
{
return;
}
int endIndex = startIndex;
while (Items[endIndex] is not null && Items[endIndex].Address < address + size)
if (startIndex == endIndex - 1)
{
if (endIndex == Count - 1)
{
break;
}
endIndex++;
RemoveAt(startIndex);
return;
}
if (endIndex < Count - 1)
RemoveRangeInternal(startIndex, endIndex);
}
/// <summary>
/// Removes a range of items from the item list
/// </summary>
/// <param name="index">Start index of the range</param>
/// <param name="endIndex">End index of the range (exclusive)</param>
private void RemoveRangeInternal(int index, int endIndex)
{
if (endIndex < Count)
{
Items[endIndex + 1].Previous = startIndex > 0 ? Items[startIndex - 1] : null;
Items[endIndex].Previous = index > 0 ? Items[index - 1] : null;
}
if (startIndex > 0)
if (index > 0)
{
Items[startIndex - 1].Next = endIndex < Count - 1 ? Items[endIndex + 1] : null;
Items[index - 1].Next = endIndex < Count ? Items[endIndex] : null;
}
if (endIndex < Count - 1)
if (endIndex < Count)
{
Array.Copy(Items, endIndex + 1, Items, startIndex, Count - endIndex - 1);
Array.Copy(Items, endIndex, Items, index, Count - endIndex);
}
Count -= endIndex - startIndex + 1;
Count -= endIndex - index;
}
/// <summary>
@@ -296,8 +232,8 @@ namespace Ryujinx.Memory.Range
// So we need to return both the split 0-1 and 1-2 ranges.
Lock.EnterWriteLock();
(RangeItem<T> first, RangeItem<T> last) = FindOverlapsAsNodes(address, size);
list = new List<T>();
(T first, T last) = FindOverlapsAsNodes(address, size);
list = [];
if (first is null)
{
@@ -311,42 +247,41 @@ namespace Ryujinx.Memory.Range
ulong lastAddress = address;
ulong endAddress = address + size;
RangeItem<T> current = first;
T current = first;
while (last is not null && current is not null && current.Address < endAddress)
{
T region = current.Value;
if (first == last && region.Address == address && region.Size == size)
if (first == last && current.Address == address && current.Size == size)
{
// Exact match, no splitting required.
list.Add(region);
list.Add(current);
Lock.ExitWriteLock();
return;
}
if (lastAddress < region.Address)
if (lastAddress < current.Address)
{
// There is a gap between this region and the last. We need to fill it.
T fillRegion = factory(lastAddress, region.Address - lastAddress);
T fillRegion = factory(lastAddress, current.Address - lastAddress);
list.Add(fillRegion);
Add(fillRegion);
}
if (region.Address < address)
if (current.Address < address)
{
// Split the region around our base address and take the high half.
region = Split(region, address);
current = Split(current, address);
}
if (region.EndAddress > address + size)
if (current.EndAddress > address + size)
{
// Split the region around our end address and take the low half.
Split(region, address + size);
Split(current, address + size);
}
list.Add(region);
lastAddress = region.EndAddress;
list.Add(current);
lastAddress = current.EndAddress;
current = current.Next;
}
@@ -374,7 +309,6 @@ namespace Ryujinx.Memory.Range
private T Split(T region, ulong splitAddress)
{
T newRegion = (T)region.Split(splitAddress);
Update(region);
Add(newRegion);
return newRegion;
}
@@ -386,16 +320,11 @@ namespace Ryujinx.Memory.Range
/// <param name="size">Size in bytes of the range</param>
/// <returns>The leftmost overlapping item, or null if none is found</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override RangeItem<T> FindOverlap(ulong address, ulong size)
public override T FindOverlap(ulong address, ulong size)
{
int index = BinarySearchLeftEdge(address, address + size);
if (index < 0)
{
return null;
}
return Items[index];
return index < 0 ? null : Items[index];
}
/// <summary>
@@ -405,16 +334,11 @@ namespace Ryujinx.Memory.Range
/// <param name="size">Size in bytes of the range</param>
/// <returns>The overlapping item, or null if none is found</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override RangeItem<T> FindOverlapFast(ulong address, ulong size)
public override T FindOverlapFast(ulong address, ulong size)
{
int index = BinarySearch(address, address + size);
if (index < 0)
{
return null;
}
return Items[index];
return index < 0 ? null : Items[index];
}
/// <summary>
@@ -424,23 +348,18 @@ namespace Ryujinx.Memory.Range
/// <param name="size">Size in bytes of the range</param>
/// <returns>The first and last overlapping items, or null if none are found</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public (RangeItem<T>, RangeItem<T>) FindOverlapsAsNodes(ulong address, ulong size)
public (T, T) FindOverlapsAsNodes(ulong address, ulong size)
{
(int index, int endIndex) = BinarySearchEdges(address, address + size);
if (index < 0)
{
return (null, null);
}
return (Items[index], Items[endIndex - 1]);
return index < 0 ? (null, null) : (Items[index], Items[endIndex - 1]);
}
public RangeItem<T>[] FindOverlapsAsArray(ulong address, ulong size, out int length)
public T[] FindOverlapsAsArray(ulong address, ulong size, out int length)
{
(int index, int endIndex) = BinarySearchEdges(address, address + size);
RangeItem<T>[] result;
T[] result;
if (index < 0)
{
@@ -449,29 +368,20 @@ namespace Ryujinx.Memory.Range
}
else
{
result = ArrayPool<RangeItem<T>>.Shared.Rent(endIndex - index);
result = ArrayPool<T>.Shared.Rent(endIndex - index);
length = endIndex - index;
Array.Copy(Items, index, result, 0, endIndex - index);
Items.AsSpan(index, endIndex - index).CopyTo(result);
}
return result;
}
public Span<RangeItem<T>> FindOverlapsAsSpan(ulong address, ulong size)
public ReadOnlySpan<T> FindOverlapsAsSpan(ulong address, ulong size)
{
(int index, int endIndex) = BinarySearchEdges(address, address + size);
Span<RangeItem<T>> result;
if (index < 0)
{
result = [];
}
else
{
result = Items.AsSpan().Slice(index, endIndex - index);
}
ReadOnlySpan<T> result = index < 0 ? [] : Items.AsSpan(index, endIndex - index);
return result;
}
@@ -480,7 +390,7 @@ namespace Ryujinx.Memory.Range
{
for (int i = 0; i < Count; i++)
{
yield return Items[i].Value;
yield return Items[i];
}
}
}

View File

@@ -14,14 +14,14 @@ namespace Ryujinx.Memory.Range
/// startIndex is inclusive.
/// endIndex is exclusive.
/// </remarks>
public readonly struct OverlapResult<T> where T : IRange
public readonly struct OverlapResult<T> where T : class, IRangeListRange<T>
{
public readonly int StartIndex = -1;
public readonly int EndIndex = -1;
public readonly RangeItem<T> QuickResult;
public readonly T QuickResult;
public int Count => EndIndex - StartIndex;
public OverlapResult(int startIndex, int endIndex, RangeItem<T> quickResult = null)
public OverlapResult(int startIndex, int endIndex, T quickResult = null)
{
this.StartIndex = startIndex;
this.EndIndex = endIndex;
@@ -33,7 +33,7 @@ namespace Ryujinx.Memory.Range
/// Sorted list of ranges that supports binary search.
/// </summary>
/// <typeparam name="T">Type of the range.</typeparam>
public class RangeList<T> : RangeListBase<T> where T : IRange
public class RangeList<T> : RangeListBase<T> where T : class, IRangeListRange<T>
{
public readonly ReaderWriterLockSlim Lock = new();
@@ -61,104 +61,6 @@ namespace Ryujinx.Memory.Range
index = ~index;
}
Insert(index, new RangeItem<T>(item));
}
/// <summary>
/// Updates an item's end address on the list. Address must be the same.
/// </summary>
/// <param name="item">The item to be updated</param>
/// <returns>True if the item was located and updated, false otherwise</returns>
protected override bool Update(T item)
{
int index = BinarySearch(item.Address);
if (index >= 0)
{
while (index < Count)
{
if (Items[index].Value.Equals(item))
{
RangeItem<T> rangeItem = new(item) { Previous = Items[index].Previous, Next = Items[index].Next };
if (index > 0)
{
Items[index - 1].Next = rangeItem;
}
if (index < Count - 1)
{
Items[index + 1].Previous = rangeItem;
}
Items[index] = rangeItem;
return true;
}
if (Items[index].Address > item.Address)
{
break;
}
index++;
}
}
return false;
}
/// <summary>
/// Updates an item's end address on the list. Address must be the same.
/// </summary>
/// <param name="item">The RangeItem to be updated</param>
/// <returns>True if the item was located and updated, false otherwise</returns>
protected override bool Update(RangeItem<T> item)
{
int index = BinarySearch(item.Address);
if (index >= 0)
{
while (index < Count)
{
if (Items[index].Equals(item))
{
RangeItem<T> rangeItem = new(item.Value) { Previous = item.Previous, Next = item.Next };
if (index > 0)
{
Items[index - 1].Next = rangeItem;
}
if (index < Count - 1)
{
Items[index + 1].Previous = rangeItem;
}
Items[index] = rangeItem;
return true;
}
if (Items[index].Address > item.Address)
{
break;
}
index++;
}
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Insert(int index, RangeItem<T> item)
{
Debug.Assert(item.Address != item.EndAddress);
Debug.Assert(item.Address % 32 == 0);
if (Count + 1 > Items.Length)
{
Array.Resize(ref Items, Items.Length + BackingGrowthSize);
@@ -220,7 +122,7 @@ namespace Ryujinx.Memory.Range
/// <param name="startItem">The first item in the range of items to be removed</param>
/// <param name="endItem">The last item in the range of items to be removed</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void RemoveRange(RangeItem<T> startItem, RangeItem<T> endItem)
public override void RemoveRange(T startItem, T endItem)
{
if (startItem is null)
{
@@ -229,30 +131,29 @@ namespace Ryujinx.Memory.Range
if (startItem == endItem)
{
Remove(startItem.Value);
Remove(startItem);
return;
}
int startIndex = BinarySearch(startItem.Address);
int endIndex = BinarySearch(endItem.Address);
(int index, int endIndex) = BinarySearchEdges(startItem.Address, endItem.EndAddress);
if (endIndex < Count - 1)
if (endIndex < Count)
{
Items[endIndex + 1].Previous = startIndex > 0 ? Items[startIndex - 1] : null;
Items[endIndex].Previous = index > 0 ? Items[index - 1] : null;
}
if (startIndex > 0)
if (index > 0)
{
Items[startIndex - 1].Next = endIndex < Count - 1 ? Items[endIndex + 1] : null;
Items[index - 1].Next = endIndex < Count ? Items[endIndex] : null;
}
if (endIndex < Count - 1)
if (endIndex < Count)
{
Array.Copy(Items, endIndex + 1, Items, startIndex, Count - endIndex - 1);
Array.Copy(Items, endIndex, Items, index, Count - endIndex);
}
Count -= endIndex - startIndex + 1;
Count -= endIndex - index;
}
/// <summary>
@@ -268,7 +169,7 @@ namespace Ryujinx.Memory.Range
{
while (index < Count)
{
if (Items[index].Value.Equals(item))
if (Items[index] == item)
{
RemoveAt(index);
@@ -298,7 +199,7 @@ namespace Ryujinx.Memory.Range
/// <param name="size">Size in bytes of the range</param>
/// <returns>The overlapping item, or the default value for the type if none found</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override RangeItem<T> FindOverlap(ulong address, ulong size)
public override T FindOverlap(ulong address, ulong size)
{
int index = BinarySearchLeftEdge(address, address + size);
@@ -321,7 +222,7 @@ namespace Ryujinx.Memory.Range
/// <param name="size">Size in bytes of the range</param>
/// <returns>The overlapping item, or the default value for the type if none found</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override RangeItem<T> FindOverlapFast(ulong address, ulong size)
public override T FindOverlapFast(ulong address, ulong size)
{
int index = BinarySearch(address, address + size);
@@ -340,7 +241,7 @@ namespace Ryujinx.Memory.Range
/// <param name="size">Size in bytes of the range</param>
/// <param name="output">Output array where matches will be written. It is automatically resized to fit the results</param>
/// <returns>Range information of overlapping items found</returns>
private OverlapResult<T> FindOverlaps(ulong address, ulong size, ref RangeItem<T>[] output)
private OverlapResult<T> FindOverlaps(ulong address, ulong size, ref T[] output)
{
int outputCount = 0;
@@ -353,7 +254,7 @@ namespace Ryujinx.Memory.Range
for (int i = startIndex; i < Count; i++)
{
ref RangeItem<T> item = ref Items[i];
T item = Items[i];
if (item.Address >= endAddress)
{
@@ -398,7 +299,7 @@ namespace Ryujinx.Memory.Range
{
for (int i = 0; i < Count; i++)
{
yield return Items[i].Value;
yield return Items[i];
}
}
}

View File

@@ -1,56 +1,22 @@
using Ryujinx.Common;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Ryujinx.Memory.Range
{
public class RangeItem<TValue> where TValue : IRange
public interface IRangeListRange<TValue> : IRange where TValue : class, IRangeListRange<TValue>
{
public RangeItem<TValue> Next;
public RangeItem<TValue> Previous;
public ulong Address;
public ulong EndAddress;
public TValue Value;
public RangeItem()
{
}
public RangeItem(TValue value)
{
Address = value.Address;
EndAddress = value.Address + value.Size;
Value = value;
}
public RangeItem<TValue> Set(TValue value)
{
Next = null;
Previous = null;
Address = value.Address;
EndAddress = value.Address + value.Size;
Value = value;
return this;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool OverlapsWith(ulong address, ulong endAddress)
{
return Address < endAddress && address < EndAddress;
}
public TValue Next { get; set; }
public TValue Previous { get; set; }
}
public unsafe abstract class RangeListBase<T> : IEnumerable<T> where T : IRange
public unsafe abstract class RangeListBase<T> : IEnumerable<T> where T : class, IRangeListRange<T>
{
protected static readonly ObjectPool<RangeItem<T>> _rangeItemPool = new(() => new RangeItem<T>());
private const int BackingInitialSize = 1024;
protected RangeItem<T>[] Items;
protected T[] Items;
protected readonly int BackingGrowthSize;
public int Count { get; protected set; }
@@ -62,32 +28,18 @@ namespace Ryujinx.Memory.Range
protected RangeListBase(int backingInitialSize = BackingInitialSize)
{
BackingGrowthSize = backingInitialSize;
Items = new RangeItem<T>[backingInitialSize];
Items = new T[backingInitialSize];
}
public abstract void Add(T item);
/// <summary>
/// Updates an item's end address on the list. Address must be the same.
/// </summary>
/// <param name="item">The item to be updated</param>
/// <returns>True if the item was located and updated, false otherwise</returns>
protected abstract bool Update(T item);
/// <summary>
/// Updates an item's end address on the list. Address must be the same.
/// </summary>
/// <param name="item">The RangeItem to be updated</param>
/// <returns>True if the item was located and updated, false otherwise</returns>
protected abstract bool Update(RangeItem<T> item);
public abstract bool Remove(T item);
public abstract void RemoveRange(RangeItem<T> startItem, RangeItem<T> endItem);
public abstract void RemoveRange(T startItem, T endItem);
public abstract RangeItem<T> FindOverlap(ulong address, ulong size);
public abstract T FindOverlap(ulong address, ulong size);
public abstract RangeItem<T> FindOverlapFast(ulong address, ulong size);
public abstract T FindOverlapFast(ulong address, ulong size);
/// <summary>
/// Performs binary search on the internal list of items.
@@ -106,7 +58,7 @@ namespace Ryujinx.Memory.Range
int middle = left + (range >> 1);
ref RangeItem<T> item = ref Items[middle];
T item = Items[middle];
if (item.Address == address)
{
@@ -144,7 +96,7 @@ namespace Ryujinx.Memory.Range
int middle = left + (range >> 1);
ref RangeItem<T> item = ref Items[middle];
T item = Items[middle];
if (item.OverlapsWith(address, endAddress))
{
@@ -185,7 +137,7 @@ namespace Ryujinx.Memory.Range
int middle = left + (range >> 1);
ref RangeItem<T> item = ref Items[middle];
T item = Items[middle];
bool match = item.OverlapsWith(address, endAddress);
@@ -237,7 +189,7 @@ namespace Ryujinx.Memory.Range
int middle = right - (range >> 1);
ref RangeItem<T> item = ref Items[middle];
T item = Items[middle];
bool match = item.OverlapsWith(address, endAddress);
@@ -282,7 +234,7 @@ namespace Ryujinx.Memory.Range
if (Count == 1)
{
ref RangeItem<T> item = ref Items[0];
T item = Items[0];
if (item.OverlapsWith(address, endAddress))
{
@@ -312,7 +264,7 @@ namespace Ryujinx.Memory.Range
int middle = left + (range >> 1);
ref RangeItem<T> item = ref Items[middle];
T item = Items[middle];
bool match = item.OverlapsWith(address, endAddress);
@@ -369,7 +321,7 @@ namespace Ryujinx.Memory.Range
int middle = right - (range >> 1);
ref RangeItem<T> item = ref Items[middle];
T item = Items[middle];
bool match = item.OverlapsWith(address, endAddress);

View File

@@ -5,7 +5,7 @@ namespace Ryujinx.Memory.Tracking
/// <summary>
/// A region of memory.
/// </summary>
abstract class AbstractRegion : INonOverlappingRange
abstract class AbstractRegion<T> : INonOverlappingRange<T> where T : class, INonOverlappingRange<T>
{
/// <summary>
/// Base address.
@@ -21,6 +21,9 @@ namespace Ryujinx.Memory.Tracking
/// End address.
/// </summary>
public ulong EndAddress => Address + Size;
public T Next { get; set; }
public T Previous { get; set; }
/// <summary>
/// Create a new region.
@@ -37,11 +40,11 @@ namespace Ryujinx.Memory.Tracking
/// Check if this range overlaps with another.
/// </summary>
/// <param name="address">Base address</param>
/// <param name="size">Size of the range</param>
/// <param name="endAddress">End address</param>
/// <returns>True if overlapping, false otherwise</returns>
public bool OverlapsWith(ulong address, ulong size)
public bool OverlapsWith(ulong address, ulong endAddress)
{
return Address < address + size && address < EndAddress;
return Address < endAddress && address < EndAddress;
}
/// <summary>
@@ -68,6 +71,6 @@ namespace Ryujinx.Memory.Tracking
/// </summary>
/// <param name="splitAddress">Address to split the region around</param>
/// <returns>The second part of the split region, with start address at the given split.</returns>
public abstract INonOverlappingRange Split(ulong splitAddress);
public abstract INonOverlappingRange<T> Split(ulong splitAddress);
}
}

View File

@@ -81,10 +81,10 @@ namespace Ryujinx.Memory.Tracking
{
NonOverlappingRangeList<VirtualRegion> regions = type == 0 ? _virtualRegions : _guestVirtualRegions;
regions.Lock.EnterReadLock();
Span<RangeItem<VirtualRegion>> overlaps = regions.FindOverlapsAsSpan(va, size);
ReadOnlySpan<VirtualRegion> overlaps = regions.FindOverlapsAsSpan(va, size);
for (int i = 0; i < overlaps.Length; i++)
{
VirtualRegion region = overlaps[i].Value;
VirtualRegion region = overlaps[i];
// If the region has been fully remapped, signal that it has been mapped again.
bool remapped = _memoryManager.IsRangeMapped(region.Address, region.Size);
@@ -117,11 +117,11 @@ namespace Ryujinx.Memory.Tracking
{
NonOverlappingRangeList<VirtualRegion> regions = type == 0 ? _virtualRegions : _guestVirtualRegions;
regions.Lock.EnterReadLock();
Span<RangeItem<VirtualRegion>> overlaps = regions.FindOverlapsAsSpan(va, size);
ReadOnlySpan<VirtualRegion> overlaps = regions.FindOverlapsAsSpan(va, size);
for (int i = 0; i < overlaps.Length; i++)
{
overlaps[i].Value.SignalMappingChanged(false);
overlaps[i].SignalMappingChanged(false);
}
regions.Lock.ExitReadLock();
}
@@ -301,7 +301,7 @@ namespace Ryujinx.Memory.Tracking
// We use the non-span method here because keeping the lock will cause a deadlock.
regions.Lock.EnterReadLock();
RangeItem<VirtualRegion>[] overlaps = regions.FindOverlapsAsArray(address, size, out int length);
VirtualRegion[] overlaps = regions.FindOverlapsAsArray(address, size, out int length);
regions.Lock.ExitReadLock();
if (length == 0 && !precise)
@@ -327,7 +327,7 @@ namespace Ryujinx.Memory.Tracking
for (int i = 0; i < length; i++)
{
VirtualRegion region = overlaps[i].Value;
VirtualRegion region = overlaps[i];
if (precise)
{
@@ -341,7 +341,7 @@ namespace Ryujinx.Memory.Tracking
if (length != 0)
{
ArrayPool<RangeItem<VirtualRegion>>.Shared.Return(overlaps);
ArrayPool<VirtualRegion>.Shared.Return(overlaps);
}
}
}

View File

@@ -58,10 +58,7 @@ namespace Ryujinx.Memory.Tracking
{
foreach (RegionHandle handle in _handles)
{
if (handle != null)
{
handle?.RegisterAction((address, size) => action(handle.Address, handle.Size));
}
handle?.RegisterAction((address, size) => action(handle.Address, handle.Size));
}
}
@@ -69,10 +66,7 @@ namespace Ryujinx.Memory.Tracking
{
foreach (RegionHandle handle in _handles)
{
if (handle != null)
{
handle?.RegisterPreciseAction((address, size, write) => action(handle.Address, handle.Size, write));
}
handle?.RegisterPreciseAction((address, size, write) => action(handle.Address, handle.Size, write));
}
}

View File

@@ -6,7 +6,7 @@ namespace Ryujinx.Memory.Tracking
/// <summary>
/// A region of virtual memory.
/// </summary>
class VirtualRegion : AbstractRegion
class VirtualRegion : AbstractRegion<VirtualRegion>
{
public List<RegionHandle> Handles = [];
@@ -137,7 +137,7 @@ namespace Ryujinx.Memory.Tracking
}
}
public override INonOverlappingRange Split(ulong splitAddress)
public override INonOverlappingRange<VirtualRegion> Split(ulong splitAddress)
{
VirtualRegion newRegion = new(_tracking, splitAddress, EndAddress - splitAddress, Guest, _lastPermission);
Size = splitAddress - Address;

View File

@@ -264,7 +264,7 @@ namespace Ryujinx.Ava.Common
{
Dispatcher.UIThread.Post(waitingDialog.Close);
NotificationHelper.ShowInformation(
RyujinxNotificationManager.ShowInformation(
RyujinxApp.FormatTitle(LocaleKeys.DialogNcaExtractionTitle),
$"{titleName}\n\n{LocaleManager.Instance[LocaleKeys.DialogNcaExtractionSuccessMessage]}");
}
@@ -380,7 +380,7 @@ namespace Ryujinx.Ava.Common
{
Dispatcher.UIThread.Post(waitingDialog.Close);
NotificationHelper.ShowInformation(
RyujinxNotificationManager.ShowInformation(
RyujinxApp.FormatTitle(LocaleKeys.DialogNcaExtractionTitle),
$"{updateName}\n\n{LocaleManager.Instance[LocaleKeys.DialogNcaExtractionSuccessMessage]}");
}

View File

@@ -38,6 +38,7 @@ namespace Ryujinx.Ava.Common.Locale
{ LocaleKeys.RyujinxConfirm, [RyujinxApp.FullAppName] },
{ LocaleKeys.RyujinxUpdater, [RyujinxApp.FullAppName] },
{ LocaleKeys.RyujinxRebooter, [RyujinxApp.FullAppName] },
{ LocaleKeys.SetupWizardGameDirsPageDescription, [RyujinxApp.FullAppName] },
{ LocaleKeys.CompatibilityListSearchBoxWatermarkWithCount, [CompatibilityDatabase.Entries.Length] },
{ LocaleKeys.CompatibilityListTitle, [CompatibilityDatabase.Entries.Length] }
});

View File

@@ -0,0 +1,32 @@
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Gommon;
using System;
namespace Ryujinx.Ava.Common
{
// ReSharper disable once InconsistentNaming
// UiImages is ugly, so no
public static class UIImages
{
public const string LogoPathFormat = "resm:Ryujinx.Assets.UIImages.Logo_{0}_{1}.png?assembly=Ryujinx";
public const string IconPathFormat = "resm:Ryujinx.Assets.UIImages.Icon_{0}.png?assembly=Ryujinx";
public static Bitmap LoadBitmap(string uri)
=> new(AssetLoader.Open(new Uri(uri)));
public static Bitmap GetIconByName(string iconName)
=> LoadBitmap(IconPathFormat.Format(iconName));
public static Bitmap GetLogoByNameAndTheme(string iconName, bool isDarkTheme) =>
LoadBitmap(LogoPathFormat.Format(iconName,
isDarkTheme
? "Dark"
: "Light"
)
);
public static Bitmap GetLogoByNameAndVariant(string iconName, string theme)
=> LoadBitmap(LogoPathFormat.Format(iconName, theme));
}
}

View File

@@ -156,12 +156,9 @@ namespace Ryujinx.Headless
option.UserProfile = profile.Name;
// Check if keys exists.
if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")))
if (!File.Exists(Path.Combine(AppDataManager.GetKeysDir(), "prod.keys")))
{
if (!(AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys"))))
{
Logger.Error?.Print(LogClass.Application, "Keys not found");
}
Logger.Error?.Print(LogClass.Application, "Keys not found");
}
ReloadConfig();

View File

@@ -580,5 +580,10 @@ namespace Ryujinx.Headless
{
return AccountSaveDataManager.GetLastUsedUser();
}
public void TakeScreenshot()
{
throw new NotImplementedException();
}
}
}

View File

@@ -17,6 +17,7 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Common.Utilities;
using Ryujinx.Graphics.Vulkan.MoltenVK;
using Ryujinx.Headless;
using Ryujinx.SDL3.Common;
@@ -31,6 +32,8 @@ namespace Ryujinx.Ava
{
internal static class Program
{
public static bool IsFirstStart { get; set; }
public static double WindowScaleFactor { get; set; }
public static double DesktopScaleFactor { get; set; } = 1.0;
public static string Version { get; private set; }
@@ -46,7 +49,7 @@ namespace Ryujinx.Ava
public static int Main(string[] args)
{
Version = ReleaseInformation.Version;
if (OperatingSystem.IsWindows())
{
if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041))
@@ -55,8 +58,11 @@ namespace Ryujinx.Ava
return 0;
}
if (Environment.CurrentDirectory.StartsWithIgnoreCase("C:\\Program Files") ||
Environment.CurrentDirectory.StartsWithIgnoreCase("C:\\Program Files (x86)"))
var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
var programFilesX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
if (Environment.CurrentDirectory.StartsWithIgnoreCase(programFiles) ||
Environment.CurrentDirectory.StartsWithIgnoreCase(programFilesX86))
{
_ = Win32NativeInterop.MessageBoxA(nint.Zero, "Ryujinx is not intended to be run from the Program Files folder. Please move it out and relaunch.", $"Ryujinx {Version}", MbIconwarning);
return 0;
@@ -73,11 +79,23 @@ namespace Ryujinx.Ava
}
}
bool noGuiArg = ConsumeCommandLineArgument(ref args, "--no-gui") || ConsumeCommandLineArgument(ref args, "nogui");
bool coreDumpArg = ConsumeCommandLineArgument(ref args, "--core-dumps");
// 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,
// 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)
{
OsUtils.SetCoreDumpable(false);
}
PreviewerDetached = true;
if (args.Length > 0 && args[0] is "--no-gui" or "nogui")
if (noGuiArg)
{
HeadlessRyujinx.Entrypoint(args[1..]);
HeadlessRyujinx.Entrypoint(args);
return 0;
}
@@ -112,6 +130,14 @@ namespace Ryujinx.Ava
: [Win32RenderingMode.Software]
});
private static bool ConsumeCommandLineArgument(ref string[] args, string targetArgument)
{
List<string> argList = [.. args];
bool found = argList.Remove(targetArgument);
args = argList.ToArray();
return found;
}
private static void Initialize(string[] args)
{
// Ensure Discord presence timestamp begins at the absolute start of when Ryujinx is launched
@@ -163,12 +189,9 @@ namespace Ryujinx.Ava
DriverUtilities.InitDriverConfig(ConfigurationState.Instance.Graphics.BackendThreading == BackendThreading.Off);
// Check if keys exists.
if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")))
if (!File.Exists(Path.Combine(AppDataManager.GetKeysDir(), "prod.keys")))
{
if (!(AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys"))))
{
MainWindow.ShowKeyErrorOnLoad = true;
}
MainWindow.ShowKeyErrorOnLoad = true;
}
if (CommandLineState.LaunchPathArg != null)
@@ -177,7 +200,6 @@ namespace Ryujinx.Ava
}
}
public static string GetDirGameUserConfig(string gameId, bool changeFolderForGame = false)
{
if (string.IsNullOrEmpty(gameId))
@@ -196,22 +218,25 @@ namespace Ryujinx.Ava
return gameDir;
}
public static void ReloadConfig()
public static void ReloadConfig(bool isRunGameWithCustomConfig = false)
{
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
// Now load the configuration as the other subsystems are now registered
if (File.Exists(localConfigurationPath))
{
ConfigurationPath = localConfigurationPath;
}
else if (File.Exists(appDataConfigurationPath))
{
ConfigurationPath = appDataConfigurationPath;
}
if (!isRunGameWithCustomConfig) // To return settings from the game folder if the user configuration exists
{
// Now load the configuration as the other subsystems are now registered
if (File.Exists(localConfigurationPath))
{
ConfigurationPath = localConfigurationPath;
}
else if (File.Exists(appDataConfigurationPath))
{
ConfigurationPath = appDataConfigurationPath;
}
}
if (ConfigurationPath == null)
{
// No configuration, we load the default values and save it to disk
@@ -220,6 +245,7 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.LoadDefault();
ConfigurationState.Instance.ToFileFormat().SaveConfig(ConfigurationPath);
IsFirstStart = true;
}
else
{
@@ -233,6 +259,10 @@ namespace Ryujinx.Ava
{
Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location: {ConfigurationPath}");
ConfigurationFileFormat.RenameInvalidConfigFile(ConfigurationPath);
IsFirstStart = true;
ConfigurationState.Instance.LoadDefault();
}
}

View File

@@ -501,18 +501,12 @@ namespace Ryujinx.Ava.Systems
private void UpdateIgnoreMissingServicesState(object sender, ReactiveEventArgs<bool> args)
{
if (Device != null)
{
Device.Configuration.IgnoreMissingServices = args.NewValue;
}
Device?.Configuration.IgnoreMissingServices = args.NewValue;
}
private void UpdateAspectRatioState(object sender, ReactiveEventArgs<AspectRatio> args)
{
if (Device != null)
{
Device.Configuration.AspectRatio = args.NewValue;
}
Device?.Configuration.AspectRatio = args.NewValue;
}
private void UpdateAntiAliasing(object sender, ReactiveEventArgs<AntiAliasing> e)

View File

@@ -6,7 +6,9 @@ using Ryujinx.Common.Configuration.Multiplayer;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE;
using System;
using System.Collections.Generic;
using System.IO;
namespace Ryujinx.Ava.Systems.Configuration
{
@@ -510,6 +512,43 @@ namespace Ryujinx.Ava.Systems.Configuration
}
}
/// <summary>
/// Renames the configuration file when it is deemed invalid
/// </summary>
/// <param name="path">The path to the invalid JSON configuration file</param>
/// <returns>The path of the renamed invalid JSON configuration file, or null if the rename failed</returns>
public static string RenameInvalidConfigFile(string path)
{
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
{
return null;
}
try
{
string directory = Path.GetDirectoryName(path) ?? string.Empty;
string originalFileName = Path.GetFileName(path);
if (string.IsNullOrWhiteSpace(originalFileName))
{
return null;
}
string renamedFileName = $"{originalFileName}.{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.invalid";
string renamedPath = string.IsNullOrEmpty(directory) ? renamedFileName : Path.Combine(directory, renamedFileName);
File.Move(path, renamedPath, overwrite: false);
Logger.Warning?.PrintMsg(LogClass.Application, $"Invalid configuration renamed to: {renamedPath}");
return renamedPath;
}
catch (Exception ex)
{
Logger.Error?.PrintMsg(LogClass.Application, $"Failed to rename invalid configuration file \"{path}\": {ex}");
return null;
}
}
/// <summary>
/// Save a configuration file to disk
/// </summary>

View File

@@ -28,6 +28,8 @@ namespace Ryujinx.Ava.Systems.Configuration
{
RyuLogger.Warning?.Print(LogClass.Application, $"Unsupported configuration version {cff.Version}, loading default.");
ConfigurationFileFormat.RenameInvalidConfigFile(configurationFilePath);
LoadDefault();
return;
}

View File

@@ -88,14 +88,10 @@ namespace Ryujinx.Ava.Systems
{
if (showVersionUpToDate)
{
UserResult userResult = await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog(
await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
string.Empty);
if (userResult is UserResult.Ok)
{
OpenHelper.OpenUrl(_versionResponse.ReleaseUrlFormat.Format(currentVersion));
}
string.Empty,
_versionResponse.ReleaseUrlFormat.Format(currentVersion));
}
Logger.Info?.Print(LogClass.Application, "Up to date.");

View File

@@ -60,14 +60,10 @@ namespace Ryujinx.Ava.Systems
{
if (showVersionUpToDate)
{
UserResult userResult = await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog(
await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
string.Empty);
if (userResult is UserResult.Ok)
{
OpenHelper.OpenUrl(_versionResponse.ReleaseUrlFormat.Format(currentVersion));
}
string.Empty,
changelogUrl: _versionResponse.ReleaseUrlFormat.Format(currentVersion));
}
Logger.Info?.Print(LogClass.Application, "Up to date.");
@@ -106,22 +102,18 @@ namespace Ryujinx.Ava.Systems
Logger.Info?.Print(LogClass.Application, $"Version found: {newVersionString.Replace("", "->")}");
RequestUserToUpdate:
// Show a message asking the user if they want to update
UserResult shouldUpdate = await ContentDialogHelper.CreateUpdaterChoiceDialog(
LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
LocaleManager.Instance[LocaleKeys.RyujinxUpdaterMessage],
newVersionString);
newVersionString,
ReleaseInformation.GetChangelogUrl(currentVersion, newVersion));
switch (shouldUpdate)
{
case UserResult.Yes:
await UpdateRyujinx(_versionResponse.ArtifactUrl);
break;
// Secondary button maps to no, which in this case is the show changelog button.
case UserResult.No:
OpenHelper.OpenUrl(ReleaseInformation.GetChangelogUrl(currentVersion, newVersion));
goto RequestUserToUpdate;
default:
_running = false;
break;

View File

@@ -327,5 +327,10 @@ namespace Ryujinx.Ava.UI.Applet
return profile;
}
public void TakeScreenshot()
{
_parent.ViewModel.AppHost.ScreenshotRequested = true;
}
}
}

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