From c3831428e08e9299ab7104a4e4c5354533c2efbf Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Tue, 19 Nov 2024 10:22:11 -0600 Subject: [PATCH 01/61] Try and fix weird nullref --- src/Ryujinx.UI.Common/Helper/FileAssociationHelper.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Ryujinx.UI.Common/Helper/FileAssociationHelper.cs b/src/Ryujinx.UI.Common/Helper/FileAssociationHelper.cs index be10deca05..9333a1b76f 100644 --- a/src/Ryujinx.UI.Common/Helper/FileAssociationHelper.cs +++ b/src/Ryujinx.UI.Common/Helper/FileAssociationHelper.cs @@ -101,13 +101,13 @@ static bool CheckRegistering(string ext) { RegistryKey key = Registry.CurrentUser.OpenSubKey(@$"Software\Classes\{ext}"); - if (key is null) + var openCmd = key?.OpenSubKey(@"shell\open\command"); + + if (openCmd is null) { return false; } - - var openCmd = key.OpenSubKey(@"shell\open\command"); - + string keyValue = (string)openCmd.GetValue(string.Empty); return keyValue is not null && (keyValue.Contains("Ryujinx") || keyValue.Contains(AppDomain.CurrentDomain.FriendlyName)); From c0a4d95c5d984df4a862a62d21f5090fb88a32de Mon Sep 17 00:00:00 2001 From: Luke Warner <65521430+LukeWarnut@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:02:24 -0500 Subject: [PATCH 02/61] ARMeilleure: Implement TPIDR2_EL0 (#280) This is an implementation of the TPIDR2_EL0 register. There may be more potential use-cases for this register not included in this PR, but this implements the use-case seen in SuperTuxKart. --- src/ARMeilleure/Instructions/InstEmitSystem.cs | 17 +++++++++++++++++ src/ARMeilleure/State/NativeContext.cs | 9 +++++++++ 2 files changed, 26 insertions(+) diff --git a/src/ARMeilleure/Instructions/InstEmitSystem.cs b/src/ARMeilleure/Instructions/InstEmitSystem.cs index 8c430fc234..fbf3b4a709 100644 --- a/src/ARMeilleure/Instructions/InstEmitSystem.cs +++ b/src/ARMeilleure/Instructions/InstEmitSystem.cs @@ -49,6 +49,9 @@ public static void Mrs(ArmEmitterContext context) case 0b11_011_1101_0000_011: EmitGetTpidrroEl0(context); return; + case 0b11_011_1101_0000_101: + EmitGetTpidr2El0(context); + return; case 0b11_011_1110_0000_000: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntfrqEl0)); break; @@ -84,6 +87,9 @@ public static void Msr(ArmEmitterContext context) case 0b11_011_1101_0000_010: EmitSetTpidrEl0(context); return; + case 0b11_011_1101_0000_101: + EmitGetTpidr2El0(context); + return; default: throw new NotImplementedException($"Unknown MSR 0x{op.RawOpCode:X8} at 0x{op.Address:X16}."); @@ -213,6 +219,17 @@ private static void EmitGetTpidrroEl0(ArmEmitterContext context) SetIntOrZR(context, op.Rt, result); } + private static void EmitGetTpidr2El0(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + + Operand result = context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidr2El0Offset()))); + + SetIntOrZR(context, op.Rt, result); + } + private static void EmitSetNzcv(ArmEmitterContext context) { OpCodeSystem op = (OpCodeSystem)context.CurrOp; diff --git a/src/ARMeilleure/State/NativeContext.cs b/src/ARMeilleure/State/NativeContext.cs index 628efde415..140b6f7a70 100644 --- a/src/ARMeilleure/State/NativeContext.cs +++ b/src/ARMeilleure/State/NativeContext.cs @@ -21,6 +21,7 @@ private unsafe struct NativeCtxStorage public ulong ExclusiveValueLow; public ulong ExclusiveValueHigh; public int Running; + public long Tpidr2El0; } private static NativeCtxStorage _dummyStorage = new(); @@ -176,6 +177,9 @@ public unsafe void SetFPState(uint value, uint mask = uint.MaxValue) public long GetTpidrroEl0() => GetStorage().TpidrroEl0; public void SetTpidrroEl0(long value) => GetStorage().TpidrroEl0 = value; + public long GetTpidr2El0() => GetStorage().Tpidr2El0; + public void SetTpidr2El0(long value) => GetStorage().Tpidr2El0 = value; + public int GetCounter() => GetStorage().Counter; public void SetCounter(int value) => GetStorage().Counter = value; @@ -232,6 +236,11 @@ public static int GetTpidrroEl0Offset() return StorageOffset(ref _dummyStorage, ref _dummyStorage.TpidrroEl0); } + public static int GetTpidr2El0Offset() + { + return StorageOffset(ref _dummyStorage, ref _dummyStorage.Tpidr2El0); + } + public static int GetCounterOffset() { return StorageOffset(ref _dummyStorage, ref _dummyStorage.Counter); From 150e06e0de20305b521b838fbe5b63379878cc85 Mon Sep 17 00:00:00 2001 From: GabCoolGuy Date: Wed, 20 Nov 2024 18:52:16 +0100 Subject: [PATCH 03/61] Add `documentation` and `ldn` labels to `labeler.yml` (#282) This should make it so that any changes made to ldn and documentation related files should be auto-labeled --- .github/labeler.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/labeler.yml b/.github/labeler.yml index 54f2757b00..871f9945f7 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -33,3 +33,11 @@ kernel: infra: - changed-files: - any-glob-to-any-file: ['.github/**', 'distribution/**', 'Directory.Packages.props'] + +documentation: +- changed-files: + - any-glob-to-any-file: 'docs/**' + +ldn: +- changed-files: + - any-glob-to-any-file: 'src/Ryujinx.HLE/HOS/Services/Ldn/**' From aaaf60b7a4e7793c137019459ab7ef7c8f20c91e Mon Sep 17 00:00:00 2001 From: GabCoolGuy Date: Wed, 20 Nov 2024 19:20:38 +0100 Subject: [PATCH 04/61] Change headless to nogui in the release artifacts (#285) This makes it so that instead of the files you download being `sdl2-ryujinx-headless` they are now `nogui-ryujinx`in the release (and canary) artifacts --- .github/workflows/build.yml | 4 ++-- .github/workflows/canary.yml | 4 ++-- .github/workflows/nightly_pr_comment.yml | 4 ++-- .github/workflows/release.yml | 4 ++-- distribution/macos/create_macos_build_headless.sh | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b678e5f8e3..21dc3eb0be 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -122,7 +122,7 @@ jobs: - name: Upload Ryujinx.Headless.SDL2 artifact uses: actions/upload-artifact@v4 with: - name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }} + name: nogui-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }} path: publish_sdl2_headless if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13' @@ -185,6 +185,6 @@ jobs: - name: Upload Ryujinx.Headless.SDL2 artifact uses: actions/upload-artifact@v4 with: - name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal + name: nogui-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal path: "publish_headless/*.tar.gz" if: github.event_name == 'pull_request' diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index d102beaa33..72e1b95156 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -116,7 +116,7 @@ jobs: pushd publish_sdl2_headless rm publish/libarmeilleure-jitsupport.dylib - 7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish + 7z a ../release_output/nogui-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish popd shell: bash @@ -132,7 +132,7 @@ jobs: pushd publish_sdl2_headless rm publish/libarmeilleure-jitsupport.dylib chmod +x publish/Ryujinx.sh publish/Ryujinx.Headless.SDL2 - tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish + tar -czvf ../release_output/nogui-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish popd shell: bash diff --git a/.github/workflows/nightly_pr_comment.yml b/.github/workflows/nightly_pr_comment.yml index 64705b6ee9..85a6e2de4b 100644 --- a/.github/workflows/nightly_pr_comment.yml +++ b/.github/workflows/nightly_pr_comment.yml @@ -38,12 +38,12 @@ jobs: return core.error(`No artifacts found`); } let body = `Download the artifacts for this pull request:\n`; - let hidden_headless_artifacts = `\n\n
GUI-less (SDL2)\n`; + let hidden_headless_artifacts = `\n\n
GUI-less\n`; let hidden_debug_artifacts = `\n\n
Only for Developers\n`; for (const art of artifacts) { if(art.name.includes('Debug')) { hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; - } else if(art.name.includes('sdl2-ryujinx-headless')) { + } else if(art.name.includes('nogui-ryujinx')) { hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; } else { body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d1ea8e449f..44b1de09b0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -115,7 +115,7 @@ jobs: pushd publish_sdl2_headless rm libarmeilleure-jitsupport.dylib - 7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish + 7z a ../release_output/nogui-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish popd shell: bash @@ -166,7 +166,7 @@ jobs: pushd publish_sdl2_headless chmod +x Ryujinx.sh Ryujinx.Headless.SDL2 - tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish + tar -czvf ../release_output/nogui-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish popd shell: bash diff --git a/distribution/macos/create_macos_build_headless.sh b/distribution/macos/create_macos_build_headless.sh index 2715699d0e..24836418d2 100755 --- a/distribution/macos/create_macos_build_headless.sh +++ b/distribution/macos/create_macos_build_headless.sh @@ -22,9 +22,9 @@ EXTRA_ARGS=$8 if [ "$VERSION" == "1.1.0" ]; then - RELEASE_TAR_FILE_NAME=sdl2-ryujinx-headless-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.tar + RELEASE_TAR_FILE_NAME=nogui-ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.tar else - RELEASE_TAR_FILE_NAME=sdl2-ryujinx-headless-$VERSION-macos_universal.tar + RELEASE_TAR_FILE_NAME=nogui-ryujinx-$VERSION-macos_universal.tar fi ARM64_OUTPUT="$TEMP_DIRECTORY/publish_arm64" From c2de5cc700e1ae6beefe3d8866b7cdadb0c7c5ba Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Thu, 21 Nov 2024 10:16:13 -0600 Subject: [PATCH 05/61] Fix really obvious typo, lol --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3bc223f3a4..f6783b4124 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Guides and documentation can be found on the Wiki tab.

- If you would like a version more preservative fork of Ryujinx, check out ryujinx-mirror. + If you would like a more preservative fork of Ryujinx, check out ryujinx-mirror.

From 1d42c29335a87bd5ac2c53a5aa1edf948f211f4b Mon Sep 17 00:00:00 2001 From: GabCoolGuy Date: Thu, 21 Nov 2024 19:34:53 +0100 Subject: [PATCH 06/61] Add more mentions of canary (#258) This should hopefully make it clearer whether or not you're using canary. Changelog: - Changed github workflows to have "canary" in the zip files - Added `App.FullAppName` in the about section, so that it's clear in there too - Changed log name for canary builds to `Ryujinx_Canary_{version}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log` (normal builds should still be "Ryujinx_{version}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log) --- .github/workflows/canary.yml | 12 ++++++------ .github/workflows/release.yml | 4 ++-- distribution/macos/create_macos_build_ava.sh | 13 +++++++------ distribution/macos/create_macos_build_headless.sh | 13 +++++++------ src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs | 3 ++- src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs | 2 +- 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index 72e1b95156..a24436de3f 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -111,12 +111,12 @@ jobs: run: | pushd publish_ava rm publish/libarmeilleure-jitsupport.dylib - 7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish + 7z a ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish popd pushd publish_sdl2_headless rm publish/libarmeilleure-jitsupport.dylib - 7z a ../release_output/nogui-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish + 7z a ../release_output/nogui-ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish popd shell: bash @@ -126,13 +126,13 @@ jobs: pushd publish_ava rm publish/libarmeilleure-jitsupport.dylib chmod +x publish/Ryujinx.sh publish/Ryujinx - tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish + tar -czvf ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish popd pushd publish_sdl2_headless rm publish/libarmeilleure-jitsupport.dylib chmod +x publish/Ryujinx.sh publish/Ryujinx.Headless.SDL2 - tar -czvf ../release_output/nogui-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish + tar -czvf ../release_output/nogui-ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish popd shell: bash @@ -236,11 +236,11 @@ 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 + ./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 - name: Publish macOS Ryujinx.Headless.SDL2 run: | - ./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release + ./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 1 - name: Pushing new release uses: ncipollo/release-action@v1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 44b1de09b0..ec02976a11 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -231,11 +231,11 @@ 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 + ./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0 - name: Publish macOS Ryujinx.Headless.SDL2 run: | - ./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release + ./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0 - name: Pushing new release uses: ncipollo/release-action@v1 diff --git a/distribution/macos/create_macos_build_ava.sh b/distribution/macos/create_macos_build_ava.sh index 80bd6662c6..b19fa48638 100755 --- a/distribution/macos/create_macos_build_ava.sh +++ b/distribution/macos/create_macos_build_ava.sh @@ -2,8 +2,8 @@ set -e -if [ "$#" -lt 7 ]; then - echo "usage " +if [ "$#" -lt 8 ]; then + echo "usage " exit 1 fi @@ -18,10 +18,11 @@ ENTITLEMENTS_FILE_PATH=$(readlink -f "$4") VERSION=$5 SOURCE_REVISION_ID=$6 CONFIGURATION=$7 -EXTRA_ARGS=$8 +CANARY=$8 -if [ "$VERSION" == "1.1.0" ]; -then +if [ "$CANARY" == "1" ]; then + RELEASE_TAR_FILE_NAME=ryujinx-canary-$VERSION-macos_universal.app.tar +elif [ "$VERSION" == "1.1.0" ]; then RELEASE_TAR_FILE_NAME=ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar else RELEASE_TAR_FILE_NAME=ryujinx-$VERSION-macos_universal.app.tar @@ -61,7 +62,7 @@ mkdir -p "$OUTPUT_DIRECTORY" cp -R "$ARM64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE" rm "$UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH" -# Make it libraries universal +# Make its libraries universal python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_APP_BUNDLE" "$X64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE" "**/*.dylib" if ! [ -x "$(command -v lipo)" ]; diff --git a/distribution/macos/create_macos_build_headless.sh b/distribution/macos/create_macos_build_headless.sh index 24836418d2..01951d8782 100755 --- a/distribution/macos/create_macos_build_headless.sh +++ b/distribution/macos/create_macos_build_headless.sh @@ -2,8 +2,8 @@ set -e -if [ "$#" -lt 7 ]; then - echo "usage " +if [ "$#" -lt 8 ]; then + echo "usage " exit 1 fi @@ -18,10 +18,11 @@ ENTITLEMENTS_FILE_PATH=$(readlink -f "$4") VERSION=$5 SOURCE_REVISION_ID=$6 CONFIGURATION=$7 -EXTRA_ARGS=$8 +CANARY=$8 -if [ "$VERSION" == "1.1.0" ]; -then +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 @@ -56,7 +57,7 @@ mkdir -p "$OUTPUT_DIRECTORY" cp -R "$ARM64_OUTPUT/" "$UNIVERSAL_OUTPUT" rm "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH" -# Make it libraries universal +# 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)" ]; diff --git a/src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs b/src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs index 631df3056f..94e9359c80 100644 --- a/src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs +++ b/src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs @@ -69,9 +69,10 @@ public static FileStream PrepareLogFile(string path) } string version = ReleaseInformation.Version; + string appName = ReleaseInformation.IsCanaryBuild ? "Ryujinx_Canary" : "Ryujinx"; // Get path for the current time - path = Path.Combine(logDir.FullName, $"Ryujinx_{version}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log"); + path = Path.Combine(logDir.FullName, $"{appName}_{version}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log"); try { diff --git a/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs index 236711c317..c48ad378f3 100644 --- a/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs @@ -49,7 +49,7 @@ public string Version public AboutWindowViewModel() { - Version = Program.Version; + Version = App.FullAppName + "\n" + Program.Version; UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle.Value); ThemeManager.ThemeChanged += ThemeManager_ThemeChanged; From e2b7738465cc3b753ff255d0e10ed63fb6895058 Mon Sep 17 00:00:00 2001 From: GabCoolGuy Date: Fri, 22 Nov 2024 18:07:47 +0100 Subject: [PATCH 07/61] Add all the missing locales from XCI Trimmer and LDN merge (#281) Hello any fellow developers that may be reading this. Whenever you add any new locales to `en_US.json`, please make sure to add them to the rest of the locale files. I will not always be there to add them myself. --- src/Ryujinx/Assets/Locales/ar_SA.json | 54 +++++++++++++++++++++++++- src/Ryujinx/Assets/Locales/de_DE.json | 54 +++++++++++++++++++++++++- src/Ryujinx/Assets/Locales/el_GR.json | 54 +++++++++++++++++++++++++- src/Ryujinx/Assets/Locales/es_ES.json | 40 +++++++++++++++++++ src/Ryujinx/Assets/Locales/fr_FR.json | 56 ++++++++++++++++++++++++++- src/Ryujinx/Assets/Locales/he_IL.json | 54 +++++++++++++++++++++++++- src/Ryujinx/Assets/Locales/it_IT.json | 14 ++++++- src/Ryujinx/Assets/Locales/ja_JP.json | 54 +++++++++++++++++++++++++- src/Ryujinx/Assets/Locales/pl_PL.json | 54 +++++++++++++++++++++++++- src/Ryujinx/Assets/Locales/pt_BR.json | 55 +++++++++++++++++++++++++- src/Ryujinx/Assets/Locales/ru_RU.json | 54 +++++++++++++++++++++++++- src/Ryujinx/Assets/Locales/th_TH.json | 54 +++++++++++++++++++++++++- src/Ryujinx/Assets/Locales/tr_TR.json | 54 +++++++++++++++++++++++++- src/Ryujinx/Assets/Locales/uk_UA.json | 54 +++++++++++++++++++++++++- src/Ryujinx/Assets/Locales/zh_CN.json | 54 +++++++++++++++++++++++++- src/Ryujinx/Assets/Locales/zh_TW.json | 54 +++++++++++++++++++++++++- 16 files changed, 797 insertions(+), 16 deletions(-) diff --git a/src/Ryujinx/Assets/Locales/ar_SA.json b/src/Ryujinx/Assets/Locales/ar_SA.json index 781568fee9..6dbc96135f 100644 --- a/src/Ryujinx/Assets/Locales/ar_SA.json +++ b/src/Ryujinx/Assets/Locales/ar_SA.json @@ -33,6 +33,7 @@ "MenuBarToolsManageFileTypes": "إدارة أنواع الملفات", "MenuBarToolsInstallFileTypes": "تثبيت أنواع الملفات", "MenuBarToolsUninstallFileTypes": "إزالة أنواع الملفات", + "MenuBarToolsXCITrimmer": "Trim XCI Files", "MenuBarView": "_عرض", "MenuBarViewWindow": "حجم النافذة", "MenuBarViewWindow720": "720p", @@ -84,8 +85,11 @@ "GameListContextMenuOpenModsDirectoryToolTip": "يفتح المجلد الذي يحتوي على تعديلات‫(mods) التطبيق", "GameListContextMenuOpenSdModsDirectory": "فتح مجلد تعديلات‫(mods) أتموسفير", "GameListContextMenuOpenSdModsDirectoryToolTip": "يفتح مجلد أتموسفير لبطاقة SD البديلة الذي يحتوي على تعديلات التطبيق. مفيد للتعديلات التي تم تعبئتها للأجهزة الحقيقية.", + "GameListContextMenuTrimXCI": "Check and Trim XCI File", + "GameListContextMenuTrimXCIToolTip": "Check and Trim XCI File to Save Disk Space", "StatusBarGamesLoaded": "{0}/{1} لعبة تم تحميلها", "StatusBarSystemVersion": "إصدار النظام: {0}", + "StatusBarXCIFileTrimming": "Trimming XCI File '{0}'", "LinuxVmMaxMapCountDialogTitle": "الحد الأدنى لتعيينات الذاكرة المكتشفة", "LinuxVmMaxMapCountDialogTextPrimary": "هل ترغب في زيادة قيمة vm.max_map_count إلى {0}", "LinuxVmMaxMapCountDialogTextSecondary": "قد تحاول بعض الألعاب إنشاء المزيد من تعيينات الذاكرة أكثر مما هو مسموح به حاليا. سيغلق ريوجينكس بمجرد تجاوز هذا الحد.", @@ -400,6 +404,8 @@ "InputDialogTitle": "حوار الإدخال", "InputDialogOk": "موافق", "InputDialogCancel": "إلغاء", + "InputDialogCancelling": "Cancelling", + "InputDialogClose": "Close", "InputDialogAddNewProfileTitle": "اختر اسم الملف الشخصي", "InputDialogAddNewProfileHeader": "الرجاء إدخال اسم الملف الشخصي", "InputDialogAddNewProfileSubtext": "(الطول الأقصى: {0})", @@ -469,6 +475,7 @@ "DialogUninstallFileTypesSuccessMessage": "تم إلغاء تثبيت أنواع الملفات بنجاح!", "DialogUninstallFileTypesErrorMessage": "فشل إلغاء تثبيت أنواع الملفات.", "DialogOpenSettingsWindowLabel": "فتح نافذة الإعدادات", + "DialogOpenXCITrimmerWindowLabel": "XCI Trimmer Window", "DialogControllerAppletTitle": "تطبيق وحدة التحكم المصغر", "DialogMessageDialogErrorExceptionMessage": "خطأ في عرض مربع حوار الرسالة: {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "خطأ في عرض لوحة مفاتيح البرامج: {0}", @@ -671,6 +678,12 @@ "TitleUpdateVersionLabel": "الإصدار: {0}", "TitleBundledUpdateVersionLabel": "Bundled: Version {0}", "TitleBundledDlcLabel": "Bundled:", + "TitleXCIStatusPartialLabel": "Partial", + "TitleXCIStatusTrimmableLabel": "Untrimmed", + "TitleXCIStatusUntrimmableLabel": "Trimmed", + "TitleXCIStatusFailedLabel": "(Failed)", + "TitleXCICanSaveLabel": "Save {0:n0} Mb", + "TitleXCISavingLabel": "Saved {0:n0} Mb", "RyujinxInfo": "ريوجينكس - معلومات", "RyujinxConfirm": "ريوجينكس - تأكيد", "FileDialogAllTypes": "كل الأنواع", @@ -723,11 +736,37 @@ "SelectDlcDialogTitle": "حدد ملفات المحتوي الإضافي", "SelectUpdateDialogTitle": "حدد ملفات التحديث", "SelectModDialogTitle": "حدد مجلد التعديل", + "TrimXCIFileDialogTitle": "Check and Trim XCI File", + "TrimXCIFileDialogPrimaryText": "This function will first check the empty space and then trim the XCI File to save disk space.", + "TrimXCIFileDialogSecondaryText": "Current File Size: {0:n} MB\nGame Data Size: {1:n} MB\nDisk Space Savings: {2:n} MB", + "TrimXCIFileNoTrimNecessary": "XCI File does not need to be trimmed. Check logs for further details", + "TrimXCIFileNoUntrimPossible": "XCI File cannot be untrimmed. Check logs for further details", + "TrimXCIFileReadOnlyFileCannotFix": "XCI File is Read Only and could not be made writable. Check logs for further details", + "TrimXCIFileFileSizeChanged": "XCI File has changed in size since it was scanned. Please check the file is not being written to and try again.", + "TrimXCIFileFreeSpaceCheckFailed": "XCI File has data in the free space area, it is not safe to trim", + "TrimXCIFileInvalidXCIFile": "XCI File contains invalid data. Check logs for further details", + "TrimXCIFileFileIOWriteError": "XCI File could not be opened for writing. Check logs for further details", + "TrimXCIFileFailedPrimaryText": "Trimming of the XCI file failed", + "TrimXCIFileCancelled": "The operation was cancelled", + "TrimXCIFileFileUndertermined": "No operation was performed", "UserProfileWindowTitle": "مدير الملفات الشخصية للمستخدمين", "CheatWindowTitle": "مدير الغش", "DlcWindowTitle": "إدارة المحتوى القابل للتنزيل لـ {0} ({1})", "ModWindowTitle": "إدارة التعديلات لـ {0} ({1})", "UpdateWindowTitle": "مدير تحديث العنوان", + "XCITrimmerWindowTitle": "XCI File Trimmer", + "XCITrimmerTitleStatusCount": "{0} of {1} Title(s) Selected", + "XCITrimmerTitleStatusCountWithFilter": "{0} of {1} Title(s) Selected ({2} displayed)", + "XCITrimmerTitleStatusTrimming": "Trimming {0} Title(s)...", + "XCITrimmerTitleStatusUntrimming": "Untrimming {0} Title(s)...", + "XCITrimmerTitleStatusFailed": "Failed", + "XCITrimmerPotentialSavings": "Potential Savings", + "XCITrimmerActualSavings": "Actual Savings", + "XCITrimmerSavingsMb": "{0:n0} Mb", + "XCITrimmerSelectDisplayed": "Select Shown", + "XCITrimmerDeselectDisplayed": "Deselect Shown", + "XCITrimmerSortName": "Title", + "XCITrimmerSortSaved": "Space Savings", "XCITrimmerTrim": "Trim", "XCITrimmerUntrim": "Untrim", "UpdateWindowUpdateAddedMessage": "{0} new update(s) added", @@ -743,6 +782,7 @@ "AutoloadUpdateRemovedMessage": "{0} missing update(s) removed", "ModWindowHeading": "{0} تعديل", "UserProfilesEditProfile": "تعديل المحدد", + "Continue": "Continue", "Cancel": "إلغاء", "Save": "حفظ", "Discard": "تجاهل", @@ -810,5 +850,17 @@ "MultiplayerMode": "الوضع:", "MultiplayerModeTooltip": "تغيير وضع LDN متعدد اللاعبين.\n\nسوف يقوم LdnMitm بتعديل وظيفة اللعب المحلية/اللاسلكية المحلية في الألعاب لتعمل كما لو كانت شبكة LAN، مما يسمح باتصالات الشبكة المحلية نفسها مع محاكيات ريوجينكس الأخرى وأجهزة نينتندو سويتش المخترقة التي تم تثبيت وحدة ldn_mitm عليها.\n\nيتطلب وضع اللاعبين المتعددين أن يكون جميع اللاعبين على نفس إصدار اللعبة (على سبيل المثال، يتعذر على الإصدار 13.0.1 من سوبر سماش برذرز ألتميت الاتصال بالإصدار 13.0.0).\n\nاتركه معطلا إذا لم تكن متأكدا.", "MultiplayerModeDisabled": "معطل", - "MultiplayerModeLdnMitm": "ldn_mitm" + "MultiplayerModeLdnMitm": "ldn_mitm", + "MultiplayerModeLdnRyu": "RyuLDN", + "MultiplayerDisableP2P": "Disable P2P Network Hosting (may increase latency)", + "MultiplayerDisableP2PTooltip": "Disable P2P network hosting, peers will proxy through the master server instead of connecting to you directly.", + "LdnPassphrase": "Network Passphrase:", + "LdnPassphraseTooltip": "You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputTooltip": "Enter a passphrase in the format Ryujinx-<8 hex chars>. You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputPublic": "(public)", + "GenLdnPass": "Generate Random", + "GenLdnPassTooltip": "Generates a new passphrase, which can be shared with other players.", + "ClearLdnPass": "Clear", + "ClearLdnPassTooltip": "Clears the current passphrase, returning to the public network.", + "InvalidLdnPassphrase": "Invalid Passphrase! Must be in the format \"Ryujinx-<8 hex chars>\"" } diff --git a/src/Ryujinx/Assets/Locales/de_DE.json b/src/Ryujinx/Assets/Locales/de_DE.json index c6f9768c6d..be95f3bc0d 100644 --- a/src/Ryujinx/Assets/Locales/de_DE.json +++ b/src/Ryujinx/Assets/Locales/de_DE.json @@ -33,6 +33,7 @@ "MenuBarToolsManageFileTypes": "Dateitypen verwalten", "MenuBarToolsInstallFileTypes": "Dateitypen installieren", "MenuBarToolsUninstallFileTypes": "Dateitypen deinstallieren", + "MenuBarToolsXCITrimmer": "Trim XCI Files", "MenuBarView": "_Ansicht", "MenuBarViewWindow": "Fenstergröße", "MenuBarViewWindow720": "720p", @@ -84,8 +85,11 @@ "GameListContextMenuOpenModsDirectoryToolTip": "Öffnet das Verzeichnis, welches Mods für die Spiele beinhaltet", "GameListContextMenuOpenSdModsDirectory": "Atmosphere-Mod-Verzeichnis öffnen", "GameListContextMenuOpenSdModsDirectoryToolTip": "Öffnet das alternative SD-Karten-Atmosphere-Verzeichnis, das die Mods der Anwendung enthält. Dieser Ordner ist nützlich für Mods, die für echte Hardware erstellt worden sind.", + "GameListContextMenuTrimXCI": "Check and Trim XCI File", + "GameListContextMenuTrimXCIToolTip": "Check and Trim XCI File to Save Disk Space", "StatusBarGamesLoaded": "{0}/{1} Spiele geladen", "StatusBarSystemVersion": "Systemversion: {0}", + "StatusBarXCIFileTrimming": "Trimming XCI File '{0}'", "LinuxVmMaxMapCountDialogTitle": "Niedriges Limit für Speicherzuordnungen erkannt", "LinuxVmMaxMapCountDialogTextPrimary": "Möchtest Du den Wert von vm.max_map_count auf {0} erhöhen", "LinuxVmMaxMapCountDialogTextSecondary": "Einige Spiele könnten versuchen, mehr Speicherzuordnungen zu erstellen, als derzeit erlaubt. Ryujinx wird abstürzen, sobald dieses Limit überschritten wird.", @@ -400,6 +404,8 @@ "InputDialogTitle": "Eingabe-Dialog", "InputDialogOk": "OK", "InputDialogCancel": "Abbrechen", + "InputDialogCancelling": "Cancelling", + "InputDialogClose": "Close", "InputDialogAddNewProfileTitle": "Wähle den Profilnamen", "InputDialogAddNewProfileHeader": "Bitte gebe einen Profilnamen ein", "InputDialogAddNewProfileSubtext": "(Maximale Länge: {0})", @@ -469,6 +475,7 @@ "DialogUninstallFileTypesSuccessMessage": "Dateitypen erfolgreich deinstalliert!", "DialogUninstallFileTypesErrorMessage": "Deinstallation der Dateitypen fehlgeschlagen.", "DialogOpenSettingsWindowLabel": "Fenster-Einstellungen öffnen", + "DialogOpenXCITrimmerWindowLabel": "XCI Trimmer Window", "DialogControllerAppletTitle": "Controller-Applet", "DialogMessageDialogErrorExceptionMessage": "Fehler bei der Anzeige des Meldungs-Dialogs: {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "Fehler bei der Anzeige der Software-Tastatur: {0}", @@ -671,6 +678,12 @@ "TitleUpdateVersionLabel": "Version {0} - {1}", "TitleBundledUpdateVersionLabel": "Bundled: Version {0}", "TitleBundledDlcLabel": "Bundled:", + "TitleXCIStatusPartialLabel": "Partial", + "TitleXCIStatusTrimmableLabel": "Untrimmed", + "TitleXCIStatusUntrimmableLabel": "Trimmed", + "TitleXCIStatusFailedLabel": "(Failed)", + "TitleXCICanSaveLabel": "Save {0:n0} Mb", + "TitleXCISavingLabel": "Saved {0:n0} Mb", "RyujinxInfo": "Ryujinx - Info", "RyujinxConfirm": "Ryujinx - Bestätigung", "FileDialogAllTypes": "Alle Typen", @@ -723,11 +736,37 @@ "SelectDlcDialogTitle": "DLC-Dateien auswählen", "SelectUpdateDialogTitle": "Update-Datei auswählen", "SelectModDialogTitle": "Mod-Ordner auswählen", + "TrimXCIFileDialogTitle": "Check and Trim XCI File", + "TrimXCIFileDialogPrimaryText": "This function will first check the empty space and then trim the XCI File to save disk space.", + "TrimXCIFileDialogSecondaryText": "Current File Size: {0:n} MB\nGame Data Size: {1:n} MB\nDisk Space Savings: {2:n} MB", + "TrimXCIFileNoTrimNecessary": "XCI File does not need to be trimmed. Check logs for further details", + "TrimXCIFileNoUntrimPossible": "XCI File cannot be untrimmed. Check logs for further details", + "TrimXCIFileReadOnlyFileCannotFix": "XCI File is Read Only and could not be made writable. Check logs for further details", + "TrimXCIFileFileSizeChanged": "XCI File has changed in size since it was scanned. Please check the file is not being written to and try again.", + "TrimXCIFileFreeSpaceCheckFailed": "XCI File has data in the free space area, it is not safe to trim", + "TrimXCIFileInvalidXCIFile": "XCI File contains invalid data. Check logs for further details", + "TrimXCIFileFileIOWriteError": "XCI File could not be opened for writing. Check logs for further details", + "TrimXCIFileFailedPrimaryText": "Trimming of the XCI file failed", + "TrimXCIFileCancelled": "The operation was cancelled", + "TrimXCIFileFileUndertermined": "No operation was performed", "UserProfileWindowTitle": "Benutzerprofile verwalten", "CheatWindowTitle": "Spiel-Cheats verwalten", "DlcWindowTitle": "Spiel-DLC verwalten", "ModWindowTitle": "Manage Mods for {0} ({1})", "UpdateWindowTitle": "Spiel-Updates verwalten", + "XCITrimmerWindowTitle": "XCI File Trimmer", + "XCITrimmerTitleStatusCount": "{0} of {1} Title(s) Selected", + "XCITrimmerTitleStatusCountWithFilter": "{0} of {1} Title(s) Selected ({2} displayed)", + "XCITrimmerTitleStatusTrimming": "Trimming {0} Title(s)...", + "XCITrimmerTitleStatusUntrimming": "Untrimming {0} Title(s)...", + "XCITrimmerTitleStatusFailed": "Failed", + "XCITrimmerPotentialSavings": "Potential Savings", + "XCITrimmerActualSavings": "Actual Savings", + "XCITrimmerSavingsMb": "{0:n0} Mb", + "XCITrimmerSelectDisplayed": "Select Shown", + "XCITrimmerDeselectDisplayed": "Deselect Shown", + "XCITrimmerSortName": "Title", + "XCITrimmerSortSaved": "Space Savings", "XCITrimmerTrim": "Trim", "XCITrimmerUntrim": "Untrim", "UpdateWindowUpdateAddedMessage": "{0} new update(s) added", @@ -743,6 +782,7 @@ "AutoloadUpdateRemovedMessage": "{0} missing update(s) removed", "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "Profil bearbeiten", + "Continue": "Continue", "Cancel": "Abbrechen", "Save": "Speichern", "Discard": "Verwerfen", @@ -810,5 +850,17 @@ "MultiplayerMode": "Modus:", "MultiplayerModeTooltip": "Ä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.", "MultiplayerModeDisabled": "Deaktiviert", - "MultiplayerModeLdnMitm": "ldn_mitm" + "MultiplayerModeLdnMitm": "ldn_mitm", + "MultiplayerModeLdnRyu": "RyuLDN", + "MultiplayerDisableP2P": "Disable P2P Network Hosting (may increase latency)", + "MultiplayerDisableP2PTooltip": "Disable P2P network hosting, peers will proxy through the master server instead of connecting to you directly.", + "LdnPassphrase": "Network Passphrase:", + "LdnPassphraseTooltip": "You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputTooltip": "Enter a passphrase in the format Ryujinx-<8 hex chars>. You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputPublic": "(public)", + "GenLdnPass": "Generate Random", + "GenLdnPassTooltip": "Generates a new passphrase, which can be shared with other players.", + "ClearLdnPass": "Clear", + "ClearLdnPassTooltip": "Clears the current passphrase, returning to the public network.", + "InvalidLdnPassphrase": "Invalid Passphrase! Must be in the format \"Ryujinx-<8 hex chars>\"" } diff --git a/src/Ryujinx/Assets/Locales/el_GR.json b/src/Ryujinx/Assets/Locales/el_GR.json index 76049fc3fc..c6cfb9d62e 100644 --- a/src/Ryujinx/Assets/Locales/el_GR.json +++ b/src/Ryujinx/Assets/Locales/el_GR.json @@ -33,6 +33,7 @@ "MenuBarToolsManageFileTypes": "Διαχείριση τύπων αρχείων", "MenuBarToolsInstallFileTypes": "Εγκαταστήσετε τύπους αρχείων.", "MenuBarToolsUninstallFileTypes": "Απεγκαταστήσετε τύπους αρχείων", + "MenuBarToolsXCITrimmer": "Trim XCI Files", "MenuBarView": "_View", "MenuBarViewWindow": "Window Size", "MenuBarViewWindow720": "720p", @@ -84,8 +85,11 @@ "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", + "GameListContextMenuTrimXCI": "Check and Trim XCI File", + "GameListContextMenuTrimXCIToolTip": "Check and Trim XCI File to Save Disk Space", "StatusBarGamesLoaded": "{0}/{1} Φορτωμένα Παιχνίδια", "StatusBarSystemVersion": "Έκδοση Συστήματος: {0}", + "StatusBarXCIFileTrimming": "Trimming XCI File '{0}'", "LinuxVmMaxMapCountDialogTitle": "Εντοπίστηκε χαμηλό όριο για αντιστοιχίσεις μνήμης", "LinuxVmMaxMapCountDialogTextPrimary": "Θα θέλατε να αυξήσετε την τιμή του vm.max_map_count σε {0}", "LinuxVmMaxMapCountDialogTextSecondary": "Μερικά παιχνίδια μπορεί να προσπαθήσουν να δημιουργήσουν περισσότερες αντιστοιχίσεις μνήμης από αυτές που επιτρέπονται τώρα. Ο Ryujinx θα καταρρεύσει μόλις ξεπεραστεί αυτό το όριο.", @@ -400,6 +404,8 @@ "InputDialogTitle": "Διάλογος Εισαγωγής", "InputDialogOk": "ΟΚ", "InputDialogCancel": "Ακύρωση", + "InputDialogCancelling": "Cancelling", + "InputDialogClose": "Close", "InputDialogAddNewProfileTitle": "Επιλογή Ονόματος Προφίλ", "InputDialogAddNewProfileHeader": "Εισαγωγή Ονόματος Προφίλ", "InputDialogAddNewProfileSubtext": "(Σύνολο Χαρακτήρων: {0})", @@ -469,6 +475,7 @@ "DialogUninstallFileTypesSuccessMessage": "Επιτυχής απεγκατάσταση τύπων αρχείων!", "DialogUninstallFileTypesErrorMessage": "Αποτυχία απεγκατάστασης τύπων αρχείων.", "DialogOpenSettingsWindowLabel": "Άνοιγμα Παραθύρου Ρυθμίσεων", + "DialogOpenXCITrimmerWindowLabel": "XCI Trimmer Window", "DialogControllerAppletTitle": "Applet Χειρισμού", "DialogMessageDialogErrorExceptionMessage": "Σφάλμα εμφάνισης του διαλόγου Μηνυμάτων: {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "Σφάλμα εμφάνισης Λογισμικού Πληκτρολογίου: {0}", @@ -671,6 +678,12 @@ "TitleUpdateVersionLabel": "Version {0} - {1}", "TitleBundledUpdateVersionLabel": "Bundled: Version {0}", "TitleBundledDlcLabel": "Bundled:", + "TitleXCIStatusPartialLabel": "Partial", + "TitleXCIStatusTrimmableLabel": "Untrimmed", + "TitleXCIStatusUntrimmableLabel": "Trimmed", + "TitleXCIStatusFailedLabel": "(Failed)", + "TitleXCICanSaveLabel": "Save {0:n0} Mb", + "TitleXCISavingLabel": "Saved {0:n0} Mb", "RyujinxInfo": "Ryujinx - Πληροφορίες", "RyujinxConfirm": "Ryujinx - Επιβεβαίωση", "FileDialogAllTypes": "Όλοι οι τύποι", @@ -723,11 +736,37 @@ "SelectDlcDialogTitle": "Επιλογή αρχείων DLC", "SelectUpdateDialogTitle": "Επιλογή αρχείων ενημέρωσης", "SelectModDialogTitle": "Select mod directory", + "TrimXCIFileDialogTitle": "Check and Trim XCI File", + "TrimXCIFileDialogPrimaryText": "This function will first check the empty space and then trim the XCI File to save disk space.", + "TrimXCIFileDialogSecondaryText": "Current File Size: {0:n} MB\nGame Data Size: {1:n} MB\nDisk Space Savings: {2:n} MB", + "TrimXCIFileNoTrimNecessary": "XCI File does not need to be trimmed. Check logs for further details", + "TrimXCIFileNoUntrimPossible": "XCI File cannot be untrimmed. Check logs for further details", + "TrimXCIFileReadOnlyFileCannotFix": "XCI File is Read Only and could not be made writable. Check logs for further details", + "TrimXCIFileFileSizeChanged": "XCI File has changed in size since it was scanned. Please check the file is not being written to and try again.", + "TrimXCIFileFreeSpaceCheckFailed": "XCI File has data in the free space area, it is not safe to trim", + "TrimXCIFileInvalidXCIFile": "XCI File contains invalid data. Check logs for further details", + "TrimXCIFileFileIOWriteError": "XCI File could not be opened for writing. Check logs for further details", + "TrimXCIFileFailedPrimaryText": "Trimming of the XCI file failed", + "TrimXCIFileCancelled": "The operation was cancelled", + "TrimXCIFileFileUndertermined": "No operation was performed", "UserProfileWindowTitle": "Διαχειριστής Προφίλ Χρήστη", "CheatWindowTitle": "Διαχειριστής των Cheats", "DlcWindowTitle": "Downloadable Content Manager", "ModWindowTitle": "Manage Mods for {0} ({1})", "UpdateWindowTitle": "Διαχειριστής Ενημερώσεων Τίτλου", + "XCITrimmerWindowTitle": "XCI File Trimmer", + "XCITrimmerTitleStatusCount": "{0} of {1} Title(s) Selected", + "XCITrimmerTitleStatusCountWithFilter": "{0} of {1} Title(s) Selected ({2} displayed)", + "XCITrimmerTitleStatusTrimming": "Trimming {0} Title(s)...", + "XCITrimmerTitleStatusUntrimming": "Untrimming {0} Title(s)...", + "XCITrimmerTitleStatusFailed": "Failed", + "XCITrimmerPotentialSavings": "Potential Savings", + "XCITrimmerActualSavings": "Actual Savings", + "XCITrimmerSavingsMb": "{0:n0} Mb", + "XCITrimmerSelectDisplayed": "Select Shown", + "XCITrimmerDeselectDisplayed": "Deselect Shown", + "XCITrimmerSortName": "Title", + "XCITrimmerSortSaved": "Space Savings", "XCITrimmerTrim": "Trim", "XCITrimmerUntrim": "Untrim", "UpdateWindowUpdateAddedMessage": "{0} new update(s) added", @@ -743,6 +782,7 @@ "AutoloadUpdateRemovedMessage": "{0} missing update(s) removed", "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "Επεξεργασία Επιλεγμένων", + "Continue": "Continue", "Cancel": "Ακύρωση", "Save": "Αποθήκευση", "Discard": "Απόρριψη", @@ -810,5 +850,17 @@ "MultiplayerMode": "Λειτουργία:", "MultiplayerModeTooltip": "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.", "MultiplayerModeDisabled": "Disabled", - "MultiplayerModeLdnMitm": "ldn_mitm" + "MultiplayerModeLdnMitm": "ldn_mitm", + "MultiplayerModeLdnRyu": "RyuLDN", + "MultiplayerDisableP2P": "Disable P2P Network Hosting (may increase latency)", + "MultiplayerDisableP2PTooltip": "Disable P2P network hosting, peers will proxy through the master server instead of connecting to you directly.", + "LdnPassphrase": "Network Passphrase:", + "LdnPassphraseTooltip": "You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputTooltip": "Enter a passphrase in the format Ryujinx-<8 hex chars>. You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputPublic": "(public)", + "GenLdnPass": "Generate Random", + "GenLdnPassTooltip": "Generates a new passphrase, which can be shared with other players.", + "ClearLdnPass": "Clear", + "ClearLdnPassTooltip": "Clears the current passphrase, returning to the public network.", + "InvalidLdnPassphrase": "Invalid Passphrase! Must be in the format \"Ryujinx-<8 hex chars>\"" } diff --git a/src/Ryujinx/Assets/Locales/es_ES.json b/src/Ryujinx/Assets/Locales/es_ES.json index cf5c586d0b..6a194960b4 100644 --- a/src/Ryujinx/Assets/Locales/es_ES.json +++ b/src/Ryujinx/Assets/Locales/es_ES.json @@ -33,6 +33,7 @@ "MenuBarToolsManageFileTypes": "Administrar tipos de archivo", "MenuBarToolsInstallFileTypes": "Instalar tipos de archivo", "MenuBarToolsUninstallFileTypes": "Desinstalar tipos de archivo", + "MenuBarToolsXCITrimmer": "Trim XCI Files", "MenuBarView": "_View", "MenuBarViewWindow": "Tamaño Ventana", "MenuBarViewWindow720": "720p", @@ -84,8 +85,11 @@ "GameListContextMenuOpenModsDirectoryToolTip": "Abre el directorio que contiene los Mods de la Aplicación.", "GameListContextMenuOpenSdModsDirectory": "Abrir Directorio de Mods de Atmosphere\n\n\n\n\n\n", "GameListContextMenuOpenSdModsDirectoryToolTip": "Abre el directorio alternativo de la tarjeta SD de Atmosphere que contiene los Mods de la Aplicación. Útil para los mods que están empaquetados para el hardware real.", + "GameListContextMenuTrimXCI": "Check and Trim XCI File", + "GameListContextMenuTrimXCIToolTip": "Check and Trim XCI File to Save Disk Space", "StatusBarGamesLoaded": "{0}/{1} juegos cargados", "StatusBarSystemVersion": "Versión del sistema: {0}", + "StatusBarXCIFileTrimming": "Trimming XCI File '{0}'", "LinuxVmMaxMapCountDialogTitle": "Límite inferior para mapeos de memoria detectado", "LinuxVmMaxMapCountDialogTextPrimary": "¿Quieres aumentar el valor de vm.max_map_count a {0}?", "LinuxVmMaxMapCountDialogTextSecondary": "Algunos juegos podrían intentar crear más mapeos de memoria de los permitidos. Ryujinx se bloqueará tan pronto como se supere este límite.", @@ -400,6 +404,8 @@ "InputDialogTitle": "Cuadro de diálogo de entrada", "InputDialogOk": "Aceptar", "InputDialogCancel": "Cancelar", + "InputDialogCancelling": "Cancelling", + "InputDialogClose": "Close", "InputDialogAddNewProfileTitle": "Introducir nombre de perfil", "InputDialogAddNewProfileHeader": "Por favor elige un nombre de usuario", "InputDialogAddNewProfileSubtext": "(Máximo de caracteres: {0})", @@ -469,6 +475,7 @@ "DialogUninstallFileTypesSuccessMessage": "¡Tipos de archivos desinstalados con éxito!", "DialogUninstallFileTypesErrorMessage": "No se pudo desinstalar los tipos de archivo.", "DialogOpenSettingsWindowLabel": "Abrir ventana de opciones", + "DialogOpenXCITrimmerWindowLabel": "XCI Trimmer Window", "DialogControllerAppletTitle": "Applet de mandos", "DialogMessageDialogErrorExceptionMessage": "Error al mostrar cuadro de diálogo: {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "Error al mostrar teclado de software: {0}", @@ -671,6 +678,12 @@ "TitleUpdateVersionLabel": "Versión {0} - {1}", "TitleBundledUpdateVersionLabel": "Bundled: Version {0}", "TitleBundledDlcLabel": "Bundled:", + "TitleXCIStatusPartialLabel": "Partial", + "TitleXCIStatusTrimmableLabel": "Untrimmed", + "TitleXCIStatusUntrimmableLabel": "Trimmed", + "TitleXCIStatusFailedLabel": "(Failed)", + "TitleXCICanSaveLabel": "Save {0:n0} Mb", + "TitleXCISavingLabel": "Saved {0:n0} Mb", "RyujinxInfo": "Ryujinx - Info", "RyujinxConfirm": "Ryujinx - Confirmación", "FileDialogAllTypes": "Todos los tipos", @@ -723,11 +736,37 @@ "SelectDlcDialogTitle": "Selecciona archivo(s) de DLC", "SelectUpdateDialogTitle": "Selecciona archivo(s) de actualización", "SelectModDialogTitle": "Seleccionar un directorio de Mods", + "TrimXCIFileDialogTitle": "Check and Trim XCI File", + "TrimXCIFileDialogPrimaryText": "This function will first check the empty space and then trim the XCI File to save disk space.", + "TrimXCIFileDialogSecondaryText": "Current File Size: {0:n} MB\nGame Data Size: {1:n} MB\nDisk Space Savings: {2:n} MB", + "TrimXCIFileNoTrimNecessary": "XCI File does not need to be trimmed. Check logs for further details", + "TrimXCIFileNoUntrimPossible": "XCI File cannot be untrimmed. Check logs for further details", + "TrimXCIFileReadOnlyFileCannotFix": "XCI File is Read Only and could not be made writable. Check logs for further details", + "TrimXCIFileFileSizeChanged": "XCI File has changed in size since it was scanned. Please check the file is not being written to and try again.", + "TrimXCIFileFreeSpaceCheckFailed": "XCI File has data in the free space area, it is not safe to trim", + "TrimXCIFileInvalidXCIFile": "XCI File contains invalid data. Check logs for further details", + "TrimXCIFileFileIOWriteError": "XCI File could not be opened for writing. Check logs for further details", + "TrimXCIFileFailedPrimaryText": "Trimming of the XCI file failed", + "TrimXCIFileCancelled": "The operation was cancelled", + "TrimXCIFileFileUndertermined": "No operation was performed", "UserProfileWindowTitle": "Administrar perfiles de usuario", "CheatWindowTitle": "Administrar cheats", "DlcWindowTitle": "Administrar contenido descargable", "ModWindowTitle": "Administrar Mods para {0} ({1})", "UpdateWindowTitle": "Administrar actualizaciones", + "XCITrimmerWindowTitle": "XCI File Trimmer", + "XCITrimmerTitleStatusCount": "{0} of {1} Title(s) Selected", + "XCITrimmerTitleStatusCountWithFilter": "{0} of {1} Title(s) Selected ({2} displayed)", + "XCITrimmerTitleStatusTrimming": "Trimming {0} Title(s)...", + "XCITrimmerTitleStatusUntrimming": "Untrimming {0} Title(s)...", + "XCITrimmerTitleStatusFailed": "Failed", + "XCITrimmerPotentialSavings": "Potential Savings", + "XCITrimmerActualSavings": "Actual Savings", + "XCITrimmerSavingsMb": "{0:n0} Mb", + "XCITrimmerSelectDisplayed": "Select Shown", + "XCITrimmerDeselectDisplayed": "Deselect Shown", + "XCITrimmerSortName": "Title", + "XCITrimmerSortSaved": "Space Savings", "XCITrimmerTrim": "Trim", "XCITrimmerUntrim": "Untrim", "UpdateWindowUpdateAddedMessage": "{0} nueva(s) actualización(es) agregada(s)", @@ -742,6 +781,7 @@ "AutoloadUpdateRemovedMessage": "Se eliminaron {0} actualización(es) faltantes", "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "Editar selección", + "Continue": "Continue", "Cancel": "Cancelar", "Save": "Guardar", "Discard": "Descartar", diff --git a/src/Ryujinx/Assets/Locales/fr_FR.json b/src/Ryujinx/Assets/Locales/fr_FR.json index 0073a2cf50..dd23bef768 100644 --- a/src/Ryujinx/Assets/Locales/fr_FR.json +++ b/src/Ryujinx/Assets/Locales/fr_FR.json @@ -33,6 +33,7 @@ "MenuBarToolsManageFileTypes": "Gérer les types de fichiers", "MenuBarToolsInstallFileTypes": "Installer les types de fichiers", "MenuBarToolsUninstallFileTypes": "Désinstaller les types de fichiers", + "MenuBarToolsXCITrimmer": "Réduire les fichiers XCI", "MenuBarView": "_Fenêtre", "MenuBarViewWindow": "Taille de la fenêtre", "MenuBarViewWindow720": "720p", @@ -84,8 +85,11 @@ "GameListContextMenuOpenModsDirectoryToolTip": "Ouvre le dossier contenant les mods du jeu", "GameListContextMenuOpenSdModsDirectory": "Ouvrir le dossier des mods Atmosphère", "GameListContextMenuOpenSdModsDirectoryToolTip": "Ouvre le dossier alternatif de la carte SD Atmosphère qui contient les mods de l'application. Utile pour les mods conçus pour console.", + "GameListContextMenuTrimXCI": "Vérifier et réduire les fichiers XCI", + "GameListContextMenuTrimXCIToolTip": "Vérifier et réduire les fichiers XCI pour économiser de l'espace", "StatusBarGamesLoaded": "{0}/{1} Jeux chargés", "StatusBarSystemVersion": "Version du Firmware: {0}", + "StatusBarXCIFileTrimming": "Réduction du fichier XCI '{0}'", "LinuxVmMaxMapCountDialogTitle": "Limite basse pour les mappings mémoire détectée", "LinuxVmMaxMapCountDialogTextPrimary": "Voulez-vous augmenter la valeur de vm.max_map_count à {0}", "LinuxVmMaxMapCountDialogTextSecondary": "Certains jeux peuvent essayer de créer plus de mappings mémoire que ce qui est actuellement autorisé. Ryujinx plantera dès que cette limite sera dépassée.", @@ -400,6 +404,8 @@ "InputDialogTitle": "Fenêtre d'entrée de texte", "InputDialogOk": "OK", "InputDialogCancel": "Annuler", + "InputDialogCancelling": "Annulation en cours", + "InputDialogClose": "Fermer", "InputDialogAddNewProfileTitle": "Choisir un nom de profil", "InputDialogAddNewProfileHeader": "Merci d'entrer un nom de profil", "InputDialogAddNewProfileSubtext": "(Longueur max.: {0})", @@ -469,6 +475,7 @@ "DialogUninstallFileTypesSuccessMessage": "Types de fichiers désinstallés avec succès!", "DialogUninstallFileTypesErrorMessage": "Échec de la désinstallation des types de fichiers.", "DialogOpenSettingsWindowLabel": "Ouvrir la fenêtre de configuration", + "DialogOpenXCITrimmerWindowLabel": "Fenêtre de réduction de fichiers XCI", "DialogControllerAppletTitle": "Programme Manette", "DialogMessageDialogErrorExceptionMessage": "Erreur lors de l'affichage de la boîte de dialogue : {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "Erreur lors de l'affichage du clavier logiciel: {0}", @@ -671,6 +678,12 @@ "TitleUpdateVersionLabel": "Version {0}", "TitleBundledUpdateVersionLabel": "Inclus avec le jeu: Version {0}", "TitleBundledDlcLabel": "Inclus avec le jeu :", + "TitleXCIStatusPartialLabel": "Partiel", + "TitleXCIStatusTrimmableLabel": "Non réduit", + "TitleXCIStatusUntrimmableLabel": "Réduit", + "TitleXCIStatusFailedLabel": "(Échoué)", + "TitleXCICanSaveLabel": "Sauvegarde de {0:n0} Mo", + "TitleXCISavingLabel": "Sauvegardé {0:n0} Mo", "RyujinxInfo": "Ryujinx - Info", "RyujinxConfirm": "Ryujinx - Confirmation", "FileDialogAllTypes": "Tous les types", @@ -723,11 +736,39 @@ "SelectDlcDialogTitle": "Sélectionner les fichiers DLC", "SelectUpdateDialogTitle": "Sélectionner les fichiers de mise à jour", "SelectModDialogTitle": "Sélectionner le répertoire du mod", + "TrimXCIFileDialogTitle": "Vérifier et Réduire le fichier XCI", + "TrimXCIFileDialogPrimaryText": "Cette fonction va vérifier l'espace vide, puis réduire le fichier XCI pour économiser de l'espace de disque dur.", + "TrimXCIFileDialogSecondaryText": "Taille actuelle du fichier: {0:n} MB\nTaille des données de jeux: {1:n} MB\nÉconomie d'espaces sur le disque: {2:n} MB", + "TrimXCIFileNoTrimNecessary": "Fichier XCI n'a pas besoin d'être réduit. Regarder les journaux pour plus de détails", + "TrimXCIFileNoUntrimPossible": "Fichier XCI ne peut pas être dé-réduit. Regarder les journaux pour plus de détails", + "TrimXCIFileReadOnlyFileCannotFix": "Fichier XCI est en Lecture Seule et n'a pas pu être rendu accessible en écriture. Regarder les journaux pour plus de détails", + "TrimXCIFileFileSizeChanged": "Fichier XCI a changé en taille depuis qu'il a été scanné. Vérifier que le fichier n'est pas en cours d'écriture et réessayer.", + "TrimXCIFileFreeSpaceCheckFailed": "Fichier XCI a des données dans la zone d'espace libre, ce n'est pas sûr de réduire", + "TrimXCIFileInvalidXCIFile": "Fichier XCI contient des données invalides. Regarder les journaux pour plus de détails", + "TrimXCIFileFileIOWriteError": "Fichier XCI n'a pas pu été ouvert pour écriture. Regarder les journaux pour plus de détails", + "TrimXCIFileFailedPrimaryText": "Réduction du fichier XCI a échoué", + "TrimXCIFileCancelled": "L'opération a été annulée", + "TrimXCIFileFileUndertermined": "Aucune opération a été faite", "UserProfileWindowTitle": "Gestionnaire de profils utilisateur", "CheatWindowTitle": "Gestionnaire de cheats", "DlcWindowTitle": "Gérer le contenu téléchargeable pour {0} ({1})", "ModWindowTitle": "Gérer les mods pour {0} ({1})", "UpdateWindowTitle": "Gestionnaire de mises à jour", + "XCITrimmerWindowTitle": "Rogneur de fichier XCI", + "XCITrimmerTitleStatusCount": "{0} sur {1} Fichier(s) Sélectionnés", + "XCITrimmerTitleStatusCountWithFilter": "{0} sur {1} Fichier(s) Sélectionnés ({2} affiché(s)", + "XCITrimmerTitleStatusTrimming": "Réduction de {0} Fichier(s)...", + "XCITrimmerTitleStatusUntrimming": "Dé-Réduction de {0} Fichier(s)...", + "XCITrimmerTitleStatusFailed": "Échoué", + "XCITrimmerPotentialSavings": "Économies potentielles d'espace de disque dur", + "XCITrimmerActualSavings": "Économies actualles d'espace de disque dur", + "XCITrimmerSavingsMb": "{0:n0} Mo", + "XCITrimmerSelectDisplayed": "Sélectionner Affiché", + "XCITrimmerDeselectDisplayed": "Désélectionner Affiché", + "XCITrimmerSortName": "Titre", + "XCITrimmerSortSaved": "Économies de disque dur", + "XCITrimmerTrim": "Réduire", + "XCITrimmerUntrim": "Dé-Réduire", "UpdateWindowUpdateAddedMessage": "{0} nouvelle(s) mise(s) à jour ajoutée(s)", "UpdateWindowBundledContentNotice": "Les mises à jour incluses avec le jeu ne peuvent pas être supprimées mais peuvent être désactivées.", "CheatWindowHeading": "Cheats disponibles pour {0} [{1}]", @@ -741,6 +782,7 @@ "AutoloadUpdateRemovedMessage": "{0} mises à jour manquantes supprimées", "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "Éditer la sélection", + "Continue": "Continuer", "Cancel": "Annuler", "Save": "Enregistrer", "Discard": "Abandonner", @@ -808,5 +850,17 @@ "MultiplayerMode": "Mode :", "MultiplayerModeTooltip": "Changer 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.", "MultiplayerModeDisabled": "Désactivé", - "MultiplayerModeLdnMitm": "ldn_mitm" + "MultiplayerModeLdnMitm": "ldn_mitm", + "MultiplayerModeLdnRyu": "RyuLDN", + "MultiplayerDisableP2P": "Désactiver PàP Hébergement de Réseau (pourrait augmenter la latence)", + "MultiplayerDisableP2PTooltip": "Désactiver PàP hébergement de réseau, les postes vont proxy avec le serveur principal au lieu de se connecter directement à vous.", + "LdnPassphrase": "Mot de passe Réseau :", + "LdnPassphraseTooltip": "Vous pourez seulement voir les jeux hébergé avec le même mot de passe que vous.", + "LdnPassphraseInputTooltip": "Entrer un mot de passe dans le format Ryujinx-<8 hex chars>. Vous pourez seulement voir les jeux hébergé avec le même mot de passe que vous.", + "LdnPassphraseInputPublic": "(publique)", + "GenLdnPass": "Générer Aléatoire", + "GenLdnPassTooltip": "Génére un nouveau mot de passe, qui peut être partagé avec les autres.", + "ClearLdnPass": "Supprimer", + "ClearLdnPassTooltip": "Supprime le mot de passe actuel, ce qui vous remet sur le réseau public.", + "InvalidLdnPassphrase": "Mot de passe invalide! Il doit être dans le format \"Ryujinx-<8 hex chars>\"" } diff --git a/src/Ryujinx/Assets/Locales/he_IL.json b/src/Ryujinx/Assets/Locales/he_IL.json index 91e3e24e41..b9f89eb37b 100644 --- a/src/Ryujinx/Assets/Locales/he_IL.json +++ b/src/Ryujinx/Assets/Locales/he_IL.json @@ -33,6 +33,7 @@ "MenuBarToolsManageFileTypes": "ניהול סוגי קבצים", "MenuBarToolsInstallFileTypes": "סוגי קבצי התקנה", "MenuBarToolsUninstallFileTypes": "סוגי קבצי הסרה", + "MenuBarToolsXCITrimmer": "Trim XCI Files", "MenuBarView": "_View", "MenuBarViewWindow": "Window Size", "MenuBarViewWindow720": "720p", @@ -84,8 +85,11 @@ "GameListContextMenuOpenModsDirectoryToolTip": "פותח את התיקייה שמכילה מודים של האפליקציה", "GameListContextMenuOpenSdModsDirectory": "פתח תיקיית מודים של Atmosphere", "GameListContextMenuOpenSdModsDirectoryToolTip": "פותח את תיקיית כרטיס ה-SD החלופית של Atmosphere המכילה את המודים של האפליקציה. שימושי עבור מודים שארוזים עבור חומרה אמיתית.", + "GameListContextMenuTrimXCI": "Check and Trim XCI File", + "GameListContextMenuTrimXCIToolTip": "Check and Trim XCI File to Save Disk Space", "StatusBarGamesLoaded": "{1}/{0} משחקים נטענו", "StatusBarSystemVersion": "גרסת מערכת: {0}", + "StatusBarXCIFileTrimming": "Trimming XCI File '{0}'", "LinuxVmMaxMapCountDialogTitle": "זוהתה מגבלה נמוכה עבור מיפויי זיכרון", "LinuxVmMaxMapCountDialogTextPrimary": "האם תרצה להגביר את הערך של vm.max_map_count ל{0}", "LinuxVmMaxMapCountDialogTextSecondary": "משחקים מסוימים עלולים לייצר עוד מיפויי זיכרון ממה שמתאפשר. Ryujinx יקרוס ברגע שהמגבלה תחרוג.", @@ -400,6 +404,8 @@ "InputDialogTitle": "דיאלוג קלט", "InputDialogOk": "בסדר", "InputDialogCancel": "ביטול", + "InputDialogCancelling": "Cancelling", + "InputDialogClose": "Close", "InputDialogAddNewProfileTitle": "בחרו את שם הפרופיל", "InputDialogAddNewProfileHeader": "אנא הזינו שם לפרופיל", "InputDialogAddNewProfileSubtext": "(אורך מרבי: {0})", @@ -469,6 +475,7 @@ "DialogUninstallFileTypesSuccessMessage": "סוגי קבצים הוסרו בהצלחה!", "DialogUninstallFileTypesErrorMessage": "נכשל בהסרת סוגי קבצים.", "DialogOpenSettingsWindowLabel": "פתח את חלון ההגדרות", + "DialogOpenXCITrimmerWindowLabel": "XCI Trimmer Window", "DialogControllerAppletTitle": "יישומון בקר", "DialogMessageDialogErrorExceptionMessage": "שגיאה בהצגת דיאלוג ההודעה: {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "שגיאה בהצגת תוכנת המקלדת: {0}", @@ -671,6 +678,12 @@ "TitleUpdateVersionLabel": "גרסה {0}", "TitleBundledUpdateVersionLabel": "Bundled: Version {0}", "TitleBundledDlcLabel": "Bundled:", + "TitleXCIStatusPartialLabel": "Partial", + "TitleXCIStatusTrimmableLabel": "Untrimmed", + "TitleXCIStatusUntrimmableLabel": "Trimmed", + "TitleXCIStatusFailedLabel": "(Failed)", + "TitleXCICanSaveLabel": "Save {0:n0} Mb", + "TitleXCISavingLabel": "Saved {0:n0} Mb", "RyujinxInfo": "ריוג'ינקס - מידע", "RyujinxConfirm": "ריוג'ינקס - אישור", "FileDialogAllTypes": "כל הסוגים", @@ -723,11 +736,37 @@ "SelectDlcDialogTitle": "בחרו קבצי הרחבות משחק", "SelectUpdateDialogTitle": "בחרו קבצי עדכון", "SelectModDialogTitle": "בחר תיקיית מודים", + "TrimXCIFileDialogTitle": "Check and Trim XCI File", + "TrimXCIFileDialogPrimaryText": "This function will first check the empty space and then trim the XCI File to save disk space.", + "TrimXCIFileDialogSecondaryText": "Current File Size: {0:n} MB\nGame Data Size: {1:n} MB\nDisk Space Savings: {2:n} MB", + "TrimXCIFileNoTrimNecessary": "XCI File does not need to be trimmed. Check logs for further details", + "TrimXCIFileNoUntrimPossible": "XCI File cannot be untrimmed. Check logs for further details", + "TrimXCIFileReadOnlyFileCannotFix": "XCI File is Read Only and could not be made writable. Check logs for further details", + "TrimXCIFileFileSizeChanged": "XCI File has changed in size since it was scanned. Please check the file is not being written to and try again.", + "TrimXCIFileFreeSpaceCheckFailed": "XCI File has data in the free space area, it is not safe to trim", + "TrimXCIFileInvalidXCIFile": "XCI File contains invalid data. Check logs for further details", + "TrimXCIFileFileIOWriteError": "XCI File could not be opened for writing. Check logs for further details", + "TrimXCIFileFailedPrimaryText": "Trimming of the XCI file failed", + "TrimXCIFileCancelled": "The operation was cancelled", + "TrimXCIFileFileUndertermined": "No operation was performed", "UserProfileWindowTitle": "ניהול פרופילי משתמש", "CheatWindowTitle": "נהל צ'יטים למשחק", "DlcWindowTitle": "נהל הרחבות משחק עבור {0} ({1})", "ModWindowTitle": "Manage Mods for {0} ({1})", "UpdateWindowTitle": "נהל עדכוני משחקים", + "XCITrimmerWindowTitle": "XCI File Trimmer", + "XCITrimmerTitleStatusCount": "{0} of {1} Title(s) Selected", + "XCITrimmerTitleStatusCountWithFilter": "{0} of {1} Title(s) Selected ({2} displayed)", + "XCITrimmerTitleStatusTrimming": "Trimming {0} Title(s)...", + "XCITrimmerTitleStatusUntrimming": "Untrimming {0} Title(s)...", + "XCITrimmerTitleStatusFailed": "Failed", + "XCITrimmerPotentialSavings": "Potential Savings", + "XCITrimmerActualSavings": "Actual Savings", + "XCITrimmerSavingsMb": "{0:n0} Mb", + "XCITrimmerSelectDisplayed": "Select Shown", + "XCITrimmerDeselectDisplayed": "Deselect Shown", + "XCITrimmerSortName": "Title", + "XCITrimmerSortSaved": "Space Savings", "XCITrimmerTrim": "Trim", "XCITrimmerUntrim": "Untrim", "UpdateWindowUpdateAddedMessage": "{0} new update(s) added", @@ -743,6 +782,7 @@ "AutoloadUpdateRemovedMessage": "{0} missing update(s) removed", "ModWindowHeading": "{0} מוד(ים)", "UserProfilesEditProfile": "ערוך נבחר/ים", + "Continue": "Continue", "Cancel": "בטל", "Save": "שמור", "Discard": "השלך", @@ -810,5 +850,17 @@ "MultiplayerMode": "מצב:", "MultiplayerModeTooltip": "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.", "MultiplayerModeDisabled": "Disabled", - "MultiplayerModeLdnMitm": "ldn_mitm" + "MultiplayerModeLdnMitm": "ldn_mitm", + "MultiplayerModeLdnRyu": "RyuLDN", + "MultiplayerDisableP2P": "Disable P2P Network Hosting (may increase latency)", + "MultiplayerDisableP2PTooltip": "Disable P2P network hosting, peers will proxy through the master server instead of connecting to you directly.", + "LdnPassphrase": "Network Passphrase:", + "LdnPassphraseTooltip": "You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputTooltip": "Enter a passphrase in the format Ryujinx-<8 hex chars>. You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputPublic": "(public)", + "GenLdnPass": "Generate Random", + "GenLdnPassTooltip": "Generates a new passphrase, which can be shared with other players.", + "ClearLdnPass": "Clear", + "ClearLdnPassTooltip": "Clears the current passphrase, returning to the public network.", + "InvalidLdnPassphrase": "Invalid Passphrase! Must be in the format \"Ryujinx-<8 hex chars>\"" } diff --git a/src/Ryujinx/Assets/Locales/it_IT.json b/src/Ryujinx/Assets/Locales/it_IT.json index 2a92e70dc7..f10dd9d352 100644 --- a/src/Ryujinx/Assets/Locales/it_IT.json +++ b/src/Ryujinx/Assets/Locales/it_IT.json @@ -850,5 +850,17 @@ "MultiplayerMode": "Modalità:", "MultiplayerModeTooltip": "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.", "MultiplayerModeDisabled": "Disabilitato", - "MultiplayerModeLdnMitm": "ldn_mitm" + "MultiplayerModeLdnMitm": "ldn_mitm", + "MultiplayerModeLdnRyu": "RyuLDN", + "MultiplayerDisableP2P": "Disable P2P Network Hosting (may increase latency)", + "MultiplayerDisableP2PTooltip": "Disable P2P network hosting, peers will proxy through the master server instead of connecting to you directly.", + "LdnPassphrase": "Network Passphrase:", + "LdnPassphraseTooltip": "You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputTooltip": "Enter a passphrase in the format Ryujinx-<8 hex chars>. You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputPublic": "(public)", + "GenLdnPass": "Generate Random", + "GenLdnPassTooltip": "Generates a new passphrase, which can be shared with other players.", + "ClearLdnPass": "Clear", + "ClearLdnPassTooltip": "Clears the current passphrase, returning to the public network.", + "InvalidLdnPassphrase": "Invalid Passphrase! Must be in the format \"Ryujinx-<8 hex chars>\"" } diff --git a/src/Ryujinx/Assets/Locales/ja_JP.json b/src/Ryujinx/Assets/Locales/ja_JP.json index b8e5870a64..34253acbf8 100644 --- a/src/Ryujinx/Assets/Locales/ja_JP.json +++ b/src/Ryujinx/Assets/Locales/ja_JP.json @@ -33,6 +33,7 @@ "MenuBarToolsManageFileTypes": "ファイル形式を管理", "MenuBarToolsInstallFileTypes": "ファイル形式をインストール", "MenuBarToolsUninstallFileTypes": "ファイル形式をアンインストール", + "MenuBarToolsXCITrimmer": "Trim XCI Files", "MenuBarView": "_View", "MenuBarViewWindow": "Window Size", "MenuBarViewWindow720": "720p", @@ -84,8 +85,11 @@ "GameListContextMenuOpenModsDirectoryToolTip": "アプリケーションの Mod データを格納するディレクトリを開きます", "GameListContextMenuOpenSdModsDirectory": "Atmosphere Mods ディレクトリを開く", "GameListContextMenuOpenSdModsDirectoryToolTip": "アプリケーションの Mod データを格納する SD カードの Atmosphere ディレクトリを開きます. 実際のハードウェア用に作成された Mod データに有用です.", + "GameListContextMenuTrimXCI": "Check and Trim XCI File", + "GameListContextMenuTrimXCIToolTip": "Check and Trim XCI File to Save Disk Space", "StatusBarGamesLoaded": "{0}/{1} ゲーム", "StatusBarSystemVersion": "システムバージョン: {0}", + "StatusBarXCIFileTrimming": "Trimming XCI File '{0}'", "LinuxVmMaxMapCountDialogTitle": "メモリマッピング上限値が小さすぎます", "LinuxVmMaxMapCountDialogTextPrimary": "vm.max_map_count の値を {0}に増やしますか?", "LinuxVmMaxMapCountDialogTextSecondary": "ゲームによっては, 現在許可されているサイズより大きなメモリマッピングを作成しようとすることがあります. この制限を超えると, Ryjinx はすぐにクラッシュします.", @@ -400,6 +404,8 @@ "InputDialogTitle": "入力ダイアログ", "InputDialogOk": "OK", "InputDialogCancel": "キャンセル", + "InputDialogCancelling": "Cancelling", + "InputDialogClose": "Close", "InputDialogAddNewProfileTitle": "プロファイル名を選択", "InputDialogAddNewProfileHeader": "プロファイル名を入力してください", "InputDialogAddNewProfileSubtext": "(最大長: {0})", @@ -469,6 +475,7 @@ "DialogUninstallFileTypesSuccessMessage": "ファイル形式のアンインストールに成功しました!", "DialogUninstallFileTypesErrorMessage": "ファイル形式のアンインストールに失敗しました.", "DialogOpenSettingsWindowLabel": "設定ウインドウを開く", + "DialogOpenXCITrimmerWindowLabel": "XCI Trimmer Window", "DialogControllerAppletTitle": "コントローラアプレット", "DialogMessageDialogErrorExceptionMessage": "メッセージダイアログ表示エラー: {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "ソフトウェアキーボード表示エラー: {0}", @@ -671,6 +678,12 @@ "TitleUpdateVersionLabel": "バージョン {0} - {1}", "TitleBundledUpdateVersionLabel": "Bundled: Version {0}", "TitleBundledDlcLabel": "Bundled:", + "TitleXCIStatusPartialLabel": "Partial", + "TitleXCIStatusTrimmableLabel": "Untrimmed", + "TitleXCIStatusUntrimmableLabel": "Trimmed", + "TitleXCIStatusFailedLabel": "(Failed)", + "TitleXCICanSaveLabel": "Save {0:n0} Mb", + "TitleXCISavingLabel": "Saved {0:n0} Mb", "RyujinxInfo": "Ryujinx - 情報", "RyujinxConfirm": "Ryujinx - 確認", "FileDialogAllTypes": "すべての種別", @@ -723,11 +736,37 @@ "SelectDlcDialogTitle": "DLC ファイルを選択", "SelectUpdateDialogTitle": "アップデートファイルを選択", "SelectModDialogTitle": "modディレクトリを選択", + "TrimXCIFileDialogTitle": "Check and Trim XCI File", + "TrimXCIFileDialogPrimaryText": "This function will first check the empty space and then trim the XCI File to save disk space.", + "TrimXCIFileDialogSecondaryText": "Current File Size: {0:n} MB\nGame Data Size: {1:n} MB\nDisk Space Savings: {2:n} MB", + "TrimXCIFileNoTrimNecessary": "XCI File does not need to be trimmed. Check logs for further details", + "TrimXCIFileNoUntrimPossible": "XCI File cannot be untrimmed. Check logs for further details", + "TrimXCIFileReadOnlyFileCannotFix": "XCI File is Read Only and could not be made writable. Check logs for further details", + "TrimXCIFileFileSizeChanged": "XCI File has changed in size since it was scanned. Please check the file is not being written to and try again.", + "TrimXCIFileFreeSpaceCheckFailed": "XCI File has data in the free space area, it is not safe to trim", + "TrimXCIFileInvalidXCIFile": "XCI File contains invalid data. Check logs for further details", + "TrimXCIFileFileIOWriteError": "XCI File could not be opened for writing. Check logs for further details", + "TrimXCIFileFailedPrimaryText": "Trimming of the XCI file failed", + "TrimXCIFileCancelled": "The operation was cancelled", + "TrimXCIFileFileUndertermined": "No operation was performed", "UserProfileWindowTitle": "ユーザプロファイルを管理", "CheatWindowTitle": "チート管理", "DlcWindowTitle": "DLC 管理", "ModWindowTitle": "Manage Mods for {0} ({1})", "UpdateWindowTitle": "アップデート管理", + "XCITrimmerWindowTitle": "XCI File Trimmer", + "XCITrimmerTitleStatusCount": "{0} of {1} Title(s) Selected", + "XCITrimmerTitleStatusCountWithFilter": "{0} of {1} Title(s) Selected ({2} displayed)", + "XCITrimmerTitleStatusTrimming": "Trimming {0} Title(s)...", + "XCITrimmerTitleStatusUntrimming": "Untrimming {0} Title(s)...", + "XCITrimmerTitleStatusFailed": "Failed", + "XCITrimmerPotentialSavings": "Potential Savings", + "XCITrimmerActualSavings": "Actual Savings", + "XCITrimmerSavingsMb": "{0:n0} Mb", + "XCITrimmerSelectDisplayed": "Select Shown", + "XCITrimmerDeselectDisplayed": "Deselect Shown", + "XCITrimmerSortName": "Title", + "XCITrimmerSortSaved": "Space Savings", "XCITrimmerTrim": "Trim", "XCITrimmerUntrim": "Untrim", "UpdateWindowUpdateAddedMessage": "{0} new update(s) added", @@ -742,6 +781,7 @@ "AutoloadUpdateRemovedMessage": "{0} missing update(s) removed", "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "編集", + "Continue": "Continue", "Cancel": "キャンセル", "Save": "セーブ", "Discard": "破棄", @@ -809,5 +849,17 @@ "MultiplayerMode": "モード:", "MultiplayerModeTooltip": "LDNマルチプレイヤーモードを変更します.\n\nldn_mitmモジュールがインストールされた, 他のRyujinxインスタンスや,ハックされたNintendo Switchコンソールとのローカル/同一ネットワーク接続を可能にします.\n\nマルチプレイでは, すべてのプレイヤーが同じゲームバージョンである必要があります(例:Super Smash Bros. Ultimate v13.0.1はv13.0.0に接続できません).\n\n不明な場合は「無効」のままにしてください.", "MultiplayerModeDisabled": "無効", - "MultiplayerModeLdnMitm": "ldn_mitm" + "MultiplayerModeLdnMitm": "ldn_mitm", + "MultiplayerModeLdnRyu": "RyuLDN", + "MultiplayerDisableP2P": "Disable P2P Network Hosting (may increase latency)", + "MultiplayerDisableP2PTooltip": "Disable P2P network hosting, peers will proxy through the master server instead of connecting to you directly.", + "LdnPassphrase": "Network Passphrase:", + "LdnPassphraseTooltip": "You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputTooltip": "Enter a passphrase in the format Ryujinx-<8 hex chars>. You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputPublic": "(public)", + "GenLdnPass": "Generate Random", + "GenLdnPassTooltip": "Generates a new passphrase, which can be shared with other players.", + "ClearLdnPass": "Clear", + "ClearLdnPassTooltip": "Clears the current passphrase, returning to the public network.", + "InvalidLdnPassphrase": "Invalid Passphrase! Must be in the format \"Ryujinx-<8 hex chars>\"" } diff --git a/src/Ryujinx/Assets/Locales/pl_PL.json b/src/Ryujinx/Assets/Locales/pl_PL.json index fa88bab5e3..015530833a 100644 --- a/src/Ryujinx/Assets/Locales/pl_PL.json +++ b/src/Ryujinx/Assets/Locales/pl_PL.json @@ -33,6 +33,7 @@ "MenuBarToolsManageFileTypes": "Zarządzaj rodzajami plików", "MenuBarToolsInstallFileTypes": "Typy plików instalacyjnych", "MenuBarToolsUninstallFileTypes": "Typy plików dezinstalacyjnych", + "MenuBarToolsXCITrimmer": "Trim XCI Files", "MenuBarView": "_View", "MenuBarViewWindow": "Window Size", "MenuBarViewWindow720": "720p", @@ -84,8 +85,11 @@ "GameListContextMenuOpenModsDirectoryToolTip": "Otwiera katalog zawierający mody dla danej aplikacji", "GameListContextMenuOpenSdModsDirectory": "Otwórz katalog modów Atmosphere", "GameListContextMenuOpenSdModsDirectoryToolTip": "Otwiera alternatywny katalog Atmosphere na karcie SD, który zawiera mody danej aplikacji. Przydatne dla modów przygotowanych pod prawdziwy sprzęt.", + "GameListContextMenuTrimXCI": "Check and Trim XCI File", + "GameListContextMenuTrimXCIToolTip": "Check and Trim XCI File to Save Disk Space", "StatusBarGamesLoaded": "{0}/{1} Załadowane gry", "StatusBarSystemVersion": "Wersja systemu: {0}", + "StatusBarXCIFileTrimming": "Trimming XCI File '{0}'", "LinuxVmMaxMapCountDialogTitle": "Wykryto niski limit dla przypisań pamięci", "LinuxVmMaxMapCountDialogTextPrimary": "Czy chcesz zwiększyć wartość vm.max_map_count do {0}", "LinuxVmMaxMapCountDialogTextSecondary": "Niektóre gry mogą próbować przypisać sobie więcej pamięci niż obecnie, jest to dozwolone. Ryujinx ulegnie awarii, gdy limit zostanie przekroczony.", @@ -400,6 +404,8 @@ "InputDialogTitle": "Okno Dialogowe Wprowadzania", "InputDialogOk": "OK", "InputDialogCancel": "Anuluj", + "InputDialogCancelling": "Cancelling", + "InputDialogClose": "Close", "InputDialogAddNewProfileTitle": "Wybierz nazwę profilu", "InputDialogAddNewProfileHeader": "Wprowadź nazwę profilu", "InputDialogAddNewProfileSubtext": "(Maksymalna długość: {0})", @@ -469,6 +475,7 @@ "DialogUninstallFileTypesSuccessMessage": "Pomyślnie odinstalowano typy plików!", "DialogUninstallFileTypesErrorMessage": "Nie udało się odinstalować typów plików.", "DialogOpenSettingsWindowLabel": "Otwórz Okno Ustawień", + "DialogOpenXCITrimmerWindowLabel": "XCI Trimmer Window", "DialogControllerAppletTitle": "Aplet Kontrolera", "DialogMessageDialogErrorExceptionMessage": "Błąd wyświetlania okna Dialogowego Wiadomości: {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "Błąd wyświetlania Klawiatury Oprogramowania: {0}", @@ -671,6 +678,12 @@ "TitleUpdateVersionLabel": "Wersja {0} - {1}", "TitleBundledUpdateVersionLabel": "Bundled: Version {0}", "TitleBundledDlcLabel": "Bundled:", + "TitleXCIStatusPartialLabel": "Partial", + "TitleXCIStatusTrimmableLabel": "Untrimmed", + "TitleXCIStatusUntrimmableLabel": "Trimmed", + "TitleXCIStatusFailedLabel": "(Failed)", + "TitleXCICanSaveLabel": "Save {0:n0} Mb", + "TitleXCISavingLabel": "Saved {0:n0} Mb", "RyujinxInfo": "Ryujinx - Info", "RyujinxConfirm": "Ryujinx - Potwierdzenie", "FileDialogAllTypes": "Wszystkie typy", @@ -723,11 +736,37 @@ "SelectDlcDialogTitle": "Wybierz pliki DLC", "SelectUpdateDialogTitle": "Wybierz pliki aktualizacji", "SelectModDialogTitle": "Wybierz katalog modów", + "TrimXCIFileDialogTitle": "Check and Trim XCI File", + "TrimXCIFileDialogPrimaryText": "This function will first check the empty space and then trim the XCI File to save disk space.", + "TrimXCIFileDialogSecondaryText": "Current File Size: {0:n} MB\nGame Data Size: {1:n} MB\nDisk Space Savings: {2:n} MB", + "TrimXCIFileNoTrimNecessary": "XCI File does not need to be trimmed. Check logs for further details", + "TrimXCIFileNoUntrimPossible": "XCI File cannot be untrimmed. Check logs for further details", + "TrimXCIFileReadOnlyFileCannotFix": "XCI File is Read Only and could not be made writable. Check logs for further details", + "TrimXCIFileFileSizeChanged": "XCI File has changed in size since it was scanned. Please check the file is not being written to and try again.", + "TrimXCIFileFreeSpaceCheckFailed": "XCI File has data in the free space area, it is not safe to trim", + "TrimXCIFileInvalidXCIFile": "XCI File contains invalid data. Check logs for further details", + "TrimXCIFileFileIOWriteError": "XCI File could not be opened for writing. Check logs for further details", + "TrimXCIFileFailedPrimaryText": "Trimming of the XCI file failed", + "TrimXCIFileCancelled": "The operation was cancelled", + "TrimXCIFileFileUndertermined": "No operation was performed", "UserProfileWindowTitle": "Menedżer Profili Użytkowników", "CheatWindowTitle": "Menedżer Kodów", "DlcWindowTitle": "Menedżer Zawartości do Pobrania", "ModWindowTitle": "Zarządzaj modami dla {0} ({1})", "UpdateWindowTitle": "Menedżer Aktualizacji Tytułu", + "XCITrimmerWindowTitle": "XCI File Trimmer", + "XCITrimmerTitleStatusCount": "{0} of {1} Title(s) Selected", + "XCITrimmerTitleStatusCountWithFilter": "{0} of {1} Title(s) Selected ({2} displayed)", + "XCITrimmerTitleStatusTrimming": "Trimming {0} Title(s)...", + "XCITrimmerTitleStatusUntrimming": "Untrimming {0} Title(s)...", + "XCITrimmerTitleStatusFailed": "Failed", + "XCITrimmerPotentialSavings": "Potential Savings", + "XCITrimmerActualSavings": "Actual Savings", + "XCITrimmerSavingsMb": "{0:n0} Mb", + "XCITrimmerSelectDisplayed": "Select Shown", + "XCITrimmerDeselectDisplayed": "Deselect Shown", + "XCITrimmerSortName": "Title", + "XCITrimmerSortSaved": "Space Savings", "XCITrimmerTrim": "Trim", "XCITrimmerUntrim": "Untrim", "UpdateWindowUpdateAddedMessage": "{0} new update(s) added", @@ -743,6 +782,7 @@ "AutoloadUpdateRemovedMessage": "{0} missing update(s) removed", "ModWindowHeading": "{0} Mod(y/ów)", "UserProfilesEditProfile": "Edytuj Zaznaczone", + "Continue": "Continue", "Cancel": "Anuluj", "Save": "Zapisz", "Discard": "Odrzuć", @@ -810,5 +850,17 @@ "MultiplayerMode": "Tryb:", "MultiplayerModeTooltip": "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.", "MultiplayerModeDisabled": "Wyłączone", - "MultiplayerModeLdnMitm": "ldn_mitm" + "MultiplayerModeLdnMitm": "ldn_mitm", + "MultiplayerModeLdnRyu": "RyuLDN", + "MultiplayerDisableP2P": "Disable P2P Network Hosting (may increase latency)", + "MultiplayerDisableP2PTooltip": "Disable P2P network hosting, peers will proxy through the master server instead of connecting to you directly.", + "LdnPassphrase": "Network Passphrase:", + "LdnPassphraseTooltip": "You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputTooltip": "Enter a passphrase in the format Ryujinx-<8 hex chars>. You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputPublic": "(public)", + "GenLdnPass": "Generate Random", + "GenLdnPassTooltip": "Generates a new passphrase, which can be shared with other players.", + "ClearLdnPass": "Clear", + "ClearLdnPassTooltip": "Clears the current passphrase, returning to the public network.", + "InvalidLdnPassphrase": "Invalid Passphrase! Must be in the format \"Ryujinx-<8 hex chars>\"" } diff --git a/src/Ryujinx/Assets/Locales/pt_BR.json b/src/Ryujinx/Assets/Locales/pt_BR.json index 5b7a214945..512581c0e8 100644 --- a/src/Ryujinx/Assets/Locales/pt_BR.json +++ b/src/Ryujinx/Assets/Locales/pt_BR.json @@ -33,6 +33,7 @@ "MenuBarToolsManageFileTypes": "Gerenciar tipos de arquivo", "MenuBarToolsInstallFileTypes": "Instalar tipos de arquivo", "MenuBarToolsUninstallFileTypes": "Desinstalar tipos de arquivos", + "MenuBarToolsXCITrimmer": "Trim XCI Files", "MenuBarView": "_View", "MenuBarViewWindow": "Window Size", "MenuBarViewWindow720": "720p", @@ -84,8 +85,11 @@ "GameListContextMenuOpenModsDirectoryToolTip": "Abre a pasta que contém os mods da aplicação ", "GameListContextMenuOpenSdModsDirectory": "Abrir diretório de mods Atmosphere", "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", + "GameListContextMenuTrimXCI": "Check and Trim XCI File", + "GameListContextMenuTrimXCIToolTip": "Check and Trim XCI File to Save Disk Space", "StatusBarGamesLoaded": "{0}/{1} jogos carregados", "StatusBarSystemVersion": "Versão do firmware: {0}", + "StatusBarXCIFileTrimming": "Trimming XCI File '{0}'", "LinuxVmMaxMapCountDialogTitle": "Limite baixo para mapeamentos de memória detectado", "LinuxVmMaxMapCountDialogTextPrimary": "Você gostaria de aumentar o valor de vm.max_map_count para {0}", "LinuxVmMaxMapCountDialogTextSecondary": "Alguns jogos podem tentar criar mais mapeamentos de memória do que o atualmente permitido. Ryujinx irá falhar assim que este limite for excedido.", @@ -400,6 +404,8 @@ "InputDialogTitle": "Diálogo de texto", "InputDialogOk": "OK", "InputDialogCancel": "Cancelar", + "InputDialogCancelling": "Cancelling", + "InputDialogClose": "Close", "InputDialogAddNewProfileTitle": "Escolha o nome de perfil", "InputDialogAddNewProfileHeader": "Escreva o nome do perfil", "InputDialogAddNewProfileSubtext": "(Máximo de caracteres: {0})", @@ -469,6 +475,7 @@ "DialogUninstallFileTypesSuccessMessage": "Tipos de arquivo desinstalados com sucesso!", "DialogUninstallFileTypesErrorMessage": "Falha ao desinstalar tipos de arquivo.", "DialogOpenSettingsWindowLabel": "Abrir janela de configurações", + "DialogOpenXCITrimmerWindowLabel": "XCI Trimmer Window", "DialogControllerAppletTitle": "Applet de controle", "DialogMessageDialogErrorExceptionMessage": "Erro ao exibir diálogo de mensagem: {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "Erro ao exibir teclado virtual: {0}", @@ -618,7 +625,6 @@ "LoadApplicationFolderTooltip": "Abre o navegador de pastas para seleção de pasta extraída do Switch compatível a ser carregada", "OpenRyujinxFolderTooltip": "Abre o diretório do sistema de arquivos do Ryujinx", "LoadTitleUpdatesFromFolderTooltip": "Abra o explorador de arquivos para selecionar uma ou mais pastas e carregar atualizações de jogo em massa.", - "OpenRyujinxFolderTooltip": "Abrir diretório do sistema de arquivos do Ryujinx", "OpenRyujinxLogsTooltip": "Abre o diretório onde os logs são salvos", "ExitTooltip": "Sair do Ryujinx", "OpenSettingsTooltip": "Abrir janela de configurações", @@ -671,6 +677,12 @@ "TitleUpdateVersionLabel": "Versão {0}", "TitleBundledUpdateVersionLabel": "Empacotado: Versão {0}", "TitleBundledDlcLabel": "Empacotado:", + "TitleXCIStatusPartialLabel": "Partial", + "TitleXCIStatusTrimmableLabel": "Untrimmed", + "TitleXCIStatusUntrimmableLabel": "Trimmed", + "TitleXCIStatusFailedLabel": "(Failed)", + "TitleXCICanSaveLabel": "Save {0:n0} Mb", + "TitleXCISavingLabel": "Saved {0:n0} Mb", "RyujinxInfo": "Ryujinx - Informação", "RyujinxConfirm": "Ryujinx - Confirmação", "FileDialogAllTypes": "Todos os tipos", @@ -724,10 +736,36 @@ "SelectUpdateDialogTitle": "Selecionar arquivos de atualização", "SelectModDialogTitle": "Select mod directory", "UserProfileWindowTitle": "Gerenciador de perfis de usuário", + "TrimXCIFileDialogTitle": "Check and Trim XCI File", + "TrimXCIFileDialogPrimaryText": "This function will first check the empty space and then trim the XCI File to save disk space.", + "TrimXCIFileDialogSecondaryText": "Current File Size: {0:n} MB\nGame Data Size: {1:n} MB\nDisk Space Savings: {2:n} MB", + "TrimXCIFileNoTrimNecessary": "XCI File does not need to be trimmed. Check logs for further details", + "TrimXCIFileNoUntrimPossible": "XCI File cannot be untrimmed. Check logs for further details", + "TrimXCIFileReadOnlyFileCannotFix": "XCI File is Read Only and could not be made writable. Check logs for further details", + "TrimXCIFileFileSizeChanged": "XCI File has changed in size since it was scanned. Please check the file is not being written to and try again.", + "TrimXCIFileFreeSpaceCheckFailed": "XCI File has data in the free space area, it is not safe to trim", + "TrimXCIFileInvalidXCIFile": "XCI File contains invalid data. Check logs for further details", + "TrimXCIFileFileIOWriteError": "XCI File could not be opened for writing. Check logs for further details", + "TrimXCIFileFailedPrimaryText": "Trimming of the XCI file failed", + "TrimXCIFileCancelled": "The operation was cancelled", + "TrimXCIFileFileUndertermined": "No operation was performed", "CheatWindowTitle": "Gerenciador de Cheats", "DlcWindowTitle": "Gerenciador de DLC", "ModWindowTitle": "Gerenciar Mods para {0} ({1})", "UpdateWindowTitle": "Gerenciador de atualizações", + "XCITrimmerWindowTitle": "XCI File Trimmer", + "XCITrimmerTitleStatusCount": "{0} of {1} Title(s) Selected", + "XCITrimmerTitleStatusCountWithFilter": "{0} of {1} Title(s) Selected ({2} displayed)", + "XCITrimmerTitleStatusTrimming": "Trimming {0} Title(s)...", + "XCITrimmerTitleStatusUntrimming": "Untrimming {0} Title(s)...", + "XCITrimmerTitleStatusFailed": "Failed", + "XCITrimmerPotentialSavings": "Potential Savings", + "XCITrimmerActualSavings": "Actual Savings", + "XCITrimmerSavingsMb": "{0:n0} Mb", + "XCITrimmerSelectDisplayed": "Select Shown", + "XCITrimmerDeselectDisplayed": "Deselect Shown", + "XCITrimmerSortName": "Title", + "XCITrimmerSortSaved": "Space Savings", "XCITrimmerTrim": "Trim", "XCITrimmerUntrim": "Untrim", "UpdateWindowUpdateAddedMessage": "{0} nova(s) atualização(ões) adicionada(s)", @@ -743,6 +781,7 @@ "AutoloadUpdateRemovedMessage": "{0} atualização(ões) ausente(s) removida(s)", "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "Editar selecionado", + "Continue": "Continue", "Cancel": "Cancelar", "Save": "Salvar", "Discard": "Descartar", @@ -810,5 +849,17 @@ "MultiplayerMode": "Modo:", "MultiplayerModeTooltip": "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.", "MultiplayerModeDisabled": "Desativado", - "MultiplayerModeLdnMitm": "ldn_mitm" + "MultiplayerModeLdnMitm": "ldn_mitm", + "MultiplayerModeLdnRyu": "RyuLDN", + "MultiplayerDisableP2P": "Disable P2P Network Hosting (may increase latency)", + "MultiplayerDisableP2PTooltip": "Disable P2P network hosting, peers will proxy through the master server instead of connecting to you directly.", + "LdnPassphrase": "Network Passphrase:", + "LdnPassphraseTooltip": "You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputTooltip": "Enter a passphrase in the format Ryujinx-<8 hex chars>. You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputPublic": "(public)", + "GenLdnPass": "Generate Random", + "GenLdnPassTooltip": "Generates a new passphrase, which can be shared with other players.", + "ClearLdnPass": "Clear", + "ClearLdnPassTooltip": "Clears the current passphrase, returning to the public network.", + "InvalidLdnPassphrase": "Invalid Passphrase! Must be in the format \"Ryujinx-<8 hex chars>\"" } diff --git a/src/Ryujinx/Assets/Locales/ru_RU.json b/src/Ryujinx/Assets/Locales/ru_RU.json index cd17eb3015..9d81116efd 100644 --- a/src/Ryujinx/Assets/Locales/ru_RU.json +++ b/src/Ryujinx/Assets/Locales/ru_RU.json @@ -33,6 +33,7 @@ "MenuBarToolsManageFileTypes": "Управление типами файлов", "MenuBarToolsInstallFileTypes": "Установить типы файлов", "MenuBarToolsUninstallFileTypes": "Удалить типы файлов", + "MenuBarToolsXCITrimmer": "Trim XCI Files", "MenuBarView": "_Вид", "MenuBarViewWindow": "Размер окна", "MenuBarViewWindow720": "720p", @@ -84,8 +85,11 @@ "GameListContextMenuOpenModsDirectoryToolTip": "Открывает папку, содержащую моды для приложений и игр", "GameListContextMenuOpenSdModsDirectory": "Открыть папку с модами Atmosphere", "GameListContextMenuOpenSdModsDirectoryToolTip": "Открывает папку Atmosphere на альтернативной SD-карте, которая содержит моды для приложений и игр. Полезно для модов, сделанных для реальной консоли.", + "GameListContextMenuTrimXCI": "Check and Trim XCI File", + "GameListContextMenuTrimXCIToolTip": "Check and Trim XCI File to Save Disk Space", "StatusBarGamesLoaded": "{0}/{1} игр загружено", "StatusBarSystemVersion": "Версия прошивки: {0}", + "StatusBarXCIFileTrimming": "Trimming XCI File '{0}'", "LinuxVmMaxMapCountDialogTitle": "Обнаружен низкий лимит разметки памяти", "LinuxVmMaxMapCountDialogTextPrimary": "Хотите увеличить значение vm.max_map_count до {0}", "LinuxVmMaxMapCountDialogTextSecondary": "Некоторые игры могут создавать большую разметку памяти, чем разрешено на данный момент по умолчанию. Ryujinx вылетит при превышении этого лимита.", @@ -400,6 +404,8 @@ "InputDialogTitle": "Диалоговое окно ввода", "InputDialogOk": "ОК", "InputDialogCancel": "Отмена", + "InputDialogCancelling": "Cancelling", + "InputDialogClose": "Close", "InputDialogAddNewProfileTitle": "Выберите никнейм", "InputDialogAddNewProfileHeader": "Пожалуйста, введите никнейм", "InputDialogAddNewProfileSubtext": "(Максимальная длина: {0})", @@ -469,6 +475,7 @@ "DialogUninstallFileTypesSuccessMessage": "Типы файлов успешно удалены", "DialogUninstallFileTypesErrorMessage": "Не удалось удалить типы файлов.", "DialogOpenSettingsWindowLabel": "Открывает окно параметров", + "DialogOpenXCITrimmerWindowLabel": "XCI Trimmer Window", "DialogControllerAppletTitle": "Апплет контроллера", "DialogMessageDialogErrorExceptionMessage": "Ошибка отображения сообщения: {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "Ошибка отображения программной клавиатуры: {0}", @@ -671,6 +678,12 @@ "TitleUpdateVersionLabel": "Version {0} - {1}", "TitleBundledUpdateVersionLabel": "Bundled: Version {0}", "TitleBundledDlcLabel": "Bundled:", + "TitleXCIStatusPartialLabel": "Partial", + "TitleXCIStatusTrimmableLabel": "Untrimmed", + "TitleXCIStatusUntrimmableLabel": "Trimmed", + "TitleXCIStatusFailedLabel": "(Failed)", + "TitleXCICanSaveLabel": "Save {0:n0} Mb", + "TitleXCISavingLabel": "Saved {0:n0} Mb", "RyujinxInfo": "Ryujinx - Информация", "RyujinxConfirm": "Ryujinx - Подтверждение", "FileDialogAllTypes": "Все типы", @@ -723,11 +736,37 @@ "SelectDlcDialogTitle": "Выберите файлы DLC", "SelectUpdateDialogTitle": "Выберите файлы обновлений", "SelectModDialogTitle": "Выбрать папку с модами", + "TrimXCIFileDialogTitle": "Check and Trim XCI File", + "TrimXCIFileDialogPrimaryText": "This function will first check the empty space and then trim the XCI File to save disk space.", + "TrimXCIFileDialogSecondaryText": "Current File Size: {0:n} MB\nGame Data Size: {1:n} MB\nDisk Space Savings: {2:n} MB", + "TrimXCIFileNoTrimNecessary": "XCI File does not need to be trimmed. Check logs for further details", + "TrimXCIFileNoUntrimPossible": "XCI File cannot be untrimmed. Check logs for further details", + "TrimXCIFileReadOnlyFileCannotFix": "XCI File is Read Only and could not be made writable. Check logs for further details", + "TrimXCIFileFileSizeChanged": "XCI File has changed in size since it was scanned. Please check the file is not being written to and try again.", + "TrimXCIFileFreeSpaceCheckFailed": "XCI File has data in the free space area, it is not safe to trim", + "TrimXCIFileInvalidXCIFile": "XCI File contains invalid data. Check logs for further details", + "TrimXCIFileFileIOWriteError": "XCI File could not be opened for writing. Check logs for further details", + "TrimXCIFileFailedPrimaryText": "Trimming of the XCI file failed", + "TrimXCIFileCancelled": "The operation was cancelled", + "TrimXCIFileFileUndertermined": "No operation was performed", "UserProfileWindowTitle": "Менеджер учетных записей", "CheatWindowTitle": "Менеджер читов", "DlcWindowTitle": "Управление DLC для {0} ({1})", "ModWindowTitle": "Управление модами для {0} ({1})", "UpdateWindowTitle": "Менеджер обновлений игр", + "XCITrimmerWindowTitle": "XCI File Trimmer", + "XCITrimmerTitleStatusCount": "{0} of {1} Title(s) Selected", + "XCITrimmerTitleStatusCountWithFilter": "{0} of {1} Title(s) Selected ({2} displayed)", + "XCITrimmerTitleStatusTrimming": "Trimming {0} Title(s)...", + "XCITrimmerTitleStatusUntrimming": "Untrimming {0} Title(s)...", + "XCITrimmerTitleStatusFailed": "Failed", + "XCITrimmerPotentialSavings": "Potential Savings", + "XCITrimmerActualSavings": "Actual Savings", + "XCITrimmerSavingsMb": "{0:n0} Mb", + "XCITrimmerSelectDisplayed": "Select Shown", + "XCITrimmerDeselectDisplayed": "Deselect Shown", + "XCITrimmerSortName": "Title", + "XCITrimmerSortSaved": "Space Savings", "XCITrimmerTrim": "Trim", "XCITrimmerUntrim": "Untrim", "UpdateWindowUpdateAddedMessage": "{0} new update(s) added", @@ -743,6 +782,7 @@ "AutoloadUpdateRemovedMessage": "{0} missing update(s) removed", "ModWindowHeading": "Моды для {0} ", "UserProfilesEditProfile": "Изменить выбранные", + "Continue": "Continue", "Cancel": "Отмена", "Save": "Сохранить", "Discard": "Отменить", @@ -810,5 +850,17 @@ "MultiplayerMode": "Режим:", "MultiplayerModeTooltip": "Меняет многопользовательский режим LDN.\n\nLdnMitm модифицирует функциональность локальной беспроводной/игры на одном устройстве в играх, позволяя играть с другими пользователями Ryujinx или взломанными консолями Nintendo Switch с установленным модулем ldn_mitm, находящимися в одной локальной сети друг с другом.\n\nМногопользовательская игра требует наличия у всех игроков одной и той же версии игры (т.е. Super Smash Bros. Ultimate v13.0.1 не может подключиться к v13.0.0).\n\nРекомендуется оставить отключенным.", "MultiplayerModeDisabled": "Отключено", - "MultiplayerModeLdnMitm": "ldn_mitm" + "MultiplayerModeLdnMitm": "ldn_mitm", + "MultiplayerModeLdnRyu": "RyuLDN", + "MultiplayerDisableP2P": "Disable P2P Network Hosting (may increase latency)", + "MultiplayerDisableP2PTooltip": "Disable P2P network hosting, peers will proxy through the master server instead of connecting to you directly.", + "LdnPassphrase": "Network Passphrase:", + "LdnPassphraseTooltip": "You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputTooltip": "Enter a passphrase in the format Ryujinx-<8 hex chars>. You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputPublic": "(public)", + "GenLdnPass": "Generate Random", + "GenLdnPassTooltip": "Generates a new passphrase, which can be shared with other players.", + "ClearLdnPass": "Clear", + "ClearLdnPassTooltip": "Clears the current passphrase, returning to the public network.", + "InvalidLdnPassphrase": "Invalid Passphrase! Must be in the format \"Ryujinx-<8 hex chars>\"" } diff --git a/src/Ryujinx/Assets/Locales/th_TH.json b/src/Ryujinx/Assets/Locales/th_TH.json index d32cfb737b..fa59ba682a 100644 --- a/src/Ryujinx/Assets/Locales/th_TH.json +++ b/src/Ryujinx/Assets/Locales/th_TH.json @@ -33,6 +33,7 @@ "MenuBarToolsManageFileTypes": "จัดการประเภทไฟล์", "MenuBarToolsInstallFileTypes": "ติดตั้งประเภทไฟล์", "MenuBarToolsUninstallFileTypes": "ถอนการติดตั้งประเภทไฟล์", + "MenuBarToolsXCITrimmer": "Trim XCI Files", "MenuBarView": "_มุมมอง", "MenuBarViewWindow": "ขนาดหน้าต่าง", "MenuBarViewWindow720": "720p", @@ -84,8 +85,11 @@ "GameListContextMenuOpenModsDirectoryToolTip": "เปิดไดเร็กทอรี่ Mods ของแอปพลิเคชัน", "GameListContextMenuOpenSdModsDirectory": "เปิดไดเร็กทอรี่ Mods Atmosphere", "GameListContextMenuOpenSdModsDirectoryToolTip": "เปิดไดเร็กทอรี่ Atmosphere ของการ์ด SD สำรองซึ่งมี Mods ของแอปพลิเคชัน ซึ่งมีประโยชน์สำหรับ Mods ที่บรรจุมากับฮาร์ดแวร์จริง", + "GameListContextMenuTrimXCI": "Check and Trim XCI File", + "GameListContextMenuTrimXCIToolTip": "Check and Trim XCI File to Save Disk Space", "StatusBarGamesLoaded": "เกมส์โหลดแล้ว {0}/{1}", "StatusBarSystemVersion": "เวอร์ชั่นของระบบ: {0}", + "StatusBarXCIFileTrimming": "Trimming XCI File '{0}'", "LinuxVmMaxMapCountDialogTitle": "การตั้งค่าหน่วยความถึงขีดจำกัดต่ำสุดแล้ว", "LinuxVmMaxMapCountDialogTextPrimary": "คุณต้องเพิ่มค่า vm.max_map_count ไปยัง {0}", "LinuxVmMaxMapCountDialogTextSecondary": "บางเกมอาจพยายามใช้งานหน่วยความจำมากกว่าที่ได้รับอนุญาตในปัจจุบัน Ryujinx จะปิดตัวลงเมื่อเกินขีดจำกัดนี้", @@ -400,6 +404,8 @@ "InputDialogTitle": "กล่องโต้ตอบการป้อนข้อมูล", "InputDialogOk": "ตกลง", "InputDialogCancel": "ยกเลิก", + "InputDialogCancelling": "Cancelling", + "InputDialogClose": "Close", "InputDialogAddNewProfileTitle": "เลือก ชื่อโปรไฟล์", "InputDialogAddNewProfileHeader": "กรุณาใส่ชื่อโปรไฟล์", "InputDialogAddNewProfileSubtext": "(ความยาวสูงสุด: {0})", @@ -469,6 +475,7 @@ "DialogUninstallFileTypesSuccessMessage": "ถอนการติดตั้งตามประเภทของไฟล์สำเร็จแล้ว!", "DialogUninstallFileTypesErrorMessage": "ไม่สามารถถอนการติดตั้งตามประเภทของไฟล์ได้", "DialogOpenSettingsWindowLabel": "เปิดหน้าต่างการตั้งค่า", + "DialogOpenXCITrimmerWindowLabel": "XCI Trimmer Window", "DialogControllerAppletTitle": "คอนโทรลเลอร์ Applet", "DialogMessageDialogErrorExceptionMessage": "เกิดข้อผิดพลาดในการแสดงกล่องโต้ตอบข้อความ: {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "เกิดข้อผิดพลาดในการแสดงซอฟต์แวร์แป้นพิมพ์: {0}", @@ -671,6 +678,12 @@ "TitleUpdateVersionLabel": "เวอร์ชั่น {0}", "TitleBundledUpdateVersionLabel": "Bundled: เวอร์ชั่น {0}", "TitleBundledDlcLabel": "Bundled:", + "TitleXCIStatusPartialLabel": "Partial", + "TitleXCIStatusTrimmableLabel": "Untrimmed", + "TitleXCIStatusUntrimmableLabel": "Trimmed", + "TitleXCIStatusFailedLabel": "(Failed)", + "TitleXCICanSaveLabel": "Save {0:n0} Mb", + "TitleXCISavingLabel": "Saved {0:n0} Mb", "RyujinxInfo": "Ryujinx – ข้อมูล", "RyujinxConfirm": "Ryujinx - ยืนยัน", "FileDialogAllTypes": "ทุกประเภท", @@ -723,11 +736,37 @@ "SelectDlcDialogTitle": "เลือกไฟล์ DLC", "SelectUpdateDialogTitle": "เลือกไฟล์อัพเดต", "SelectModDialogTitle": "เลือกไดเรกทอรี Mods", + "TrimXCIFileDialogTitle": "Check and Trim XCI File", + "TrimXCIFileDialogPrimaryText": "This function will first check the empty space and then trim the XCI File to save disk space.", + "TrimXCIFileDialogSecondaryText": "Current File Size: {0:n} MB\nGame Data Size: {1:n} MB\nDisk Space Savings: {2:n} MB", + "TrimXCIFileNoTrimNecessary": "XCI File does not need to be trimmed. Check logs for further details", + "TrimXCIFileNoUntrimPossible": "XCI File cannot be untrimmed. Check logs for further details", + "TrimXCIFileReadOnlyFileCannotFix": "XCI File is Read Only and could not be made writable. Check logs for further details", + "TrimXCIFileFileSizeChanged": "XCI File has changed in size since it was scanned. Please check the file is not being written to and try again.", + "TrimXCIFileFreeSpaceCheckFailed": "XCI File has data in the free space area, it is not safe to trim", + "TrimXCIFileInvalidXCIFile": "XCI File contains invalid data. Check logs for further details", + "TrimXCIFileFileIOWriteError": "XCI File could not be opened for writing. Check logs for further details", + "TrimXCIFileFailedPrimaryText": "Trimming of the XCI file failed", + "TrimXCIFileCancelled": "The operation was cancelled", + "TrimXCIFileFileUndertermined": "No operation was performed", "UserProfileWindowTitle": "จัดการโปรไฟล์ผู้ใช้", "CheatWindowTitle": "จัดการสูตรโกง", "DlcWindowTitle": "จัดการ DLC ที่ดาวน์โหลดได้สำหรับ {0} ({1})", "ModWindowTitle": "จัดการม็อดที่ดาวน์โหลดได้สำหรับ {0} ({1})", "UpdateWindowTitle": "จัดการอัปเดตหัวข้อ", + "XCITrimmerWindowTitle": "XCI File Trimmer", + "XCITrimmerTitleStatusCount": "{0} of {1} Title(s) Selected", + "XCITrimmerTitleStatusCountWithFilter": "{0} of {1} Title(s) Selected ({2} displayed)", + "XCITrimmerTitleStatusTrimming": "Trimming {0} Title(s)...", + "XCITrimmerTitleStatusUntrimming": "Untrimming {0} Title(s)...", + "XCITrimmerTitleStatusFailed": "Failed", + "XCITrimmerPotentialSavings": "Potential Savings", + "XCITrimmerActualSavings": "Actual Savings", + "XCITrimmerSavingsMb": "{0:n0} Mb", + "XCITrimmerSelectDisplayed": "Select Shown", + "XCITrimmerDeselectDisplayed": "Deselect Shown", + "XCITrimmerSortName": "Title", + "XCITrimmerSortSaved": "Space Savings", "XCITrimmerTrim": "Trim", "XCITrimmerUntrim": "Untrim", "UpdateWindowUpdateAddedMessage": "{0} อัพเดตที่เพิ่มมาใหม่", @@ -743,6 +782,7 @@ "AutoloadUpdateRemovedMessage": "{0} missing update(s) removed", "ModWindowHeading": "{0} ม็อด", "UserProfilesEditProfile": "แก้ไขที่เลือกแล้ว", + "Continue": "Continue", "Cancel": "ยกเลิก", "Save": "บันทึก", "Discard": "ละทิ้ง", @@ -810,5 +850,17 @@ "MultiplayerMode": "โหมด:", "MultiplayerModeTooltip": "เปลี่ยนโหมดผู้เล่นหลายคนของ LDN\n\nLdnMitm จะปรับเปลี่ยนฟังก์ชันการเล่นแบบไร้สาย/ภายใน จะให้เกมทำงานเหมือนกับว่าเป็น LAN ช่วยให้สามารถเชื่อมต่อภายในเครือข่ายเดียวกันกับอินสแตนซ์ Ryujinx อื่น ๆ และคอนโซล Nintendo Switch ที่ถูกแฮ็กซึ่งมีโมดูล ldn_mitm ติดตั้งอยู่\n\nผู้เล่นหลายคนต้องการให้ผู้เล่นทุกคนอยู่ในเกมเวอร์ชันเดียวกัน (เช่น Super Smash Bros. Ultimate v13.0.1 ไม่สามารถเชื่อมต่อกับ v13.0.0)\n\nปล่อยให้ปิดการใช้งานหากไม่แน่ใจ", "MultiplayerModeDisabled": "ปิดใช้งาน", - "MultiplayerModeLdnMitm": "ldn_mitm" + "MultiplayerModeLdnMitm": "ldn_mitm", + "MultiplayerModeLdnRyu": "RyuLDN", + "MultiplayerDisableP2P": "Disable P2P Network Hosting (may increase latency)", + "MultiplayerDisableP2PTooltip": "Disable P2P network hosting, peers will proxy through the master server instead of connecting to you directly.", + "LdnPassphrase": "Network Passphrase:", + "LdnPassphraseTooltip": "You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputTooltip": "Enter a passphrase in the format Ryujinx-<8 hex chars>. You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputPublic": "(public)", + "GenLdnPass": "Generate Random", + "GenLdnPassTooltip": "Generates a new passphrase, which can be shared with other players.", + "ClearLdnPass": "Clear", + "ClearLdnPassTooltip": "Clears the current passphrase, returning to the public network.", + "InvalidLdnPassphrase": "Invalid Passphrase! Must be in the format \"Ryujinx-<8 hex chars>\"" } diff --git a/src/Ryujinx/Assets/Locales/tr_TR.json b/src/Ryujinx/Assets/Locales/tr_TR.json index 1ac9a0b6ed..9b321c4233 100644 --- a/src/Ryujinx/Assets/Locales/tr_TR.json +++ b/src/Ryujinx/Assets/Locales/tr_TR.json @@ -33,6 +33,7 @@ "MenuBarToolsManageFileTypes": "Dosya uzantılarını yönet", "MenuBarToolsInstallFileTypes": "Dosya uzantılarını yükle", "MenuBarToolsUninstallFileTypes": "Dosya uzantılarını kaldır", + "MenuBarToolsXCITrimmer": "Trim XCI Files", "MenuBarView": "_Görüntüle", "MenuBarViewWindow": "Pencere Boyutu", "MenuBarViewWindow720": "720p", @@ -84,8 +85,11 @@ "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", + "GameListContextMenuTrimXCI": "Check and Trim XCI File", + "GameListContextMenuTrimXCIToolTip": "Check and Trim XCI File to Save Disk Space", "StatusBarGamesLoaded": "{0}/{1} Oyun Yüklendi", "StatusBarSystemVersion": "Sistem Sürümü: {0}", + "StatusBarXCIFileTrimming": "Trimming XCI File '{0}'", "LinuxVmMaxMapCountDialogTitle": "Bellek Haritaları İçin Düşük Limit Tespit Edildi ", "LinuxVmMaxMapCountDialogTextPrimary": "vm.max_map_count değerini {0} sayısına yükseltmek ister misiniz", "LinuxVmMaxMapCountDialogTextSecondary": "Bazı oyunlar şu an izin verilen bellek haritası limitinden daha fazlasını yaratmaya çalışabilir. Ryujinx bu limitin geçildiği takdirde kendini kapatıcaktır.", @@ -400,6 +404,8 @@ "InputDialogTitle": "Giriş Yöntemi Diyaloğu", "InputDialogOk": "Tamam", "InputDialogCancel": "İptal", + "InputDialogCancelling": "Cancelling", + "InputDialogClose": "Close", "InputDialogAddNewProfileTitle": "Profil İsmini Seç", "InputDialogAddNewProfileHeader": "Lütfen Bir Profil İsmi Girin", "InputDialogAddNewProfileSubtext": "(Maksimum Uzunluk: {0})", @@ -469,6 +475,7 @@ "DialogUninstallFileTypesSuccessMessage": "Dosya uzantıları başarıyla kaldırıldı!", "DialogUninstallFileTypesErrorMessage": "Dosya uzantıları kaldırma işlemi başarısız oldu.", "DialogOpenSettingsWindowLabel": "Seçenekler Penceresini Aç", + "DialogOpenXCITrimmerWindowLabel": "XCI Trimmer Window", "DialogControllerAppletTitle": "Kumanda Applet'i", "DialogMessageDialogErrorExceptionMessage": "Mesaj diyaloğu gösterilirken hata: {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "Mesaj diyaloğu gösterilirken hata: {0}", @@ -671,6 +678,12 @@ "TitleUpdateVersionLabel": "Sürüm {0} - {1}", "TitleBundledUpdateVersionLabel": "Bundled: Version {0}", "TitleBundledDlcLabel": "Bundled:", + "TitleXCIStatusPartialLabel": "Partial", + "TitleXCIStatusTrimmableLabel": "Untrimmed", + "TitleXCIStatusUntrimmableLabel": "Trimmed", + "TitleXCIStatusFailedLabel": "(Failed)", + "TitleXCICanSaveLabel": "Save {0:n0} Mb", + "TitleXCISavingLabel": "Saved {0:n0} Mb", "RyujinxInfo": "Ryujinx - Bilgi", "RyujinxConfirm": "Ryujinx - Doğrulama", "FileDialogAllTypes": "Tüm türler", @@ -723,11 +736,37 @@ "SelectDlcDialogTitle": "DLC dosyalarını seç", "SelectUpdateDialogTitle": "Güncelleme dosyalarını seç", "SelectModDialogTitle": "Mod Dizinini Seç", + "TrimXCIFileDialogTitle": "Check and Trim XCI File", + "TrimXCIFileDialogPrimaryText": "This function will first check the empty space and then trim the XCI File to save disk space.", + "TrimXCIFileDialogSecondaryText": "Current File Size: {0:n} MB\nGame Data Size: {1:n} MB\nDisk Space Savings: {2:n} MB", + "TrimXCIFileNoTrimNecessary": "XCI File does not need to be trimmed. Check logs for further details", + "TrimXCIFileNoUntrimPossible": "XCI File cannot be untrimmed. Check logs for further details", + "TrimXCIFileReadOnlyFileCannotFix": "XCI File is Read Only and could not be made writable. Check logs for further details", + "TrimXCIFileFileSizeChanged": "XCI File has changed in size since it was scanned. Please check the file is not being written to and try again.", + "TrimXCIFileFreeSpaceCheckFailed": "XCI File has data in the free space area, it is not safe to trim", + "TrimXCIFileInvalidXCIFile": "XCI File contains invalid data. Check logs for further details", + "TrimXCIFileFileIOWriteError": "XCI File could not be opened for writing. Check logs for further details", + "TrimXCIFileFailedPrimaryText": "Trimming of the XCI file failed", + "TrimXCIFileCancelled": "The operation was cancelled", + "TrimXCIFileFileUndertermined": "No operation was performed", "UserProfileWindowTitle": "Kullanıcı Profillerini Yönet", "CheatWindowTitle": "Oyun Hilelerini Yönet", "DlcWindowTitle": "Oyun DLC'lerini Yönet", "ModWindowTitle": "Manage Mods for {0} ({1})", "UpdateWindowTitle": "Oyun Güncellemelerini Yönet", + "XCITrimmerWindowTitle": "XCI File Trimmer", + "XCITrimmerTitleStatusCount": "{0} of {1} Title(s) Selected", + "XCITrimmerTitleStatusCountWithFilter": "{0} of {1} Title(s) Selected ({2} displayed)", + "XCITrimmerTitleStatusTrimming": "Trimming {0} Title(s)...", + "XCITrimmerTitleStatusUntrimming": "Untrimming {0} Title(s)...", + "XCITrimmerTitleStatusFailed": "Failed", + "XCITrimmerPotentialSavings": "Potential Savings", + "XCITrimmerActualSavings": "Actual Savings", + "XCITrimmerSavingsMb": "{0:n0} Mb", + "XCITrimmerSelectDisplayed": "Select Shown", + "XCITrimmerDeselectDisplayed": "Deselect Shown", + "XCITrimmerSortName": "Title", + "XCITrimmerSortSaved": "Space Savings", "XCITrimmerTrim": "Trim", "XCITrimmerUntrim": "Untrim", "UpdateWindowUpdateAddedMessage": "{0} new update(s) added", @@ -743,6 +782,7 @@ "AutoloadUpdateRemovedMessage": "{0} missing update(s) removed", "ModWindowHeading": "{0} Mod(lar)", "UserProfilesEditProfile": "Seçiliyi Düzenle", + "Continue": "Continue", "Cancel": "İptal", "Save": "Kaydet", "Discard": "Iskarta", @@ -810,5 +850,17 @@ "MultiplayerMode": "Mod:", "MultiplayerModeTooltip": "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.", "MultiplayerModeDisabled": "Devre Dışı", - "MultiplayerModeLdnMitm": "ldn_mitm" + "MultiplayerModeLdnMitm": "ldn_mitm", + "MultiplayerModeLdnRyu": "RyuLDN", + "MultiplayerDisableP2P": "Disable P2P Network Hosting (may increase latency)", + "MultiplayerDisableP2PTooltip": "Disable P2P network hosting, peers will proxy through the master server instead of connecting to you directly.", + "LdnPassphrase": "Network Passphrase:", + "LdnPassphraseTooltip": "You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputTooltip": "Enter a passphrase in the format Ryujinx-<8 hex chars>. You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputPublic": "(public)", + "GenLdnPass": "Generate Random", + "GenLdnPassTooltip": "Generates a new passphrase, which can be shared with other players.", + "ClearLdnPass": "Clear", + "ClearLdnPassTooltip": "Clears the current passphrase, returning to the public network.", + "InvalidLdnPassphrase": "Invalid Passphrase! Must be in the format \"Ryujinx-<8 hex chars>\"" } diff --git a/src/Ryujinx/Assets/Locales/uk_UA.json b/src/Ryujinx/Assets/Locales/uk_UA.json index 0e22263b6f..09a7e8cb40 100644 --- a/src/Ryujinx/Assets/Locales/uk_UA.json +++ b/src/Ryujinx/Assets/Locales/uk_UA.json @@ -33,6 +33,7 @@ "MenuBarToolsManageFileTypes": "Керувати типами файлів", "MenuBarToolsInstallFileTypes": "Установити типи файлів", "MenuBarToolsUninstallFileTypes": "Видалити типи файлів", + "MenuBarToolsXCITrimmer": "Trim XCI Files", "MenuBarView": "_View", "MenuBarViewWindow": "Window Size", "MenuBarViewWindow720": "720p", @@ -84,8 +85,11 @@ "GameListContextMenuOpenModsDirectoryToolTip": "Відкриває каталог, який містить модифікації Додатків", "GameListContextMenuOpenSdModsDirectory": "Відкрити каталог модифікацій Atmosphere", "GameListContextMenuOpenSdModsDirectoryToolTip": "Відкриває альтернативний каталог SD-карти Atmosphere, що містить модифікації Додатків. Корисно для модифікацій, зроблених для реального обладнання.", + "GameListContextMenuTrimXCI": "Check and Trim XCI File", + "GameListContextMenuTrimXCIToolTip": "Check and Trim XCI File to Save Disk Space", "StatusBarGamesLoaded": "{0}/{1} ігор завантажено", "StatusBarSystemVersion": "Версія системи: {0}", + "StatusBarXCIFileTrimming": "Trimming XCI File '{0}'", "LinuxVmMaxMapCountDialogTitle": "Виявлено низьку межу для відображення памʼяті", "LinuxVmMaxMapCountDialogTextPrimary": "Бажаєте збільшити значення vm.max_map_count на {0}", "LinuxVmMaxMapCountDialogTextSecondary": "Деякі ігри можуть спробувати створити більше відображень памʼяті, ніж дозволено наразі. Ryujinx завершить роботу, щойно цей ліміт буде перевищено.", @@ -400,6 +404,8 @@ "InputDialogTitle": "Діалог введення", "InputDialogOk": "Гаразд", "InputDialogCancel": "Скасувати", + "InputDialogCancelling": "Cancelling", + "InputDialogClose": "Close", "InputDialogAddNewProfileTitle": "Виберіть ім'я профілю", "InputDialogAddNewProfileHeader": "Будь ласка, введіть ім'я профілю", "InputDialogAddNewProfileSubtext": "(Макс. довжина: {0})", @@ -469,6 +475,7 @@ "DialogUninstallFileTypesSuccessMessage": "Успішно видалено типи файлів!", "DialogUninstallFileTypesErrorMessage": "Не вдалося видалити типи файлів.", "DialogOpenSettingsWindowLabel": "Відкрити вікно налаштувань", + "DialogOpenXCITrimmerWindowLabel": "XCI Trimmer Window", "DialogControllerAppletTitle": "Аплет контролера", "DialogMessageDialogErrorExceptionMessage": "Помилка показу діалогового вікна повідомлення: {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "Помилка показу програмної клавіатури: {0}", @@ -671,6 +678,12 @@ "TitleUpdateVersionLabel": "Версія {0} - {1}", "TitleBundledUpdateVersionLabel": "Bundled: Version {0}", "TitleBundledDlcLabel": "Bundled:", + "TitleXCIStatusPartialLabel": "Partial", + "TitleXCIStatusTrimmableLabel": "Untrimmed", + "TitleXCIStatusUntrimmableLabel": "Trimmed", + "TitleXCIStatusFailedLabel": "(Failed)", + "TitleXCICanSaveLabel": "Save {0:n0} Mb", + "TitleXCISavingLabel": "Saved {0:n0} Mb", "RyujinxInfo": "Ryujin x - Інформація", "RyujinxConfirm": "Ryujinx - Підтвердження", "FileDialogAllTypes": "Всі типи", @@ -723,11 +736,37 @@ "SelectDlcDialogTitle": "Виберіть файли DLC", "SelectUpdateDialogTitle": "Виберіть файли оновлення", "SelectModDialogTitle": "Виберіть теку з модами", + "TrimXCIFileDialogTitle": "Check and Trim XCI File", + "TrimXCIFileDialogPrimaryText": "This function will first check the empty space and then trim the XCI File to save disk space.", + "TrimXCIFileDialogSecondaryText": "Current File Size: {0:n} MB\nGame Data Size: {1:n} MB\nDisk Space Savings: {2:n} MB", + "TrimXCIFileNoTrimNecessary": "XCI File does not need to be trimmed. Check logs for further details", + "TrimXCIFileNoUntrimPossible": "XCI File cannot be untrimmed. Check logs for further details", + "TrimXCIFileReadOnlyFileCannotFix": "XCI File is Read Only and could not be made writable. Check logs for further details", + "TrimXCIFileFileSizeChanged": "XCI File has changed in size since it was scanned. Please check the file is not being written to and try again.", + "TrimXCIFileFreeSpaceCheckFailed": "XCI File has data in the free space area, it is not safe to trim", + "TrimXCIFileInvalidXCIFile": "XCI File contains invalid data. Check logs for further details", + "TrimXCIFileFileIOWriteError": "XCI File could not be opened for writing. Check logs for further details", + "TrimXCIFileFailedPrimaryText": "Trimming of the XCI file failed", + "TrimXCIFileCancelled": "The operation was cancelled", + "TrimXCIFileFileUndertermined": "No operation was performed", "UserProfileWindowTitle": "Менеджер профілів користувачів", "CheatWindowTitle": "Менеджер читів", "DlcWindowTitle": "Менеджер вмісту для завантаження", "ModWindowTitle": "Керувати модами для {0} ({1})", "UpdateWindowTitle": "Менеджер оновлення назв", + "XCITrimmerWindowTitle": "XCI File Trimmer", + "XCITrimmerTitleStatusCount": "{0} of {1} Title(s) Selected", + "XCITrimmerTitleStatusCountWithFilter": "{0} of {1} Title(s) Selected ({2} displayed)", + "XCITrimmerTitleStatusTrimming": "Trimming {0} Title(s)...", + "XCITrimmerTitleStatusUntrimming": "Untrimming {0} Title(s)...", + "XCITrimmerTitleStatusFailed": "Failed", + "XCITrimmerPotentialSavings": "Potential Savings", + "XCITrimmerActualSavings": "Actual Savings", + "XCITrimmerSavingsMb": "{0:n0} Mb", + "XCITrimmerSelectDisplayed": "Select Shown", + "XCITrimmerDeselectDisplayed": "Deselect Shown", + "XCITrimmerSortName": "Title", + "XCITrimmerSortSaved": "Space Savings", "XCITrimmerTrim": "Trim", "XCITrimmerUntrim": "Untrim", "UpdateWindowUpdateAddedMessage": "{0} new update(s) added", @@ -743,6 +782,7 @@ "AutoloadUpdateRemovedMessage": "{0} missing update(s) removed", "ModWindowHeading": "{0} мод(ів)", "UserProfilesEditProfile": "Редагувати вибране", + "Continue": "Continue", "Cancel": "Скасувати", "Save": "Зберегти", "Discard": "Скасувати", @@ -810,5 +850,17 @@ "MultiplayerMode": "Режим:", "MultiplayerModeTooltip": "Змінити LDN мультиплеєру.\n\nLdnMitm змінить функціонал бездротової/локальної гри в іграх, щоб вони працювали так, ніби це LAN, що дозволяє локальні підключення в тій самій мережі з іншими екземплярами Ryujinx та хакнутими консолями Nintendo Switch, які мають встановлений модуль ldn_mitm.\n\nМультиплеєр вимагає, щоб усі гравці були на одній і тій же версії гри (наприклад Super Smash Bros. Ultimate v13.0.1 не зможе під'єднатися до v13.0.0).\n\nЗалиште на \"Вимкнено\", якщо не впевнені, ", "MultiplayerModeDisabled": "Вимкнено", - "MultiplayerModeLdnMitm": "ldn_mitm" + "MultiplayerModeLdnMitm": "ldn_mitm", + "MultiplayerModeLdnRyu": "RyuLDN", + "MultiplayerDisableP2P": "Disable P2P Network Hosting (may increase latency)", + "MultiplayerDisableP2PTooltip": "Disable P2P network hosting, peers will proxy through the master server instead of connecting to you directly.", + "LdnPassphrase": "Network Passphrase:", + "LdnPassphraseTooltip": "You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputTooltip": "Enter a passphrase in the format Ryujinx-<8 hex chars>. You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputPublic": "(public)", + "GenLdnPass": "Generate Random", + "GenLdnPassTooltip": "Generates a new passphrase, which can be shared with other players.", + "ClearLdnPass": "Clear", + "ClearLdnPassTooltip": "Clears the current passphrase, returning to the public network.", + "InvalidLdnPassphrase": "Invalid Passphrase! Must be in the format \"Ryujinx-<8 hex chars>\"" } diff --git a/src/Ryujinx/Assets/Locales/zh_CN.json b/src/Ryujinx/Assets/Locales/zh_CN.json index 004d5007ba..11840e864f 100644 --- a/src/Ryujinx/Assets/Locales/zh_CN.json +++ b/src/Ryujinx/Assets/Locales/zh_CN.json @@ -33,6 +33,7 @@ "MenuBarToolsManageFileTypes": "管理文件扩展名", "MenuBarToolsInstallFileTypes": "关联文件扩展名", "MenuBarToolsUninstallFileTypes": "取消关联扩展名", + "MenuBarToolsXCITrimmer": "Trim XCI Files", "MenuBarView": "视图(_V)", "MenuBarViewWindow": "窗口大小", "MenuBarViewWindow720": "720p", @@ -84,8 +85,11 @@ "GameListContextMenuOpenModsDirectoryToolTip": "打开存放游戏 MOD 的目录", "GameListContextMenuOpenSdModsDirectory": "打开大气层系统 MOD 目录", "GameListContextMenuOpenSdModsDirectoryToolTip": "打开存放适用于大气层系统的游戏 MOD 的目录,对于为真实硬件打包的 MOD 非常有用", + "GameListContextMenuTrimXCI": "Check and Trim XCI File", + "GameListContextMenuTrimXCIToolTip": "Check and Trim XCI File to Save Disk Space", "StatusBarGamesLoaded": "{0}/{1} 游戏加载完成", "StatusBarSystemVersion": "系统固件版本:{0}", + "StatusBarXCIFileTrimming": "Trimming XCI File '{0}'", "LinuxVmMaxMapCountDialogTitle": "检测到操作系统内存映射最大数量被设置的过低", "LinuxVmMaxMapCountDialogTextPrimary": "你想要将操作系统 vm.max_map_count 的值增加到 {0} 吗", "LinuxVmMaxMapCountDialogTextSecondary": "有些游戏可能会尝试创建超过当前系统允许的内存映射最大数量,若超过当前最大数量,Ryujinx 模拟器将会闪退。", @@ -400,6 +404,8 @@ "InputDialogTitle": "输入对话框", "InputDialogOk": "完成", "InputDialogCancel": "取消", + "InputDialogCancelling": "Cancelling", + "InputDialogClose": "Close", "InputDialogAddNewProfileTitle": "选择用户名称", "InputDialogAddNewProfileHeader": "请输入账户名称", "InputDialogAddNewProfileSubtext": "(最大长度:{0})", @@ -469,6 +475,7 @@ "DialogUninstallFileTypesSuccessMessage": "成功解除文件类型关联!", "DialogUninstallFileTypesErrorMessage": "解除文件类型关联失败!", "DialogOpenSettingsWindowLabel": "打开设置窗口", + "DialogOpenXCITrimmerWindowLabel": "XCI Trimmer Window", "DialogControllerAppletTitle": "控制器小窗口", "DialogMessageDialogErrorExceptionMessage": "显示消息对话框时出错:{0}", "DialogSoftwareKeyboardErrorExceptionMessage": "显示软件键盘时出错:{0}", @@ -671,6 +678,12 @@ "TitleUpdateVersionLabel": "游戏更新的版本 {0}", "TitleBundledUpdateVersionLabel": "捆绑:版本 {0}", "TitleBundledDlcLabel": "捆绑:", + "TitleXCIStatusPartialLabel": "Partial", + "TitleXCIStatusTrimmableLabel": "Untrimmed", + "TitleXCIStatusUntrimmableLabel": "Trimmed", + "TitleXCIStatusFailedLabel": "(Failed)", + "TitleXCICanSaveLabel": "Save {0:n0} Mb", + "TitleXCISavingLabel": "Saved {0:n0} Mb", "RyujinxInfo": "Ryujinx - 信息", "RyujinxConfirm": "Ryujinx - 确认", "FileDialogAllTypes": "全部类型", @@ -723,11 +736,37 @@ "SelectDlcDialogTitle": "选择 DLC 文件", "SelectUpdateDialogTitle": "选择更新文件", "SelectModDialogTitle": "选择 MOD 目录", + "TrimXCIFileDialogTitle": "Check and Trim XCI File", + "TrimXCIFileDialogPrimaryText": "This function will first check the empty space and then trim the XCI File to save disk space.", + "TrimXCIFileDialogSecondaryText": "Current File Size: {0:n} MB\nGame Data Size: {1:n} MB\nDisk Space Savings: {2:n} MB", + "TrimXCIFileNoTrimNecessary": "XCI File does not need to be trimmed. Check logs for further details", + "TrimXCIFileNoUntrimPossible": "XCI File cannot be untrimmed. Check logs for further details", + "TrimXCIFileReadOnlyFileCannotFix": "XCI File is Read Only and could not be made writable. Check logs for further details", + "TrimXCIFileFileSizeChanged": "XCI File has changed in size since it was scanned. Please check the file is not being written to and try again.", + "TrimXCIFileFreeSpaceCheckFailed": "XCI File has data in the free space area, it is not safe to trim", + "TrimXCIFileInvalidXCIFile": "XCI File contains invalid data. Check logs for further details", + "TrimXCIFileFileIOWriteError": "XCI File could not be opened for writing. Check logs for further details", + "TrimXCIFileFailedPrimaryText": "Trimming of the XCI file failed", + "TrimXCIFileCancelled": "The operation was cancelled", + "TrimXCIFileFileUndertermined": "No operation was performed", "UserProfileWindowTitle": "管理用户账户", "CheatWindowTitle": "金手指管理器", "DlcWindowTitle": "管理 {0} ({1}) 的 DLC", "ModWindowTitle": "管理 {0} ({1}) 的 MOD", "UpdateWindowTitle": "游戏更新管理器", + "XCITrimmerWindowTitle": "XCI File Trimmer", + "XCITrimmerTitleStatusCount": "{0} of {1} Title(s) Selected", + "XCITrimmerTitleStatusCountWithFilter": "{0} of {1} Title(s) Selected ({2} displayed)", + "XCITrimmerTitleStatusTrimming": "Trimming {0} Title(s)...", + "XCITrimmerTitleStatusUntrimming": "Untrimming {0} Title(s)...", + "XCITrimmerTitleStatusFailed": "Failed", + "XCITrimmerPotentialSavings": "Potential Savings", + "XCITrimmerActualSavings": "Actual Savings", + "XCITrimmerSavingsMb": "{0:n0} Mb", + "XCITrimmerSelectDisplayed": "Select Shown", + "XCITrimmerDeselectDisplayed": "Deselect Shown", + "XCITrimmerSortName": "Title", + "XCITrimmerSortSaved": "Space Savings", "XCITrimmerTrim": "Trim", "XCITrimmerUntrim": "Untrim", "UpdateWindowUpdateAddedMessage": "{0} 个更新被添加", @@ -743,6 +782,7 @@ "AutoloadUpdateRemovedMessage": "{0} 个失效的游戏更新已移除", "ModWindowHeading": "{0} Mod", "UserProfilesEditProfile": "编辑所选", + "Continue": "Continue", "Cancel": "取消", "Save": "保存", "Discard": "放弃", @@ -810,5 +850,17 @@ "MultiplayerMode": "联机模式:", "MultiplayerModeTooltip": "修改 LDN 多人联机游玩模式。\n\nldn_mitm 联机插件将修改游戏中的本地无线和本地游玩功能,使其表现得像局域网一样,允许和其他安装了 ldn_mitm 插件的 Ryujinx 模拟器和破解的任天堂 Switch 主机在同一网络下进行本地连接,实现多人联机游玩。\n\n多人联机游玩要求所有玩家必须运行相同的游戏版本(例如,游戏版本 v13.0.1 无法与 v13.0.0 联机)。\n\n如果不确定,请保持为“禁用”。", "MultiplayerModeDisabled": "禁用", - "MultiplayerModeLdnMitm": "ldn_mitm" + "MultiplayerModeLdnMitm": "ldn_mitm", + "MultiplayerModeLdnRyu": "RyuLDN", + "MultiplayerDisableP2P": "Disable P2P Network Hosting (may increase latency)", + "MultiplayerDisableP2PTooltip": "Disable P2P network hosting, peers will proxy through the master server instead of connecting to you directly.", + "LdnPassphrase": "Network Passphrase:", + "LdnPassphraseTooltip": "You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputTooltip": "Enter a passphrase in the format Ryujinx-<8 hex chars>. You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputPublic": "(public)", + "GenLdnPass": "Generate Random", + "GenLdnPassTooltip": "Generates a new passphrase, which can be shared with other players.", + "ClearLdnPass": "Clear", + "ClearLdnPassTooltip": "Clears the current passphrase, returning to the public network.", + "InvalidLdnPassphrase": "Invalid Passphrase! Must be in the format \"Ryujinx-<8 hex chars>\"" } diff --git a/src/Ryujinx/Assets/Locales/zh_TW.json b/src/Ryujinx/Assets/Locales/zh_TW.json index 9bfc243aef..d59df0e5bd 100644 --- a/src/Ryujinx/Assets/Locales/zh_TW.json +++ b/src/Ryujinx/Assets/Locales/zh_TW.json @@ -33,6 +33,7 @@ "MenuBarToolsManageFileTypes": "管理檔案類型", "MenuBarToolsInstallFileTypes": "安裝檔案類型", "MenuBarToolsUninstallFileTypes": "移除檔案類型", + "MenuBarToolsXCITrimmer": "Trim XCI Files", "MenuBarView": "檢視(_V)", "MenuBarViewWindow": "視窗大小", "MenuBarViewWindow720": "720p", @@ -84,8 +85,11 @@ "GameListContextMenuOpenModsDirectoryToolTip": "開啟此應用程式模組的資料夾", "GameListContextMenuOpenSdModsDirectory": "開啟 Atmosphere 模組資料夾", "GameListContextMenuOpenSdModsDirectoryToolTip": "開啟此應用程式模組的另一個 SD 卡 Atmosphere 資料夾。適用於為真實硬體封裝的模組。", + "GameListContextMenuTrimXCI": "Check and Trim XCI File", + "GameListContextMenuTrimXCIToolTip": "Check and Trim XCI File to Save Disk Space", "StatusBarGamesLoaded": "{0}/{1} 遊戲已載入", "StatusBarSystemVersion": "系統版本: {0}", + "StatusBarXCIFileTrimming": "Trimming XCI File '{0}'", "LinuxVmMaxMapCountDialogTitle": "檢測到記憶體映射的低限值", "LinuxVmMaxMapCountDialogTextPrimary": "您是否要將 vm.max_map_count 的數值增至 {0}?", "LinuxVmMaxMapCountDialogTextSecondary": "某些遊戲可能會嘗試建立超過目前允許的記憶體映射。一旦超過此限制,Ryujinx 就會崩潰。", @@ -400,6 +404,8 @@ "InputDialogTitle": "輸入對話方塊", "InputDialogOk": "確定", "InputDialogCancel": "取消", + "InputDialogCancelling": "Cancelling", + "InputDialogClose": "Close", "InputDialogAddNewProfileTitle": "選擇設定檔名稱", "InputDialogAddNewProfileHeader": "請輸入設定檔名稱", "InputDialogAddNewProfileSubtext": "(最大長度: {0})", @@ -469,6 +475,7 @@ "DialogUninstallFileTypesSuccessMessage": "成功移除檔案類型!", "DialogUninstallFileTypesErrorMessage": "無法移除檔案類型。", "DialogOpenSettingsWindowLabel": "開啟設定視窗", + "DialogOpenXCITrimmerWindowLabel": "XCI Trimmer Window", "DialogControllerAppletTitle": "控制器小程式", "DialogMessageDialogErrorExceptionMessage": "顯示訊息對話方塊時出現錯誤: {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "顯示軟體鍵盤時出現錯誤: {0}", @@ -671,6 +678,12 @@ "TitleUpdateVersionLabel": "版本 {0}", "TitleBundledUpdateVersionLabel": "附帶: 版本 {0}", "TitleBundledDlcLabel": "附帶:", + "TitleXCIStatusPartialLabel": "Partial", + "TitleXCIStatusTrimmableLabel": "Untrimmed", + "TitleXCIStatusUntrimmableLabel": "Trimmed", + "TitleXCIStatusFailedLabel": "(Failed)", + "TitleXCICanSaveLabel": "Save {0:n0} Mb", + "TitleXCISavingLabel": "Saved {0:n0} Mb", "RyujinxInfo": "Ryujinx - 資訊", "RyujinxConfirm": "Ryujinx - 確認", "FileDialogAllTypes": "全部類型", @@ -723,11 +736,37 @@ "SelectDlcDialogTitle": "選取 DLC 檔案", "SelectUpdateDialogTitle": "選取更新檔", "SelectModDialogTitle": "選取模組資料夾", + "TrimXCIFileDialogTitle": "Check and Trim XCI File", + "TrimXCIFileDialogPrimaryText": "This function will first check the empty space and then trim the XCI File to save disk space.", + "TrimXCIFileDialogSecondaryText": "Current File Size: {0:n} MB\nGame Data Size: {1:n} MB\nDisk Space Savings: {2:n} MB", + "TrimXCIFileNoTrimNecessary": "XCI File does not need to be trimmed. Check logs for further details", + "TrimXCIFileNoUntrimPossible": "XCI File cannot be untrimmed. Check logs for further details", + "TrimXCIFileReadOnlyFileCannotFix": "XCI File is Read Only and could not be made writable. Check logs for further details", + "TrimXCIFileFileSizeChanged": "XCI File has changed in size since it was scanned. Please check the file is not being written to and try again.", + "TrimXCIFileFreeSpaceCheckFailed": "XCI File has data in the free space area, it is not safe to trim", + "TrimXCIFileInvalidXCIFile": "XCI File contains invalid data. Check logs for further details", + "TrimXCIFileFileIOWriteError": "XCI File could not be opened for writing. Check logs for further details", + "TrimXCIFileFailedPrimaryText": "Trimming of the XCI file failed", + "TrimXCIFileCancelled": "The operation was cancelled", + "TrimXCIFileFileUndertermined": "No operation was performed", "UserProfileWindowTitle": "使用者設定檔管理員", "CheatWindowTitle": "密技管理員", "DlcWindowTitle": "管理 {0} 的可下載內容 ({1})", "ModWindowTitle": "管理 {0} 的模組 ({1})", "UpdateWindowTitle": "遊戲更新管理員", + "XCITrimmerWindowTitle": "XCI File Trimmer", + "XCITrimmerTitleStatusCount": "{0} of {1} Title(s) Selected", + "XCITrimmerTitleStatusCountWithFilter": "{0} of {1} Title(s) Selected ({2} displayed)", + "XCITrimmerTitleStatusTrimming": "Trimming {0} Title(s)...", + "XCITrimmerTitleStatusUntrimming": "Untrimming {0} Title(s)...", + "XCITrimmerTitleStatusFailed": "Failed", + "XCITrimmerPotentialSavings": "Potential Savings", + "XCITrimmerActualSavings": "Actual Savings", + "XCITrimmerSavingsMb": "{0:n0} Mb", + "XCITrimmerSelectDisplayed": "Select Shown", + "XCITrimmerDeselectDisplayed": "Deselect Shown", + "XCITrimmerSortName": "Title", + "XCITrimmerSortSaved": "Space Savings", "XCITrimmerTrim": "Trim", "XCITrimmerUntrim": "Untrim", "UpdateWindowUpdateAddedMessage": "已加入 {0} 個遊戲更新", @@ -743,6 +782,7 @@ "AutoloadUpdateRemovedMessage": "已刪除 {0} 個遺失的遊戲更新", "ModWindowHeading": "{0} 模組", "UserProfilesEditProfile": "編輯所選", + "Continue": "Continue", "Cancel": "取消", "Save": "儲存", "Discard": "放棄變更", @@ -810,5 +850,17 @@ "MultiplayerMode": "模式:", "MultiplayerModeTooltip": "變更 LDN 多人遊戲模式。\n\nLdnMitm 將修改遊戲中的本機無線/本機遊戲功能,使其如同區域網路一樣執行,允許與其他安裝了 ldn_mitm 模組的 Ryujinx 實例和已破解的 Nintendo Switch 遊戲機進行本機同網路連線。\n\n多人遊戲要求所有玩家使用相同的遊戲版本 (例如,Super Smash Bros. Ultimate v13.0.1 無法連接 v13.0.0)。\n\n如果不確定,請保持 Disabled (停用) 狀態。", "MultiplayerModeDisabled": "已停用", - "MultiplayerModeLdnMitm": "ldn_mitm" + "MultiplayerModeLdnMitm": "ldn_mitm", + "MultiplayerModeLdnRyu": "RyuLDN", + "MultiplayerDisableP2P": "Disable P2P Network Hosting (may increase latency)", + "MultiplayerDisableP2PTooltip": "Disable P2P network hosting, peers will proxy through the master server instead of connecting to you directly.", + "LdnPassphrase": "Network Passphrase:", + "LdnPassphraseTooltip": "You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputTooltip": "Enter a passphrase in the format Ryujinx-<8 hex chars>. You will only be able to see hosted games with the same passphrase as you.", + "LdnPassphraseInputPublic": "(public)", + "GenLdnPass": "Generate Random", + "GenLdnPassTooltip": "Generates a new passphrase, which can be shared with other players.", + "ClearLdnPass": "Clear", + "ClearLdnPassTooltip": "Clears the current passphrase, returning to the public network.", + "InvalidLdnPassphrase": "Invalid Passphrase! Must be in the format \"Ryujinx-<8 hex chars>\"" } From f8d63f9a2fe6a094f147b414201c882e34f27e29 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Fri, 22 Nov 2024 14:38:58 -0600 Subject: [PATCH 08/61] UI: Add a show changelog button in the Updater, for new updates & when you're on the latest version. --- Directory.Packages.props | 4 +-- src/Ryujinx.Common/ReleaseInformation.cs | 9 +++++ src/Ryujinx/Assets/Locales/ar_SA.json | 1 + src/Ryujinx/Assets/Locales/de_DE.json | 1 + src/Ryujinx/Assets/Locales/el_GR.json | 1 + src/Ryujinx/Assets/Locales/en_US.json | 1 + src/Ryujinx/Assets/Locales/es_ES.json | 1 + src/Ryujinx/Assets/Locales/fr_FR.json | 1 + src/Ryujinx/Assets/Locales/he_IL.json | 1 + src/Ryujinx/Assets/Locales/it_IT.json | 1 + src/Ryujinx/Assets/Locales/ja_JP.json | 1 + src/Ryujinx/Assets/Locales/ko_KR.json | 1 + src/Ryujinx/Assets/Locales/pl_PL.json | 1 + src/Ryujinx/Assets/Locales/pt_BR.json | 1 + src/Ryujinx/Assets/Locales/ru_RU.json | 1 + src/Ryujinx/Assets/Locales/th_TH.json | 1 + src/Ryujinx/Assets/Locales/tr_TR.json | 1 + src/Ryujinx/Assets/Locales/uk_UA.json | 1 + src/Ryujinx/Assets/Locales/zh_CN.json | 1 + src/Ryujinx/Assets/Locales/zh_TW.json | 1 + src/Ryujinx/UI/Helpers/ContentDialogHelper.cs | 34 +++++++++++++++++++ src/Ryujinx/Updater.cs | 33 +++++++++++++----- 22 files changed, 87 insertions(+), 11 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index c0ace079d1..ffb5f2ead4 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -38,7 +38,7 @@ - + @@ -52,4 +52,4 @@ - \ No newline at end of file + diff --git a/src/Ryujinx.Common/ReleaseInformation.cs b/src/Ryujinx.Common/ReleaseInformation.cs index f4c62155af..011d9848a9 100644 --- a/src/Ryujinx.Common/ReleaseInformation.cs +++ b/src/Ryujinx.Common/ReleaseInformation.cs @@ -1,3 +1,4 @@ +using System; using System.Reflection; namespace Ryujinx.Common @@ -35,5 +36,13 @@ public static class ReleaseInformation public static bool IsReleaseBuild => IsValid && ReleaseChannelName.Equals(ReleaseChannel); public static string Version => IsValid ? BuildVersion : Assembly.GetEntryAssembly()!.GetCustomAttribute()?.InformationalVersion; + + public static string GetChangelogUrl(Version currentVersion, Version newVersion) => + IsCanaryBuild + ? $"https://github.com/{ReleaseChannelOwner}/{ReleaseChannelSourceRepo}/compare/Canary-{currentVersion}...Canary-{newVersion}" + : $"https://github.com/{ReleaseChannelOwner}/{ReleaseChannelSourceRepo}/releases/tag/{newVersion}"; + + public static string GetChangelogForVersion(Version version) => + $"https://github.com/{ReleaseChannelOwner}/{ReleaseChannelRepo}/releases/tag/{version}"; } } diff --git a/src/Ryujinx/Assets/Locales/ar_SA.json b/src/Ryujinx/Assets/Locales/ar_SA.json index 6dbc96135f..c937a2eede 100644 --- a/src/Ryujinx/Assets/Locales/ar_SA.json +++ b/src/Ryujinx/Assets/Locales/ar_SA.json @@ -457,6 +457,7 @@ "DialogUpdaterExtractionMessage": "جاري استخراج التحديث...", "DialogUpdaterRenamingMessage": "إعادة تسمية التحديث...", "DialogUpdaterAddingFilesMessage": "إضافة تحديث جديد...", + "DialogUpdaterShowChangelogMessage": "Show Changelog", "DialogUpdaterCompleteMessage": "اكتمل التحديث", "DialogUpdaterRestartMessage": "هل تريد إعادة تشغيل ريوجينكس الآن؟", "DialogUpdaterNoInternetMessage": "أنت غير متصل بالإنترنت.", diff --git a/src/Ryujinx/Assets/Locales/de_DE.json b/src/Ryujinx/Assets/Locales/de_DE.json index be95f3bc0d..c27de56087 100644 --- a/src/Ryujinx/Assets/Locales/de_DE.json +++ b/src/Ryujinx/Assets/Locales/de_DE.json @@ -457,6 +457,7 @@ "DialogUpdaterExtractionMessage": "Update wird entpackt...", "DialogUpdaterRenamingMessage": "Update wird umbenannt...", "DialogUpdaterAddingFilesMessage": "Update wird hinzugefügt...", + "DialogUpdaterShowChangelogMessage": "Show Changelog", "DialogUpdaterCompleteMessage": "Update abgeschlossen!", "DialogUpdaterRestartMessage": "Ryujinx jetzt neu starten?", "DialogUpdaterNoInternetMessage": "Es besteht keine Verbindung mit dem Internet!", diff --git a/src/Ryujinx/Assets/Locales/el_GR.json b/src/Ryujinx/Assets/Locales/el_GR.json index c6cfb9d62e..d47c8b9fe9 100644 --- a/src/Ryujinx/Assets/Locales/el_GR.json +++ b/src/Ryujinx/Assets/Locales/el_GR.json @@ -457,6 +457,7 @@ "DialogUpdaterExtractionMessage": "Εξαγωγή Ενημέρωσης...", "DialogUpdaterRenamingMessage": "Μετονομασία Ενημέρωσης...", "DialogUpdaterAddingFilesMessage": "Προσθήκη Νέας Ενημέρωσης...", + "DialogUpdaterShowChangelogMessage": "Show Changelog", "DialogUpdaterCompleteMessage": "Η Ενημέρωση Ολοκληρώθηκε!", "DialogUpdaterRestartMessage": "Θέλετε να επανεκκινήσετε το Ryujinx τώρα;", "DialogUpdaterNoInternetMessage": "Δεν είστε συνδεδεμένοι στο Διαδίκτυο!", diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json index 9354c8a412..23135866d3 100644 --- a/src/Ryujinx/Assets/Locales/en_US.json +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -457,6 +457,7 @@ "DialogUpdaterExtractionMessage": "Extracting Update...", "DialogUpdaterRenamingMessage": "Renaming Update...", "DialogUpdaterAddingFilesMessage": "Adding New Update...", + "DialogUpdaterShowChangelogMessage": "Show Changelog", "DialogUpdaterCompleteMessage": "Update Complete!", "DialogUpdaterRestartMessage": "Do you want to restart Ryujinx now?", "DialogUpdaterNoInternetMessage": "You are not connected to the Internet!", diff --git a/src/Ryujinx/Assets/Locales/es_ES.json b/src/Ryujinx/Assets/Locales/es_ES.json index 6a194960b4..8456040ce5 100644 --- a/src/Ryujinx/Assets/Locales/es_ES.json +++ b/src/Ryujinx/Assets/Locales/es_ES.json @@ -457,6 +457,7 @@ "DialogUpdaterExtractionMessage": "Extrayendo actualización...", "DialogUpdaterRenamingMessage": "Renombrando actualización...", "DialogUpdaterAddingFilesMessage": "Aplicando actualización...", + "DialogUpdaterShowChangelogMessage": "Show Changelog", "DialogUpdaterCompleteMessage": "¡Actualización completa!", "DialogUpdaterRestartMessage": "¿Quieres reiniciar Ryujinx?", "DialogUpdaterNoInternetMessage": "¡No estás conectado a internet!", diff --git a/src/Ryujinx/Assets/Locales/fr_FR.json b/src/Ryujinx/Assets/Locales/fr_FR.json index dd23bef768..f17a7ba952 100644 --- a/src/Ryujinx/Assets/Locales/fr_FR.json +++ b/src/Ryujinx/Assets/Locales/fr_FR.json @@ -457,6 +457,7 @@ "DialogUpdaterExtractionMessage": "Extraction de la mise à jour…", "DialogUpdaterRenamingMessage": "Renommage de la mise à jour...", "DialogUpdaterAddingFilesMessage": "Ajout d'une nouvelle mise à jour...", + "DialogUpdaterShowChangelogMessage": "Show Changelog", "DialogUpdaterCompleteMessage": "Mise à jour terminée !", "DialogUpdaterRestartMessage": "Voulez-vous redémarrer Ryujinx maintenant ?", "DialogUpdaterNoInternetMessage": "Vous n'êtes pas connecté à Internet !", diff --git a/src/Ryujinx/Assets/Locales/he_IL.json b/src/Ryujinx/Assets/Locales/he_IL.json index b9f89eb37b..f0cf4eb682 100644 --- a/src/Ryujinx/Assets/Locales/he_IL.json +++ b/src/Ryujinx/Assets/Locales/he_IL.json @@ -457,6 +457,7 @@ "DialogUpdaterExtractionMessage": "מחלץ עדכון...", "DialogUpdaterRenamingMessage": "משנה את שם העדכון...", "DialogUpdaterAddingFilesMessage": "מוסיף עדכון חדש...", + "DialogUpdaterShowChangelogMessage": "Show Changelog", "DialogUpdaterCompleteMessage": "העדכון הושלם!", "DialogUpdaterRestartMessage": "האם אתם רוצים להפעיל מחדש את ריוג'ינקס עכשיו?", "DialogUpdaterNoInternetMessage": "אתם לא מחוברים לאינטרנט!", diff --git a/src/Ryujinx/Assets/Locales/it_IT.json b/src/Ryujinx/Assets/Locales/it_IT.json index f10dd9d352..dd408bf5b8 100644 --- a/src/Ryujinx/Assets/Locales/it_IT.json +++ b/src/Ryujinx/Assets/Locales/it_IT.json @@ -457,6 +457,7 @@ "DialogUpdaterExtractionMessage": "Estrazione dell'aggiornamento...", "DialogUpdaterRenamingMessage": "Rinominazione dell'aggiornamento...", "DialogUpdaterAddingFilesMessage": "Aggiunta del nuovo aggiornamento...", + "DialogUpdaterShowChangelogMessage": "Show Changelog", "DialogUpdaterCompleteMessage": "Aggiornamento completato!", "DialogUpdaterRestartMessage": "Vuoi riavviare Ryujinx adesso?", "DialogUpdaterNoInternetMessage": "Non sei connesso ad Internet!", diff --git a/src/Ryujinx/Assets/Locales/ja_JP.json b/src/Ryujinx/Assets/Locales/ja_JP.json index 34253acbf8..2447304941 100644 --- a/src/Ryujinx/Assets/Locales/ja_JP.json +++ b/src/Ryujinx/Assets/Locales/ja_JP.json @@ -457,6 +457,7 @@ "DialogUpdaterExtractionMessage": "アップデートを展開中...", "DialogUpdaterRenamingMessage": "アップデートをリネーム中...", "DialogUpdaterAddingFilesMessage": "新規アップデートを追加中...", + "DialogUpdaterShowChangelogMessage": "Show Changelog", "DialogUpdaterCompleteMessage": "アップデート完了!", "DialogUpdaterRestartMessage": "すぐに Ryujinx を再起動しますか?", "DialogUpdaterNoInternetMessage": "インターネットに接続されていません!", diff --git a/src/Ryujinx/Assets/Locales/ko_KR.json b/src/Ryujinx/Assets/Locales/ko_KR.json index 5bda1565b0..47a619054b 100644 --- a/src/Ryujinx/Assets/Locales/ko_KR.json +++ b/src/Ryujinx/Assets/Locales/ko_KR.json @@ -457,6 +457,7 @@ "DialogUpdaterExtractionMessage": "업데이트 추출 중...", "DialogUpdaterRenamingMessage": "이름 변경 업데이트...", "DialogUpdaterAddingFilesMessage": "새 업데이트 추가 중...", + "DialogUpdaterShowChangelogMessage": "Show Changelog", "DialogUpdaterCompleteMessage": "업데이트가 완료되었습니다!", "DialogUpdaterRestartMessage": "지금 Ryujinx를 다시 시작하시겠습니까?", "DialogUpdaterNoInternetMessage": "인터넷에 연결되어 있지 않습니다!", diff --git a/src/Ryujinx/Assets/Locales/pl_PL.json b/src/Ryujinx/Assets/Locales/pl_PL.json index 015530833a..cfa9d7a76a 100644 --- a/src/Ryujinx/Assets/Locales/pl_PL.json +++ b/src/Ryujinx/Assets/Locales/pl_PL.json @@ -457,6 +457,7 @@ "DialogUpdaterExtractionMessage": "Wypakowywanie Aktualizacji...", "DialogUpdaterRenamingMessage": "Zmiana Nazwy Aktualizacji...", "DialogUpdaterAddingFilesMessage": "Dodawanie Nowej Aktualizacji...", + "DialogUpdaterShowChangelogMessage": "Show Changelog", "DialogUpdaterCompleteMessage": "Aktualizacja Zakończona!", "DialogUpdaterRestartMessage": "Czy chcesz teraz zrestartować Ryujinx?", "DialogUpdaterNoInternetMessage": "Nie masz połączenia z Internetem!", diff --git a/src/Ryujinx/Assets/Locales/pt_BR.json b/src/Ryujinx/Assets/Locales/pt_BR.json index 512581c0e8..352fae46ba 100644 --- a/src/Ryujinx/Assets/Locales/pt_BR.json +++ b/src/Ryujinx/Assets/Locales/pt_BR.json @@ -457,6 +457,7 @@ "DialogUpdaterExtractionMessage": "Extraindo atualização...", "DialogUpdaterRenamingMessage": "Renomeando atualização...", "DialogUpdaterAddingFilesMessage": "Adicionando nova atualização...", + "DialogUpdaterShowChangelogMessage": "Show Changelog", "DialogUpdaterCompleteMessage": "Atualização concluída!", "DialogUpdaterRestartMessage": "Deseja reiniciar o Ryujinx agora?", "DialogUpdaterNoInternetMessage": "Você não está conectado à Internet!", diff --git a/src/Ryujinx/Assets/Locales/ru_RU.json b/src/Ryujinx/Assets/Locales/ru_RU.json index 9d81116efd..112735e2d3 100644 --- a/src/Ryujinx/Assets/Locales/ru_RU.json +++ b/src/Ryujinx/Assets/Locales/ru_RU.json @@ -457,6 +457,7 @@ "DialogUpdaterExtractionMessage": "Извлечение обновления...", "DialogUpdaterRenamingMessage": "Переименование обновления...", "DialogUpdaterAddingFilesMessage": "Добавление нового обновления...", + "DialogUpdaterShowChangelogMessage": "Show Changelog", "DialogUpdaterCompleteMessage": "Обновление завершено", "DialogUpdaterRestartMessage": "Перезапустить Ryujinx?", "DialogUpdaterNoInternetMessage": "Вы не подключены к интернету", diff --git a/src/Ryujinx/Assets/Locales/th_TH.json b/src/Ryujinx/Assets/Locales/th_TH.json index fa59ba682a..35959ddbd9 100644 --- a/src/Ryujinx/Assets/Locales/th_TH.json +++ b/src/Ryujinx/Assets/Locales/th_TH.json @@ -457,6 +457,7 @@ "DialogUpdaterExtractionMessage": "กำลังแตกไฟล์อัปเดต...", "DialogUpdaterRenamingMessage": "กำลังลบไฟล์เก่า...", "DialogUpdaterAddingFilesMessage": "กำลังเพิ่มไฟล์อัปเดตใหม่...", + "DialogUpdaterShowChangelogMessage": "Show Changelog", "DialogUpdaterCompleteMessage": "อัปเดตเสร็จสมบูรณ์แล้ว!", "DialogUpdaterRestartMessage": "คุณต้องการรีสตาร์ท Ryujinx ตอนนี้หรือไม่?", "DialogUpdaterNoInternetMessage": "คุณไม่ได้เชื่อมต่อกับอินเทอร์เน็ต!", diff --git a/src/Ryujinx/Assets/Locales/tr_TR.json b/src/Ryujinx/Assets/Locales/tr_TR.json index 9b321c4233..5d50b67dbd 100644 --- a/src/Ryujinx/Assets/Locales/tr_TR.json +++ b/src/Ryujinx/Assets/Locales/tr_TR.json @@ -457,6 +457,7 @@ "DialogUpdaterExtractionMessage": "Güncelleme Ayıklanıyor...", "DialogUpdaterRenamingMessage": "Güncelleme Yeniden Adlandırılıyor...", "DialogUpdaterAddingFilesMessage": "Yeni Güncelleme Ekleniyor...", + "DialogUpdaterShowChangelogMessage": "Show Changelog", "DialogUpdaterCompleteMessage": "Güncelleme Tamamlandı!", "DialogUpdaterRestartMessage": "Ryujinx'i şimdi yeniden başlatmak istiyor musunuz?", "DialogUpdaterNoInternetMessage": "İnternete bağlı değilsiniz!", diff --git a/src/Ryujinx/Assets/Locales/uk_UA.json b/src/Ryujinx/Assets/Locales/uk_UA.json index 09a7e8cb40..a45208486d 100644 --- a/src/Ryujinx/Assets/Locales/uk_UA.json +++ b/src/Ryujinx/Assets/Locales/uk_UA.json @@ -457,6 +457,7 @@ "DialogUpdaterExtractionMessage": "Видобування оновлення...", "DialogUpdaterRenamingMessage": "Перейменування оновлення...", "DialogUpdaterAddingFilesMessage": "Додавання нового оновлення...", + "DialogUpdaterShowChangelogMessage": "Show Changelog", "DialogUpdaterCompleteMessage": "Оновлення завершено!", "DialogUpdaterRestartMessage": "Перезапустити Ryujinx зараз?", "DialogUpdaterNoInternetMessage": "Ви не підключені до Інтернету!", diff --git a/src/Ryujinx/Assets/Locales/zh_CN.json b/src/Ryujinx/Assets/Locales/zh_CN.json index 11840e864f..8a4995ea70 100644 --- a/src/Ryujinx/Assets/Locales/zh_CN.json +++ b/src/Ryujinx/Assets/Locales/zh_CN.json @@ -457,6 +457,7 @@ "DialogUpdaterExtractionMessage": "正在提取更新...", "DialogUpdaterRenamingMessage": "正在重命名更新...", "DialogUpdaterAddingFilesMessage": "安装更新中...", + "DialogUpdaterShowChangelogMessage": "Show Changelog", "DialogUpdaterCompleteMessage": "更新成功!", "DialogUpdaterRestartMessage": "是否立即重启 Ryujinx 模拟器?", "DialogUpdaterNoInternetMessage": "没有连接到网络", diff --git a/src/Ryujinx/Assets/Locales/zh_TW.json b/src/Ryujinx/Assets/Locales/zh_TW.json index d59df0e5bd..5649ba00aa 100644 --- a/src/Ryujinx/Assets/Locales/zh_TW.json +++ b/src/Ryujinx/Assets/Locales/zh_TW.json @@ -457,6 +457,7 @@ "DialogUpdaterExtractionMessage": "正在提取更新...", "DialogUpdaterRenamingMessage": "重新命名更新...", "DialogUpdaterAddingFilesMessage": "加入新更新...", + "DialogUpdaterShowChangelogMessage": "Show Changelog", "DialogUpdaterCompleteMessage": "更新成功!", "DialogUpdaterRestartMessage": "您現在要重新啟動 Ryujinx 嗎?", "DialogUpdaterNoInternetMessage": "您沒有連線到網際網路!", diff --git a/src/Ryujinx/UI/Helpers/ContentDialogHelper.cs b/src/Ryujinx/UI/Helpers/ContentDialogHelper.cs index a7fe3f0cef..3f0f0f0334 100644 --- a/src/Ryujinx/UI/Helpers/ContentDialogHelper.cs +++ b/src/Ryujinx/UI/Helpers/ContentDialogHelper.cs @@ -261,6 +261,16 @@ internal static async Task CreateUpdaterInfoDialog(string primary, string second string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], (int)Symbol.Important); + + internal static async Task CreateUpdaterUpToDateInfoDialog(string primary, string secondaryText) + => await ShowTextDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterTitle], + primary, + secondaryText, + LocaleManager.Instance[LocaleKeys.DialogUpdaterShowChangelogMessage], + string.Empty, + LocaleManager.Instance[LocaleKeys.InputDialogOk], + (int)Symbol.Important); internal static async Task CreateWarningDialog(string primary, string secondaryText) => await ShowTextDialog( @@ -309,6 +319,30 @@ internal static async Task CreateChoiceDialog(string title, string primary return response == UserResult.Yes; } + + internal static async Task CreateUpdaterChoiceDialog(string title, string primary, string secondaryText) + { + if (_isChoiceDialogOpen) + { + return UserResult.Cancel; + } + + _isChoiceDialogOpen = true; + + UserResult response = await ShowTextDialog( + title, + primary, + secondaryText, + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.DialogUpdaterShowChangelogMessage], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + (int)Symbol.Help, + UserResult.Yes); + + _isChoiceDialogOpen = false; + + return response; + } internal static async Task CreateExitDialog() { diff --git a/src/Ryujinx/Updater.cs b/src/Ryujinx/Updater.cs index 9deff5e862..5f3ddb119d 100644 --- a/src/Ryujinx/Updater.cs +++ b/src/Ryujinx/Updater.cs @@ -176,9 +176,14 @@ await ContentDialogHelper.CreateWarningDialog( { if (showVersionUpToDate) { - await ContentDialogHelper.CreateUpdaterInfoDialog( + UserResult userResult = await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog( LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], string.Empty); + + if (userResult is UserResult.Yes) + { + OpenHelper.OpenUrl(ReleaseInformation.GetChangelogForVersion(currentVersion)); + } } _running = false; @@ -206,19 +211,29 @@ await ContentDialogHelper.CreateUpdaterInfoDialog( await Dispatcher.UIThread.InvokeAsync(async () => { + string newVersionString = ReleaseInformation.IsCanaryBuild + ? $"Canary {currentVersion} -> Canary {newVersion}" + : $"{currentVersion} -> {newVersion}"; + + RequestUserToUpdate: // Show a message asking the user if they want to update - var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog( + UserResult shouldUpdate = await ContentDialogHelper.CreateUpdaterChoiceDialog( LocaleManager.Instance[LocaleKeys.RyujinxUpdater], LocaleManager.Instance[LocaleKeys.RyujinxUpdaterMessage], - $"{Program.Version} -> {newVersion}"); + newVersionString); - if (shouldUpdate) - { - await UpdateRyujinx(mainWindow, _buildUrl); - } - else + switch (shouldUpdate) { - _running = false; + case UserResult.Yes: + await UpdateRyujinx(mainWindow, _buildUrl); + 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; } }); } From 49eeb26b6f4fd9ab94a1168d1a77bdcee4617ef9 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Fri, 22 Nov 2024 14:46:10 -0600 Subject: [PATCH 09/61] UI: I may be stupid. Primary button result is Ok, not Yes. --- src/Ryujinx/Updater.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ryujinx/Updater.cs b/src/Ryujinx/Updater.cs index 5f3ddb119d..47acbc3432 100644 --- a/src/Ryujinx/Updater.cs +++ b/src/Ryujinx/Updater.cs @@ -180,7 +180,7 @@ await ContentDialogHelper.CreateWarningDialog( LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], string.Empty); - if (userResult is UserResult.Yes) + if (userResult is UserResult.Ok) { OpenHelper.OpenUrl(ReleaseInformation.GetChangelogForVersion(currentVersion)); } From e05875a079e1a31a8e5401932949c6cdb0196856 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Fri, 22 Nov 2024 14:52:56 -0600 Subject: [PATCH 10/61] UI: It's called "live testing." --- src/Ryujinx/Updater.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx/Updater.cs b/src/Ryujinx/Updater.cs index 47acbc3432..bdb44d668d 100644 --- a/src/Ryujinx/Updater.cs +++ b/src/Ryujinx/Updater.cs @@ -114,9 +114,14 @@ await ContentDialogHelper.CreateWarningDialog( { if (showVersionUpToDate) { - await ContentDialogHelper.CreateUpdaterInfoDialog( + UserResult userResult = await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog( LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], string.Empty); + + if (userResult is UserResult.Ok) + { + OpenHelper.OpenUrl(ReleaseInformation.GetChangelogForVersion(currentVersion)); + } } _running = false; @@ -133,9 +138,14 @@ await ContentDialogHelper.CreateUpdaterInfoDialog( { if (showVersionUpToDate) { - await ContentDialogHelper.CreateUpdaterInfoDialog( + UserResult userResult = await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog( LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], string.Empty); + + if (userResult is UserResult.Ok) + { + OpenHelper.OpenUrl(ReleaseInformation.GetChangelogForVersion(currentVersion)); + } } _running = false; From 55340011528aa8c05a826397ea41178cfc8de226 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Fri, 22 Nov 2024 15:08:24 -0600 Subject: [PATCH 11/61] UI: Always save screenshots to the Ryujinx data directory. --- src/Ryujinx/AppHost.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index 7246be4b91..d1398f194a 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -352,11 +352,7 @@ private void Renderer_ScreenCaptured(object sender, ScreenCaptureImageInfo e) string filename = $"{sanitizedApplicationName}_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png"; - string directory = AppDataManager.Mode switch - { - AppDataManager.LaunchMode.Portable or AppDataManager.LaunchMode.Custom => Path.Combine(AppDataManager.BaseDirPath, "screenshots"), - _ => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Ryujinx"), - }; + string directory = Path.Combine(AppDataManager.BaseDirPath, "screenshots"); string path = Path.Combine(directory, filename); From e653848a2cdbd6571a325bededd3535a5dc09de2 Mon Sep 17 00:00:00 2001 From: LotP1 <68976644+LotP1@users.noreply.github.com> Date: Fri, 22 Nov 2024 22:33:44 +0100 Subject: [PATCH 12/61] JIT Sparse Function Table (#250) More up to date build of the JIT Sparse PR for continued development. JIT Sparse Function Table was originally developed by riperiperi for the original Ryujinx project, and decreased the amount of layers in the Function Table structure, to decrease lookup times at the cost of slightly higher RAM usage. This PR rebalances the JIT Sparse Function Table to be a bit more RAM intensive, but faster in workloads where the JIT Function Table is a bottleneck. Faster RAM will see a bigger impact and slower RAM (DDR3 and potentially slow DDR4) will see a slight performance decrease. This PR also implements a base for a PPTC profile system that could allow for PPTC with ExeFS mods enabled in the future. This PR also potentially fixes a strange issue where Avalonia would time out in some rare instances, e.g. when running ExeFS mods with TotK and a strange controller configuration. --------- Co-authored-by: Evan Husted --- src/ARMeilleure/Common/AddressTable.cs | 252 --------- src/ARMeilleure/Common/AddressTableLevel.cs | 44 ++ src/ARMeilleure/Common/AddressTablePresets.cs | 75 +++ src/ARMeilleure/Common/Allocator.cs | 2 +- src/ARMeilleure/Common/IAddressTable.cs | 51 ++ src/ARMeilleure/Common/NativeAllocator.cs | 2 +- .../Instructions/InstEmitFlowHelper.cs | 26 + .../Signal/NativeSignalHandlerGenerator.cs | 2 +- .../Translation/ArmEmitterContext.cs | 4 +- src/ARMeilleure/Translation/PTC/Ptc.cs | 15 +- src/ARMeilleure/Translation/Translator.cs | 30 +- .../Translation/TranslatorStubs.cs | 4 +- src/Ryujinx.Cpu/AddressTable.cs | 482 ++++++++++++++++++ src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs | 2 +- src/Ryujinx.Cpu/ICpuContext.cs | 2 +- src/Ryujinx.Cpu/Jit/JitCpuContext.cs | 10 +- .../Arm32/Target/Arm64/InstEmitFlow.cs | 62 ++- .../Arm64/Target/Arm64/InstEmitSystem.cs | 62 ++- .../LightningJit/LightningJitCpuContext.cs | 11 +- src/Ryujinx.Cpu/LightningJit/Translator.cs | 23 +- .../LightningJit/TranslatorStubs.cs | 4 +- src/Ryujinx.HLE/HOS/ArmProcessContext.cs | 8 +- .../HOS/ArmProcessContextFactory.cs | 2 +- src/Ryujinx.Memory/SparseMemoryBlock.cs | 125 +++++ src/Ryujinx.Tests/Cpu/CpuContext.cs | 3 +- src/Ryujinx.Tests/Cpu/EnvironmentTests.cs | 7 +- src/Ryujinx.Tests/Memory/PartialUnmaps.cs | 7 +- 27 files changed, 990 insertions(+), 327 deletions(-) delete mode 100644 src/ARMeilleure/Common/AddressTable.cs create mode 100644 src/ARMeilleure/Common/AddressTableLevel.cs create mode 100644 src/ARMeilleure/Common/AddressTablePresets.cs create mode 100644 src/ARMeilleure/Common/IAddressTable.cs create mode 100644 src/Ryujinx.Cpu/AddressTable.cs create mode 100644 src/Ryujinx.Memory/SparseMemoryBlock.cs diff --git a/src/ARMeilleure/Common/AddressTable.cs b/src/ARMeilleure/Common/AddressTable.cs deleted file mode 100644 index a3ffaf470e..0000000000 --- a/src/ARMeilleure/Common/AddressTable.cs +++ /dev/null @@ -1,252 +0,0 @@ -using ARMeilleure.Diagnostics; -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; - -namespace ARMeilleure.Common -{ - ///

- /// Represents a table of guest address to a value. - /// - /// Type of the value - public unsafe class AddressTable : IDisposable where TEntry : unmanaged - { - /// - /// Represents a level in an . - /// - public readonly struct Level - { - /// - /// Gets the index of the in the guest address. - /// - public int Index { get; } - - /// - /// Gets the length of the in the guest address. - /// - public int Length { get; } - - /// - /// Gets the mask which masks the bits used by the . - /// - public ulong Mask => ((1ul << Length) - 1) << Index; - - /// - /// Initializes a new instance of the structure with the specified - /// and . - /// - /// Index of the - /// Length of the - public Level(int index, int length) - { - (Index, Length) = (index, length); - } - - /// - /// Gets the value of the from the specified guest . - /// - /// Guest address - /// Value of the from the specified guest - public int GetValue(ulong address) - { - return (int)((address & Mask) >> Index); - } - } - - private bool _disposed; - private TEntry** _table; - private readonly List _pages; - - /// - /// Gets the bits used by the of the instance. - /// - public ulong Mask { get; } - - /// - /// Gets the s used by the instance. - /// - public Level[] Levels { get; } - - /// - /// Gets or sets the default fill value of newly created leaf pages. - /// - public TEntry Fill { get; set; } - - /// - /// Gets the base address of the . - /// - /// instance was disposed - public nint Base - { - get - { - ObjectDisposedException.ThrowIf(_disposed, this); - - lock (_pages) - { - return (nint)GetRootPage(); - } - } - } - - /// - /// Constructs a new instance of the class with the specified list of - /// . - /// - /// is null - /// Length of is less than 2 - public AddressTable(Level[] levels) - { - ArgumentNullException.ThrowIfNull(levels); - - if (levels.Length < 2) - { - throw new ArgumentException("Table must be at least 2 levels deep.", nameof(levels)); - } - - _pages = new List(capacity: 16); - - Levels = levels; - Mask = 0; - - foreach (var level in Levels) - { - Mask |= level.Mask; - } - } - - /// - /// Determines if the specified is in the range of the - /// . - /// - /// Guest address - /// if is valid; otherwise - public bool IsValid(ulong address) - { - return (address & ~Mask) == 0; - } - - /// - /// Gets a reference to the value at the specified guest . - /// - /// Guest address - /// Reference to the value at the specified guest - /// instance was disposed - /// is not mapped - public ref TEntry GetValue(ulong address) - { - ObjectDisposedException.ThrowIf(_disposed, this); - - if (!IsValid(address)) - { - throw new ArgumentException($"Address 0x{address:X} is not mapped onto the table.", nameof(address)); - } - - lock (_pages) - { - return ref GetPage(address)[Levels[^1].GetValue(address)]; - } - } - - /// - /// Gets the leaf page for the specified guest . - /// - /// Guest address - /// Leaf page for the specified guest - private TEntry* GetPage(ulong address) - { - TEntry** page = GetRootPage(); - - for (int i = 0; i < Levels.Length - 1; i++) - { - ref Level level = ref Levels[i]; - ref TEntry* nextPage = ref page[level.GetValue(address)]; - - if (nextPage == null) - { - ref Level nextLevel = ref Levels[i + 1]; - - nextPage = i == Levels.Length - 2 ? - (TEntry*)Allocate(1 << nextLevel.Length, Fill, leaf: true) : - (TEntry*)Allocate(1 << nextLevel.Length, nint.Zero, leaf: false); - } - - page = (TEntry**)nextPage; - } - - return (TEntry*)page; - } - - /// - /// Lazily initialize and get the root page of the . - /// - /// Root page of the - private TEntry** GetRootPage() - { - if (_table == null) - { - _table = (TEntry**)Allocate(1 << Levels[0].Length, fill: nint.Zero, leaf: false); - } - - return _table; - } - - /// - /// Allocates a block of memory of the specified type and length. - /// - /// Type of elements - /// Number of elements - /// Fill value - /// if leaf; otherwise - /// Allocated block - private nint Allocate(int length, T fill, bool leaf) where T : unmanaged - { - var size = sizeof(T) * length; - var page = (nint)NativeAllocator.Instance.Allocate((uint)size); - var span = new Span((void*)page, length); - - span.Fill(fill); - - _pages.Add(page); - - TranslatorEventSource.Log.AddressTableAllocated(size, leaf); - - return page; - } - - /// - /// Releases all resources used by the instance. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases all unmanaged and optionally managed resources used by the - /// instance. - /// - /// to dispose managed resources also; otherwise just unmanaged resouces - protected virtual void Dispose(bool disposing) - { - if (!_disposed) - { - foreach (var page in _pages) - { - Marshal.FreeHGlobal(page); - } - - _disposed = true; - } - } - - /// - /// Frees resources used by the instance. - /// - ~AddressTable() - { - Dispose(false); - } - } -} diff --git a/src/ARMeilleure/Common/AddressTableLevel.cs b/src/ARMeilleure/Common/AddressTableLevel.cs new file mode 100644 index 0000000000..6107726eef --- /dev/null +++ b/src/ARMeilleure/Common/AddressTableLevel.cs @@ -0,0 +1,44 @@ +namespace ARMeilleure.Common +{ + /// + /// Represents a level in an . + /// + public readonly struct AddressTableLevel + { + /// + /// Gets the index of the in the guest address. + /// + public int Index { get; } + + /// + /// Gets the length of the in the guest address. + /// + public int Length { get; } + + /// + /// Gets the mask which masks the bits used by the . + /// + public ulong Mask => ((1ul << Length) - 1) << Index; + + /// + /// Initializes a new instance of the structure with the specified + /// and . + /// + /// Index of the + /// Length of the + public AddressTableLevel(int index, int length) + { + (Index, Length) = (index, length); + } + + /// + /// Gets the value of the from the specified guest . + /// + /// Guest address + /// Value of the from the specified guest + public int GetValue(ulong address) + { + return (int)((address & Mask) >> Index); + } + } +} diff --git a/src/ARMeilleure/Common/AddressTablePresets.cs b/src/ARMeilleure/Common/AddressTablePresets.cs new file mode 100644 index 0000000000..977e84a363 --- /dev/null +++ b/src/ARMeilleure/Common/AddressTablePresets.cs @@ -0,0 +1,75 @@ +namespace ARMeilleure.Common +{ + public static class AddressTablePresets + { + private static readonly AddressTableLevel[] _levels64Bit = + new AddressTableLevel[] + { + new(31, 17), + new(23, 8), + new(15, 8), + new( 7, 8), + new( 2, 5), + }; + + private static readonly AddressTableLevel[] _levels32Bit = + new AddressTableLevel[] + { + new(31, 17), + new(23, 8), + new(15, 8), + new( 7, 8), + new( 1, 6), + }; + + private static readonly AddressTableLevel[] _levels64BitSparseTiny = + new AddressTableLevel[] + { + new( 11, 28), + new( 2, 9), + }; + + private static readonly AddressTableLevel[] _levels32BitSparseTiny = + new AddressTableLevel[] + { + new( 10, 22), + new( 1, 9), + }; + + private static readonly AddressTableLevel[] _levels64BitSparseGiant = + new AddressTableLevel[] + { + new( 38, 1), + new( 2, 36), + }; + + private static readonly AddressTableLevel[] _levels32BitSparseGiant = + new AddressTableLevel[] + { + new( 31, 1), + new( 1, 30), + }; + + //high power will run worse on DDR3 systems and some DDR4 systems due to the higher ram utilization + //low power will never run worse than non-sparse, but for most systems it won't be necessary + //high power is always used, but I've left low power in here for future reference + public static AddressTableLevel[] GetArmPreset(bool for64Bits, bool sparse, bool lowPower = false) + { + if (sparse) + { + if (lowPower) + { + return for64Bits ? _levels64BitSparseTiny : _levels32BitSparseTiny; + } + else + { + return for64Bits ? _levels64BitSparseGiant : _levels32BitSparseGiant; + } + } + else + { + return for64Bits ? _levels64Bit : _levels32Bit; + } + } + } +} diff --git a/src/ARMeilleure/Common/Allocator.cs b/src/ARMeilleure/Common/Allocator.cs index 6905a614f0..de6a77ebef 100644 --- a/src/ARMeilleure/Common/Allocator.cs +++ b/src/ARMeilleure/Common/Allocator.cs @@ -2,7 +2,7 @@ namespace ARMeilleure.Common { - unsafe abstract class Allocator : IDisposable + public unsafe abstract class Allocator : IDisposable { public T* Allocate(ulong count = 1) where T : unmanaged { diff --git a/src/ARMeilleure/Common/IAddressTable.cs b/src/ARMeilleure/Common/IAddressTable.cs new file mode 100644 index 0000000000..65077ec436 --- /dev/null +++ b/src/ARMeilleure/Common/IAddressTable.cs @@ -0,0 +1,51 @@ +using System; + +namespace ARMeilleure.Common +{ + public interface IAddressTable : IDisposable where TEntry : unmanaged + { + /// + /// True if the address table's bottom level is sparsely mapped. + /// This also ensures the second bottom level is filled with a dummy page rather than 0. + /// + bool Sparse { get; } + + /// + /// Gets the bits used by the of the instance. + /// + ulong Mask { get; } + + /// + /// Gets the s used by the instance. + /// + AddressTableLevel[] Levels { get; } + + /// + /// Gets or sets the default fill value of newly created leaf pages. + /// + TEntry Fill { get; set; } + + /// + /// Gets the base address of the . + /// + /// instance was disposed + nint Base { get; } + + /// + /// Determines if the specified is in the range of the + /// . + /// + /// Guest address + /// if is valid; otherwise + bool IsValid(ulong address); + + /// + /// Gets a reference to the value at the specified guest . + /// + /// Guest address + /// Reference to the value at the specified guest + /// instance was disposed + /// is not mapped + ref TEntry GetValue(ulong address); + } +} diff --git a/src/ARMeilleure/Common/NativeAllocator.cs b/src/ARMeilleure/Common/NativeAllocator.cs index ca5d3a850f..ffcffa4bc6 100644 --- a/src/ARMeilleure/Common/NativeAllocator.cs +++ b/src/ARMeilleure/Common/NativeAllocator.cs @@ -3,7 +3,7 @@ namespace ARMeilleure.Common { - unsafe sealed class NativeAllocator : Allocator + public unsafe sealed class NativeAllocator : Allocator { public static NativeAllocator Instance { get; } = new(); diff --git a/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs b/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs index 2009bafdac..a602ea49ed 100644 --- a/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs +++ b/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs @@ -193,6 +193,8 @@ private static void EmitTableBranch(ArmEmitterContext context, Operand guestAddr Operand hostAddress; + var table = context.FunctionTable; + // If address is mapped onto the function table, we can skip the table walk. Otherwise we fallback // onto the dispatch stub. if (guestAddress.Kind == OperandKind.Constant && context.FunctionTable.IsValid(guestAddress.Value)) @@ -203,6 +205,30 @@ private static void EmitTableBranch(ArmEmitterContext context, Operand guestAddr hostAddress = context.Load(OperandType.I64, hostAddressAddr); } + else if (table.Sparse) + { + // Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels. + // Deliberately attempts to avoid branches. + + Operand tableBase = !context.HasPtc ? + Const(table.Base) : + Const(table.Base, Ptc.FunctionTableSymbol); + + hostAddress = tableBase; + + for (int i = 0; i < table.Levels.Length; i++) + { + var level = table.Levels[i]; + int clearBits = 64 - (level.Index + level.Length); + + Operand index = context.ShiftLeft( + context.ShiftRightUI(context.ShiftLeft(guestAddress, Const(clearBits)), Const(clearBits + level.Index)), + Const(3) + ); + + hostAddress = context.Load(OperandType.I64, context.Add(hostAddress, index)); + } + } else { hostAddress = !context.HasPtc ? diff --git a/src/ARMeilleure/Signal/NativeSignalHandlerGenerator.cs b/src/ARMeilleure/Signal/NativeSignalHandlerGenerator.cs index 1b3689e3f4..35747d7a4d 100644 --- a/src/ARMeilleure/Signal/NativeSignalHandlerGenerator.cs +++ b/src/ARMeilleure/Signal/NativeSignalHandlerGenerator.cs @@ -8,7 +8,7 @@ namespace ARMeilleure.Signal { public static class NativeSignalHandlerGenerator { - public const int MaxTrackedRanges = 8; + public const int MaxTrackedRanges = 16; private const int StructAddressOffset = 0; private const int StructWriteOffset = 4; diff --git a/src/ARMeilleure/Translation/ArmEmitterContext.cs b/src/ARMeilleure/Translation/ArmEmitterContext.cs index 5d79171a27..82f12bb027 100644 --- a/src/ARMeilleure/Translation/ArmEmitterContext.cs +++ b/src/ARMeilleure/Translation/ArmEmitterContext.cs @@ -46,7 +46,7 @@ public Block CurrBlock public IMemoryManager Memory { get; } public EntryTable CountTable { get; } - public AddressTable FunctionTable { get; } + public IAddressTable FunctionTable { get; } public TranslatorStubs Stubs { get; } public ulong EntryAddress { get; } @@ -62,7 +62,7 @@ public Block CurrBlock public ArmEmitterContext( IMemoryManager memory, EntryTable countTable, - AddressTable funcTable, + IAddressTable funcTable, TranslatorStubs stubs, ulong entryAddress, bool highCq, diff --git a/src/ARMeilleure/Translation/PTC/Ptc.cs b/src/ARMeilleure/Translation/PTC/Ptc.cs index 8236150fe9..c722ce6be2 100644 --- a/src/ARMeilleure/Translation/PTC/Ptc.cs +++ b/src/ARMeilleure/Translation/PTC/Ptc.cs @@ -30,7 +30,7 @@ class Ptc : IPtcLoadState private const string OuterHeaderMagicString = "PTCohd\0\0"; private const string InnerHeaderMagicString = "PTCihd\0\0"; - private const uint InternalVersion = 6950; //! To be incremented manually for each change to the ARMeilleure project. + private const uint InternalVersion = 6992; //! To be incremented manually for each change to the ARMeilleure project. private const string ActualDir = "0"; private const string BackupDir = "1"; @@ -41,6 +41,7 @@ class Ptc : IPtcLoadState public static readonly Symbol PageTableSymbol = new(SymbolType.Special, 1); public static readonly Symbol CountTableSymbol = new(SymbolType.Special, 2); public static readonly Symbol DispatchStubSymbol = new(SymbolType.Special, 3); + public static readonly Symbol FunctionTableSymbol = new(SymbolType.Special, 4); private const byte FillingByte = 0x00; private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest; @@ -101,7 +102,7 @@ public Ptc() Disable(); } - public void Initialize(string titleIdText, string displayVersion, bool enabled, MemoryManagerType memoryMode) + public void Initialize(string titleIdText, string displayVersion, bool enabled, MemoryManagerType memoryMode, string cacheSelector) { Wait(); @@ -127,6 +128,8 @@ public void Initialize(string titleIdText, string displayVersion, bool enabled, DisplayVersion = !string.IsNullOrEmpty(displayVersion) ? displayVersion : DisplayVersionDefault; _memoryMode = memoryMode; + Logger.Info?.Print(LogClass.Ptc, $"PPTC (v{InternalVersion}) Profile: {DisplayVersion}-{cacheSelector}"); + string workPathActual = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", ActualDir); string workPathBackup = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", BackupDir); @@ -140,8 +143,8 @@ public void Initialize(string titleIdText, string displayVersion, bool enabled, Directory.CreateDirectory(workPathBackup); } - CachePathActual = Path.Combine(workPathActual, DisplayVersion); - CachePathBackup = Path.Combine(workPathBackup, DisplayVersion); + CachePathActual = Path.Combine(workPathActual, DisplayVersion) + "-" + cacheSelector; + CachePathBackup = Path.Combine(workPathBackup, DisplayVersion) + "-" + cacheSelector; PreLoad(); Profiler.PreLoad(); @@ -706,6 +709,10 @@ private static void PatchCode(Translator translator, Span code, RelocEntry { imm = translator.Stubs.DispatchStub; } + else if (symbol == FunctionTableSymbol) + { + imm = translator.FunctionTable.Base; + } if (imm == null) { diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs index 24fbd76219..162368782f 100644 --- a/src/ARMeilleure/Translation/Translator.cs +++ b/src/ARMeilleure/Translation/Translator.cs @@ -22,33 +22,13 @@ namespace ARMeilleure.Translation { public class Translator { - private static readonly AddressTable.Level[] _levels64Bit = - new AddressTable.Level[] - { - new(31, 17), - new(23, 8), - new(15, 8), - new( 7, 8), - new( 2, 5), - }; - - private static readonly AddressTable.Level[] _levels32Bit = - new AddressTable.Level[] - { - new(31, 17), - new(23, 8), - new(15, 8), - new( 7, 8), - new( 1, 6), - }; - private readonly IJitMemoryAllocator _allocator; private readonly ConcurrentQueue> _oldFuncs; private readonly Ptc _ptc; internal TranslatorCache Functions { get; } - internal AddressTable FunctionTable { get; } + internal IAddressTable FunctionTable { get; } internal EntryTable CountTable { get; } internal TranslatorStubs Stubs { get; } internal TranslatorQueue Queue { get; } @@ -57,7 +37,7 @@ public class Translator private Thread[] _backgroundTranslationThreads; private volatile int _threadCount; - public Translator(IJitMemoryAllocator allocator, IMemoryManager memory, bool for64Bits) + public Translator(IJitMemoryAllocator allocator, IMemoryManager memory, IAddressTable functionTable) { _allocator = allocator; Memory = memory; @@ -72,15 +52,15 @@ public Translator(IJitMemoryAllocator allocator, IMemoryManager memory, bool for CountTable = new EntryTable(); Functions = new TranslatorCache(); - FunctionTable = new AddressTable(for64Bits ? _levels64Bit : _levels32Bit); + FunctionTable = functionTable; Stubs = new TranslatorStubs(FunctionTable); FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub; } - public IPtcLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled) + public IPtcLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector) { - _ptc.Initialize(titleIdText, displayVersion, enabled, Memory.Type); + _ptc.Initialize(titleIdText, displayVersion, enabled, Memory.Type, cacheSelector); return _ptc; } diff --git a/src/ARMeilleure/Translation/TranslatorStubs.cs b/src/ARMeilleure/Translation/TranslatorStubs.cs index 364cca13c5..bd9aed8d45 100644 --- a/src/ARMeilleure/Translation/TranslatorStubs.cs +++ b/src/ARMeilleure/Translation/TranslatorStubs.cs @@ -19,7 +19,7 @@ class TranslatorStubs : IDisposable private bool _disposed; - private readonly AddressTable _functionTable; + private readonly IAddressTable _functionTable; private readonly Lazy _dispatchStub; private readonly Lazy _dispatchLoop; private readonly Lazy _contextWrapper; @@ -86,7 +86,7 @@ public WrapperFunction ContextWrapper /// /// Function table used to store pointers to the functions that the guest code will call /// is null - public TranslatorStubs(AddressTable functionTable) + public TranslatorStubs(IAddressTable functionTable) { ArgumentNullException.ThrowIfNull(functionTable); diff --git a/src/Ryujinx.Cpu/AddressTable.cs b/src/Ryujinx.Cpu/AddressTable.cs new file mode 100644 index 0000000000..d87b12ab01 --- /dev/null +++ b/src/Ryujinx.Cpu/AddressTable.cs @@ -0,0 +1,482 @@ +using ARMeilleure.Memory; +using Ryujinx.Common; +using Ryujinx.Cpu.Signal; +using Ryujinx.Memory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using static Ryujinx.Cpu.MemoryEhMeilleure; + +namespace ARMeilleure.Common +{ + /// + /// Represents a table of guest address to a value. + /// + /// Type of the value + public unsafe class AddressTable : IAddressTable where TEntry : unmanaged + { + /// + /// Represents a page of the address table. + /// + private readonly struct AddressTablePage + { + /// + /// True if the allocation belongs to a sparse block, false otherwise. + /// + public readonly bool IsSparse; + + /// + /// Base address for the page. + /// + public readonly IntPtr Address; + + public AddressTablePage(bool isSparse, IntPtr address) + { + IsSparse = isSparse; + Address = address; + } + } + + /// + /// A sparsely mapped block of memory with a signal handler to map pages as they're accessed. + /// + private readonly struct TableSparseBlock : IDisposable + { + public readonly SparseMemoryBlock Block; + private readonly TrackingEventDelegate _trackingEvent; + + public TableSparseBlock(ulong size, Action ensureMapped, PageInitDelegate pageInit) + { + var block = new SparseMemoryBlock(size, pageInit, null); + + _trackingEvent = (ulong address, ulong size, bool write) => + { + ulong pointer = (ulong)block.Block.Pointer + address; + ensureMapped((IntPtr)pointer); + return pointer; + }; + + bool added = NativeSignalHandler.AddTrackedRegion( + (nuint)block.Block.Pointer, + (nuint)(block.Block.Pointer + (IntPtr)block.Block.Size), + Marshal.GetFunctionPointerForDelegate(_trackingEvent)); + + if (!added) + { + throw new InvalidOperationException("Number of allowed tracked regions exceeded."); + } + + Block = block; + } + + public void Dispose() + { + NativeSignalHandler.RemoveTrackedRegion((nuint)Block.Block.Pointer); + + Block.Dispose(); + } + } + + private bool _disposed; + private TEntry** _table; + private readonly List _pages; + private TEntry _fill; + + private readonly MemoryBlock _sparseFill; + private readonly SparseMemoryBlock _fillBottomLevel; + private readonly TEntry* _fillBottomLevelPtr; + + private readonly List _sparseReserved; + private readonly ReaderWriterLockSlim _sparseLock; + + private ulong _sparseBlockSize; + private ulong _sparseReservedOffset; + + public bool Sparse { get; } + + /// + public ulong Mask { get; } + + /// + public AddressTableLevel[] Levels { get; } + + /// + public TEntry Fill + { + get + { + return _fill; + } + set + { + UpdateFill(value); + } + } + + /// + public IntPtr Base + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_pages) + { + return (IntPtr)GetRootPage(); + } + } + } + + /// + /// Constructs a new instance of the class with the specified list of + /// . + /// + /// Levels for the address table + /// True if the bottom page should be sparsely mapped + /// is null + /// Length of is less than 2 + public AddressTable(AddressTableLevel[] levels, bool sparse) + { + ArgumentNullException.ThrowIfNull(levels); + + _pages = new List(capacity: 16); + + Levels = levels; + Mask = 0; + + foreach (var level in Levels) + { + Mask |= level.Mask; + } + + Sparse = sparse; + + if (sparse) + { + // If the address table is sparse, allocate a fill block + + _sparseFill = new MemoryBlock(268435456ul, MemoryAllocationFlags.Mirrorable); //low Power TC uses size: 65536ul + + ulong bottomLevelSize = (1ul << levels.Last().Length) * (ulong)sizeof(TEntry); + + _fillBottomLevel = new SparseMemoryBlock(bottomLevelSize, null, _sparseFill); + _fillBottomLevelPtr = (TEntry*)_fillBottomLevel.Block.Pointer; + + _sparseReserved = new List(); + _sparseLock = new ReaderWriterLockSlim(); + + _sparseBlockSize = bottomLevelSize; + } + } + + /// + /// Create an instance for an ARM function table. + /// Selects the best table structure for A32/A64, taking into account the selected memory manager type. + /// + /// True if the guest is A64, false otherwise + /// Memory manager type + /// An for ARM function lookup + public static AddressTable CreateForArm(bool for64Bits, MemoryManagerType type) + { + // Assume software memory means that we don't want to use any signal handlers. + bool sparse = type != MemoryManagerType.SoftwareMmu && type != MemoryManagerType.SoftwarePageTable; + + return new AddressTable(AddressTablePresets.GetArmPreset(for64Bits, sparse), sparse); + } + + /// + /// Update the fill value for the bottom level of the table. + /// + /// New fill value + private void UpdateFill(TEntry fillValue) + { + if (_sparseFill != null) + { + Span span = _sparseFill.GetSpan(0, (int)_sparseFill.Size); + MemoryMarshal.Cast(span).Fill(fillValue); + } + + _fill = fillValue; + } + + /// + /// Signal that the given code range exists. + /// + /// + /// + public void SignalCodeRange(ulong address, ulong size) + { + AddressTableLevel bottom = Levels.Last(); + ulong bottomLevelEntries = 1ul << bottom.Length; + + ulong entryIndex = address >> bottom.Index; + ulong entries = size >> bottom.Index; + entries += entryIndex - BitUtils.AlignDown(entryIndex, bottomLevelEntries); + + _sparseBlockSize = Math.Max(_sparseBlockSize, BitUtils.AlignUp(entries, bottomLevelEntries) * (ulong)sizeof(TEntry)); + } + + /// + public bool IsValid(ulong address) + { + return (address & ~Mask) == 0; + } + + /// + public ref TEntry GetValue(ulong address) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + if (!IsValid(address)) + { + throw new ArgumentException($"Address 0x{address:X} is not mapped onto the table.", nameof(address)); + } + + lock (_pages) + { + TEntry* page = GetPage(address); + + int index = Levels[^1].GetValue(address); + + EnsureMapped((IntPtr)(page + index)); + + return ref page[index]; + } + } + + /// + /// Gets the leaf page for the specified guest . + /// + /// Guest address + /// Leaf page for the specified guest + private TEntry* GetPage(ulong address) + { + TEntry** page = GetRootPage(); + + for (int i = 0; i < Levels.Length - 1; i++) + { + ref AddressTableLevel level = ref Levels[i]; + ref TEntry* nextPage = ref page[level.GetValue(address)]; + + if (nextPage == null || nextPage == _fillBottomLevelPtr) + { + ref AddressTableLevel nextLevel = ref Levels[i + 1]; + + if (i == Levels.Length - 2) + { + nextPage = (TEntry*)Allocate(1 << nextLevel.Length, Fill, leaf: true); + } + else + { + nextPage = (TEntry*)Allocate(1 << nextLevel.Length, GetFillValue(i), leaf: false); + } + } + + page = (TEntry**)nextPage; + } + + return (TEntry*)page; + } + + /// + /// Ensure the given pointer is mapped in any overlapping sparse reservations. + /// + /// Pointer to be mapped + private void EnsureMapped(IntPtr ptr) + { + if (Sparse) + { + // Check sparse allocations to see if the pointer is in any of them. + // Ensure the page is committed if there's a match. + + _sparseLock.EnterReadLock(); + + try + { + foreach (TableSparseBlock reserved in _sparseReserved) + { + SparseMemoryBlock sparse = reserved.Block; + + if (ptr >= sparse.Block.Pointer && ptr < sparse.Block.Pointer + (IntPtr)sparse.Block.Size) + { + sparse.EnsureMapped((ulong)(ptr - sparse.Block.Pointer)); + + break; + } + } + } + finally + { + _sparseLock.ExitReadLock(); + } + } + } + + /// + /// Get the fill value for a non-leaf level of the table. + /// + /// Level to get the fill value for + /// The fill value + private IntPtr GetFillValue(int level) + { + if (_fillBottomLevel != null && level == Levels.Length - 2) + { + return (IntPtr)_fillBottomLevelPtr; + } + else + { + return IntPtr.Zero; + } + } + + /// + /// Lazily initialize and get the root page of the . + /// + /// Root page of the + private TEntry** GetRootPage() + { + if (_table == null) + { + if (Levels.Length == 1) + _table = (TEntry**)Allocate(1 << Levels[0].Length, Fill, leaf: true); + else + _table = (TEntry**)Allocate(1 << Levels[0].Length, GetFillValue(0), leaf: false); + } + + return _table; + } + + /// + /// Initialize a leaf page with the fill value. + /// + /// Page to initialize + private void InitLeafPage(Span page) + { + MemoryMarshal.Cast(page).Fill(_fill); + } + + /// + /// Reserve a new sparse block, and add it to the list. + /// + /// The new sparse block that was added + private TableSparseBlock ReserveNewSparseBlock() + { + var block = new TableSparseBlock(_sparseBlockSize, EnsureMapped, InitLeafPage); + + _sparseReserved.Add(block); + _sparseReservedOffset = 0; + + return block; + } + + /// + /// Allocates a block of memory of the specified type and length. + /// + /// Type of elements + /// Number of elements + /// Fill value + /// if leaf; otherwise + /// Allocated block + private IntPtr Allocate(int length, T fill, bool leaf) where T : unmanaged + { + var size = sizeof(T) * length; + + AddressTablePage page; + + if (Sparse && leaf) + { + _sparseLock.EnterWriteLock(); + + SparseMemoryBlock block; + + if (_sparseReserved.Count == 0) + { + block = ReserveNewSparseBlock().Block; + } + else + { + block = _sparseReserved.Last().Block; + + if (_sparseReservedOffset == block.Block.Size) + { + block = ReserveNewSparseBlock().Block; + } + } + + page = new AddressTablePage(true, block.Block.Pointer + (IntPtr)_sparseReservedOffset); + + _sparseReservedOffset += (ulong)size; + + _sparseLock.ExitWriteLock(); + } + else + { + var address = (IntPtr)NativeAllocator.Instance.Allocate((uint)size); + page = new AddressTablePage(false, address); + + var span = new Span((void*)page.Address, length); + span.Fill(fill); + } + + _pages.Add(page); + + //TranslatorEventSource.Log.AddressTableAllocated(size, leaf); + + return page.Address; + } + + /// + /// Releases all resources used by the instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases all unmanaged and optionally managed resources used by the + /// instance. + /// + /// to dispose managed resources also; otherwise just unmanaged resouces + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + foreach (var page in _pages) + { + if (!page.IsSparse) + { + Marshal.FreeHGlobal(page.Address); + } + } + + if (Sparse) + { + foreach (TableSparseBlock block in _sparseReserved) + { + block.Dispose(); + } + + _sparseReserved.Clear(); + + _fillBottomLevel.Dispose(); + _sparseFill.Dispose(); + _sparseLock.Dispose(); + } + + _disposed = true; + } + } + + /// + /// Frees resources used by the instance. + /// + ~AddressTable() + { + Dispose(false); + } + } +} diff --git a/src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs b/src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs index 99e4c0479d..784949441c 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs @@ -32,7 +32,7 @@ public void InvalidateCacheRegion(ulong address, ulong size) { } - public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled) + public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector) { return new DummyDiskCacheLoadState(); } diff --git a/src/Ryujinx.Cpu/ICpuContext.cs b/src/Ryujinx.Cpu/ICpuContext.cs index edcebdfc4a..1fb3b674db 100644 --- a/src/Ryujinx.Cpu/ICpuContext.cs +++ b/src/Ryujinx.Cpu/ICpuContext.cs @@ -48,7 +48,7 @@ public interface ICpuContext : IDisposable /// Version of the application /// True if the cache should be loaded from disk if it exists, false otherwise /// Disk cache load progress reporter and manager - IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled); + IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector); /// /// Indicates that code has been loaded into guest memory, and that it might be executed in the future. diff --git a/src/Ryujinx.Cpu/Jit/JitCpuContext.cs b/src/Ryujinx.Cpu/Jit/JitCpuContext.cs index 9893c59b29..0793f382d2 100644 --- a/src/Ryujinx.Cpu/Jit/JitCpuContext.cs +++ b/src/Ryujinx.Cpu/Jit/JitCpuContext.cs @@ -1,3 +1,4 @@ +using ARMeilleure.Common; using ARMeilleure.Memory; using ARMeilleure.Translation; using Ryujinx.Cpu.Signal; @@ -9,11 +10,13 @@ class JitCpuContext : ICpuContext { private readonly ITickSource _tickSource; private readonly Translator _translator; + private readonly AddressTable _functionTable; public JitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit) { _tickSource = tickSource; - _translator = new Translator(new JitMemoryAllocator(forJit: true), memory, for64Bit); + _functionTable = AddressTable.CreateForArm(for64Bit, memory.Type); + _translator = new Translator(new JitMemoryAllocator(forJit: true), memory, _functionTable); if (memory.Type.IsHostMappedOrTracked()) { @@ -47,14 +50,15 @@ public void InvalidateCacheRegion(ulong address, ulong size) } /// - public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled) + public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector) { - return new JitDiskCacheLoadState(_translator.LoadDiskCache(titleIdText, displayVersion, enabled)); + return new JitDiskCacheLoadState(_translator.LoadDiskCache(titleIdText, displayVersion, enabled, cacheSelector)); } /// public void PrepareCodeRange(ulong address, ulong size) { + _functionTable.SignalCodeRange(address, size); _translator.PrepareCodeRange(address, size); } diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs index 7f5e4835c8..48bdbb573f 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs @@ -140,6 +140,10 @@ public unsafe static void WriteCallWithGuestAddress( bool isTail = false) { int tempRegister; + int tempGuestAddress = -1; + + bool inlineLookup = guestAddress.Kind != OperandKind.Constant && + funcTable is { Sparse: true }; if (guestAddress.Kind == OperandKind.Constant) { @@ -153,9 +157,16 @@ public unsafe static void WriteCallWithGuestAddress( else { asm.StrRiUn(guestAddress, Register(regAlloc.FixedContextRegister), NativeContextOffsets.DispatchAddressOffset); + + if (inlineLookup && guestAddress.Value == 0) + { + // X0 will be overwritten. Move the address to a temp register. + tempGuestAddress = regAlloc.AllocateTempGprRegister(); + asm.Mov(Register(tempGuestAddress), guestAddress); + } } - tempRegister = regAlloc.FixedContextRegister == 1 ? 2 : 1; + tempRegister = NextFreeRegister(1, tempGuestAddress); if (!isTail) { @@ -176,6 +187,40 @@ public unsafe static void WriteCallWithGuestAddress( asm.Mov(rn, funcPtrLoc & ~0xfffUL); asm.LdrRiUn(rn, rn, (int)(funcPtrLoc & 0xfffUL)); } + else if (inlineLookup) + { + // Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels. + + Operand indexReg = Register(NextFreeRegister(tempRegister + 1, tempGuestAddress)); + + if (tempGuestAddress != -1) + { + guestAddress = Register(tempGuestAddress); + } + + ulong tableBase = (ulong)funcTable.Base; + + // Index into the table. + asm.Mov(rn, tableBase); + + for (int i = 0; i < funcTable.Levels.Length; i++) + { + var level = funcTable.Levels[i]; + asm.Ubfx(indexReg, guestAddress, level.Index, level.Length); + asm.Lsl(indexReg, indexReg, Const(3)); + + // Index into the page. + asm.Add(rn, rn, indexReg); + + // Load the page address. + asm.LdrRiUn(rn, rn, 0); + } + + if (tempGuestAddress != -1) + { + regAlloc.FreeTempGprRegister(tempGuestAddress); + } + } else { asm.Mov(rn, (ulong)funcPtr); @@ -252,5 +297,20 @@ private static Operand Register(int register, OperandType type = OperandType.I64 { return new Operand(register, RegisterType.Integer, type); } + + private static Operand Const(long value, OperandType type = OperandType.I64) + { + return new Operand(type, (ulong)value); + } + + private static int NextFreeRegister(int start, int avoid) + { + if (start == avoid) + { + start++; + } + + return start; + } } } diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs index 1eeeb746e1..f534e8b6e7 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs @@ -305,6 +305,10 @@ public unsafe static void WriteCallWithGuestAddress( bool isTail = false) { int tempRegister; + int tempGuestAddress = -1; + + bool inlineLookup = guestAddress.Kind != OperandKind.Constant && + funcTable is { Sparse: true }; if (guestAddress.Kind == OperandKind.Constant) { @@ -318,9 +322,16 @@ public unsafe static void WriteCallWithGuestAddress( else { asm.StrRiUn(guestAddress, Register(regAlloc.FixedContextRegister), NativeContextOffsets.DispatchAddressOffset); + + if (inlineLookup && guestAddress.Value == 0) + { + // X0 will be overwritten. Move the address to a temp register. + tempGuestAddress = regAlloc.AllocateTempGprRegister(); + asm.Mov(Register(tempGuestAddress), guestAddress); + } } - tempRegister = regAlloc.FixedContextRegister == 1 ? 2 : 1; + tempRegister = NextFreeRegister(1, tempGuestAddress); if (!isTail) { @@ -341,6 +352,40 @@ public unsafe static void WriteCallWithGuestAddress( asm.Mov(rn, funcPtrLoc & ~0xfffUL); asm.LdrRiUn(rn, rn, (int)(funcPtrLoc & 0xfffUL)); } + else if (inlineLookup) + { + // Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels. + + Operand indexReg = Register(NextFreeRegister(tempRegister + 1, tempGuestAddress)); + + if (tempGuestAddress != -1) + { + guestAddress = Register(tempGuestAddress); + } + + ulong tableBase = (ulong)funcTable.Base; + + // Index into the table. + asm.Mov(rn, tableBase); + + for (int i = 0; i < funcTable.Levels.Length; i++) + { + var level = funcTable.Levels[i]; + asm.Ubfx(indexReg, guestAddress, level.Index, level.Length); + asm.Lsl(indexReg, indexReg, Const(3)); + + // Index into the page. + asm.Add(rn, rn, indexReg); + + // Load the page address. + asm.LdrRiUn(rn, rn, 0); + } + + if (tempGuestAddress != -1) + { + regAlloc.FreeTempGprRegister(tempGuestAddress); + } + } else { asm.Mov(rn, (ulong)funcPtr); @@ -613,5 +658,20 @@ private static Operand Register(int register, OperandType type = OperandType.I64 { return new Operand(register, RegisterType.Integer, type); } + + private static Operand Const(long value, OperandType type = OperandType.I64) + { + return new Operand(type, (ulong)value); + } + + private static int NextFreeRegister(int start, int avoid) + { + if (start == avoid) + { + start++; + } + + return start; + } } } diff --git a/src/Ryujinx.Cpu/LightningJit/LightningJitCpuContext.cs b/src/Ryujinx.Cpu/LightningJit/LightningJitCpuContext.cs index b63636e39a..0f47ffb154 100644 --- a/src/Ryujinx.Cpu/LightningJit/LightningJitCpuContext.cs +++ b/src/Ryujinx.Cpu/LightningJit/LightningJitCpuContext.cs @@ -1,3 +1,4 @@ +using ARMeilleure.Common; using ARMeilleure.Memory; using Ryujinx.Cpu.Jit; using Ryujinx.Cpu.LightningJit.State; @@ -8,11 +9,16 @@ class LightningJitCpuContext : ICpuContext { private readonly ITickSource _tickSource; private readonly Translator _translator; + private readonly AddressTable _functionTable; public LightningJitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit) { _tickSource = tickSource; - _translator = new Translator(memory, for64Bit); + + _functionTable = AddressTable.CreateForArm(for64Bit, memory.Type); + + _translator = new Translator(memory, _functionTable); + memory.UnmapEvent += UnmapHandler; } @@ -40,7 +46,7 @@ public void InvalidateCacheRegion(ulong address, ulong size) } /// - public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled) + public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector) { return new DummyDiskCacheLoadState(); } @@ -48,6 +54,7 @@ public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersi /// public void PrepareCodeRange(ulong address, ulong size) { + _functionTable.SignalCodeRange(address, size); } public void Dispose() diff --git a/src/Ryujinx.Cpu/LightningJit/Translator.cs b/src/Ryujinx.Cpu/LightningJit/Translator.cs index b4710e34e1..4c4011f113 100644 --- a/src/Ryujinx.Cpu/LightningJit/Translator.cs +++ b/src/Ryujinx.Cpu/LightningJit/Translator.cs @@ -19,25 +19,6 @@ class Translator : IDisposable // Should be enabled on platforms that enforce W^X. private static bool IsNoWxPlatform => false; - private static readonly AddressTable.Level[] _levels64Bit = - new AddressTable.Level[] - { - new(31, 17), - new(23, 8), - new(15, 8), - new( 7, 8), - new( 2, 5), - }; - - private static readonly AddressTable.Level[] _levels32Bit = - new AddressTable.Level[] - { - new(23, 9), - new(15, 8), - new( 7, 8), - new( 1, 6), - }; - private readonly ConcurrentQueue> _oldFuncs; private readonly NoWxCache _noWxCache; private bool _disposed; @@ -47,7 +28,7 @@ class Translator : IDisposable internal TranslatorStubs Stubs { get; } internal IMemoryManager Memory { get; } - public Translator(IMemoryManager memory, bool for64Bits) + public Translator(IMemoryManager memory, AddressTable functionTable) { Memory = memory; @@ -63,7 +44,7 @@ public Translator(IMemoryManager memory, bool for64Bits) } Functions = new TranslatorCache(); - FunctionTable = new AddressTable(for64Bits ? _levels64Bit : _levels32Bit); + FunctionTable = functionTable; Stubs = new TranslatorStubs(FunctionTable, _noWxCache); FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub; diff --git a/src/Ryujinx.Cpu/LightningJit/TranslatorStubs.cs b/src/Ryujinx.Cpu/LightningJit/TranslatorStubs.cs index e88414d5e4..c5231e506b 100644 --- a/src/Ryujinx.Cpu/LightningJit/TranslatorStubs.cs +++ b/src/Ryujinx.Cpu/LightningJit/TranslatorStubs.cs @@ -23,7 +23,7 @@ class TranslatorStubs : IDisposable private bool _disposed; - private readonly AddressTable _functionTable; + private readonly IAddressTable _functionTable; private readonly NoWxCache _noWxCache; private readonly GetFunctionAddressDelegate _getFunctionAddressRef; private readonly nint _getFunctionAddress; @@ -79,7 +79,7 @@ public DispatcherFunction DispatchLoop /// Function table used to store pointers to the functions that the guest code will call /// Cache used on platforms that enforce W^X, otherwise should be null /// is null - public TranslatorStubs(AddressTable functionTable, NoWxCache noWxCache) + public TranslatorStubs(IAddressTable functionTable, NoWxCache noWxCache) { ArgumentNullException.ThrowIfNull(functionTable); diff --git a/src/Ryujinx.HLE/HOS/ArmProcessContext.cs b/src/Ryujinx.HLE/HOS/ArmProcessContext.cs index fde489ab7d..09a7216442 100644 --- a/src/Ryujinx.HLE/HOS/ArmProcessContext.cs +++ b/src/Ryujinx.HLE/HOS/ArmProcessContext.cs @@ -13,7 +13,8 @@ IDiskCacheLoadState Initialize( string displayVersion, bool diskCacheEnabled, ulong codeAddress, - ulong codeSize); + ulong codeSize, + string cacheSelector); } class ArmProcessContext : IArmProcessContext where T : class, IVirtualMemoryManagerTracked, IMemoryManager @@ -67,10 +68,11 @@ public IDiskCacheLoadState Initialize( string displayVersion, bool diskCacheEnabled, ulong codeAddress, - ulong codeSize) + ulong codeSize, + string cacheSelector) { _cpuContext.PrepareCodeRange(codeAddress, codeSize); - return _cpuContext.LoadDiskCache(titleIdText, displayVersion, diskCacheEnabled); + return _cpuContext.LoadDiskCache(titleIdText, displayVersion, diskCacheEnabled, cacheSelector); } public void InvalidateCacheRegion(ulong address, ulong size) diff --git a/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs index 6646826cb0..14775fb1d6 100644 --- a/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs +++ b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs @@ -114,7 +114,7 @@ public IProcessContext Create(KernelContext context, ulong pid, ulong addressSpa } } - DiskCacheLoadState = processContext.Initialize(_titleIdText, _displayVersion, _diskCacheEnabled, _codeAddress, _codeSize); + DiskCacheLoadState = processContext.Initialize(_titleIdText, _displayVersion, _diskCacheEnabled, _codeAddress, _codeSize, "default"); //Ready for exefs profiles return processContext; } diff --git a/src/Ryujinx.Memory/SparseMemoryBlock.cs b/src/Ryujinx.Memory/SparseMemoryBlock.cs new file mode 100644 index 0000000000..523685de13 --- /dev/null +++ b/src/Ryujinx.Memory/SparseMemoryBlock.cs @@ -0,0 +1,125 @@ +using Ryujinx.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Ryujinx.Memory +{ + public delegate void PageInitDelegate(Span page); + + public class SparseMemoryBlock : IDisposable + { + private const ulong MapGranularity = 1UL << 17; + + private readonly PageInitDelegate _pageInit; + + private readonly object _lock = new object(); + private readonly ulong _pageSize; + private readonly MemoryBlock _reservedBlock; + private readonly List _mappedBlocks; + private ulong _mappedBlockUsage; + + private readonly ulong[] _mappedPageBitmap; + + public MemoryBlock Block => _reservedBlock; + + public SparseMemoryBlock(ulong size, PageInitDelegate pageInit, MemoryBlock fill) + { + _pageSize = MemoryBlock.GetPageSize(); + _reservedBlock = new MemoryBlock(size, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible); + _mappedBlocks = new List(); + _pageInit = pageInit; + + int pages = (int)BitUtils.DivRoundUp(size, _pageSize); + int bitmapEntries = BitUtils.DivRoundUp(pages, 64); + _mappedPageBitmap = new ulong[bitmapEntries]; + + if (fill != null) + { + // Fill the block with mappings from the fill block. + + if (fill.Size % _pageSize != 0) + { + throw new ArgumentException("Fill memory block should be page aligned.", nameof(fill)); + } + + int repeats = (int)BitUtils.DivRoundUp(size, fill.Size); + + ulong offset = 0; + for (int i = 0; i < repeats; i++) + { + _reservedBlock.MapView(fill, 0, offset, Math.Min(fill.Size, size - offset)); + offset += fill.Size; + } + } + + // If a fill block isn't provided, the pages that aren't EnsureMapped are unmapped. + // The caller can rely on signal handler to fill empty pages instead. + } + + private void MapPage(ulong pageOffset) + { + // Take a page from the latest mapped block. + MemoryBlock block = _mappedBlocks.LastOrDefault(); + + if (block == null || _mappedBlockUsage == MapGranularity) + { + // Need to map some more memory. + + block = new MemoryBlock(MapGranularity, MemoryAllocationFlags.Mirrorable); + + _mappedBlocks.Add(block); + + _mappedBlockUsage = 0; + } + + _pageInit(block.GetSpan(_mappedBlockUsage, (int)_pageSize)); + _reservedBlock.MapView(block, _mappedBlockUsage, pageOffset, _pageSize); + + _mappedBlockUsage += _pageSize; + } + + public void EnsureMapped(ulong offset) + { + int pageIndex = (int)(offset / _pageSize); + int bitmapIndex = pageIndex >> 6; + + ref ulong entry = ref _mappedPageBitmap[bitmapIndex]; + ulong bit = 1UL << (pageIndex & 63); + + if ((Volatile.Read(ref entry) & bit) == 0) + { + // Not mapped. + + lock (_lock) + { + // Check the bit while locked to make sure that this only happens once. + + ulong lockedEntry = Volatile.Read(ref entry); + + if ((lockedEntry & bit) == 0) + { + MapPage(offset & ~(_pageSize - 1)); + + lockedEntry |= bit; + + Interlocked.Exchange(ref entry, lockedEntry); + } + } + } + } + + public void Dispose() + { + _reservedBlock.Dispose(); + + foreach (MemoryBlock block in _mappedBlocks) + { + block.Dispose(); + } + + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuContext.cs b/src/Ryujinx.Tests/Cpu/CpuContext.cs index 96b4965a21..81e8ba8c91 100644 --- a/src/Ryujinx.Tests/Cpu/CpuContext.cs +++ b/src/Ryujinx.Tests/Cpu/CpuContext.cs @@ -1,3 +1,4 @@ +using ARMeilleure.Common; using ARMeilleure.Memory; using ARMeilleure.State; using ARMeilleure.Translation; @@ -12,7 +13,7 @@ public class CpuContext public CpuContext(IMemoryManager memory, bool for64Bit) { - _translator = new Translator(new JitMemoryAllocator(), memory, for64Bit); + _translator = new Translator(new JitMemoryAllocator(), memory, AddressTable.CreateForArm(for64Bit, memory.Type)); memory.UnmapEvent += UnmapHandler; } diff --git a/src/Ryujinx.Tests/Cpu/EnvironmentTests.cs b/src/Ryujinx.Tests/Cpu/EnvironmentTests.cs index 2a4775a319..43c84c1935 100644 --- a/src/Ryujinx.Tests/Cpu/EnvironmentTests.cs +++ b/src/Ryujinx.Tests/Cpu/EnvironmentTests.cs @@ -1,3 +1,5 @@ +using ARMeilleure.Common; +using ARMeilleure.Memory; using ARMeilleure.Translation; using NUnit.Framework; using Ryujinx.Cpu.Jit; @@ -17,7 +19,10 @@ internal class EnvironmentTests private static void EnsureTranslator() { // Create a translator, as one is needed to register the signal handler or emit methods. - _translator ??= new Translator(new JitMemoryAllocator(), new MockMemoryManager(), true); + _translator ??= new Translator( + new JitMemoryAllocator(), + new MockMemoryManager(), + AddressTable.CreateForArm(true, MemoryManagerType.SoftwarePageTable)); } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] diff --git a/src/Ryujinx.Tests/Memory/PartialUnmaps.cs b/src/Ryujinx.Tests/Memory/PartialUnmaps.cs index 6d2ad8fb01..3e5b474238 100644 --- a/src/Ryujinx.Tests/Memory/PartialUnmaps.cs +++ b/src/Ryujinx.Tests/Memory/PartialUnmaps.cs @@ -1,3 +1,5 @@ +using ARMeilleure.Common; +using ARMeilleure.Memory; using ARMeilleure.Signal; using ARMeilleure.Translation; using NUnit.Framework; @@ -53,7 +55,10 @@ private static int CountThreads(ref PartialUnmapState state) private static void EnsureTranslator() { // Create a translator, as one is needed to register the signal handler or emit methods. - _translator ??= new Translator(new JitMemoryAllocator(), new MockMemoryManager(), true); + _translator ??= new Translator( + new JitMemoryAllocator(), + new MockMemoryManager(), + AddressTable.CreateForArm(true, MemoryManagerType.SoftwarePageTable)); } [Test] From 3b6731a3519f408a361b7355f368583fbcc76107 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Fri, 22 Nov 2024 17:51:42 -0600 Subject: [PATCH 13/61] infra: Undo packing native libraries into executable. --- .github/workflows/canary.yml | 4 ++-- .github/workflows/release.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index a24436de3f..df28e47848 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -103,8 +103,8 @@ jobs: - name: Publish run: | - dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_ava/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 -p:IncludeNativeLibrariesForSelfExtract=true - dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained -p:IncludeNativeLibrariesForSelfExtract=true + dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_ava/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 + dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained - name: Packing Windows builds if: matrix.platform.os == 'windows-latest' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ec02976a11..fbf7157563 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -102,8 +102,8 @@ jobs: - 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 -p:IncludeNativeLibrariesForSelfExtract=true - dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained -p:IncludeNativeLibrariesForSelfExtract=true + 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 + dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained - name: Packing Windows builds if: matrix.platform.os == 'windows-latest' From e8d3ad4d8b0d8ad195db32a04c97b7a253f98c1c Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Sat, 23 Nov 2024 13:10:53 -0600 Subject: [PATCH 14/61] UI: RPC: TSUKIHIME -A piece of blue glass moon- asset image --- src/Ryujinx.UI.Common/DiscordIntegrationModule.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs b/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs index a26f6a7b2d..295a663b26 100644 --- a/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs +++ b/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs @@ -264,6 +264,7 @@ public static void Exit() "0100800015926000", // Suika Game "0100e46006708000", // Terraria "01000a10041ea000", // The Elder Scrolls V: Skyrim + "010057a01e4d4000", // TSUKIHIME -A piece of blue glass moon- "010080b00ad66000", // Undertale ]; } From a81212bbf12418c0862384104f191b396732b2c7 Mon Sep 17 00:00:00 2001 From: Daniel Zauner Date: Sun, 24 Nov 2024 16:49:44 +0100 Subject: [PATCH 15/61] Fix window decorations being too wide (#309) --- src/Ryujinx/Assets/Styles/Styles.xaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx/Assets/Styles/Styles.xaml b/src/Ryujinx/Assets/Styles/Styles.xaml index 05212a7ddf..878b5e7f15 100644 --- a/src/Ryujinx/Assets/Styles/Styles.xaml +++ b/src/Ryujinx/Assets/Styles/Styles.xaml @@ -1,7 +1,8 @@  + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia"> @@ -231,7 +232,7 @@ - From 7e16fccfc108c92aef1015fb6b37d956f9526947 Mon Sep 17 00:00:00 2001 From: GabCoolGuy Date: Sun, 24 Nov 2024 18:33:53 +0100 Subject: [PATCH 16/61] UI: Fix icons getting cutoff in the About window (#310) Before: ![image](https://github.com/user-attachments/assets/c8d6b7d5-487b-4ab9-83e3-9489eaa0a076) After: ![image](https://github.com/user-attachments/assets/18ea6360-f6ee-48e6-9a0a-cd8d88a0cf51) --- src/Ryujinx/UI/Windows/AboutWindow.axaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx/UI/Windows/AboutWindow.axaml b/src/Ryujinx/UI/Windows/AboutWindow.axaml index 6d4a7b7e30..1d0e36ae98 100644 --- a/src/Ryujinx/UI/Windows/AboutWindow.axaml +++ b/src/Ryujinx/UI/Windows/AboutWindow.axaml @@ -6,8 +6,10 @@ xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:viewModel="clr-namespace:Ryujinx.Ava.UI.ViewModels" - Width="550" - Height="260" + MinWidth="550" + MinHeight="260" + MaxWidth="600" + MaxHeight="500" Margin="0,-12,0,0" d:DesignHeight="260" d:DesignWidth="550" From 2e6794e69b33405990aff15e7dbe5a0f589666bf Mon Sep 17 00:00:00 2001 From: Keaton Date: Mon, 25 Nov 2024 13:39:09 -0600 Subject: [PATCH 17/61] Add custom refresh rate mode to VSync option (#238) Rebased @jcm93's refreshinterval branch: https://github.com/jcm93/Ryujinx/tree/refreshinterval The option is placed under System/Hacks. Disabled, it's the default Ryujinx behavior. Enabled, the behavior is shown in the attached screenshots. If a framerate is too high or low, you can adjust the value where you normally toggle VSync on and off. It will also cycle through the default on/off toggles. Also, in order to reduce clutter, I made an adjustment to remove the target FPS and only show the percentage. --------- Co-authored-by: jcm <6864788+jcm93@users.noreply.github.com> --- .../Configuration/Hid/KeyboardHotkeys.cs | 4 +- src/Ryujinx.Common/Configuration/VSyncMode.cs | 9 ++ src/Ryujinx.Graphics.GAL/IWindow.cs | 2 +- .../Multithreading/ThreadedWindow.cs | 2 +- src/Ryujinx.Graphics.GAL/VSyncMode.cs | 9 ++ src/Ryujinx.Graphics.OpenGL/Window.cs | 2 +- src/Ryujinx.Graphics.Vulkan/Window.cs | 13 +- src/Ryujinx.Graphics.Vulkan/WindowBase.cs | 2 +- src/Ryujinx.HLE/HLEConfiguration.cs | 18 ++- .../Services/SurfaceFlinger/SurfaceFlinger.cs | 20 ++- src/Ryujinx.HLE/Switch.cs | 38 +++++- src/Ryujinx.Headless.SDL2/Options.cs | 7 +- src/Ryujinx.Headless.SDL2/Program.cs | 5 +- .../StatusUpdatedEventArgs.cs | 4 +- src/Ryujinx.Headless.SDL2/WindowBase.cs | 2 +- .../Configuration/ConfigurationFileFormat.cs | 20 ++- .../ConfigurationState.Migration.cs | 45 +++++-- .../Configuration/ConfigurationState.Model.cs | 24 +++- .../Configuration/ConfigurationState.cs | 10 +- src/Ryujinx/AppHost.cs | 105 +++++++++++++-- src/Ryujinx/Assets/Locales/en_US.json | 20 ++- src/Ryujinx/Assets/Styles/Themes.xaml | 5 +- src/Ryujinx/Common/KeyboardHotkeyState.cs | 4 +- src/Ryujinx/UI/Models/Input/HotkeyConfig.cs | 38 +++++- src/Ryujinx/UI/Models/SaveModel.cs | 2 +- .../UI/Models/StatusUpdatedEventArgs.cs | 7 +- .../UI/ViewModels/MainWindowViewModel.cs | 123 ++++++++++++++++-- .../UI/ViewModels/SettingsViewModel.cs | 82 +++++++++++- .../UI/Views/Main/MainStatusBarView.axaml | 54 +++++++- .../UI/Views/Main/MainStatusBarView.axaml.cs | 7 +- .../Views/Settings/SettingsHotkeysView.axaml | 20 ++- .../Settings/SettingsHotkeysView.axaml.cs | 10 +- .../Views/Settings/SettingsSystemView.axaml | 71 +++++++++- src/Ryujinx/UI/Windows/MainWindow.axaml.cs | 4 +- 34 files changed, 678 insertions(+), 110 deletions(-) create mode 100644 src/Ryujinx.Common/Configuration/VSyncMode.cs create mode 100644 src/Ryujinx.Graphics.GAL/VSyncMode.cs diff --git a/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs b/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs index 0cb49ca8ce..6b8152b9db 100644 --- a/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs +++ b/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs @@ -2,7 +2,7 @@ namespace Ryujinx.Common.Configuration.Hid { public class KeyboardHotkeys { - public Key ToggleVsync { get; set; } + public Key ToggleVSyncMode { get; set; } public Key Screenshot { get; set; } public Key ShowUI { get; set; } public Key Pause { get; set; } @@ -11,5 +11,7 @@ public class KeyboardHotkeys public Key ResScaleDown { get; set; } public Key VolumeUp { get; set; } public Key VolumeDown { get; set; } + public Key CustomVSyncIntervalIncrement { get; set; } + public Key CustomVSyncIntervalDecrement { get; set; } } } diff --git a/src/Ryujinx.Common/Configuration/VSyncMode.cs b/src/Ryujinx.Common/Configuration/VSyncMode.cs new file mode 100644 index 0000000000..ca93b5e1c2 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/VSyncMode.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Common.Configuration +{ + public enum VSyncMode + { + Switch, + Unbounded, + Custom + } +} diff --git a/src/Ryujinx.Graphics.GAL/IWindow.cs b/src/Ryujinx.Graphics.GAL/IWindow.cs index 83418e7090..12686cb281 100644 --- a/src/Ryujinx.Graphics.GAL/IWindow.cs +++ b/src/Ryujinx.Graphics.GAL/IWindow.cs @@ -8,7 +8,7 @@ public interface IWindow void SetSize(int width, int height); - void ChangeVSyncMode(bool vsyncEnabled); + void ChangeVSyncMode(VSyncMode vSyncMode); void SetAntiAliasing(AntiAliasing antialiasing); void SetScalingFilter(ScalingFilter type); diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs index acda37ef36..102fdb1bb3 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs @@ -31,7 +31,7 @@ public void SetSize(int width, int height) _impl.Window.SetSize(width, height); } - public void ChangeVSyncMode(bool vsyncEnabled) { } + public void ChangeVSyncMode(VSyncMode vSyncMode) { } public void SetAntiAliasing(AntiAliasing effect) { } diff --git a/src/Ryujinx.Graphics.GAL/VSyncMode.cs b/src/Ryujinx.Graphics.GAL/VSyncMode.cs new file mode 100644 index 0000000000..c5794b8f77 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/VSyncMode.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum VSyncMode + { + Switch, + Unbounded, + Custom + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Window.cs b/src/Ryujinx.Graphics.OpenGL/Window.cs index 285ab725e2..1dc8a51f60 100644 --- a/src/Ryujinx.Graphics.OpenGL/Window.cs +++ b/src/Ryujinx.Graphics.OpenGL/Window.cs @@ -54,7 +54,7 @@ public void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback GL.PixelStore(PixelStoreParameter.UnpackAlignment, 4); } - public void ChangeVSyncMode(bool vsyncEnabled) { } + public void ChangeVSyncMode(VSyncMode vSyncMode) { } public void SetSize(int width, int height) { diff --git a/src/Ryujinx.Graphics.Vulkan/Window.cs b/src/Ryujinx.Graphics.Vulkan/Window.cs index 3dc6d4e191..3e8d3b375a 100644 --- a/src/Ryujinx.Graphics.Vulkan/Window.cs +++ b/src/Ryujinx.Graphics.Vulkan/Window.cs @@ -29,7 +29,7 @@ class Window : WindowBase, IDisposable private int _width; private int _height; - private bool _vsyncEnabled; + private VSyncMode _vSyncMode; private bool _swapchainIsDirty; private VkFormat _format; private AntiAliasing _currentAntiAliasing; @@ -139,7 +139,7 @@ private unsafe void CreateSwapchain() ImageArrayLayers = 1, PreTransform = capabilities.CurrentTransform, CompositeAlpha = ChooseCompositeAlpha(capabilities.SupportedCompositeAlpha), - PresentMode = ChooseSwapPresentMode(presentModes, _vsyncEnabled), + PresentMode = ChooseSwapPresentMode(presentModes, _vSyncMode), Clipped = true, }; @@ -279,9 +279,9 @@ private static CompositeAlphaFlagsKHR ChooseCompositeAlpha(CompositeAlphaFlagsKH } } - private static PresentModeKHR ChooseSwapPresentMode(PresentModeKHR[] availablePresentModes, bool vsyncEnabled) + private static PresentModeKHR ChooseSwapPresentMode(PresentModeKHR[] availablePresentModes, VSyncMode vSyncMode) { - if (!vsyncEnabled && availablePresentModes.Contains(PresentModeKHR.ImmediateKhr)) + if (vSyncMode == VSyncMode.Unbounded && availablePresentModes.Contains(PresentModeKHR.ImmediateKhr)) { return PresentModeKHR.ImmediateKhr; } @@ -634,9 +634,10 @@ public override void SetSize(int width, int height) _swapchainIsDirty = true; } - public override void ChangeVSyncMode(bool vsyncEnabled) + public override void ChangeVSyncMode(VSyncMode vSyncMode) { - _vsyncEnabled = vsyncEnabled; + _vSyncMode = vSyncMode; + //present mode may change, so mark the swapchain for recreation _swapchainIsDirty = true; } diff --git a/src/Ryujinx.Graphics.Vulkan/WindowBase.cs b/src/Ryujinx.Graphics.Vulkan/WindowBase.cs index edb9c688c9..ca06ec0b86 100644 --- a/src/Ryujinx.Graphics.Vulkan/WindowBase.cs +++ b/src/Ryujinx.Graphics.Vulkan/WindowBase.cs @@ -10,7 +10,7 @@ internal abstract class WindowBase : IWindow public abstract void Dispose(); public abstract void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback); public abstract void SetSize(int width, int height); - public abstract void ChangeVSyncMode(bool vsyncEnabled); + public abstract void ChangeVSyncMode(VSyncMode vSyncMode); public abstract void SetAntiAliasing(AntiAliasing effect); public abstract void SetScalingFilter(ScalingFilter scalerType); public abstract void SetScalingFilterLevel(float scale); diff --git a/src/Ryujinx.HLE/HLEConfiguration.cs b/src/Ryujinx.HLE/HLEConfiguration.cs index 70fcf278db..f75ead5886 100644 --- a/src/Ryujinx.HLE/HLEConfiguration.cs +++ b/src/Ryujinx.HLE/HLEConfiguration.cs @@ -9,6 +9,7 @@ using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.UI; using System; +using VSyncMode = Ryujinx.Common.Configuration.VSyncMode; namespace Ryujinx.HLE { @@ -84,9 +85,14 @@ public class HLEConfiguration internal readonly RegionCode Region; /// - /// Control the initial state of the vertical sync in the SurfaceFlinger service. + /// Control the initial state of the present interval in the SurfaceFlinger service (previously Vsync). /// - internal readonly bool EnableVsync; + internal readonly VSyncMode VSyncMode; + + /// + /// Control the custom VSync interval, if enabled and active. + /// + internal readonly int CustomVSyncInterval; /// /// Control the initial state of the docked mode. @@ -195,7 +201,7 @@ public HLEConfiguration(VirtualFileSystem virtualFileSystem, IHostUIHandler hostUIHandler, SystemLanguage systemLanguage, RegionCode region, - bool enableVsync, + VSyncMode vSyncMode, bool enableDockedMode, bool enablePtc, bool enableInternetAccess, @@ -212,7 +218,8 @@ public HLEConfiguration(VirtualFileSystem virtualFileSystem, MultiplayerMode multiplayerMode, bool multiplayerDisableP2p, string multiplayerLdnPassphrase, - string multiplayerLdnServer) + string multiplayerLdnServer, + int customVSyncInterval) { VirtualFileSystem = virtualFileSystem; LibHacHorizonManager = libHacHorizonManager; @@ -225,7 +232,8 @@ public HLEConfiguration(VirtualFileSystem virtualFileSystem, HostUIHandler = hostUIHandler; SystemLanguage = systemLanguage; Region = region; - EnableVsync = enableVsync; + VSyncMode = vSyncMode; + CustomVSyncInterval = customVSyncInterval; EnableDockedMode = enableDockedMode; EnablePtc = enablePtc; EnableInternetAccess = enableInternetAccess; diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs index 4c17e7aedc..601e858674 100644 --- a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs @@ -10,13 +10,12 @@ using System.Diagnostics; using System.Linq; using System.Threading; +using VSyncMode = Ryujinx.Common.Configuration.VSyncMode; namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger { class SurfaceFlinger : IConsumerListener, IDisposable { - private const int TargetFps = 60; - private readonly Switch _device; private readonly Dictionary _layers; @@ -32,6 +31,9 @@ class SurfaceFlinger : IConsumerListener, IDisposable private readonly long _spinTicks; private readonly long _1msTicks; + private VSyncMode _vSyncMode; + private long _targetVSyncInterval; + private int _swapInterval; private int _swapIntervalDelay; @@ -88,7 +90,8 @@ private void UpdateSwapInterval(int swapInterval) } else { - _ticksPerFrame = Stopwatch.Frequency / TargetFps; + _ticksPerFrame = Stopwatch.Frequency / _device.TargetVSyncInterval; + _targetVSyncInterval = _device.TargetVSyncInterval; } } @@ -370,15 +373,20 @@ public void Compose() if (acquireStatus == Status.Success) { - // If device vsync is disabled, reflect the change. - if (!_device.EnableDeviceVsync) + if (_device.VSyncMode == VSyncMode.Unbounded) { if (_swapInterval != 0) { UpdateSwapInterval(0); + _vSyncMode = _device.VSyncMode; } } - else if (item.SwapInterval != _swapInterval) + else if (_device.VSyncMode != _vSyncMode) + { + UpdateSwapInterval(_device.VSyncMode == VSyncMode.Unbounded ? 0 : item.SwapInterval); + _vSyncMode = _device.VSyncMode; + } + else if (item.SwapInterval != _swapInterval || _device.TargetVSyncInterval != _targetVSyncInterval) { UpdateSwapInterval(item.SwapInterval); } diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs index d12cb8f777..4663521527 100644 --- a/src/Ryujinx.HLE/Switch.cs +++ b/src/Ryujinx.HLE/Switch.cs @@ -27,7 +27,11 @@ public class Switch : IDisposable public TamperMachine TamperMachine { get; } public IHostUIHandler UIHandler { get; } - public bool EnableDeviceVsync { get; set; } + public VSyncMode VSyncMode { get; set; } = VSyncMode.Switch; + public bool CustomVSyncIntervalEnabled { get; set; } = false; + public int CustomVSyncInterval { get; set; } + + public long TargetVSyncInterval { get; set; } = 60; public bool IsFrameAvailable => Gpu.Window.IsFrameAvailable; @@ -59,12 +63,14 @@ public Switch(HLEConfiguration configuration) System.State.SetLanguage(Configuration.SystemLanguage); System.State.SetRegion(Configuration.Region); - EnableDeviceVsync = Configuration.EnableVsync; + VSyncMode = Configuration.VSyncMode; + CustomVSyncInterval = Configuration.CustomVSyncInterval; System.State.DockedMode = Configuration.EnableDockedMode; System.PerformanceState.PerformanceMode = System.State.DockedMode ? PerformanceMode.Boost : PerformanceMode.Default; System.EnablePtc = Configuration.EnablePtc; System.FsIntegrityCheckLevel = Configuration.FsIntegrityCheckLevel; System.GlobalAccessLogMode = Configuration.FsGlobalAccessLogMode; + UpdateVSyncInterval(); #pragma warning restore IDE0055 } @@ -75,6 +81,34 @@ public void ProcessFrame() Gpu.GPFifo.DispatchCalls(); } + public void IncrementCustomVSyncInterval() + { + CustomVSyncInterval += 1; + UpdateVSyncInterval(); + } + + public void DecrementCustomVSyncInterval() + { + CustomVSyncInterval -= 1; + UpdateVSyncInterval(); + } + + public void UpdateVSyncInterval() + { + switch (VSyncMode) + { + case VSyncMode.Custom: + TargetVSyncInterval = CustomVSyncInterval; + break; + case VSyncMode.Switch: + TargetVSyncInterval = 60; + break; + case VSyncMode.Unbounded: + TargetVSyncInterval = 1; + break; + } + } + public bool LoadCart(string exeFsDir, string romFsFile = null) => Processes.LoadUnpackedNca(exeFsDir, romFsFile); public bool LoadXci(string xciFile, ulong applicationId = 0) => Processes.LoadXci(xciFile, applicationId); public bool LoadNca(string ncaFile) => Processes.LoadNca(ncaFile); diff --git a/src/Ryujinx.Headless.SDL2/Options.cs b/src/Ryujinx.Headless.SDL2/Options.cs index 8078ca5e4e..4e2ad5b586 100644 --- a/src/Ryujinx.Headless.SDL2/Options.cs +++ b/src/Ryujinx.Headless.SDL2/Options.cs @@ -115,8 +115,11 @@ public class Options [Option("fs-global-access-log-mode", Required = false, Default = 0, HelpText = "Enables FS access log output to the console.")] public int FsGlobalAccessLogMode { get; set; } - [Option("disable-vsync", Required = false, HelpText = "Disables Vertical Sync.")] - public bool DisableVSync { get; set; } + [Option("vsync-mode", Required = false, Default = VSyncMode.Switch, HelpText = "Sets the emulated VSync mode (Switch, Unbounded, or Custom).")] + public VSyncMode VSyncMode { get; set; } + + [Option("custom-refresh-rate", Required = false, Default = 90, HelpText = "Sets the custom refresh rate target value (integer).")] + public int CustomVSyncInterval { get; set; } [Option("disable-shader-cache", Required = false, HelpText = "Disables Shader cache.")] public bool DisableShaderCache { get; set; } diff --git a/src/Ryujinx.Headless.SDL2/Program.cs b/src/Ryujinx.Headless.SDL2/Program.cs index e3bbd1e515..ff87a38457 100644 --- a/src/Ryujinx.Headless.SDL2/Program.cs +++ b/src/Ryujinx.Headless.SDL2/Program.cs @@ -563,7 +563,7 @@ private static Switch InitializeEmulationContext(WindowBase window, IRenderer re window, options.SystemLanguage, options.SystemRegion, - !options.DisableVSync, + options.VSyncMode, !options.DisableDockedMode, !options.DisablePTC, options.EnableInternetAccess, @@ -580,7 +580,8 @@ private static Switch InitializeEmulationContext(WindowBase window, IRenderer re Common.Configuration.Multiplayer.MultiplayerMode.Disabled, false, "", - ""); + "", + options.CustomVSyncInterval); return new Switch(configuration); } diff --git a/src/Ryujinx.Headless.SDL2/StatusUpdatedEventArgs.cs b/src/Ryujinx.Headless.SDL2/StatusUpdatedEventArgs.cs index cd7715712e..c1dd3805f8 100644 --- a/src/Ryujinx.Headless.SDL2/StatusUpdatedEventArgs.cs +++ b/src/Ryujinx.Headless.SDL2/StatusUpdatedEventArgs.cs @@ -3,7 +3,7 @@ namespace Ryujinx.Headless.SDL2 { class StatusUpdatedEventArgs( - bool vSyncEnabled, + string vSyncMode, string dockedMode, string aspectRatio, string gameStatus, @@ -11,7 +11,7 @@ class StatusUpdatedEventArgs( string gpuName) : EventArgs { - public bool VSyncEnabled = vSyncEnabled; + public string VSyncMode = vSyncMode; public string DockedMode = dockedMode; public string AspectRatio = aspectRatio; public string GameStatus = gameStatus; diff --git a/src/Ryujinx.Headless.SDL2/WindowBase.cs b/src/Ryujinx.Headless.SDL2/WindowBase.cs index 6d681e100d..2479ec1272 100644 --- a/src/Ryujinx.Headless.SDL2/WindowBase.cs +++ b/src/Ryujinx.Headless.SDL2/WindowBase.cs @@ -314,7 +314,7 @@ public void Render() } StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs( - Device.EnableDeviceVsync, + Device.VSyncMode.ToString(), dockedMode, Device.Configuration.AspectRatio.ToText(), $"Game: {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)", diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs index 80ba1b1866..027e1052b0 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Multiplayer; @@ -16,7 +17,7 @@ public class ConfigurationFileFormat /// /// The current version of the file format /// - public const int CurrentVersion = 56; + public const int CurrentVersion = 57; /// /// Version of the configuration file format @@ -191,8 +192,25 @@ public class ConfigurationFileFormat /// /// Enables or disables Vertical Sync /// + /// Kept for file format compatibility (to avoid possible failure when parsing configuration on old versions) + /// TODO: Remove this when those older versions aren't in use anymore. public bool EnableVsync { get; set; } + /// + /// Current VSync mode; 60 (Switch), unbounded ("Vsync off"), or custom + /// + public VSyncMode VSyncMode { get; set; } + + /// + /// Enables or disables the custom present interval + /// + public bool EnableCustomVSyncInterval { get; set; } + + /// + /// The custom present interval value + /// + public int CustomVSyncInterval { get; set; } + /// /// Enables or disables Shader cache /// diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.Migration.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.Migration.cs index 65dd881068..a41ea2cd73 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.Migration.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.Migration.cs @@ -82,7 +82,7 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu configurationFileFormat.Hotkeys = new KeyboardHotkeys { - ToggleVsync = Key.F1, + ToggleVSyncMode = Key.F1, }; configurationFileUpdated = true; @@ -276,7 +276,7 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu configurationFileFormat.Hotkeys = new KeyboardHotkeys { - ToggleVsync = Key.F1, + ToggleVSyncMode = Key.F1, Screenshot = Key.F8, }; @@ -289,7 +289,7 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu configurationFileFormat.Hotkeys = new KeyboardHotkeys { - ToggleVsync = Key.F1, + ToggleVSyncMode = Key.F1, Screenshot = Key.F8, ShowUI = Key.F4, }; @@ -332,7 +332,7 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu configurationFileFormat.Hotkeys = new KeyboardHotkeys { - ToggleVsync = configurationFileFormat.Hotkeys.ToggleVsync, + ToggleVSyncMode = configurationFileFormat.Hotkeys.ToggleVSyncMode, Screenshot = configurationFileFormat.Hotkeys.Screenshot, ShowUI = configurationFileFormat.Hotkeys.ShowUI, Pause = Key.F5, @@ -347,7 +347,7 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu configurationFileFormat.Hotkeys = new KeyboardHotkeys { - ToggleVsync = configurationFileFormat.Hotkeys.ToggleVsync, + ToggleVSyncMode = configurationFileFormat.Hotkeys.ToggleVSyncMode, Screenshot = configurationFileFormat.Hotkeys.Screenshot, ShowUI = configurationFileFormat.Hotkeys.ShowUI, Pause = configurationFileFormat.Hotkeys.Pause, @@ -421,7 +421,7 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu configurationFileFormat.Hotkeys = new KeyboardHotkeys { - ToggleVsync = configurationFileFormat.Hotkeys.ToggleVsync, + ToggleVSyncMode = configurationFileFormat.Hotkeys.ToggleVSyncMode, Screenshot = configurationFileFormat.Hotkeys.Screenshot, ShowUI = configurationFileFormat.Hotkeys.ShowUI, Pause = configurationFileFormat.Hotkeys.Pause, @@ -448,7 +448,7 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu configurationFileFormat.Hotkeys = new KeyboardHotkeys { - ToggleVsync = configurationFileFormat.Hotkeys.ToggleVsync, + ToggleVSyncMode = configurationFileFormat.Hotkeys.ToggleVSyncMode, Screenshot = configurationFileFormat.Hotkeys.Screenshot, ShowUI = configurationFileFormat.Hotkeys.ShowUI, Pause = configurationFileFormat.Hotkeys.Pause, @@ -611,6 +611,33 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu configurationFileUpdated = true; } + if (configurationFileFormat.Version < 57) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 57."); + + configurationFileFormat.VSyncMode = VSyncMode.Switch; + configurationFileFormat.EnableCustomVSyncInterval = false; + + configurationFileFormat.Hotkeys = new KeyboardHotkeys + { + ToggleVSyncMode = Key.F1, + Screenshot = configurationFileFormat.Hotkeys.Screenshot, + ShowUI = configurationFileFormat.Hotkeys.ShowUI, + Pause = configurationFileFormat.Hotkeys.Pause, + ToggleMute = configurationFileFormat.Hotkeys.ToggleMute, + ResScaleUp = configurationFileFormat.Hotkeys.ResScaleUp, + ResScaleDown = configurationFileFormat.Hotkeys.ResScaleDown, + VolumeUp = configurationFileFormat.Hotkeys.VolumeUp, + VolumeDown = configurationFileFormat.Hotkeys.VolumeDown, + CustomVSyncIntervalIncrement = Key.Unbound, + CustomVSyncIntervalDecrement = Key.Unbound, + }; + + configurationFileFormat.CustomVSyncInterval = 120; + + configurationFileUpdated = true; + } + Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog; Graphics.ResScale.Value = configurationFileFormat.ResScale; Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom; @@ -646,7 +673,9 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu ShowTitleBar.Value = configurationFileFormat.ShowTitleBar; EnableHardwareAcceleration.Value = configurationFileFormat.EnableHardwareAcceleration; HideCursor.Value = configurationFileFormat.HideCursor; - Graphics.EnableVsync.Value = configurationFileFormat.EnableVsync; + Graphics.VSyncMode.Value = configurationFileFormat.VSyncMode; + Graphics.EnableCustomVSyncInterval.Value = configurationFileFormat.EnableCustomVSyncInterval; + Graphics.CustomVSyncInterval.Value = configurationFileFormat.CustomVSyncInterval; Graphics.EnableShaderCache.Value = configurationFileFormat.EnableShaderCache; Graphics.EnableTextureRecompression.Value = configurationFileFormat.EnableTextureRecompression; Graphics.EnableMacroHLE.Value = configurationFileFormat.EnableMacroHLE; diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.Model.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.Model.cs index 9be8f4df7a..f28ce0348c 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.Model.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.Model.cs @@ -1,4 +1,4 @@ -using ARMeilleure; +using ARMeilleure; using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Hid; @@ -474,9 +474,19 @@ public class GraphicsSection public ReactiveObject ShadersDumpPath { get; private set; } /// - /// Enables or disables Vertical Sync + /// Toggles the present interval mode. Options are Switch (60Hz), Unbounded (previously Vsync off), and Custom, if enabled. /// - public ReactiveObject EnableVsync { get; private set; } + public ReactiveObject VSyncMode { get; private set; } + + /// + /// Enables or disables the custom present interval mode. + /// + public ReactiveObject EnableCustomVSyncInterval { get; private set; } + + /// + /// Changes the custom present interval. + /// + public ReactiveObject CustomVSyncInterval { get; private set; } /// /// Enables or disables Shader cache @@ -536,8 +546,12 @@ public GraphicsSection() AspectRatio = new ReactiveObject(); AspectRatio.LogChangesToValue(nameof(AspectRatio)); ShadersDumpPath = new ReactiveObject(); - EnableVsync = new ReactiveObject(); - EnableVsync.LogChangesToValue(nameof(EnableVsync)); + VSyncMode = new ReactiveObject(); + VSyncMode.LogChangesToValue(nameof(VSyncMode)); + EnableCustomVSyncInterval = new ReactiveObject(); + EnableCustomVSyncInterval.LogChangesToValue(nameof(EnableCustomVSyncInterval)); + CustomVSyncInterval = new ReactiveObject(); + CustomVSyncInterval.LogChangesToValue(nameof(CustomVSyncInterval)); EnableShaderCache = new ReactiveObject(); EnableShaderCache.LogChangesToValue(nameof(EnableShaderCache)); EnableTextureRecompression = new ReactiveObject(); diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs index b3012568e8..badb047df2 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs @@ -64,7 +64,9 @@ public ConfigurationFileFormat ToFileFormat() ShowTitleBar = ShowTitleBar, EnableHardwareAcceleration = EnableHardwareAcceleration, HideCursor = HideCursor, - EnableVsync = Graphics.EnableVsync, + VSyncMode = Graphics.VSyncMode, + EnableCustomVSyncInterval = Graphics.EnableCustomVSyncInterval, + CustomVSyncInterval = Graphics.CustomVSyncInterval, EnableShaderCache = Graphics.EnableShaderCache, EnableTextureRecompression = Graphics.EnableTextureRecompression, EnableMacroHLE = Graphics.EnableMacroHLE, @@ -179,7 +181,9 @@ public void LoadDefault() ShowTitleBar.Value = !OperatingSystem.IsWindows(); EnableHardwareAcceleration.Value = true; HideCursor.Value = HideCursorMode.OnIdle; - Graphics.EnableVsync.Value = true; + Graphics.VSyncMode.Value = VSyncMode.Switch; + Graphics.CustomVSyncInterval.Value = 120; + Graphics.EnableCustomVSyncInterval.Value = false; Graphics.EnableShaderCache.Value = true; Graphics.EnableTextureRecompression.Value = false; Graphics.EnableMacroHLE.Value = true; @@ -240,7 +244,7 @@ public void LoadDefault() Hid.EnableMouse.Value = false; Hid.Hotkeys.Value = new KeyboardHotkeys { - ToggleVsync = Key.F1, + ToggleVSyncMode = Key.F1, ToggleMute = Key.F2, Screenshot = Key.F8, ShowUI = Key.F4, diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index d1398f194a..5789737d69 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -57,6 +57,8 @@ using MouseButton = Ryujinx.Input.MouseButton; using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter; using Size = Avalonia.Size; +using Switch = Ryujinx.HLE.Switch; +using VSyncMode = Ryujinx.Common.Configuration.VSyncMode; namespace Ryujinx.Ava { @@ -203,6 +205,9 @@ public AppHost( ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter; ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel; ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event += UpdateColorSpacePassthrough; + ConfigurationState.Instance.Graphics.VSyncMode.Event += UpdateVSyncMode; + ConfigurationState.Instance.Graphics.CustomVSyncInterval.Event += UpdateCustomVSyncIntervalValue; + ConfigurationState.Instance.Graphics.EnableCustomVSyncInterval.Event += UpdateCustomVSyncIntervalEnabled; ConfigurationState.Instance.System.EnableInternetAccess.Event += UpdateEnableInternetAccessState; ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState; @@ -295,6 +300,66 @@ private void UpdateColorSpacePassthrough(object sender, ReactiveEventArgs _renderer.Window?.SetColorSpacePassthrough((bool)ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Value); } + public void UpdateVSyncMode(object sender, ReactiveEventArgs e) + { + if (Device != null) + { + Device.VSyncMode = e.NewValue; + Device.UpdateVSyncInterval(); + } + _renderer.Window?.ChangeVSyncMode((Ryujinx.Graphics.GAL.VSyncMode)e.NewValue); + + _viewModel.ShowCustomVSyncIntervalPicker = (e.NewValue == VSyncMode.Custom); + } + + public void VSyncModeToggle() + { + VSyncMode oldVSyncMode = Device.VSyncMode; + VSyncMode newVSyncMode = VSyncMode.Switch; + bool customVSyncIntervalEnabled = ConfigurationState.Instance.Graphics.EnableCustomVSyncInterval.Value; + + switch (oldVSyncMode) + { + case VSyncMode.Switch: + newVSyncMode = VSyncMode.Unbounded; + break; + case VSyncMode.Unbounded: + if (customVSyncIntervalEnabled) + { + newVSyncMode = VSyncMode.Custom; + } + else + { + newVSyncMode = VSyncMode.Switch; + } + + break; + case VSyncMode.Custom: + newVSyncMode = VSyncMode.Switch; + break; + } + + UpdateVSyncMode(this, new ReactiveEventArgs(oldVSyncMode, newVSyncMode)); + } + + private void UpdateCustomVSyncIntervalValue(object sender, ReactiveEventArgs e) + { + if (Device != null) + { + Device.TargetVSyncInterval = e.NewValue; + Device.UpdateVSyncInterval(); + } + } + + private void UpdateCustomVSyncIntervalEnabled(object sender, ReactiveEventArgs e) + { + if (Device != null) + { + Device.CustomVSyncIntervalEnabled = e.NewValue; + Device.UpdateVSyncInterval(); + } + } + private void ShowCursor() { Dispatcher.UIThread.Post(() => @@ -505,12 +570,6 @@ private void UpdateDisableP2pState(object sender, ReactiveEventArgs e) Device.Configuration.MultiplayerDisableP2p = e.NewValue; } - public void ToggleVSync() - { - Device.EnableDeviceVsync = !Device.EnableDeviceVsync; - _renderer.Window.ChangeVSyncMode(Device.EnableDeviceVsync); - } - public void Stop() { _isActive = false; @@ -864,7 +923,7 @@ private void InitializeSwitchInstance() _viewModel.UiHandler, (SystemLanguage)ConfigurationState.Instance.System.Language.Value, (RegionCode)ConfigurationState.Instance.System.Region.Value, - ConfigurationState.Instance.Graphics.EnableVsync, + ConfigurationState.Instance.Graphics.VSyncMode, ConfigurationState.Instance.System.EnableDockedMode, ConfigurationState.Instance.System.EnablePtc, ConfigurationState.Instance.System.EnableInternetAccess, @@ -881,7 +940,8 @@ private void InitializeSwitchInstance() ConfigurationState.Instance.Multiplayer.Mode, ConfigurationState.Instance.Multiplayer.DisableP2p, ConfigurationState.Instance.Multiplayer.LdnPassphrase, - ConfigurationState.Instance.Multiplayer.LdnServer)); + ConfigurationState.Instance.Multiplayer.LdnServer, + ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value)); } private static IHardwareDeviceDriver InitializeAudio() @@ -1002,7 +1062,7 @@ private void RenderLoop() Device.Gpu.SetGpuThread(); Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token); - _renderer.Window.ChangeVSyncMode(Device.EnableDeviceVsync); + _renderer.Window.ChangeVSyncMode((Ryujinx.Graphics.GAL.VSyncMode)Device.VSyncMode); while (_isActive) { @@ -1063,6 +1123,7 @@ public void UpdateStatus() { // Run a status update only when a frame is to be drawn. This prevents from updating the ui and wasting a render when no frame is queued. string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? LocaleManager.Instance[LocaleKeys.Docked] : LocaleManager.Instance[LocaleKeys.Handheld]; + string vSyncMode = Device.VSyncMode.ToString(); UpdateShaderCount(); @@ -1072,7 +1133,7 @@ public void UpdateStatus() } StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs( - Device.EnableDeviceVsync, + vSyncMode, LocaleManager.Instance[LocaleKeys.VolumeShort] + $": {(int)(Device.GetVolume() * 100)}%", dockedMode, ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(), @@ -1175,8 +1236,16 @@ private bool UpdateFrame() { switch (currentHotkeyState) { - case KeyboardHotkeyState.ToggleVSync: - ToggleVSync(); + case KeyboardHotkeyState.ToggleVSyncMode: + VSyncModeToggle(); + break; + case KeyboardHotkeyState.CustomVSyncIntervalDecrement: + Device.DecrementCustomVSyncInterval(); + _viewModel.CustomVSyncInterval -= 1; + break; + case KeyboardHotkeyState.CustomVSyncIntervalIncrement: + Device.IncrementCustomVSyncInterval(); + _viewModel.CustomVSyncInterval += 1; break; case KeyboardHotkeyState.Screenshot: ScreenshotRequested = true; @@ -1263,9 +1332,9 @@ private KeyboardHotkeyState GetHotkeyState() { KeyboardHotkeyState state = KeyboardHotkeyState.None; - if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleVsync)) + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleVSyncMode)) { - state = KeyboardHotkeyState.ToggleVSync; + state = KeyboardHotkeyState.ToggleVSyncMode; } else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot)) { @@ -1299,6 +1368,14 @@ private KeyboardHotkeyState GetHotkeyState() { state = KeyboardHotkeyState.VolumeDown; } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.CustomVSyncIntervalIncrement)) + { + state = KeyboardHotkeyState.CustomVSyncIntervalIncrement; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.CustomVSyncIntervalDecrement)) + { + state = KeyboardHotkeyState.CustomVSyncIntervalDecrement; + } return state; } diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json index 23135866d3..13ffeb759f 100644 --- a/src/Ryujinx/Assets/Locales/en_US.json +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -142,9 +142,20 @@ "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Latin American Spanish", "SettingsTabSystemSystemLanguageSimplifiedChinese": "Simplified Chinese", "SettingsTabSystemSystemLanguageTraditionalChinese": "Traditional Chinese", - "SettingsTabSystemSystemTimeZone": "System TimeZone:", + "SettingsTabSystemSystemTimeZone": "System Time Zone:", "SettingsTabSystemSystemTime": "System Time:", - "SettingsTabSystemEnableVsync": "VSync", + "SettingsTabSystemVSyncMode": "VSync:", + "SettingsTabSystemEnableCustomVSyncInterval": "Enable custom refresh rate (Experimental)", + "SettingsTabSystemVSyncModeSwitch": "Switch", + "SettingsTabSystemVSyncModeUnbounded": "Unbounded", + "SettingsTabSystemVSyncModeCustom": "Custom Refresh Rate", + "SettingsTabSystemVSyncModeTooltip": "Emulated Vertical Sync. 'Switch' emulates the Switch's refresh rate of 60Hz. 'Unbounded' is an unbounded refresh rate.", + "SettingsTabSystemVSyncModeTooltipCustom": "Emulated Vertical Sync. 'Switch' emulates the Switch's refresh rate of 60Hz. 'Unbounded' is an unbounded refresh rate. 'Custom' emulates the specified custom refresh rate.", + "SettingsTabSystemEnableCustomVSyncIntervalTooltip": "Allows the user to specify an emulated refresh rate. In some titles, this may speed up or slow down the rate of gameplay logic. In other titles, it may allow for capping FPS at some multiple of the refresh rate, or lead to unpredictable behavior. This is an experimental feature, with no guarantees for how gameplay will be affected. \n\nLeave OFF if unsure.", + "SettingsTabSystemCustomVSyncIntervalValueTooltip": "The custom refresh rate target value.", + "SettingsTabSystemCustomVSyncIntervalSliderTooltip": "The custom refresh rate, as a percentage of the normal Switch refresh rate.", + "SettingsTabSystemCustomVSyncIntervalPercentage": "Custom Refresh Rate %:", + "SettingsTabSystemCustomVSyncIntervalValue": "Custom Refresh Rate Value:", "SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)", "SettingsTabSystemEnableLowPowerPptc": "Low-power PPTC cache", "SettingsTabSystemEnableFsIntegrityChecks": "FS Integrity Checks", @@ -153,6 +164,7 @@ "SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemCustomVSyncInterval": "Interval", "SettingsTabSystemHacks": "Hacks", "SettingsTabSystemHacksNote": "May cause instability", "SettingsTabSystemDramSize": "DRAM size:", @@ -720,11 +732,13 @@ "RyujinxUpdater": "Ryujinx Updater", "SettingsTabHotkeys": "Keyboard Hotkeys", "SettingsTabHotkeysHotkeys": "Keyboard Hotkeys", - "SettingsTabHotkeysToggleVsyncHotkey": "Toggle VSync:", + "SettingsTabHotkeysToggleVSyncModeHotkey": "Toggle VSync mode:", "SettingsTabHotkeysScreenshotHotkey": "Screenshot:", "SettingsTabHotkeysShowUiHotkey": "Show UI:", "SettingsTabHotkeysPauseHotkey": "Pause:", "SettingsTabHotkeysToggleMuteHotkey": "Mute:", + "SettingsTabHotkeysIncrementCustomVSyncIntervalHotkey": "Raise custom refresh rate", + "SettingsTabHotkeysDecrementCustomVSyncIntervalHotkey": "Lower custom refresh rate", "ControllerMotionTitle": "Motion Control Settings", "ControllerRumbleTitle": "Rumble Settings", "SettingsSelectThemeFileDialogTitle": "Select Theme File", diff --git a/src/Ryujinx/Assets/Styles/Themes.xaml b/src/Ryujinx/Assets/Styles/Themes.xaml index 0f323f84b2..056eba2282 100644 --- a/src/Ryujinx/Assets/Styles/Themes.xaml +++ b/src/Ryujinx/Assets/Styles/Themes.xaml @@ -26,8 +26,9 @@ #b3ffffff #80cccccc #A0000000 - #FF2EEAC9 - #FFFF4554 + #FF2EEAC9 + #FFFF4554 + #6483F5 _toggleVsync; + get => _toggleVSyncMode; set { - _toggleVsync = value; + _toggleVSyncMode = value; OnPropertyChanged(); } } @@ -104,11 +104,33 @@ public Key VolumeDown } } + private Key _customVSyncIntervalIncrement; + public Key CustomVSyncIntervalIncrement + { + get => _customVSyncIntervalIncrement; + set + { + _customVSyncIntervalIncrement = value; + OnPropertyChanged(); + } + } + + private Key _customVSyncIntervalDecrement; + public Key CustomVSyncIntervalDecrement + { + get => _customVSyncIntervalDecrement; + set + { + _customVSyncIntervalDecrement = value; + OnPropertyChanged(); + } + } + public HotkeyConfig(KeyboardHotkeys config) { if (config != null) { - ToggleVsync = config.ToggleVsync; + ToggleVSyncMode = config.ToggleVSyncMode; Screenshot = config.Screenshot; ShowUI = config.ShowUI; Pause = config.Pause; @@ -117,6 +139,8 @@ public HotkeyConfig(KeyboardHotkeys config) ResScaleDown = config.ResScaleDown; VolumeUp = config.VolumeUp; VolumeDown = config.VolumeDown; + CustomVSyncIntervalIncrement = config.CustomVSyncIntervalIncrement; + CustomVSyncIntervalDecrement = config.CustomVSyncIntervalDecrement; } } @@ -124,7 +148,7 @@ public KeyboardHotkeys GetConfig() { var config = new KeyboardHotkeys { - ToggleVsync = ToggleVsync, + ToggleVSyncMode = ToggleVSyncMode, Screenshot = Screenshot, ShowUI = ShowUI, Pause = Pause, @@ -133,6 +157,8 @@ public KeyboardHotkeys GetConfig() ResScaleDown = ResScaleDown, VolumeUp = VolumeUp, VolumeDown = VolumeDown, + CustomVSyncIntervalIncrement = CustomVSyncIntervalIncrement, + CustomVSyncIntervalDecrement = CustomVSyncIntervalDecrement, }; return config; diff --git a/src/Ryujinx/UI/Models/SaveModel.cs b/src/Ryujinx/UI/Models/SaveModel.cs index 55408ac3ad..cfc397c6e5 100644 --- a/src/Ryujinx/UI/Models/SaveModel.cs +++ b/src/Ryujinx/UI/Models/SaveModel.cs @@ -47,7 +47,7 @@ public SaveModel(SaveDataInfo info) TitleId = info.ProgramId; UserId = info.UserId; - var appData = App.MainWindow.ViewModel.Applications.FirstOrDefault(x => x.IdString.Equals(TitleIdString, StringComparison.OrdinalIgnoreCase)); + var appData = MainWindow.MainWindowViewModel.Applications.FirstOrDefault(x => x.IdString.Equals(TitleIdString, StringComparison.OrdinalIgnoreCase)); InGameList = appData != null; diff --git a/src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs b/src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs index 40f783c448..6f0f5ab5d3 100644 --- a/src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs +++ b/src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs @@ -4,18 +4,17 @@ namespace Ryujinx.Ava.UI.Models { internal class StatusUpdatedEventArgs : EventArgs { - public bool VSyncEnabled { get; } + public string VSyncMode { get; } public string VolumeStatus { get; } public string AspectRatio { get; } public string DockedMode { get; } public string FifoStatus { get; } public string GameStatus { get; } - public uint ShaderCount { get; } - public StatusUpdatedEventArgs(bool vSyncEnabled, string volumeStatus, string dockedMode, string aspectRatio, string gameStatus, string fifoStatus, uint shaderCount) + public StatusUpdatedEventArgs(string vSyncMode, string volumeStatus, string dockedMode, string aspectRatio, string gameStatus, string fifoStatus, uint shaderCount) { - VSyncEnabled = vSyncEnabled; + VSyncMode = vSyncMode; VolumeStatus = volumeStatus; DockedMode = dockedMode; AspectRatio = aspectRatio; diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index f1587a0ff3..824fdd717a 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -63,6 +63,7 @@ public class MainWindowViewModel : BaseModel private string _searchText; private Timer _searchTimer; private string _dockedStatusText; + private string _vSyncModeText; private string _fifoStatusText; private string _gameStatusText; private string _volumeStatusText; @@ -80,7 +81,7 @@ public class MainWindowViewModel : BaseModel private bool _showStatusSeparator; private Brush _progressBarForegroundColor; private Brush _progressBarBackgroundColor; - private Brush _vsyncColor; + private Brush _vSyncModeColor; private byte[] _selectedIcon; private bool _isAppletMenuActive; private int _statusBarProgressMaximum; @@ -111,6 +112,8 @@ public class MainWindowViewModel : BaseModel private WindowState _windowState; private double _windowWidth; private double _windowHeight; + private int _customVSyncInterval; + private int _customVSyncIntervalPercentageProxy; private bool _isActive; private bool _isSubMenuOpen; @@ -145,6 +148,7 @@ public MainWindowViewModel() Volume = ConfigurationState.Instance.System.AudioVolume; } + CustomVSyncInterval = ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value; } public void Initialize( @@ -447,17 +451,87 @@ public Brush ProgressBarForegroundColor } } - public Brush VsyncColor + public Brush VSyncModeColor { - get => _vsyncColor; + get => _vSyncModeColor; set { - _vsyncColor = value; + _vSyncModeColor = value; OnPropertyChanged(); } } + public bool ShowCustomVSyncIntervalPicker + { + get + { + if (_isGameRunning) + { + return AppHost.Device.VSyncMode == + VSyncMode.Custom; + } + else + { + return false; + } + } + set + { + OnPropertyChanged(); + } + } + + public int CustomVSyncIntervalPercentageProxy + { + get => _customVSyncIntervalPercentageProxy; + set + { + int newInterval = (int)((value / 100f) * 60); + _customVSyncInterval = newInterval; + _customVSyncIntervalPercentageProxy = value; + if (_isGameRunning) + { + AppHost.Device.CustomVSyncInterval = newInterval; + AppHost.Device.UpdateVSyncInterval(); + } + OnPropertyChanged((nameof(CustomVSyncInterval))); + OnPropertyChanged((nameof(CustomVSyncIntervalPercentageText))); + } + } + + public string CustomVSyncIntervalPercentageText + { + get + { + string text = CustomVSyncIntervalPercentageProxy.ToString() + "%"; + return text; + } + set + { + + } + } + + public int CustomVSyncInterval + { + get => _customVSyncInterval; + set + { + _customVSyncInterval = value; + int newPercent = (int)((value / 60f) * 100); + _customVSyncIntervalPercentageProxy = newPercent; + if (_isGameRunning) + { + AppHost.Device.CustomVSyncInterval = value; + AppHost.Device.UpdateVSyncInterval(); + } + OnPropertyChanged(nameof(CustomVSyncIntervalPercentageProxy)); + OnPropertyChanged(nameof(CustomVSyncIntervalPercentageText)); + OnPropertyChanged(); + } + } + public byte[] SelectedIcon { get => _selectedIcon; @@ -578,6 +652,17 @@ public string BackendText } } + public string VSyncModeText + { + get => _vSyncModeText; + set + { + _vSyncModeText = value; + + OnPropertyChanged(); + } + } + public string DockedStatusText { get => _dockedStatusText; @@ -1292,17 +1377,18 @@ private void Update_StatusBar(object sender, StatusUpdatedEventArgs args) { Dispatcher.UIThread.InvokeAsync(() => { - Application.Current!.Styles.TryGetResource(args.VSyncEnabled - ? "VsyncEnabled" - : "VsyncDisabled", + Application.Current!.Styles.TryGetResource(args.VSyncMode, Application.Current.ActualThemeVariant, out object color); if (color is Color clr) { - VsyncColor = new SolidColorBrush(clr); + VSyncModeColor = new SolidColorBrush(clr); } + VSyncModeText = args.VSyncMode == "Custom" ? "Custom" : "VSync"; + ShowCustomVSyncIntervalPicker = + args.VSyncMode == VSyncMode.Custom.ToString(); DockedStatusText = args.DockedMode; AspectRatioStatusText = args.AspectRatio; GameStatusText = args.GameStatus; @@ -1495,6 +1581,27 @@ public void ToggleDockMode() } } + public void ToggleVSyncMode() + { + AppHost.VSyncModeToggle(); + OnPropertyChanged(nameof(ShowCustomVSyncIntervalPicker)); + } + + public void VSyncModeSettingChanged() + { + if (_isGameRunning) + { + AppHost.Device.CustomVSyncInterval = ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value; + AppHost.Device.UpdateVSyncInterval(); + } + + CustomVSyncInterval = ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value; + OnPropertyChanged(nameof(ShowCustomVSyncIntervalPicker)); + OnPropertyChanged(nameof(CustomVSyncIntervalPercentageProxy)); + OnPropertyChanged(nameof(CustomVSyncIntervalPercentageText)); + OnPropertyChanged(nameof(CustomVSyncInterval)); + } + public async Task ExitCurrentState() { if (WindowState is WindowState.FullScreen) diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index 2da252d002..a5abeb36b5 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -52,6 +52,10 @@ public partial class SettingsViewModel : BaseModel private int _graphicsBackendIndex; private int _scalingFilter; private int _scalingFilterLevel; + private int _customVSyncInterval; + private bool _enableCustomVSyncInterval; + private int _customVSyncIntervalPercentageProxy; + private VSyncMode _vSyncMode; public event Action CloseWindow; public event Action SaveSettingsEvent; @@ -154,7 +158,74 @@ public bool AutoloadDirectoryChanged public bool EnableDockedMode { get; set; } public bool EnableKeyboard { get; set; } public bool EnableMouse { get; set; } - public bool EnableVsync { get; set; } + public VSyncMode VSyncMode + { + get => _vSyncMode; + set + { + if (value == VSyncMode.Custom || + value == VSyncMode.Switch || + value == VSyncMode.Unbounded) + { + _vSyncMode = value; + OnPropertyChanged(); + } + } + } + + public int CustomVSyncIntervalPercentageProxy + { + get => _customVSyncIntervalPercentageProxy; + set + { + int newInterval = (int)((value / 100f) * 60); + _customVSyncInterval = newInterval; + _customVSyncIntervalPercentageProxy = value; + OnPropertyChanged((nameof(CustomVSyncInterval))); + OnPropertyChanged((nameof(CustomVSyncIntervalPercentageText))); + } + } + + public string CustomVSyncIntervalPercentageText + { + get + { + string text = CustomVSyncIntervalPercentageProxy.ToString() + "%"; + return text; + } + } + + public bool EnableCustomVSyncInterval + { + get => _enableCustomVSyncInterval; + set + { + _enableCustomVSyncInterval = value; + if (_vSyncMode == VSyncMode.Custom && !value) + { + VSyncMode = VSyncMode.Switch; + } + else if (value) + { + VSyncMode = VSyncMode.Custom; + } + OnPropertyChanged(); + } + } + + public int CustomVSyncInterval + { + get => _customVSyncInterval; + set + { + _customVSyncInterval = value; + int newPercent = (int)((value / 60f) * 100); + _customVSyncIntervalPercentageProxy = newPercent; + OnPropertyChanged(nameof(CustomVSyncIntervalPercentageProxy)); + OnPropertyChanged(nameof(CustomVSyncIntervalPercentageText)); + OnPropertyChanged(); + } + } public bool EnablePptc { get; set; } public bool EnableLowPowerPptc { get; set; } public bool EnableInternetAccess { get; set; } @@ -484,7 +555,9 @@ public void LoadCurrentConfiguration() CurrentDate = currentDateTime.Date; CurrentTime = currentDateTime.TimeOfDay; - EnableVsync = config.Graphics.EnableVsync; + EnableCustomVSyncInterval = config.Graphics.EnableCustomVSyncInterval.Value; + CustomVSyncInterval = config.Graphics.CustomVSyncInterval; + VSyncMode = config.Graphics.VSyncMode; EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks; DramSize = config.System.DramSize; IgnoreMissingServices = config.System.IgnoreMissingServices; @@ -590,7 +663,9 @@ public void SaveSettings() } config.System.SystemTimeOffset.Value = Convert.ToInt64((CurrentDate.ToUnixTimeSeconds() + CurrentTime.TotalSeconds) - DateTimeOffset.Now.ToUnixTimeSeconds()); - config.Graphics.EnableVsync.Value = EnableVsync; + config.Graphics.VSyncMode.Value = VSyncMode; + config.Graphics.EnableCustomVSyncInterval.Value = EnableCustomVSyncInterval; + config.Graphics.CustomVSyncInterval.Value = CustomVSyncInterval; config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks; config.System.DramSize.Value = DramSize; config.System.IgnoreMissingServices.Value = IgnoreMissingServices; @@ -660,6 +735,7 @@ public void SaveSettings() config.ToFileFormat().SaveConfig(Program.ConfigurationPath); MainWindow.UpdateGraphicsConfig(); + MainWindow.MainWindowViewModel.VSyncModeSettingChanged(); SaveSettingsEvent?.Invoke(); diff --git a/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml b/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml index 0e0526f494..597cf10e1c 100644 --- a/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml +++ b/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml @@ -79,15 +79,59 @@ MaxHeight="18" Orientation="Horizontal"> + PointerReleased="VSyncMode_PointerReleased" + Text="{Binding VSyncModeText}" + TextAlignment="Start"/> + - - - + + + @@ -103,6 +103,18 @@ + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs index fb0fe2bb12..609f616335 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs +++ b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs @@ -82,8 +82,8 @@ private void Button_IsCheckedChanged(object sender, RoutedEventArgs e) switch (button.Name) { - case "ToggleVsync": - viewModel.KeyboardHotkey.ToggleVsync = buttonValue.AsHidType(); + case "ToggleVSyncMode": + viewModel.KeyboardHotkey.ToggleVSyncMode = buttonValue.AsHidType(); break; case "Screenshot": viewModel.KeyboardHotkey.Screenshot = buttonValue.AsHidType(); @@ -109,6 +109,12 @@ private void Button_IsCheckedChanged(object sender, RoutedEventArgs e) case "VolumeDown": viewModel.KeyboardHotkey.VolumeDown = buttonValue.AsHidType(); break; + case "CustomVSyncIntervalIncrement": + viewModel.KeyboardHotkey.CustomVSyncIntervalIncrement = buttonValue.AsHidType(); + break; + case "CustomVSyncIntervalDecrement": + viewModel.KeyboardHotkey.CustomVSyncIntervalDecrement = buttonValue.AsHidType(); + break; } } }; diff --git a/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml index 4fe57b4258..e04e541c34 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml @@ -4,6 +4,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" @@ -181,11 +182,68 @@ Width="350" ToolTip.Tip="{ext:Locale TimeTooltip}" /> - + - + VerticalAlignment="Center" + Text="{ext:Locale SettingsTabSystemVSyncMode}" + ToolTip.Tip="{ext:Locale SettingsTabSystemVSyncModeTooltip}" + Width="250" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs index 829db4bc98..059f99a60a 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -38,6 +38,8 @@ namespace Ryujinx.Ava.UI.Windows { public partial class MainWindow : StyleableAppWindow { + internal static MainWindowViewModel MainWindowViewModel { get; private set; } + public MainWindowViewModel ViewModel { get; } internal readonly AvaHostUIHandler UiHandler; @@ -73,7 +75,7 @@ public partial class MainWindow : StyleableAppWindow public MainWindow() { - DataContext = ViewModel = new MainWindowViewModel + DataContext = ViewModel = MainWindowViewModel = new MainWindowViewModel { Window = this }; From a18cecbc30168e347757ced631e652a40b001133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hack=E8=8C=B6=E3=82=93?= <120134269+Hackjjang@users.noreply.github.com> Date: Tue, 26 Nov 2024 04:40:39 +0900 Subject: [PATCH 18/61] Korean "Show Changelog" translation (#313) --- src/Ryujinx/Assets/Locales/ko_KR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ryujinx/Assets/Locales/ko_KR.json b/src/Ryujinx/Assets/Locales/ko_KR.json index 47a619054b..8baf559be7 100644 --- a/src/Ryujinx/Assets/Locales/ko_KR.json +++ b/src/Ryujinx/Assets/Locales/ko_KR.json @@ -457,7 +457,7 @@ "DialogUpdaterExtractionMessage": "업데이트 추출 중...", "DialogUpdaterRenamingMessage": "이름 변경 업데이트...", "DialogUpdaterAddingFilesMessage": "새 업데이트 추가 중...", - "DialogUpdaterShowChangelogMessage": "Show Changelog", + "DialogUpdaterShowChangelogMessage": "변경 로그 보기", "DialogUpdaterCompleteMessage": "업데이트가 완료되었습니다!", "DialogUpdaterRestartMessage": "지금 Ryujinx를 다시 시작하시겠습니까?", "DialogUpdaterNoInternetMessage": "인터넷에 연결되어 있지 않습니다!", From f72d2c1b2bd17aa25df146d31a39b98a47b524aa Mon Sep 17 00:00:00 2001 From: GabCoolGuy Date: Mon, 25 Nov 2024 20:43:01 +0100 Subject: [PATCH 19/61] UI: Add Mii Edit Applet Locale (#311) This allows the "Mii Edit Applet" dropdown to be localized ( I already went ahead and localized French ) --- src/Ryujinx/Assets/Locales/ar_SA.json | 1 + src/Ryujinx/Assets/Locales/de_DE.json | 1 + src/Ryujinx/Assets/Locales/el_GR.json | 1 + src/Ryujinx/Assets/Locales/en_US.json | 1 + src/Ryujinx/Assets/Locales/es_ES.json | 1 + src/Ryujinx/Assets/Locales/fr_FR.json | 1 + src/Ryujinx/Assets/Locales/he_IL.json | 1 + src/Ryujinx/Assets/Locales/it_IT.json | 1 + src/Ryujinx/Assets/Locales/ja_JP.json | 1 + src/Ryujinx/Assets/Locales/ko_KR.json | 1 + src/Ryujinx/Assets/Locales/pl_PL.json | 1 + src/Ryujinx/Assets/Locales/pt_BR.json | 1 + src/Ryujinx/Assets/Locales/ru_RU.json | 1 + src/Ryujinx/Assets/Locales/th_TH.json | 1 + src/Ryujinx/Assets/Locales/tr_TR.json | 1 + src/Ryujinx/Assets/Locales/uk_UA.json | 1 + src/Ryujinx/Assets/Locales/zh_CN.json | 1 + src/Ryujinx/Assets/Locales/zh_TW.json | 1 + src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml | 2 +- 19 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx/Assets/Locales/ar_SA.json b/src/Ryujinx/Assets/Locales/ar_SA.json index c937a2eede..62992ff34e 100644 --- a/src/Ryujinx/Assets/Locales/ar_SA.json +++ b/src/Ryujinx/Assets/Locales/ar_SA.json @@ -1,6 +1,7 @@ { "Language": "اَلْعَرَبِيَّةُ", "MenuBarFileOpenApplet": "فتح التطبيق المصغر", + "MenuBarFileOpenAppletOpenMiiApplet": "Mii Edit Applet", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "‫افتح تطبيق تحرير Mii في الوضع المستقل", "SettingsTabInputDirectMouseAccess": "الوصول المباشر للفأرة", "SettingsTabSystemMemoryManagerMode": "وضع إدارة الذاكرة:", diff --git a/src/Ryujinx/Assets/Locales/de_DE.json b/src/Ryujinx/Assets/Locales/de_DE.json index c27de56087..91141b7af5 100644 --- a/src/Ryujinx/Assets/Locales/de_DE.json +++ b/src/Ryujinx/Assets/Locales/de_DE.json @@ -1,6 +1,7 @@ { "Language": "Deutsch", "MenuBarFileOpenApplet": "Öffne Anwendung", + "MenuBarFileOpenAppletOpenMiiApplet": "Mii Edit Applet", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Öffnet das Mii-Editor-Applet im Standalone-Modus", "SettingsTabInputDirectMouseAccess": "Direkter Mauszugriff", "SettingsTabSystemMemoryManagerMode": "Speichermanagermodus:", diff --git a/src/Ryujinx/Assets/Locales/el_GR.json b/src/Ryujinx/Assets/Locales/el_GR.json index d47c8b9fe9..a589d31ad3 100644 --- a/src/Ryujinx/Assets/Locales/el_GR.json +++ b/src/Ryujinx/Assets/Locales/el_GR.json @@ -1,6 +1,7 @@ { "Language": "Ελληνικά", "MenuBarFileOpenApplet": "Άνοιγμα Applet", + "MenuBarFileOpenAppletOpenMiiApplet": "Mii Edit Applet", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Άνοιγμα του Mii Editor Applet σε Αυτόνομη λειτουργία", "SettingsTabInputDirectMouseAccess": "Άμεση Πρόσβαση Ποντικιού", "SettingsTabSystemMemoryManagerMode": "Λειτουργία Διαχείρισης Μνήμης:", diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json index 13ffeb759f..90290b7607 100644 --- a/src/Ryujinx/Assets/Locales/en_US.json +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -1,6 +1,7 @@ { "Language": "English (US)", "MenuBarFileOpenApplet": "Open Applet", + "MenuBarFileOpenAppletOpenMiiApplet": "Mii Edit Applet", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Open Mii Editor Applet in Standalone mode", "SettingsTabInputDirectMouseAccess": "Direct Mouse Access", "SettingsTabSystemMemoryManagerMode": "Memory Manager Mode:", diff --git a/src/Ryujinx/Assets/Locales/es_ES.json b/src/Ryujinx/Assets/Locales/es_ES.json index 8456040ce5..8a426b3a41 100644 --- a/src/Ryujinx/Assets/Locales/es_ES.json +++ b/src/Ryujinx/Assets/Locales/es_ES.json @@ -1,6 +1,7 @@ { "Language": "Español (ES)", "MenuBarFileOpenApplet": "Abrir applet", + "MenuBarFileOpenAppletOpenMiiApplet": "Mii Edit Applet", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Abre el editor de Mii en modo autónomo", "SettingsTabInputDirectMouseAccess": "Acceso directo al ratón", "SettingsTabSystemMemoryManagerMode": "Modo del administrador de memoria:", diff --git a/src/Ryujinx/Assets/Locales/fr_FR.json b/src/Ryujinx/Assets/Locales/fr_FR.json index f17a7ba952..355c2814da 100644 --- a/src/Ryujinx/Assets/Locales/fr_FR.json +++ b/src/Ryujinx/Assets/Locales/fr_FR.json @@ -1,6 +1,7 @@ { "Language": "Français", "MenuBarFileOpenApplet": "Ouvrir un programme", + "MenuBarFileOpenAppletOpenMiiApplet": "Éditeur de Mii", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Ouvrir l'éditeur Mii en mode Standalone", "SettingsTabInputDirectMouseAccess": "Accès direct à la souris", "SettingsTabSystemMemoryManagerMode": "Mode de gestion de la mémoire :", diff --git a/src/Ryujinx/Assets/Locales/he_IL.json b/src/Ryujinx/Assets/Locales/he_IL.json index f0cf4eb682..51c3c88359 100644 --- a/src/Ryujinx/Assets/Locales/he_IL.json +++ b/src/Ryujinx/Assets/Locales/he_IL.json @@ -1,6 +1,7 @@ { "Language": "עִברִית", "MenuBarFileOpenApplet": "פתח יישומון", + "MenuBarFileOpenAppletOpenMiiApplet": "Mii Edit Applet", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "פתח את יישומון עורך ה- Mii במצב עצמאי", "SettingsTabInputDirectMouseAccess": "גישה ישירה לעכבר", "SettingsTabSystemMemoryManagerMode": "מצב מנהל זיכרון:", diff --git a/src/Ryujinx/Assets/Locales/it_IT.json b/src/Ryujinx/Assets/Locales/it_IT.json index dd408bf5b8..52ea833d3c 100644 --- a/src/Ryujinx/Assets/Locales/it_IT.json +++ b/src/Ryujinx/Assets/Locales/it_IT.json @@ -1,6 +1,7 @@ { "Language": "Italiano", "MenuBarFileOpenApplet": "Apri applet", + "MenuBarFileOpenAppletOpenMiiApplet": "Mii Edit Applet", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Apri l'applet Mii Editor in modalità Standalone", "SettingsTabInputDirectMouseAccess": "Accesso diretto al mouse", "SettingsTabSystemMemoryManagerMode": "Modalità di gestione della memoria:", diff --git a/src/Ryujinx/Assets/Locales/ja_JP.json b/src/Ryujinx/Assets/Locales/ja_JP.json index 2447304941..59b7aa3b37 100644 --- a/src/Ryujinx/Assets/Locales/ja_JP.json +++ b/src/Ryujinx/Assets/Locales/ja_JP.json @@ -1,6 +1,7 @@ { "Language": "日本語", "MenuBarFileOpenApplet": "アプレットを開く", + "MenuBarFileOpenAppletOpenMiiApplet": "Mii Edit Applet", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "スタンドアロンモードで Mii エディタアプレットを開きます", "SettingsTabInputDirectMouseAccess": "マウス直接アクセス", "SettingsTabSystemMemoryManagerMode": "メモリ管理モード:", diff --git a/src/Ryujinx/Assets/Locales/ko_KR.json b/src/Ryujinx/Assets/Locales/ko_KR.json index 8baf559be7..aeeb84c629 100644 --- a/src/Ryujinx/Assets/Locales/ko_KR.json +++ b/src/Ryujinx/Assets/Locales/ko_KR.json @@ -1,6 +1,7 @@ { "Language": "한국어", "MenuBarFileOpenApplet": "애플릿 열기", + "MenuBarFileOpenAppletOpenMiiApplet": "Mii Edit Applet", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "독립 실행형 모드로 Mii 편집기 애플릿 열기", "SettingsTabInputDirectMouseAccess": "마우스 직접 접근", "SettingsTabSystemMemoryManagerMode": "메모리 관리자 모드 :", diff --git a/src/Ryujinx/Assets/Locales/pl_PL.json b/src/Ryujinx/Assets/Locales/pl_PL.json index cfa9d7a76a..1d8cf4f03d 100644 --- a/src/Ryujinx/Assets/Locales/pl_PL.json +++ b/src/Ryujinx/Assets/Locales/pl_PL.json @@ -1,6 +1,7 @@ { "Language": "Polski", "MenuBarFileOpenApplet": "Otwórz Aplet", + "MenuBarFileOpenAppletOpenMiiApplet": "Mii Edit Applet", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Otwórz aplet Mii Editor w trybie indywidualnym", "SettingsTabInputDirectMouseAccess": "Bezpośredni dostęp do myszy", "SettingsTabSystemMemoryManagerMode": "Tryb menedżera pamięci:", diff --git a/src/Ryujinx/Assets/Locales/pt_BR.json b/src/Ryujinx/Assets/Locales/pt_BR.json index 352fae46ba..7574c1d20e 100644 --- a/src/Ryujinx/Assets/Locales/pt_BR.json +++ b/src/Ryujinx/Assets/Locales/pt_BR.json @@ -1,6 +1,7 @@ { "Language": "Português (BR)", "MenuBarFileOpenApplet": "Abrir Applet", + "MenuBarFileOpenAppletOpenMiiApplet": "Mii Edit Applet", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Abrir editor Mii em modo avulso", "SettingsTabInputDirectMouseAccess": "Acesso direto ao mouse", "SettingsTabSystemMemoryManagerMode": "Modo de gerenciamento de memória:", diff --git a/src/Ryujinx/Assets/Locales/ru_RU.json b/src/Ryujinx/Assets/Locales/ru_RU.json index 112735e2d3..86e51f09fe 100644 --- a/src/Ryujinx/Assets/Locales/ru_RU.json +++ b/src/Ryujinx/Assets/Locales/ru_RU.json @@ -1,6 +1,7 @@ { "Language": "Русский (RU)", "MenuBarFileOpenApplet": "Открыть апплет", + "MenuBarFileOpenAppletOpenMiiApplet": "Mii Edit Applet", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Открывает апплет Mii Editor в автономном режиме", "SettingsTabInputDirectMouseAccess": "Прямой ввод мыши", "SettingsTabSystemMemoryManagerMode": "Режим менеджера памяти:", diff --git a/src/Ryujinx/Assets/Locales/th_TH.json b/src/Ryujinx/Assets/Locales/th_TH.json index 35959ddbd9..259828583f 100644 --- a/src/Ryujinx/Assets/Locales/th_TH.json +++ b/src/Ryujinx/Assets/Locales/th_TH.json @@ -1,6 +1,7 @@ { "Language": "ภาษาไทย", "MenuBarFileOpenApplet": "เปิด Applet", + "MenuBarFileOpenAppletOpenMiiApplet": "Mii Edit Applet", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "เปิดโปรแกรม Mii Editor Applet", "SettingsTabInputDirectMouseAccess": "เข้าถึงเมาส์ได้โดยตรง", "SettingsTabSystemMemoryManagerMode": "โหมดจัดการหน่วยความจำ:", diff --git a/src/Ryujinx/Assets/Locales/tr_TR.json b/src/Ryujinx/Assets/Locales/tr_TR.json index 5d50b67dbd..18dbb12b0d 100644 --- a/src/Ryujinx/Assets/Locales/tr_TR.json +++ b/src/Ryujinx/Assets/Locales/tr_TR.json @@ -1,6 +1,7 @@ { "Language": "Türkçe", "MenuBarFileOpenApplet": "Applet'i Aç", + "MenuBarFileOpenAppletOpenMiiApplet": "Mii Edit Applet", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Mii Editör Applet'ini Bağımsız Mod'da Aç", "SettingsTabInputDirectMouseAccess": "Doğrudan Mouse Erişimi", "SettingsTabSystemMemoryManagerMode": "Hafıza Yönetim Modu:", diff --git a/src/Ryujinx/Assets/Locales/uk_UA.json b/src/Ryujinx/Assets/Locales/uk_UA.json index a45208486d..e123afa6b1 100644 --- a/src/Ryujinx/Assets/Locales/uk_UA.json +++ b/src/Ryujinx/Assets/Locales/uk_UA.json @@ -1,6 +1,7 @@ { "Language": "Українська", "MenuBarFileOpenApplet": "Відкрити аплет", + "MenuBarFileOpenAppletOpenMiiApplet": "Mii Edit Applet", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Відкрити аплет Mii Editor в автономному режимі", "SettingsTabInputDirectMouseAccess": "Прямий доступ мишею", "SettingsTabSystemMemoryManagerMode": "Режим диспетчера пам’яті:", diff --git a/src/Ryujinx/Assets/Locales/zh_CN.json b/src/Ryujinx/Assets/Locales/zh_CN.json index 8a4995ea70..8fcd41cd25 100644 --- a/src/Ryujinx/Assets/Locales/zh_CN.json +++ b/src/Ryujinx/Assets/Locales/zh_CN.json @@ -1,6 +1,7 @@ { "Language": "简体中文", "MenuBarFileOpenApplet": "打开小程序", + "MenuBarFileOpenAppletOpenMiiApplet": "Mii Edit Applet", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "打开独立的 Mii 小程序", "SettingsTabInputDirectMouseAccess": "直通鼠标操作", "SettingsTabSystemMemoryManagerMode": "内存管理模式:", diff --git a/src/Ryujinx/Assets/Locales/zh_TW.json b/src/Ryujinx/Assets/Locales/zh_TW.json index 5649ba00aa..d219bc7088 100644 --- a/src/Ryujinx/Assets/Locales/zh_TW.json +++ b/src/Ryujinx/Assets/Locales/zh_TW.json @@ -1,6 +1,7 @@ { "Language": "繁體中文 (台灣)", "MenuBarFileOpenApplet": "開啟小程式", + "MenuBarFileOpenAppletOpenMiiApplet": "Mii Edit Applet", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "在獨立模式下開啟 Mii 編輯器小程式", "SettingsTabInputDirectMouseAccess": "滑鼠直接存取", "SettingsTabSystemMemoryManagerMode": "記憶體管理員模式:", diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml index 883bf89711..6cf76cf496 100644 --- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml @@ -58,7 +58,7 @@ From 0caeab22707b336d66427d91b35c437f44d9c6d2 Mon Sep 17 00:00:00 2001 From: Luke Warner <65521430+LukeWarnut@users.noreply.github.com> Date: Mon, 25 Nov 2024 14:46:41 -0500 Subject: [PATCH 20/61] Remove 'Enter' hotkey in settings menu (#95) This allows the Enter key to be bound to a button when using the Avalonia UI. --- src/Ryujinx/UI/Windows/SettingsWindow.axaml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Ryujinx/UI/Windows/SettingsWindow.axaml b/src/Ryujinx/UI/Windows/SettingsWindow.axaml index f9d10fe4f6..2bf5b55e79 100644 --- a/src/Ryujinx/UI/Windows/SettingsWindow.axaml +++ b/src/Ryujinx/UI/Windows/SettingsWindow.axaml @@ -109,7 +109,6 @@ HorizontalAlignment="Right" ReverseOrder="{Binding IsMacOS}"> /// Guest address /// Value of the from the specified guest - public int GetValue(ulong address) + public long GetValue(ulong address) { - return (int)((address & Mask) >> Index); + return (long)((address & Mask) >> Index); } } } diff --git a/src/ARMeilleure/Translation/PTC/Ptc.cs b/src/ARMeilleure/Translation/PTC/Ptc.cs index c722ce6be2..841e5fefa3 100644 --- a/src/ARMeilleure/Translation/PTC/Ptc.cs +++ b/src/ARMeilleure/Translation/PTC/Ptc.cs @@ -30,7 +30,7 @@ class Ptc : IPtcLoadState private const string OuterHeaderMagicString = "PTCohd\0\0"; private const string InnerHeaderMagicString = "PTCihd\0\0"; - private const uint InternalVersion = 6992; //! To be incremented manually for each change to the ARMeilleure project. + private const uint InternalVersion = 6997; //! To be incremented manually for each change to the ARMeilleure project. private const string ActualDir = "0"; private const string BackupDir = "1"; diff --git a/src/Ryujinx.Cpu/AddressTable.cs b/src/Ryujinx.Cpu/AddressTable.cs index d87b12ab01..038a2009cd 100644 --- a/src/Ryujinx.Cpu/AddressTable.cs +++ b/src/Ryujinx.Cpu/AddressTable.cs @@ -238,7 +238,7 @@ public ref TEntry GetValue(ulong address) { TEntry* page = GetPage(address); - int index = Levels[^1].GetValue(address); + long index = Levels[^1].GetValue(address); EnsureMapped((IntPtr)(page + index)); From 3680df6092394493f165f963cee4b202b63beb96 Mon Sep 17 00:00:00 2001 From: Piplup <100526773+piplup55@users.noreply.github.com> Date: Sat, 30 Nov 2024 23:17:30 +0000 Subject: [PATCH 27/61] Fix for missing text with specific system locale encoding (#330) --- distribution/linux/Ryujinx.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distribution/linux/Ryujinx.sh b/distribution/linux/Ryujinx.sh index 30eb143991..daeea9bfdb 100755 --- a/distribution/linux/Ryujinx.sh +++ b/distribution/linux/Ryujinx.sh @@ -14,7 +14,7 @@ if [ -z "$RYUJINX_BIN" ]; then exit 1 fi -COMMAND="env DOTNET_EnableAlternateStackCheck=1" +COMMAND="env LANG=C.UTF-8 DOTNET_EnableAlternateStackCheck=1" if command -v gamemoderun > /dev/null 2>&1; then COMMAND="$COMMAND gamemoderun" From 6b5cb151c3574d6b08f421071968121bbed6ab7f Mon Sep 17 00:00:00 2001 From: Luke Warner <65521430+LukeWarnut@users.noreply.github.com> Date: Sat, 30 Nov 2024 18:20:48 -0500 Subject: [PATCH 28/61] Implement and stub services required for Mario Kart Live: Home Circuit (#331) These changes allow Mario Kart Live: Home Circuit (v2.0.0) to boot into menus. Kart functionality has not been implemented and will not work. Version 1.0.0 is currently unsupported due to unimplemented ARM registers. I plan on addressing this issue at a later date. ### Here is a list of the implemented and stubbed services in this PR: #### Implemented: Ldn.Lp2p.IServiceCreator: 0 (CreateNetworkService) Ldn.Lp2p.IServiceCreator: 8 (CreateNetworkServiceMonitor) Ldn.Lp2p.ISfService: 0 (Initialize) Ldn.Lp2p.ISfServiceMonitor: 0 (Initialize) Ldn.Lp2p.ISfServiceMonitor: 256 (AttachNetworkInterfaceStateChangeEvent) Ldn.Lp2p.ISfServiceMonitor: 328 (AttachJoinEvent) #### Stubbed: Ldn.Lp2p.ISfService: 768 (CreateGroup) Ldn.Lp2p.ISfService: 1536 (SendToOtherGroup) Ldn.Lp2p.ISfService: 1544 (RecvFromOtherGroup) Ldn.Lp2p.ISfServiceMonitor: 288 (GetGroupInfo) Ldn.Lp2p.ISfServiceMonitor: 296 (GetGroupInfo2) Ldn.Lp2p.ISfServiceMonitor: 312 (GetIpConfig) --- .../HOS/Services/Ldn/Lp2p/IServiceCreator.cs | 18 ++++ .../HOS/Services/Ldn/Lp2p/ISfService.cs | 45 ++++++++++ .../Services/Ldn/Lp2p/ISfServiceMonitor.cs | 86 +++++++++++++++++++ 3 files changed, 149 insertions(+) create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfServiceMonitor.cs diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/IServiceCreator.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/IServiceCreator.cs index 797a7a9bd1..705e5f258b 100644 --- a/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/IServiceCreator.cs +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/IServiceCreator.cs @@ -5,5 +5,23 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.Lp2p class IServiceCreator : IpcService { public IServiceCreator(ServiceCtx context) { } + + [CommandCmif(0)] + // CreateNetworkService(pid, u64, u32) -> object + public ResultCode CreateNetworkService(ServiceCtx context) + { + MakeObject(context, new ISfService(context)); + + return ResultCode.Success; + } + + [CommandCmif(8)] + // CreateNetworkServiceMonitor(pid, u64) -> object + public ResultCode CreateNetworkServiceMonitor(ServiceCtx context) + { + MakeObject(context, new ISfServiceMonitor(context)); + + return ResultCode.Success; + } } } diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfService.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfService.cs new file mode 100644 index 0000000000..d48a889788 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfService.cs @@ -0,0 +1,45 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Ldn.Lp2p +{ + class ISfService : IpcService + { + public ISfService(ServiceCtx context) { } + + [CommandCmif(0)] + // Initialize() + public ResultCode Initialize(ServiceCtx context) + { + context.ResponseData.Write(0); + + return ResultCode.Success; + } + + [CommandCmif(768)] + // CreateGroup(buffer) + public ResultCode SendToOtherGroup(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceLdn); + + return ResultCode.Success; + } + + [CommandCmif(1544)] + // RecvFromOtherGroup(u32, buffer) -> (nn::lp2p::MacAddress, u16, s16, u32, s32) + public ResultCode RecvFromOtherGroup(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceLdn); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfServiceMonitor.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfServiceMonitor.cs new file mode 100644 index 0000000000..d3a8bead25 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfServiceMonitor.cs @@ -0,0 +1,86 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Ldn.Lp2p +{ + class ISfServiceMonitor : IpcService + { + private readonly KEvent _stateChangeEvent; + private readonly KEvent _jointEvent; + private int _stateChangeEventHandle = 0; + private int _jointEventHandle = 0; + + public ISfServiceMonitor(ServiceCtx context) + { + _stateChangeEvent = new KEvent(context.Device.System.KernelContext); + _jointEvent = new KEvent(context.Device.System.KernelContext); + } + + [CommandCmif(0)] + // Initialize() + public ResultCode Initialize(ServiceCtx context) + { + context.ResponseData.Write(0); + + return ResultCode.Success; + } + + [CommandCmif(256)] + // AttachNetworkInterfaceStateChangeEvent() -> handle + public ResultCode AttachNetworkInterfaceStateChangeEvent(ServiceCtx context) + { + if (context.Process.HandleTable.GenerateHandle(_stateChangeEvent.ReadableEvent, out _stateChangeEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_stateChangeEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(288)] + // GetGroupInfo(buffer) + public ResultCode GetGroupInfo(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceLdn); + + return ResultCode.Success; + } + + [CommandCmif(296)] + // GetGroupInfo2(buffer, buffer) + public ResultCode GetGroupInfo2(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceLdn); + + return ResultCode.Success; + } + + [CommandCmif(312)] + // GetIpConfig(buffer, 0x1a>) + public ResultCode GetIpConfig(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceLdn); + + return ResultCode.Success; + } + + [CommandCmif(328)] + // AttachNetworkInterfaceStateChangeEvent() -> handle + public ResultCode AttachJoinEvent(ServiceCtx context) + { + if (context.Process.HandleTable.GenerateHandle(_jointEvent.ReadableEvent, out _jointEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_jointEventHandle); + + return ResultCode.Success; + } + } +} From 17483aad247c6c7ee97337e1a11140de70aebda9 Mon Sep 17 00:00:00 2001 From: Luke Warner <65521430+LukeWarnut@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:42:07 -0500 Subject: [PATCH 29/61] ARMeilleure: Allow TPIDR2_EL0 to be set properly (#339) --- src/ARMeilleure/Instructions/InstEmitSystem.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/ARMeilleure/Instructions/InstEmitSystem.cs b/src/ARMeilleure/Instructions/InstEmitSystem.cs index fbf3b4a709..11c1d0328e 100644 --- a/src/ARMeilleure/Instructions/InstEmitSystem.cs +++ b/src/ARMeilleure/Instructions/InstEmitSystem.cs @@ -88,7 +88,7 @@ public static void Msr(ArmEmitterContext context) EmitSetTpidrEl0(context); return; case 0b11_011_1101_0000_101: - EmitGetTpidr2El0(context); + EmitSetTpidr2El0(context); return; default: @@ -291,5 +291,16 @@ private static void EmitSetTpidrEl0(ArmEmitterContext context) context.Store(context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())), value); } + + private static void EmitSetTpidr2El0(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + Operand value = GetIntOrZR(context, op.Rt); + + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + + context.Store(context.Add(nativeContext, Const((ulong)NativeContext.GetTpidr2El0Offset())), value); + } } } From 08b7257be5ca27b0f4fdd0269d325dd58f68a4c5 Mon Sep 17 00:00:00 2001 From: Jacobwasbeast <38381609+Jacobwasbeast@users.noreply.github.com> Date: Mon, 2 Dec 2024 23:40:02 -0600 Subject: [PATCH 30/61] Add the Cabinet Applet (#340) This adds the missing Cabinet Applet, which allows for formatting Amiibos and changing their names. --- src/Ryujinx.HLE/HOS/Applets/AppletManager.cs | 3 + .../HOS/Applets/Cabinet/CabinetApplet.cs | 195 ++++++++++++++++++ .../HOS/Services/Nfc/Nfp/VirtualAmiibo.cs | 7 + src/Ryujinx.HLE/UI/IHostUIHandler.cs | 12 ++ src/Ryujinx.Headless.SDL2/WindowBase.cs | 14 ++ src/Ryujinx/Assets/Locales/ar_SA.json | 3 + src/Ryujinx/Assets/Locales/de_DE.json | 3 + src/Ryujinx/Assets/Locales/el_GR.json | 3 + src/Ryujinx/Assets/Locales/en_US.json | 3 + src/Ryujinx/Assets/Locales/es_ES.json | 3 + src/Ryujinx/Assets/Locales/fr_FR.json | 3 + src/Ryujinx/Assets/Locales/he_IL.json | 3 + src/Ryujinx/Assets/Locales/it_IT.json | 3 + src/Ryujinx/Assets/Locales/ja_JP.json | 3 + src/Ryujinx/Assets/Locales/ko_KR.json | 3 + src/Ryujinx/Assets/Locales/pl_PL.json | 3 + src/Ryujinx/Assets/Locales/pt_BR.json | 3 + src/Ryujinx/Assets/Locales/ru_RU.json | 3 + src/Ryujinx/Assets/Locales/th_TH.json | 3 + src/Ryujinx/Assets/Locales/tr_TR.json | 3 + src/Ryujinx/Assets/Locales/uk_UA.json | 3 + src/Ryujinx/Assets/Locales/zh_CN.json | 3 + src/Ryujinx/Assets/Locales/zh_TW.json | 3 + src/Ryujinx/UI/Applet/AvaHostUIHandler.cs | 50 +++++ 24 files changed, 335 insertions(+) create mode 100644 src/Ryujinx.HLE/HOS/Applets/Cabinet/CabinetApplet.cs diff --git a/src/Ryujinx.HLE/HOS/Applets/AppletManager.cs b/src/Ryujinx.HLE/HOS/Applets/AppletManager.cs index da4d2e51b3..a2ddd573de 100644 --- a/src/Ryujinx.HLE/HOS/Applets/AppletManager.cs +++ b/src/Ryujinx.HLE/HOS/Applets/AppletManager.cs @@ -1,5 +1,6 @@ using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Applets.Browser; +using Ryujinx.HLE.HOS.Applets.Cabinet; using Ryujinx.HLE.HOS.Applets.Dummy; using Ryujinx.HLE.HOS.Applets.Error; using Ryujinx.HLE.HOS.Services.Am.AppletAE; @@ -31,6 +32,8 @@ public static IApplet Create(AppletId applet, Horizon system) case AppletId.MiiEdit: Logger.Warning?.Print(LogClass.Application, $"Please use the MiiEdit inside File/Open Applet"); return new DummyApplet(system); + case AppletId.Cabinet: + return new CabinetApplet(system); } Logger.Warning?.Print(LogClass.Application, $"Applet {applet} not implemented!"); diff --git a/src/Ryujinx.HLE/HOS/Applets/Cabinet/CabinetApplet.cs b/src/Ryujinx.HLE/HOS/Applets/Cabinet/CabinetApplet.cs new file mode 100644 index 0000000000..f4f935d34b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Cabinet/CabinetApplet.cs @@ -0,0 +1,195 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Am.AppletAE; +using Ryujinx.HLE.HOS.Services.Hid.HidServer; +using Ryujinx.HLE.HOS.Services.Hid; +using Ryujinx.HLE.HOS.Services.Nfc.Nfp; +using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Applets.Cabinet +{ + internal unsafe class CabinetApplet : IApplet + { + private readonly Horizon _system; + private AppletSession _normalSession; + + public event EventHandler AppletStateChanged; + + public CabinetApplet(Horizon system) + { + _system = system; + } + + public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession) + { + _normalSession = normalSession; + + byte[] launchParams = _normalSession.Pop(); + byte[] startParamBytes = _normalSession.Pop(); + + StartParamForAmiiboSettings startParam = IApplet.ReadStruct(startParamBytes); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, $"CabinetApplet Start Type: {startParam.Type}"); + + switch (startParam.Type) + { + case 0: + StartNicknameAndOwnerSettings(ref startParam); + break; + case 1: + case 3: + StartFormatter(ref startParam); + break; + default: + Logger.Error?.Print(LogClass.ServiceAm, $"Unknown AmiiboSettings type: {startParam.Type}"); + break; + } + + // Prepare the response + ReturnValueForAmiiboSettings returnValue = new() + { + AmiiboSettingsReturnFlag = (byte)AmiiboSettingsReturnFlag.HasRegisterInfo, + DeviceHandle = new DeviceHandle + { + Handle = 0 // Dummy device handle + }, + RegisterInfo = startParam.RegisterInfo + }; + + // Push the response + _normalSession.Push(BuildResponse(returnValue)); + AppletStateChanged?.Invoke(this, null); + + _system.ReturnFocus(); + + return ResultCode.Success; + } + + public ResultCode GetResult() + { + _system.Device.System.NfpDevices.RemoveAt(0); + return ResultCode.Success; + } + + private void StartFormatter(ref StartParamForAmiiboSettings startParam) + { + // Initialize RegisterInfo + startParam.RegisterInfo = new RegisterInfo(); + } + + private void StartNicknameAndOwnerSettings(ref StartParamForAmiiboSettings startParam) + { + _system.Device.UIHandler.DisplayCabinetDialog(out string newName); + byte[] nameBytes = Encoding.UTF8.GetBytes(newName); + Array41 nickName = new Array41(); + nameBytes.CopyTo(nickName.AsSpan()); + startParam.RegisterInfo.Nickname = nickName; + NfpDevice devicePlayer1 = new() + { + NpadIdType = NpadIdType.Player1, + Handle = HidUtils.GetIndexFromNpadIdType(NpadIdType.Player1), + State = NfpDeviceState.SearchingForTag, + }; + _system.Device.System.NfpDevices.Add(devicePlayer1); + _system.Device.UIHandler.DisplayCabinetMessageDialog(); + string amiiboId = string.Empty; + bool scanned = false; + while (!scanned) + { + for (int i = 0; i < _system.Device.System.NfpDevices.Count; i++) + { + if (_system.Device.System.NfpDevices[i].State == NfpDeviceState.TagFound) + { + amiiboId = _system.Device.System.NfpDevices[i].AmiiboId; + scanned = true; + } + } + } + VirtualAmiibo.UpdateNickName(amiiboId, newName); + } + + private static byte[] BuildResponse(ReturnValueForAmiiboSettings returnValue) + { + int size = Unsafe.SizeOf(); + byte[] bytes = new byte[size]; + + fixed (byte* bytesPtr = bytes) + { + Unsafe.Write(bytesPtr, returnValue); + } + + return bytes; + } + + public static T ReadStruct(byte[] data) where T : unmanaged + { + if (data.Length < Unsafe.SizeOf()) + { + throw new ArgumentException("Not enough data to read the struct"); + } + + fixed (byte* dataPtr = data) + { + return Unsafe.Read(dataPtr); + } + } + + #region Structs + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct TagInfo + { + public fixed byte Data[0x58]; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct StartParamForAmiiboSettings + { + public byte ZeroValue; // Left at zero by sdknso + public byte Type; + public byte Flags; + public byte AmiiboSettingsStartParamOffset28; + public ulong AmiiboSettingsStartParam0; + + public TagInfo TagInfo; // Only enabled when flags bit 1 is set + public RegisterInfo RegisterInfo; // Only enabled when flags bit 2 is set + + public fixed byte StartParamExtraData[0x20]; + + public fixed byte Reserved[0x24]; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct ReturnValueForAmiiboSettings + { + public byte AmiiboSettingsReturnFlag; + private byte Padding1; + private byte Padding2; + private byte Padding3; + public DeviceHandle DeviceHandle; + public TagInfo TagInfo; + public RegisterInfo RegisterInfo; + public fixed byte IgnoredBySdknso[0x24]; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DeviceHandle + { + public ulong Handle; + } + + public enum AmiiboSettingsReturnFlag : byte + { + Cancel = 0, + HasTagInfo = 2, + HasRegisterInfo = 4, + HasTagInfoAndRegisterInfo = 6 + } + + #endregion + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs index 7ce749d1a7..0c685471c7 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs @@ -93,6 +93,13 @@ public static RegisterInfo GetRegisterInfo(ITickSource tickSource, string amiibo return registerInfo; } + public static void UpdateNickName(string amiiboId, string newNickName) + { + VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); + virtualAmiiboFile.NickName = newNickName; + SaveAmiiboFile(virtualAmiiboFile); + } + public static bool OpenApplicationArea(string amiiboId, uint applicationAreaId) { VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); diff --git a/src/Ryujinx.HLE/UI/IHostUIHandler.cs b/src/Ryujinx.HLE/UI/IHostUIHandler.cs index 8debfcca0d..88af837359 100644 --- a/src/Ryujinx.HLE/UI/IHostUIHandler.cs +++ b/src/Ryujinx.HLE/UI/IHostUIHandler.cs @@ -24,6 +24,18 @@ public interface IHostUIHandler /// True when OK is pressed, False otherwise. bool DisplayMessageDialog(ControllerAppletUIArgs args); + /// + /// Displays an Input Dialog box to the user so they can enter the Amiibo's new name + /// + /// Text that the user entered. Set to `null` on internal errors + /// True when OK is pressed, False otherwise. Also returns True on internal errors + bool DisplayCabinetDialog(out string userText); + + /// + /// Displays a Message Dialog box to the user to notify them to scan the Amiibo. + /// + void DisplayCabinetMessageDialog(); + /// /// Tell the UI that we need to transition to another program. /// diff --git a/src/Ryujinx.Headless.SDL2/WindowBase.cs b/src/Ryujinx.Headless.SDL2/WindowBase.cs index 2479ec1272..fbe7cb49c4 100644 --- a/src/Ryujinx.Headless.SDL2/WindowBase.cs +++ b/src/Ryujinx.Headless.SDL2/WindowBase.cs @@ -1,4 +1,5 @@ using Humanizer; +using LibHac.Tools.Fs; using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Logging; @@ -485,6 +486,19 @@ public bool DisplayMessageDialog(string title, string message) return true; } + public bool DisplayCabinetDialog(out string userText) + { + // SDL2 doesn't support input dialogs + userText = "Ryujinx"; + + return true; + } + + public void DisplayCabinetMessageDialog() + { + SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags.SDL_MESSAGEBOX_INFORMATION, "Cabinet Dialog", "Please scan your Amiibo now.", WindowHandle); + } + public bool DisplayMessageDialog(ControllerAppletUIArgs args) { if (_ignoreControllerApplet) return false; diff --git a/src/Ryujinx/Assets/Locales/ar_SA.json b/src/Ryujinx/Assets/Locales/ar_SA.json index 34b4f72124..c1ee30f19d 100644 --- a/src/Ryujinx/Assets/Locales/ar_SA.json +++ b/src/Ryujinx/Assets/Locales/ar_SA.json @@ -702,6 +702,9 @@ "Never": "مطلقا", "SwkbdMinCharacters": "يجب أن يبلغ طوله {0} حرفا على الأقل", "SwkbdMinRangeCharacters": "يجب أن يتكون من {0}-{1} حرفا", + "CabinetTitle": "Cabinet Dialog", + "CabinetDialog": "Enter your Amiibo's new name", + "CabinetScanDialog": "Please scan your Amiibo now.", "SoftwareKeyboard": "لوحة المفاتيح البرمجية", "SoftwareKeyboardModeNumeric": "يجب أن يكون 0-9 أو '.' فقط", "SoftwareKeyboardModeAlphabet": "يجب أن تكون الأحرف غير CJK فقط", diff --git a/src/Ryujinx/Assets/Locales/de_DE.json b/src/Ryujinx/Assets/Locales/de_DE.json index 0131207387..e3f6b1be18 100644 --- a/src/Ryujinx/Assets/Locales/de_DE.json +++ b/src/Ryujinx/Assets/Locales/de_DE.json @@ -702,6 +702,9 @@ "Never": "Niemals", "SwkbdMinCharacters": "Muss mindestens {0} Zeichen lang sein", "SwkbdMinRangeCharacters": "Muss {0}-{1} Zeichen lang sein", + "CabinetTitle": "Cabinet Dialog", + "CabinetDialog": "Enter your Amiibo's new name", + "CabinetScanDialog": "Please scan your Amiibo now.", "SoftwareKeyboard": "Software-Tastatur", "SoftwareKeyboardModeNumeric": "Darf nur 0-9 oder \".\" sein", "SoftwareKeyboardModeAlphabet": "Keine CJK-Zeichen", diff --git a/src/Ryujinx/Assets/Locales/el_GR.json b/src/Ryujinx/Assets/Locales/el_GR.json index c5d6a60e6f..e93e9310ae 100644 --- a/src/Ryujinx/Assets/Locales/el_GR.json +++ b/src/Ryujinx/Assets/Locales/el_GR.json @@ -702,6 +702,9 @@ "Never": "Ποτέ", "SwkbdMinCharacters": "Πρέπει να έχει μήκος τουλάχιστον {0} χαρακτήρες", "SwkbdMinRangeCharacters": "Πρέπει να έχει μήκος {0}-{1} χαρακτήρες", + "CabinetTitle": "Cabinet Dialog", + "CabinetDialog": "Enter your Amiibo's new name", + "CabinetScanDialog": "Please scan your Amiibo now.", "SoftwareKeyboard": "Εικονικό Πληκτρολόγιο", "SoftwareKeyboardModeNumeric": "Πρέπει να είναι 0-9 ή '.' μόνο", "SoftwareKeyboardModeAlphabet": "Πρέπει να μην είναι μόνο χαρακτήρες CJK", diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json index b7ab8969b4..ee0d031718 100644 --- a/src/Ryujinx/Assets/Locales/en_US.json +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -714,6 +714,9 @@ "Never": "Never", "SwkbdMinCharacters": "Must be at least {0} characters long", "SwkbdMinRangeCharacters": "Must be {0}-{1} characters long", + "CabinetTitle": "Cabinet Dialog", + "CabinetDialog": "Enter your Amiibo's new name", + "CabinetScanDialog": "Please scan your Amiibo now.", "SoftwareKeyboard": "Software Keyboard", "SoftwareKeyboardModeNumeric": "Must be 0-9 or '.' only", "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", diff --git a/src/Ryujinx/Assets/Locales/es_ES.json b/src/Ryujinx/Assets/Locales/es_ES.json index 730bd7961f..0a68d44c6d 100644 --- a/src/Ryujinx/Assets/Locales/es_ES.json +++ b/src/Ryujinx/Assets/Locales/es_ES.json @@ -702,6 +702,9 @@ "Never": "Nunca", "SwkbdMinCharacters": "Debe tener al menos {0} caracteres", "SwkbdMinRangeCharacters": "Debe tener {0}-{1} caracteres", + "CabinetTitle": "Cabinet Dialog", + "CabinetDialog": "Enter your Amiibo's new name", + "CabinetScanDialog": "Please scan your Amiibo now.", "SoftwareKeyboard": "Teclado de software", "SoftwareKeyboardModeNumeric": "Debe ser sólo 0-9 o '.'", "SoftwareKeyboardModeAlphabet": "Solo deben ser caracteres no CJK", diff --git a/src/Ryujinx/Assets/Locales/fr_FR.json b/src/Ryujinx/Assets/Locales/fr_FR.json index 947c48eabb..471dfbe5e7 100644 --- a/src/Ryujinx/Assets/Locales/fr_FR.json +++ b/src/Ryujinx/Assets/Locales/fr_FR.json @@ -702,6 +702,9 @@ "Never": "Jamais", "SwkbdMinCharacters": "Doit comporter au moins {0} caractères", "SwkbdMinRangeCharacters": "Doit comporter entre {0} et {1} caractères", + "CabinetTitle": "Cabinet Dialog", + "CabinetDialog": "Enter your Amiibo's new name", + "CabinetScanDialog": "Please scan your Amiibo now.", "SoftwareKeyboard": "Clavier logiciel", "SoftwareKeyboardModeNumeric": "Doit être 0-9 ou '.' uniquement", "SoftwareKeyboardModeAlphabet": "Doit être uniquement des caractères non CJK", diff --git a/src/Ryujinx/Assets/Locales/he_IL.json b/src/Ryujinx/Assets/Locales/he_IL.json index 88b6a059a8..dbacf5ea1b 100644 --- a/src/Ryujinx/Assets/Locales/he_IL.json +++ b/src/Ryujinx/Assets/Locales/he_IL.json @@ -702,6 +702,9 @@ "Never": "אף פעם", "SwkbdMinCharacters": "לפחות {0} תווים", "SwkbdMinRangeCharacters": "באורך {0}-{1} תווים", + "CabinetTitle": "Cabinet Dialog", + "CabinetDialog": "Enter your Amiibo's new name", + "CabinetScanDialog": "Please scan your Amiibo now.", "SoftwareKeyboard": "מקלדת וירטואלית", "SoftwareKeyboardModeNumeric": "חייב להיות בין 0-9 או '.' בלבד", "SoftwareKeyboardModeAlphabet": "מחויב להיות ללא אותיות CJK", diff --git a/src/Ryujinx/Assets/Locales/it_IT.json b/src/Ryujinx/Assets/Locales/it_IT.json index e689a2cd95..61ea2a355d 100644 --- a/src/Ryujinx/Assets/Locales/it_IT.json +++ b/src/Ryujinx/Assets/Locales/it_IT.json @@ -702,6 +702,9 @@ "Never": "Mai", "SwkbdMinCharacters": "Non può avere meno di {0} caratteri", "SwkbdMinRangeCharacters": "Può avere da {0} a {1} caratteri", + "CabinetTitle": "Cabinet Dialog", + "CabinetDialog": "Enter your Amiibo's new name", + "CabinetScanDialog": "Please scan your Amiibo now.", "SoftwareKeyboard": "Tastiera software", "SoftwareKeyboardModeNumeric": "Deve essere solo 0-9 o '.'", "SoftwareKeyboardModeAlphabet": "Deve essere solo caratteri non CJK", diff --git a/src/Ryujinx/Assets/Locales/ja_JP.json b/src/Ryujinx/Assets/Locales/ja_JP.json index d55d1449de..9acd1c486e 100644 --- a/src/Ryujinx/Assets/Locales/ja_JP.json +++ b/src/Ryujinx/Assets/Locales/ja_JP.json @@ -702,6 +702,9 @@ "Never": "決して", "SwkbdMinCharacters": "最低 {0} 文字必要です", "SwkbdMinRangeCharacters": "{0}-{1} 文字にしてください", + "CabinetTitle": "Cabinet Dialog", + "CabinetDialog": "Enter your Amiibo's new name", + "CabinetScanDialog": "Please scan your Amiibo now.", "SoftwareKeyboard": "ソフトウェアキーボード", "SoftwareKeyboardModeNumeric": "0-9 または '.' のみでなければなりません", "SoftwareKeyboardModeAlphabet": "CJK文字以外のみ", diff --git a/src/Ryujinx/Assets/Locales/ko_KR.json b/src/Ryujinx/Assets/Locales/ko_KR.json index 8a3799e15f..86592aa699 100644 --- a/src/Ryujinx/Assets/Locales/ko_KR.json +++ b/src/Ryujinx/Assets/Locales/ko_KR.json @@ -702,6 +702,9 @@ "Never": "절대 안 함", "SwkbdMinCharacters": "{0}자 이상이어야 함", "SwkbdMinRangeCharacters": "{0}-{1}자 길이여야 함", + "CabinetTitle": "Cabinet Dialog", + "CabinetDialog": "Enter your Amiibo's new name", + "CabinetScanDialog": "Please scan your Amiibo now.", "SoftwareKeyboard": "소프트웨어 키보드", "SoftwareKeyboardModeNumeric": "0-9 또는 '.'만 가능", "SoftwareKeyboardModeAlphabet": "CJK 문자가 아닌 문자만 가능", diff --git a/src/Ryujinx/Assets/Locales/pl_PL.json b/src/Ryujinx/Assets/Locales/pl_PL.json index c3202020fe..1ed0988f9b 100644 --- a/src/Ryujinx/Assets/Locales/pl_PL.json +++ b/src/Ryujinx/Assets/Locales/pl_PL.json @@ -702,6 +702,9 @@ "Never": "Nigdy", "SwkbdMinCharacters": "Musi mieć co najmniej {0} znaków", "SwkbdMinRangeCharacters": "Musi mieć długość od {0}-{1} znaków", + "CabinetTitle": "Cabinet Dialog", + "CabinetDialog": "Enter your Amiibo's new name", + "CabinetScanDialog": "Please scan your Amiibo now.", "SoftwareKeyboard": "Klawiatura Oprogramowania", "SoftwareKeyboardModeNumeric": "Może składać się jedynie z 0-9 lub '.'", "SoftwareKeyboardModeAlphabet": "Nie może zawierać znaków CJK", diff --git a/src/Ryujinx/Assets/Locales/pt_BR.json b/src/Ryujinx/Assets/Locales/pt_BR.json index 71992434bf..676d89d967 100644 --- a/src/Ryujinx/Assets/Locales/pt_BR.json +++ b/src/Ryujinx/Assets/Locales/pt_BR.json @@ -701,6 +701,9 @@ "Never": "Nunca", "SwkbdMinCharacters": "Deve ter pelo menos {0} caracteres", "SwkbdMinRangeCharacters": "Deve ter entre {0}-{1} caracteres", + "CabinetTitle": "Cabinet Dialog", + "CabinetDialog": "Enter your Amiibo's new name", + "CabinetScanDialog": "Please scan your Amiibo now.", "SoftwareKeyboard": "Teclado por Software", "SoftwareKeyboardModeNumeric": "Deve ser somente 0-9 ou '.'", "SoftwareKeyboardModeAlphabet": "Apenas devem ser caracteres não CJK.", diff --git a/src/Ryujinx/Assets/Locales/ru_RU.json b/src/Ryujinx/Assets/Locales/ru_RU.json index f0218ffccf..ea4dcc8c80 100644 --- a/src/Ryujinx/Assets/Locales/ru_RU.json +++ b/src/Ryujinx/Assets/Locales/ru_RU.json @@ -702,6 +702,9 @@ "Never": "Никогда", "SwkbdMinCharacters": "Должно быть не менее {0} символов.", "SwkbdMinRangeCharacters": "Должно быть {0}-{1} символов", + "CabinetTitle": "Cabinet Dialog", + "CabinetDialog": "Enter your Amiibo's new name", + "CabinetScanDialog": "Please scan your Amiibo now.", "SoftwareKeyboard": "Программная клавиатура", "SoftwareKeyboardModeNumeric": "Должно быть в диапазоне 0-9 или '.'", "SoftwareKeyboardModeAlphabet": "Не должно быть CJK-символов", diff --git a/src/Ryujinx/Assets/Locales/th_TH.json b/src/Ryujinx/Assets/Locales/th_TH.json index 02ddda899e..fa4c1d3343 100644 --- a/src/Ryujinx/Assets/Locales/th_TH.json +++ b/src/Ryujinx/Assets/Locales/th_TH.json @@ -702,6 +702,9 @@ "Never": "ไม่ต้อง", "SwkbdMinCharacters": "ต้องมีความยาวของตัวอักษรอย่างน้อย {0} ตัว", "SwkbdMinRangeCharacters": "ต้องมีความยาวของตัวอักษร {0}-{1} ตัว", + "CabinetTitle": "Cabinet Dialog", + "CabinetDialog": "Enter your Amiibo's new name", + "CabinetScanDialog": "Please scan your Amiibo now.", "SoftwareKeyboard": "ซอฟต์แวร์คีย์บอร์ด", "SoftwareKeyboardModeNumeric": "ต้องเป็น 0-9 หรือ '.' เท่านั้น", "SoftwareKeyboardModeAlphabet": "ต้องเป็นตัวอักษรที่ไม่ใช่ประเภท CJK เท่านั้น", diff --git a/src/Ryujinx/Assets/Locales/tr_TR.json b/src/Ryujinx/Assets/Locales/tr_TR.json index a65064a382..475086e441 100644 --- a/src/Ryujinx/Assets/Locales/tr_TR.json +++ b/src/Ryujinx/Assets/Locales/tr_TR.json @@ -702,6 +702,9 @@ "Never": "Hiçbir Zaman", "SwkbdMinCharacters": "En az {0} karakter uzunluğunda olmalı", "SwkbdMinRangeCharacters": "{0}-{1} karakter uzunluğunda olmalı", + "CabinetTitle": "Cabinet Dialog", + "CabinetDialog": "Enter your Amiibo's new name", + "CabinetScanDialog": "Please scan your Amiibo now.", "SoftwareKeyboard": "Yazılım Klavyesi", "SoftwareKeyboardModeNumeric": "Sadece 0-9 veya '.' olabilir", "SoftwareKeyboardModeAlphabet": "Sadece CJK-characters olmayan karakterler olabilir", diff --git a/src/Ryujinx/Assets/Locales/uk_UA.json b/src/Ryujinx/Assets/Locales/uk_UA.json index ef26ace65a..68679a9b23 100644 --- a/src/Ryujinx/Assets/Locales/uk_UA.json +++ b/src/Ryujinx/Assets/Locales/uk_UA.json @@ -702,6 +702,9 @@ "Never": "Ніколи", "SwkbdMinCharacters": "Мінімальна кількість символів: {0}", "SwkbdMinRangeCharacters": "Має бути {0}-{1} символів", + "CabinetTitle": "Cabinet Dialog", + "CabinetDialog": "Enter your Amiibo's new name", + "CabinetScanDialog": "Please scan your Amiibo now.", "SoftwareKeyboard": "Програмна клавіатура", "SoftwareKeyboardModeNumeric": "Повинно бути лише 0-9 або “.”", "SoftwareKeyboardModeAlphabet": "Повинно бути лише не CJK-символи", diff --git a/src/Ryujinx/Assets/Locales/zh_CN.json b/src/Ryujinx/Assets/Locales/zh_CN.json index dc3f27b5ac..741b5b370e 100644 --- a/src/Ryujinx/Assets/Locales/zh_CN.json +++ b/src/Ryujinx/Assets/Locales/zh_CN.json @@ -702,6 +702,9 @@ "Never": "从不", "SwkbdMinCharacters": "不少于 {0} 个字符", "SwkbdMinRangeCharacters": "必须为 {0}-{1} 个字符", + "CabinetTitle": "Cabinet Dialog", + "CabinetDialog": "Enter your Amiibo's new name", + "CabinetScanDialog": "Please scan your Amiibo now.", "SoftwareKeyboard": "软键盘", "SoftwareKeyboardModeNumeric": "只能输入 0-9 或 \".\"", "SoftwareKeyboardModeAlphabet": "仅支持非中文字符", diff --git a/src/Ryujinx/Assets/Locales/zh_TW.json b/src/Ryujinx/Assets/Locales/zh_TW.json index c338857846..aaf8170c0d 100644 --- a/src/Ryujinx/Assets/Locales/zh_TW.json +++ b/src/Ryujinx/Assets/Locales/zh_TW.json @@ -702,6 +702,9 @@ "Never": "從不", "SwkbdMinCharacters": "長度必須至少為 {0} 個字元", "SwkbdMinRangeCharacters": "長度必須為 {0} 到 {1} 個字元", + "CabinetTitle": "Cabinet Dialog", + "CabinetDialog": "Enter your Amiibo's new name", + "CabinetScanDialog": "Please scan your Amiibo now.", "SoftwareKeyboard": "軟體鍵盤", "SoftwareKeyboardModeNumeric": "必須是 0 到 9 或「.」", "SoftwareKeyboardModeAlphabet": "必須是「非中日韓字元」 (non CJK)", diff --git a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs index 2ebba7ac0d..893ea95ac4 100644 --- a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs +++ b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs @@ -7,6 +7,7 @@ using Ryujinx.Ava.UI.Windows; using Ryujinx.HLE; using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard; using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; using Ryujinx.HLE.UI; using Ryujinx.UI.Common.Configuration; @@ -155,6 +156,55 @@ public bool DisplayInputDialog(SoftwareKeyboardUIArgs args, out string userText) return error || okPressed; } + public bool DisplayCabinetDialog(out string userText) + { + ManualResetEvent dialogCloseEvent = new(false); + bool okPressed = false; + string inputText = "My Amiibo"; + Dispatcher.UIThread.InvokeAsync(async () => + { + try + { + _parent.ViewModel.AppHost.NpadManager.BlockInputUpdates(); + SoftwareKeyboardUIArgs args = new SoftwareKeyboardUIArgs(); + args.KeyboardMode = KeyboardMode.Default; + args.InitialText = "Ryujinx"; + args.StringLengthMin = 1; + args.StringLengthMax = 25; + (UserResult result, string userInput) = await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.CabinetDialog], args); + if (result == UserResult.Ok) + { + inputText = userInput; + okPressed = true; + } + } + finally + { + dialogCloseEvent.Set(); + } + }); + dialogCloseEvent.WaitOne(); + _parent.ViewModel.AppHost.NpadManager.UnblockInputUpdates(); + userText = inputText; + return okPressed; + } + + public void DisplayCabinetMessageDialog() + { + ManualResetEvent dialogCloseEvent = new(false); + Dispatcher.UIThread.InvokeAsync(async () => + { + dialogCloseEvent.Set(); + await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.CabinetScanDialog], + string.Empty, + LocaleManager.Instance[LocaleKeys.InputDialogOk], + string.Empty, + LocaleManager.Instance[LocaleKeys.CabinetTitle]); + }); + dialogCloseEvent.WaitOne(); + } + + public void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value) { device.Configuration.UserChannelPersistence.ExecuteProgram(kind, value); From 07690e452726d64054dca239fd3e0b0a6e333287 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Wed, 4 Dec 2024 02:24:40 -0600 Subject: [PATCH 31/61] chore: applets: Cleanup redundant ReadStruct implementations & provide a default implementation for IApplet#GetResult. --- src/Ryujinx.HLE/HOS/Applets/AppletManager.cs | 4 +--- .../HOS/Applets/Browser/BrowserApplet.cs | 7 ------- .../HOS/Applets/Cabinet/CabinetApplet.cs | 13 ------------- .../HOS/Applets/Controller/ControllerApplet.cs | 5 ----- src/Ryujinx.HLE/HOS/Applets/Dummy/DummyApplet.cs | 12 ++++-------- src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs | 5 ----- src/Ryujinx.HLE/HOS/Applets/IApplet.cs | 2 +- .../HOS/Applets/PlayerSelect/PlayerSelectApplet.cs | 5 ----- .../SoftwareKeyboard/SoftwareKeyboardApplet.cs | 5 ----- .../HOS/Applets/SoftwareKeyboard/TRef.cs | 2 +- 10 files changed, 7 insertions(+), 53 deletions(-) diff --git a/src/Ryujinx.HLE/HOS/Applets/AppletManager.cs b/src/Ryujinx.HLE/HOS/Applets/AppletManager.cs index a2ddd573de..5895c67bb3 100644 --- a/src/Ryujinx.HLE/HOS/Applets/AppletManager.cs +++ b/src/Ryujinx.HLE/HOS/Applets/AppletManager.cs @@ -24,11 +24,9 @@ public static IApplet Create(AppletId applet, Horizon system) case AppletId.SoftwareKeyboard: return new SoftwareKeyboardApplet(system); case AppletId.LibAppletWeb: - return new BrowserApplet(system); case AppletId.LibAppletShop: - return new BrowserApplet(system); case AppletId.LibAppletOff: - return new BrowserApplet(system); + return new BrowserApplet(); case AppletId.MiiEdit: Logger.Warning?.Print(LogClass.Application, $"Please use the MiiEdit inside File/Open Applet"); return new DummyApplet(system); diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs index 6afbe4a721..c5f13dab31 100644 --- a/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs +++ b/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs @@ -18,13 +18,6 @@ internal class BrowserApplet : IApplet private List _arguments; private ShimKind _shimKind; - public BrowserApplet(Horizon system) { } - - public ResultCode GetResult() - { - return ResultCode.Success; - } - public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession) { _normalSession = normalSession; diff --git a/src/Ryujinx.HLE/HOS/Applets/Cabinet/CabinetApplet.cs b/src/Ryujinx.HLE/HOS/Applets/Cabinet/CabinetApplet.cs index f4f935d34b..294b8d1f62 100644 --- a/src/Ryujinx.HLE/HOS/Applets/Cabinet/CabinetApplet.cs +++ b/src/Ryujinx.HLE/HOS/Applets/Cabinet/CabinetApplet.cs @@ -125,19 +125,6 @@ private static byte[] BuildResponse(ReturnValueForAmiiboSettings returnValue) return bytes; } - public static T ReadStruct(byte[] data) where T : unmanaged - { - if (data.Length < Unsafe.SizeOf()) - { - throw new ArgumentException("Not enough data to read the struct"); - } - - fixed (byte* dataPtr = data) - { - return Unsafe.Read(dataPtr); - } - } - #region Structs [StructLayout(LayoutKind.Sequential, Pack = 1)] diff --git a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs index 5ec9d4b08b..3a7b29ab55 100644 --- a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs +++ b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs @@ -117,11 +117,6 @@ public ResultCode Start(AppletSession normalSession, AppletSession interactiveSe return ResultCode.Success; } - public ResultCode GetResult() - { - return ResultCode.Success; - } - private static byte[] BuildResponse(ControllerSupportResultInfo result) { using MemoryStream stream = MemoryStreamManager.Shared.GetStream(); diff --git a/src/Ryujinx.HLE/HOS/Applets/Dummy/DummyApplet.cs b/src/Ryujinx.HLE/HOS/Applets/Dummy/DummyApplet.cs index 75df7a3737..6b16aee7bf 100644 --- a/src/Ryujinx.HLE/HOS/Applets/Dummy/DummyApplet.cs +++ b/src/Ryujinx.HLE/HOS/Applets/Dummy/DummyApplet.cs @@ -11,11 +11,14 @@ internal class DummyApplet : IApplet { private readonly Horizon _system; private AppletSession _normalSession; + public event EventHandler AppletStateChanged; + public DummyApplet(Horizon system) { _system = system; } + public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession) { _normalSession = normalSession; @@ -24,10 +27,7 @@ public ResultCode Start(AppletSession normalSession, AppletSession interactiveSe _system.ReturnFocus(); return ResultCode.Success; } - private static T ReadStruct(byte[] data) where T : struct - { - return MemoryMarshal.Read(data.AsSpan()); - } + private static byte[] BuildResponse() { using MemoryStream stream = MemoryStreamManager.Shared.GetStream(); @@ -35,9 +35,5 @@ private static byte[] BuildResponse() writer.Write((ulong)ResultCode.Success); return stream.ToArray(); } - public ResultCode GetResult() - { - return ResultCode.Success; - } } } diff --git a/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs index 87d88fc657..0e043cc454 100644 --- a/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs +++ b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs @@ -203,10 +203,5 @@ private void ParseApplicationErrorArg() _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Number: {applicationErrorArg.ErrorNumber} (Details)", "\n" + detailsText, buttons.ToArray()); } } - - public ResultCode GetResult() - { - return ResultCode.Success; - } } } diff --git a/src/Ryujinx.HLE/HOS/Applets/IApplet.cs b/src/Ryujinx.HLE/HOS/Applets/IApplet.cs index bc53538414..4500b2f63e 100644 --- a/src/Ryujinx.HLE/HOS/Applets/IApplet.cs +++ b/src/Ryujinx.HLE/HOS/Applets/IApplet.cs @@ -13,7 +13,7 @@ interface IApplet ResultCode Start(AppletSession normalSession, AppletSession interactiveSession); - ResultCode GetResult(); + ResultCode GetResult() => ResultCode.Success; bool DrawTo(RenderingSurfaceInfo surfaceInfo, IVirtualMemoryManager destination, ulong position) => false; diff --git a/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs b/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs index ccc761ba18..05bddc76f4 100644 --- a/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs +++ b/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs @@ -37,11 +37,6 @@ public ResultCode Start(AppletSession normalSession, AppletSession interactiveSe return ResultCode.Success; } - public ResultCode GetResult() - { - return ResultCode.Success; - } - private byte[] BuildResponse() { UserProfile currentUser = _system.AccountManager.LastOpenedUser; diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs index e04fc64fe9..9ec202357e 100644 --- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs @@ -144,11 +144,6 @@ public ResultCode Start(AppletSession normalSession, AppletSession interactiveSe } } - public ResultCode GetResult() - { - return ResultCode.Success; - } - private bool IsKeyboardActive() { return _backgroundState >= InlineKeyboardState.Appearing && _backgroundState < InlineKeyboardState.Disappearing; diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/TRef.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/TRef.cs index 32d9e68da4..51571401f6 100644 --- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/TRef.cs +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/TRef.cs @@ -2,7 +2,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard { /// /// Wraps a type in a class so it gets stored in the GC managed heap. This is used as communication mechanism - /// between classed that need to be disposed and, thus, can't share their references. + /// between classes that need to be disposed and, thus, can't share their references. /// /// The internal type. class TRef From 1d0152b9617a8918c7db3d01873bbf46c546c969 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Wed, 4 Dec 2024 03:37:21 -0600 Subject: [PATCH 32/61] UI: Move Shader Compilation hint, graphics backend, and GPU manufacturer to the right side of the status bar, next to firmware version. Removed the "Game:" prefix in front of FPS. --- Directory.Packages.props | 2 +- .../LdnRyu/Proxy/P2pProxyServer.cs | 12 ++++- src/Ryujinx/AppHost.cs | 2 +- src/Ryujinx/Assets/Locales/ar_SA.json | 1 - src/Ryujinx/Assets/Locales/de_DE.json | 1 - src/Ryujinx/Assets/Locales/el_GR.json | 1 - src/Ryujinx/Assets/Locales/en_US.json | 1 - src/Ryujinx/Assets/Locales/es_ES.json | 1 - src/Ryujinx/Assets/Locales/fr_FR.json | 1 - src/Ryujinx/Assets/Locales/he_IL.json | 1 - src/Ryujinx/Assets/Locales/it_IT.json | 1 - src/Ryujinx/Assets/Locales/ja_JP.json | 1 - src/Ryujinx/Assets/Locales/ko_KR.json | 1 - src/Ryujinx/Assets/Locales/pl_PL.json | 1 - src/Ryujinx/Assets/Locales/pt_BR.json | 1 - src/Ryujinx/Assets/Locales/ru_RU.json | 1 - src/Ryujinx/Assets/Locales/th_TH.json | 1 - src/Ryujinx/Assets/Locales/tr_TR.json | 1 - src/Ryujinx/Assets/Locales/uk_UA.json | 1 - src/Ryujinx/Assets/Locales/zh_CN.json | 1 - src/Ryujinx/Assets/Locales/zh_TW.json | 1 - .../UI/ViewModels/MainWindowViewModel.cs | 10 ++--- .../UI/Views/Main/MainMenuBarView.axaml.cs | 3 +- .../UI/Views/Main/MainStatusBarView.axaml | 45 +++++++++++-------- src/Ryujinx/UI/Windows/MainWindow.axaml.cs | 7 ++- src/Ryujinx/Updater.cs | 11 +++-- 26 files changed, 52 insertions(+), 58 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index ffb5f2ead4..7059af0e05 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -38,7 +38,7 @@ - + diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnRyu/Proxy/P2pProxyServer.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnRyu/Proxy/P2pProxyServer.cs index 598fb654fb..fbce5c10c5 100644 --- a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnRyu/Proxy/P2pProxyServer.cs +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnRyu/Proxy/P2pProxyServer.cs @@ -1,3 +1,5 @@ +using Gommon; +using Humanizer; using NetCoreServer; using Open.Nat; using Ryujinx.Common.Logging; @@ -153,7 +155,10 @@ public async Task NatPunch() if (_publicPort != 0) { - _ = Task.Delay(PortLeaseRenew * 1000, _disposedCancellation.Token).ContinueWith((task) => Task.Run(RefreshLease)); + _ = Executor.ExecuteAfterDelayAsync( + PortLeaseRenew.Seconds(), + _disposedCancellation.Token, + RefreshLease); } _natDevice = device; @@ -257,7 +262,10 @@ private async Task RefreshLease() } - _ = Task.Delay(PortLeaseRenew, _disposedCancellation.Token).ContinueWith((task) => Task.Run(RefreshLease)); + _ = Executor.ExecuteAfterDelayAsync( + PortLeaseRenew.Milliseconds(), + _disposedCancellation.Token, + RefreshLease); } public bool TryRegisterUser(P2pProxySession session, ExternalProxyConfig config) diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index 5789737d69..9a7f82661c 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -1137,7 +1137,7 @@ public void UpdateStatus() LocaleManager.Instance[LocaleKeys.VolumeShort] + $": {(int)(Device.GetVolume() * 100)}%", dockedMode, ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(), - LocaleManager.Instance[LocaleKeys.Game] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)", + $"{Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)", $"FIFO: {Device.Statistics.GetFifoPercent():00.00} %", _displayCount)); } diff --git a/src/Ryujinx/Assets/Locales/ar_SA.json b/src/Ryujinx/Assets/Locales/ar_SA.json index c1ee30f19d..412695af69 100644 --- a/src/Ryujinx/Assets/Locales/ar_SA.json +++ b/src/Ryujinx/Assets/Locales/ar_SA.json @@ -718,7 +718,6 @@ "UpdaterAddingFiles": "إضافة ملفات جديدة...", "UpdaterExtracting": "استخراج التحديث...", "UpdaterDownloading": "تحميل التحديث...", - "Game": "لعبة", "Docked": "تركيب بالمنصة", "Handheld": "محمول", "ConnectionError": "خطأ في الاتصال", diff --git a/src/Ryujinx/Assets/Locales/de_DE.json b/src/Ryujinx/Assets/Locales/de_DE.json index e3f6b1be18..76e8dfadd3 100644 --- a/src/Ryujinx/Assets/Locales/de_DE.json +++ b/src/Ryujinx/Assets/Locales/de_DE.json @@ -718,7 +718,6 @@ "UpdaterAddingFiles": "Neue Dateien hinzufügen...", "UpdaterExtracting": "Update extrahieren...", "UpdaterDownloading": "Update herunterladen...", - "Game": "Spiel", "Docked": "Docked", "Handheld": "Handheld", "ConnectionError": "Verbindungsfehler.", diff --git a/src/Ryujinx/Assets/Locales/el_GR.json b/src/Ryujinx/Assets/Locales/el_GR.json index e93e9310ae..0409297acf 100644 --- a/src/Ryujinx/Assets/Locales/el_GR.json +++ b/src/Ryujinx/Assets/Locales/el_GR.json @@ -718,7 +718,6 @@ "UpdaterAddingFiles": "Προσθήκη Νέων Αρχείων...", "UpdaterExtracting": "Εξαγωγή Ενημέρωσης...", "UpdaterDownloading": "Λήψη Ενημέρωσης...", - "Game": "Παιχνίδι", "Docked": "Προσκολλημένο", "Handheld": "Χειροκίνητο", "ConnectionError": "Σφάλμα Σύνδεσης.", diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json index ee0d031718..ba183c8bdb 100644 --- a/src/Ryujinx/Assets/Locales/en_US.json +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -730,7 +730,6 @@ "UpdaterAddingFiles": "Adding New Files...", "UpdaterExtracting": "Extracting Update...", "UpdaterDownloading": "Downloading Update...", - "Game": "Game", "Docked": "Docked", "Handheld": "Handheld", "ConnectionError": "Connection Error.", diff --git a/src/Ryujinx/Assets/Locales/es_ES.json b/src/Ryujinx/Assets/Locales/es_ES.json index 0a68d44c6d..b473b11975 100644 --- a/src/Ryujinx/Assets/Locales/es_ES.json +++ b/src/Ryujinx/Assets/Locales/es_ES.json @@ -718,7 +718,6 @@ "UpdaterAddingFiles": "Añadiendo nuevos archivos...", "UpdaterExtracting": "Extrayendo actualización...", "UpdaterDownloading": "Descargando actualización...", - "Game": "Juego", "Docked": "Dock/TV", "Handheld": "Portátil", "ConnectionError": "Error de conexión.", diff --git a/src/Ryujinx/Assets/Locales/fr_FR.json b/src/Ryujinx/Assets/Locales/fr_FR.json index 471dfbe5e7..0223e322eb 100644 --- a/src/Ryujinx/Assets/Locales/fr_FR.json +++ b/src/Ryujinx/Assets/Locales/fr_FR.json @@ -718,7 +718,6 @@ "UpdaterAddingFiles": "Ajout des nouveaux fichiers...", "UpdaterExtracting": "Extraction de la mise à jour…", "UpdaterDownloading": "Téléchargement de la mise à jour...", - "Game": "Jeu", "Docked": "Mode station d'accueil", "Handheld": "Mode Portable", "ConnectionError": "Erreur de connexion.", diff --git a/src/Ryujinx/Assets/Locales/he_IL.json b/src/Ryujinx/Assets/Locales/he_IL.json index dbacf5ea1b..318068bf3e 100644 --- a/src/Ryujinx/Assets/Locales/he_IL.json +++ b/src/Ryujinx/Assets/Locales/he_IL.json @@ -718,7 +718,6 @@ "UpdaterAddingFiles": "מוסיף קבצים חדשים...", "UpdaterExtracting": "מחלץ עדכון...", "UpdaterDownloading": "מוריד עדכון...", - "Game": "משחק", "Docked": "בתחנת עגינה", "Handheld": "נייד", "ConnectionError": "שגיאת חיבור", diff --git a/src/Ryujinx/Assets/Locales/it_IT.json b/src/Ryujinx/Assets/Locales/it_IT.json index 61ea2a355d..5ca17bc2ed 100644 --- a/src/Ryujinx/Assets/Locales/it_IT.json +++ b/src/Ryujinx/Assets/Locales/it_IT.json @@ -718,7 +718,6 @@ "UpdaterAddingFiles": "Aggiunta dei nuovi file...", "UpdaterExtracting": "Estrazione dell'aggiornamento...", "UpdaterDownloading": "Download dell'aggiornamento...", - "Game": "Gioco", "Docked": "TV", "Handheld": "Portatile", "ConnectionError": "Errore di connessione.", diff --git a/src/Ryujinx/Assets/Locales/ja_JP.json b/src/Ryujinx/Assets/Locales/ja_JP.json index 9acd1c486e..ffa768c137 100644 --- a/src/Ryujinx/Assets/Locales/ja_JP.json +++ b/src/Ryujinx/Assets/Locales/ja_JP.json @@ -718,7 +718,6 @@ "UpdaterAddingFiles": "新規ファイルを追加中...", "UpdaterExtracting": "アップデートを展開中...", "UpdaterDownloading": "アップデートをダウンロード中...", - "Game": "ゲーム", "Docked": "ドッキング", "Handheld": "携帯", "ConnectionError": "接続エラー.", diff --git a/src/Ryujinx/Assets/Locales/ko_KR.json b/src/Ryujinx/Assets/Locales/ko_KR.json index 86592aa699..6b7140b3c5 100644 --- a/src/Ryujinx/Assets/Locales/ko_KR.json +++ b/src/Ryujinx/Assets/Locales/ko_KR.json @@ -718,7 +718,6 @@ "UpdaterAddingFiles": "새 파일 추가...", "UpdaterExtracting": "업데이트 추출...", "UpdaterDownloading": "업데이트 내려받기 중...", - "Game": "게임", "Docked": "도킹", "Handheld": "휴대", "ConnectionError": "연결 오류가 발생했습니다.", diff --git a/src/Ryujinx/Assets/Locales/pl_PL.json b/src/Ryujinx/Assets/Locales/pl_PL.json index 1ed0988f9b..d87453ef2a 100644 --- a/src/Ryujinx/Assets/Locales/pl_PL.json +++ b/src/Ryujinx/Assets/Locales/pl_PL.json @@ -718,7 +718,6 @@ "UpdaterAddingFiles": "Dodawanie Nowych Plików...", "UpdaterExtracting": "Wypakowywanie Aktualizacji...", "UpdaterDownloading": "Pobieranie Aktualizacji...", - "Game": "Gra", "Docked": "Zadokowany", "Handheld": "Przenośny", "ConnectionError": "Błąd Połączenia.", diff --git a/src/Ryujinx/Assets/Locales/pt_BR.json b/src/Ryujinx/Assets/Locales/pt_BR.json index 676d89d967..c240bd8049 100644 --- a/src/Ryujinx/Assets/Locales/pt_BR.json +++ b/src/Ryujinx/Assets/Locales/pt_BR.json @@ -717,7 +717,6 @@ "UpdaterAddingFiles": "Adicionando novos arquivos...", "UpdaterExtracting": "Extraíndo atualização...", "UpdaterDownloading": "Baixando atualização...", - "Game": "Jogo", "Docked": "TV", "Handheld": "Portátil", "ConnectionError": "Erro de conexão.", diff --git a/src/Ryujinx/Assets/Locales/ru_RU.json b/src/Ryujinx/Assets/Locales/ru_RU.json index ea4dcc8c80..1046208fbf 100644 --- a/src/Ryujinx/Assets/Locales/ru_RU.json +++ b/src/Ryujinx/Assets/Locales/ru_RU.json @@ -718,7 +718,6 @@ "UpdaterAddingFiles": "Добавление новых файлов...", "UpdaterExtracting": "Извлечение обновления...", "UpdaterDownloading": "Загрузка обновления...", - "Game": "Игра", "Docked": "Стационарный режим", "Handheld": "Портативный режим", "ConnectionError": "Ошибка соединения", diff --git a/src/Ryujinx/Assets/Locales/th_TH.json b/src/Ryujinx/Assets/Locales/th_TH.json index fa4c1d3343..e29004e109 100644 --- a/src/Ryujinx/Assets/Locales/th_TH.json +++ b/src/Ryujinx/Assets/Locales/th_TH.json @@ -718,7 +718,6 @@ "UpdaterAddingFiles": "กำลังเพิ่มไฟล์ใหม่...", "UpdaterExtracting": "กำลังแยกการอัปเดต...", "UpdaterDownloading": "กำลังดาวน์โหลดอัปเดต...", - "Game": "เกมส์", "Docked": "ด็อก", "Handheld": "แฮนด์เฮลด์", "ConnectionError": "การเชื่อมต่อล้มเหลว", diff --git a/src/Ryujinx/Assets/Locales/tr_TR.json b/src/Ryujinx/Assets/Locales/tr_TR.json index 475086e441..1012062104 100644 --- a/src/Ryujinx/Assets/Locales/tr_TR.json +++ b/src/Ryujinx/Assets/Locales/tr_TR.json @@ -718,7 +718,6 @@ "UpdaterAddingFiles": "Yeni Dosyalar Ekleniyor...", "UpdaterExtracting": "Güncelleme Ayrıştırılıyor...", "UpdaterDownloading": "Güncelleme İndiriliyor...", - "Game": "Oyun", "Docked": "Docked", "Handheld": "El tipi", "ConnectionError": "Bağlantı Hatası.", diff --git a/src/Ryujinx/Assets/Locales/uk_UA.json b/src/Ryujinx/Assets/Locales/uk_UA.json index 68679a9b23..89e565bf3a 100644 --- a/src/Ryujinx/Assets/Locales/uk_UA.json +++ b/src/Ryujinx/Assets/Locales/uk_UA.json @@ -718,7 +718,6 @@ "UpdaterAddingFiles": "Додавання нових файлів...", "UpdaterExtracting": "Видобування оновлення...", "UpdaterDownloading": "Завантаження оновлення...", - "Game": "Гра", "Docked": "Док-станція", "Handheld": "Портативний", "ConnectionError": "Помилка з'єднання.", diff --git a/src/Ryujinx/Assets/Locales/zh_CN.json b/src/Ryujinx/Assets/Locales/zh_CN.json index 741b5b370e..66ac309de0 100644 --- a/src/Ryujinx/Assets/Locales/zh_CN.json +++ b/src/Ryujinx/Assets/Locales/zh_CN.json @@ -718,7 +718,6 @@ "UpdaterAddingFiles": "安装更新中...", "UpdaterExtracting": "正在提取更新...", "UpdaterDownloading": "下载更新中...", - "Game": "游戏", "Docked": "主机模式", "Handheld": "掌机模式", "ConnectionError": "连接错误。", diff --git a/src/Ryujinx/Assets/Locales/zh_TW.json b/src/Ryujinx/Assets/Locales/zh_TW.json index aaf8170c0d..792ced42b2 100644 --- a/src/Ryujinx/Assets/Locales/zh_TW.json +++ b/src/Ryujinx/Assets/Locales/zh_TW.json @@ -718,7 +718,6 @@ "UpdaterAddingFiles": "正在加入新檔案...", "UpdaterExtracting": "正在提取更新...", "UpdaterDownloading": "正在下載更新...", - "Game": "遊戲", "Docked": "底座模式", "Handheld": "手提模式", "ConnectionError": "連線錯誤。", diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 3672f8c715..1bfcd439b6 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -70,7 +70,7 @@ public class MainWindowViewModel : BaseModel private string _gpuStatusText; private string _shaderCountText; private bool _isAmiiboRequested; - private bool _showRightmostSeparator; + private bool _showShaderCompilationHint; private bool _isGameRunning; private bool _isFullScreen; private int _progressMaximum; @@ -275,12 +275,12 @@ public bool StatusBarVisible public bool ShowFirmwareStatus => !ShowLoadProgress; - public bool ShowRightmostSeparator + public bool ShowShaderCompilationHint { - get => _showRightmostSeparator; + get => _showShaderCompilationHint; set { - _showRightmostSeparator = value; + _showShaderCompilationHint = value; OnPropertyChanged(); } @@ -1497,7 +1497,7 @@ private void Update_StatusBar(object sender, StatusUpdatedEventArgs args) VolumeStatusText = args.VolumeStatus; FifoStatusText = args.FifoStatus; - ShaderCountText = (ShowRightmostSeparator = args.ShaderCount > 0) + ShaderCountText = (ShowShaderCompilationHint = args.ShaderCount > 0) ? $"{LocaleManager.Instance[LocaleKeys.CompilingShaders]}: {args.ShaderCount}" : string.Empty; diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs index 41b27e9c13..a3aa58f2cc 100644 --- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs @@ -200,7 +200,6 @@ private async void ChangeWindowSize_Click(object sender, RoutedEventArgs e) await Dispatcher.UIThread.InvokeAsync(() => { - ViewModel.WindowState = WindowState.Normal; Window.Arrange(new Rect(Window.Position.X, Window.Position.Y, windowWidthScaled, windowHeightScaled)); @@ -210,7 +209,7 @@ await Dispatcher.UIThread.InvokeAsync(() => public async void CheckForUpdates(object sender, RoutedEventArgs e) { if (Updater.CanUpdate(true)) - await Window.BeginUpdateAsync(true); + await Updater.BeginUpdateAsync(true); } public async void OpenXCITrimmerWindow(object sender, RoutedEventArgs e) => await XCITrimmerWindow.Show(ViewModel); diff --git a/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml b/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml index 597cf10e1c..6e72a8b4b7 100644 --- a/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml +++ b/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml @@ -23,7 +23,7 @@ Background="{DynamicResource ThemeContentBackgroundColor}" DockPanel.Dock="Bottom" IsVisible="{Binding ShowMenuAndStatusBar}" - ColumnDefinitions="Auto,Auto,*,Auto"> + ColumnDefinitions="Auto,Auto,*,Auto,Auto"> + + + + IsVisible="{Binding ShowShaderCompilationHint}" /> - - + diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs index 059f99a60a..09c8b94488 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -7,6 +7,7 @@ using DynamicData; using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Windowing; +using Gommon; using LibHac.Tools.FsSystem; using Ryujinx.Ava.Common; using Ryujinx.Ava.Common.Locale; @@ -387,10 +388,8 @@ private async Task CheckLaunchState() if (ConfigurationState.Instance.CheckUpdatesOnStart && !CommandLineState.HideAvailableUpdates && Updater.CanUpdate()) { - await this.BeginUpdateAsync() - .ContinueWith( - task => Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}"), - TaskContinuationOptions.OnlyOnFaulted); + await Updater.BeginUpdateAsync() + .Catch(task => Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}")); } } diff --git a/src/Ryujinx/Updater.cs b/src/Ryujinx/Updater.cs index bdb44d668d..6a1701208e 100644 --- a/src/Ryujinx/Updater.cs +++ b/src/Ryujinx/Updater.cs @@ -1,4 +1,3 @@ -using Avalonia.Controls; using Avalonia.Threading; using FluentAvalonia.UI.Controls; using Gommon; @@ -51,7 +50,7 @@ internal static class Updater private static readonly string[] _windowsDependencyDirs = []; - public static async Task BeginUpdateAsync(this Window mainWindow, bool showVersionUpToDate = false) + public static async Task BeginUpdateAsync(bool showVersionUpToDate = false) { if (_running) { @@ -225,7 +224,7 @@ await Dispatcher.UIThread.InvokeAsync(async () => ? $"Canary {currentVersion} -> Canary {newVersion}" : $"{currentVersion} -> {newVersion}"; - RequestUserToUpdate: + RequestUserToUpdate: // Show a message asking the user if they want to update UserResult shouldUpdate = await ContentDialogHelper.CreateUpdaterChoiceDialog( LocaleManager.Instance[LocaleKeys.RyujinxUpdater], @@ -235,7 +234,7 @@ await Dispatcher.UIThread.InvokeAsync(async () => switch (shouldUpdate) { case UserResult.Yes: - await UpdateRyujinx(mainWindow, _buildUrl); + await UpdateRyujinx(_buildUrl); break; // Secondary button maps to no, which in this case is the show changelog button. case UserResult.No: @@ -258,7 +257,7 @@ private static HttpClient ConstructHttpClient() return result; } - private static async Task UpdateRyujinx(Window parent, string downloadUrl) + private static async Task UpdateRyujinx(string downloadUrl) { _updateSuccessful = false; @@ -278,7 +277,7 @@ private static async Task UpdateRyujinx(Window parent, string downloadUrl) SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading], IconSource = new SymbolIconSource { Symbol = Symbol.Download }, ShowProgressBar = true, - XamlRoot = parent, + XamlRoot = App.MainWindow, }; taskDialog.Opened += (s, e) => From 000c1756de0851a2d4bd2f458e5e987c3cba66dd Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Fri, 6 Dec 2024 08:17:04 -0600 Subject: [PATCH 33/61] version 1.2 in Info.plist --- distribution/macos/Info.plist | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/distribution/macos/Info.plist b/distribution/macos/Info.plist index 53929f95eb..2602f9905f 100644 --- a/distribution/macos/Info.plist +++ b/distribution/macos/Info.plist @@ -40,11 +40,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.1 + 1.2 CFBundleSignature ???? CFBundleVersion - 1.1.0 + 1.2.0 NSHighResolutionCapable CSResourcesFileMapped From 3d168a8bfa7bd5a418b50ce7a82a7780d4c3b5f5 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Fri, 6 Dec 2024 08:18:24 -0600 Subject: [PATCH 34/61] direct errored updates to ryujinx.app --- distribution/macos/updater.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/distribution/macos/updater.sh b/distribution/macos/updater.sh index 12e4c3aa18..0465d7c91b 100755 --- a/distribution/macos/updater.sh +++ b/distribution/macos/updater.sh @@ -17,7 +17,7 @@ error_handler() { set the button_pressed to the button returned of the result if the button_pressed is \"Open Download Page\" then - open location \"https://ryujinx.org/download\" + open location \"https://ryujinx.app/download\" end if """ @@ -54,4 +54,4 @@ if [ "$#" -le 3 ]; then open -a "$INSTALL_DIRECTORY" else open -a "$INSTALL_DIRECTORY" --args "${APP_ARGUMENTS[@]}" -fi \ No newline at end of file +fi From a1e6d11dcb0b125b1a953b5ba81d3b39aeecbff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hack=E8=8C=B6=E3=82=93?= <120134269+Hackjjang@users.noreply.github.com> Date: Sat, 7 Dec 2024 00:18:09 +0900 Subject: [PATCH 35/61] Update Korean translation (#352) --- src/Ryujinx/Assets/Locales/ko_KR.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Ryujinx/Assets/Locales/ko_KR.json b/src/Ryujinx/Assets/Locales/ko_KR.json index 6b7140b3c5..8731c8662a 100644 --- a/src/Ryujinx/Assets/Locales/ko_KR.json +++ b/src/Ryujinx/Assets/Locales/ko_KR.json @@ -1,7 +1,7 @@ { "Language": "한국어", "MenuBarFileOpenApplet": "애플릿 열기", - "MenuBarFileOpenAppletOpenMiiApplet": "Mii Edit Applet", + "MenuBarFileOpenAppletOpenMiiApplet": "Mii 편집 애플릿", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "독립 실행형 모드로 Mii 편집기 애플릿 열기", "SettingsTabInputDirectMouseAccess": "마우스 직접 접근", "SettingsTabSystemMemoryManagerMode": "메모리 관리자 모드 :", @@ -484,7 +484,7 @@ "DialogControllerAppletTitle": "컨트롤러 애플릿", "DialogMessageDialogErrorExceptionMessage": "메시지 대화 상자 표시 오류 : {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "소프트웨어 키보드 표시 오류 : {0}", - "DialogErrorAppletErrorExceptionMessage": "ErrorApplet 대화 상자 표시 오류 : {0}", + "DialogErrorAppletErrorExceptionMessage": "애플릿 오류류 대화 상자 표시 오류 : {0}", "DialogUserErrorDialogMessage": "{0}: {1}", "DialogUserErrorDialogInfoMessage": "\n이 오류를 해결하는 방법에 대한 자세한 내용은 설정 가이드를 참조하세요.", "DialogUserErrorDialogTitle": "Ryujinx 오류 ({0})", @@ -702,9 +702,9 @@ "Never": "절대 안 함", "SwkbdMinCharacters": "{0}자 이상이어야 함", "SwkbdMinRangeCharacters": "{0}-{1}자 길이여야 함", - "CabinetTitle": "Cabinet Dialog", - "CabinetDialog": "Enter your Amiibo's new name", - "CabinetScanDialog": "Please scan your Amiibo now.", + "CabinetTitle": "캐비닛 대화 상자", + "CabinetDialog": "Amiibo의 새 이름 입력하기", + "CabinetScanDialog": "지금 Amiibo를 스캔하세요.", "SoftwareKeyboard": "소프트웨어 키보드", "SoftwareKeyboardModeNumeric": "0-9 또는 '.'만 가능", "SoftwareKeyboardModeAlphabet": "CJK 문자가 아닌 문자만 가능", @@ -781,8 +781,8 @@ "XCITrimmerDeselectDisplayed": "표시됨 선택 취소", "XCITrimmerSortName": "타이틀", "XCITrimmerSortSaved": "공간 절약s", - "XCITrimmerTrim": "Trim", - "XCITrimmerUntrim": "Untrim", + "XCITrimmerTrim": "트림", + "XCITrimmerUntrim": "언트림", "UpdateWindowUpdateAddedMessage": "{0}개의 새 업데이트가 추가됨", "UpdateWindowBundledContentNotice": "번들 업데이트는 제거할 수 없으며, 비활성화만 가능합니다.", "CheatWindowHeading": "{0} [{1}]에 사용 가능한 치트", From baad1e313f80bf53a20fc1ee04fdc716b1728bf1 Mon Sep 17 00:00:00 2001 From: Luke Warner <65521430+LukeWarnut@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:43:31 -0500 Subject: [PATCH 36/61] Stub Ldn.Lp2p.ISfService: 776 (DestroyGroup) (#353) This prevents a crash in Mario Kart Live: Home Circuit that would occur after exiting the kart pairing screen. --- src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfService.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfService.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfService.cs index d48a889788..8f9f0e3e49 100644 --- a/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfService.cs +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfService.cs @@ -24,6 +24,15 @@ public ResultCode CreateGroup(ServiceCtx context) return ResultCode.Success; } + [CommandCmif(776)] + // DestroyGroup() + public ResultCode DestroyGroup(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceLdn); + + return ResultCode.Success; + } + [CommandCmif(1536)] // SendToOtherGroup(nn::lp2p::MacAddress, nn::lp2p::GroupId, s16, s16, u32, buffer) public ResultCode SendToOtherGroup(ServiceCtx context) From 0bc1eddaebc03d790fa0c16729382967f7f229dc Mon Sep 17 00:00:00 2001 From: maxdlpee <77379259+maxdlpee@users.noreply.github.com> Date: Sat, 7 Dec 2024 00:57:35 -0300 Subject: [PATCH 37/61] Update Spanish translation (#332) - Added translations for XCI trimmer - Added translations for Cabinet applet - Added translations for Keys installer - Other miscellaneous translations added --- src/Ryujinx/Assets/Locales/es_ES.json | 108 +++++++++++++------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/src/Ryujinx/Assets/Locales/es_ES.json b/src/Ryujinx/Assets/Locales/es_ES.json index b473b11975..934031c728 100644 --- a/src/Ryujinx/Assets/Locales/es_ES.json +++ b/src/Ryujinx/Assets/Locales/es_ES.json @@ -1,7 +1,7 @@ { "Language": "Español (ES)", "MenuBarFileOpenApplet": "Abrir applet", - "MenuBarFileOpenAppletOpenMiiApplet": "Mii Edit Applet", + "MenuBarFileOpenAppletOpenMiiApplet": "Applet Editor Mii", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Abre el editor de Mii en modo autónomo", "SettingsTabInputDirectMouseAccess": "Acceso directo al ratón", "SettingsTabSystemMemoryManagerMode": "Modo del administrador de memoria:", @@ -32,12 +32,12 @@ "MenuBarFileToolsInstallFirmwareFromFile": "Instalar firmware desde un archivo XCI o ZIP", "MenuBarFileToolsInstallFirmwareFromDirectory": "Instalar firmware desde una carpeta", "MenuBarToolsInstallKeys": "Install Keys", - "MenuBarFileToolsInstallKeysFromFile": "Install keys from KEYS or ZIP", - "MenuBarFileToolsInstallKeysFromFolder": "Install keys from a directory", + "MenuBarFileToolsInstallKeysFromFile": "Instalar keys de KEYS o ZIP", + "MenuBarFileToolsInstallKeysFromFolder": "Instalar keys de un directorio", "MenuBarToolsManageFileTypes": "Administrar tipos de archivo", "MenuBarToolsInstallFileTypes": "Instalar tipos de archivo", "MenuBarToolsUninstallFileTypes": "Desinstalar tipos de archivo", - "MenuBarToolsXCITrimmer": "Trim XCI Files", + "MenuBarToolsXCITrimmer": "Recortar archivos XCI", "MenuBarView": "_View", "MenuBarViewWindow": "Tamaño Ventana", "MenuBarViewWindow720": "720p", @@ -89,11 +89,11 @@ "GameListContextMenuOpenModsDirectoryToolTip": "Abre el directorio que contiene los Mods de la Aplicación.", "GameListContextMenuOpenSdModsDirectory": "Abrir Directorio de Mods de Atmosphere\n\n\n\n\n\n", "GameListContextMenuOpenSdModsDirectoryToolTip": "Abre el directorio alternativo de la tarjeta SD de Atmosphere que contiene los Mods de la Aplicación. Útil para los mods que están empaquetados para el hardware real.", - "GameListContextMenuTrimXCI": "Check and Trim XCI File", - "GameListContextMenuTrimXCIToolTip": "Check and Trim XCI File to Save Disk Space", + "GameListContextMenuTrimXCI": "Verificar y recortar archivo XCI", + "GameListContextMenuTrimXCIToolTip": "Verificar y recortar archivo XCI para ahorrar espacio en disco", "StatusBarGamesLoaded": "{0}/{1} juegos cargados", "StatusBarSystemVersion": "Versión del sistema: {0}", - "StatusBarXCIFileTrimming": "Trimming XCI File '{0}'", + "StatusBarXCIFileTrimming": "Recortando el siguiente archivo XCI: '{0}'", "LinuxVmMaxMapCountDialogTitle": "Límite inferior para mapeos de memoria detectado", "LinuxVmMaxMapCountDialogTextPrimary": "¿Quieres aumentar el valor de vm.max_map_count a {0}?", "LinuxVmMaxMapCountDialogTextSecondary": "Algunos juegos podrían intentar crear más mapeos de memoria de los permitidos. Ryujinx se bloqueará tan pronto como se supere este límite.", @@ -480,7 +480,7 @@ "DialogUninstallFileTypesSuccessMessage": "¡Tipos de archivos desinstalados con éxito!", "DialogUninstallFileTypesErrorMessage": "No se pudo desinstalar los tipos de archivo.", "DialogOpenSettingsWindowLabel": "Abrir ventana de opciones", - "DialogOpenXCITrimmerWindowLabel": "XCI Trimmer Window", + "DialogOpenXCITrimmerWindowLabel": "Ventana recortador XCI", "DialogControllerAppletTitle": "Applet de mandos", "DialogMessageDialogErrorExceptionMessage": "Error al mostrar cuadro de diálogo: {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "Error al mostrar teclado de software: {0}", @@ -509,13 +509,13 @@ "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n¿Continuar?", "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Instalando firmware...", "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Versión de sistema {0} instalada con éxito.", - "DialogKeysInstallerKeysNotFoundErrorMessage": "An invalid Keys file was found in {0}", - "DialogKeysInstallerKeysInstallTitle": "Install Keys", - "DialogKeysInstallerKeysInstallMessage": "New Keys file will be installed.", - "DialogKeysInstallerKeysInstallSubMessage": "\n\nThis may replace some of the current installed Keys.", - "DialogKeysInstallerKeysInstallConfirmMessage": "\n\nDo you want to continue?", - "DialogKeysInstallerKeysInstallWaitMessage": "Installing Keys...", - "DialogKeysInstallerKeysInstallSuccessMessage": "New Keys file successfully installed.", + "DialogKeysInstallerKeysNotFoundErrorMessage": "Se halló un archivo Keys inválido en {0}", + "DialogKeysInstallerKeysInstallTitle": "Instalar Keys", + "DialogKeysInstallerKeysInstallMessage": "Un nuevo archivo Keys será instalado.", + "DialogKeysInstallerKeysInstallSubMessage": "\n\nEsto puede reemplazar algunas de las Keys actualmente instaladas.", + "DialogKeysInstallerKeysInstallConfirmMessage": "\n\nDeseas continuar?", + "DialogKeysInstallerKeysInstallWaitMessage": "Instalando Keys...", + "DialogKeysInstallerKeysInstallSuccessMessage": "Nuevo archivo Keys instalado con éxito.", "DialogUserProfileDeletionWarningMessage": "Si eliminas el perfil seleccionado no quedará ningún otro perfil", "DialogUserProfileDeletionConfirmMessage": "¿Quieres eliminar el perfil seleccionado?", "DialogUserProfileUnsavedChangesTitle": "Advertencia - Cambios sin guardar", @@ -688,23 +688,23 @@ "OpenSetupGuideMessage": "Abrir la guía de instalación", "NoUpdate": "No actualizado", "TitleUpdateVersionLabel": "Versión {0} - {1}", - "TitleBundledUpdateVersionLabel": "Bundled: Version {0}", - "TitleBundledDlcLabel": "Bundled:", - "TitleXCIStatusPartialLabel": "Partial", - "TitleXCIStatusTrimmableLabel": "Untrimmed", - "TitleXCIStatusUntrimmableLabel": "Trimmed", - "TitleXCIStatusFailedLabel": "(Failed)", - "TitleXCICanSaveLabel": "Save {0:n0} Mb", - "TitleXCISavingLabel": "Saved {0:n0} Mb", + "TitleBundledUpdateVersionLabel": "Incorporado: Versión {0}", + "TitleBundledDlcLabel": "Incorporado:", + "TitleXCIStatusPartialLabel": "Parcial", + "TitleXCIStatusTrimmableLabel": "Sin recortar", + "TitleXCIStatusUntrimmableLabel": "Recortado", + "TitleXCIStatusFailedLabel": "(Fallido)", + "TitleXCICanSaveLabel": "Ahorra {0:n0} Mb", + "TitleXCISavingLabel": "{0:n0} Mb ahorrado(s)", "RyujinxInfo": "Ryujinx - Info", "RyujinxConfirm": "Ryujinx - Confirmación", "FileDialogAllTypes": "Todos los tipos", "Never": "Nunca", "SwkbdMinCharacters": "Debe tener al menos {0} caracteres", "SwkbdMinRangeCharacters": "Debe tener {0}-{1} caracteres", - "CabinetTitle": "Cabinet Dialog", - "CabinetDialog": "Enter your Amiibo's new name", - "CabinetScanDialog": "Please scan your Amiibo now.", + "CabinetTitle": "Diálogo Gabinete", + "CabinetDialog": "Ingresa el nuevo nombre de tu Amiibo", + "CabinetScanDialog": "Escanea tu Amiibo ahora.", "SoftwareKeyboard": "Teclado de software", "SoftwareKeyboardModeNumeric": "Debe ser sólo 0-9 o '.'", "SoftwareKeyboardModeAlphabet": "Solo deben ser caracteres no CJK", @@ -750,39 +750,39 @@ "SelectDlcDialogTitle": "Selecciona archivo(s) de DLC", "SelectUpdateDialogTitle": "Selecciona archivo(s) de actualización", "SelectModDialogTitle": "Seleccionar un directorio de Mods", - "TrimXCIFileDialogTitle": "Check and Trim XCI File", - "TrimXCIFileDialogPrimaryText": "This function will first check the empty space and then trim the XCI File to save disk space.", - "TrimXCIFileDialogSecondaryText": "Current File Size: {0:n} MB\nGame Data Size: {1:n} MB\nDisk Space Savings: {2:n} MB", - "TrimXCIFileNoTrimNecessary": "XCI File does not need to be trimmed. Check logs for further details", - "TrimXCIFileNoUntrimPossible": "XCI File cannot be untrimmed. Check logs for further details", - "TrimXCIFileReadOnlyFileCannotFix": "XCI File is Read Only and could not be made writable. Check logs for further details", - "TrimXCIFileFileSizeChanged": "XCI File has changed in size since it was scanned. Please check the file is not being written to and try again.", - "TrimXCIFileFreeSpaceCheckFailed": "XCI File has data in the free space area, it is not safe to trim", - "TrimXCIFileInvalidXCIFile": "XCI File contains invalid data. Check logs for further details", - "TrimXCIFileFileIOWriteError": "XCI File could not be opened for writing. Check logs for further details", - "TrimXCIFileFailedPrimaryText": "Trimming of the XCI file failed", - "TrimXCIFileCancelled": "The operation was cancelled", - "TrimXCIFileFileUndertermined": "No operation was performed", + "TrimXCIFileDialogTitle": "Verificar y recortar archivo XCI", + "TrimXCIFileDialogPrimaryText": "Esta función verificará el espacio vacío y después recortará el archivo XCI para ahorrar espacio en disco", + "TrimXCIFileDialogSecondaryText": "Tamaño de archivo actual: {0:n} MB\nTamaño de datos de juego: {1:n} MB\nAhorro de espacio en disco: {2:n} MB", + "TrimXCIFileNoTrimNecessary": "El archivo XCI no necesita ser recortado. Verifica los logs para más detalles.", + "TrimXCIFileNoUntrimPossible": "El recorte del archivo XCI no puede ser deshecho. Verifica los registros para más detalles.", + "TrimXCIFileReadOnlyFileCannotFix": "El archivo XCI es de solo Lectura y no se le puede escribir. Lee el registro para más información.", + "TrimXCIFileFileSizeChanged": "El archivo XCI ha cambiado de tamaño desde que fue escaneado. Verifica que no se esté escribiendo al archivo y vuelve a intentarlo.", + "TrimXCIFileFreeSpaceCheckFailed": "El archivo XCI tiene datos en el área de espacio libre, no es seguro recortar.", + "TrimXCIFileInvalidXCIFile": "El archivo XCI contiene datos inválidos. Lee el registro para más información.", + "TrimXCIFileFileIOWriteError": "El archivo XCI no se puede abrir para escribirlo. Lee el registro para más información.", + "TrimXCIFileFailedPrimaryText": "El recorte del archivo XCI falló", + "TrimXCIFileCancelled": "La operación fue cancelada", + "TrimXCIFileFileUndertermined": "No se realizó ninguna operación", "UserProfileWindowTitle": "Administrar perfiles de usuario", "CheatWindowTitle": "Administrar cheats", "DlcWindowTitle": "Administrar contenido descargable", "ModWindowTitle": "Administrar Mods para {0} ({1})", "UpdateWindowTitle": "Administrar actualizaciones", - "XCITrimmerWindowTitle": "XCI File Trimmer", - "XCITrimmerTitleStatusCount": "{0} of {1} Title(s) Selected", - "XCITrimmerTitleStatusCountWithFilter": "{0} of {1} Title(s) Selected ({2} displayed)", - "XCITrimmerTitleStatusTrimming": "Trimming {0} Title(s)...", - "XCITrimmerTitleStatusUntrimming": "Untrimming {0} Title(s)...", - "XCITrimmerTitleStatusFailed": "Failed", - "XCITrimmerPotentialSavings": "Potential Savings", - "XCITrimmerActualSavings": "Actual Savings", + "XCITrimmerWindowTitle": "Recortador de archivos XCI", + "XCITrimmerTitleStatusCount": "{0} de {1} Título(s) seleccionado(s)", + "XCITrimmerTitleStatusCountWithFilter": "{0} de {1} Título(s) seleccionado(s) ({2} mostrado(s))", + "XCITrimmerTitleStatusTrimming": "Recortando {0} Título(s)...", + "XCITrimmerTitleStatusUntrimming": "Deshaciendo recorte de {0} Título(s)...", + "XCITrimmerTitleStatusFailed": "Fallido", + "XCITrimmerPotentialSavings": "Ahorro potencial", + "XCITrimmerActualSavings": "Ahorro real", "XCITrimmerSavingsMb": "{0:n0} Mb", - "XCITrimmerSelectDisplayed": "Select Shown", - "XCITrimmerDeselectDisplayed": "Deselect Shown", - "XCITrimmerSortName": "Title", - "XCITrimmerSortSaved": "Space Savings", - "XCITrimmerTrim": "Trim", - "XCITrimmerUntrim": "Untrim", + "XCITrimmerSelectDisplayed": "Seleccionar mostrado(s)", + "XCITrimmerDeselectDisplayed": "Deseleccionar mostrado(s)", + "XCITrimmerSortName": "Título", + "XCITrimmerSortSaved": "Ahorro de espacio", + "XCITrimmerTrim": "Recortar", + "XCITrimmerUntrim": "Deshacer recorte", "UpdateWindowUpdateAddedMessage": "{0} nueva(s) actualización(es) agregada(s)", "UpdateWindowBundledContentNotice": "Las actualizaciones agrupadas no pueden ser eliminadas, solamente deshabilitadas.", "CheatWindowHeading": "Cheats disponibles para {0} [{1}]", @@ -795,7 +795,7 @@ "AutoloadUpdateRemovedMessage": "Se eliminaron {0} actualización(es) faltantes", "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "Editar selección", - "Continue": "Continue", + "Continue": "Continuar", "Cancel": "Cancelar", "Save": "Guardar", "Discard": "Descartar", From d00754477eab8ec47ed3824d96b3a766dfe93bc2 Mon Sep 17 00:00:00 2001 From: WilliamWsyHK Date: Sat, 7 Dec 2024 18:03:01 +0800 Subject: [PATCH 38/61] Add Firmware keyword in log if it is indeed firmware (#343) Co-authored-by: LotP1 --- .../Processes/Extensions/NcaExtensions.cs | 6 ++++- .../Loaders/Processes/ProcessLoader.cs | 4 +-- .../Loaders/Processes/ProcessResult.cs | 13 ++++++--- src/Ryujinx.HLE/Switch.cs | 4 ++- src/Ryujinx/AppHost.cs | 6 +++-- .../UI/ViewModels/MainWindowViewModel.cs | 5 ++-- .../UI/Views/Main/MainMenuBarView.axaml.cs | 27 ++++++++++++++++--- 7 files changed, 50 insertions(+), 15 deletions(-) diff --git a/src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs b/src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs index 2928ac7fe5..361a9159eb 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs @@ -26,7 +26,7 @@ public static class NcaExtensions { private static readonly TitleUpdateMetadataJsonSerializerContext _applicationSerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - public static ProcessResult Load(this Nca nca, Switch device, Nca patchNca, Nca controlNca) + public static ProcessResult Load(this Nca nca, Switch device, Nca patchNca, Nca controlNca, BlitStruct? customNacpData = null) { // Extract RomFs and ExeFs from NCA. IStorage romFs = nca.GetRomFs(device, patchNca); @@ -55,6 +55,10 @@ public static ProcessResult Load(this Nca nca, Switch device, Nca patchNca, Nca { nacpData = controlNca.GetNacp(device); } + else if (customNacpData != null) // if the Application doesn't provide a nacp file but the Application provides an override, use the provided nacp override + { + nacpData = (BlitStruct)customNacpData; + } /* TODO: Rework this since it's wrong and doesn't work as it takes the DisplayVersion from a "potential" non-existent update. diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs index a0e7e0fa1e..fe8360f04e 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs @@ -98,12 +98,12 @@ public bool LoadNsp(string path, ulong applicationId) return false; } - public bool LoadNca(string path) + public bool LoadNca(string path, BlitStruct? customNacpData = null) { FileStream file = new(path, FileMode.Open, FileAccess.Read); Nca nca = new(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false)); - ProcessResult processResult = nca.Load(_device, null, null); + ProcessResult processResult = nca.Load(_device, null, null, customNacpData); if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult)) { diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs index e187b23605..3a70426704 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs @@ -84,12 +84,19 @@ public bool Start(Switch device) return false; } + bool isFirmware = ProgramId is >= 0x0100000000000819 and <= 0x010000000000081C; + bool isFirmwareApplication = ProgramId <= 0x0100000000007FFF; + + string name = !isFirmware + ? (isFirmwareApplication ? "Firmware Application " : "") + (!string.IsNullOrWhiteSpace(Name) ? Name : "") + : "Firmware"; + // TODO: LibHac npdm currently doesn't support version field. - string version = ProgramId > 0x0100000000007FFF - ? DisplayVersion + string version = !isFirmware + ? (!string.IsNullOrWhiteSpace(DisplayVersion) ? DisplayVersion : "") : device.System.ContentManager.GetCurrentFirmwareVersion()?.VersionString ?? "?"; - Logger.Info?.Print(LogClass.Loader, $"Application Loaded: {Name} v{version} [{ProgramIdText}] [{(Is64Bit ? "64-bit" : "32-bit")}]"); + Logger.Info?.Print(LogClass.Loader, $"Application Loaded: {name} v{version} [{ProgramIdText}] [{(Is64Bit ? "64-bit" : "32-bit")}]"); return true; } diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs index 4663521527..d0afdf1733 100644 --- a/src/Ryujinx.HLE/Switch.cs +++ b/src/Ryujinx.HLE/Switch.cs @@ -1,3 +1,5 @@ +using LibHac.Common; +using LibHac.Ns; using Ryujinx.Audio.Backends.CompatLayer; using Ryujinx.Audio.Integration; using Ryujinx.Common.Configuration; @@ -111,7 +113,7 @@ public void UpdateVSyncInterval() public bool LoadCart(string exeFsDir, string romFsFile = null) => Processes.LoadUnpackedNca(exeFsDir, romFsFile); public bool LoadXci(string xciFile, ulong applicationId = 0) => Processes.LoadXci(xciFile, applicationId); - public bool LoadNca(string ncaFile) => Processes.LoadNca(ncaFile); + public bool LoadNca(string ncaFile, BlitStruct? customNacpData = null) => Processes.LoadNca(ncaFile, customNacpData); public bool LoadNsp(string nspFile, ulong applicationId = 0) => Processes.LoadNsp(nspFile, applicationId); public bool LoadProgram(string fileName) => Processes.LoadNxo(fileName); diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index 9a7f82661c..65c798ac26 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -3,6 +3,8 @@ using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Input; using Avalonia.Threading; +using LibHac.Common; +using LibHac.Ns; using LibHac.Tools.FsSystem; using Ryujinx.Audio.Backends.Dummy; using Ryujinx.Audio.Backends.OpenAL; @@ -670,7 +672,7 @@ private void HideCursorState_Changed(object sender, ReactiveEventArgs LoadGuestApplication() + public async Task LoadGuestApplication(BlitStruct? customNacpData = null) { InitializeSwitchInstance(); MainWindow.UpdateGraphicsConfig(); @@ -740,7 +742,7 @@ await ContentDialogHelper.CreateInfoDialog( { Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA)."); - if (!Device.LoadNca(ApplicationPath)) + if (!Device.LoadNca(ApplicationPath, customNacpData)) { Device.Dispose(); diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 1bfcd439b6..04db947b94 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -10,6 +10,7 @@ using DynamicData.Binding; using FluentAvalonia.UI.Controls; using LibHac.Common; +using LibHac.Ns; using Ryujinx.Ava.Common; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Input; @@ -1897,7 +1898,7 @@ public async Task OpenFolder() } } - public async Task LoadApplication(ApplicationData application, bool startFullscreen = false) + public async Task LoadApplication(ApplicationData application, bool startFullscreen = false, BlitStruct? customNacpData = null) { if (AppHost != null) { @@ -1935,7 +1936,7 @@ await ContentDialogHelper.CreateInfoDialog( this, TopLevel); - if (!await AppHost.LoadGuestApplication()) + if (!await AppHost.LoadGuestApplication(customNacpData)) { AppHost.DisposeContext(); AppHost = null; diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs index a3aa58f2cc..94f5cf9d34 100644 --- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs @@ -3,7 +3,9 @@ using Avalonia.Interactivity; using Avalonia.Threading; using Gommon; +using LibHac.Common; using LibHac.Ncm; +using LibHac.Ns; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.UI.Helpers; @@ -19,6 +21,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; namespace Ryujinx.Ava.UI.Views.Main { @@ -123,18 +126,34 @@ public async void OpenSettings(object sender, RoutedEventArgs e) public async void OpenMiiApplet(object sender, RoutedEventArgs e) { - string contentPath = ViewModel.ContentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program); + const string name = "miiEdit"; + const ulong programId = 0x0100000000001009; + string contentPath = ViewModel.ContentManager.GetInstalledContentPath(programId, StorageId.BuiltInSystem, NcaContentType.Program); if (!string.IsNullOrEmpty(contentPath)) { ApplicationData applicationData = new() { - Name = "miiEdit", - Id = 0x0100000000001009, + Name = name, + Id = programId, Path = contentPath, }; - await ViewModel.LoadApplication(applicationData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen); + string version = "1.0.0"; + var nacpData = new BlitStruct(1); + + //version buffer + Encoding.ASCII.GetBytes(version).AsSpan().CopyTo(nacpData.ByteSpan.Slice(0x3060)); + + //name and distributor buffer + //repeat once for each locale (the ApplicationControlProperty has 16 locales) + for (int i = 0; i < 0x10; i++) + { + Encoding.ASCII.GetBytes(name).AsSpan().CopyTo(nacpData.ByteSpan.Slice(i * 0x300)); + "Ryujinx"u8.ToArray().AsSpan().CopyTo(nacpData.ByteSpan.Slice(i * 0x300 + 0x200)); + } + + await ViewModel.LoadApplication(applicationData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen, nacpData); } } From 5fbcb1f3a7b7f18a120db350f6b1fb3bd6cb305d Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Sat, 7 Dec 2024 04:05:39 -0600 Subject: [PATCH 39/61] misc: chore: Cleanups & unused parameter removal --- .../Controller/JoyconConfigControllerStick.cs | 4 +- .../App/ApplicationLibrary.cs | 39 +++++++------------ .../Configuration/ConfigurationState.cs | 8 +--- .../Helper/DownloadableContentsHelper.cs | 2 +- .../Helper/TitleUpdatesHelper.cs | 2 +- 5 files changed, 22 insertions(+), 33 deletions(-) diff --git a/src/Ryujinx.Common/Configuration/Hid/Controller/JoyconConfigControllerStick.cs b/src/Ryujinx.Common/Configuration/Hid/Controller/JoyconConfigControllerStick.cs index 6086815512..0765307443 100644 --- a/src/Ryujinx.Common/Configuration/Hid/Controller/JoyconConfigControllerStick.cs +++ b/src/Ryujinx.Common/Configuration/Hid/Controller/JoyconConfigControllerStick.cs @@ -1,6 +1,8 @@ namespace Ryujinx.Common.Configuration.Hid.Controller { - public class JoyconConfigControllerStick where TButton : unmanaged where TStick : unmanaged + public class JoyconConfigControllerStick + where TButton : unmanaged + where TStick : unmanaged { public TStick Joystick { get; set; } public bool InvertStickX { get; set; } diff --git a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs index 174db51adf..cc5a63ab8a 100644 --- a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs +++ b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs @@ -1,5 +1,4 @@ using DynamicData; -using DynamicData.Kernel; using Gommon; using LibHac; using LibHac.Common; @@ -37,14 +36,13 @@ using ContentType = LibHac.Ncm.ContentType; using MissingKeyException = LibHac.Common.Keys.MissingKeyException; using Path = System.IO.Path; -using SpanHelpers = LibHac.Common.SpanHelpers; using TimeSpan = System.TimeSpan; namespace Ryujinx.UI.App.Common { public class ApplicationLibrary { - public static string DefaultLanPlayWebHost = "ryuldnweb.vudjun.com"; + public const string DefaultLanPlayWebHost = "ryuldnweb.vudjun.com"; public Language DesiredLanguage { get; set; } public event EventHandler ApplicationCountUpdated; public event EventHandler LdnGameDataReceived; @@ -191,12 +189,9 @@ private ApplicationData GetApplicationFromNsp(PartitionFileSystem pfs, string fi } } - if (isExeFs) - { - return GetApplicationFromExeFs(pfs, filePath); - } - - return null; + return isExeFs + ? GetApplicationFromExeFs(pfs, filePath) + : null; } /// The configured key set is missing a key. @@ -512,10 +507,6 @@ public bool TryGetDownloadableContentFromFile(string filePath, out List { - DownloadableContentsHelper.SaveDownloadableContentsJson(_virtualFileSystem, application.IdBase, dlcs); + DownloadableContentsHelper.SaveDownloadableContentsJson(application.IdBase, dlcs); it.Remove(it.Items.Where(item => item.Dlc.TitleIdBase == application.IdBase)); it.AddOrUpdate(dlcs); @@ -839,7 +830,7 @@ public void SaveTitleUpdatesForGame(ApplicationData application, List<(TitleUpda { _titleUpdates.Edit(it => { - TitleUpdatesHelper.SaveTitleUpdatesJson(_virtualFileSystem, application.IdBase, updates); + TitleUpdatesHelper.SaveTitleUpdatesJson(application.IdBase, updates); it.Remove(it.Items.Where(item => item.TitleUpdate.TitleIdBase == application.IdBase)); it.AddOrUpdate(updates); @@ -1088,7 +1079,7 @@ public int AutoLoadTitleUpdates(List appDirs, out int numUpdatesRemoved) private bool AddAndAutoSelectUpdate(TitleUpdateModel update) { - var currentlySelected = TitleUpdates.Items.FirstOrOptional(it => + var currentlySelected = TitleUpdates.Items.FindFirst(it => it.TitleUpdate.TitleIdBase == update.TitleIdBase && it.IsSelected); var shouldSelect = !currentlySelected.HasValue || @@ -1464,7 +1455,7 @@ private void LoadDlcForApplication(ApplicationData application) if (addedNewDlc) { var gameDlcs = it.Items.Where(dlc => dlc.Dlc.TitleIdBase == application.IdBase).ToList(); - DownloadableContentsHelper.SaveDownloadableContentsJson(_virtualFileSystem, application.IdBase, + DownloadableContentsHelper.SaveDownloadableContentsJson(application.IdBase, gameDlcs); } } @@ -1483,7 +1474,7 @@ private bool LoadTitleUpdatesForApplication(ApplicationData application) TitleUpdatesHelper.LoadTitleUpdatesJson(_virtualFileSystem, application.IdBase); it.AddOrUpdate(savedUpdates); - var selectedUpdate = savedUpdates.FirstOrOptional(update => update.IsSelected); + var selectedUpdate = savedUpdates.FindFirst(update => update.IsSelected); if (TryGetTitleUpdatesFromFile(application.Path, out var bundledUpdates)) { @@ -1498,9 +1489,9 @@ private bool LoadTitleUpdatesForApplication(ApplicationData application) if (!selectedUpdate.HasValue || selectedUpdate.Value.Item1.Version < update.Version) { shouldSelect = true; - if (selectedUpdate.HasValue) + if (selectedUpdate) _titleUpdates.AddOrUpdate((selectedUpdate.Value.Item1, false)); - selectedUpdate = DynamicData.Kernel.Optional<(TitleUpdateModel, bool IsSelected)>.Create((update, true)); + selectedUpdate = (update, true); } modifiedVersion = modifiedVersion || shouldSelect; @@ -1513,7 +1504,7 @@ private bool LoadTitleUpdatesForApplication(ApplicationData application) if (updatesChanged) { var gameUpdates = it.Items.Where(update => update.TitleUpdate.TitleIdBase == application.IdBase).ToList(); - TitleUpdatesHelper.SaveTitleUpdatesJson(_virtualFileSystem, application.IdBase, gameUpdates); + TitleUpdatesHelper.SaveTitleUpdatesJson(application.IdBase, gameUpdates); } } }); @@ -1525,14 +1516,14 @@ private bool LoadTitleUpdatesForApplication(ApplicationData application) private void SaveDownloadableContentsForGame(ulong titleIdBase) { var dlcs = DownloadableContents.Items.Where(dlc => dlc.Dlc.TitleIdBase == titleIdBase).ToList(); - DownloadableContentsHelper.SaveDownloadableContentsJson(_virtualFileSystem, titleIdBase, dlcs); + DownloadableContentsHelper.SaveDownloadableContentsJson(titleIdBase, dlcs); } // Save the _currently tracked_ update state for the game private void SaveTitleUpdatesForGame(ulong titleIdBase) { var updates = TitleUpdates.Items.Where(update => update.TitleUpdate.TitleIdBase == titleIdBase).ToList(); - TitleUpdatesHelper.SaveTitleUpdatesJson(_virtualFileSystem, titleIdBase, updates); + TitleUpdatesHelper.SaveTitleUpdatesJson(titleIdBase, updates); } // ApplicationData isnt live-updating (e.g. when an update gets applied) and so this is meant to trigger a refresh diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs index badb047df2..04ddd442f0 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs @@ -1,16 +1,12 @@ -using ARMeilleure; using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Hid; -using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Keyboard; using Ryujinx.Common.Configuration.Multiplayer; -using Ryujinx.Common.Logging; using Ryujinx.Graphics.Vulkan; using Ryujinx.HLE; using Ryujinx.UI.Common.Configuration.System; using Ryujinx.UI.Common.Configuration.UI; using System; -using System.Collections.Generic; namespace Ryujinx.UI.Common.Configuration { @@ -21,10 +17,10 @@ public static void Initialize() if (Instance != null) { throw new InvalidOperationException("Configuration is already initialized"); - } + } Instance = new ConfigurationState(); - } + } public ConfigurationFileFormat ToFileFormat() { diff --git a/src/Ryujinx.UI.Common/Helper/DownloadableContentsHelper.cs b/src/Ryujinx.UI.Common/Helper/DownloadableContentsHelper.cs index 3695c5c5c8..020529b553 100644 --- a/src/Ryujinx.UI.Common/Helper/DownloadableContentsHelper.cs +++ b/src/Ryujinx.UI.Common/Helper/DownloadableContentsHelper.cs @@ -42,7 +42,7 @@ public static class DownloadableContentsHelper } } - public static void SaveDownloadableContentsJson(VirtualFileSystem vfs, ulong applicationIdBase, List<(DownloadableContentModel, bool IsEnabled)> dlcs) + public static void SaveDownloadableContentsJson(ulong applicationIdBase, List<(DownloadableContentModel, bool IsEnabled)> dlcs) { DownloadableContentContainer container = default; List downloadableContentContainerList = new(); diff --git a/src/Ryujinx.UI.Common/Helper/TitleUpdatesHelper.cs b/src/Ryujinx.UI.Common/Helper/TitleUpdatesHelper.cs index 18fbabd6d8..c6bacfd919 100644 --- a/src/Ryujinx.UI.Common/Helper/TitleUpdatesHelper.cs +++ b/src/Ryujinx.UI.Common/Helper/TitleUpdatesHelper.cs @@ -49,7 +49,7 @@ public static class TitleUpdatesHelper } } - public static void SaveTitleUpdatesJson(VirtualFileSystem vfs, ulong applicationIdBase, List<(TitleUpdateModel, bool IsSelected)> updates) + public static void SaveTitleUpdatesJson(ulong applicationIdBase, List<(TitleUpdateModel, bool IsSelected)> updates) { var titleUpdateWindowData = new TitleUpdateMetadata { From eda4f4349bdde7809ccbea44364634901c3c8c7b Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Sat, 7 Dec 2024 04:06:07 -0600 Subject: [PATCH 40/61] headless: Actually log the command line errors --- src/Ryujinx.Headless.SDL2/Program.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Ryujinx.Headless.SDL2/Program.cs b/src/Ryujinx.Headless.SDL2/Program.cs index ff87a38457..12158176a5 100644 --- a/src/Ryujinx.Headless.SDL2/Program.cs +++ b/src/Ryujinx.Headless.SDL2/Program.cs @@ -1,4 +1,5 @@ using CommandLine; +using Gommon; using LibHac.Tools.FsSystem; using Ryujinx.Audio.Backends.SDL2; using Ryujinx.Common; @@ -96,8 +97,13 @@ static void Main(string[] args) } Parser.Default.ParseArguments(args) - .WithParsed(Load) - .WithNotParsed(errors => errors.Output()); + .WithParsed(Load) + .WithNotParsed(errors => + { + Logger.Error?.PrintMsg(LogClass.Application, "Error parsing command-line arguments:"); + + errors.ForEach(err => Logger.Error?.PrintMsg(LogClass.Application, $" - {err.Tag}")); + }); } private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index) @@ -579,8 +585,8 @@ private static Switch InitializeEmulationContext(WindowBase window, IRenderer re options.MultiplayerLanInterfaceId, Common.Configuration.Multiplayer.MultiplayerMode.Disabled, false, - "", - "", + string.Empty, + string.Empty, options.CustomVSyncInterval); return new Switch(configuration); From 290a6ad5de02251604c9a85bb8a5b162fa01e0b7 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Sat, 7 Dec 2024 04:30:04 -0600 Subject: [PATCH 41/61] HLE: extract custom NACP data functionality into a static helper for generic reuse elsewhere, and clarify magic numbers. --- src/Ryujinx.HLE/StructHelpers.cs | 37 +++++++++++++++++++ .../UI/Views/Main/MainMenuBarView.axaml.cs | 31 ++++++---------- 2 files changed, 48 insertions(+), 20 deletions(-) create mode 100644 src/Ryujinx.HLE/StructHelpers.cs diff --git a/src/Ryujinx.HLE/StructHelpers.cs b/src/Ryujinx.HLE/StructHelpers.cs new file mode 100644 index 0000000000..6e6af8cea1 --- /dev/null +++ b/src/Ryujinx.HLE/StructHelpers.cs @@ -0,0 +1,37 @@ +using LibHac.Common; +using LibHac.Ns; +using System; +using System.Text; + +namespace Ryujinx.HLE +{ + public static class StructHelpers + { + public static BlitStruct CreateCustomNacpData(string name, string version) + { + // https://switchbrew.org/wiki/NACP + const int OffsetOfDisplayVersion = 0x3060; + + // https://switchbrew.org/wiki/NACP#ApplicationTitle + const int TotalApplicationTitles = 0x10; + const int SizeOfApplicationTitle = 0x300; + const int OffsetOfApplicationPublisherStrings = 0x200; + + + var nacpData = new BlitStruct(1); + + // name and publisher buffer + // repeat once for each locale (the ApplicationControlProperty has 16 locales) + for (int i = 0; i < TotalApplicationTitles; i++) + { + Encoding.ASCII.GetBytes(name).AsSpan().CopyTo(nacpData.ByteSpan[(i * SizeOfApplicationTitle)..]); + "Ryujinx"u8.CopyTo(nacpData.ByteSpan[(i * SizeOfApplicationTitle + OffsetOfApplicationPublisherStrings)..]); + } + + // version buffer + Encoding.ASCII.GetBytes(version).AsSpan().CopyTo(nacpData.ByteSpan[OffsetOfDisplayVersion..]); + + return nacpData; + } + } +} diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs index 94f5cf9d34..ffe9b066c3 100644 --- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs @@ -13,6 +13,7 @@ using Ryujinx.Ava.UI.Windows; using Ryujinx.Common; using Ryujinx.Common.Utilities; +using Ryujinx.HLE; using Ryujinx.UI.App.Common; using Ryujinx.UI.Common; using Ryujinx.UI.Common.Configuration; @@ -126,32 +127,22 @@ public async void OpenSettings(object sender, RoutedEventArgs e) public async void OpenMiiApplet(object sender, RoutedEventArgs e) { - const string name = "miiEdit"; - const ulong programId = 0x0100000000001009; - string contentPath = ViewModel.ContentManager.GetInstalledContentPath(programId, StorageId.BuiltInSystem, NcaContentType.Program); + const string AppletName = "miiEdit"; + const ulong AppletProgramId = 0x0100000000001009; + const string AppletVersion = "1.0.0"; + + string contentPath = ViewModel.ContentManager.GetInstalledContentPath(AppletProgramId, StorageId.BuiltInSystem, NcaContentType.Program); if (!string.IsNullOrEmpty(contentPath)) { ApplicationData applicationData = new() { - Name = name, - Id = programId, - Path = contentPath, + Name = AppletName, + Id = AppletProgramId, + Path = contentPath }; - - string version = "1.0.0"; - var nacpData = new BlitStruct(1); - - //version buffer - Encoding.ASCII.GetBytes(version).AsSpan().CopyTo(nacpData.ByteSpan.Slice(0x3060)); - - //name and distributor buffer - //repeat once for each locale (the ApplicationControlProperty has 16 locales) - for (int i = 0; i < 0x10; i++) - { - Encoding.ASCII.GetBytes(name).AsSpan().CopyTo(nacpData.ByteSpan.Slice(i * 0x300)); - "Ryujinx"u8.ToArray().AsSpan().CopyTo(nacpData.ByteSpan.Slice(i * 0x300 + 0x200)); - } + + var nacpData = StructHelpers.CreateCustomNacpData(AppletName, AppletVersion); await ViewModel.LoadApplication(applicationData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen, nacpData); } From 4ffb8aef129ac4b0469c862cc595aa97816d4808 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Sat, 7 Dec 2024 05:21:16 -0600 Subject: [PATCH 42/61] Try and fix nullref --- src/Ryujinx.UI.Common/App/ApplicationLibrary.cs | 12 +++++------- src/Ryujinx.UI.Common/Helper/TitleUpdatesHelper.cs | 4 ++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs index cc5a63ab8a..a750db997e 100644 --- a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs +++ b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs @@ -1082,11 +1082,10 @@ private bool AddAndAutoSelectUpdate(TitleUpdateModel update) var currentlySelected = TitleUpdates.Items.FindFirst(it => it.TitleUpdate.TitleIdBase == update.TitleIdBase && it.IsSelected); - var shouldSelect = !currentlySelected.HasValue || - currentlySelected.Value.TitleUpdate.Version < update.Version; + var shouldSelect = currentlySelected.Check(curr => curr.TitleUpdate.Version < update.Version); _titleUpdates.AddOrUpdate((update, shouldSelect)); - + if (currentlySelected.HasValue && shouldSelect) { _titleUpdates.AddOrUpdate((currentlySelected.Value.TitleUpdate, false)); @@ -1478,7 +1477,7 @@ private bool LoadTitleUpdatesForApplication(ApplicationData application) if (TryGetTitleUpdatesFromFile(application.Path, out var bundledUpdates)) { - var savedUpdateLookup = savedUpdates.Select(update => update.Item1).ToHashSet(); + var savedUpdateLookup = savedUpdates.Select(update => update.Update).ToHashSet(); bool updatesChanged = false; foreach (var update in bundledUpdates.OrderByDescending(bundled => bundled.Version)) @@ -1486,11 +1485,10 @@ private bool LoadTitleUpdatesForApplication(ApplicationData application) if (!savedUpdateLookup.Contains(update)) { bool shouldSelect = false; - if (!selectedUpdate.HasValue || selectedUpdate.Value.Item1.Version < update.Version) + if (selectedUpdate.Check(su => su.Update.Version < update.Version)) { shouldSelect = true; - if (selectedUpdate) - _titleUpdates.AddOrUpdate((selectedUpdate.Value.Item1, false)); + _titleUpdates.AddOrUpdate((selectedUpdate.Value.Update, false)); selectedUpdate = (update, true); } diff --git a/src/Ryujinx.UI.Common/Helper/TitleUpdatesHelper.cs b/src/Ryujinx.UI.Common/Helper/TitleUpdatesHelper.cs index c6bacfd919..36de8b31a3 100644 --- a/src/Ryujinx.UI.Common/Helper/TitleUpdatesHelper.cs +++ b/src/Ryujinx.UI.Common/Helper/TitleUpdatesHelper.cs @@ -28,7 +28,7 @@ public static class TitleUpdatesHelper { private static readonly TitleUpdateMetadataJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - public static List<(TitleUpdateModel, bool IsSelected)> LoadTitleUpdatesJson(VirtualFileSystem vfs, ulong applicationIdBase) + public static List<(TitleUpdateModel Update, bool IsSelected)> LoadTitleUpdatesJson(VirtualFileSystem vfs, ulong applicationIdBase) { var titleUpdatesJsonPath = PathToGameUpdatesJson(applicationIdBase); @@ -77,7 +77,7 @@ public static void SaveTitleUpdatesJson(ulong applicationIdBase, List<(TitleUpda JsonHelper.SerializeToFile(titleUpdatesJsonPath, titleUpdateWindowData, _serializerContext.TitleUpdateMetadata); } - private static List<(TitleUpdateModel, bool IsSelected)> LoadTitleUpdates(VirtualFileSystem vfs, TitleUpdateMetadata titleUpdateMetadata, ulong applicationIdBase) + private static List<(TitleUpdateModel Update, bool IsSelected)> LoadTitleUpdates(VirtualFileSystem vfs, TitleUpdateMetadata titleUpdateMetadata, ulong applicationIdBase) { var result = new List<(TitleUpdateModel, bool IsSelected)>(); From 315a1819c0f85b6dea7ae971af59b6de64598d75 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Sat, 7 Dec 2024 05:31:37 -0600 Subject: [PATCH 43/61] Attempt #2 --- src/Ryujinx.UI.Common/App/ApplicationLibrary.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs index a750db997e..cb6467f5e2 100644 --- a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs +++ b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs @@ -1079,10 +1079,12 @@ public int AutoLoadTitleUpdates(List appDirs, out int numUpdatesRemoved) private bool AddAndAutoSelectUpdate(TitleUpdateModel update) { + if (update == null) return false; + var currentlySelected = TitleUpdates.Items.FindFirst(it => it.TitleUpdate.TitleIdBase == update.TitleIdBase && it.IsSelected); - var shouldSelect = currentlySelected.Check(curr => curr.TitleUpdate.Version < update.Version); + var shouldSelect = currentlySelected.Check(curr => curr.TitleUpdate?.Version < update.Version); _titleUpdates.AddOrUpdate((update, shouldSelect)); @@ -1485,7 +1487,7 @@ private bool LoadTitleUpdatesForApplication(ApplicationData application) if (!savedUpdateLookup.Contains(update)) { bool shouldSelect = false; - if (selectedUpdate.Check(su => su.Update.Version < update.Version)) + if (selectedUpdate.Check(su => su.Update?.Version < update.Version)) { shouldSelect = true; _titleUpdates.AddOrUpdate((selectedUpdate.Value.Update, false)); From de00a71690d482f14e54d1b1fd21a8d04de84929 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Sat, 7 Dec 2024 05:48:11 -0600 Subject: [PATCH 44/61] UI: Fix missing total DLC count. Fixes #347. --- src/Ryujinx/Assets/Locales/ar_SA.json | 2 +- src/Ryujinx/Assets/Locales/de_DE.json | 2 +- src/Ryujinx/Assets/Locales/el_GR.json | 2 +- src/Ryujinx/Assets/Locales/en_US.json | 2 +- src/Ryujinx/Assets/Locales/es_ES.json | 2 +- src/Ryujinx/Assets/Locales/fr_FR.json | 2 +- src/Ryujinx/Assets/Locales/it_IT.json | 2 +- src/Ryujinx/Assets/Locales/ja_JP.json | 2 +- src/Ryujinx/Assets/Locales/ko_KR.json | 2 +- src/Ryujinx/Assets/Locales/pl_PL.json | 2 +- src/Ryujinx/Assets/Locales/pt_BR.json | 2 +- src/Ryujinx/Assets/Locales/ru_RU.json | 2 +- src/Ryujinx/Assets/Locales/th_TH.json | 2 +- src/Ryujinx/Assets/Locales/tr_TR.json | 2 +- src/Ryujinx/Assets/Locales/uk_UA.json | 2 +- src/Ryujinx/Assets/Locales/zh_TW.json | 2 +- .../DownloadableContentManagerWindow.axaml.cs | 14 ++++---------- 17 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/Ryujinx/Assets/Locales/ar_SA.json b/src/Ryujinx/Assets/Locales/ar_SA.json index 412695af69..bdb6a92ecc 100644 --- a/src/Ryujinx/Assets/Locales/ar_SA.json +++ b/src/Ryujinx/Assets/Locales/ar_SA.json @@ -788,7 +788,7 @@ "CheatWindowHeading": "الغش متوفر لـ {0} [{1}]", "BuildId": "معرف البناء:", "DlcWindowBundledContentNotice": "Bundled DLC cannot be removed, only disabled.", - "DlcWindowHeading": "المحتويات القابلة للتنزيل {0}", + "DlcWindowHeading": "{0} DLC(s) available", "DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added", "AutoloadDlcAddedMessage": "{0} new downloadable content(s) added", "AutoloadDlcRemovedMessage": "{0} missing downloadable content(s) removed", diff --git a/src/Ryujinx/Assets/Locales/de_DE.json b/src/Ryujinx/Assets/Locales/de_DE.json index 76e8dfadd3..9f4b63a951 100644 --- a/src/Ryujinx/Assets/Locales/de_DE.json +++ b/src/Ryujinx/Assets/Locales/de_DE.json @@ -788,7 +788,7 @@ "CheatWindowHeading": "Cheats verfügbar für {0} [{1}]", "DlcWindowBundledContentNotice": "Bundled DLC cannot be removed, only disabled.", "BuildId": "BuildId:", - "DlcWindowHeading": "DLC verfügbar für {0} [{1}]", + "DlcWindowHeading": "{0} DLC(s) available", "DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added", "AutoloadDlcAddedMessage": "{0} new downloadable content(s) added", "AutoloadDlcRemovedMessage": "{0} missing downloadable content(s) removed", diff --git a/src/Ryujinx/Assets/Locales/el_GR.json b/src/Ryujinx/Assets/Locales/el_GR.json index 0409297acf..7979b92288 100644 --- a/src/Ryujinx/Assets/Locales/el_GR.json +++ b/src/Ryujinx/Assets/Locales/el_GR.json @@ -788,7 +788,7 @@ "CheatWindowHeading": "Διαθέσιμα Cheats για {0} [{1}]", "BuildId": "BuildId:", "DlcWindowBundledContentNotice": "Bundled DLC cannot be removed, only disabled.", - "DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})", + "DlcWindowHeading": "{0} DLC(s) available", "DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added", "AutoloadDlcAddedMessage": "{0} new downloadable content(s) added", "AutoloadDlcRemovedMessage": "{0} missing downloadable content(s) removed", diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json index ba183c8bdb..0598665fd9 100644 --- a/src/Ryujinx/Assets/Locales/en_US.json +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -802,7 +802,7 @@ "CheatWindowHeading": "Cheats Available for {0} [{1}]", "BuildId": "BuildId:", "DlcWindowBundledContentNotice": "Bundled DLC cannot be removed, only disabled.", - "DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})", + "DlcWindowHeading": "{0} DLC(s) available", "DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added", "AutoloadDlcAddedMessage": "{0} new downloadable content(s) added", "AutoloadDlcRemovedMessage": "{0} missing downloadable content(s) removed", diff --git a/src/Ryujinx/Assets/Locales/es_ES.json b/src/Ryujinx/Assets/Locales/es_ES.json index 934031c728..3774605f62 100644 --- a/src/Ryujinx/Assets/Locales/es_ES.json +++ b/src/Ryujinx/Assets/Locales/es_ES.json @@ -787,7 +787,7 @@ "UpdateWindowBundledContentNotice": "Las actualizaciones agrupadas no pueden ser eliminadas, solamente deshabilitadas.", "CheatWindowHeading": "Cheats disponibles para {0} [{1}]", "BuildId": "Id de compilación:", - "DlcWindowHeading": "Contenido descargable disponible para {0} [{1}]", + "DlcWindowHeading": "{0} DLC(s) available", "DlcWindowDlcAddedMessage": "Se agregaron {0} nuevo(s) contenido(s) descargable(s)", "AutoloadDlcAddedMessage": "Se agregaron {0} nuevo(s) contenido(s) descargable(s)", "AutoloadDlcRemovedMessage": "Se eliminaron {0} contenido(s) descargable(s) faltantes", diff --git a/src/Ryujinx/Assets/Locales/fr_FR.json b/src/Ryujinx/Assets/Locales/fr_FR.json index 0223e322eb..c5a4bbeec8 100644 --- a/src/Ryujinx/Assets/Locales/fr_FR.json +++ b/src/Ryujinx/Assets/Locales/fr_FR.json @@ -788,7 +788,7 @@ "CheatWindowHeading": "Cheats disponibles pour {0} [{1}]", "BuildId": "BuildId :", "DlcWindowBundledContentNotice": "Les DLC inclus avec le jeu ne peuvent pas être supprimés mais peuvent être désactivés.", - "DlcWindowHeading": "{0} Contenu(s) téléchargeable(s)", + "DlcWindowHeading": "{0} DLC(s) available", "DlcWindowDlcAddedMessage": "{0} nouveau(x) contenu(s) téléchargeable(s) ajouté(s)", "AutoloadDlcAddedMessage": "{0} nouveau(x) contenu(s) téléchargeable(s) ajouté(s)", "AutoloadDlcRemovedMessage": "{0} contenu(s) téléchargeable(s) manquant(s) supprimé(s)", diff --git a/src/Ryujinx/Assets/Locales/it_IT.json b/src/Ryujinx/Assets/Locales/it_IT.json index 5ca17bc2ed..18e4ee04f7 100644 --- a/src/Ryujinx/Assets/Locales/it_IT.json +++ b/src/Ryujinx/Assets/Locales/it_IT.json @@ -788,7 +788,7 @@ "CheatWindowHeading": "Trucchi disponibili per {0} [{1}]", "BuildId": "ID Build", "DlcWindowBundledContentNotice": "i DLC \"impacchettati\" non possono essere rimossi, ma solo disabilitati.", - "DlcWindowHeading": "DLC disponibili per {0} [{1}]", + "DlcWindowHeading": "{0} DLC(s) available", "DlcWindowDlcAddedMessage": "{0} nuovo/i contenuto/i scaricabile/i aggiunto/i", "AutoloadDlcAddedMessage": "{0} contenuto/i scaricabile/i aggiunto/i", "AutoloadDlcRemovedMessage": "{0} contenuto/i scaricabile/i mancante/i rimosso/i", diff --git a/src/Ryujinx/Assets/Locales/ja_JP.json b/src/Ryujinx/Assets/Locales/ja_JP.json index ffa768c137..6ecc740097 100644 --- a/src/Ryujinx/Assets/Locales/ja_JP.json +++ b/src/Ryujinx/Assets/Locales/ja_JP.json @@ -787,7 +787,7 @@ "UpdateWindowBundledContentNotice": "Bundled updates cannot be removed, only disabled.", "CheatWindowHeading": "利用可能なチート {0} [{1}]", "BuildId": "ビルドID:", - "DlcWindowHeading": "利用可能な DLC {0} [{1}]", + "DlcWindowHeading": "{0} DLC(s) available", "DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added", "AutoloadDlcAddedMessage": "{0} new downloadable content(s) added", "AutoloadDlcRemovedMessage": "{0} missing downloadable content(s) removed", diff --git a/src/Ryujinx/Assets/Locales/ko_KR.json b/src/Ryujinx/Assets/Locales/ko_KR.json index 8731c8662a..71aaa41e87 100644 --- a/src/Ryujinx/Assets/Locales/ko_KR.json +++ b/src/Ryujinx/Assets/Locales/ko_KR.json @@ -788,7 +788,7 @@ "CheatWindowHeading": "{0} [{1}]에 사용 가능한 치트", "BuildId": "빌드ID:", "DlcWindowBundledContentNotice": "번들 DLC는 제거할 수 없으며 비활성화만 가능합니다.", - "DlcWindowHeading": "{1} ({2})에 내려받기 가능한 콘텐츠 {0}개 사용 가능", + "DlcWindowHeading": "{0} DLC(s) available", "DlcWindowDlcAddedMessage": "{0}개의 새로운 내려받기 가능한 콘텐츠가 추가됨", "AutoloadDlcAddedMessage": "{0}개의 새로운 내려받기 가능한 콘텐츠가 추가됨", "AutoloadDlcRemovedMessage": "{0}개의 내려받기 가능한 콘텐츠가 제거됨", diff --git a/src/Ryujinx/Assets/Locales/pl_PL.json b/src/Ryujinx/Assets/Locales/pl_PL.json index d87453ef2a..6a2fa08b1a 100644 --- a/src/Ryujinx/Assets/Locales/pl_PL.json +++ b/src/Ryujinx/Assets/Locales/pl_PL.json @@ -788,7 +788,7 @@ "CheatWindowHeading": "Kody Dostępne dla {0} [{1}]", "BuildId": "Identyfikator wersji:", "DlcWindowBundledContentNotice": "Bundled DLC cannot be removed, only disabled.", - "DlcWindowHeading": "{0} Zawartości do Pobrania dostępna dla {1} ({2})", + "DlcWindowHeading": "{0} DLC(s) available", "DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added", "AutoloadDlcAddedMessage": "{0} new downloadable content(s) added", "AutoloadDlcRemovedMessage": "{0} missing downloadable content(s) removed", diff --git a/src/Ryujinx/Assets/Locales/pt_BR.json b/src/Ryujinx/Assets/Locales/pt_BR.json index c240bd8049..90b78b7326 100644 --- a/src/Ryujinx/Assets/Locales/pt_BR.json +++ b/src/Ryujinx/Assets/Locales/pt_BR.json @@ -787,7 +787,7 @@ "CheatWindowHeading": "Cheats disponíveis para {0} [{1}]", "BuildId": "ID da Build:", "DlcWindowBundledContentNotice": "DLCs incorporadas não podem ser removidas, apenas desativadas.", - "DlcWindowHeading": "{0} DLCs disponíveis para {1} ({2})", + "DlcWindowHeading": "{0} DLC(s) available", "DlcWindowDlcAddedMessage": "{0} novo(s) conteúdo(s) para download adicionado(s)", "AutoloadDlcAddedMessage": "{0} novo(s) conteúdo(s) para download adicionado(s)", "AutoloadDlcRemovedMessage": "{0} conteúdo(s) para download ausente(s) removido(s)", diff --git a/src/Ryujinx/Assets/Locales/ru_RU.json b/src/Ryujinx/Assets/Locales/ru_RU.json index 1046208fbf..f058154e93 100644 --- a/src/Ryujinx/Assets/Locales/ru_RU.json +++ b/src/Ryujinx/Assets/Locales/ru_RU.json @@ -788,7 +788,7 @@ "CheatWindowHeading": "Доступные читы для {0} [{1}]", "BuildId": "ID версии:", "DlcWindowBundledContentNotice": "Bundled DLC cannot be removed, only disabled.", - "DlcWindowHeading": "{0} DLC", + "DlcWindowHeading": "{0} DLC(s) available", "DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added", "AutoloadDlcAddedMessage": "{0} new downloadable content(s) added", "AutoloadDlcRemovedMessage": "{0} missing downloadable content(s) removed", diff --git a/src/Ryujinx/Assets/Locales/th_TH.json b/src/Ryujinx/Assets/Locales/th_TH.json index e29004e109..33b2c4f308 100644 --- a/src/Ryujinx/Assets/Locales/th_TH.json +++ b/src/Ryujinx/Assets/Locales/th_TH.json @@ -788,7 +788,7 @@ "CheatWindowHeading": "สูตรโกงมีให้สำหรับ {0} [{1}]", "BuildId": "รหัสการสร้าง:", "DlcWindowBundledContentNotice": "แพ็ค DLC ไม่สามารถลบทิ้งได้ สามารถปิดใช้งานได้เท่านั้น", - "DlcWindowHeading": "{0} DLC ที่สามารถดาวน์โหลดได้", + "DlcWindowHeading": "{0} DLC(s) available", "DlcWindowDlcAddedMessage": "{0} DLC ใหม่ที่เพิ่มเข้ามา", "AutoloadDlcAddedMessage": "{0} ใหม่ที่เพิ่มเข้ามา", "AutoloadDlcRemovedMessage": "{0} missing downloadable content(s) removed", diff --git a/src/Ryujinx/Assets/Locales/tr_TR.json b/src/Ryujinx/Assets/Locales/tr_TR.json index 1012062104..72da205cbc 100644 --- a/src/Ryujinx/Assets/Locales/tr_TR.json +++ b/src/Ryujinx/Assets/Locales/tr_TR.json @@ -788,7 +788,7 @@ "CheatWindowHeading": "{0} için Hile mevcut [{1}]", "BuildId": "BuildId:", "DlcWindowBundledContentNotice": "Bundled DLC cannot be removed, only disabled.", - "DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})", + "DlcWindowHeading": "{0} DLC(s) available", "DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added", "AutoloadDlcAddedMessage": "{0} new downloadable content(s) added", "AutoloadDlcRemovedMessage": "{0} missing downloadable content(s) removed", diff --git a/src/Ryujinx/Assets/Locales/uk_UA.json b/src/Ryujinx/Assets/Locales/uk_UA.json index 89e565bf3a..06f6586409 100644 --- a/src/Ryujinx/Assets/Locales/uk_UA.json +++ b/src/Ryujinx/Assets/Locales/uk_UA.json @@ -788,7 +788,7 @@ "CheatWindowHeading": "Коди доступні для {0} [{1}]", "BuildId": "ID збірки:", "DlcWindowBundledContentNotice": "Bundled DLC cannot be removed, only disabled.", - "DlcWindowHeading": "Вміст для завантаження, доступний для {1} ({2}): {0}", + "DlcWindowHeading": "{0} DLC(s) available", "DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added", "AutoloadDlcAddedMessage": "{0} new downloadable content(s) added", "AutoloadDlcRemovedMessage": "{0} missing downloadable content(s) removed", diff --git a/src/Ryujinx/Assets/Locales/zh_TW.json b/src/Ryujinx/Assets/Locales/zh_TW.json index 792ced42b2..64f1378853 100644 --- a/src/Ryujinx/Assets/Locales/zh_TW.json +++ b/src/Ryujinx/Assets/Locales/zh_TW.json @@ -788,7 +788,7 @@ "CheatWindowHeading": "可用於 {0} [{1}] 的密技", "BuildId": "組建識別碼:", "DlcWindowBundledContentNotice": "附帶的 DLC 只能被停用而無法被刪除。", - "DlcWindowHeading": "{0} 個可下載內容", + "DlcWindowHeading": "{0} DLC(s) available", "DlcWindowDlcAddedMessage": "已加入 {0} 個 DLC", "AutoloadDlcAddedMessage": "已加入 {0} 個 DLC", "AutoloadDlcRemovedMessage": "已刪除 {0} 個遺失的 DLC", diff --git a/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs b/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs index 340515a5b9..2afa8b5298 100644 --- a/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs @@ -61,23 +61,17 @@ private void Close(object sender, RoutedEventArgs e) private void RemoveDLC(object sender, RoutedEventArgs e) { - if (sender is Button button) + if (sender is Button { DataContext: DownloadableContentModel dlc }) { - if (button.DataContext is DownloadableContentModel model) - { - ViewModel.Remove(model); - } + ViewModel.Remove(dlc); } } private void OpenLocation(object sender, RoutedEventArgs e) { - if (sender is Button button) + if (sender is Button { DataContext: DownloadableContentModel dlc }) { - if (button.DataContext is DownloadableContentModel model) - { - OpenHelper.LocateFile(model.ContainerPath); - } + OpenHelper.LocateFile(dlc.ContainerPath); } } From 06abba25c1f63737e5bccbbabbc71a6ade33c366 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Sat, 7 Dec 2024 06:22:46 -0600 Subject: [PATCH 45/61] UI: Adapt accent color to the user's system. https://amwx.github.io/FluentAvaloniaDocs/pages/FATheme/Accents#using-the-systems-accent-color --- src/Ryujinx/App.axaml | 2 +- src/Ryujinx/Assets/Styles/Themes.xaml | 36 +------------------ .../Common/Markup/BasicMarkupExtension.cs | 4 +-- src/Ryujinx/Common/Markup/MarkupExtensions.cs | 6 ++-- .../UI/Controls/ApplicationGridView.axaml | 2 +- .../UI/Controls/ApplicationListView.axaml | 2 +- 6 files changed, 9 insertions(+), 43 deletions(-) diff --git a/src/Ryujinx/App.axaml b/src/Ryujinx/App.axaml index 5a603509cd..5c96f97f2c 100644 --- a/src/Ryujinx/App.axaml +++ b/src/Ryujinx/App.axaml @@ -11,7 +11,7 @@ - + diff --git a/src/Ryujinx/Assets/Styles/Themes.xaml b/src/Ryujinx/Assets/Styles/Themes.xaml index 056eba2282..46e2980353 100644 --- a/src/Ryujinx/Assets/Styles/Themes.xaml +++ b/src/Ryujinx/Assets/Styles/Themes.xaml @@ -4,18 +4,6 @@ - - - #FF00C3E3 - #FF00C3E3 - #FF00C3E3 - #FF00C3E3 - #FF00C3E3 - #FF00C3E3 - #FF00C3E3 - #FFe8e8e8 #FF00FABB #FFF0F0F0 #FFd6d6d6 @@ -26,6 +14,7 @@ #b3ffffff #80cccccc #A0000000 + #fffcd12a #FF2EEAC9 #FFFF4554 #6483F5 @@ -33,18 +22,6 @@ - - - #FF00C3E3 - #FF00C3E3 - #FF00C3E3 - #FF00C3E3 - #FF00C3E3 - #FF00C3E3 - #FF00C3E3 - #FFe8e8e8 #FF00FABB #FFF0F0F0 #FFd6d6d6 @@ -59,18 +36,7 @@ - - #008AA8 - #FF00C3E3 - #FF99b000 - #FF006d7d - #FF00525E - #FF00dbff - #FF19dfff - #FF33e3ff #FF00FABB #FF2D2D2D #FF505050 diff --git a/src/Ryujinx/Common/Markup/BasicMarkupExtension.cs b/src/Ryujinx/Common/Markup/BasicMarkupExtension.cs index 67c0165629..b1b7361a62 100644 --- a/src/Ryujinx/Common/Markup/BasicMarkupExtension.cs +++ b/src/Ryujinx/Common/Markup/BasicMarkupExtension.cs @@ -17,13 +17,13 @@ internal abstract class BasicMarkupExtension : MarkupExtension public virtual string Name => "Item"; public virtual Action? Setter => null; - protected abstract T? GetValue(); + protected abstract T? Value { get; } protected virtual void ConfigureBindingExtension(CompiledBindingExtension _) { } private ClrPropertyInfo PropertyInfo => new(Name, - _ => GetValue(), + _ => Value, Setter as Action, typeof(T)); diff --git a/src/Ryujinx/Common/Markup/MarkupExtensions.cs b/src/Ryujinx/Common/Markup/MarkupExtensions.cs index a804792c71..cae6d8c2c2 100644 --- a/src/Ryujinx/Common/Markup/MarkupExtensions.cs +++ b/src/Ryujinx/Common/Markup/MarkupExtensions.cs @@ -6,17 +6,17 @@ namespace Ryujinx.Ava.Common.Markup { internal class IconExtension(string iconString) : BasicMarkupExtension { - protected override Icon GetValue() => new() { Value = iconString }; + protected override Icon Value => new() { Value = iconString }; } internal class SpinningIconExtension(string iconString) : BasicMarkupExtension { - protected override Icon GetValue() => new() { Value = iconString, Animation = IconAnimation.Spin }; + protected override Icon Value => new() { Value = iconString, Animation = IconAnimation.Spin }; } internal class LocaleExtension(LocaleKeys key) : BasicMarkupExtension { - protected override string GetValue() => LocaleManager.Instance[key]; + protected override string Value => LocaleManager.Instance[key]; protected override void ConfigureBindingExtension(CompiledBindingExtension bindingExtension) => bindingExtension.Source = LocaleManager.Instance; diff --git a/src/Ryujinx/UI/Controls/ApplicationGridView.axaml b/src/Ryujinx/UI/Controls/ApplicationGridView.axaml index 98a1c004b2..3bcb468ae6 100644 --- a/src/Ryujinx/UI/Controls/ApplicationGridView.axaml +++ b/src/Ryujinx/UI/Controls/ApplicationGridView.axaml @@ -91,7 +91,7 @@ HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="16" - Foreground="{DynamicResource SystemAccentColor}" + Foreground="{DynamicResource FavoriteApplicationIconColor}" IsVisible="{Binding Favorite}" Symbol="StarFilled" /> diff --git a/src/Ryujinx/UI/Controls/ApplicationListView.axaml b/src/Ryujinx/UI/Controls/ApplicationListView.axaml index 0daa77ac42..8a72ebfbfb 100644 --- a/src/Ryujinx/UI/Controls/ApplicationListView.axaml +++ b/src/Ryujinx/UI/Controls/ApplicationListView.axaml @@ -146,7 +146,7 @@ HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="16" - Foreground="{DynamicResource SystemAccentColor}" + Foreground="{DynamicResource FavoriteApplicationIconColor}" IsVisible="{Binding Favorite}" Symbol="StarFilled" /> From 8ae72c1a0002a3c1bae7d92fad125333eb6e3edb Mon Sep 17 00:00:00 2001 From: bangfire <168100143+bangfire@users.noreply.github.com> Date: Sat, 7 Dec 2024 13:17:39 +0000 Subject: [PATCH 46/61] Fix Windows Terminal hide/show functions (#342) https://stackoverflow.com/a/78577080 --- src/Ryujinx.UI.Common/Helper/ConsoleHelper.cs | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/Ryujinx.UI.Common/Helper/ConsoleHelper.cs b/src/Ryujinx.UI.Common/Helper/ConsoleHelper.cs index 623952b37c..99b209c6e0 100644 --- a/src/Ryujinx.UI.Common/Helper/ConsoleHelper.cs +++ b/src/Ryujinx.UI.Common/Helper/ConsoleHelper.cs @@ -7,6 +7,24 @@ namespace Ryujinx.UI.Common.Helper { public static partial class ConsoleHelper { + [SupportedOSPlatform("windows")] + [LibraryImport("kernel32")] + private static partial nint GetConsoleWindow(); + + [SupportedOSPlatform("windows")] + [LibraryImport("user32")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool ShowWindow(nint hWnd, int nCmdShow); + + [SupportedOSPlatform("windows")] + [LibraryImport("user32")] + private static partial nint GetForegroundWindow(); + + [SupportedOSPlatform("windows")] + [LibraryImport("user32")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool SetForegroundWindow(nint hWnd); + public static bool SetConsoleWindowStateSupported => OperatingSystem.IsWindows(); public static void SetConsoleWindowState(bool show) @@ -35,16 +53,11 @@ private static void SetConsoleWindowStateWindows(bool show) return; } - ShowWindow(hWnd, show ? SW_SHOW : SW_HIDE); - } + SetForegroundWindow(hWnd); - [SupportedOSPlatform("windows")] - [LibraryImport("kernel32")] - private static partial nint GetConsoleWindow(); + hWnd = GetForegroundWindow(); - [SupportedOSPlatform("windows")] - [LibraryImport("user32")] - [return: MarshalAs(UnmanagedType.Bool)] - private static partial bool ShowWindow(nint hWnd, int nCmdShow); + ShowWindow(hWnd, show ? SW_SHOW : SW_HIDE); + } } } From ec11bf2af9087a9ec834252fb74bf8df5365efd0 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Sat, 7 Dec 2024 08:53:23 -0600 Subject: [PATCH 47/61] i18n: Clean out old translations and reset outdated ones --- src/Ryujinx/Assets/Locales/ar_SA.json | 6 ++---- src/Ryujinx/Assets/Locales/de_DE.json | 6 ++---- src/Ryujinx/Assets/Locales/el_GR.json | 6 ++---- src/Ryujinx/Assets/Locales/en_US.json | 6 ++---- src/Ryujinx/Assets/Locales/es_ES.json | 6 ++---- src/Ryujinx/Assets/Locales/fr_FR.json | 6 ++---- src/Ryujinx/Assets/Locales/he_IL.json | 6 ++---- src/Ryujinx/Assets/Locales/it_IT.json | 6 ++---- src/Ryujinx/Assets/Locales/ja_JP.json | 6 ++---- src/Ryujinx/Assets/Locales/ko_KR.json | 6 ++---- src/Ryujinx/Assets/Locales/pl_PL.json | 6 ++---- src/Ryujinx/Assets/Locales/pt_BR.json | 6 ++---- src/Ryujinx/Assets/Locales/ru_RU.json | 6 ++---- src/Ryujinx/Assets/Locales/th_TH.json | 6 ++---- src/Ryujinx/Assets/Locales/tr_TR.json | 6 ++---- src/Ryujinx/Assets/Locales/uk_UA.json | 6 ++---- src/Ryujinx/Assets/Locales/zh_CN.json | 6 ++---- src/Ryujinx/Assets/Locales/zh_TW.json | 6 ++---- 18 files changed, 36 insertions(+), 72 deletions(-) diff --git a/src/Ryujinx/Assets/Locales/ar_SA.json b/src/Ryujinx/Assets/Locales/ar_SA.json index bdb6a92ecc..fee84779a0 100644 --- a/src/Ryujinx/Assets/Locales/ar_SA.json +++ b/src/Ryujinx/Assets/Locales/ar_SA.json @@ -552,15 +552,13 @@ "AboutUrlTooltipMessage": "انقر لفتح موقع ريوجينكس في متصفحك الافتراضي.", "AboutDisclaimerMessage": "ريوجينكس لا ينتمي إلى نينتندو™،\nأو أي من شركائها بأي شكل من الأشكال.", "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) يتم \nاستخدامه في محاكاة أمبيو لدينا.", - "AboutPatreonUrlTooltipMessage": "انقر لفتح صفحة ريوجينكس في باتريون في متصفحك الافتراضي.", "AboutGithubUrlTooltipMessage": "انقر لفتح صفحة ريوجينكس في غيت هاب في متصفحك الافتراضي.", "AboutDiscordUrlTooltipMessage": "انقر لفتح دعوة إلى خادم ريوجينكس في ديكسورد في متصفحك الافتراضي.", - "AboutTwitterUrlTooltipMessage": "انقر لفتح صفحة ريوجينكس في تويتر في متصفحك الافتراضي.", "AboutRyujinxAboutTitle": "حول:", - "AboutRyujinxAboutContent": "ريوجينكس هو محاكي لجهاز نينتندو سويتش™.\nمن فضلك ادعمنا على باتريون.\nاحصل على آخر الأخبار على تويتر أو ديسكورد.\nيمكن للمطورين المهتمين بالمساهمة معرفة المزيد على غيت هاب أو ديسكورد.", + "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", "AboutRyujinxMaintainersTitle": "تتم صيانته بواسطة:", + "AboutRyujinxFormerMaintainersTitle": "Formerly Maintained By:", "AboutRyujinxMaintainersContentTooltipMessage": "انقر لفتح صفحة المساهمين في متصفحك الافتراضي.", - "AboutRyujinxSupprtersTitle": "مدعوم على باتريون بواسطة:", "AmiiboSeriesLabel": "مجموعة أميبو", "AmiiboCharacterLabel": "شخصية", "AmiiboScanButtonLabel": "فحصه", diff --git a/src/Ryujinx/Assets/Locales/de_DE.json b/src/Ryujinx/Assets/Locales/de_DE.json index 9f4b63a951..5391df474d 100644 --- a/src/Ryujinx/Assets/Locales/de_DE.json +++ b/src/Ryujinx/Assets/Locales/de_DE.json @@ -552,15 +552,13 @@ "AboutUrlTooltipMessage": "Klicke hier, um die Ryujinx Website im Standardbrowser zu öffnen.", "AboutDisclaimerMessage": "Ryujinx ist in keinster Weise weder mit Nintendo™, \nnoch mit deren Partnern verbunden.", "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) wird in unserer Amiibo \nEmulation benutzt.", - "AboutPatreonUrlTooltipMessage": "Klicke hier, um die Ryujinx Patreon Seite im Standardbrowser zu öffnen.", "AboutGithubUrlTooltipMessage": "Klicke hier, um die Ryujinx GitHub Seite im Standardbrowser zu öffnen.", "AboutDiscordUrlTooltipMessage": "Klicke hier, um eine Einladung zum Ryujinx Discord Server im Standardbrowser zu öffnen.", - "AboutTwitterUrlTooltipMessage": "Klicke hier, um die Ryujinx Twitter Seite im Standardbrowser zu öffnen.", "AboutRyujinxAboutTitle": "Über:", - "AboutRyujinxAboutContent": "Ryujinx ist ein Nintendo Switch™ Emulator.\nBitte unterstütze uns auf Patreon.\nAuf Twitter oder Discord erfährst du alle Neuigkeiten.\nEntwickler, die an einer Mitarbeit interessiert sind, können auf GitHub oder Discord mehr erfahren.", + "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", "AboutRyujinxMaintainersTitle": "Entwickelt von:", + "AboutRyujinxFormerMaintainersTitle": "Formerly Maintained By:", "AboutRyujinxMaintainersContentTooltipMessage": "Klicke hier, um die Liste der Mitwirkenden im Standardbrowser zu öffnen.", - "AboutRyujinxSupprtersTitle": "Unterstützt auf Patreon von:", "AmiiboSeriesLabel": "Amiibo-Serie", "AmiiboCharacterLabel": "Charakter", "AmiiboScanButtonLabel": "Einscannen", diff --git a/src/Ryujinx/Assets/Locales/el_GR.json b/src/Ryujinx/Assets/Locales/el_GR.json index 7979b92288..3c308ac841 100644 --- a/src/Ryujinx/Assets/Locales/el_GR.json +++ b/src/Ryujinx/Assets/Locales/el_GR.json @@ -552,15 +552,13 @@ "AboutUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τον ιστότοπο Ryujinx στο προεπιλεγμένο πρόγραμμα περιήγησης.", "AboutDisclaimerMessage": "Το Ryujinx δεν είναι συνδεδεμένο με τη Nintendo™,\nούτε με κανέναν από τους συνεργάτες της, με οποιονδήποτε τρόπο.", "AboutAmiiboDisclaimerMessage": "Το AmiiboAPI (www.amiiboapi.com) χρησιμοποιείται\nστην προσομοίωση Amiibo.", - "AboutPatreonUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τη σελίδα Ryujinx Patreon στο προεπιλεγμένο πρόγραμμα περιήγησης.", "AboutGithubUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τη σελίδα Ryujinx GitHub στο προεπιλεγμένο πρόγραμμα περιήγησης.", "AboutDiscordUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε μία πρόσκληση στον διακομιστή Ryujinx Discord στο προεπιλεγμένο πρόγραμμα περιήγησης.", - "AboutTwitterUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τη σελίδα Ryujinx Twitter στο προεπιλεγμένο πρόγραμμα περιήγησης.", "AboutRyujinxAboutTitle": "Σχετικά με:", - "AboutRyujinxAboutContent": "Το Ryujinx είναι ένας εξομοιωτής για το Nintendo Switch™.\nΥποστηρίξτε μας στο Patreon.\nΛάβετε όλα τα τελευταία νέα στο Twitter ή στο Discord.\nΟι προγραμματιστές που ενδιαφέρονται να συνεισφέρουν μπορούν να μάθουν περισσότερα στο GitHub ή στο Discord μας.", + "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", "AboutRyujinxMaintainersTitle": "Συντηρείται από:", + "AboutRyujinxFormerMaintainersTitle": "Formerly Maintained By:", "AboutRyujinxMaintainersContentTooltipMessage": "Κάντε κλικ για να ανοίξετε τη σελίδα Συνεισφέροντες στο προεπιλεγμένο πρόγραμμα περιήγησης.", - "AboutRyujinxSupprtersTitle": "Υποστηρίζεται στο Patreon από:", "AmiiboSeriesLabel": "Σειρά Amiibo", "AmiiboCharacterLabel": "Χαρακτήρας", "AmiiboScanButtonLabel": "Σαρώστε το", diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json index 0598665fd9..bad69a41cd 100644 --- a/src/Ryujinx/Assets/Locales/en_US.json +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -564,15 +564,13 @@ "AboutUrlTooltipMessage": "Click to open the Ryujinx website in your default browser.", "AboutDisclaimerMessage": "Ryujinx is not affiliated with Nintendo™,\nor any of its partners, in any way.", "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) is used\nin our Amiibo emulation.", - "AboutPatreonUrlTooltipMessage": "Click to open the Ryujinx Patreon page in your default browser.", "AboutGithubUrlTooltipMessage": "Click to open the Ryujinx GitHub page in your default browser.", "AboutDiscordUrlTooltipMessage": "Click to open an invite to the Ryujinx Discord server in your default browser.", - "AboutTwitterUrlTooltipMessage": "Click to open the Ryujinx Twitter page in your default browser.", "AboutRyujinxAboutTitle": "About:", - "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nPlease support us on Patreon.\nGet all the latest news on our Twitter or Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", + "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", "AboutRyujinxMaintainersTitle": "Maintained By:", + "AboutRyujinxFormerMaintainersTitle": "Formerly Maintained By:", "AboutRyujinxMaintainersContentTooltipMessage": "Click to open the Contributors page in your default browser.", - "AboutRyujinxSupprtersTitle": "Supported on Patreon By:", "AmiiboSeriesLabel": "Amiibo Series", "AmiiboCharacterLabel": "Character", "AmiiboScanButtonLabel": "Scan It", diff --git a/src/Ryujinx/Assets/Locales/es_ES.json b/src/Ryujinx/Assets/Locales/es_ES.json index 3774605f62..fe8b57f783 100644 --- a/src/Ryujinx/Assets/Locales/es_ES.json +++ b/src/Ryujinx/Assets/Locales/es_ES.json @@ -552,15 +552,13 @@ "AboutUrlTooltipMessage": "Haz clic para abrir el sitio web de Ryujinx en tu navegador predeterminado.", "AboutDisclaimerMessage": "Ryujinx no tiene afiliación alguna con Nintendo™,\nni con ninguno de sus socios.", "AboutAmiiboDisclaimerMessage": "Utilizamos AmiiboAPI (www.amiiboapi.com)\nen nuestra emulación de Amiibo.", - "AboutPatreonUrlTooltipMessage": "Haz clic para abrir el Patreon de Ryujinx en tu navegador predeterminado.", "AboutGithubUrlTooltipMessage": "Haz clic para abrir el GitHub de Ryujinx en tu navegador predeterminado.", "AboutDiscordUrlTooltipMessage": "Haz clic para recibir una invitación al Discord de Ryujinx en tu navegador predeterminado.", - "AboutTwitterUrlTooltipMessage": "Haz clic para abrir el Twitter de Ryujinx en tu navegador predeterminado.", "AboutRyujinxAboutTitle": "Acerca de:", - "AboutRyujinxAboutContent": "Ryujinx es un emulador para Nintendo Switch™.\nPor favor, apóyanos en Patreon.\nEncuentra las noticias más recientes en nuestro Twitter o Discord.\nDesarrolladores interesados en contribuir pueden encontrar más información en GitHub o Discord.", + "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", "AboutRyujinxMaintainersTitle": "Mantenido por:", + "AboutRyujinxFormerMaintainersTitle": "Formerly Maintained By:", "AboutRyujinxMaintainersContentTooltipMessage": "Haz clic para abrir la página de contribuidores en tu navegador predeterminado.", - "AboutRyujinxSupprtersTitle": "Apoyado en Patreon Por:", "AmiiboSeriesLabel": "Serie de Amiibo", "AmiiboCharacterLabel": "Personaje", "AmiiboScanButtonLabel": "Escanear", diff --git a/src/Ryujinx/Assets/Locales/fr_FR.json b/src/Ryujinx/Assets/Locales/fr_FR.json index c5a4bbeec8..cd4e74f041 100644 --- a/src/Ryujinx/Assets/Locales/fr_FR.json +++ b/src/Ryujinx/Assets/Locales/fr_FR.json @@ -552,15 +552,13 @@ "AboutUrlTooltipMessage": "Cliquez pour ouvrir le site de Ryujinx dans votre navigateur par défaut.", "AboutDisclaimerMessage": "Ryujinx n'est pas affilié à Nintendo™,\nou à aucun de ses partenaires, de quelque manière que ce soit.", "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) est utilisé\ndans notre émulation Amiibo.", - "AboutPatreonUrlTooltipMessage": "Cliquez pour ouvrir la page Patreon de Ryujinx dans votre navigateur par défaut.", "AboutGithubUrlTooltipMessage": "Cliquez pour ouvrir la page GitHub de Ryujinx dans votre navigateur par défaut.", "AboutDiscordUrlTooltipMessage": "Cliquez pour ouvrir une invitation au serveur Discord de Ryujinx dans votre navigateur par défaut.", - "AboutTwitterUrlTooltipMessage": "Cliquez pour ouvrir la page Twitter de Ryujinx dans votre navigateur par défaut.", "AboutRyujinxAboutTitle": "À propos :", - "AboutRyujinxAboutContent": "Ryujinx est un émulateur pour la Nintendo Switch™.\nMerci de nous soutenir sur Patreon.\nObtenez toutes les dernières actualités sur notre Twitter ou notre Discord.\nLes développeurs intéressés à contribuer peuvent en savoir plus sur notre GitHub ou notre Discord.", + "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", "AboutRyujinxMaintainersTitle": "Maintenu par :", + "AboutRyujinxFormerMaintainersTitle": "Formerly Maintained By:", "AboutRyujinxMaintainersContentTooltipMessage": "Cliquez pour ouvrir la page Contributeurs dans votre navigateur par défaut.", - "AboutRyujinxSupprtersTitle": "Supporté sur Patreon par :", "AmiiboSeriesLabel": "Séries Amiibo", "AmiiboCharacterLabel": "Personnage", "AmiiboScanButtonLabel": "Scanner", diff --git a/src/Ryujinx/Assets/Locales/he_IL.json b/src/Ryujinx/Assets/Locales/he_IL.json index 318068bf3e..14cfc4977e 100644 --- a/src/Ryujinx/Assets/Locales/he_IL.json +++ b/src/Ryujinx/Assets/Locales/he_IL.json @@ -552,15 +552,13 @@ "AboutUrlTooltipMessage": "לחץ כדי לפתוח את אתר ריוג'ינקס בדפדפן ברירת המחדל שלך.", "AboutDisclaimerMessage": "ריוג'ינקס אינה מזוהת עם נינטנדו,\nאו שוטפייה בכל דרך שהיא.", "AboutAmiiboDisclaimerMessage": "ממשק אמיבו (www.amiiboapi.com) משומש בהדמיית האמיבו שלנו.", - "AboutPatreonUrlTooltipMessage": "לחץ כדי לפתוח את דף הפטראון של ריוג'ינקס בדפדפן ברירת המחדל שלך.", "AboutGithubUrlTooltipMessage": "לחץ כדי לפתוח את דף הגיטהב של ריוג'ינקס בדפדפן ברירת המחדל שלך.", "AboutDiscordUrlTooltipMessage": "לחץ כדי לפתוח הזמנה לשרת הדיסקורד של ריוג'ינקס בדפדפן ברירת המחדל שלך.", - "AboutTwitterUrlTooltipMessage": "לחץ כדי לפתוח את דף הטוויטר של Ryujinx בדפדפן ברירת המחדל שלך.", "AboutRyujinxAboutTitle": "אודות:", - "AboutRyujinxAboutContent": "ריוג'ינקס הוא אמולטור עבור הנינטנדו סוויץ' (כל הזכויות שמורות).\nבבקשה תתמכו בנו בפטראון.\nקבל את כל החדשות האחרונות בטוויטר או בדיסקורד שלנו.\nמפתחים המעוניינים לתרום יכולים לקבל מידע נוסף ב-גיטהאב או ב-דיסקורד שלנו.", + "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", "AboutRyujinxMaintainersTitle": "מתוחזק על ידי:", + "AboutRyujinxFormerMaintainersTitle": "Formerly Maintained By:", "AboutRyujinxMaintainersContentTooltipMessage": "לחץ כדי לפתוח את דף התורמים בדפדפן ברירת המחדל שלך.", - "AboutRyujinxSupprtersTitle": "תמוך באמצעות Patreon", "AmiiboSeriesLabel": "סדרת אמיבו", "AmiiboCharacterLabel": "דמות", "AmiiboScanButtonLabel": "סרוק את זה", diff --git a/src/Ryujinx/Assets/Locales/it_IT.json b/src/Ryujinx/Assets/Locales/it_IT.json index 18e4ee04f7..854b831c14 100644 --- a/src/Ryujinx/Assets/Locales/it_IT.json +++ b/src/Ryujinx/Assets/Locales/it_IT.json @@ -552,15 +552,13 @@ "AboutUrlTooltipMessage": "Clicca per aprire il sito web di Ryujinx nel tuo browser predefinito.", "AboutDisclaimerMessage": "Ryujinx non è affiliato con Nintendo™,\no i suoi partner, in alcun modo.", "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) è usata\nnella nostra emulazione Amiibo.", - "AboutPatreonUrlTooltipMessage": "Clicca per aprire la pagina Patreon di Ryujinx nel tuo browser predefinito.", "AboutGithubUrlTooltipMessage": "Clicca per aprire la pagina GitHub di Ryujinx nel tuo browser predefinito.", "AboutDiscordUrlTooltipMessage": "Clicca per aprire un invito al server Discord di Ryujinx nel tuo browser predefinito.", - "AboutTwitterUrlTooltipMessage": "Clicca per aprire la pagina Twitter di Ryujinx nel tuo browser predefinito.", "AboutRyujinxAboutTitle": "Informazioni:", - "AboutRyujinxAboutContent": "Ryujinx è un emulatore per la console Nintendo Switch™.\nSostienici su Patreon.\nRicevi tutte le ultime notizie sul nostro Twitter o su Discord.\nGli sviluppatori interessati a contribuire possono trovare più informazioni sul nostro GitHub o Discord.", + "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", "AboutRyujinxMaintainersTitle": "Mantenuto da:", + "AboutRyujinxFormerMaintainersTitle": "Formerly Maintained By:", "AboutRyujinxMaintainersContentTooltipMessage": "Clicca per aprire la pagina dei contributori nel tuo browser predefinito.", - "AboutRyujinxSupprtersTitle": "Supportato su Patreon da:", "AmiiboSeriesLabel": "Serie Amiibo", "AmiiboCharacterLabel": "Personaggio", "AmiiboScanButtonLabel": "Scansiona", diff --git a/src/Ryujinx/Assets/Locales/ja_JP.json b/src/Ryujinx/Assets/Locales/ja_JP.json index 6ecc740097..19236c21b3 100644 --- a/src/Ryujinx/Assets/Locales/ja_JP.json +++ b/src/Ryujinx/Assets/Locales/ja_JP.json @@ -552,15 +552,13 @@ "AboutUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx のウェブサイトを開きます.", "AboutDisclaimerMessage": "Ryujinx は Nintendo™ および\nそのパートナー企業とは一切関係ありません.", "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) は\nAmiibo エミュレーションに使用されています.", - "AboutPatreonUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx の Patreon ページを開きます.", "AboutGithubUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx の Github ページを開きます.", "AboutDiscordUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx の Discord サーバを開きます.", - "AboutTwitterUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx の Twitter ページを開きます.", "AboutRyujinxAboutTitle": "Ryujinx について:", - "AboutRyujinxAboutContent": "Ryujinx は Nintendo Switch™ のエミュレータです.\nPatreon で私達の活動を支援してください.\n最新の情報は Twitter または Discord から取得できます.\n貢献したい開発者の方は GitHub または Discord で詳細をご確認ください.", + "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", "AboutRyujinxMaintainersTitle": "開発者:", + "AboutRyujinxFormerMaintainersTitle": "Formerly Maintained By:", "AboutRyujinxMaintainersContentTooltipMessage": "クリックするとデフォルトのブラウザで 貢献者のページを開きます.", - "AboutRyujinxSupprtersTitle": "Patreon での支援者:", "AmiiboSeriesLabel": "Amiibo シリーズ", "AmiiboCharacterLabel": "キャラクタ", "AmiiboScanButtonLabel": "スキャン", diff --git a/src/Ryujinx/Assets/Locales/ko_KR.json b/src/Ryujinx/Assets/Locales/ko_KR.json index 71aaa41e87..8ae7a022a5 100644 --- a/src/Ryujinx/Assets/Locales/ko_KR.json +++ b/src/Ryujinx/Assets/Locales/ko_KR.json @@ -552,15 +552,13 @@ "AboutUrlTooltipMessage": "클릭하면 기본 브라우저에서 Ryujinx 웹사이트가 열립니다.", "AboutDisclaimerMessage": "Ryujinx는 Nintendo™\n또는 그 파트너와 제휴한 바가 없습니다.", "AboutAmiiboDisclaimerMessage": "AmiiboAPI(www.amiiboapi.com)는\nAmiibo 에뮬레이션에 사용됩니다.", - "AboutPatreonUrlTooltipMessage": "클릭하면 기본 브라우저에서 Ryujinx Patreon 페이지가 열립니다.", "AboutGithubUrlTooltipMessage": "클릭하면 기본 브라우저에서 Ryujinx GitHub 페이지가 열립니다.", "AboutDiscordUrlTooltipMessage": "클릭하면 기본 브라우저에서 Ryujinx 디스코드 서버 초대장이 열립니다.", - "AboutTwitterUrlTooltipMessage": "클릭하면 기본 브라우저에서 Ryujinx 트위터 페이지가 열립니다.", "AboutRyujinxAboutTitle": "정보 :", - "AboutRyujinxAboutContent": "Ryujinx는 Nintendo Switch™용 에뮬레이터입니다.\nPatreon에서 저희를 후원해 주세요.\nTwitter나 Discord에서 최신 뉴스를 모두 받아보세요.\n기여에 관심이 있는 개발자는 GitHub이나 Discord에서 자세한 내용을 알아볼 수 있습니다.", + "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", "AboutRyujinxMaintainersTitle": "유지 관리 :", + "AboutRyujinxFormerMaintainersTitle": "Formerly Maintained By:", "AboutRyujinxMaintainersContentTooltipMessage": "클릭하면 기본 브라우저에서 기여자 페이지가 열립니다.", - "AboutRyujinxSupprtersTitle": "Patreon에서 후원 :", "AmiiboSeriesLabel": "Amiibo 시리즈", "AmiiboCharacterLabel": "캐릭터", "AmiiboScanButtonLabel": "스캔하기", diff --git a/src/Ryujinx/Assets/Locales/pl_PL.json b/src/Ryujinx/Assets/Locales/pl_PL.json index 6a2fa08b1a..7a0b16f6f9 100644 --- a/src/Ryujinx/Assets/Locales/pl_PL.json +++ b/src/Ryujinx/Assets/Locales/pl_PL.json @@ -552,15 +552,13 @@ "AboutUrlTooltipMessage": "Kliknij, aby otworzyć stronę Ryujinx w domyślnej przeglądarce.", "AboutDisclaimerMessage": "Ryujinx nie jest w żaden sposób powiązany z Nintendo™,\nani z żadnym z jej partnerów.", "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) jest używane\nw naszej emulacji Amiibo.", - "AboutPatreonUrlTooltipMessage": "Kliknij, aby otworzyć stronę Patreon Ryujinx w domyślnej przeglądarce.", "AboutGithubUrlTooltipMessage": "Kliknij, aby otworzyć stronę GitHub Ryujinx w domyślnej przeglądarce.", "AboutDiscordUrlTooltipMessage": "Kliknij, aby otworzyć zaproszenie na serwer Discord Ryujinx w domyślnej przeglądarce.", - "AboutTwitterUrlTooltipMessage": "Kliknij, aby otworzyć stronę Twitter Ryujinx w domyślnej przeglądarce.", "AboutRyujinxAboutTitle": "O Aplikacji:", - "AboutRyujinxAboutContent": "Ryujinx to emulator Nintendo Switch™.\nWspieraj nas na Patreonie.\nOtrzymuj najnowsze wiadomości na naszym Twitterze lub Discordzie.\nDeweloperzy zainteresowani współpracą mogą dowiedzieć się więcej na naszym GitHubie lub Discordzie.", + "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", "AboutRyujinxMaintainersTitle": "Utrzymywany Przez:", + "AboutRyujinxFormerMaintainersTitle": "Formerly Maintained By:", "AboutRyujinxMaintainersContentTooltipMessage": "Kliknij, aby otworzyć stronę Współtwórcy w domyślnej przeglądarce.", - "AboutRyujinxSupprtersTitle": "Wspierani na Patreonie Przez:", "AmiiboSeriesLabel": "Seria Amiibo", "AmiiboCharacterLabel": "Postać", "AmiiboScanButtonLabel": "Zeskanuj", diff --git a/src/Ryujinx/Assets/Locales/pt_BR.json b/src/Ryujinx/Assets/Locales/pt_BR.json index 90b78b7326..acb063aea6 100644 --- a/src/Ryujinx/Assets/Locales/pt_BR.json +++ b/src/Ryujinx/Assets/Locales/pt_BR.json @@ -552,15 +552,13 @@ "AboutUrlTooltipMessage": "Clique para abrir o site do Ryujinx no seu navegador padrão.", "AboutDisclaimerMessage": "Ryujinx não é afiliado com a Nintendo™,\nou qualquer um de seus parceiros, de nenhum modo.", "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) é usado\nem nossa emulação de Amiibo.", - "AboutPatreonUrlTooltipMessage": "Clique para abrir a página do Patreon do Ryujinx no seu navegador padrão.", "AboutGithubUrlTooltipMessage": "Clique para abrir a página do GitHub do Ryujinx no seu navegador padrão.", "AboutDiscordUrlTooltipMessage": "Clique para abrir um convite ao servidor do Discord do Ryujinx no seu navegador padrão.", - "AboutTwitterUrlTooltipMessage": "Clique para abrir a página do Twitter do Ryujinx no seu navegador padrão.", "AboutRyujinxAboutTitle": "Sobre:", - "AboutRyujinxAboutContent": "Ryujinx é um emulador de Nintendo Switch™.\nPor favor, nos dê apoio no Patreon.\nFique por dentro de todas as novidades no Twitter ou Discord.\nDesenvolvedores com interesse em contribuir podem conseguir mais informações no GitHub ou Discord.", + "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", "AboutRyujinxMaintainersTitle": "Mantido por:", + "AboutRyujinxFormerMaintainersTitle": "Formerly Maintained By:", "AboutRyujinxMaintainersContentTooltipMessage": "Clique para abrir a página de contribuidores no seu navegador padrão.", - "AboutRyujinxSupprtersTitle": "Apoiado no Patreon por:", "AmiiboSeriesLabel": "Franquia Amiibo", "AmiiboCharacterLabel": "Personagem", "AmiiboScanButtonLabel": "Escanear", diff --git a/src/Ryujinx/Assets/Locales/ru_RU.json b/src/Ryujinx/Assets/Locales/ru_RU.json index f058154e93..eb93de21d0 100644 --- a/src/Ryujinx/Assets/Locales/ru_RU.json +++ b/src/Ryujinx/Assets/Locales/ru_RU.json @@ -552,15 +552,13 @@ "AboutUrlTooltipMessage": "Нажмите, чтобы открыть веб-сайт Ryujinx", "AboutDisclaimerMessage": "Ryujinx никоим образом не связан ни с Nintendo™, ни с кем-либо из ее партнеров.", "AboutAmiiboDisclaimerMessage": "Amiibo API (www.amiiboapi.com) используется для эмуляции Amiibo.", - "AboutPatreonUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx на Patreon", "AboutGithubUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx на GitHub", "AboutDiscordUrlTooltipMessage": "Нажмите, чтобы открыть приглашение на сервер Ryujinx в Discord", - "AboutTwitterUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx в X (бывший Twitter)", "AboutRyujinxAboutTitle": "О программе:", - "AboutRyujinxAboutContent": "Ryujinx — это эмулятор Nintendo Switch™.\nПожалуйста, поддержите нас на Patreon.\nЧитайте последние новости в наших X (Twitter) или Discord.\nРазработчики, заинтересованные в участии, могут ознакомиться с проектом на GitHub или в Discord.", + "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", "AboutRyujinxMaintainersTitle": "Разработка:", + "AboutRyujinxFormerMaintainersTitle": "Formerly Maintained By:", "AboutRyujinxMaintainersContentTooltipMessage": "Нажмите, чтобы открыть страницу с участниками", - "AboutRyujinxSupprtersTitle": "Поддержка на Patreon:", "AmiiboSeriesLabel": "Серия Amiibo", "AmiiboCharacterLabel": "Персонаж", "AmiiboScanButtonLabel": "Сканировать", diff --git a/src/Ryujinx/Assets/Locales/th_TH.json b/src/Ryujinx/Assets/Locales/th_TH.json index 33b2c4f308..6364084c7e 100644 --- a/src/Ryujinx/Assets/Locales/th_TH.json +++ b/src/Ryujinx/Assets/Locales/th_TH.json @@ -552,15 +552,13 @@ "AboutUrlTooltipMessage": "คลิกเพื่อเปิดเว็บไซต์ Ryujinx บนเบราว์เซอร์เริ่มต้นของคุณ", "AboutDisclaimerMessage": "ทางผู้พัฒนาโปรแกรม Ryujinx ไม่มีส่วนเกี่ยวข้องกับทางบริษัท Nintendo™\nหรือพันธมิตรใดๆ ทั้งสิ้น!", "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) ถูกใช้\nในการจำลอง อะมิโบ ของเรา", - "AboutPatreonUrlTooltipMessage": "คลิกเพื่อเปิดหน้า Patreon ของ Ryujinx บนเบราว์เซอร์เริ่มต้นของคุณ", "AboutGithubUrlTooltipMessage": "คลิกเพื่อเปิดหน้า Github ของ Ryujinx บนเบราว์เซอร์เริ่มต้นของคุณ", "AboutDiscordUrlTooltipMessage": "คลิกเพื่อเปิดคำเชิญเข้าสู่เซิร์ฟเวอร์ Discord ของ Ryujinx บนเบราว์เซอร์เริ่มต้นของคุณ", - "AboutTwitterUrlTooltipMessage": "คลิกเพื่อเปิดหน้าเพจ Twitter ของ Ryujinx บนเบราว์เซอร์เริ่มต้นของคุณ", "AboutRyujinxAboutTitle": "เกี่ยวกับ:", - "AboutRyujinxAboutContent": "Ryujinx เป็นอีมูเลเตอร์สำหรับ Nintendo Switch™\nโปรดสนับสนุนเราบน Patreon\nรับข่าวสารล่าสุดทั้งหมดบน Twitter หรือ Discord ของเรา\nนักพัฒนาที่สนใจจะมีส่วนร่วมสามารถดูข้อมูลเพิ่มเติมได้ที่ GitHub หรือ Discord ของเรา", + "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", "AboutRyujinxMaintainersTitle": "ได้รับการดูแลโดย:", + "AboutRyujinxFormerMaintainersTitle": "Formerly Maintained By:", "AboutRyujinxMaintainersContentTooltipMessage": "คลิกเพื่อเปิดหน้าผู้มีส่วนร่วมบนเบราว์เซอร์เริ่มต้นของคุณ", - "AboutRyujinxSupprtersTitle": "ผู้สนับสนุนบน Patreon:", "AmiiboSeriesLabel": "Amiibo Series", "AmiiboCharacterLabel": "ตัวละคร", "AmiiboScanButtonLabel": "สแกนเลย", diff --git a/src/Ryujinx/Assets/Locales/tr_TR.json b/src/Ryujinx/Assets/Locales/tr_TR.json index 72da205cbc..1be779d67a 100644 --- a/src/Ryujinx/Assets/Locales/tr_TR.json +++ b/src/Ryujinx/Assets/Locales/tr_TR.json @@ -552,15 +552,13 @@ "AboutUrlTooltipMessage": "Ryujinx'in websitesini varsayılan tarayıcınızda açmak için tıklayın.", "AboutDisclaimerMessage": "Ryujinx, Nintendo™ veya ortaklarıyla herhangi bir şekilde bağlantılı değildir.", "AboutAmiiboDisclaimerMessage": "Amiibo emülasyonumuzda \nAmiiboAPI (www.amiiboapi.com) kullanılmaktadır.", - "AboutPatreonUrlTooltipMessage": "Ryujinx'in Patreon sayfasını varsayılan tarayıcınızda açmak için tıklayın.", "AboutGithubUrlTooltipMessage": "Ryujinx'in GitHub sayfasını varsayılan tarayıcınızda açmak için tıklayın.", "AboutDiscordUrlTooltipMessage": "Varsayılan tarayıcınızda Ryujinx'in Discord'una bir davet açmak için tıklayın.", - "AboutTwitterUrlTooltipMessage": "Ryujinx'in Twitter sayfasını varsayılan tarayıcınızda açmak için tıklayın.", "AboutRyujinxAboutTitle": "Hakkında:", - "AboutRyujinxAboutContent": "Ryujinx bir Nintendo Switch™ emülatörüdür.\nLütfen bizi Patreon'da destekleyin.\nEn son haberleri Twitter veya Discord'umuzdan alın.\nKatkıda bulunmak isteyen geliştiriciler GitHub veya Discord üzerinden daha fazla bilgi edinebilir.", + "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", "AboutRyujinxMaintainersTitle": "Geliştiriciler:", + "AboutRyujinxFormerMaintainersTitle": "Formerly Maintained By:", "AboutRyujinxMaintainersContentTooltipMessage": "Katkıda bulunanlar sayfasını varsayılan tarayıcınızda açmak için tıklayın.", - "AboutRyujinxSupprtersTitle": "Patreon Destekleyicileri:", "AmiiboSeriesLabel": "Amiibo Serisi", "AmiiboCharacterLabel": "Karakter", "AmiiboScanButtonLabel": "Tarat", diff --git a/src/Ryujinx/Assets/Locales/uk_UA.json b/src/Ryujinx/Assets/Locales/uk_UA.json index 06f6586409..a5d519398b 100644 --- a/src/Ryujinx/Assets/Locales/uk_UA.json +++ b/src/Ryujinx/Assets/Locales/uk_UA.json @@ -552,15 +552,13 @@ "AboutUrlTooltipMessage": "Натисніть, щоб відкрити сайт Ryujinx у браузері за замовчування.", "AboutDisclaimerMessage": "Ryujinx жодним чином не пов’язано з Nintendo™,\nчи будь-яким із їхніх партнерів.", "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) використовується в нашій емуляції Amiibo.", - "AboutPatreonUrlTooltipMessage": "Натисніть, щоб відкрити сторінку Patreon Ryujinx у вашому браузері за замовчування.", "AboutGithubUrlTooltipMessage": "Натисніть, щоб відкрити сторінку GitHub Ryujinx у браузері за замовчуванням.", "AboutDiscordUrlTooltipMessage": "Натисніть, щоб відкрити запрошення на сервер Discord Ryujinx у браузері за замовчуванням.", - "AboutTwitterUrlTooltipMessage": "Натисніть, щоб відкрити сторінку Twitter Ryujinx у браузері за замовчуванням.", "AboutRyujinxAboutTitle": "Про програму:", - "AboutRyujinxAboutContent": "Ryujinx — це емулятор для Nintendo Switch™.\nБудь ласка, підтримайте нас на Patreon.\nОтримуйте всі останні новини в нашому Twitter або Discord.\nРозробники, які хочуть зробити внесок, можуть дізнатися більше на нашому GitHub або в Discord.", + "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", "AboutRyujinxMaintainersTitle": "Підтримується:", + "AboutRyujinxFormerMaintainersTitle": "Formerly Maintained By:", "AboutRyujinxMaintainersContentTooltipMessage": "Натисніть, щоб відкрити сторінку співавторів у вашому браузері за замовчування.", - "AboutRyujinxSupprtersTitle": "Підтримується на Patreon:", "AmiiboSeriesLabel": "Серія Amiibo", "AmiiboCharacterLabel": "Персонаж", "AmiiboScanButtonLabel": "Сканувати", diff --git a/src/Ryujinx/Assets/Locales/zh_CN.json b/src/Ryujinx/Assets/Locales/zh_CN.json index 66ac309de0..8b8e5d37ce 100644 --- a/src/Ryujinx/Assets/Locales/zh_CN.json +++ b/src/Ryujinx/Assets/Locales/zh_CN.json @@ -552,15 +552,13 @@ "AboutUrlTooltipMessage": "在浏览器中打开 Ryujinx 模拟器官网。", "AboutDisclaimerMessage": "Ryujinx 与 Nintendo™ 以及其合作伙伴没有任何关联。", "AboutAmiiboDisclaimerMessage": "我们的 Amiibo 模拟使用了\nAmiiboAPI (www.amiiboapi.com)。", - "AboutPatreonUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Patreon 赞助页。", "AboutGithubUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 GitHub 代码库。", "AboutDiscordUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Discord 邀请链接。", - "AboutTwitterUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Twitter 主页。", "AboutRyujinxAboutTitle": "关于:", - "AboutRyujinxAboutContent": "Ryujinx 是一款 Nintendo Switch™ 模拟器。\n您可以在 Patreon 上赞助 Ryujinx。\n关注 Twitter 或 Discord 可以获取模拟器最新动态。\n如果您对开发感兴趣,欢迎来 GitHub 或 Discord 加入我们!", + "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", "AboutRyujinxMaintainersTitle": "开发维护人员名单:", + "AboutRyujinxFormerMaintainersTitle": "Formerly Maintained By:", "AboutRyujinxMaintainersContentTooltipMessage": "在浏览器中打开贡献者页面", - "AboutRyujinxSupprtersTitle": "感谢 Patreon 上的赞助者:", "AmiiboSeriesLabel": "Amiibo 系列", "AmiiboCharacterLabel": "角色", "AmiiboScanButtonLabel": "扫描", diff --git a/src/Ryujinx/Assets/Locales/zh_TW.json b/src/Ryujinx/Assets/Locales/zh_TW.json index 64f1378853..46761ff025 100644 --- a/src/Ryujinx/Assets/Locales/zh_TW.json +++ b/src/Ryujinx/Assets/Locales/zh_TW.json @@ -552,15 +552,13 @@ "AboutUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 網站。", "AboutDisclaimerMessage": "Ryujinx 和 Nintendo™\n或其任何合作夥伴完全沒有關聯。", "AboutAmiiboDisclaimerMessage": "我們在 Amiibo 模擬中\n使用了 AmiiboAPI (www.amiiboapi.com)。", - "AboutPatreonUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 Patreon 網頁。", "AboutGithubUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 GitHub 網頁。", "AboutDiscordUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 Discord 邀請連結。", - "AboutTwitterUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 Twitter 網頁。", "AboutRyujinxAboutTitle": "關於:", - "AboutRyujinxAboutContent": "Ryujinx 是一款 Nintendo Switch™ 模擬器。\n請在 Patreon 上支持我們。\n關注我們的 Twitter 或 Discord 取得所有最新消息。\n對於有興趣貢獻的開發者,可以在我們的 GitHub 或 Discord 上了解更多資訊。", + "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", "AboutRyujinxMaintainersTitle": "維護者:", + "AboutRyujinxFormerMaintainersTitle": "Formerly Maintained By:", "AboutRyujinxMaintainersContentTooltipMessage": "在預設瀏覽器中開啟貢獻者的網頁", - "AboutRyujinxSupprtersTitle": "Patreon 支持者:", "AmiiboSeriesLabel": "Amiibo 系列", "AmiiboCharacterLabel": "角色", "AmiiboScanButtonLabel": "掃描", From 39252b7267a38288f18449356ba20d793883fe04 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Sun, 8 Dec 2024 13:04:01 -0600 Subject: [PATCH 48/61] UI: Update About window with the current status of the project. --- src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs | 4 +++- src/Ryujinx/UI/Windows/AboutWindow.axaml | 11 ++++++++++- src/Ryujinx/UI/Windows/AboutWindow.axaml.cs | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs index c48ad378f3..23d0f963c9 100644 --- a/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs @@ -45,7 +45,9 @@ public string Version } } - public string Developers => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.AboutPageDeveloperListMore, "gdkchan, Ac_K, marysaka, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, GoffyDude, TSRBerry, IsaacMarovitz, GreemDev"); + public string Developers => "GreemDev"; + + public string FormerDevelopers => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.AboutPageDeveloperListMore, "gdkchan, Ac_K, marysaka, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, GoffyDude, TSRBerry, IsaacMarovitz"); public AboutWindowViewModel() { diff --git a/src/Ryujinx/UI/Windows/AboutWindow.axaml b/src/Ryujinx/UI/Windows/AboutWindow.axaml index 1d0e36ae98..bce0fde570 100644 --- a/src/Ryujinx/UI/Windows/AboutWindow.axaml +++ b/src/Ryujinx/UI/Windows/AboutWindow.axaml @@ -165,7 +165,16 @@ Text="{ext:Locale AboutRyujinxMaintainersTitle}" /> + +