Compare commits

...

18 Commits

Author SHA1 Message Date
Evan Husted
045f9a39bb UI: Remove title bar, put icon next to File button. Added icons to most dropdown menu actions. Title bar content is now displayed when hovering the logo in the title bar. 2024-10-17 01:24:16 -05:00
Evan Husted
235083ad75 misc: Code cleanups & improvements, again 2024-10-17 01:21:32 -05:00
Evan Husted
a13cf098b4 misc: More cleanup changes 2024-10-16 19:32:18 -05:00
Evan Husted
1800ecc1b4 UI: Add Skyrim, KAFTL & HW:AOC to RPC.
Minor code improvements and comment fixes.
2024-10-16 19:23:11 -05:00
Evan Husted
280b94fc0c I18n: Move low-power PPTC strings into translation files & add german version. 2024-10-15 21:56:20 -05:00
Evan Husted
0faf3f5709 CPU: Add low-power PPTC load mode.
Specifically, this setting causes the translation load core count to get reduced by two-thirds, for lower-power but still fast loading, and for unstable CPUs.
2024-10-14 21:48:21 -05:00
Evan Husted
4ee1dff497 SDL: Move Mouse to the Input project instead of Headless. 2024-10-14 15:03:36 -05:00
Evan Husted
b2a35ecf6c misc: Small code improvements. 2024-10-14 15:03:09 -05:00
Evan Husted
5f6d9eef6b misc: Use a method to create window titles. 2024-10-14 15:01:31 -05:00
Evan Husted
40a488799e Ignore docs/ & fix shell image actually (real) 2024-10-13 16:32:24 -05:00
Evan Husted
f9f037a951 Fix README 2024-10-13 16:23:06 -05:00
Evan Husted
456f1ec739 UI: Link to the Discord in the About window, more Discord RPC games. 2024-10-12 21:44:13 -05:00
Evan Husted
df9450d2ad misc: More minor code style changes. 2024-10-12 21:43:02 -05:00
Evan Husted
a989d28e03 misc: Convert UIntUtils.CreateRandom into an extension on Random. 2024-10-12 21:41:36 -05:00
Evan Husted
cb31d79164 misc: Collapse XXHash128 into Hash128. 2024-10-12 21:39:30 -05:00
Evan Husted
290e7c5ec8 infra: Cleanup CI artifacts, remove duplicate executable in releases. 2024-10-12 21:37:02 -05:00
Evan Husted
4d311dfc1a UI: RPC: show game version when hovering large image asset. 2024-10-11 23:55:50 -05:00
Evan Husted
5b6e3521d8 misc: More minor style changes. 2024-10-11 23:55:12 -05:00
109 changed files with 1398 additions and 1667 deletions

View File

@@ -7,6 +7,7 @@ on:
branches: [ master ]
paths-ignore:
- '.github/**'
- 'docs/**'
- '*.yml'
- '*.json'
- '*.config'
@@ -102,9 +103,7 @@ jobs:
if: matrix.platform.os == 'windows-latest'
run: |
pushd publish_ava
cp publish/Ryujinx.exe publish/Ryujinx.Ava.exe
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
popd
pushd publish_sdl2_headless
@@ -116,10 +115,8 @@ jobs:
if: matrix.platform.os == 'ubuntu-latest'
run: |
pushd publish_ava
cp publish/Ryujinx publish/Ryujinx.Ava
chmod +x publish/Ryujinx.sh publish/Ryujinx.Ava publish/Ryujinx
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/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
popd
pushd publish_sdl2_headless

View File

@@ -10,6 +10,9 @@
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.10" />
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.18" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.18" />
<PackageVersion Include="Projektanker.Icons.Avalonia" Version="9.4.0" />
<PackageVersion Include="Projektanker.Icons.Avalonia.FontAwesome" Version="9.4.0"/>
<PackageVersion Include="Projektanker.Icons.Avalonia.MaterialDesign" Version="9.4.0"/>
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="2.2.0" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
@@ -34,6 +37,7 @@
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
<PackageVersion Include="Gommon" Version="2.6.5" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="shaderc.net" Version="0.1.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" />

View File

@@ -1,6 +1,6 @@
<h1 align="center">
<br>
<a href="https://ryujinx.org/"><img src="https://raw.githubusercontent.com/Ryujinx/Ryujinx/master/distribution/misc/Logo.svg" alt="Ryujinx" width="150"></a>
<img src="https://raw.githubusercontent.com/GreemDev/Ryujinx/master/distribution/misc/Logo.svg" alt="Ryujinx" width="150"></a>
<br>
<b>Ryujinx</b>
<br>
@@ -9,29 +9,34 @@
</h1>
<p align="center">
Ryujinx is an open-source Nintendo Switch emulator, created by gdkchan, written in C#.
Ryujinx is an open-source Nintendo Switch emulator, originally created by gdkchan, written in C#.
This emulator aims at providing excellent accuracy and performance, a user-friendly interface and consistent builds.
It was written from scratch and development on the project began in September 2017.
Ryujinx is available on Github under the <a href="https://github.com/Ryujinx/Ryujinx/blob/master/LICENSE.txt" target="_blank">MIT license</a>.
Ryujinx is available on Github under the <a href="https://github.com/GreemDev/Ryujinx/blob/master/LICENSE.txt" target="_blank">MIT license</a>.
<br />
</p>
<p align="center">
On October 1st 2024, Ryujinx was discontinued as the creator was forced to abandon the project.
This fork is intended to be a direct continuation for existing Ryujinx users.
Guides and documentation will not be provided at this time, though you can find the old ones on the Internet Archive.
</p>
<p align="center">
<a href="https://github.com/Ryujinx/Ryujinx/actions/workflows/release.yml">
<img src="https://github.com/Ryujinx/Ryujinx/actions/workflows/release.yml/badge.svg"
<a href="https://github.com/GreemDev/Ryujinx/actions/workflows/release.yml">
<img src="https://github.com/GreemDev/Ryujinx/actions/workflows/release.yml/badge.svg"
alt="">
</a>
<a href="https://crwd.in/ryujinx">
<img src="https://badges.crowdin.net/ryujinx/localized.svg"
alt="">
</a>
<a href="https://discord.com/invite/VkQYXAZ">
<img src="https://img.shields.io/discord/410208534861447168?color=5865F2&label=Ryujinx&logo=discord&logoColor=white"
<a href="https://discord.gg/dHPrkBkkyA">
<img src="https://img.shields.io/discord/1294443224030511104?color=5865F2&label=Ryujinx&logo=discord&logoColor=white"
alt="Discord">
</a>
<br>
<br>
<img src="https://raw.githubusercontent.com/Ryujinx/Ryujinx-Website/master/public/assets/images/shell.png">
<img src="https://raw.githubusercontent.com/GreemDev/Ryujinx/refs/heads/master/docs/shell.png">
</p>
## Compatibility
@@ -39,8 +44,6 @@
As of May 2024, Ryujinx has been tested on approximately 4,300 titles;
over 4,100 boot past menus and into gameplay, with roughly 3,550 of those being considered playable.
You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues).
Anyone is free to submit a new game test or update an existing game test entry;
simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue.
Use the search function to see if a game has been tested already!
@@ -50,22 +53,11 @@ Use the search function to see if a game has been tested already!
To run this emulator, your PC must be equipped with at least 8GiB of RAM;
failing to meet this requirement may result in a poor gameplay experience or unexpected crashes.
See our [Setup & Configuration Guide](https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide) on how to set up the emulator.
For our Local Wireless (LDN) builds, see our [Multiplayer: Local Play/Local Wireless Guide
](https://github.com/Ryujinx/Ryujinx/wiki/Multiplayer-(LDN-Local-Wireless)-Guide).
Avalonia UI comes with translations for various languages. See [Crowdin](https://crwd.in/ryujinx) for more information.
## Latest build
These builds are compiled automatically for each commit on the master branch.
While we strive to ensure optimal stability and performance prior to pushing an update, our automated builds **may be unstable or completely broken**.
If you want to see details on updates to the emulator, you can visit our [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog).
The latest automatic build for Windows, macOS, and Linux can be found on the [Official Website](https://ryujinx.org/download).
## Documentation
If you are planning to contribute or just want to learn more about this project please read through our [documentation](docs/README.md).
@@ -81,7 +73,7 @@ Make sure your SDK version is higher or equal to the required version specified
### Step 2
Either use `git clone https://github.com/Ryujinx/Ryujinx` on the command line to clone the repository or use Code --> Download zip button to get the files.
Either use `git clone https://github.com/GreemDev/Ryujinx` on the command line to clone the repository or use Code --> Download zip button to get the files.
### Step 3
@@ -135,27 +127,6 @@ This folder is located in the user folder, which can be accessed by clicking `Op
The emulator has settings for enabling or disabling some logging, remapping controllers, and more.
You can configure all of them through the graphical interface or manually through the config file, `Config.json`, found in the user folder which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI.
## Contact
If you have contributions, suggestions, need emulator support or just want to get in touch with the team, join our [Discord server](https://discord.com/invite/Ryujinx).
You may also review our [FAQ](https://github.com/Ryujinx/Ryujinx/wiki/Frequently-Asked-Questions).
## Donations
If you'd like to support the project financially, Ryujinx has an active Patreon campaign.
<a href="https://www.patreon.com/ryujinx">
<img src="https://images.squarespace-cdn.com/content/v1/560c1d39e4b0b4fae0c9cf2a/1567548955044-WVD994WZP76EWF15T0L3/Patreon+Button.png?format=500w" width="150">
</a>
All developers working on the project do so in their free time, but the project has several expenses:
* Hackable Nintendo Switch consoles to reverse-engineer the hardware
* Additional computer hardware for testing purposes (e.g. GPUs to diagnose graphical bugs, etc.)
* Licenses for various software development tools (e.g. Jetbrains, IDA)
* Web hosting and infrastructure maintenance (e.g. LDN servers)
All funds received through Patreon are considered a donation to support the project. Patrons receive early access to progress reports and exclusive access to developer interviews.
## License
This software is licensed under the terms of the [MIT license](LICENSE.txt).

View File

@@ -109,12 +109,6 @@ python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "
gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz"
rm "$RELEASE_TAR_FILE_NAME"
# Create legacy update package for Avalonia to not left behind old testers.
if [ "$VERSION" != "1.1.0" ];
then
cp $RELEASE_TAR_FILE_NAME.gz test-ava-ryujinx-$VERSION-macos_universal.app.tar.gz
fi
popd
echo "Done"

BIN
docs/shell.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 KiB

View File

@@ -5,6 +5,9 @@ namespace ARMeilleure
public static class Optimizations
{
// low-core count PPTC
public static bool LowPower { get; set; } = false;
public static bool FastFP { get; set; } = true;
public static bool AllowLcqInFunctionTable { get; set; } = true;
@@ -51,8 +54,8 @@ namespace ARMeilleure
internal static bool UseSse41 => UseSse41IfAvailable && X86HardwareCapabilities.SupportsSse41;
internal static bool UseSse42 => UseSse42IfAvailable && X86HardwareCapabilities.SupportsSse42;
internal static bool UsePopCnt => UsePopCntIfAvailable && X86HardwareCapabilities.SupportsPopcnt;
internal static bool UseAvx => UseAvxIfAvailable && X86HardwareCapabilities.SupportsAvx && !ForceLegacySse;
internal static bool UseAvx512F => UseAvx512FIfAvailable && X86HardwareCapabilities.SupportsAvx512F && !ForceLegacySse;
internal static bool UseAvx => UseAvxIfAvailable && X86HardwareCapabilities.SupportsAvx && !ForceLegacySse;
internal static bool UseAvx512F => UseAvx512FIfAvailable && X86HardwareCapabilities.SupportsAvx512F && !ForceLegacySse;
internal static bool UseAvx512Vl => UseAvx512VlIfAvailable && X86HardwareCapabilities.SupportsAvx512Vl && !ForceLegacySse;
internal static bool UseAvx512Bw => UseAvx512BwIfAvailable && X86HardwareCapabilities.SupportsAvx512Bw && !ForceLegacySse;
internal static bool UseAvx512Dq => UseAvx512DqIfAvailable && X86HardwareCapabilities.SupportsAvx512Dq && !ForceLegacySse;

View File

@@ -309,7 +309,7 @@ namespace ARMeilleure.Translation.PTC
ReadOnlySpan<byte> infosBytes = new(stream.PositionPointer, innerHeader.InfosLength);
stream.Seek(innerHeader.InfosLength, SeekOrigin.Current);
Hash128 infosHash = XXHash128.ComputeHash(infosBytes);
Hash128 infosHash = Hash128.ComputeHash(infosBytes);
if (innerHeader.InfosHash != infosHash)
{
@@ -321,7 +321,7 @@ namespace ARMeilleure.Translation.PTC
ReadOnlySpan<byte> codesBytes = (int)innerHeader.CodesLength > 0 ? new(stream.PositionPointer, (int)innerHeader.CodesLength) : ReadOnlySpan<byte>.Empty;
stream.Seek(innerHeader.CodesLength, SeekOrigin.Current);
Hash128 codesHash = XXHash128.ComputeHash(codesBytes);
Hash128 codesHash = Hash128.ComputeHash(codesBytes);
if (innerHeader.CodesHash != codesHash)
{
@@ -333,7 +333,7 @@ namespace ARMeilleure.Translation.PTC
ReadOnlySpan<byte> relocsBytes = new(stream.PositionPointer, innerHeader.RelocsLength);
stream.Seek(innerHeader.RelocsLength, SeekOrigin.Current);
Hash128 relocsHash = XXHash128.ComputeHash(relocsBytes);
Hash128 relocsHash = Hash128.ComputeHash(relocsBytes);
if (innerHeader.RelocsHash != relocsHash)
{
@@ -345,7 +345,7 @@ namespace ARMeilleure.Translation.PTC
ReadOnlySpan<byte> unwindInfosBytes = new(stream.PositionPointer, innerHeader.UnwindInfosLength);
stream.Seek(innerHeader.UnwindInfosLength, SeekOrigin.Current);
Hash128 unwindInfosHash = XXHash128.ComputeHash(unwindInfosBytes);
Hash128 unwindInfosHash = Hash128.ComputeHash(unwindInfosBytes);
if (innerHeader.UnwindInfosHash != unwindInfosHash)
{
@@ -478,10 +478,10 @@ namespace ARMeilleure.Translation.PTC
Debug.Assert(stream.Position == stream.Length);
innerHeader.InfosHash = XXHash128.ComputeHash(infosBytes);
innerHeader.CodesHash = XXHash128.ComputeHash(codesBytes);
innerHeader.RelocsHash = XXHash128.ComputeHash(relocsBytes);
innerHeader.UnwindInfosHash = XXHash128.ComputeHash(unwindInfosBytes);
innerHeader.InfosHash = Hash128.ComputeHash(infosBytes);
innerHeader.CodesHash = Hash128.ComputeHash(codesBytes);
innerHeader.RelocsHash = Hash128.ComputeHash(relocsBytes);
innerHeader.UnwindInfosHash = Hash128.ComputeHash(unwindInfosBytes);
innerHeader.SetHeaderHash();
@@ -795,10 +795,15 @@ namespace ARMeilleure.Translation.PTC
return;
}
int degreeOfParallelism = Environment.ProcessorCount;
if (Optimizations.LowPower)
degreeOfParallelism /= 3;
// If there are enough cores lying around, we leave one alone for other tasks.
if (degreeOfParallelism > 4)
if (degreeOfParallelism > 4 && !Optimizations.LowPower)
{
degreeOfParallelism--;
}
@@ -907,7 +912,7 @@ namespace ARMeilleure.Translation.PTC
public static Hash128 ComputeHash(IMemoryManager memory, ulong address, ulong guestSize)
{
return XXHash128.ComputeHash(memory.GetSpan(address, checked((int)(guestSize))));
return Hash128.ComputeHash(memory.GetSpan(address, checked((int)(guestSize))));
}
public void WriteCompiledFunction(ulong address, ulong guestSize, Hash128 hash, bool highCq, CompiledFunction compiledFunc)
@@ -1036,14 +1041,14 @@ namespace ARMeilleure.Translation.PTC
{
Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())]);
HeaderHash = Hash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())]);
}
public bool IsHeaderValid()
{
Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())]) == HeaderHash;
return Hash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())]) == HeaderHash;
}
}
@@ -1071,14 +1076,14 @@ namespace ARMeilleure.Translation.PTC
{
Span<InnerHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<InnerHeader>() - Unsafe.SizeOf<Hash128>())]);
HeaderHash = Hash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<InnerHeader>() - Unsafe.SizeOf<Hash128>())]);
}
public bool IsHeaderValid()
{
Span<InnerHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<InnerHeader>() - Unsafe.SizeOf<Hash128>())]) == HeaderHash;
return Hash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<InnerHeader>() - Unsafe.SizeOf<Hash128>())]) == HeaderHash;
}
}

View File

@@ -209,7 +209,7 @@ namespace ARMeilleure.Translation.PTC
Hash128 expectedHash = DeserializeStructure<Hash128>(stream);
Hash128 actualHash = XXHash128.ComputeHash(GetReadOnlySpan(stream));
Hash128 actualHash = Hash128.ComputeHash(GetReadOnlySpan(stream));
if (actualHash != expectedHash)
{
@@ -313,7 +313,7 @@ namespace ARMeilleure.Translation.PTC
Debug.Assert(stream.Position == stream.Length);
stream.Seek(Unsafe.SizeOf<Hash128>(), SeekOrigin.Begin);
Hash128 hash = XXHash128.ComputeHash(GetReadOnlySpan(stream));
Hash128 hash = Hash128.ComputeHash(GetReadOnlySpan(stream));
stream.Seek(0L, SeekOrigin.Begin);
SerializeStructure(stream, hash);
@@ -374,14 +374,14 @@ namespace ARMeilleure.Translation.PTC
{
Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())]);
HeaderHash = Hash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())]);
}
public bool IsHeaderValid()
{
Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())]) == HeaderHash;
return Hash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())]) == HeaderHash;
}
}

View File

@@ -492,7 +492,7 @@ namespace Ryujinx.Common.Collections
Start = start;
End = end;
Max = end;
Values = new List<RangeNode<TKey, TValue>> { new RangeNode<TKey, TValue>(start, end, value) };
Values = [ new RangeNode<TKey, TValue>(start, end, value) ];
Parent = parent;
}
}

View File

@@ -8,7 +8,7 @@ namespace Ryujinx.Common
public static class StreamExtensions
{
/// <summary>
/// Writes a <cref="ReadOnlySpan<int>" /> to this stream.
/// Writes a <see cref="ReadOnlySpan{int}" /> to this stream.
///
/// This default implementation converts each buffer value to a stack-allocated
/// byte array, then writes it to the Stream using <cref="System.Stream.Write(byte[])" />.
@@ -66,8 +66,8 @@ namespace Ryujinx.Common
}
/// <summary>
// Writes a four-byte unsigned integer to this stream. The current position
// of the stream is advanced by four.
/// Writes a four-byte unsigned integer to this stream. The current position
/// of the stream is advanced by four.
/// </summary>
/// <param name="stream">The stream to be written to</param>
/// <param name="value">The value to be written</param>

View File

@@ -2,6 +2,7 @@ using Ryujinx.Common.GraphicsDriver.NVAPI;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// ReSharper disable InconsistentNaming
namespace Ryujinx.Common.GraphicsDriver
{

View File

@@ -1,48 +1,556 @@
using System;
using System.Buffers.Binary;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.X86;
using System.Runtime.Intrinsics;
// ReSharper disable InconsistentNaming
namespace Ryujinx.Common
{
[StructLayout(LayoutKind.Sequential)]
public struct Hash128 : IEquatable<Hash128>
public struct Hash128(ulong low, ulong high) : IEquatable<Hash128>
{
public ulong Low;
public ulong High;
public ulong Low = low;
public ulong High = high;
public Hash128(ulong low, ulong high)
public readonly override string ToString() => $"{High:x16}{Low:x16}";
public static bool operator ==(Hash128 x, Hash128 y) => x.Equals(y);
public static bool operator !=(Hash128 x, Hash128 y) => !x.Equals(y);
public readonly override bool Equals(object obj) => obj is Hash128 hash128 && Equals(hash128);
public readonly bool Equals(Hash128 cmpObj) => Low == cmpObj.Low && High == cmpObj.High;
public readonly override int GetHashCode() => HashCode.Combine(Low, High);
public static Hash128 ComputeHash(ReadOnlySpan<byte> input) => Xxh3128bitsInternal(input, Xxh3KSecret, 0UL);
#region Hash computation
private const int StripeLen = 64;
private const int AccNb = StripeLen / sizeof(ulong);
private const int SecretConsumeRate = 8;
private const int SecretLastAccStart = 7;
private const int SecretMergeAccsStart = 11;
private const int SecretSizeMin = 136;
private const int MidSizeStartOffset = 3;
private const int MidSizeLastOffset = 17;
private const uint Prime32_1 = 0x9E3779B1U;
private const uint Prime32_2 = 0x85EBCA77U;
private const uint Prime32_3 = 0xC2B2AE3DU;
private const uint Prime32_4 = 0x27D4EB2FU;
private const uint Prime32_5 = 0x165667B1U;
private const ulong Prime64_1 = 0x9E3779B185EBCA87UL;
private const ulong Prime64_2 = 0xC2B2AE3D27D4EB4FUL;
private const ulong Prime64_3 = 0x165667B19E3779F9UL;
private const ulong Prime64_4 = 0x85EBCA77C2B2AE63UL;
private const ulong Prime64_5 = 0x27D4EB2F165667C5UL;
private static readonly ulong[] _xxh3InitAcc =
[
Prime32_3,
Prime64_1,
Prime64_2,
Prime64_3,
Prime64_4,
Prime32_2,
Prime64_5,
Prime32_1
];
private static ReadOnlySpan<byte> Xxh3KSecret =>
[
0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c, 0xf7, 0x21, 0xad, 0x1c,
0xde, 0xd4, 0x6d, 0xe9, 0x83, 0x90, 0x97, 0xdb, 0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f,
0xcb, 0x79, 0xe6, 0x4e, 0xcc, 0xc0, 0xe5, 0x78, 0x82, 0x5a, 0xd0, 0x7d, 0xcc, 0xff, 0x72, 0x21,
0xb8, 0x08, 0x46, 0x74, 0xf7, 0x43, 0x24, 0x8e, 0xe0, 0x35, 0x90, 0xe6, 0x81, 0x3a, 0x26, 0x4c,
0x3c, 0x28, 0x52, 0xbb, 0x91, 0xc3, 0x00, 0xcb, 0x88, 0xd0, 0x65, 0x8b, 0x1b, 0x53, 0x2e, 0xa3,
0x71, 0x64, 0x48, 0x97, 0xa2, 0x0d, 0xf9, 0x4e, 0x38, 0x19, 0xef, 0x46, 0xa9, 0xde, 0xac, 0xd8,
0xa8, 0xfa, 0x76, 0x3f, 0xe3, 0x9c, 0x34, 0x3f, 0xf9, 0xdc, 0xbb, 0xc7, 0xc7, 0x0b, 0x4f, 0x1d,
0x8a, 0x51, 0xe0, 0x4b, 0xcd, 0xb4, 0x59, 0x31, 0xc8, 0x9f, 0x7e, 0xc9, 0xd9, 0x78, 0x73, 0x64,
0xea, 0xc5, 0xac, 0x83, 0x34, 0xd3, 0xeb, 0xc3, 0xc5, 0x81, 0xa0, 0xff, 0xfa, 0x13, 0x63, 0xeb,
0x17, 0x0d, 0xdd, 0x51, 0xb7, 0xf0, 0xda, 0x49, 0xd3, 0x16, 0x55, 0x26, 0x29, 0xd4, 0x68, 0x9e,
0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc, 0x8f, 0xf8, 0xb8, 0xd1, 0x7a, 0xd0, 0x31, 0xce,
0x45, 0xcb, 0x3a, 0x8f, 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b, 0x40, 0x7e
];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong Mult32To64(ulong x, ulong y) => (uint)x * (ulong)(uint)y;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Hash128 Mult64To128(ulong lhs, ulong rhs)
{
Low = low;
High = high;
ulong high = Math.BigMul(lhs, rhs, out ulong low);
return new Hash128
{
Low = low,
High = high,
};
}
public readonly override string ToString()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong Mul128Fold64(ulong lhs, ulong rhs)
{
return $"{High:x16}{Low:x16}";
Hash128 product = Mult64To128(lhs, rhs);
return product.Low ^ product.High;
}
public static bool operator ==(Hash128 x, Hash128 y)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong XorShift64(ulong v64, int shift)
{
return x.Equals(y);
Debug.Assert(shift is >= 0 and < 64);
return v64 ^ (v64 >> shift);
}
public static bool operator !=(Hash128 x, Hash128 y)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong Xxh3Avalanche(ulong h64)
{
return !x.Equals(y);
h64 = XorShift64(h64, 37);
h64 *= 0x165667919E3779F9UL;
h64 = XorShift64(h64, 32);
return h64;
}
public readonly override bool Equals(object obj)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong Xxh64Avalanche(ulong h64)
{
return obj is Hash128 hash128 && Equals(hash128);
h64 ^= h64 >> 33;
h64 *= Prime64_2;
h64 ^= h64 >> 29;
h64 *= Prime64_3;
h64 ^= h64 >> 32;
return h64;
}
public readonly bool Equals(Hash128 cmpObj)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe static void Xxh3Accumulate512(Span<ulong> acc, ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret)
{
return Low == cmpObj.Low && High == cmpObj.High;
if (Avx2.IsSupported)
{
fixed (ulong* pAcc = acc)
{
fixed (byte* pInput = input, pSecret = secret)
{
Vector256<ulong>* xAcc = (Vector256<ulong>*)pAcc;
Vector256<byte>* xInput = (Vector256<byte>*)pInput;
Vector256<byte>* xSecret = (Vector256<byte>*)pSecret;
for (ulong i = 0; i < StripeLen / 32; i++)
{
Vector256<byte> dataVec = xInput[i];
Vector256<byte> keyVec = xSecret[i];
Vector256<byte> dataKey = Avx2.Xor(dataVec, keyVec);
Vector256<uint> dataKeyLo = Avx2.Shuffle(dataKey.AsUInt32(), 0b00110001);
Vector256<ulong> product = Avx2.Multiply(dataKey.AsUInt32(), dataKeyLo);
Vector256<uint> dataSwap = Avx2.Shuffle(dataVec.AsUInt32(), 0b01001110);
Vector256<ulong> sum = Avx2.Add(xAcc[i], dataSwap.AsUInt64());
xAcc[i] = Avx2.Add(product, sum);
}
}
}
}
else if (Sse2.IsSupported)
{
fixed (ulong* pAcc = acc)
{
fixed (byte* pInput = input, pSecret = secret)
{
Vector128<ulong>* xAcc = (Vector128<ulong>*)pAcc;
Vector128<byte>* xInput = (Vector128<byte>*)pInput;
Vector128<byte>* xSecret = (Vector128<byte>*)pSecret;
for (ulong i = 0; i < StripeLen / 16; i++)
{
Vector128<byte> dataVec = xInput[i];
Vector128<byte> keyVec = xSecret[i];
Vector128<byte> dataKey = Sse2.Xor(dataVec, keyVec);
Vector128<uint> dataKeyLo = Sse2.Shuffle(dataKey.AsUInt32(), 0b00110001);
Vector128<ulong> product = Sse2.Multiply(dataKey.AsUInt32(), dataKeyLo);
Vector128<uint> dataSwap = Sse2.Shuffle(dataVec.AsUInt32(), 0b01001110);
Vector128<ulong> sum = Sse2.Add(xAcc[i], dataSwap.AsUInt64());
xAcc[i] = Sse2.Add(product, sum);
}
}
}
}
else
{
for (int i = 0; i < AccNb; i++)
{
ulong dataVal = BinaryPrimitives.ReadUInt64LittleEndian(input[(i * sizeof(ulong))..]);
ulong dataKey = dataVal ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[(i * sizeof(ulong))..]);
acc[i ^ 1] += dataVal;
acc[i] += Mult32To64((uint)dataKey, dataKey >> 32);
}
}
}
public readonly override int GetHashCode()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe static void Xxh3ScrambleAcc(Span<ulong> acc, ReadOnlySpan<byte> secret)
{
return HashCode.Combine(Low, High);
if (Avx2.IsSupported)
{
fixed (ulong* pAcc = acc)
{
fixed (byte* pSecret = secret)
{
Vector256<uint> prime32 = Vector256.Create(Prime32_1);
Vector256<ulong>* xAcc = (Vector256<ulong>*)pAcc;
Vector256<byte>* xSecret = (Vector256<byte>*)pSecret;
for (ulong i = 0; i < StripeLen / 32; i++)
{
Vector256<ulong> accVec = xAcc[i];
Vector256<ulong> shifted = Avx2.ShiftRightLogical(accVec, 47);
Vector256<ulong> dataVec = Avx2.Xor(accVec, shifted);
Vector256<byte> keyVec = xSecret[i];
Vector256<uint> dataKey = Avx2.Xor(dataVec.AsUInt32(), keyVec.AsUInt32());
Vector256<uint> dataKeyHi = Avx2.Shuffle(dataKey.AsUInt32(), 0b00110001);
Vector256<ulong> prodLo = Avx2.Multiply(dataKey, prime32);
Vector256<ulong> prodHi = Avx2.Multiply(dataKeyHi, prime32);
xAcc[i] = Avx2.Add(prodLo, Avx2.ShiftLeftLogical(prodHi, 32));
}
}
}
}
else if (Sse2.IsSupported)
{
fixed (ulong* pAcc = acc)
{
fixed (byte* pSecret = secret)
{
Vector128<uint> prime32 = Vector128.Create(Prime32_1);
Vector128<ulong>* xAcc = (Vector128<ulong>*)pAcc;
Vector128<byte>* xSecret = (Vector128<byte>*)pSecret;
for (ulong i = 0; i < StripeLen / 16; i++)
{
Vector128<ulong> accVec = xAcc[i];
Vector128<ulong> shifted = Sse2.ShiftRightLogical(accVec, 47);
Vector128<ulong> dataVec = Sse2.Xor(accVec, shifted);
Vector128<byte> keyVec = xSecret[i];
Vector128<uint> dataKey = Sse2.Xor(dataVec.AsUInt32(), keyVec.AsUInt32());
Vector128<uint> dataKeyHi = Sse2.Shuffle(dataKey.AsUInt32(), 0b00110001);
Vector128<ulong> prodLo = Sse2.Multiply(dataKey, prime32);
Vector128<ulong> prodHi = Sse2.Multiply(dataKeyHi, prime32);
xAcc[i] = Sse2.Add(prodLo, Sse2.ShiftLeftLogical(prodHi, 32));
}
}
}
}
else
{
for (int i = 0; i < AccNb; i++)
{
ulong key64 = BinaryPrimitives.ReadUInt64LittleEndian(secret[(i * sizeof(ulong))..]);
ulong acc64 = acc[i];
acc64 = XorShift64(acc64, 47);
acc64 ^= key64;
acc64 *= Prime32_1;
acc[i] = acc64;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Xxh3Accumulate(Span<ulong> acc, ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, int nbStripes)
{
for (int n = 0; n < nbStripes; n++)
{
ReadOnlySpan<byte> inData = input[(n * StripeLen)..];
Xxh3Accumulate512(acc, inData, secret[(n * SecretConsumeRate)..]);
}
}
private static void Xxh3HashLongInternalLoop(Span<ulong> acc, ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret)
{
int nbStripesPerBlock = (secret.Length - StripeLen) / SecretConsumeRate;
int blockLen = StripeLen * nbStripesPerBlock;
int nbBlocks = (input.Length - 1) / blockLen;
Debug.Assert(secret.Length >= SecretSizeMin);
for (int n = 0; n < nbBlocks; n++)
{
Xxh3Accumulate(acc, input[(n * blockLen)..], secret, nbStripesPerBlock);
Xxh3ScrambleAcc(acc, secret[^StripeLen..]);
}
Debug.Assert(input.Length > StripeLen);
int nbStripes = (input.Length - 1 - (blockLen * nbBlocks)) / StripeLen;
Debug.Assert(nbStripes <= (secret.Length / SecretConsumeRate));
Xxh3Accumulate(acc, input[(nbBlocks * blockLen)..], secret, nbStripes);
ReadOnlySpan<byte> p = input[^StripeLen..];
Xxh3Accumulate512(acc, p, secret[(secret.Length - StripeLen - SecretLastAccStart)..]);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong Xxh3Mix2Accs(Span<ulong> acc, ReadOnlySpan<byte> secret)
{
return Mul128Fold64(
acc[0] ^ BinaryPrimitives.ReadUInt64LittleEndian(secret),
acc[1] ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[8..]));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong Xxh3MergeAccs(Span<ulong> acc, ReadOnlySpan<byte> secret, ulong start)
{
ulong result64 = start;
for (int i = 0; i < 4; i++)
{
result64 += Xxh3Mix2Accs(acc[(2 * i)..], secret[(16 * i)..]);
}
return Xxh3Avalanche(result64);
}
[SkipLocalsInit]
private static Hash128 Xxh3HashLong128bInternal(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret)
{
Span<ulong> acc = stackalloc ulong[AccNb];
_xxh3InitAcc.CopyTo(acc);
Xxh3HashLongInternalLoop(acc, input, secret);
Debug.Assert(acc.Length == 8);
Debug.Assert(secret.Length >= acc.Length * sizeof(ulong) + SecretMergeAccsStart);
return new Hash128
{
Low = Xxh3MergeAccs(acc, secret[SecretMergeAccsStart..], (ulong)input.Length * Prime64_1),
High = Xxh3MergeAccs(
acc,
secret[(secret.Length - acc.Length * sizeof(ulong) - SecretMergeAccsStart)..],
~((ulong)input.Length * Prime64_2)),
};
}
private static Hash128 Xxh3Len1To3128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
{
Debug.Assert(1 <= input.Length && input.Length <= 3);
byte c1 = input[0];
byte c2 = input[input.Length >> 1];
byte c3 = input[^1];
uint combinedL = ((uint)c1 << 16) | ((uint)c2 << 24) | c3 | ((uint)input.Length << 8);
uint combinedH = BitOperations.RotateLeft(BinaryPrimitives.ReverseEndianness(combinedL), 13);
ulong bitFlipL = (BinaryPrimitives.ReadUInt32LittleEndian(secret) ^ BinaryPrimitives.ReadUInt32LittleEndian(secret[4..])) + seed;
ulong bitFlipH = (BinaryPrimitives.ReadUInt32LittleEndian(secret[8..]) ^ BinaryPrimitives.ReadUInt32LittleEndian(secret[12..])) - seed;
ulong keyedLo = combinedL ^ bitFlipL;
ulong keyedHi = combinedH ^ bitFlipH;
return new Hash128
{
Low = Xxh64Avalanche(keyedLo),
High = Xxh64Avalanche(keyedHi),
};
}
private static Hash128 Xxh3Len4To8128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
{
Debug.Assert(4 <= input.Length && input.Length <= 8);
seed ^= BinaryPrimitives.ReverseEndianness((uint)seed) << 32;
uint inputLo = BinaryPrimitives.ReadUInt32LittleEndian(input);
uint inputHi = BinaryPrimitives.ReadUInt32LittleEndian(input[^4..]);
ulong input64 = inputLo + ((ulong)inputHi << 32);
ulong bitFlip = (BinaryPrimitives.ReadUInt64LittleEndian(secret[16..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[24..])) + seed;
ulong keyed = input64 ^ bitFlip;
Hash128 m128 = Mult64To128(keyed, Prime64_1 + ((ulong)input.Length << 2));
m128.High += m128.Low << 1;
m128.Low ^= m128.High >> 3;
m128.Low = XorShift64(m128.Low, 35);
m128.Low *= 0x9FB21C651E98DF25UL;
m128.Low = XorShift64(m128.Low, 28);
m128.High = Xxh3Avalanche(m128.High);
return m128;
}
private static Hash128 Xxh3Len9To16128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
{
Debug.Assert(9 <= input.Length && input.Length <= 16);
ulong bitFlipL = (BinaryPrimitives.ReadUInt64LittleEndian(secret[32..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[40..])) - seed;
ulong bitFlipH = (BinaryPrimitives.ReadUInt64LittleEndian(secret[48..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[56..])) + seed;
ulong inputLo = BinaryPrimitives.ReadUInt64LittleEndian(input);
ulong inputHi = BinaryPrimitives.ReadUInt64LittleEndian(input[^8..]);
Hash128 m128 = Mult64To128(inputLo ^ inputHi ^ bitFlipL, Prime64_1);
m128.Low += ((ulong)input.Length - 1) << 54;
inputHi ^= bitFlipH;
m128.High += inputHi + Mult32To64((uint)inputHi, Prime32_2 - 1);
m128.Low ^= BinaryPrimitives.ReverseEndianness(m128.High);
Hash128 h128 = Mult64To128(m128.Low, Prime64_2);
h128.High += m128.High * Prime64_2;
h128.Low = Xxh3Avalanche(h128.Low);
h128.High = Xxh3Avalanche(h128.High);
return h128;
}
private static Hash128 Xxh3Len0To16128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
{
Debug.Assert(input.Length <= 16);
if (input.Length > 8)
{
return Xxh3Len9To16128b(input, secret, seed);
}
if (input.Length >= 4)
{
return Xxh3Len4To8128b(input, secret, seed);
}
if (input.Length != 0)
{
return Xxh3Len1To3128b(input, secret, seed);
}
Hash128 h128 = new();
ulong bitFlipL = BinaryPrimitives.ReadUInt64LittleEndian(secret[64..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[72..]);
ulong bitFlipH = BinaryPrimitives.ReadUInt64LittleEndian(secret[80..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[88..]);
h128.Low = Xxh64Avalanche(seed ^ bitFlipL);
h128.High = Xxh64Avalanche(seed ^ bitFlipH);
return h128;
}
private static ulong Xxh3Mix16b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
{
ulong inputLo = BinaryPrimitives.ReadUInt64LittleEndian(input);
ulong inputHi = BinaryPrimitives.ReadUInt64LittleEndian(input[8..]);
return Mul128Fold64(
inputLo ^ (BinaryPrimitives.ReadUInt64LittleEndian(secret) + seed),
inputHi ^ (BinaryPrimitives.ReadUInt64LittleEndian(secret[8..]) - seed));
}
private static Hash128 Xxh128Mix32b(Hash128 acc, ReadOnlySpan<byte> input, ReadOnlySpan<byte> input2, ReadOnlySpan<byte> secret, ulong seed)
{
acc.Low += Xxh3Mix16b(input, secret, seed);
acc.Low ^= BinaryPrimitives.ReadUInt64LittleEndian(input2) + BinaryPrimitives.ReadUInt64LittleEndian(input2[8..]);
acc.High += Xxh3Mix16b(input2, secret[16..], seed);
acc.High ^= BinaryPrimitives.ReadUInt64LittleEndian(input) + BinaryPrimitives.ReadUInt64LittleEndian(input[8..]);
return acc;
}
private static Hash128 Xxh3Len17To128128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
{
Debug.Assert(secret.Length >= SecretSizeMin);
Debug.Assert(16 < input.Length && input.Length <= 128);
Hash128 acc = new()
{
Low = (ulong)input.Length * Prime64_1,
High = 0,
};
if (input.Length > 32)
{
if (input.Length > 64)
{
if (input.Length > 96)
{
acc = Xxh128Mix32b(acc, input[48..], input[^64..], secret[96..], seed);
}
acc = Xxh128Mix32b(acc, input[32..], input[^48..], secret[64..], seed);
}
acc = Xxh128Mix32b(acc, input[16..], input[^32..], secret[32..], seed);
}
acc = Xxh128Mix32b(acc, input, input[^16..], secret, seed);
Hash128 h128 = new()
{
Low = acc.Low + acc.High,
High = acc.Low * Prime64_1 + acc.High * Prime64_4 + ((ulong)input.Length - seed) * Prime64_2,
};
h128.Low = Xxh3Avalanche(h128.Low);
h128.High = 0UL - Xxh3Avalanche(h128.High);
return h128;
}
private static Hash128 Xxh3Len129To240128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
{
Debug.Assert(secret.Length >= SecretSizeMin);
Debug.Assert(128 < input.Length && input.Length <= 240);
Hash128 acc = new();
int nbRounds = input.Length / 32;
acc.Low = (ulong)input.Length * Prime64_1;
acc.High = 0;
for (int i = 0; i < 4; i++)
{
acc = Xxh128Mix32b(acc, input[(32 * i)..], input[(32 * i + 16)..], secret[(32 * i)..], seed);
}
acc.Low = Xxh3Avalanche(acc.Low);
acc.High = Xxh3Avalanche(acc.High);
Debug.Assert(nbRounds >= 4);
for (int i = 4; i < nbRounds; i++)
{
acc = Xxh128Mix32b(acc, input[(32 * i)..], input[(32 * i + 16)..], secret[(MidSizeStartOffset + 32 * (i - 4))..], seed);
}
acc = Xxh128Mix32b(acc, input[^16..], input[^32..], secret[(SecretSizeMin - MidSizeLastOffset - 16)..], 0UL - seed);
Hash128 h128 = new()
{
Low = acc.Low + acc.High,
High = acc.Low * Prime64_1 + acc.High * Prime64_4 + ((ulong)input.Length - seed) * Prime64_2,
};
h128.Low = Xxh3Avalanche(h128.Low);
h128.High = 0UL - Xxh3Avalanche(h128.High);
return h128;
}
private static Hash128 Xxh3128bitsInternal(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
{
Debug.Assert(secret.Length >= SecretSizeMin);
return input.Length switch
{
<= 16 => Xxh3Len0To16128b(input, secret, seed),
<= 128 => Xxh3Len17To128128b(input, secret, seed),
<= 240 => Xxh3Len129To240128b(input, secret, seed),
_ => Xxh3HashLong128bInternal(input, secret)
};
}
#endregion
}
}

View File

@@ -24,7 +24,7 @@ namespace Ryujinx.Common.Logging
public readonly struct Log
{
private static readonly string _homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
private static readonly string _homeDirRedacted = Path.Combine(Directory.GetParent(_homeDir).FullName, "[redacted]");
private static readonly string _homeDirRedacted = Path.Combine(Directory.GetParent(_homeDir)!.FullName, "[redacted]");
internal readonly LogLevel Level;
@@ -233,7 +233,7 @@ namespace Ryujinx.Common.Logging
case LogLevel.AccessLog : AccessLog = enabled ? new Log(LogLevel.AccessLog) : new Log?(); break;
case LogLevel.Stub : Stub = enabled ? new Log(LogLevel.Stub) : new Log?(); break;
case LogLevel.Trace : Trace = enabled ? new Log(LogLevel.Trace) : new Log?(); break;
default: throw new ArgumentException("Unknown Log Level");
default: throw new ArgumentException("Unknown Log Level", nameof(logLevel));
#pragma warning restore IDE0055
}
}

View File

@@ -8,7 +8,7 @@ namespace Ryujinx.Common
private const string FlatHubChannelOwner = "flathub";
private const string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
private const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
public const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
private const string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%";
private const string ConfigFileName = "%%RYUJINX_CONFIG_FILE_NAME%%";

View File

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

View File

@@ -4,23 +4,18 @@ namespace Ryujinx.Common
{
public static class BitUtils
{
public static T AlignUp<T>(T value, T size)
where T : IBinaryInteger<T>
{
return (value + (size - T.One)) & -size;
}
public static T AlignUp<T>(T value, T size) where T : IBinaryInteger<T>
=> (value + (size - T.One)) & -size;
public static T AlignDown<T>(T value, T size)
where T : IBinaryInteger<T>
{
return value & -size;
}
public static T AlignDown<T>(T value, T size) where T : IBinaryInteger<T>
=> value & -size;
public static T DivRoundUp<T>(T value, T dividend)
where T : IBinaryInteger<T>
{
return (value + (dividend - T.One)) / dividend;
}
public static T DivRoundUp<T>(T value, T dividend) where T : IBinaryInteger<T>
=> (value + (dividend - T.One)) / dividend;
public static int Pow2RoundDown(int value) => BitOperations.IsPow2(value) ? value : Pow2RoundUp(value) >> 1;
public static long ReverseBits64(long value) => (long)ReverseBits64((ulong)value);
public static int Pow2RoundUp(int value)
{
@@ -35,16 +30,6 @@ namespace Ryujinx.Common
return ++value;
}
public static int Pow2RoundDown(int value)
{
return BitOperations.IsPow2(value) ? value : Pow2RoundUp(value) >> 1;
}
public static long ReverseBits64(long value)
{
return (long)ReverseBits64((ulong)value);
}
private static ulong ReverseBits64(ulong value)
{
value = ((value & 0xaaaaaaaaaaaaaaaa) >> 1) | ((value & 0x5555555555555555) << 1);

View File

@@ -75,15 +75,10 @@ namespace Ryujinx.Common.Utilities
if (char.IsUpper(c))
{
if (i == 0 || char.IsUpper(name[i - 1]))
{
builder.Append(char.ToLowerInvariant(c));
}
else
{
if (!(i == 0 || char.IsUpper(name[i - 1])))
builder.Append('_');
builder.Append(char.ToLowerInvariant(c));
}
builder.Append(char.ToLowerInvariant(c));
}
else
{

View File

@@ -5,14 +5,16 @@ namespace Ryujinx.Common.Utilities
{
public static class UInt128Utils
{
public static UInt128 FromHex(string hex)
{
return new UInt128(ulong.Parse(hex.AsSpan(0, 16), NumberStyles.HexNumber), ulong.Parse(hex.AsSpan(16), NumberStyles.HexNumber));
}
public static UInt128 FromHex(string hex) =>
new(
ulong.Parse(hex.AsSpan(0, 16), NumberStyles.HexNumber),
ulong.Parse(hex.AsSpan(16), NumberStyles.HexNumber)
);
public static UInt128 CreateRandom()
{
return new UInt128((ulong)Random.Shared.NextInt64(), (ulong)Random.Shared.NextInt64());
}
public static Int128 NextInt128(this Random rand) =>
new((ulong)rand.NextInt64(), (ulong)rand.NextInt64());
public static UInt128 NextUInt128(this Random rand) =>
new((ulong)rand.NextInt64(), (ulong)rand.NextInt64());
}
}

View File

@@ -1,548 +0,0 @@
using System;
using System.Buffers.Binary;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace Ryujinx.Common
{
public static class XXHash128
{
private const int StripeLen = 64;
private const int AccNb = StripeLen / sizeof(ulong);
private const int SecretConsumeRate = 8;
private const int SecretLastAccStart = 7;
private const int SecretMergeAccsStart = 11;
private const int SecretSizeMin = 136;
private const int MidSizeStartOffset = 3;
private const int MidSizeLastOffset = 17;
private const uint Prime32_1 = 0x9E3779B1U;
private const uint Prime32_2 = 0x85EBCA77U;
private const uint Prime32_3 = 0xC2B2AE3DU;
private const uint Prime32_4 = 0x27D4EB2FU;
private const uint Prime32_5 = 0x165667B1U;
private const ulong Prime64_1 = 0x9E3779B185EBCA87UL;
private const ulong Prime64_2 = 0xC2B2AE3D27D4EB4FUL;
private const ulong Prime64_3 = 0x165667B19E3779F9UL;
private const ulong Prime64_4 = 0x85EBCA77C2B2AE63UL;
private const ulong Prime64_5 = 0x27D4EB2F165667C5UL;
private static readonly ulong[] _xxh3InitAcc = {
Prime32_3,
Prime64_1,
Prime64_2,
Prime64_3,
Prime64_4,
Prime32_2,
Prime64_5,
Prime32_1,
};
private static ReadOnlySpan<byte> Xxh3KSecret => new byte[]
{
0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c, 0xf7, 0x21, 0xad, 0x1c,
0xde, 0xd4, 0x6d, 0xe9, 0x83, 0x90, 0x97, 0xdb, 0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f,
0xcb, 0x79, 0xe6, 0x4e, 0xcc, 0xc0, 0xe5, 0x78, 0x82, 0x5a, 0xd0, 0x7d, 0xcc, 0xff, 0x72, 0x21,
0xb8, 0x08, 0x46, 0x74, 0xf7, 0x43, 0x24, 0x8e, 0xe0, 0x35, 0x90, 0xe6, 0x81, 0x3a, 0x26, 0x4c,
0x3c, 0x28, 0x52, 0xbb, 0x91, 0xc3, 0x00, 0xcb, 0x88, 0xd0, 0x65, 0x8b, 0x1b, 0x53, 0x2e, 0xa3,
0x71, 0x64, 0x48, 0x97, 0xa2, 0x0d, 0xf9, 0x4e, 0x38, 0x19, 0xef, 0x46, 0xa9, 0xde, 0xac, 0xd8,
0xa8, 0xfa, 0x76, 0x3f, 0xe3, 0x9c, 0x34, 0x3f, 0xf9, 0xdc, 0xbb, 0xc7, 0xc7, 0x0b, 0x4f, 0x1d,
0x8a, 0x51, 0xe0, 0x4b, 0xcd, 0xb4, 0x59, 0x31, 0xc8, 0x9f, 0x7e, 0xc9, 0xd9, 0x78, 0x73, 0x64,
0xea, 0xc5, 0xac, 0x83, 0x34, 0xd3, 0xeb, 0xc3, 0xc5, 0x81, 0xa0, 0xff, 0xfa, 0x13, 0x63, 0xeb,
0x17, 0x0d, 0xdd, 0x51, 0xb7, 0xf0, 0xda, 0x49, 0xd3, 0x16, 0x55, 0x26, 0x29, 0xd4, 0x68, 0x9e,
0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc, 0x8f, 0xf8, 0xb8, 0xd1, 0x7a, 0xd0, 0x31, 0xce,
0x45, 0xcb, 0x3a, 0x8f, 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b, 0x40, 0x7e,
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong Mult32To64(ulong x, ulong y)
{
return (uint)x * (ulong)(uint)y;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Hash128 Mult64To128(ulong lhs, ulong rhs)
{
ulong high = Math.BigMul(lhs, rhs, out ulong low);
return new Hash128
{
Low = low,
High = high,
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong Mul128Fold64(ulong lhs, ulong rhs)
{
Hash128 product = Mult64To128(lhs, rhs);
return product.Low ^ product.High;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong XorShift64(ulong v64, int shift)
{
Debug.Assert(0 <= shift && shift < 64);
return v64 ^ (v64 >> shift);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong Xxh3Avalanche(ulong h64)
{
h64 = XorShift64(h64, 37);
h64 *= 0x165667919E3779F9UL;
h64 = XorShift64(h64, 32);
return h64;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong Xxh64Avalanche(ulong h64)
{
h64 ^= h64 >> 33;
h64 *= Prime64_2;
h64 ^= h64 >> 29;
h64 *= Prime64_3;
h64 ^= h64 >> 32;
return h64;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe static void Xxh3Accumulate512(Span<ulong> acc, ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret)
{
if (Avx2.IsSupported)
{
fixed (ulong* pAcc = acc)
{
fixed (byte* pInput = input, pSecret = secret)
{
Vector256<ulong>* xAcc = (Vector256<ulong>*)pAcc;
Vector256<byte>* xInput = (Vector256<byte>*)pInput;
Vector256<byte>* xSecret = (Vector256<byte>*)pSecret;
for (ulong i = 0; i < StripeLen / 32; i++)
{
Vector256<byte> dataVec = xInput[i];
Vector256<byte> keyVec = xSecret[i];
Vector256<byte> dataKey = Avx2.Xor(dataVec, keyVec);
Vector256<uint> dataKeyLo = Avx2.Shuffle(dataKey.AsUInt32(), 0b00110001);
Vector256<ulong> product = Avx2.Multiply(dataKey.AsUInt32(), dataKeyLo);
Vector256<uint> dataSwap = Avx2.Shuffle(dataVec.AsUInt32(), 0b01001110);
Vector256<ulong> sum = Avx2.Add(xAcc[i], dataSwap.AsUInt64());
xAcc[i] = Avx2.Add(product, sum);
}
}
}
}
else if (Sse2.IsSupported)
{
fixed (ulong* pAcc = acc)
{
fixed (byte* pInput = input, pSecret = secret)
{
Vector128<ulong>* xAcc = (Vector128<ulong>*)pAcc;
Vector128<byte>* xInput = (Vector128<byte>*)pInput;
Vector128<byte>* xSecret = (Vector128<byte>*)pSecret;
for (ulong i = 0; i < StripeLen / 16; i++)
{
Vector128<byte> dataVec = xInput[i];
Vector128<byte> keyVec = xSecret[i];
Vector128<byte> dataKey = Sse2.Xor(dataVec, keyVec);
Vector128<uint> dataKeyLo = Sse2.Shuffle(dataKey.AsUInt32(), 0b00110001);
Vector128<ulong> product = Sse2.Multiply(dataKey.AsUInt32(), dataKeyLo);
Vector128<uint> dataSwap = Sse2.Shuffle(dataVec.AsUInt32(), 0b01001110);
Vector128<ulong> sum = Sse2.Add(xAcc[i], dataSwap.AsUInt64());
xAcc[i] = Sse2.Add(product, sum);
}
}
}
}
else
{
for (int i = 0; i < AccNb; i++)
{
ulong dataVal = BinaryPrimitives.ReadUInt64LittleEndian(input[(i * sizeof(ulong))..]);
ulong dataKey = dataVal ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[(i * sizeof(ulong))..]);
acc[i ^ 1] += dataVal;
acc[i] += Mult32To64((uint)dataKey, dataKey >> 32);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe static void Xxh3ScrambleAcc(Span<ulong> acc, ReadOnlySpan<byte> secret)
{
if (Avx2.IsSupported)
{
fixed (ulong* pAcc = acc)
{
fixed (byte* pSecret = secret)
{
Vector256<uint> prime32 = Vector256.Create(Prime32_1);
Vector256<ulong>* xAcc = (Vector256<ulong>*)pAcc;
Vector256<byte>* xSecret = (Vector256<byte>*)pSecret;
for (ulong i = 0; i < StripeLen / 32; i++)
{
Vector256<ulong> accVec = xAcc[i];
Vector256<ulong> shifted = Avx2.ShiftRightLogical(accVec, 47);
Vector256<ulong> dataVec = Avx2.Xor(accVec, shifted);
Vector256<byte> keyVec = xSecret[i];
Vector256<uint> dataKey = Avx2.Xor(dataVec.AsUInt32(), keyVec.AsUInt32());
Vector256<uint> dataKeyHi = Avx2.Shuffle(dataKey.AsUInt32(), 0b00110001);
Vector256<ulong> prodLo = Avx2.Multiply(dataKey, prime32);
Vector256<ulong> prodHi = Avx2.Multiply(dataKeyHi, prime32);
xAcc[i] = Avx2.Add(prodLo, Avx2.ShiftLeftLogical(prodHi, 32));
}
}
}
}
else if (Sse2.IsSupported)
{
fixed (ulong* pAcc = acc)
{
fixed (byte* pSecret = secret)
{
Vector128<uint> prime32 = Vector128.Create(Prime32_1);
Vector128<ulong>* xAcc = (Vector128<ulong>*)pAcc;
Vector128<byte>* xSecret = (Vector128<byte>*)pSecret;
for (ulong i = 0; i < StripeLen / 16; i++)
{
Vector128<ulong> accVec = xAcc[i];
Vector128<ulong> shifted = Sse2.ShiftRightLogical(accVec, 47);
Vector128<ulong> dataVec = Sse2.Xor(accVec, shifted);
Vector128<byte> keyVec = xSecret[i];
Vector128<uint> dataKey = Sse2.Xor(dataVec.AsUInt32(), keyVec.AsUInt32());
Vector128<uint> dataKeyHi = Sse2.Shuffle(dataKey.AsUInt32(), 0b00110001);
Vector128<ulong> prodLo = Sse2.Multiply(dataKey, prime32);
Vector128<ulong> prodHi = Sse2.Multiply(dataKeyHi, prime32);
xAcc[i] = Sse2.Add(prodLo, Sse2.ShiftLeftLogical(prodHi, 32));
}
}
}
}
else
{
for (int i = 0; i < AccNb; i++)
{
ulong key64 = BinaryPrimitives.ReadUInt64LittleEndian(secret[(i * sizeof(ulong))..]);
ulong acc64 = acc[i];
acc64 = XorShift64(acc64, 47);
acc64 ^= key64;
acc64 *= Prime32_1;
acc[i] = acc64;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Xxh3Accumulate(Span<ulong> acc, ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, int nbStripes)
{
for (int n = 0; n < nbStripes; n++)
{
ReadOnlySpan<byte> inData = input[(n * StripeLen)..];
Xxh3Accumulate512(acc, inData, secret[(n * SecretConsumeRate)..]);
}
}
private static void Xxh3HashLongInternalLoop(Span<ulong> acc, ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret)
{
int nbStripesPerBlock = (secret.Length - StripeLen) / SecretConsumeRate;
int blockLen = StripeLen * nbStripesPerBlock;
int nbBlocks = (input.Length - 1) / blockLen;
Debug.Assert(secret.Length >= SecretSizeMin);
for (int n = 0; n < nbBlocks; n++)
{
Xxh3Accumulate(acc, input[(n * blockLen)..], secret, nbStripesPerBlock);
Xxh3ScrambleAcc(acc, secret[^StripeLen..]);
}
Debug.Assert(input.Length > StripeLen);
int nbStripes = (input.Length - 1 - (blockLen * nbBlocks)) / StripeLen;
Debug.Assert(nbStripes <= (secret.Length / SecretConsumeRate));
Xxh3Accumulate(acc, input[(nbBlocks * blockLen)..], secret, nbStripes);
ReadOnlySpan<byte> p = input[^StripeLen..];
Xxh3Accumulate512(acc, p, secret[(secret.Length - StripeLen - SecretLastAccStart)..]);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong Xxh3Mix2Accs(Span<ulong> acc, ReadOnlySpan<byte> secret)
{
return Mul128Fold64(
acc[0] ^ BinaryPrimitives.ReadUInt64LittleEndian(secret),
acc[1] ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[8..]));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong Xxh3MergeAccs(Span<ulong> acc, ReadOnlySpan<byte> secret, ulong start)
{
ulong result64 = start;
for (int i = 0; i < 4; i++)
{
result64 += Xxh3Mix2Accs(acc[(2 * i)..], secret[(16 * i)..]);
}
return Xxh3Avalanche(result64);
}
[SkipLocalsInit]
private static Hash128 Xxh3HashLong128bInternal(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret)
{
Span<ulong> acc = stackalloc ulong[AccNb];
_xxh3InitAcc.CopyTo(acc);
Xxh3HashLongInternalLoop(acc, input, secret);
Debug.Assert(acc.Length == 8);
Debug.Assert(secret.Length >= acc.Length * sizeof(ulong) + SecretMergeAccsStart);
return new Hash128
{
Low = Xxh3MergeAccs(acc, secret[SecretMergeAccsStart..], (ulong)input.Length * Prime64_1),
High = Xxh3MergeAccs(
acc,
secret[(secret.Length - acc.Length * sizeof(ulong) - SecretMergeAccsStart)..],
~((ulong)input.Length * Prime64_2)),
};
}
private static Hash128 Xxh3Len1To3128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
{
Debug.Assert(1 <= input.Length && input.Length <= 3);
byte c1 = input[0];
byte c2 = input[input.Length >> 1];
byte c3 = input[^1];
uint combinedL = ((uint)c1 << 16) | ((uint)c2 << 24) | c3 | ((uint)input.Length << 8);
uint combinedH = BitOperations.RotateLeft(BinaryPrimitives.ReverseEndianness(combinedL), 13);
ulong bitFlipL = (BinaryPrimitives.ReadUInt32LittleEndian(secret) ^ BinaryPrimitives.ReadUInt32LittleEndian(secret[4..])) + seed;
ulong bitFlipH = (BinaryPrimitives.ReadUInt32LittleEndian(secret[8..]) ^ BinaryPrimitives.ReadUInt32LittleEndian(secret[12..])) - seed;
ulong keyedLo = combinedL ^ bitFlipL;
ulong keyedHi = combinedH ^ bitFlipH;
return new Hash128
{
Low = Xxh64Avalanche(keyedLo),
High = Xxh64Avalanche(keyedHi),
};
}
private static Hash128 Xxh3Len4To8128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
{
Debug.Assert(4 <= input.Length && input.Length <= 8);
seed ^= BinaryPrimitives.ReverseEndianness((uint)seed) << 32;
uint inputLo = BinaryPrimitives.ReadUInt32LittleEndian(input);
uint inputHi = BinaryPrimitives.ReadUInt32LittleEndian(input[^4..]);
ulong input64 = inputLo + ((ulong)inputHi << 32);
ulong bitFlip = (BinaryPrimitives.ReadUInt64LittleEndian(secret[16..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[24..])) + seed;
ulong keyed = input64 ^ bitFlip;
Hash128 m128 = Mult64To128(keyed, Prime64_1 + ((ulong)input.Length << 2));
m128.High += m128.Low << 1;
m128.Low ^= m128.High >> 3;
m128.Low = XorShift64(m128.Low, 35);
m128.Low *= 0x9FB21C651E98DF25UL;
m128.Low = XorShift64(m128.Low, 28);
m128.High = Xxh3Avalanche(m128.High);
return m128;
}
private static Hash128 Xxh3Len9To16128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
{
Debug.Assert(9 <= input.Length && input.Length <= 16);
ulong bitFlipL = (BinaryPrimitives.ReadUInt64LittleEndian(secret[32..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[40..])) - seed;
ulong bitFlipH = (BinaryPrimitives.ReadUInt64LittleEndian(secret[48..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[56..])) + seed;
ulong inputLo = BinaryPrimitives.ReadUInt64LittleEndian(input);
ulong inputHi = BinaryPrimitives.ReadUInt64LittleEndian(input[^8..]);
Hash128 m128 = Mult64To128(inputLo ^ inputHi ^ bitFlipL, Prime64_1);
m128.Low += ((ulong)input.Length - 1) << 54;
inputHi ^= bitFlipH;
m128.High += inputHi + Mult32To64((uint)inputHi, Prime32_2 - 1);
m128.Low ^= BinaryPrimitives.ReverseEndianness(m128.High);
Hash128 h128 = Mult64To128(m128.Low, Prime64_2);
h128.High += m128.High * Prime64_2;
h128.Low = Xxh3Avalanche(h128.Low);
h128.High = Xxh3Avalanche(h128.High);
return h128;
}
private static Hash128 Xxh3Len0To16128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
{
Debug.Assert(input.Length <= 16);
if (input.Length > 8)
{
return Xxh3Len9To16128b(input, secret, seed);
}
if (input.Length >= 4)
{
return Xxh3Len4To8128b(input, secret, seed);
}
if (input.Length != 0)
{
return Xxh3Len1To3128b(input, secret, seed);
}
Hash128 h128 = new();
ulong bitFlipL = BinaryPrimitives.ReadUInt64LittleEndian(secret[64..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[72..]);
ulong bitFlipH = BinaryPrimitives.ReadUInt64LittleEndian(secret[80..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[88..]);
h128.Low = Xxh64Avalanche(seed ^ bitFlipL);
h128.High = Xxh64Avalanche(seed ^ bitFlipH);
return h128;
}
private static ulong Xxh3Mix16b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
{
ulong inputLo = BinaryPrimitives.ReadUInt64LittleEndian(input);
ulong inputHi = BinaryPrimitives.ReadUInt64LittleEndian(input[8..]);
return Mul128Fold64(
inputLo ^ (BinaryPrimitives.ReadUInt64LittleEndian(secret) + seed),
inputHi ^ (BinaryPrimitives.ReadUInt64LittleEndian(secret[8..]) - seed));
}
private static Hash128 Xxh128Mix32b(Hash128 acc, ReadOnlySpan<byte> input, ReadOnlySpan<byte> input2, ReadOnlySpan<byte> secret, ulong seed)
{
acc.Low += Xxh3Mix16b(input, secret, seed);
acc.Low ^= BinaryPrimitives.ReadUInt64LittleEndian(input2) + BinaryPrimitives.ReadUInt64LittleEndian(input2[8..]);
acc.High += Xxh3Mix16b(input2, secret[16..], seed);
acc.High ^= BinaryPrimitives.ReadUInt64LittleEndian(input) + BinaryPrimitives.ReadUInt64LittleEndian(input[8..]);
return acc;
}
private static Hash128 Xxh3Len17To128128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
{
Debug.Assert(secret.Length >= SecretSizeMin);
Debug.Assert(16 < input.Length && input.Length <= 128);
Hash128 acc = new()
{
Low = (ulong)input.Length * Prime64_1,
High = 0,
};
if (input.Length > 32)
{
if (input.Length > 64)
{
if (input.Length > 96)
{
acc = Xxh128Mix32b(acc, input[48..], input[^64..], secret[96..], seed);
}
acc = Xxh128Mix32b(acc, input[32..], input[^48..], secret[64..], seed);
}
acc = Xxh128Mix32b(acc, input[16..], input[^32..], secret[32..], seed);
}
acc = Xxh128Mix32b(acc, input, input[^16..], secret, seed);
Hash128 h128 = new()
{
Low = acc.Low + acc.High,
High = acc.Low * Prime64_1 + acc.High * Prime64_4 + ((ulong)input.Length - seed) * Prime64_2,
};
h128.Low = Xxh3Avalanche(h128.Low);
h128.High = 0UL - Xxh3Avalanche(h128.High);
return h128;
}
private static Hash128 Xxh3Len129To240128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
{
Debug.Assert(secret.Length >= SecretSizeMin);
Debug.Assert(128 < input.Length && input.Length <= 240);
Hash128 acc = new();
int nbRounds = input.Length / 32;
acc.Low = (ulong)input.Length * Prime64_1;
acc.High = 0;
for (int i = 0; i < 4; i++)
{
acc = Xxh128Mix32b(acc, input[(32 * i)..], input[(32 * i + 16)..], secret[(32 * i)..], seed);
}
acc.Low = Xxh3Avalanche(acc.Low);
acc.High = Xxh3Avalanche(acc.High);
Debug.Assert(nbRounds >= 4);
for (int i = 4; i < nbRounds; i++)
{
acc = Xxh128Mix32b(acc, input[(32 * i)..], input[(32 * i + 16)..], secret[(MidSizeStartOffset + 32 * (i - 4))..], seed);
}
acc = Xxh128Mix32b(acc, input[^16..], input[^32..], secret[(SecretSizeMin - MidSizeLastOffset - 16)..], 0UL - seed);
Hash128 h128 = new()
{
Low = acc.Low + acc.High,
High = acc.Low * Prime64_1 + acc.High * Prime64_4 + ((ulong)input.Length - seed) * Prime64_2,
};
h128.Low = Xxh3Avalanche(h128.Low);
h128.High = 0UL - Xxh3Avalanche(h128.High);
return h128;
}
private static Hash128 Xxh3128bitsInternal(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
{
Debug.Assert(secret.Length >= SecretSizeMin);
if (input.Length <= 16)
{
return Xxh3Len0To16128b(input, secret, seed);
}
if (input.Length <= 128)
{
return Xxh3Len17To128128b(input, secret, seed);
}
if (input.Length <= 240)
{
return Xxh3Len129To240128b(input, secret, seed);
}
return Xxh3HashLong128bInternal(input, secret);
}
public static Hash128 ComputeHash(ReadOnlySpan<byte> input)
{
return Xxh3128bitsInternal(input, Xxh3KSecret, 0UL);
}
}
}

View File

@@ -96,7 +96,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
{
ref var entry = ref _table[i];
var hash = XXHash128.ComputeHash(mc[..entry.Length]);
var hash = Hash128.ComputeHash(mc[..entry.Length]);
if (hash == entry.Hash)
{
if (IsMacroHLESupported(caps, entry.Name))

View File

@@ -223,7 +223,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.Blender
foreach (var entry in Table)
{
Hash128 hash = XXHash128.ComputeHash(MemoryMarshal.Cast<uint, byte>(entry.Code));
Hash128 hash = Hash128.ComputeHash(MemoryMarshal.Cast<uint, byte>(entry.Code));
string[] constants = new string[entry.Constants != null ? entry.Constants.Length : 0];

View File

@@ -62,7 +62,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.Blender
currentCode = currentCode[..codeLength];
}
Hash128 hash = XXHash128.ComputeHash(MemoryMarshal.Cast<uint, byte>(currentCode));
Hash128 hash = Hash128.ComputeHash(MemoryMarshal.Cast<uint, byte>(currentCode));
descriptor = default;

View File

@@ -453,7 +453,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// <returns>Hash of the data</returns>
private static uint CalcHash(ReadOnlySpan<byte> data)
{
return (uint)XXHash128.ComputeHash(data).Low;
return (uint)Hash128.ComputeHash(data).Low;
}
}
}

View File

@@ -52,7 +52,7 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg
int avCodecMajorVersion = avCodecRawVersion >> 16;
int avCodecMinorVersion = (avCodecRawVersion >> 8) & 0xFF;
// libavcodec 59.24 changed AvCodec to move its private API and also move the codec function to an union.
// libavcodec 59.24 changed AvCodec to move its private API and also move the codec function to a union.
if (avCodecMajorVersion > 59 || (avCodecMajorVersion == 59 && avCodecMinorVersion > 24))
{
_decodeFrame = Marshal.GetDelegateForFunctionPointer<AVCodec_decode>(((FFCodec<AVCodec>*)_codec)->CodecCallback);

View File

@@ -26,8 +26,7 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg.H264
{
Surface outSurf = (Surface)output;
if (outSurf.RequestedWidth != _oldOutputWidth ||
outSurf.RequestedHeight != _oldOutputHeight)
if (outSurf.RequestedWidth != _oldOutputWidth || outSurf.RequestedHeight != _oldOutputHeight)
{
_context.Dispose();
_context = new FFmpegContext(AVCodecID.AV_CODEC_ID_H264);
@@ -38,7 +37,7 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg.H264
Span<byte> bs = Prepend(bitstream, SpsAndPpsReconstruction.Reconstruct(ref pictureInfo, _workBuffer));
return _context.DecodeFrame(outSurf, bs) == 0;
return _context.DecodeFrame(outSurf, bs) is 0;
}
private static byte[] Prepend(ReadOnlySpan<byte> data, ReadOnlySpan<byte> prep)

View File

@@ -3,23 +3,13 @@ using System.Numerics;
namespace Ryujinx.Graphics.Nvdec.FFmpeg.H264
{
struct H264BitStreamWriter
struct H264BitStreamWriter(byte[] workBuffer)
{
private const int BufferSize = 8;
private readonly byte[] _workBuffer;
private int _offset;
private int _buffer;
private int _bufferPos;
public H264BitStreamWriter(byte[] workBuffer)
{
_workBuffer = workBuffer;
_offset = 0;
_buffer = 0;
_bufferPos = 0;
}
private int _offset = 0;
private int _buffer = 0;
private int _bufferPos = 0;
public void WriteBit(bool value)
{
@@ -59,9 +49,7 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg.H264
private int GetFreeBufferBits()
{
if (_bufferPos == BufferSize)
{
Flush();
}
return BufferSize - _bufferPos;
}
@@ -70,7 +58,7 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg.H264
{
if (_bufferPos != 0)
{
_workBuffer[_offset++] = (byte)_buffer;
workBuffer[_offset++] = (byte)_buffer;
_buffer = 0;
_bufferPos = 0;
@@ -84,10 +72,8 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg.H264
Flush();
}
public readonly Span<byte> AsSpan()
{
return new Span<byte>(_workBuffer)[.._offset];
}
public readonly Span<byte> AsSpan()
=> new Span<byte>(workBuffer)[.._offset];
public void WriteU(uint value, int valueSize) => WriteBits((int)value, valueSize);
public void WriteSe(int value) => WriteExpGolombCodedInt(value);

View File

@@ -243,7 +243,7 @@ namespace Ryujinx.Graphics.OpenGL
// This is required to disable [0, 1] clamping for SNorm outputs on compatibility profiles.
// This call is expected to fail if we're running with a core profile,
// as this clamp target was deprecated, but that's fine as a core profile
// should already have the desired behaviour were outputs are not clamped.
// should already have the desired behaviour where outputs are not clamped.
GL.ClampColor(ClampColorTarget.ClampFragmentColor, ClampColorMode.False);
}

View File

@@ -45,7 +45,7 @@ namespace Ryujinx.Graphics.Texture
if (subsetCount == 0)
{
// Mode is invalid, the spec mandates that hardware fills the block with
// a opaque black color.
// an opaque black color.
for (int ty = 0; ty < h; ty++)
{
int baseOffs = ty * width;

View File

@@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Texture
private readonly BlockLinearLayout _layoutConverter;
// Variables for built in iteration.
// Variables for built-in iteration.
private int _yPart;
public OffsetCalculator(
@@ -73,69 +73,50 @@ namespace Ryujinx.Graphics.Texture
public int GetOffset(int x, int y)
{
if (_isLinear)
{
return x * _bytesPerPixel + y * _stride;
}
else
{
return _layoutConverter.GetOffset(x, y, 0);
}
return _layoutConverter.GetOffset(x, y, 0);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetOffset(int x)
{
if (_isLinear)
{
return x * _bytesPerPixel + _yPart;
}
else
{
return _layoutConverter.GetOffset(x);
}
return _layoutConverter.GetOffset(x);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetOffsetWithLineOffset64(int x)
{
if (_isLinear)
{
return x + _yPart;
}
else
{
return _layoutConverter.GetOffsetWithLineOffset64(x);
}
return _layoutConverter.GetOffsetWithLineOffset64(x);
}
public (int offset, int size) GetRectangleRange(int x, int y, int width, int height)
{
if (_isLinear)
{
int start = y * Math.Abs(_stride) + x * _bytesPerPixel;
int end = (y + height - 1) * Math.Abs(_stride) + (x + width) * _bytesPerPixel;
return (y * _stride + x * _bytesPerPixel, end - start);
}
else
{
if (!_isLinear)
return _layoutConverter.GetRectangleRange(x, y, width, height);
}
int start = y * Math.Abs(_stride) + x * _bytesPerPixel;
int end = (y + height - 1) * Math.Abs(_stride) + (x + width) * _bytesPerPixel;
return (y * _stride + x * _bytesPerPixel, end - start);
}
public bool LayoutMatches(OffsetCalculator other)
{
if (_isLinear)
{
return other._isLinear &&
_width == other._width &&
_height == other._height &&
_stride == other._stride &&
_bytesPerPixel == other._bytesPerPixel;
}
else
{
return !other._isLinear && _layoutConverter.LayoutMatches(other._layoutConverter);
}
return !other._isLinear && _layoutConverter.LayoutMatches(other._layoutConverter);
}
}
}

View File

@@ -76,9 +76,7 @@ namespace Ryujinx.Graphics.Vulkan
}
}
internal int FindSuitableMemoryTypeIndex(
uint memoryTypeBits,
MemoryPropertyFlags flags)
internal int FindSuitableMemoryTypeIndex(uint memoryTypeBits, MemoryPropertyFlags flags)
{
for (int i = 0; i < _physicalDevice.PhysicalDeviceMemoryProperties.MemoryTypeCount; i++)
{

View File

@@ -25,7 +25,7 @@ namespace Ryujinx.HLE.Generators
var name = GetFullName(className, context).Replace("global::", "");
if (!name.StartsWith("Ryujinx.HLE.HOS.Services"))
continue;
var constructors = className.ChildNodes().Where(x => x.IsKind(SyntaxKind.ConstructorDeclaration)).Select(y => y as ConstructorDeclarationSyntax);
var constructors = className.ChildNodes().Where(x => x.IsKind(SyntaxKind.ConstructorDeclaration)).Select(y => y as ConstructorDeclarationSyntax).ToArray();
if (!constructors.Any(x => x.ParameterList.Parameters.Count >= 1))
continue;

View File

@@ -588,21 +588,15 @@ namespace Ryujinx.HLE.FileSystem
// LibHac.NcaHeader's DecryptHeader doesn't check if HeaderKey is empty and throws InvalidDataException instead
// So, we check it early for a better user experience.
if (_virtualFileSystem.KeySet.HeaderKey.IsZeros())
{
throw new MissingKeyException("HeaderKey is empty. Cannot decrypt NCA headers.");
}
Dictionary<ulong, List<(NcaContentType type, string path)>> updateNcas = new();
if (Directory.Exists(firmwarePackage))
{
return VerifyAndGetVersionDirectory(firmwarePackage);
}
if (!File.Exists(firmwarePackage))
{
throw new FileNotFoundException("Firmware file does not exist.");
}
FileInfo info = new(firmwarePackage);
@@ -612,30 +606,22 @@ namespace Ryujinx.HLE.FileSystem
{
case ".zip":
using (ZipArchive archive = ZipFile.OpenRead(firmwarePackage))
{
return VerifyAndGetVersionZip(archive);
}
case ".xci":
Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage());
if (xci.HasPartition(XciPartitionType.Update))
{
XciPartition partition = xci.OpenPartition(XciPartitionType.Update);
return VerifyAndGetVersion(partition);
}
else
{
if (!xci.HasPartition(XciPartitionType.Update))
throw new InvalidFirmwarePackageException("Update not found in xci file.");
}
default:
break;
XciPartition partition = xci.OpenPartition(XciPartitionType.Update);
return VerifyAndGetVersion(partition);
}
SystemVersion VerifyAndGetVersionDirectory(string firmwareDirectory)
{
return VerifyAndGetVersion(new LocalFileSystem(firmwareDirectory));
}
return null;
SystemVersion VerifyAndGetVersionDirectory(string firmwareDirectory)
=> VerifyAndGetVersion(new LocalFileSystem(firmwareDirectory));
SystemVersion VerifyAndGetVersionZip(ZipArchive archive)
{
@@ -925,8 +911,6 @@ namespace Ryujinx.HLE.FileSystem
return systemVersion;
}
return null;
}
public SystemVersion GetCurrentFirmwareVersion()

View File

@@ -63,7 +63,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii
public CreateId MakeCreateId()
{
UInt128 value = UInt128Utils.CreateRandom();
UInt128 value = Random.Shared.NextUInt128();
// Ensure the random ID generated is valid as a create id.
value &= ~new UInt128(0xC0, 0);

View File

@@ -78,7 +78,7 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
NetworkProfileData networkProfile = new()
{
Uuid = UInt128Utils.CreateRandom(),
Uuid = Random.Shared.NextUInt128(),
};
networkProfile.IpSettingData.IpAddressSetting = new IpAddressSetting(interfaceProperties, unicastAddress);

View File

@@ -12,7 +12,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
public SteadyClockCore()
{
_clockSourceId = UInt128Utils.CreateRandom();
_clockSourceId = Random.Shared.NextUInt128();
_isRtcResetDetected = false;
_isInitialized = false;
}

View File

@@ -36,7 +36,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
return new SteadyClockTimePoint
{
TimePoint = 0,
ClockSourceId = UInt128Utils.CreateRandom(),
ClockSourceId = Random.Shared.NextUInt128(),
};
}
}

View File

@@ -27,7 +27,7 @@ namespace Ryujinx.HLE
public TamperMachine TamperMachine { get; }
public IHostUIHandler UIHandler { get; }
public bool EnableDeviceVsync { get; set; } = true;
public bool EnableDeviceVsync { get; set; }
public bool IsFrameAvailable => Gpu.Window.IsFrameAvailable;
@@ -68,36 +68,6 @@ namespace Ryujinx.HLE
#pragma warning restore IDE0055
}
public bool LoadCart(string exeFsDir, string romFsFile = null)
{
return Processes.LoadUnpackedNca(exeFsDir, romFsFile);
}
public bool LoadXci(string xciFile, ulong applicationId = 0)
{
return Processes.LoadXci(xciFile, applicationId);
}
public bool LoadNca(string ncaFile)
{
return Processes.LoadNca(ncaFile);
}
public bool LoadNsp(string nspFile, ulong applicationId = 0)
{
return Processes.LoadNsp(nspFile, applicationId);
}
public bool LoadProgram(string fileName)
{
return Processes.LoadNxo(fileName);
}
public bool WaitFifo()
{
return Gpu.GPFifo.WaitForCommands();
}
public void ProcessFrame()
{
Gpu.ProcessShaderCacheQueue();
@@ -105,40 +75,22 @@ namespace Ryujinx.HLE
Gpu.GPFifo.DispatchCalls();
}
public bool ConsumeFrameAvailable()
{
return Gpu.Window.ConsumeFrameAvailable();
}
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 LoadNsp(string nspFile, ulong applicationId = 0) => Processes.LoadNsp(nspFile, applicationId);
public bool LoadProgram(string fileName) => Processes.LoadNxo(fileName);
public void PresentFrame(Action swapBuffersCallback)
{
Gpu.Window.Present(swapBuffersCallback);
}
public void SetVolume(float volume) => AudioDeviceDriver.Volume = Math.Clamp(volume, 0f, 1f);
public float GetVolume() => AudioDeviceDriver.Volume;
public bool IsAudioMuted() => AudioDeviceDriver.Volume == 0;
public void SetVolume(float volume)
{
AudioDeviceDriver.Volume = Math.Clamp(volume, 0f, 1f);
}
public void EnableCheats() => ModLoader.EnableCheats(Processes.ActiveApplication.ProgramId, TamperMachine);
public float GetVolume()
{
return AudioDeviceDriver.Volume;
}
public void EnableCheats()
{
ModLoader.EnableCheats(Processes.ActiveApplication.ProgramId, TamperMachine);
}
public bool IsAudioMuted()
{
return AudioDeviceDriver.Volume == 0;
}
public void DisposeGpu()
{
Gpu.Dispose();
}
public bool WaitFifo() => Gpu.GPFifo.WaitForCommands();
public bool ConsumeFrameAvailable() => Gpu.Window.ConsumeFrameAvailable();
public void PresentFrame(Action swapBuffersCallback) => Gpu.Window.Present(swapBuffersCallback);
public void DisposeGpu() => Gpu.Dispose();
public void Dispose()
{

View File

@@ -17,11 +17,8 @@ namespace Ryujinx.Headless.SDL2
public bool TextProcessingEnabled
{
get
{
return Volatile.Read(ref _canProcessInput);
}
get => Volatile.Read(ref _canProcessInput);
set
{
Volatile.Write(ref _canProcessInput, value);

View File

@@ -2,23 +2,20 @@ using System;
namespace Ryujinx.Headless.SDL2
{
class StatusUpdatedEventArgs : EventArgs
class StatusUpdatedEventArgs(
bool vSyncEnabled,
string dockedMode,
string aspectRatio,
string gameStatus,
string fifoStatus,
string gpuName)
: EventArgs
{
public bool VSyncEnabled;
public string DockedMode;
public string AspectRatio;
public string GameStatus;
public string FifoStatus;
public string GpuName;
public StatusUpdatedEventArgs(bool vSyncEnabled, string dockedMode, string aspectRatio, string gameStatus, string fifoStatus, string gpuName)
{
VSyncEnabled = vSyncEnabled;
DockedMode = dockedMode;
AspectRatio = aspectRatio;
GameStatus = gameStatus;
FifoStatus = fifoStatus;
GpuName = gpuName;
}
public bool VSyncEnabled = vSyncEnabled;
public string DockedMode = dockedMode;
public string AspectRatio = aspectRatio;
public string GameStatus = gameStatus;
public string FifoStatus = fifoStatus;
public string GpuName = gpuName;
}
}

View File

@@ -10,6 +10,7 @@ using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationPr
using Ryujinx.HLE.UI;
using Ryujinx.Input;
using Ryujinx.Input.HLE;
using Ryujinx.Input.SDL2;
using Ryujinx.SDL2.Common;
using System;
using System.Collections.Concurrent;

View File

@@ -12,7 +12,10 @@ namespace Ryujinx.Input.SDL2
{
private bool HasConfiguration => _configuration != null;
private record struct ButtonMappingEntry(GamepadButtonInputId To, GamepadButtonInputId From);
private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, GamepadButtonInputId From)
{
public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not GamepadButtonInputId.Unbound;
}
private StandardControllerInputConfig _configuration;
@@ -144,86 +147,64 @@ namespace Ryujinx.Input.SDL2
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
{
if (Features.HasFlag(GamepadFeaturesFlag.Rumble))
{
ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue);
ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue);
if (!Features.HasFlag(GamepadFeaturesFlag.Rumble)) return;
if (durationMs == uint.MaxValue)
{
if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, SDL_HAPTIC_INFINITY) != 0)
{
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
}
}
else if (durationMs > SDL_HAPTIC_INFINITY)
{
Logger.Error?.Print(LogClass.Hid, $"Unsupported rumble duration {durationMs}");
}
else
{
if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs) != 0)
{
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
}
}
ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue);
ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue);
if (durationMs == uint.MaxValue)
{
if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, SDL_HAPTIC_INFINITY) != 0)
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
}
else if (durationMs > SDL_HAPTIC_INFINITY)
{
Logger.Error?.Print(LogClass.Hid, $"Unsupported rumble duration {durationMs}");
}
else
{
if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs) != 0)
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
}
}
public Vector3 GetMotionData(MotionInputId inputId)
{
SDL_SensorType sensorType = SDL_SensorType.SDL_SENSOR_INVALID;
if (inputId == MotionInputId.Accelerometer)
SDL_SensorType sensorType = inputId switch
{
sensorType = SDL_SensorType.SDL_SENSOR_ACCEL;
}
else if (inputId == MotionInputId.Gyroscope)
{
sensorType = SDL_SensorType.SDL_SENSOR_GYRO;
}
MotionInputId.Accelerometer => SDL_SensorType.SDL_SENSOR_ACCEL,
MotionInputId.Gyroscope => SDL_SensorType.SDL_SENSOR_GYRO,
_ => SDL_SensorType.SDL_SENSOR_INVALID
};
if (Features.HasFlag(GamepadFeaturesFlag.Motion) && sensorType != SDL_SensorType.SDL_SENSOR_INVALID)
{
const int ElementCount = 3;
if (!Features.HasFlag(GamepadFeaturesFlag.Motion) || sensorType is SDL_SensorType.SDL_SENSOR_INVALID)
return Vector3.Zero;
unsafe
const int ElementCount = 3;
unsafe
{
float* values = stackalloc float[ElementCount];
int result = SDL_GameControllerGetSensorData(_gamepadHandle, sensorType, (IntPtr)values, ElementCount);
if (result != 0)
return Vector3.Zero;
Vector3 value = new(values[0], values[1], values[2]);
return inputId switch
{
float* values = stackalloc float[ElementCount];
int result = SDL_GameControllerGetSensorData(_gamepadHandle, sensorType, (IntPtr)values, ElementCount);
if (result == 0)
{
Vector3 value = new(values[0], values[1], values[2]);
if (inputId == MotionInputId.Gyroscope)
{
return RadToDegree(value);
}
if (inputId == MotionInputId.Accelerometer)
{
return GsToMs2(value);
}
return value;
}
}
MotionInputId.Gyroscope => RadToDegree(value),
MotionInputId.Accelerometer => GsToMs2(value),
_ => value
};
}
return Vector3.Zero;
}
private static Vector3 RadToDegree(Vector3 rad)
{
return rad * (180 / MathF.PI);
}
private static Vector3 RadToDegree(Vector3 rad) => rad * (180 / MathF.PI);
private static Vector3 GsToMs2(Vector3 gs)
{
return gs / SDL_STANDARD_GRAVITY;
}
private static Vector3 GsToMs2(Vector3 gs) => gs / SDL_STANDARD_GRAVITY;
public void SetConfiguration(InputConfig configuration)
{
@@ -278,16 +259,13 @@ namespace Ryujinx.Input.SDL2
lock (_userMappingLock)
{
if (_buttonsUserMapping.Count == 0)
{
return rawState;
}
// ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
foreach (ButtonMappingEntry entry in _buttonsUserMapping)
{
if (entry.From == GamepadButtonInputId.Unbound || entry.To == GamepadButtonInputId.Unbound)
{
continue;
}
if (!entry.IsValid) continue;
// Do not touch state of button already pressed
if (!result.IsPressed(entry.To))
@@ -316,9 +294,8 @@ namespace Ryujinx.Input.SDL2
public (float, float) GetStick(StickInputId inputId)
{
if (inputId == StickInputId.Unbound)
{
return (0.0f, 0.0f);
}
(short stickX, short stickY) = GetStickXY(inputId);
@@ -351,6 +328,7 @@ namespace Ryujinx.Input.SDL2
return (resultX, resultY);
}
// ReSharper disable once InconsistentNaming
private (short, short) GetStickXY(StickInputId inputId) =>
inputId switch
{
@@ -365,14 +343,12 @@ namespace Ryujinx.Input.SDL2
public bool IsPressed(GamepadButtonInputId inputId)
{
if (inputId == GamepadButtonInputId.LeftTrigger)
switch (inputId)
{
return ConvertRawStickValue(SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT)) > _triggerThreshold;
}
if (inputId == GamepadButtonInputId.RightTrigger)
{
return ConvertRawStickValue(SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT)) > _triggerThreshold;
case GamepadButtonInputId.LeftTrigger:
return ConvertRawStickValue(SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT)) > _triggerThreshold;
case GamepadButtonInputId.RightTrigger:
return ConvertRawStickValue(SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT)) > _triggerThreshold;
}
if (_buttonsDriverMapping[(int)inputId] == SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID)

View File

@@ -12,16 +12,9 @@ namespace Ryujinx.Input.SDL2
{
class SDL2Keyboard : IKeyboard
{
private class ButtonMappingEntry
private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, Key From)
{
public readonly GamepadButtonInputId To;
public readonly Key From;
public ButtonMappingEntry(GamepadButtonInputId to, Key from)
{
To = to;
From = from;
}
public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not Key.Unbound;
}
private readonly object _userMappingLock = new();

View File

@@ -1,12 +1,11 @@
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Input;
using System;
using System.Drawing;
using System.Numerics;
namespace Ryujinx.Headless.SDL2
namespace Ryujinx.Input.SDL2
{
class SDL2Mouse : IMouse
public class SDL2Mouse : IMouse
{
private SDL2MouseDriver _driver;

View File

@@ -1,6 +1,5 @@
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Input;
using System;
using System.Diagnostics;
using System.Drawing;
@@ -8,9 +7,9 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using static SDL2.SDL;
namespace Ryujinx.Headless.SDL2
namespace Ryujinx.Input.SDL2
{
class SDL2MouseDriver : IGamepadDriver
public class SDL2MouseDriver : IGamepadDriver
{
private const int CursorHideIdleTime = 5; // seconds
@@ -44,7 +43,7 @@ namespace Ryujinx.Headless.SDL2
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static MouseButton DriverButtonToMouseButton(uint rawButton)
{
Debug.Assert(rawButton > 0 && rawButton <= (int)MouseButton.Count);
Debug.Assert(rawButton is > 0 and <= (int)MouseButton.Count);
return (MouseButton)(rawButton - 1);
}

View File

@@ -143,7 +143,7 @@ namespace Ryujinx.SDL2.Common
OnJoystickDisconnected?.Invoke(evnt.cbutton.which);
}
else if (evnt.type == SDL_EventType.SDL_WINDOWEVENT || evnt.type == SDL_EventType.SDL_MOUSEBUTTONDOWN || evnt.type == SDL_EventType.SDL_MOUSEBUTTONUP)
else if (evnt.type is SDL_EventType.SDL_WINDOWEVENT or SDL_EventType.SDL_MOUSEBUTTONDOWN or SDL_EventType.SDL_MOUSEBUTTONUP)
{
if (_registeredWindowHandlers.TryGetValue(evnt.window.windowID, out Action<SDL_Event> handler))
{

View File

@@ -19,6 +19,8 @@ namespace Ryujinx.UI.App.Common
{
public class ApplicationData
{
public static Func<string> LocalizedNever = () => "Never";
public bool Favorite { get; set; }
public byte[] Icon { get; set; }
public string Name { get; set; } = "Unknown";
@@ -34,7 +36,7 @@ namespace Ryujinx.UI.App.Common
public string TimePlayedString => ValueFormatUtils.FormatTimeSpan(TimePlayed);
public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed);
public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed) ?? LocalizedNever();
public string FileSizeString => ValueFormatUtils.FormatFileSize(FileSize);

View File

@@ -15,7 +15,7 @@ namespace Ryujinx.UI.Common.Configuration
/// <summary>
/// The current version of the file format
/// </summary>
public const int CurrentVersion = 52;
public const int CurrentVersion = 53;
/// <summary>
/// Version of the configuration file format
@@ -207,6 +207,11 @@ namespace Ryujinx.UI.Common.Configuration
/// </summary>
public bool EnablePtc { get; set; }
/// <summary>
/// Enables or disables low-power profiled translation cache persistency loading
/// </summary>
public bool EnableLowPowerPtc { get; set; }
/// <summary>
/// Enables or disables guest Internet access
/// </summary>

View File

@@ -1,3 +1,4 @@
using ARMeilleure;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
@@ -327,6 +328,11 @@ namespace Ryujinx.UI.Common.Configuration
/// </summary>
public ReactiveObject<bool> EnablePtc { get; private set; }
/// <summary>
/// Enables or disables low-power profiled translation cache persistency loading
/// </summary>
public ReactiveObject<bool> EnableLowPowerPtc { get; private set; }
/// <summary>
/// Enables or disables guest Internet access
/// </summary>
@@ -382,6 +388,8 @@ namespace Ryujinx.UI.Common.Configuration
EnableDockedMode.Event += static (sender, e) => LogValueChange(e, nameof(EnableDockedMode));
EnablePtc = new ReactiveObject<bool>();
EnablePtc.Event += static (sender, e) => LogValueChange(e, nameof(EnablePtc));
EnableLowPowerPtc = new ReactiveObject<bool>();
EnableLowPowerPtc.Event += static (sender, e) => LogValueChange(e, nameof(EnableLowPowerPtc));
EnableInternetAccess = new ReactiveObject<bool>();
EnableInternetAccess.Event += static (sender, e) => LogValueChange(e, nameof(EnableInternetAccess));
EnableFsIntegrityChecks = new ReactiveObject<bool>();
@@ -706,6 +714,7 @@ namespace Ryujinx.UI.Common.Configuration
EnableMacroHLE = Graphics.EnableMacroHLE,
EnableColorSpacePassthrough = Graphics.EnableColorSpacePassthrough,
EnablePtc = System.EnablePtc,
EnableLowPowerPtc = System.EnableLowPowerPtc,
EnableInternetAccess = System.EnableInternetAccess,
EnableFsIntegrityChecks = System.EnableFsIntegrityChecks,
FsGlobalAccessLogMode = System.FsGlobalAccessLogMode,
@@ -1495,6 +1504,15 @@ namespace Ryujinx.UI.Common.Configuration
configurationFileUpdated = true;
}
if (configurationFileFormat.Version < 53)
{
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 53.");
configurationFileFormat.EnableLowPowerPtc = false;
configurationFileUpdated = true;
}
Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog;
Graphics.ResScale.Value = configurationFileFormat.ResScale;
Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom;
@@ -1534,6 +1552,7 @@ namespace Ryujinx.UI.Common.Configuration
Graphics.EnableMacroHLE.Value = configurationFileFormat.EnableMacroHLE;
Graphics.EnableColorSpacePassthrough.Value = configurationFileFormat.EnableColorSpacePassthrough;
System.EnablePtc.Value = configurationFileFormat.EnablePtc;
System.EnableLowPowerPtc.Value = configurationFileFormat.EnableLowPowerPtc;
System.EnableInternetAccess.Value = configurationFileFormat.EnableInternetAccess;
System.EnableFsIntegrityChecks.Value = configurationFileFormat.EnableFsIntegrityChecks;
System.FsGlobalAccessLogMode.Value = configurationFileFormat.FsGlobalAccessLogMode;
@@ -1627,6 +1646,8 @@ namespace Ryujinx.UI.Common.Configuration
}
Instance = new ConfigurationState();
Instance.System.EnableLowPowerPtc.Event += (_, evnt) => Optimizations.LowPower = evnt.NewValue;
}
}
}

View File

@@ -1,6 +1,8 @@
using DiscordRPC;
using Humanizer;
using LibHac.Bcat;
using Ryujinx.Common;
using Ryujinx.HLE.Loaders.Processes;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common.Configuration;
using System.Collections.Generic;
@@ -13,7 +15,10 @@ namespace Ryujinx.UI.Common
{
public static Timestamps StartedAt { get; set; }
private const string Description = "A simple, experimental Nintendo Switch emulator.";
private static readonly string _description = ReleaseInformation.IsValid
? $"v{ReleaseInformation.Version} {ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}@{ReleaseInformation.BuildGitHash}"
: "dev build";
private const string ApplicationId = "1293250299716173864";
private const int ApplicationByteLimit = 128;
@@ -29,7 +34,7 @@ namespace Ryujinx.UI.Common
Assets = new Assets
{
LargeImageKey = "ryujinx",
LargeImageText = Description
LargeImageText = TruncateToByteLength(_description)
},
Details = "Main Menu",
State = "Idling",
@@ -62,19 +67,21 @@ namespace Ryujinx.UI.Common
}
}
public static void SwitchToPlayingState(string titleId, ApplicationMetadata appMeta)
public static void SwitchToPlayingState(ApplicationMetadata appMeta, ProcessResult procRes)
{
_discordClient?.SetPresence(new RichPresence
{
Assets = new Assets
{
LargeImageKey = _discordGameAssets.Contains(titleId.ToLower()) ? titleId : "game",
LargeImageText = TruncateToByteLength(appMeta.Title),
LargeImageKey = _discordGameAssetKeys.Contains(procRes.ProgramIdText.ToLower()) ? procRes.ProgramIdText : "game",
LargeImageText = TruncateToByteLength($"{appMeta.Title} | {procRes.DisplayVersion}"),
SmallImageKey = "ryujinx",
SmallImageText = Description
SmallImageText = TruncateToByteLength(_description)
},
Details = TruncateToByteLength($"Playing {appMeta.Title}"),
State = $"Total play time: {appMeta.TimePlayed.Humanize(2, false)}",
State = appMeta.LastPlayed.HasValue && appMeta.TimePlayed.TotalSeconds > 5
? $"Total play time: {appMeta.TimePlayed.Humanize(2, false)}"
: "Never played",
Timestamps = Timestamps.Now
});
}
@@ -111,11 +118,13 @@ namespace Ryujinx.UI.Common
_discordClient?.Dispose();
}
private static readonly string[] _discordGameAssets = [
private static readonly string[] _discordGameAssetKeys =
[
"01002da013484000", // The Legend of Zelda: Skyward Sword HD
"01007ef00011e000", // The Legend of Zelda: Breath of the Wild
"0100f2c0115b6000", // The Legend of Zelda: Tears of the Kingdom
"01008cf01baac000", // The Legend of Zelda: Echoes of Wisdom
"01006bb00c6f0000", // The Legend of Zelda: Link's Awakening
"0100000000010000", // SUPER MARIO ODYSSEY
"010015100b514000", // Super Mario Bros. Wonder
@@ -124,6 +133,9 @@ namespace Ryujinx.UI.Common
"010028600ebda000", // Super Mario 3D World + Bowser's Fury
"0100ecd018ebe000", // Paper Mario: The Thousand-Year Door
"010048701995e000", // Luigi's Mansion 2 HD
"0100dca0064a6000", // Luigi's Mansion 3
"01008f6008c5e000", // Pokémon Violet
"0100abf008968000", // Pokémon Sword
"01008db008c2c000", // Pokémon Shield
@@ -133,12 +145,16 @@ namespace Ryujinx.UI.Common
"0100aa80194b0000", // Pikmin 1
"0100d680194b2000", // Pikmin 2
"0100f4c009322000", // Pikmin 3 Deluxe
"0100b7c00933a000", // Pikmin
"0100b7c00933a000", // Pikmin 4
"0100c2500fc20000", // Splatoon 3
"0100ba0018500000", // Splatoon 3: Splatfest World Premiere
"01000a10041ea000", // The Elder Scrolls V: Skyrim
"01007820196a6000", // Red Dead Redemption
"0100744001588000", // Cars 3: Driven to Win
"01002b00111a2000", // Hyrule Warriors: Age of Calamity
"01006f8002326000", // Animal Crossing: New Horizons
"01004d300c5ae000", // Kirby and the Forgotten Land
"0100853015e86000", // No Man's Sky
"01008d100d43e000", // Saints Row IV
"0100de600beee000", // Saints Row: The Third - The Full Package

View File

@@ -75,12 +75,11 @@ namespace Ryujinx.UI.Common.Helper
return true;
}
catch (Exception) { }
catch
{
// ignored
}
}
outError = error;
return false;
}
}
@@ -96,19 +95,15 @@ namespace Ryujinx.UI.Common.Helper
string baseApplicationExtension = Path.GetExtension(baseApplicationPath).ToLowerInvariant();
// NOTE: We don't force homebrew developers to install a system firmware.
if (baseApplicationExtension == ".nro" || baseApplicationExtension == ".nso")
{
error = UserError.Success;
if (baseApplicationExtension is not (".nro" or ".nso"))
return IsFirmwareValid(contentManager, out error);
return true;
}
return IsFirmwareValid(contentManager, out error);
error = UserError.Success;
}
error = UserError.ApplicationNotFound;
return false;
return error is UserError.Success;
}
}
}

View File

@@ -140,11 +140,11 @@ namespace Ryujinx.UI.Common.Helper
argsList.Add($"\"{appFilePath}\"");
return String.Join(" ", argsList);
return string.Join(" ", argsList);
}
/// <summary>
/// Creates a Icon (.ico) file using the source bitmap image at the specified file path.
/// Creates an Icon (.ico) file using the source bitmap image at the specified file path.
/// </summary>
/// <param name="source">The source bitmap image that will be saved as an .ico file</param>
/// <param name="filePath">The location that the new .ico file will be saved too (Make sure to include '.ico' in the path).</param>

View File

@@ -8,10 +8,8 @@ namespace Ryujinx.UI.Common.Helper
public static string ActiveApplicationTitle(ProcessResult activeProcess, string applicationVersion, string pauseString = "")
{
if (activeProcess == null)
{
return String.Empty;
}
return string.Empty;
string titleNameSection = string.IsNullOrWhiteSpace(activeProcess.Name) ? string.Empty : $" {activeProcess.Name}";
string titleVersionSection = string.IsNullOrWhiteSpace(activeProcess.DisplayVersion) ? string.Empty : $" v{activeProcess.DisplayVersion}";
string titleIdSection = $" ({activeProcess.ProgramIdText.ToUpper()})";
@@ -19,12 +17,9 @@ namespace Ryujinx.UI.Common.Helper
string appTitle = $"Ryujinx {applicationVersion} -{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
if (!string.IsNullOrEmpty(pauseString))
{
appTitle += $" ({pauseString})";
}
return appTitle;
return !string.IsNullOrEmpty(pauseString)
? appTitle + $" ({pauseString})"
: appTitle;
}
}
}

View File

@@ -87,10 +87,7 @@ namespace Ryujinx.UI.Common.Helper
foreach (string path in titleUpdateMetadata.Paths)
{
if (!File.Exists(path))
{
continue;
}
if (!File.Exists(path)) continue;
try
{
@@ -99,22 +96,15 @@ namespace Ryujinx.UI.Common.Helper
Dictionary<ulong, ContentMetaData> updates =
pfs.GetContentData(ContentMetaType.Patch, vfs, checkLevel);
Nca patchNca = null;
Nca controlNca = null;
if (!updates.TryGetValue(applicationIdBase, out ContentMetaData content))
{
continue;
}
patchNca = content.GetNcaByType(vfs.KeySet, ContentType.Program);
controlNca = content.GetNcaByType(vfs.KeySet, ContentType.Control);
Nca patchNca = content.GetNcaByType(vfs.KeySet, ContentType.Program);
Nca controlNca = content.GetNcaByType(vfs.KeySet, ContentType.Control);
if (controlNca == null || patchNca == null)
{
if (controlNca is null || patchNca is null)
continue;
}
ApplicationControlProperty controlData = new();
using UniqueRef<IFile> nacpFile = new();
@@ -138,7 +128,7 @@ namespace Ryujinx.UI.Common.Helper
catch (InvalidDataException)
{
Logger.Warning?.Print(LogClass.Application,
$"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {path}");
$"The header key is incorrect or missing and therefore the NCA header content type check has failed. Malformed File: {path}");
}
catch (IOException exception)
{
@@ -154,9 +144,7 @@ namespace Ryujinx.UI.Common.Helper
return result;
}
private static string PathToGameUpdatesJson(ulong applicationIdBase)
{
return Path.Combine(AppDataManager.GamesDirPath, applicationIdBase.ToString("x16"), "updates.json");
}
private static string PathToGameUpdatesJson(ulong applicationIdBase)
=> Path.Combine(AppDataManager.GamesDirPath, applicationIdBase.ToString("x16"), "updates.json");
}
}

View File

@@ -75,13 +75,7 @@ namespace Ryujinx.UI.Common.Helper
{
culture ??= CultureInfo.CurrentCulture;
if (!utcDateTime.HasValue)
{
// In the Avalonia UI, this is turned into a localized version of "Never" by LocalizedNeverConverter.
return "Never";
}
return utcDateTime.Value.ToLocalTime().ToString(culture);
return utcDateTime?.ToLocalTime().ToString(culture);
}
/// <summary>

View File

@@ -19,9 +19,14 @@ namespace Ryujinx.Ava
{
public class App : Application
{
internal static string FormatTitle(LocaleKeys? windowTitleKey = null)
=> windowTitleKey is null
? $"Ryujinx {Program.Version}"
: $"Ryujinx {Program.Version} - {LocaleManager.Instance[windowTitleKey.Value]}";
public override void Initialize()
{
Name = $"Ryujinx {Program.Version}";
Name = FormatTitle();
AvaloniaXamlLoader.Load(this);
@@ -112,7 +117,7 @@ namespace Ryujinx.Ava
}
catch (Exception)
{
Logger.Warning?.Print(LogClass.Application, "Failed to Apply Theme. A restart is needed to apply the selected theme");
Logger.Warning?.Print(LogClass.Application, "Failed to apply theme. A restart is needed to apply the selected theme.");
ShowRestartDialog();
}

View File

@@ -367,12 +367,12 @@ namespace Ryujinx.Ava
}
var colorType = e.IsBgra ? SKColorType.Bgra8888 : SKColorType.Rgba8888;
using SKBitmap bitmap = new SKBitmap(new SKImageInfo(e.Width, e.Height, colorType, SKAlphaType.Premul));
using SKBitmap bitmap = new(new SKImageInfo(e.Width, e.Height, colorType, SKAlphaType.Premul));
Marshal.Copy(e.Data, 0, bitmap.GetPixels(), e.Data.Length);
using SKBitmap bitmapToSave = new SKBitmap(bitmap.Width, bitmap.Height);
using SKCanvas canvas = new SKCanvas(bitmapToSave);
using SKBitmap bitmapToSave = new(bitmap.Width, bitmap.Height);
using SKCanvas canvas = new(bitmapToSave);
canvas.Clear(SKColors.Black);
@@ -396,7 +396,7 @@ namespace Ryujinx.Ava
}
}
private void SaveBitmapAsPng(SKBitmap bitmap, string path)
private static void SaveBitmapAsPng(SKBitmap bitmap, string path)
{
using var data = bitmap.Encode(SKEncodedImageFormat.Png, 100);
using var stream = File.OpenWrite(path);
@@ -538,9 +538,8 @@ namespace Ryujinx.Ava
private void Dispose()
{
if (Device.Processes != null)
{
MainWindowViewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText);
}
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState;
@@ -785,12 +784,11 @@ namespace Ryujinx.Ava
return false;
}
ApplicationMetadata appMeta = ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata =>
{
appMetadata.UpdatePreGame();
});
ApplicationMetadata appMeta = ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText,
appMetadata => appMetadata.UpdatePreGame()
);
DiscordIntegrationModule.SwitchToPlayingState(Device.Processes.ActiveApplication.ProgramIdText, appMeta);
DiscordIntegrationModule.SwitchToPlayingState(appMeta, Device.Processes.ActiveApplication);
return true;
}

View File

@@ -136,6 +136,7 @@
"SettingsTabSystemSystemTime": "توقيت النظام:",
"SettingsTabSystemEnableVsync": "VSync",
"SettingsTabSystemEnablePptc": "PPTC (ذاكرة التخزين المؤقت للترجمة المستمرة)",
"SettingsTabSystemEnableLowPowerPptc": "Low-power PPTC",
"SettingsTabSystemEnableFsIntegrityChecks": "التحقق من سلامة نظام الملفات",
"SettingsTabSystemAudioBackend": "خلفية الصوت:",
"SettingsTabSystemAudioBackendDummy": "زائف",
@@ -566,6 +567,7 @@
"TimeTooltip": "تغيير وقت النظام",
"VSyncToggleTooltip": "محاكاة المزامنة العمودية للجهاز. في الأساس محدد الإطار لغالبية الألعاب؛ قد يؤدي تعطيله إلى تشغيل الألعاب بسرعة أعلى أو جعل شاشات التحميل تستغرق وقتا أطول أو تتعطل.\n\nيمكن تبديله داخل اللعبة باستخدام مفتاح التشغيل السريع الذي تفضله (F1 افتراضيا). نوصي بالقيام بذلك إذا كنت تخطط لتعطيله.\n\nاتركه ممكنا إذا لم تكن متأكدا.",
"PptcToggleTooltip": "يحفظ وظائف JIT المترجمة بحيث لا تحتاج إلى ترجمتها في كل مرة يتم فيها تحميل اللعبة.\n\nيقلل من التقطيع ويسرع بشكل ملحوظ أوقات التشغيل بعد التشغيل الأول للعبة.\n\nاتركه ممكنا إذا لم تكن متأكدا.",
"LowPowerPptcToggleTooltip": "Load the PPTC using a third of the amount of cores.",
"FsIntegrityToggleTooltip": "يتحقق من وجود ملفات تالفة عند تشغيل لعبة ما، وإذا تم اكتشاف ملفات تالفة، فسيتم عرض خطأ تجزئة في السجل.\n\nليس له أي تأثير على الأداء ويهدف إلى المساعدة في استكشاف الأخطاء وإصلاحها.\n\nاتركه مفعلا إذا كنت غير متأكد.",
"AudioBackendTooltip": "يغير الواجهة الخلفية المستخدمة لتقديم الصوت.\n\nSDL2 هو الخيار المفضل، بينما يتم استخدام OpenAL وSoundIO كبديلين. زائف لن يكون لها صوت.\n\nاضبط على SDL2 إذا لم تكن متأكدا.",
"MemoryManagerTooltip": "تغيير كيفية تعيين ذاكرة الضيف والوصول إليها. يؤثر بشكل كبير على أداء وحدة المعالجة المركزية التي تمت محاكاتها.\n\nاضبط على المضيف غير محدد إذا لم تكن متأكدا.",

View File

@@ -136,6 +136,7 @@
"SettingsTabSystemSystemTime": "Systemzeit:",
"SettingsTabSystemEnableVsync": "VSync",
"SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)",
"SettingsTabSystemEnableLowPowerPptc": "Kleinleistungs-PPTC",
"SettingsTabSystemEnableFsIntegrityChecks": "FS Integritätsprüfung",
"SettingsTabSystemAudioBackend": "Audio-Backend:",
"SettingsTabSystemAudioBackendDummy": "Ohne Funktion",
@@ -566,6 +567,7 @@
"TimeTooltip": "Ändert die Systemzeit",
"VSyncToggleTooltip": "Vertikale Synchronisierung der emulierten Konsole. Diese Option ist quasi ein Frame-Limiter für die meisten Spiele; die Deaktivierung kann dazu führen, dass Spiele mit höherer Geschwindigkeit laufen oder Ladebildschirme länger benötigen/hängen bleiben.\n\nKann beim Spielen mit einem frei wählbaren Hotkey ein- und ausgeschaltet werden (standardmäßig F1). \n\nIm Zweifelsfall AN lassen.",
"PptcToggleTooltip": "Speichert übersetzte JIT-Funktionen, sodass jene nicht jedes Mal übersetzt werden müssen, wenn das Spiel geladen wird.\n\nVerringert Stottern und die Zeit beim zweiten und den darauffolgenden Startvorgängen eines Spiels erheblich.\n\nIm Zweifelsfall AN lassen.",
"LowPowerPptcToggleTooltip": "Lädt den PPTC mit einem Drittel der verfügbaren Prozessorkernen",
"FsIntegrityToggleTooltip": "Prüft beim Startvorgang auf beschädigte Dateien und zeigt bei beschädigten Dateien einen Hash-Fehler (Hash Error) im Log an.\n\nDiese Einstellung hat keinen Einfluss auf die Leistung und hilft bei der Fehlersuche.\n\nIm Zweifelsfall AN lassen.",
"AudioBackendTooltip": "Ändert das Backend, das zum Rendern von Audio verwendet wird.\n\nSDL2 ist das bevorzugte Audio-Backend, OpenAL und SoundIO sind als Alternativen vorhanden. Dummy wird keinen Audio-Output haben.\n\nIm Zweifelsfall SDL2 auswählen.",
"MemoryManagerTooltip": "Ändert wie der Gastspeicher abgebildet wird und wie auf ihn zugegriffen wird. Beinflusst die Leistung der emulierten CPU erheblich.\n\nIm Zweifelsfall Host ungeprüft auswählen.",

View File

@@ -136,6 +136,7 @@
"SettingsTabSystemSystemTime": "Ώρα Συστήματος:",
"SettingsTabSystemEnableVsync": "Ενεργοποίηση Κατακόρυφου Συγχρονισμού",
"SettingsTabSystemEnablePptc": "Ενεργοποίηση PPTC (Profiled Persistent Translation Cache)",
"SettingsTabSystemEnableLowPowerPptc": "Low-power PPTC",
"SettingsTabSystemEnableFsIntegrityChecks": "Ενεργοποίηση Ελέγχων Ακεραιότητας FS",
"SettingsTabSystemAudioBackend": "Backend Ήχου:",
"SettingsTabSystemAudioBackendDummy": "Απενεργοποιημένο",
@@ -566,6 +567,7 @@
"TimeTooltip": "Αλλαγή Ώρας Συστήματος",
"VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.",
"PptcToggleTooltip": "Ενεργοποιεί ή απενεργοποιεί το PPTC",
"LowPowerPptcToggleTooltip": "Load the PPTC using a third of the amount of cores.",
"FsIntegrityToggleTooltip": "Ενεργοποιεί τους ελέγχους ακεραιότητας σε αρχεία περιεχομένου παιχνιδιού",
"AudioBackendTooltip": "Αλλαγή ήχου υποστήριξης",
"MemoryManagerTooltip": "Αλλάξτε τον τρόπο αντιστοίχισης και πρόσβασης στη μνήμη επισκέπτη. Επηρεάζει σε μεγάλο βαθμό την απόδοση της προσομοίωσης της CPU.",

View File

@@ -140,6 +140,7 @@
"SettingsTabSystemSystemTime": "System Time:",
"SettingsTabSystemEnableVsync": "VSync",
"SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)",
"SettingsTabSystemEnableLowPowerPptc": "Low-power PPTC cache",
"SettingsTabSystemEnableFsIntegrityChecks": "FS Integrity Checks",
"SettingsTabSystemAudioBackend": "Audio Backend:",
"SettingsTabSystemAudioBackendDummy": "Dummy",
@@ -574,6 +575,7 @@
"TimeTooltip": "Change System Time",
"VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.",
"PptcToggleTooltip": "Saves translated JIT functions so that they do not need to be translated every time the game loads.\n\nReduces stuttering and significantly speeds up boot times after the first boot of a game.\n\nLeave ON if unsure.",
"LowPowerPptcToggleTooltip": "Load the PPTC using a third of the amount of cores.",
"FsIntegrityToggleTooltip": "Checks for corrupt files when booting a game, and if corrupt files are detected, displays a hash error in the log.\n\nHas no impact on performance and is meant to help troubleshooting.\n\nLeave ON if unsure.",
"AudioBackendTooltip": "Changes the backend used to render audio.\n\nSDL2 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound.\n\nSet to SDL2 if unsure.",
"MemoryManagerTooltip": "Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance.\n\nSet to HOST UNCHECKED if unsure.",

View File

@@ -136,6 +136,7 @@
"SettingsTabSystemSystemTime": "Hora del sistema:",
"SettingsTabSystemEnableVsync": "Sincronización vertical",
"SettingsTabSystemEnablePptc": "PPTC (Cache de Traducción de Perfil Persistente)",
"SettingsTabSystemEnableLowPowerPptc": "Low-power PPTC",
"SettingsTabSystemEnableFsIntegrityChecks": "Comprobar integridad de los archivos",
"SettingsTabSystemAudioBackend": "Motor de audio:",
"SettingsTabSystemAudioBackendDummy": "Vacio",
@@ -566,6 +567,7 @@
"TimeTooltip": "Cambia la hora del sistema",
"VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.",
"PptcToggleTooltip": "Guarda funciones de JIT traducidas para que no sea necesario traducirlas cada vez que el juego carga.\n\nReduce los tirones y acelera significativamente el tiempo de inicio de los juegos después de haberlos ejecutado al menos una vez.\n\nActívalo si no sabes qué hacer.",
"LowPowerPptcToggleTooltip": "Load the PPTC using a third of the amount of cores.",
"FsIntegrityToggleTooltip": "Comprueba si hay archivos corruptos en los juegos que ejecutes al abrirlos, y si detecta archivos corruptos, muestra un error de Hash en los registros.\n\nEsto no tiene impacto alguno en el rendimiento y está pensado para ayudar a resolver problemas.\n\nActívalo si no sabes qué hacer.",
"AudioBackendTooltip": "Cambia el motor usado para renderizar audio.\n\nSDL2 es el preferido, mientras que OpenAL y SoundIO se usan si hay problemas con este. Dummy no produce audio.\n\nSelecciona SDL2 si no sabes qué hacer.",
"MemoryManagerTooltip": "Cambia la forma de mapear y acceder a la memoria del guest. Afecta en gran medida al rendimiento de la CPU emulada.\n\nSelecciona \"Host sin verificación\" si no sabes qué hacer.",

View File

@@ -136,6 +136,7 @@
"SettingsTabSystemSystemTime": "Heure du système:",
"SettingsTabSystemEnableVsync": "Synchronisation verticale (VSync)",
"SettingsTabSystemEnablePptc": "Activer le PPTC (Profiled Persistent Translation Cache)",
"SettingsTabSystemEnableLowPowerPptc": "Low-power PPTC",
"SettingsTabSystemEnableFsIntegrityChecks": "Activer la vérification de l'intégrité du système de fichiers",
"SettingsTabSystemAudioBackend": "Bibliothèque Audio :",
"SettingsTabSystemAudioBackendDummy": "Factice",
@@ -566,6 +567,7 @@
"TimeTooltip": "Changer l'heure du système",
"VSyncToggleTooltip": "La synchronisation verticale de la console émulée. Essentiellement un limiteur de trame pour la majorité des jeux ; le désactiver peut entraîner un fonctionnement plus rapide des jeux ou prolonger ou bloquer les écrans de chargement.\n\nPeut être activé ou désactivé en jeu avec un raccourci clavier de votre choix (F1 par défaut). Nous recommandons de le faire si vous envisagez de le désactiver.\n\nLaissez activé si vous n'êtes pas sûr.",
"PptcToggleTooltip": "Sauvegarde les fonctions JIT afin qu'elles n'aient pas besoin d'être à chaque fois recompiler lorsque le jeu se charge.\n\nRéduit les lags et accélère considérablement le temps de chargement après le premier lancement d'un jeu.\n\nLaissez par défaut si vous n'êtes pas sûr.",
"LowPowerPptcToggleTooltip": "Load the PPTC using a third of the amount of cores.",
"FsIntegrityToggleTooltip": "Vérifie si des fichiers sont corrompus lors du lancement d'un jeu, et si des fichiers corrompus sont détectés, affiche une erreur de hachage dans la console.\n\nN'a aucun impact sur les performances et est destiné à aider le dépannage.\n\nLaissez activer en cas d'incertitude.",
"AudioBackendTooltip": "Modifie le backend utilisé pour donnée un rendu audio.\n\nSDL2 est préféré, tandis que OpenAL et SoundIO sont utilisés comme backend secondaire. Le backend Dummy (Factice) ne rends aucun son.\n\nLaissez sur SDL2 si vous n'êtes pas sûr.",
"MemoryManagerTooltip": "Change la façon dont la mémoire émulée est mappée et utiliser. Cela affecte grandement les performances du processeur.\n\nRéglez sur Host Uncheked en cas d'incertitude.",

View File

@@ -136,6 +136,7 @@
"SettingsTabSystemSystemTime": "זמן מערכת:",
"SettingsTabSystemEnableVsync": "VSync",
"SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)",
"SettingsTabSystemEnableLowPowerPptc": "Low-power PPTC",
"SettingsTabSystemEnableFsIntegrityChecks": "FS בדיקות תקינות",
"SettingsTabSystemAudioBackend": "אחראי שמע:",
"SettingsTabSystemAudioBackendDummy": "גולם",
@@ -566,6 +567,7 @@
"TimeTooltip": "שנה זמן מערכת",
"VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.",
"PptcToggleTooltip": "שומר את פונקציות ה-JIT המתורגמות כך שלא יצטרכו לעבור תרגום שוב כאשר משחק עולה.\n\nמפחית תקיעות ומשפר מהירות עלייה של המערכת אחרי הפתיחה הראשונה של המשחק.\n\nמוטב להשאיר דלוק אם לא בטוחים.",
"LowPowerPptcToggleTooltip": "Load the PPTC using a third of the amount of cores.",
"FsIntegrityToggleTooltip": "בודק לקבצים שגויים כאשר משחק עולה, ואם מתגלים כאלו, מציג את מזהה השגיאה שלהם לקובץ הלוג.\n\nאין לכך השפעה על הביצועים ונועד לעזור לבדיקה וניפוי שגיאות של האמולטור.\n\nמוטב להשאיר דלוק אם לא בטוחים.",
"AudioBackendTooltip": "משנה את אחראי השמע.\n\nSDL2 הוא הנבחר, למראת שOpenAL וגם SoundIO משומשים כאפשרויות חלופיות. אפשרות הDummy לא תשמיע קול כלל.\n\nמוטב להשאיר על SDL2 אם לא בטוחים.",
"MemoryManagerTooltip": "שנה איך שזיכרון מארח מיוחד ומונגד. משפיע מאוד על ביצועי המעבד המדומה.\n\nמוטב להשאיר על מארח לא מבוקר אם לא בטוחים.",

View File

@@ -136,6 +136,7 @@
"SettingsTabSystemSystemTime": "Data e ora del sistema:",
"SettingsTabSystemEnableVsync": "Attiva VSync",
"SettingsTabSystemEnablePptc": "Attiva PPTC (Profiled Persistent Translation Cache)",
"SettingsTabSystemEnableLowPowerPptc": "Low-power PPTC",
"SettingsTabSystemEnableFsIntegrityChecks": "Attiva controlli d'integrità FS",
"SettingsTabSystemAudioBackend": "Backend audio:",
"SettingsTabSystemAudioBackendDummy": "Dummy",
@@ -566,6 +567,7 @@
"TimeTooltip": "Cambia data e ora di sistema",
"VSyncToggleTooltip": "Sincronizzazione verticale della console Emulata. Essenzialmente un limitatore di frame per la maggior parte dei giochi; disabilitarlo può far girare giochi a velocità più alta, allungare le schermate di caricamento o farle bloccare.\n\nPuò essere attivata in gioco con un tasto di scelta rapida (F1 per impostazione predefinita). Ti consigliamo di farlo se hai intenzione di disabilitarlo.\n\nLascia ON se non sei sicuro.",
"PptcToggleTooltip": "Salva le funzioni JIT tradotte in modo che non debbano essere tradotte tutte le volte che si avvia un determinato gioco.\n\nRiduce i fenomeni di stuttering e velocizza sensibilmente gli avvii successivi del gioco.\n\nNel dubbio, lascia l'opzione attiva.",
"LowPowerPptcToggleTooltip": "Load the PPTC using a third of the amount of cores.",
"FsIntegrityToggleTooltip": "Controlla la presenza di file corrotti quando si avvia un gioco. Se vengono rilevati dei file corrotti, verrà mostrato un errore di hash nel log.\n\nQuesta opzione non influisce sulle prestazioni ed è pensata per facilitare la risoluzione dei problemi.\n\nNel dubbio, lascia l'opzione attiva.",
"AudioBackendTooltip": "Cambia il backend usato per riprodurre l'audio.\n\nSDL2 è quello preferito, mentre OpenAL e SoundIO sono usati come ripiego. Dummy non riprodurrà alcun suono.\n\nNel dubbio, imposta l'opzione su SDL2.",
"MemoryManagerTooltip": "Cambia il modo in cui la memoria guest è mappata e vi si accede. Influisce notevolmente sulle prestazioni della CPU emulata.\n\nNel dubbio, imposta l'opzione su Host Unchecked.",

View File

@@ -136,6 +136,7 @@
"SettingsTabSystemSystemTime": "時刻:",
"SettingsTabSystemEnableVsync": "VSync",
"SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)",
"SettingsTabSystemEnableLowPowerPptc": "Low-power PPTC",
"SettingsTabSystemEnableFsIntegrityChecks": "ファイルシステム整合性チェック",
"SettingsTabSystemAudioBackend": "音声バックエンド:",
"SettingsTabSystemAudioBackendDummy": "ダミー",
@@ -566,6 +567,7 @@
"TimeTooltip": "システムの時刻を変更します",
"VSyncToggleTooltip": "エミュレートされたゲーム機の垂直同期です. 多くのゲームにおいて, フレームリミッタとして機能します. 無効にすると, ゲームが高速で実行されたり, ロード中に時間がかかったり, 止まったりすることがあります.\n\n設定したホットキー(デフォルトではF1)で, ゲーム内で切り替え可能です. 無効にする場合は, この操作を行うことをおすすめします.\n\nよくわからない場合はオンのままにしてください.",
"PptcToggleTooltip": "翻訳されたJIT関数をセーブすることで, ゲームをロードするたびに毎回翻訳する処理を不要とします.\n\n一度ゲームを起動すれば,二度目以降の起動時遅延を大きく軽減できます.\n\nよくわからない場合はオンのままにしてください.",
"LowPowerPptcToggleTooltip": "Load the PPTC using a third of the amount of cores.",
"FsIntegrityToggleTooltip": "ゲーム起動時にファイル破損をチェックし,破損が検出されたらログにハッシュエラーを表示します..\n\nパフォーマンスには影響なく, トラブルシューティングに役立ちます.\n\nよくわからない場合はオンのままにしてください.",
"AudioBackendTooltip": "音声レンダリングに使用するバックエンドを変更します.\n\nSDL2 が優先され, OpenAL と SoundIO はフォールバックとして使用されます. ダミーは音声出力しません.\n\nよくわからない場合は SDL2 を設定してください.",
"MemoryManagerTooltip": "ゲストメモリのマップ/アクセス方式を変更します. エミュレートされるCPUのパフォーマンスに大きな影響を与えます.\n\nよくわからない場合は「ホスト,チェックなし」を設定してください.",

View File

@@ -136,6 +136,7 @@
"SettingsTabSystemSystemTime": "시스템 시간:",
"SettingsTabSystemEnableVsync": "수직 동기화",
"SettingsTabSystemEnablePptc": "PPTC(프로파일된 영구 번역 캐시)",
"SettingsTabSystemEnableLowPowerPptc": "Low-power PPTC",
"SettingsTabSystemEnableFsIntegrityChecks": "파일 시스템 무결성 검사",
"SettingsTabSystemAudioBackend": "음향 후단부 :",
"SettingsTabSystemAudioBackendDummy": "더미",
@@ -566,6 +567,7 @@
"TimeTooltip": "시스템 시간 변경",
"VSyncToggleTooltip": "에뮬레이트된 콘솔의 수직 동기화. 기본적으로 대부분의 게임에 대한 프레임 제한 장치로, 비활성화시 게임이 더 빠른 속도로 실행되거나 로딩 화면이 더 오래 걸리거나 멈출 수 있습니다.\n\n게임 내에서 선호하는 핫키로 전환할 수 있습니다(기본값 F1). 핫키를 비활성화할 계획이라면 이 작업을 수행하는 것이 좋습니다.\n\n이 옵션에 대해 잘 모른다면 켜기를 권장드립니다.",
"PptcToggleTooltip": "게임이 불러올 때마다 번역할 필요가 없도록 번역된 JIT 기능을 저장합니다.\n\n게임을 처음 부팅한 후 끊김 현상을 줄이고 부팅 시간을 크게 단축합니다.\n\n확실하지 않으면 켜 두세요.",
"LowPowerPptcToggleTooltip": "Load the PPTC using a third of the amount of cores.",
"FsIntegrityToggleTooltip": "게임을 부팅할 때 손상된 파일을 확인하고 손상된 파일이 감지되면 로그에 해시 오류를 표시합니다.\n\n성능에 영향을 미치지 않으며 문제 해결에 도움이 됩니다.\n\n확실하지 않으면 켜 두세요.",
"AudioBackendTooltip": "오디오를 렌더링하는 데 사용되는 백엔드를 변경합니다.\n\nSDL2가 선호되는 반면 OpenAL 및 사운드IO는 폴백으로 사용됩니다. 더미는 소리가 나지 않습니다.\n\n확실하지 않으면 SDL2로 설정하세요.",
"MemoryManagerTooltip": "게스트 메모리가 매핑되고 접속되는 방식을 변경합니다. 에뮬레이트된 CPU 성능에 크게 영향을 미칩니다.\n\n확실하지 않은 경우 호스트 확인 안함으로 설정하세요.",

View File

@@ -136,6 +136,7 @@
"SettingsTabSystemSystemTime": "Czas systemu:",
"SettingsTabSystemEnableVsync": "Synchronizacja pionowa",
"SettingsTabSystemEnablePptc": "PPTC (Profilowana pamięć podręczna trwałych łłumaczeń)",
"SettingsTabSystemEnableLowPowerPptc": "Low-power PPTC",
"SettingsTabSystemEnableFsIntegrityChecks": "Sprawdzanie integralności systemu plików",
"SettingsTabSystemAudioBackend": "Backend Dżwięku:",
"SettingsTabSystemAudioBackendDummy": "Atrapa",
@@ -566,6 +567,7 @@
"TimeTooltip": "Zmień czas systemowy",
"VSyncToggleTooltip": "Synchronizacja pionowa emulowanej konsoli. Zasadniczo ogranicznik klatek dla większości gier; wyłączenie jej może spowodować, że gry będą działać z większą szybkością, ekrany wczytywania wydłużą się lub nawet utkną.\n\nMoże być przełączana w grze za pomocą preferowanego skrótu klawiszowego. Zalecamy to zrobić, jeśli planujesz ją wyłączyć.\n\nW razie wątpliwości pozostaw WŁĄCZONĄ.",
"PptcToggleTooltip": "Zapisuje przetłumaczone funkcje JIT, dzięki czemu nie muszą być tłumaczone za każdym razem, gdy gra się ładuje.\n\nZmniejsza zacinanie się i znacznie przyspiesza uruchamianie po pierwszym uruchomieniu gry.\n\nJeśli nie masz pewności, pozostaw WŁĄCZONE",
"LowPowerPptcToggleTooltip": "Load the PPTC using a third of the amount of cores.",
"FsIntegrityToggleTooltip": "Sprawdza pliki podczas uruchamiania gry i jeśli zostaną wykryte uszkodzone pliki, wyświetla w dzienniku błąd hash.\n\nNie ma wpływu na wydajność i ma pomóc w rozwiązywaniu problemów.\n\nPozostaw WŁĄCZONE, jeśli nie masz pewności.",
"AudioBackendTooltip": "Zmienia backend używany do renderowania dźwięku.\n\nSDL2 jest preferowany, podczas gdy OpenAL i SoundIO są używane jako rezerwy. Dummy nie będzie odtwarzać dźwięku.\n\nW razie wątpliwości ustaw SDL2.",
"MemoryManagerTooltip": "Zmień sposób mapowania i uzyskiwania dostępu do pamięci gości. Znacznie wpływa na wydajność emulowanego procesora.\n\nUstaw na HOST UNCHECKED, jeśli nie masz pewności.",

View File

@@ -136,6 +136,7 @@
"SettingsTabSystemSystemTime": "Hora do sistema:",
"SettingsTabSystemEnableVsync": "Habilitar sincronia vertical",
"SettingsTabSystemEnablePptc": "Habilitar PPTC (Profiled Persistent Translation Cache)",
"SettingsTabSystemEnableLowPowerPptc": "Low-power PPTC",
"SettingsTabSystemEnableFsIntegrityChecks": "Habilitar verificação de integridade do sistema de arquivos",
"SettingsTabSystemAudioBackend": "Biblioteca de saída de áudio:",
"SettingsTabSystemAudioBackendDummy": "Nenhuma",
@@ -566,6 +567,7 @@
"TimeTooltip": "Mudar a hora do sistema",
"VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.",
"PptcToggleTooltip": "Habilita ou desabilita PPTC",
"LowPowerPptcToggleTooltip": "Load the PPTC using a third of the amount of cores.",
"FsIntegrityToggleTooltip": "Habilita ou desabilita verificação de integridade dos arquivos do jogo",
"AudioBackendTooltip": "Mudar biblioteca de áudio",
"MemoryManagerTooltip": "Muda como a memória do sistema convidado é acessada. Tem um grande impacto na performance da CPU emulada.",

View File

@@ -136,6 +136,7 @@
"SettingsTabSystemSystemTime": "Системное время в прошивке:",
"SettingsTabSystemEnableVsync": "Вертикальная синхронизация",
"SettingsTabSystemEnablePptc": "Использовать PPTC (Profiled Persistent Translation Cache)",
"SettingsTabSystemEnableLowPowerPptc": "Low-power PPTC",
"SettingsTabSystemEnableFsIntegrityChecks": "Проверка целостности файловой системы",
"SettingsTabSystemAudioBackend": "Аудио бэкенд:",
"SettingsTabSystemAudioBackendDummy": "Без звука",
@@ -566,6 +567,7 @@
"TimeTooltip": "Меняет системное время прошивки",
"VSyncToggleTooltip": "Эмуляция вертикальной синхронизации консоли, которая ограничивает количество кадров в секунду в большинстве игр; отключение может привести к тому, что игры будут запущены с более высокой частотой кадров, но загрузка игры может занять больше времени, либо игра не запустится вообще.\n\nМожно включать и выключать эту настройку непосредственно в игре с помощью горячих клавиш (F1 по умолчанию). Если планируете отключить вертикальную синхронизацию, рекомендуем настроить горячие клавиши.\n\nРекомендуется оставить включенным.",
"PptcToggleTooltip": "Сохраняет скомпилированные JIT-функции для того, чтобы не преобразовывать их по новой каждый раз при запуске игры.\n\nУменьшает статтеры и значительно ускоряет последующую загрузку игр.\n\nРекомендуется оставить включенным.",
"LowPowerPptcToggleTooltip": "Load the PPTC using a third of the amount of cores.",
"FsIntegrityToggleTooltip": "Проверяет файлы при загрузке игры и если обнаружены поврежденные файлы, выводит сообщение о поврежденном хэше в журнале.\n\nНе влияет на производительность и необходим для помощи в устранении неполадок.\n\nРекомендуется оставить включенным.",
"AudioBackendTooltip": "Изменяет используемый аудио бэкенд для рендера звука.\n\nSDL2 является предпочтительным вариантом, в то время как OpenAL и SoundIO используются в качестве резервных.\n\nРекомендуется использование SDL2.",
"MemoryManagerTooltip": "Меняет разметку и доступ к гостевой памяти. Значительно влияет на производительность процессора.\n\nРекомендуется оставить \"Хост не установлен\"",

View File

@@ -136,6 +136,7 @@
"SettingsTabSystemSystemTime": "เวลาของระบบ:",
"SettingsTabSystemEnableVsync": "VSync",
"SettingsTabSystemEnablePptc": "PPTC (แคชโปรไฟล์การแปลแบบถาวร)",
"SettingsTabSystemEnableLowPowerPptc": "Low-power PPTC",
"SettingsTabSystemEnableFsIntegrityChecks": "ตรวจสอบความถูกต้องของ FS",
"SettingsTabSystemAudioBackend": "ระบบเสียงเบื้องหลัง:",
"SettingsTabSystemAudioBackendDummy": "Dummy",
@@ -566,6 +567,7 @@
"TimeTooltip": "เปลี่ยนเวลาของระบบ",
"VSyncToggleTooltip": "Vertical Sync ของคอนโซลจำลอง โดยพื้นฐานแล้วเป็นตัวจำกัดเฟรมสำหรับเกมส่วนใหญ่ การปิดใช้งานอาจทำให้เกมทำงานด้วยความเร็วสูงขึ้น หรือทำให้หน้าจอการโหลดใช้เวลานานขึ้นหรือค้าง\n\nสามารถสลับได้ในเกมด้วยปุ่มลัดตามที่คุณต้องการ (F1 เป็นค่าเริ่มต้น) เราขอแนะนำให้ทำเช่นนี้หากคุณวางแผนที่จะปิดการใช้งาน\n\nหากคุณไม่แน่ใจให้ปล่อยไว้อย่างนั้น",
"PptcToggleTooltip": "บันทึกฟังก์ชั่น JIT ที่แปลแล้ว ดังนั้นจึงไม่จำเป็นต้องแปลทุกครั้งที่โหลดเกม\n\nลดอาการกระตุกและเร่งความเร็วการบูตได้อย่างมากหลังจากการบูตครั้งแรกของเกม\n\nปล่อยไว้หากคุณไม่แน่ใจ",
"LowPowerPptcToggleTooltip": "Load the PPTC using a third of the amount of cores.",
"FsIntegrityToggleTooltip": "ตรวจสอบไฟล์ที่เสียหายเมื่อบูตเกม และหากตรวจพบไฟล์ที่เสียหาย จะแสดงข้อผิดพลาดของแฮชในบันทึก\n\nไม่มีผลกระทบต่อประสิทธิภาพการทำงานและมีไว้เพื่อช่วยในการแก้ไขปัญหา\n\nปล่อยไว้หากคุณไม่แน่ใจ",
"AudioBackendTooltip": "เปลี่ยนแบ็กเอนด์ที่ใช้ในการเรนเดอร์เสียง\n\nSDL2 เป็นที่ต้องการ ในขณะที่ OpenAL และ SoundIO ถูกใช้เป็นทางเลือกสำรอง ดัมมี่จะไม่มีเสียง\n\nปล่อยไว้หากคุณไม่แน่ใจ",
"MemoryManagerTooltip": "เปลี่ยนวิธีการแมปและเข้าถึงหน่วยความจำของผู้เยี่ยมชม ส่งผลอย่างมากต่อประสิทธิภาพการทำงานของ CPU ที่จำลอง\n\nตั้งค่าเป็น ไม่ทำการตรวจสอบ โฮสต์ หากคุณไม่แน่ใจ",

View File

@@ -136,6 +136,7 @@
"SettingsTabSystemSystemTime": "Sistem Saati:",
"SettingsTabSystemEnableVsync": "Dikey Eşitleme",
"SettingsTabSystemEnablePptc": "PPTC (Profilli Sürekli Çeviri Önbelleği)",
"SettingsTabSystemEnableLowPowerPptc": "Low-power PPTC",
"SettingsTabSystemEnableFsIntegrityChecks": "FS Bütünlük Kontrolleri",
"SettingsTabSystemAudioBackend": "Ses Motoru:",
"SettingsTabSystemAudioBackendDummy": "Yapay",
@@ -566,6 +567,7 @@
"TimeTooltip": "Sistem Saatini Değiştir",
"VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.",
"PptcToggleTooltip": "Çevrilen JIT fonksiyonlarını oyun her açıldığında çevrilmek zorunda kalmaması için kaydeder.\n\nTeklemeyi azaltır ve ilk açılıştan sonra oyunların ilk açılış süresini ciddi biçimde hızlandırır.\n\nEmin değilseniz aktif halde bırakın.",
"LowPowerPptcToggleTooltip": "Load the PPTC using a third of the amount of cores.",
"FsIntegrityToggleTooltip": "Oyun açarken hatalı dosyaların olup olmadığını kontrol eder, ve hatalı dosya bulursa log dosyasında hash hatası görüntüler.\n\nPerformansa herhangi bir etkisi yoktur ve sorun gidermeye yardımcı olur.\n\nEmin değilseniz aktif halde bırakın.",
"AudioBackendTooltip": "Ses çıkış motorunu değiştirir.\n\nSDL2 tercih edilen seçenektir, OpenAL ve SoundIO ise alternatif olarak kullanılabilir. Dummy seçeneğinde ses çıkışı olmayacaktır.\n\nEmin değilseniz SDL2 seçeneğine ayarlayın.",
"MemoryManagerTooltip": "Guest hafızasının nasıl tahsis edilip erişildiğini değiştirir. Emüle edilen CPU performansını ciddi biçimde etkiler.\n\nEmin değilseniz HOST UNCHECKED seçeneğine ayarlayın.",

View File

@@ -136,6 +136,7 @@
"SettingsTabSystemSystemTime": "Час системи:",
"SettingsTabSystemEnableVsync": "Вертикальна синхронізація",
"SettingsTabSystemEnablePptc": "PPTC (профільований постійний кеш перекладу)",
"SettingsTabSystemEnableLowPowerPptc": "Low-power PPTC",
"SettingsTabSystemEnableFsIntegrityChecks": "Перевірка цілісності FS",
"SettingsTabSystemAudioBackend": "Аудіосистема:",
"SettingsTabSystemAudioBackendDummy": "Dummy",
@@ -566,6 +567,7 @@
"TimeTooltip": "Змінити час системи",
"VSyncToggleTooltip": "Емульована вертикальна синхронізація консолі. По суті, обмежувач кадрів для більшості ігор; його вимкнення може призвести до того, що ігри працюватимуть на вищій швидкості, екрани завантаження триватимуть довше чи зупинятимуться.\n\nМожна перемикати в грі гарячою клавішею (За умовчанням F1). Якщо ви плануєте вимкнути функцію, рекомендуємо зробити це через гарячу клавішу.\n\nЗалиште увімкненим, якщо не впевнені.",
"PptcToggleTooltip": "Зберігає перекладені функції JIT, щоб їх не потрібно було перекладати кожного разу, коли гра завантажується.\n\nЗменшує заїкання та значно прискорює час завантаження після першого завантаження гри.\n\nЗалиште увімкненим, якщо не впевнені.",
"LowPowerPptcToggleTooltip": "Load the PPTC using a third of the amount of cores.",
"FsIntegrityToggleTooltip": "Перевіряє наявність пошкоджених файлів під час завантаження гри, і якщо виявлено пошкоджені файли, показує помилку хешу в журналі.\n\nНе впливає на продуктивність і призначений для усунення несправностей.\n\nЗалиште увімкненим, якщо не впевнені.",
"AudioBackendTooltip": "Змінює серверну частину, яка використовується для відтворення аудіо.\n\nSDL2 є кращим, тоді як OpenAL і SoundIO використовуються як резервні варіанти. Dummy не матиме звуку.\n\nВстановіть SDL2, якщо не впевнені.",
"MemoryManagerTooltip": "Змінює спосіб відображення та доступу до гостьової пам’яті. Значно впливає на продуктивність емульованого ЦП.\n\nВстановіть «Неперевірений хост», якщо не впевнені.",

View File

@@ -136,6 +136,7 @@
"SettingsTabSystemSystemTime": "系统时钟:",
"SettingsTabSystemEnableVsync": "启用垂直同步",
"SettingsTabSystemEnablePptc": "开启 PPTC 缓存",
"SettingsTabSystemEnableLowPowerPptc": "Low-power PPTC",
"SettingsTabSystemEnableFsIntegrityChecks": "启用文件系统完整性检查",
"SettingsTabSystemAudioBackend": "音频处理引擎:",
"SettingsTabSystemAudioBackendDummy": "无",
@@ -566,6 +567,7 @@
"TimeTooltip": "更改系统时间",
"VSyncToggleTooltip": "模拟控制台的垂直同步,开启后会降低大部分游戏的帧率。关闭后,可以获得更高的帧率,但也可能导致游戏画面加载耗时更长或卡住。\n\n在游戏中可以使用热键进行切换默认为 F1 键)。\n\n如果不确定请保持开启状态。",
"PptcToggleTooltip": "缓存已编译的游戏指令,这样每次游戏加载时就无需重新编译。\n\n可以减少卡顿和启动时间提高游戏响应速度。\n\n如果不确定请保持开启状态。",
"LowPowerPptcToggleTooltip": "Load the PPTC using a third of the amount of cores.",
"FsIntegrityToggleTooltip": "启动游戏时检查游戏文件的完整性,并在日志中记录损坏的文件。\n\n对性能没有影响用于排查故障。\n\n如果不确定请保持开启状态。",
"AudioBackendTooltip": "更改音频处理引擎。\n\n推荐选择“SDL2”另外“OpenAL”和“SoundIO”可以作为备选选择“无”将没有声音。\n\n如果不确定请设置为“SDL2”。",
"MemoryManagerTooltip": "更改模拟器内存映射和访问的方式,对模拟器 CPU 的性能影响很大。\n\n如果不确定请设置为“跳过检查的本机映射”。",

View File

@@ -136,6 +136,7 @@
"SettingsTabSystemSystemTime": "系統時鐘:",
"SettingsTabSystemEnableVsync": "垂直同步",
"SettingsTabSystemEnablePptc": "PPTC (剖析式持久轉譯快取, Profiled Persistent Translation Cache)",
"SettingsTabSystemEnableLowPowerPptc": "Low-power PPTC",
"SettingsTabSystemEnableFsIntegrityChecks": "檔案系統完整性檢查",
"SettingsTabSystemAudioBackend": "音效後端:",
"SettingsTabSystemAudioBackendDummy": "虛設 (Dummy)",
@@ -566,6 +567,7 @@
"TimeTooltip": "變更系統時鐘",
"VSyncToggleTooltip": "模擬遊戲機的垂直同步。對大多數遊戲來說,它本質上是一個幀率限制器;停用它可能會導致遊戲以更高的速度執行,或使載入畫面耗時更長或卡住。\n\n可以在遊戲中使用快速鍵進行切換 (預設為 F1)。如果您打算停用,我們建議您這樣做。\n\n如果不確定請保持開啟狀態。",
"PptcToggleTooltip": "儲存已轉譯的 JIT 函數,這樣每次載入遊戲時就無需再轉譯這些函數。\n\n減少遊戲首次啟動後的卡頓現象並大大加快啟動時間。\n\n如果不確定請保持開啟狀態。",
"LowPowerPptcToggleTooltip": "Load the PPTC using a third of the amount of cores.",
"FsIntegrityToggleTooltip": "在啟動遊戲時檢查損壞的檔案,如果檢測到損壞的檔案,則在日誌中顯示雜湊值錯誤。\n\n對效能沒有影響旨在幫助排除故障。\n\n如果不確定請保持開啟狀態。",
"AudioBackendTooltip": "變更用於繪製音訊的後端。\n\nSDL2 是首選,而 OpenAL 和 SoundIO 則作為備用。虛設 (Dummy) 將沒有聲音。\n\n如果不確定請設定為 SDL2。",
"MemoryManagerTooltip": "變更客體記憶體的映射和存取方式。這會極大地影響模擬 CPU 效能。\n\n如果不確定請設定為主體略過檢查模式。",

View File

@@ -0,0 +1,26 @@
using Avalonia.Data.Core;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings;
using System;
namespace Ryujinx.Ava.Common.Icon
{
internal class IconExtension(string iconString) : MarkupExtension
{
private ClrPropertyInfo PropertyInfo
=> new(
"Item",
_ => new Projektanker.Icons.Avalonia.Icon { Value = iconString },
null,
typeof(Projektanker.Icons.Avalonia.Icon)
);
public override object ProvideValue(IServiceProvider serviceProvider) =>
new CompiledBindingExtension(
new CompiledBindingPathBuilder()
.Property(PropertyInfo, PropertyInfoAccessorFactory.CreateInpcPropertyAccessor)
.Build()
).ProvideValue(serviceProvider);
}
}

View File

@@ -8,26 +8,20 @@ namespace Ryujinx.Ava.Common.Locale
{
internal class LocaleExtension(LocaleKeys key) : MarkupExtension
{
public LocaleKeys Key { get; } = key;
private ClrPropertyInfo PropertyInfo
=> new(
"Item",
_ => LocaleManager.Instance[key],
null,
typeof(string)
);
public override object ProvideValue(IServiceProvider serviceProvider)
{
var builder = new CompiledBindingPathBuilder();
builder.Property(
new ClrPropertyInfo("Item",
_ => LocaleManager.Instance[Key],
null,
typeof(string)
),
PropertyInfoAccessorFactory.CreateInpcPropertyAccessor);
var binding = new CompiledBindingExtension(builder.Build())
{
Source = LocaleManager.Instance
};
return binding.ProvideValue(serviceProvider);
}
public override object ProvideValue(IServiceProvider serviceProvider) =>
new CompiledBindingExtension(
new CompiledBindingPathBuilder()
.Property(PropertyInfo, PropertyInfoAccessorFactory.CreateInpcPropertyAccessor)
.Build()
) { Source = LocaleManager.Instance }
.ProvideValue(serviceProvider);
}
}

View File

@@ -57,40 +57,32 @@ namespace Ryujinx.Ava.Common.Locale
{
// Check if the localized string needs to be formatted.
if (_dynamicValues.TryGetValue(key, out var dynamicValue))
{
try
{
return string.Format(value, dynamicValue);
}
catch (Exception)
catch
{
// If formatting failed use the default text instead.
if (_localeDefaultStrings.TryGetValue(key, out value))
{
try
{
return string.Format(value, dynamicValue);
}
catch (Exception)
catch
{
// If formatting the default text failed return the key.
return key.ToString();
}
}
}
}
return value;
}
// If the locale doesn't contain the key return the default one.
if (_localeDefaultStrings.TryGetValue(key, out string defaultValue))
{
return defaultValue;
}
// If the locale text doesn't exist return the key.
return key.ToString();
return _localeDefaultStrings.TryGetValue(key, out string defaultValue)
? defaultValue
: key.ToString(); // If the locale text doesn't exist return the key.
}
set
{
@@ -100,14 +92,12 @@ namespace Ryujinx.Ava.Common.Locale
}
}
public bool IsRTL()
{
return _localeLanguageCode switch
public bool IsRTL() =>
_localeLanguageCode switch
{
"ar_SA" or "he_IL" => true,
_ => false
};
}
public string UpdateAndGetDynamicValue(LocaleKeys key, params object[] values)
{

View File

@@ -1,6 +1,11 @@
using ARMeilleure;
using Avalonia;
using Avalonia.Threading;
using DiscordRPC;
using Projektanker.Icons.Avalonia;
using Projektanker.Icons.Avalonia.FontAwesome;
using Projektanker.Icons.Avalonia.MaterialDesign;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
@@ -11,6 +16,7 @@ using Ryujinx.Common.SystemInterop;
using Ryujinx.Graphics.Vulkan.MoltenVK;
using Ryujinx.Modules;
using Ryujinx.SDL2.Common;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper;
@@ -51,31 +57,32 @@ namespace Ryujinx.Ava
LoggerAdapter.Register();
IconProvider.Current
.Register<FontAwesomeIconProvider>()
.Register<MaterialDesignIconProvider>();
return BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
public static AppBuilder BuildAvaloniaApp()
{
return AppBuilder.Configure<App>()
public static AppBuilder BuildAvaloniaApp() =>
AppBuilder.Configure<App>()
.UsePlatformDetect()
.With(new X11PlatformOptions
{
EnableMultiTouch = true,
EnableIme = true,
EnableInputFocusProxy = Environment.GetEnvironmentVariable("XDG_CURRENT_DESKTOP") == "gamescope",
RenderingMode = UseHardwareAcceleration ?
new[] { X11RenderingMode.Glx, X11RenderingMode.Software } :
new[] { X11RenderingMode.Software },
RenderingMode = UseHardwareAcceleration
? [ X11RenderingMode.Glx, X11RenderingMode.Software ]
: [ X11RenderingMode.Software ],
})
.With(new Win32PlatformOptions
{
WinUICompositionBackdropCornerRadius = 8.0f,
RenderingMode = UseHardwareAcceleration ?
new[] { Win32RenderingMode.AngleEgl, Win32RenderingMode.Software } :
new[] { Win32RenderingMode.Software },
})
.UseSkia();
}
RenderingMode = UseHardwareAcceleration
? [ Win32RenderingMode.AngleEgl, Win32RenderingMode.Software ]
: [ Win32RenderingMode.Software ],
});
private static void Initialize(string[] args)
{
@@ -102,6 +109,9 @@ namespace Ryujinx.Ava
// Setup base data directory.
AppDataManager.Initialize(CommandLineState.BaseDirPathArg);
// Set the delegate for localizing the word "never" in the UI
ApplicationData.LocalizedNever = () => LocaleManager.Instance[LocaleKeys.Never];
// Initialize the configuration.
ConfigurationState.Initialize();
@@ -182,41 +192,33 @@ namespace Ryujinx.Ava
UseHardwareAcceleration = ConfigurationState.Instance.EnableHardwareAcceleration.Value;
// Check if graphics backend was overridden
if (CommandLineState.OverrideGraphicsBackend != null)
{
if (CommandLineState.OverrideGraphicsBackend.ToLower() == "opengl")
if (CommandLineState.OverrideGraphicsBackend is not null)
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = CommandLineState.OverrideGraphicsBackend.ToLower() switch
{
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.OpenGl;
}
else if (CommandLineState.OverrideGraphicsBackend.ToLower() == "vulkan")
{
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.Vulkan;
}
}
"opengl" => GraphicsBackend.OpenGl,
"vulkan" => GraphicsBackend.Vulkan,
_ => ConfigurationState.Instance.Graphics.GraphicsBackend
};
// Check if docked mode was overriden.
if (CommandLineState.OverrideDockedMode.HasValue)
{
ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value;
}
// Check if HideCursor was overridden.
if (CommandLineState.OverrideHideCursor is not null)
{
ConfigurationState.Instance.HideCursor.Value = CommandLineState.OverrideHideCursor!.ToLower() switch
ConfigurationState.Instance.HideCursor.Value = CommandLineState.OverrideHideCursor.ToLower() switch
{
"never" => HideCursorMode.Never,
"onidle" => HideCursorMode.OnIdle,
"always" => HideCursorMode.Always,
_ => ConfigurationState.Instance.HideCursor.Value,
_ => ConfigurationState.Instance.HideCursor,
};
}
// Check if hardware-acceleration was overridden.
if (CommandLineState.OverrideHardwareAcceleration != null)
{
UseHardwareAcceleration = CommandLineState.OverrideHardwareAcceleration.Value;
}
}
private static void PrintSystemInfo()
@@ -226,14 +228,10 @@ namespace Ryujinx.Ava
Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(Logger.GetEnabledLevels().Count == 0 ? "<None>" : string.Join(", ", Logger.GetEnabledLevels()))}");
if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom)
{
Logger.Notice.Print(LogClass.Application, $"Launch Mode: Custom Path {AppDataManager.BaseDirPath}");
}
else
{
Logger.Notice.Print(LogClass.Application, $"Launch Mode: {AppDataManager.Mode}");
}
Logger.Notice.Print(LogClass.Application,
AppDataManager.Mode == AppDataManager.LaunchMode.Custom
? $"Launch Mode: Custom Path {AppDataManager.BaseDirPath}"
: $"Launch Mode: {AppDataManager.Mode}");
}
private static void ProcessUnhandledException(Exception ex, bool isTerminating)

View File

@@ -44,7 +44,9 @@
<PackageReference Include="Avalonia.Svg.Skia" />
<PackageReference Include="DynamicData" />
<PackageReference Include="FluentAvaloniaUI" />
<PackageReference Include="Projektanker.Icons.Avalonia" />
<PackageReference Include="Projektanker.Icons.Avalonia.FontAwesome" />
<PackageReference Include="Projektanker.Icons.Avalonia.MaterialDesign" />
<PackageReference Include="OpenTK.Core" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" />
@@ -69,8 +71,7 @@
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" />
<ProjectReference Include="..\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj" />
<ProjectReference Include="..\Ryujinx.UI.LocaleGenerator\Ryujinx.UI.LocaleGenerator.csproj"
OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\Ryujinx.UI.LocaleGenerator\Ryujinx.UI.LocaleGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup>

View File

@@ -122,7 +122,7 @@ namespace Ryujinx.Ava.UI.Applet
{
try
{
_parent.ViewModel.AppHost.NpadManager.BlockInputUpdates();
MainWindow.ViewModel.AppHost.NpadManager.BlockInputUpdates();
var response = await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.SoftwareKeyboard], args);
if (response.Result == UserResult.Ok)
@@ -144,7 +144,7 @@ namespace Ryujinx.Ava.UI.Applet
});
dialogCloseEvent.WaitOne();
_parent.ViewModel.AppHost.NpadManager.UnblockInputUpdates();
MainWindow.ViewModel.AppHost.NpadManager.UnblockInputUpdates();
userText = error ? null : inputText;
@@ -154,7 +154,7 @@ namespace Ryujinx.Ava.UI.Applet
public void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value)
{
device.Configuration.UserChannelPersistence.ExecuteProgram(kind, value);
_parent.ViewModel.AppHost?.Stop();
MainWindow.ViewModel.AppHost?.Stop();
}
public bool DisplayErrorAppletDialog(string title, string message, string[] buttons)

View File

@@ -129,7 +129,7 @@ namespace Ryujinx.Ava.UI.Applet
Dispatcher.UIThread.Post(() =>
{
_hiddenTextBox.Clear();
_parent.ViewModel.RendererHostControl.Focus();
MainWindow.ViewModel.RendererHostControl.Focus();
_parent = null;
});

View File

@@ -3,24 +3,29 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:icon="clr-namespace:Ryujinx.Ava.Common.Icon"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
x:DataType="viewModels:MainWindowViewModel">
<MenuItem
<MenuItem
Click="RunApplication_Click"
Header="{locale:Locale GameListContextMenuRunApplication}" />
Header="{locale:Locale GameListContextMenuRunApplication}"
Icon="{icon:Icon fa-solid fa-play}"/>
<MenuItem
Click="ToggleFavorite_Click"
Header="{locale:Locale GameListContextMenuToggleFavorite}"
Icon="{icon:Icon fa-solid fa-star}"
ToolTip.Tip="{locale:Locale GameListContextMenuToggleFavoriteToolTip}" />
<MenuItem
Click="CreateApplicationShortcut_Click"
Header="{locale:Locale GameListContextMenuCreateShortcut}"
<MenuItem
Click="CreateApplicationShortcut_Click"
Header="{locale:Locale GameListContextMenuCreateShortcut}"
IsEnabled="{Binding CreateShortcutEnabled}"
ToolTip.Tip="{OnPlatform Default={locale:Locale GameListContextMenuCreateShortcutToolTip}, macOS={locale:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" />
Icon="{icon:Icon fa-solid fa-bookmark}"
ToolTip.Tip="{OnPlatform Default={locale:Locale GameListContextMenuCreateShortcutToolTip}, macOS={locale:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" />
<Separator />
<MenuItem
Click="OpenUserSaveDirectory_Click"
Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}"
Icon="{icon:Icon mdi-folder-account}"
IsEnabled="{Binding OpenUserSaveDirectoryEnabled}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" />
<MenuItem
@@ -37,45 +42,55 @@
<MenuItem
Click="OpenTitleUpdateManager_Click"
Header="{locale:Locale GameListContextMenuManageTitleUpdates}"
Icon="{icon:Icon fa-solid fa-code-compare}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageTitleUpdatesToolTip}" />
<MenuItem
Click="OpenDownloadableContentManager_Click"
Header="{locale:Locale GameListContextMenuManageDlc}"
Icon="{icon:Icon fa-solid fa-download}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageDlcToolTip}" />
<MenuItem
Click="OpenCheatManager_Click"
Header="{locale:Locale GameListContextMenuManageCheat}"
Icon="{icon:Icon fa-solid fa-code}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageCheatToolTip}" />
<MenuItem
Click="OpenModManager_Click"
Header="{locale:Locale GameListContextMenuManageMod}"
Icon="{icon:Icon mdi-view-module}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageModToolTip}" />
<Separator />
<MenuItem
Click="OpenModsDirectory_Click"
Header="{locale:Locale GameListContextMenuOpenModsDirectory}"
Icon="{icon:Icon mdi-folder-file}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenModsDirectoryToolTip}" />
<MenuItem
Click="OpenSdModsDirectory_Click"
Header="{locale:Locale GameListContextMenuOpenSdModsDirectory}"
Icon="{icon:Icon mdi-folder-file}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" />
<Separator />
<MenuItem Header="{locale:Locale GameListContextMenuCacheManagement}">
<MenuItem Header="{locale:Locale GameListContextMenuCacheManagement}" Icon="{icon:Icon mdi-cached}">
<MenuItem
Click="PurgePtcCache_Click"
Header="{locale:Locale GameListContextMenuCacheManagementPurgePptc}"
Icon="{icon:Icon mdi-refresh}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgePptcToolTip}" />
<MenuItem
Click="PurgeShaderCache_Click"
Header="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCache}"
Icon="{icon:Icon mdi-delete-alert}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCacheToolTip}" />
<MenuItem
Click="OpenPtcDirectory_Click"
Header="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectory}"
Icon="{icon:Icon mdi-folder-arrow-up-down}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectoryToolTip}" />
<MenuItem
Click="OpenShaderCacheDirectory_Click"
Header="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectory}"
Icon="{icon:Icon mdi-folder-arrow-up-down}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip}" />
</MenuItem>
<MenuItem Header="{locale:Locale GameListContextMenuExtractData}">

View File

@@ -13,6 +13,7 @@ using Ryujinx.Common.Configuration;
using Ryujinx.HLE.HOS;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common.Helper;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.IO;
@@ -34,165 +35,132 @@ namespace Ryujinx.Ava.UI.Controls
public void ToggleFavorite_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
return;
if (viewModel?.SelectedApplication != null)
viewModel.SelectedApplication.Favorite = !viewModel.SelectedApplication.Favorite;
ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.IdString, appMetadata =>
{
viewModel.SelectedApplication.Favorite = !viewModel.SelectedApplication.Favorite;
appMetadata.Favorite = viewModel.SelectedApplication.Favorite;
});
ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.IdString, appMetadata =>
{
appMetadata.Favorite = viewModel.SelectedApplication.Favorite;
});
viewModel.RefreshView();
}
viewModel.RefreshView();
}
public void OpenUserSaveDirectory_Click(object sender, RoutedEventArgs args)
{
if (sender is MenuItem { DataContext: MainWindowViewModel viewModel })
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
OpenSaveDirectory(viewModel, SaveDataType.Account, new UserId((ulong)viewModel.AccountManager.LastOpenedUser.UserId.High, (ulong)viewModel.AccountManager.LastOpenedUser.UserId.Low));
}
}
public void OpenDeviceSaveDirectory_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
OpenSaveDirectory(viewModel, SaveDataType.Device, default);
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
OpenSaveDirectory(viewModel, SaveDataType.Device, default);
}
public void OpenBcatSaveDirectory_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
OpenSaveDirectory(viewModel, SaveDataType.Bcat, default);
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
OpenSaveDirectory(viewModel, SaveDataType.Bcat, default);
}
private static void OpenSaveDirectory(MainWindowViewModel viewModel, SaveDataType saveDataType, UserId userId)
{
if (viewModel?.SelectedApplication != null)
{
var saveDataFilter = SaveDataFilter.Make(viewModel.SelectedApplication.Id, saveDataType, userId, saveDataId: default, index: default);
var saveDataFilter = SaveDataFilter.Make(viewModel.SelectedApplication.Id, saveDataType, userId, saveDataId: default, index: default);
ApplicationHelper.OpenSaveDir(in saveDataFilter, viewModel.SelectedApplication.Id, viewModel.SelectedApplication.ControlHolder, viewModel.SelectedApplication.Name);
}
ApplicationHelper.OpenSaveDir(in saveDataFilter, viewModel.SelectedApplication.Id, viewModel.SelectedApplication.ControlHolder, viewModel.SelectedApplication.Name);
}
public async void OpenTitleUpdateManager_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
await TitleUpdateWindow.Show(viewModel.ApplicationLibrary, viewModel.SelectedApplication);
}
}
public async void OpenDownloadableContentManager_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
await DownloadableContentManagerWindow.Show(viewModel.ApplicationLibrary, viewModel.SelectedApplication);
}
}
public async void OpenCheatManager_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
await new CheatWindow(
viewModel.VirtualFileSystem,
viewModel.SelectedApplication.IdString,
viewModel.SelectedApplication.Name,
viewModel.SelectedApplication.Path).ShowDialog(viewModel.TopLevel as Window);
}
viewModel.SelectedApplication.Path).ShowDialog((Window)viewModel.TopLevel);
}
public void OpenModsDirectory_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
return;
if (viewModel?.SelectedApplication != null)
{
string modsBasePath = ModLoader.GetModsBasePath();
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, viewModel.SelectedApplication.IdString);
string modsBasePath = ModLoader.GetModsBasePath();
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, viewModel.SelectedApplication.IdString);
OpenHelper.OpenFolder(titleModsPath);
}
OpenHelper.OpenFolder(titleModsPath);
}
public void OpenSdModsDirectory_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
return;
if (viewModel?.SelectedApplication != null)
{
string sdModsBasePath = ModLoader.GetSdModsBasePath();
string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, viewModel.SelectedApplication.IdString);
string sdModsBasePath = ModLoader.GetSdModsBasePath();
string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, viewModel.SelectedApplication.IdString);
OpenHelper.OpenFolder(titleModsPath);
}
OpenHelper.OpenFolder(titleModsPath);
}
public async void OpenModManager_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
await ModManagerWindow.Show(viewModel.SelectedApplication.Id, viewModel.SelectedApplication.Name);
}
}
public async void PurgePtcCache_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
return;
if (viewModel?.SelectedApplication != null)
UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogWarning],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.Name)
);
if (result == UserResult.Yes)
{
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogWarning],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.Name),
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "0"));
DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "1"));
if (result == UserResult.Yes)
List<FileInfo> cacheFiles = new();
if (mainDir.Exists)
{
DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "0"));
DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "1"));
cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache"));
}
List<FileInfo> cacheFiles = new();
if (backupDir.Exists)
{
cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache"));
}
if (mainDir.Exists)
if (cacheFiles.Count > 0)
{
foreach (FileInfo file in cacheFiles)
{
cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache"));
}
if (backupDir.Exists)
{
cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache"));
}
if (cacheFiles.Count > 0)
{
foreach (FileInfo file in cacheFiles)
try
{
try
{
file.Delete();
}
catch (Exception ex)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, file.Name, ex));
}
file.Delete();
}
catch (Exception ex)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, file.Name, ex));
}
}
}
@@ -201,55 +169,51 @@ namespace Ryujinx.Ava.UI.Controls
public async void PurgeShaderCache_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
return;
if (viewModel?.SelectedApplication != null)
UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogWarning],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.Name)
);
if (result == UserResult.Yes)
{
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogWarning],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.Name),
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "shader"));
if (result == UserResult.Yes)
List<DirectoryInfo> oldCacheDirectories = new();
List<FileInfo> newCacheFiles = new();
if (shaderCacheDir.Exists)
{
DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "shader"));
oldCacheDirectories.AddRange(shaderCacheDir.EnumerateDirectories("*"));
newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.toc"));
newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.data"));
}
List<DirectoryInfo> oldCacheDirectories = new();
List<FileInfo> newCacheFiles = new();
if (shaderCacheDir.Exists)
if ((oldCacheDirectories.Count > 0 || newCacheFiles.Count > 0))
{
foreach (DirectoryInfo directory in oldCacheDirectories)
{
oldCacheDirectories.AddRange(shaderCacheDir.EnumerateDirectories("*"));
newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.toc"));
newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.data"));
try
{
directory.Delete(true);
}
catch (Exception ex)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, directory.Name, ex));
}
}
if ((oldCacheDirectories.Count > 0 || newCacheFiles.Count > 0))
foreach (FileInfo file in newCacheFiles)
{
foreach (DirectoryInfo directory in oldCacheDirectories)
try
{
try
{
directory.Delete(true);
}
catch (Exception ex)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, directory.Name, ex));
}
file.Delete();
}
foreach (FileInfo file in newCacheFiles)
catch (Exception ex)
{
try
{
file.Delete();
}
catch (Exception ex)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.ShaderCachePurgeError, file.Name, ex));
}
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.ShaderCachePurgeError, file.Name, ex));
}
}
}
@@ -258,30 +222,26 @@ namespace Ryujinx.Ava.UI.Controls
public void OpenPtcDirectory_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
return;
string ptcDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu");
string mainDir = Path.Combine(ptcDir, "0");
string backupDir = Path.Combine(ptcDir, "1");
if (viewModel?.SelectedApplication != null)
if (!Directory.Exists(ptcDir))
{
string ptcDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu");
string mainDir = Path.Combine(ptcDir, "0");
string backupDir = Path.Combine(ptcDir, "1");
if (!Directory.Exists(ptcDir))
{
Directory.CreateDirectory(ptcDir);
Directory.CreateDirectory(mainDir);
Directory.CreateDirectory(backupDir);
}
OpenHelper.OpenFolder(ptcDir);
Directory.CreateDirectory(ptcDir);
Directory.CreateDirectory(mainDir);
Directory.CreateDirectory(backupDir);
}
OpenHelper.OpenFolder(ptcDir);
}
public void OpenShaderCacheDirectory_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
{
string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "shader");
@@ -296,9 +256,7 @@ namespace Ryujinx.Ava.UI.Controls
public async void ExtractApplicationExeFs_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
{
await ApplicationHelper.ExtractSection(
viewModel.StorageProvider,
@@ -310,67 +268,60 @@ namespace Ryujinx.Ava.UI.Controls
public async void ExtractApplicationRomFs_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
await ApplicationHelper.ExtractSection(
viewModel.StorageProvider,
NcaSectionType.Data,
viewModel.SelectedApplication.Path,
viewModel.SelectedApplication.Name);
}
}
public async void ExtractApplicationLogo_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
return;
if (viewModel?.SelectedApplication is { } selectedApp)
var result = await viewModel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
var result = await viewModel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle],
AllowMultiple = false,
});
Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle],
AllowMultiple = false,
});
if (result.Count == 0)
{
return;
}
if (result.Count == 0)
return;
ApplicationHelper.ExtractSection(
result[0].Path.LocalPath,
NcaSectionType.Logo,
viewModel.SelectedApplication.Path,
viewModel.SelectedApplication.Name);
ApplicationHelper.ExtractSection(
result[0].Path.LocalPath,
NcaSectionType.Logo,
viewModel.SelectedApplication.Path,
viewModel.SelectedApplication.Name);
var iconFile = await result[0].CreateFileAsync(selectedApp.IdString + ".png");
await using var fileStream = await iconFile.OpenWriteAsync();
var iconFile = await result[0].CreateFileAsync($"{viewModel.SelectedApplication.IdString}.png");
await using var fileStream = await iconFile.OpenWriteAsync();
fileStream.Write(selectedApp.Icon);
}
using var bitmap = SKBitmap.Decode(viewModel.SelectedApplication.Icon)
.Resize(new SKSizeI(512, 512), SKFilterQuality.High);
using var png = bitmap.Encode(SKEncodedImageFormat.Png, 100);
png.SaveTo(fileStream);
}
public void CreateApplicationShortcut_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
ApplicationData selectedApplication = viewModel.SelectedApplication;
ShortcutHelper.CreateAppShortcut(selectedApplication.Path, selectedApplication.Name, selectedApplication.IdString, selectedApplication.Icon);
}
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
ShortcutHelper.CreateAppShortcut(
viewModel.SelectedApplication.Path,
viewModel.SelectedApplication.Name,
viewModel.SelectedApplication.IdString,
viewModel.SelectedApplication.Icon
);
}
public async void RunApplication_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
await viewModel.LoadApplication(viewModel.SelectedApplication);
}
}
}
}

View File

@@ -15,37 +15,22 @@ namespace Ryujinx.Ava.UI.Controls
public event EventHandler<ApplicationOpenedEventArgs> ApplicationOpened
{
add { AddHandler(ApplicationOpenedEvent, value); }
remove { RemoveHandler(ApplicationOpenedEvent, value); }
add => AddHandler(ApplicationOpenedEvent, value);
remove => RemoveHandler(ApplicationOpenedEvent, value);
}
public ApplicationGridView()
{
InitializeComponent();
}
public ApplicationGridView() => InitializeComponent();
public void GameList_DoubleTapped(object sender, TappedEventArgs args)
{
if (sender is ListBox listBox)
{
if (listBox.SelectedItem is ApplicationData selected)
{
RaiseEvent(new ApplicationOpenedEventArgs(selected, ApplicationOpenedEvent));
}
}
if (sender is ListBox { SelectedItem: ApplicationData selected })
RaiseEvent(new ApplicationOpenedEventArgs(selected, ApplicationOpenedEvent));
}
public void GameList_SelectionChanged(object sender, SelectionChangedEventArgs args)
{
if (sender is ListBox listBox)
{
(DataContext as MainWindowViewModel).GridSelectedApplication = listBox.SelectedItem as ApplicationData;
}
}
private void SearchBox_OnKeyUp(object sender, KeyEventArgs args)
{
(DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text;
if (DataContext is MainWindowViewModel viewModel && sender is ListBox { SelectedItem: ApplicationData selected })
viewModel.GridSelectedApplication = selected;
}
}
}

View File

@@ -109,7 +109,7 @@
Spacing="5">
<TextBlock
HorizontalAlignment="Stretch"
Text="{Binding Id, StringFormat=X16}"
Text="{Binding IdString}"
TextAlignment="Start"
TextWrapping="Wrap" />
<TextBlock
@@ -131,7 +131,7 @@
TextWrapping="Wrap" />
<TextBlock
HorizontalAlignment="Stretch"
Text="{Binding LastPlayedString, Converter={helpers:LocalizedNeverConverter}}"
Text="{Binding LastPlayedString}"
TextAlignment="End"
TextWrapping="Wrap" />
<TextBlock

View File

@@ -15,37 +15,22 @@ namespace Ryujinx.Ava.UI.Controls
public event EventHandler<ApplicationOpenedEventArgs> ApplicationOpened
{
add { AddHandler(ApplicationOpenedEvent, value); }
remove { RemoveHandler(ApplicationOpenedEvent, value); }
add => AddHandler(ApplicationOpenedEvent, value);
remove => RemoveHandler(ApplicationOpenedEvent, value);
}
public ApplicationListView()
{
InitializeComponent();
}
public ApplicationListView() => InitializeComponent();
public void GameList_DoubleTapped(object sender, TappedEventArgs args)
{
if (sender is ListBox listBox)
{
if (listBox.SelectedItem is ApplicationData selected)
{
RaiseEvent(new ApplicationOpenedEventArgs(selected, ApplicationOpenedEvent));
}
}
if (sender is ListBox { SelectedItem: ApplicationData selected })
RaiseEvent(new ApplicationOpenedEventArgs(selected, ApplicationOpenedEvent));
}
public void GameList_SelectionChanged(object sender, SelectionChangedEventArgs args)
{
if (sender is ListBox listBox)
{
(DataContext as MainWindowViewModel).ListSelectedApplication = listBox.SelectedItem as ApplicationData;
}
}
private void SearchBox_OnKeyUp(object sender, KeyEventArgs args)
{
(DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text;
if (DataContext is MainWindowViewModel viewModel && sender is ListBox { SelectedItem: ApplicationData selected })
viewModel.ListSelectedApplication = selected;
}
}
}

View File

@@ -3,6 +3,7 @@ using Avalonia.Controls;
using Avalonia.Styling;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using Gommon;
using LibHac;
using LibHac.Common;
using LibHac.Fs;
@@ -84,10 +85,7 @@ namespace Ryujinx.Ava.UI.Controls
Padding = new Thickness(0),
};
contentDialog.Closed += (sender, args) =>
{
content.ViewModel.Dispose();
};
contentDialog.Closed += (_, _) => content.ViewModel.Dispose();
Style footer = new(x => x.Name("DialogSpace").Child().OfType<Border>());
footer.Setters.Add(new Setter(IsVisibleProperty, false));
@@ -109,12 +107,9 @@ namespace Ryujinx.Ava.UI.Controls
ViewModel.Profiles.Clear();
ViewModel.LostProfiles.Clear();
var profiles = AccountManager.GetAllUsers().OrderBy(x => x.Name);
foreach (var profile in profiles)
{
ViewModel.Profiles.Add(new UserProfile(profile, this));
}
AccountManager.GetAllUsers()
.OrderBy(x => x.Name)
.ForEach(profile => ViewModel.Profiles.Add(new UserProfile(profile, this)));
var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: SaveDataType.Account, default, saveDataId: default, index: default);

View File

@@ -10,20 +10,7 @@ namespace Ryujinx.Ava.UI.Controls
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
{
var newValue = Value + e.Delta.Y * TickFrequency;
if (newValue < Minimum)
{
Value = Minimum;
}
else if (newValue > Maximum)
{
Value = Maximum;
}
else
{
Value = newValue;
}
Value = Math.Clamp(Value + e.Delta.Y * TickFrequency, Minimum, Maximum);
e.Handled = true;
}

View File

@@ -84,7 +84,7 @@ namespace Ryujinx.Ava.UI.Helpers
return await ShowContentDialog(title, content, primaryButton, secondaryButton, closeButton, primaryButtonResult, deferResetEvent, deferCloseAction);
}
public async static Task<UserResult> ShowDeferredContentDialog(
public static Task<UserResult> ShowDeferredContentDialog(
StyleableWindow window,
string title,
string primaryText,
@@ -98,7 +98,7 @@ namespace Ryujinx.Ava.UI.Helpers
{
bool startedDeferring = false;
return await ShowTextDialog(
return ShowTextDialog(
title,
primaryText,
secondaryText,
@@ -148,8 +148,8 @@ namespace Ryujinx.Ava.UI.Helpers
{
Grid content = new()
{
RowDefinitions = new RowDefinitions { new(), new() },
ColumnDefinitions = new ColumnDefinitions { new(GridLength.Auto), new() },
RowDefinitions = [new(), new()],
ColumnDefinitions = [new(GridLength.Auto), new()],
MinHeight = 80,
};
@@ -194,14 +194,13 @@ namespace Ryujinx.Ava.UI.Helpers
return content;
}
public static async Task<UserResult> CreateInfoDialog(
public static Task<UserResult> CreateInfoDialog(
string primary,
string secondaryText,
string acceptButton,
string closeButton,
string title)
{
return await ShowTextDialog(
string title)
=> ShowTextDialog(
title,
primary,
secondaryText,
@@ -209,17 +208,15 @@ namespace Ryujinx.Ava.UI.Helpers
"",
closeButton,
(int)Symbol.Important);
}
internal static async Task<UserResult> CreateConfirmationDialog(
internal static Task<UserResult> CreateConfirmationDialog(
string primaryText,
string secondaryText,
string acceptButtonText,
string cancelButtonText,
string title,
UserResult primaryButtonResult = UserResult.Yes)
{
return await ShowTextDialog(
UserResult primaryButtonResult = UserResult.Yes)
=> ShowTextDialog(
string.IsNullOrWhiteSpace(title) ? LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle] : title,
primaryText,
secondaryText,
@@ -228,11 +225,17 @@ namespace Ryujinx.Ava.UI.Helpers
cancelButtonText,
(int)Symbol.Help,
primaryButtonResult);
}
internal static async Task CreateUpdaterInfoDialog(string primary, string secondaryText)
{
await ShowTextDialog(
internal static Task<UserResult> CreateLocalizedConfirmationDialog(string primaryText, string secondaryText)
=> CreateConfirmationDialog(
primaryText,
secondaryText,
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
internal static Task CreateUpdaterInfoDialog(string primary, string secondaryText)
=> ShowTextDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterTitle],
primary,
secondaryText,
@@ -240,11 +243,9 @@ namespace Ryujinx.Ava.UI.Helpers
"",
LocaleManager.Instance[LocaleKeys.InputDialogOk],
(int)Symbol.Important);
}
internal static async Task CreateWarningDialog(string primary, string secondaryText)
{
await ShowTextDialog(
internal static Task CreateWarningDialog(string primary, string secondaryText)
=> ShowTextDialog(
LocaleManager.Instance[LocaleKeys.DialogWarningTitle],
primary,
secondaryText,
@@ -252,13 +253,12 @@ namespace Ryujinx.Ava.UI.Helpers
"",
LocaleManager.Instance[LocaleKeys.InputDialogOk],
(int)Symbol.Important);
}
internal static async Task CreateErrorDialog(string errorMessage, string secondaryErrorMessage = "")
internal static Task CreateErrorDialog(string errorMessage, string secondaryErrorMessage = "")
{
Logger.Error?.Print(LogClass.Application, errorMessage);
await ShowTextDialog(
return ShowTextDialog(
LocaleManager.Instance[LocaleKeys.DialogErrorTitle],
LocaleManager.Instance[LocaleKeys.DialogErrorMessage],
errorMessage,

View File

@@ -5,6 +5,7 @@ using Ryujinx.Ava.UI.Windows;
using Ryujinx.HLE.FileSystem;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common.Helper;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@@ -18,7 +19,7 @@ namespace Ryujinx.Ava.UI.Models
public ulong SaveId { get; }
public ProgramId TitleId { get; }
public string TitleIdString => $"{TitleId.Value:X16}";
public string TitleIdString => TitleId.ToString();
public UserId UserId { get; }
public bool InGameList { get; }
public string Title { get; }
@@ -46,7 +47,7 @@ namespace Ryujinx.Ava.UI.Models
TitleId = info.ProgramId;
UserId = info.UserId;
var appData = MainWindow.MainWindowViewModel.Applications.FirstOrDefault(x => x.IdString.ToUpper() == TitleIdString);
var appData = MainWindow.ViewModel.Applications.FirstOrDefault(x => x.IdString.Equals(TitleIdString, StringComparison.OrdinalIgnoreCase));
InGameList = appData != null;

View File

@@ -244,7 +244,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
_mainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates();
MainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates();
_isLoaded = false;
@@ -847,7 +847,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
}
}
_mainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
MainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
// Atomically replace and signal input change.
// NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event.
@@ -879,7 +879,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected;
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected;
_mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates();
MainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates();
SelectedGamepad?.Dispose();

View File

@@ -6,7 +6,6 @@ using Avalonia.Media;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using DynamicData;
using DynamicData.Alias;
using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
using LibHac.Common;
@@ -115,12 +114,13 @@ namespace Ryujinx.Ava.UI.ViewModels
public MainWindowViewModel()
{
Applications = new ObservableCollectionExtended<ApplicationData>();
Applications = [];
Applications.ToObservableChangeSet()
.Filter(Filter)
.Sort(GetComparer())
.Bind(out _appsObservableList).AsObservableList();
.Bind(out _appsObservableList)
.AsObservableList();
_rendererWaitEvent = new AutoResetEvent(false);
@@ -1114,7 +1114,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private void ProgressHandler<T>(T state, int current, int total) where T : Enum
{
Dispatcher.UIThread.Post((() =>
Dispatcher.UIThread.Post(() =>
{
ProgressMaximum = total;
ProgressValue = current;
@@ -1160,7 +1160,7 @@ namespace Ryujinx.Ava.UI.ViewModels
default:
throw new ArgumentException($"Unknown Progress Handler type {typeof(T)}");
}
}));
});
}
private void PrepareLoadScreen()
@@ -1231,15 +1231,15 @@ namespace Ryujinx.Ava.UI.ViewModels
{
Dispatcher.UIThread.InvokeAsync(() =>
{
Application.Current.Styles.TryGetResource(args.VSyncEnabled
Application.Current!.Styles.TryGetResource(args.VSyncEnabled
? "VsyncEnabled"
: "VsyncDisabled",
Application.Current.ActualThemeVariant,
out object color);
if (color is not null)
if (color is Color clr)
{
VsyncColor = new SolidColorBrush((Color)color);
VsyncColor = new SolidColorBrush(clr);
}
DockedStatusText = args.DockedMode;
@@ -1534,14 +1534,12 @@ namespace Ryujinx.Ava.UI.ViewModels
public async Task LoadDlcFromFolder()
{
await LoadContentFromFolder(LocaleKeys.AutoloadDlcAddedMessage,
dirs => ApplicationLibrary.AutoLoadDownloadableContents(dirs));
await LoadContentFromFolder(LocaleKeys.AutoloadDlcAddedMessage, ApplicationLibrary.AutoLoadDownloadableContents);
}
public async Task LoadTitleUpdatesFromFolder()
{
await LoadContentFromFolder(LocaleKeys.AutoloadUpdateAddedMessage,
dirs => ApplicationLibrary.AutoLoadTitleUpdates(dirs));
await LoadContentFromFolder(LocaleKeys.AutoloadUpdateAddedMessage, ApplicationLibrary.AutoLoadTitleUpdates);
}
public async Task OpenFolder()
@@ -1655,7 +1653,10 @@ namespace Ryujinx.Ava.UI.ViewModels
{
version = ContentManager.GetCurrentFirmwareVersion();
}
catch (Exception) { }
catch (Exception)
{
// ignored
}
bool hasApplet = false;
@@ -1705,7 +1706,7 @@ namespace Ryujinx.Ava.UI.ViewModels
Dispatcher.UIThread.InvokeAsync(() =>
{
Title = $"Ryujinx {Program.Version}";
Title = App.FormatTitle();
});
}
@@ -1752,12 +1753,7 @@ namespace Ryujinx.Ava.UI.ViewModels
string mainMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckLoggingEnabledMessage];
string secondaryMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckLoggingEnabledConfirmMessage];
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
mainMessage,
secondaryMessage,
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog(mainMessage, secondaryMessage);
if (result == UserResult.Yes)
{
@@ -1772,12 +1768,7 @@ namespace Ryujinx.Ava.UI.ViewModels
string mainMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckShaderDumpEnabledMessage];
string secondaryMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckShaderDumpEnabledConfirmMessage];
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
mainMessage,
secondaryMessage,
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog(mainMessage, secondaryMessage);
if (result == UserResult.Yes)
{

View File

@@ -150,6 +150,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool EnableMouse { get; set; }
public bool EnableVsync { get; set; }
public bool EnablePptc { get; set; }
public bool EnableLowPowerPptc { get; set; }
public bool EnableInternetAccess { get; set; }
public bool EnableFsIntegrityChecks { get; set; }
public bool IgnoreMissingServices { get; set; }
@@ -448,19 +449,20 @@ namespace Ryujinx.Ava.UI.ViewModels
// CPU
EnablePptc = config.System.EnablePtc;
EnableLowPowerPptc = config.System.EnableLowPowerPtc;
MemoryMode = (int)config.System.MemoryManagerMode.Value;
UseHypervisor = config.System.UseHypervisor;
// Graphics
GraphicsBackendIndex = (int)config.Graphics.GraphicsBackend.Value;
// Physical devices are queried asynchronously hence the prefered index config value is loaded in LoadAvailableGpus().
// Physical devices are queried asynchronously hence the preferred index config value is loaded in LoadAvailableGpus().
EnableShaderCache = config.Graphics.EnableShaderCache;
EnableTextureRecompression = config.Graphics.EnableTextureRecompression;
EnableMacroHLE = config.Graphics.EnableMacroHLE;
EnableColorSpacePassthrough = config.Graphics.EnableColorSpacePassthrough;
ResolutionScale = config.Graphics.ResScale == -1 ? 4 : config.Graphics.ResScale - 1;
CustomResolutionScale = config.Graphics.ResScaleCustom;
MaxAnisotropy = config.Graphics.MaxAnisotropy == -1 ? 0 : (int)(MathF.Log2(config.Graphics.MaxAnisotropy));
MaxAnisotropy = config.Graphics.MaxAnisotropy == -1 ? 0 : (int)MathF.Log2(config.Graphics.MaxAnisotropy);
AspectRatio = (int)config.Graphics.AspectRatio.Value;
GraphicsBackendMultithreadingIndex = (int)config.Graphics.BackendThreading.Value;
ShaderDumpPath = config.Graphics.ShadersDumpPath;
@@ -548,6 +550,7 @@ namespace Ryujinx.Ava.UI.ViewModels
// CPU
config.System.EnablePtc.Value = EnablePptc;
config.System.EnableLowPowerPtc.Value = EnableLowPowerPptc;
config.System.MemoryManagerMode.Value = (MemoryManagerMode)MemoryMode;
config.System.UseHypervisor.Value = UseHypervisor;

View File

@@ -4,6 +4,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:icon="clr-namespace:Ryujinx.Ava.Common.Icon"
mc:Ignorable="d"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
x:DataType="viewModels:MainWindowViewModel"
@@ -12,6 +13,13 @@
<viewModels:MainWindowViewModel />
</Design.DataContext>
<DockPanel HorizontalAlignment="Stretch">
<Border Padding="7, 0, 0, 0" VerticalAlignment="Center" HorizontalAlignment="Center">
<Image
ToolTip.Tip="{Binding Title}"
Height="25"
Width="25"
Source="resm:Ryujinx.UI.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.UI.Common" />
</Border>
<Menu
Name="Menu"
Height="35"
@@ -27,27 +35,32 @@
<MenuItem
Command="{Binding OpenFile}"
Header="{locale:Locale MenuBarFileOpenFromFile}"
Icon="{icon:Icon fa-solid fa-file}"
IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale LoadApplicationFileTooltip}" />
<MenuItem
Command="{Binding OpenFolder}"
Header="{locale:Locale MenuBarFileOpenUnpacked}"
Icon="{icon:Icon fa-solid fa-folder}"
IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale LoadApplicationFolderTooltip}" />
<MenuItem
Command="{Binding LoadDlcFromFolder}"
Header="{locale:Locale MenuBarFileLoadDlcFromFolder}"
Icon="{icon:Icon fa-solid fa-download}"
IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale LoadDlcFromFolderTooltip}" />
<MenuItem
Command="{Binding LoadTitleUpdatesFromFolder}"
Header="{locale:Locale MenuBarFileLoadTitleUpdatesFromFolder}"
Icon="{icon:Icon fa-solid fa-code-compare}"
IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale LoadTitleUpdatesFromFolderTooltip}" />
<MenuItem Header="{locale:Locale MenuBarFileOpenApplet}" IsEnabled="{Binding IsAppletMenuActive}">
<MenuItem Header="{locale:Locale MenuBarFileOpenApplet}" IsEnabled="{Binding IsAppletMenuActive}" Icon="{icon:Icon mdi-launch}">
<MenuItem
Click="OpenMiiApplet"
Header="Mii Edit Applet"
Icon="{icon:Icon fa-solid fa-person}"
ToolTip.Tip="{locale:Locale MenuBarFileOpenAppletOpenMiiAppletToolTip}" />
</MenuItem>
<Separator />
@@ -63,14 +76,28 @@
<MenuItem
Click="CloseWindow"
Header="{locale:Locale MenuBarFileExit}"
Icon="{icon:Icon fa-solid fa-xmark}"
ToolTip.Tip="{locale:Locale ExitTooltip}" />
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarOptions}">
<MenuItem
Padding="-10,0,0,0"
Command="{Binding ToggleFullscreen}"
Header="{locale:Locale MenuBarOptionsToggleFullscreen}"
InputGesture="F11" />
Padding="0"
Icon="{icon:Icon fa-solid fa-expand}"
InputGesture="F11">
<MenuItem.Styles>
<Style Selector="Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</MenuItem.Styles>
</MenuItem>
<MenuItem
Padding="0"
Command="{Binding ToggleStartGamesInFullscreen}"
@@ -100,7 +127,7 @@
Command="{Binding ToggleShowConsole}"
Header="{locale:Locale MenuBarOptionsShowConsole}">
<MenuItem.Icon>
<CheckBox
<CheckBox
MinWidth="{DynamicResource CheckBoxSize}"
MinHeight="{DynamicResource CheckBoxSize}"
IsChecked="{Binding ShowConsole, Mode=TwoWay}"
@@ -118,11 +145,24 @@
</Style>
</MenuItem.Styles>
</MenuItem>
<Separator />
<Separator/>
<MenuItem
Name="ChangeLanguageMenuItem"
Padding="-10,0,0,0"
Header="{locale:Locale MenuBarOptionsChangeLanguage}" />
Padding="0"
Header="{locale:Locale MenuBarOptionsChangeLanguage}"
Icon="{icon:Icon fa-solid fa-language}">
<MenuItem.Styles>
<Style Selector="Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</MenuItem.Styles>
</MenuItem>
<MenuItem
Name="ToggleFileTypesMenuItem"
Padding="-10,0,0,0"
@@ -130,15 +170,41 @@
<Separator />
<MenuItem
Click="OpenSettings"
Padding="-10,0,0,0"
Padding="0"
Header="{locale:Locale MenuBarOptionsSettings}"
ToolTip.Tip="{locale:Locale OpenSettingsTooltip}" />
Icon="{icon:Icon fa-solid fa-gear}"
ToolTip.Tip="{locale:Locale OpenSettingsTooltip}">
<MenuItem.Styles>
<Style Selector="Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</MenuItem.Styles>
</MenuItem>
<MenuItem
Command="{Binding ManageProfiles}"
Padding="-10,0,0,0"
Padding="0"
Header="{locale:Locale MenuBarOptionsManageUserProfiles}"
Icon="{icon:Icon mdi-account}"
IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale OpenProfileManagerTooltip}" />
ToolTip.Tip="{locale:Locale OpenProfileManagerTooltip}">
<MenuItem.Styles>
<Style Selector="Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</MenuItem.Styles>
</MenuItem>
</MenuItem>
<MenuItem
Name="ActionsMenuItem"
@@ -148,18 +214,21 @@
<MenuItem
Click="PauseEmulation_Click"
Header="{locale:Locale MenuBarOptionsPauseEmulation}"
Icon="{icon:Icon fa-solid fa-pause}"
InputGesture="{Binding PauseKey}"
IsEnabled="{Binding !IsPaused}"
IsVisible="{Binding !IsPaused}" />
<MenuItem
Click="ResumeEmulation_Click"
Header="{locale:Locale MenuBarOptionsResumeEmulation}"
Icon="{icon:Icon fa-solid fa-play}"
InputGesture="{Binding PauseKey}"
IsEnabled="{Binding IsPaused}"
IsVisible="{Binding IsPaused}" />
<MenuItem
Click="StopEmulation_Click"
Header="{locale:Locale MenuBarOptionsStopEmulation}"
Icon="{icon:Icon fa-solid fa-stop}"
InputGesture="Escape"
IsEnabled="{Binding IsGameRunning}"
ToolTip.Tip="{locale:Locale StopEmulationTooltip}" />
@@ -170,26 +239,30 @@
AttachedToVisualTree="ScanAmiiboMenuItem_AttachedToVisualTree"
Click="OpenAmiiboWindow"
Header="{locale:Locale MenuBarActionsScanAmiibo}"
Icon="{icon:Icon mdi-cube-scan}"
IsEnabled="{Binding IsAmiiboRequested}" />
<MenuItem
Command="{Binding TakeScreenshot}"
Header="{locale:Locale MenuBarFileToolsTakeScreenshot}"
Icon="{icon:Icon mdi-monitor-screenshot}"
InputGesture="{Binding ScreenshotKey}"
IsEnabled="{Binding IsGameRunning}" />
<MenuItem
Command="{Binding HideUi}"
Header="{locale:Locale MenuBarFileToolsHideUi}"
Icon="{icon:Icon mdi-eye-off}"
InputGesture="{Binding ShowUiKey}"
IsEnabled="{Binding IsGameRunning}" />
<MenuItem
Click="OpenCheatManagerForCurrentApp"
Header="{locale:Locale GameListContextMenuManageCheat}"
Icon="{icon:Icon fa-solid fa-code}"
IsEnabled="{Binding IsGameRunning}" />
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarTools}">
<MenuItem Header="{locale:Locale MenuBarToolsInstallFirmware}" IsEnabled="{Binding EnableNonGameRunningControls}">
<MenuItem Command="{Binding InstallFirmwareFromFile}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" />
<MenuItem Command="{Binding InstallFirmwareFromFolder}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" />
<MenuItem Header="{locale:Locale MenuBarToolsInstallFirmware}" Icon="{icon:Icon fa-solid fa-download}" IsEnabled="{Binding EnableNonGameRunningControls}">
<MenuItem Command="{Binding InstallFirmwareFromFile}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" Icon="{icon:Icon mdi-file-cog}" />
<MenuItem Command="{Binding InstallFirmwareFromFolder}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" Icon="{icon:Icon mdi-folder-cog}" />
</MenuItem>
<MenuItem Header="{locale:Locale MenuBarToolsManageFileTypes}" IsVisible="{Binding ManageFileTypesVisible}">
<MenuItem Header="{locale:Locale MenuBarToolsInstallFileTypes}" Click="InstallFileTypes_Click"/>
@@ -198,8 +271,8 @@
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarView}">
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarViewWindow}">
<MenuItem Header="{locale:Locale MenuBarViewWindow720}" Tag="720" Click="ChangeWindowSize_Click" />
<MenuItem Header="{locale:Locale MenuBarViewWindow1080}" Tag="1080" Click="ChangeWindowSize_Click" />
<MenuItem Header="{locale:Locale MenuBarViewWindow720}" Tag="720 1280" Click="ChangeWindowSize_Click" />
<MenuItem Header="{locale:Locale MenuBarViewWindow1080}" Tag="1080 1920" Click="ChangeWindowSize_Click" />
</MenuItem>
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarHelp}">
@@ -208,11 +281,13 @@
IsEnabled="{Binding CanUpdate}"
Click="CheckForUpdates"
Header="{locale:Locale MenuBarHelpCheckForUpdates}"
Icon="{icon:Icon mdi-update}"
ToolTip.Tip="{locale:Locale CheckUpdatesTooltip}" />
<Separator />
<MenuItem
Click="OpenAboutWindow"
Header="{locale:Locale MenuBarHelpAbout}"
Icon="{icon:Icon fa-solid fa-circle-info}"
ToolTip.Tip="{locale:Locale OpenAboutTooltip}" />
</MenuItem>
</Menu>

View File

@@ -2,6 +2,7 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Threading;
using Gommon;
using LibHac.Ncm;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale;
@@ -77,11 +78,9 @@ namespace Ryujinx.Ava.UI.Views.Main
MenuItem menuItem = new()
{
Header = languageName,
Command = MiniCommand.Create(() =>
{
MainWindowViewModel.ChangeLanguage(languageCode);
}),
Padding = new Thickness(10, 0, 0, 0),
Header = " " + languageName,
Command = MiniCommand.Create(() => MainWindowViewModel.ChangeLanguage(languageCode)),
};
menuItems.Add(menuItem);
@@ -99,23 +98,23 @@ namespace Ryujinx.Ava.UI.Views.Main
Window = window;
}
ViewModel = Window.ViewModel;
ViewModel = MainWindow.ViewModel;
DataContext = ViewModel;
}
private async void StopEmulation_Click(object sender, RoutedEventArgs e)
{
await Window.ViewModel.AppHost?.ShowExitPrompt();
await MainWindow.ViewModel.AppHost?.ShowExitPrompt().OrCompleted()!;
}
private void PauseEmulation_Click(object sender, RoutedEventArgs e)
{
Window.ViewModel.AppHost?.Pause();
MainWindow.ViewModel.AppHost?.Pause();
}
private void ResumeEmulation_Click(object sender, RoutedEventArgs e)
{
Window.ViewModel.AppHost?.Resume();
MainWindow.ViewModel.AppHost?.Resume();
}
public async void OpenSettings(object sender, RoutedEventArgs e)
@@ -149,9 +148,7 @@ namespace Ryujinx.Ava.UI.Views.Main
public async void OpenAmiiboWindow(object sender, RoutedEventArgs e)
{
if (!ViewModel.IsAmiiboRequested)
{
return;
}
if (ViewModel.AppHost.Device.System.SearchingForAmiibo(out int deviceId))
{
@@ -173,17 +170,15 @@ namespace Ryujinx.Ava.UI.Views.Main
public async void OpenCheatManagerForCurrentApp(object sender, RoutedEventArgs e)
{
if (!ViewModel.IsGameRunning)
{
return;
}
string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString();
await new CheatWindow(
Window.VirtualFileSystem,
ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText,
name,
Window.ViewModel.SelectedApplication.Path).ShowDialog(Window);
MainWindow.ViewModel.SelectedApplication.Path).ShowDialog(Window);
ViewModel.AppHost.Device.EnableCheats();
}
@@ -191,85 +186,50 @@ namespace Ryujinx.Ava.UI.Views.Main
private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
{
if (sender is MenuItem)
{
ViewModel.IsAmiiboRequested = Window.ViewModel.AppHost.Device.System.SearchingForAmiibo(out _);
}
ViewModel.IsAmiiboRequested = MainWindow.ViewModel.AppHost.Device.System.SearchingForAmiibo(out _);
}
private async void InstallFileTypes_Click(object sender, RoutedEventArgs e)
{
if (FileAssociationHelper.Install())
{
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty);
}
else
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesErrorMessage]);
}
}
private async void UninstallFileTypes_Click(object sender, RoutedEventArgs e)
{
if (FileAssociationHelper.Uninstall())
{
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty);
}
else
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesErrorMessage]);
}
}
private async void ChangeWindowSize_Click(object sender, RoutedEventArgs e)
{
if (sender is MenuItem item)
if (sender is not MenuItem { Tag: string resolution }) return;
(int height, int width) = resolution.Split(' ')
.Into(parts => (int.Parse(parts[0]), int.Parse(parts[1])));
await Dispatcher.UIThread.InvokeAsync(() =>
{
int height;
int width;
ViewModel.WindowState = WindowState.Normal;
switch (item.Tag)
{
case "720":
height = 720;
width = 1280;
break;
height += (int)Window.StatusBarHeight + (int)Window.MenuBarHeight;
case "1080":
height = 1080;
width = 1920;
break;
default:
throw new ArgumentNullException($"Invalid Tag for {item}");
}
await Dispatcher.UIThread.InvokeAsync(() =>
{
ViewModel.WindowState = WindowState.Normal;
height += (int)Window.StatusBarHeight + (int)Window.MenuBarHeight;
Window.Arrange(new Rect(Window.Position.X, Window.Position.Y, width, height));
});
}
Window.Arrange(new Rect(Window.Position.X, Window.Position.Y, width, height));
});
}
public async void CheckForUpdates(object sender, RoutedEventArgs e)
{
if (Updater.CanUpdate(true))
{
await Updater.BeginParse(Window, true);
}
}
public async void OpenAboutWindow(object sender, RoutedEventArgs e)
{
await AboutWindow.Show();
}
public async void OpenAboutWindow(object sender, RoutedEventArgs e) => await AboutWindow.Show();
public void CloseWindow(object sender, RoutedEventArgs e)
{
Window.Close();
}
public void CloseWindow(object sender, RoutedEventArgs e) => Window.Close();
}
}

View File

@@ -1,4 +1,4 @@
<UserControl
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@@ -22,13 +22,8 @@
VerticalAlignment="Bottom"
Background="{DynamicResource ThemeContentBackgroundColor}"
DockPanel.Dock="Bottom"
IsVisible="{Binding ShowMenuAndStatusBar}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
IsVisible="{Binding ShowMenuAndStatusBar}"
ColumnDefinitions="Auto,Auto,*,Auto">
<StackPanel
Grid.Column="0"
Margin="5"

View File

@@ -28,14 +28,14 @@ namespace Ryujinx.Ava.UI.Views.Main
Window = window;
}
DataContext = Window.ViewModel;
DataContext = MainWindow.ViewModel;
}
private void VsyncStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
{
Window.ViewModel.AppHost.ToggleVSync();
MainWindow.ViewModel.AppHost.ToggleVSync();
Logger.Info?.Print(LogClass.Application, $"VSync toggled to: {Window.ViewModel.AppHost.Device.EnableDeviceVsync}");
Logger.Info?.Print(LogClass.Application, $"VSync toggled to: {MainWindow.ViewModel.AppHost.Device.EnableDeviceVsync}");
}
private void DockedStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
@@ -57,9 +57,9 @@ namespace Ryujinx.Ava.UI.Views.Main
private void VolumeStatus_OnPointerWheelChanged(object sender, PointerWheelEventArgs e)
{
// Change the volume by 5% at a time
float newValue = Window.ViewModel.Volume + (float)e.Delta.Y * 0.05f;
float newValue = MainWindow.ViewModel.Volume + (float)e.Delta.Y * 0.05f;
Window.ViewModel.Volume = newValue switch
MainWindow.ViewModel.Volume = newValue switch
{
< 0 => 0,
> 1 => 1,

View File

@@ -1,4 +1,4 @@
<UserControl
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

View File

@@ -22,9 +22,9 @@ namespace Ryujinx.Ava.UI.Views.Main
{
base.OnAttachedToVisualTree(e);
if (VisualRoot is MainWindow window)
if (VisualRoot is MainWindow)
{
ViewModel = window.ViewModel;
ViewModel = MainWindow.ViewModel;
}
DataContext = ViewModel;
@@ -32,18 +32,14 @@ namespace Ryujinx.Ava.UI.Views.Main
public void Sort_Checked(object sender, RoutedEventArgs args)
{
if (sender is RadioButton button)
{
ViewModel.Sort(Enum.Parse<ApplicationSort>(button.Tag.ToString()));
}
if (sender is RadioButton { Tag: string sortStrategy })
ViewModel.Sort(Enum.Parse<ApplicationSort>(sortStrategy));
}
public void Order_Checked(object sender, RoutedEventArgs args)
{
if (sender is RadioButton button)
{
ViewModel.Sort(button.Tag.ToString() != "Descending");
}
if (sender is RadioButton { Tag: string sortOrder })
ViewModel.Sort(sortOrder is not "Descending");
}
private void SearchBox_OnKeyUp(object sender, KeyEventArgs e)

View File

@@ -1,4 +1,4 @@
<UserControl
<UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsCPUView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -32,6 +32,10 @@
<TextBlock Text="{locale:Locale SettingsTabSystemEnablePptc}"
ToolTip.Tip="{locale:Locale PptcToggleTooltip}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableLowPowerPptc}">
<TextBlock Text="{locale:Locale SettingsTabSystemEnableLowPowerPptc}"
ToolTip.Tip="{locale:Locale LowPowerPptcToggleTooltip}" />
</CheckBox>
</StackPanel>
<Separator Height="1" />
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabCpuMemory}" />

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