Compare commits

..

15 Commits

Author SHA1 Message Date
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
Evan Husted
e1dda4cef1 UI: Add more Discord presence games. 2024-10-11 17:57:33 -05:00
Evan Husted
169126e511 UI: Remember the last state of UseRandomUuid. 2024-10-11 17:57:20 -05:00
Evan Husted
4c237c4793 misc: Code cleanups & remove references to Patreon & Twitter. 2024-10-11 17:56:59 -05:00
71 changed files with 917 additions and 1272 deletions

View File

@@ -6,7 +6,7 @@ on:
env:
POWERSHELL_TELEMETRY_OPTOUT: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1
RYUJINX_BASE_VERSION: "1.1.0"
RYUJINX_BASE_VERSION: "1.2.0"
jobs:
build:

View File

@@ -9,7 +9,6 @@ jobs:
pr_comment:
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
steps:
- uses: actions/github-script@v6
with:
@@ -39,24 +38,19 @@ jobs:
return core.error(`No artifacts found`);
}
let body = `Download the artifacts for this pull request:\n`;
let hidden_gtk_artifacts = `\n\n <details><summary>Old GUI (GTK3)</summary>\n`;
let hidden_headless_artifacts = `\n\n <details><summary>GUI-less (SDL2)</summary>\n`;
let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`;
for (const art of artifacts) {
if(art.name.includes('Debug')) {
hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else if(art.name.includes('gtk-ryujinx')) {
hidden_gtk_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else if(art.name.includes('sdl2-ryujinx-headless')) {
hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else {
body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
}
}
hidden_gtk_artifacts += `\n</details>`;
hidden_headless_artifacts += `\n</details>`;
hidden_debug_artifacts += `\n</details>`;
body += hidden_gtk_artifacts;
body += hidden_headless_artifacts;
body += hidden_debug_artifacts;

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

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

@@ -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();
@@ -907,7 +907,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 +1036,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 +1071,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

@@ -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(0 <= shift && shift < 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

@@ -3,19 +3,11 @@ using System.Threading;
namespace Ryujinx.Common
{
public class ObjectPool<T>
public class ObjectPool<T>(Func<T> factory, int size)
where T : class
{
private T _firstItem;
private readonly T[] _items;
private readonly Func<T> _factory;
public ObjectPool(Func<T> factory, int size)
{
_items = new T[size - 1];
_factory = factory;
}
private readonly T[] _items = new T[size - 1];
public T Allocate()
{
@@ -43,7 +35,7 @@ namespace Ryujinx.Common
}
}
return _factory();
return factory();
}
public void Release(T obj)

View File

@@ -47,15 +47,9 @@ namespace Ryujinx.Common
}
}
public class ReactiveEventArgs<T>
public class ReactiveEventArgs<T>(T oldValue, T newValue)
{
public T OldValue { get; }
public T NewValue { get; }
public ReactiveEventArgs(T oldValue, T newValue)
{
OldValue = oldValue;
NewValue = newValue;
}
public T OldValue { get; } = oldValue;
public T NewValue { get; } = newValue;
}
}

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

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

@@ -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);
}
return null;
SystemVersion VerifyAndGetVersionDirectory(string firmwareDirectory)
{
return VerifyAndGetVersion(new LocalFileSystem(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

@@ -158,10 +158,7 @@ namespace Ryujinx.HLE.HOS.Applets.Error
if (message == "")
{
message = "An error has occured.\n\n"
+ "Please try again later.\n\n"
+ "If the problem persists, please refer to the Ryujinx website.\n"
+ "www.ryujinx.org";
message = "An error has occured.\n\nPlease try again later.";
}
string[] buttons = GetButtonsText(module, description, "DlgBtn");

View File

@@ -659,7 +659,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
if (string.IsNullOrWhiteSpace(filePath))
{
throw new InvalidSystemResourceException("JIT (010000000000003B) system title not found! The JIT will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx#requirements for more information)");
throw new InvalidSystemResourceException("JIT (010000000000003B) system title not found! The JIT will not work, provide the system archive to fix this error. (See https://github.com/GreemDev/Ryujinx#requirements for more information)");
}
context.Device.LoadNca(filePath);

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

@@ -105,7 +105,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pl
titleName = "Unknown";
}
throw new InvalidSystemResourceException($"{titleName} ({fontTitle:x8}) system title not found! This font will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx#requirements for more information)");
throw new InvalidSystemResourceException($"{titleName} ({fontTitle:x8}) system title not found! This font will not work, provide the system archive to fix this error. (See https://github.com/GreemDev/Ryujinx#requirements for more information)");
}
}
else

View File

@@ -23,7 +23,7 @@ namespace Ryujinx.HLE.HOS.Services.Ssl
{
private const long CertStoreTitleId = 0x0100000000000800;
private const string CertStoreTitleMissingErrorMessage = "CertStore system title not found! SSL CA retrieving will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide#initial-setup-continued---installation-of-firmware for more information)";
private const string CertStoreTitleMissingErrorMessage = "CertStore system title not found! SSL CA retrieving will not work, provide the system archive to fix this error.";
private static BuiltInCertificateManager _instance;

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

@@ -23,7 +23,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{
private const long TimeZoneBinaryTitleId = 0x010000000000080E;
private const string TimeZoneSystemTitleMissingErrorMessage = "TimeZoneBinary system title not found! TimeZone conversions will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide#initial-setup-continued---installation-of-firmware for more information)";
private const string TimeZoneSystemTitleMissingErrorMessage = "TimeZoneBinary system title not found! TimeZone conversions will not work, provide the system archive to fix this error.";
private VirtualFileSystem _virtualFileSystem;
private IntegrityCheckLevel _fsIntegrityCheckLevel;

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;

View File

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

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

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

@@ -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,7 @@ 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 = $"v{ReleaseInformation.Version} {ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}@{ReleaseInformation.BuildGitHash}";
private const string ApplicationId = "1293250299716173864";
private const int ApplicationByteLimit = 128;
@@ -29,7 +31,7 @@ namespace Ryujinx.UI.Common
Assets = new Assets
{
LargeImageKey = "ryujinx",
LargeImageText = Description
LargeImageText = TruncateToByteLength(_description)
},
Details = "Main Menu",
State = "Idling",
@@ -62,38 +64,34 @@ namespace Ryujinx.UI.Common
}
}
private static readonly string[] _discordGameAssets = [
"0100f2c0115b6000", // Tears of the Kingdom
"0100744001588000", // Cars 3: Driven to Win
];
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, ApplicationByteLimit),
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}", ApplicationByteLimit),
State = $"Total play time: {appMeta.TimePlayed.Humanize(2, false)}",
Details = TruncateToByteLength($"Playing {appMeta.Title}"),
State = appMeta.LastPlayed.HasValue
? $"Total play time: {appMeta.TimePlayed.Humanize(2, false)}"
: "Never played",
Timestamps = Timestamps.Now
});
}
private static string TruncateToByteLength(string input, int byteLimit)
private static string TruncateToByteLength(string input)
{
if (Encoding.UTF8.GetByteCount(input) <= byteLimit)
if (Encoding.UTF8.GetByteCount(input) <= ApplicationByteLimit)
{
return input;
}
// Find the length to trim the string to guarantee we have space for the trailing ellipsis.
int trimLimit = byteLimit - Encoding.UTF8.GetByteCount(Ellipsis);
int trimLimit = ApplicationByteLimit - Encoding.UTF8.GetByteCount(Ellipsis);
// Make sure the string is long enough to perform the basic trim.
// Amount of bytes != Length of the string
@@ -116,5 +114,54 @@ namespace Ryujinx.UI.Common
{
_discordClient?.Dispose();
}
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
"0100152000022000", // Mario Kart 8 Deluxe
"010049900f546000", // Super Mario 3D All-Stars
"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
"0100000011d90000", // Pokémon Brilliant Diamond
"01001f5010dfa000", // Pokémon Legends: Arceus
"0100aa80194b0000", // Pikmin 1
"0100d680194b2000", // Pikmin 2
"0100f4c009322000", // Pikmin 3 Deluxe
"0100b7c00933a000", // Pikmin 4
"0100c2500fc20000", // Splatoon 3
"0100ba0018500000", // Splatoon 3: Splatfest World Premiere
"01007820196a6000", // Red Dead Redemption
"0100744001588000", // Cars 3: Driven to Win
"01006f8002326000", // Animal Crossing: New Horizons
"0100853015e86000", // No Man's Sky
"01008d100d43e000", // Saints Row IV
"0100de600beee000", // Saints Row: The Third - The Full Package
"0100d7a01b7a2000", // Star Wars: Bounty Hunter
"0100dbf01000a000", // Burnout Paradise Remastered
"0100e46006708000", // Terraria
"010056e00853a000", // A Hat in Time
"01006a800016e000", // Super Smash Bros. Ultimate
"01007bb017812000", // Portal
"0100abd01785c000", // Portal 2
"01008e200c5c2000", // Muse Dash
"01001180021fa000", // Shovel Knight: Specter of Torment
"010012101468c000", // Metroid Prime Remastered
"0100c9a00ece6000", // Nintendo 64 - Nintendo Switch Online
];
}
}

View File

@@ -11,7 +11,7 @@ namespace Ryujinx.UI.Common.Helper
{
public static partial class FileAssociationHelper
{
private static readonly string[] _fileExtensions = { ".nca", ".nro", ".nso", ".nsp", ".xci" };
private static readonly string[] _fileExtensions = [".nca", ".nro", ".nso", ".nsp", ".xci"];
[SupportedOSPlatform("linux")]
private static readonly string _mimeDbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "mime");

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,9 +8,7 @@ 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}";
@@ -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,21 +96,14 @@ 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();
@@ -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)
{
@@ -155,8 +145,6 @@ namespace Ryujinx.UI.Common.Helper
}
private static string PathToGameUpdatesJson(ulong applicationIdBase)
{
return Path.Combine(AppDataManager.GamesDirPath, applicationIdBase.ToString("x16"), "updates.json");
}
=> Path.Combine(AppDataManager.GamesDirPath, applicationIdBase.ToString("x16"), "updates.json");
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -18,9 +18,7 @@
<None Remove="Resources\Logo_Amiibo.png" />
<None Remove="Resources\Logo_Discord.png" />
<None Remove="Resources\Logo_GitHub.png" />
<None Remove="Resources\Logo_Patreon.png" />
<None Remove="Resources\Logo_Ryujinx.png" />
<None Remove="Resources\Logo_Twitter.png" />
</ItemGroup>
<ItemGroup>
@@ -39,10 +37,6 @@
<EmbeddedResource Include="Resources\Logo_Discord_Light.png" />
<EmbeddedResource Include="Resources\Logo_GitHub_Dark.png" />
<EmbeddedResource Include="Resources\Logo_GitHub_Light.png" />
<EmbeddedResource Include="Resources\Logo_Patreon_Dark.png" />
<EmbeddedResource Include="Resources\Logo_Patreon_Light.png" />
<EmbeddedResource Include="Resources\Logo_Twitter_Dark.png" />
<EmbeddedResource Include="Resources\Logo_Twitter_Light.png" />
</ItemGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64' OR '$(RuntimeIdentifier)' == 'linux-arm64' OR '$(RuntimeIdentifier)' == ''">

View File

@@ -33,17 +33,13 @@ namespace Ryujinx.UI.Common.SystemInfo
public static SystemInfo Gather()
{
if (OperatingSystem.IsWindows())
{
return new WindowsSystemInfo();
}
else if (OperatingSystem.IsLinux())
{
if (OperatingSystem.IsLinux())
return new LinuxSystemInfo();
}
else if (OperatingSystem.IsMacOS())
{
if (OperatingSystem.IsMacOS())
return new MacOSSystemInfo();
}
Logger.Error?.Print(LogClass.Application, "SystemInfo unsupported on this platform");

View File

@@ -40,7 +40,7 @@ namespace Ryujinx.UI.Common.SystemInfo
}
}
return Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER").Trim();
return Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER")?.Trim();
}
[StructLayout(LayoutKind.Sequential)]

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);
@@ -71,8 +76,7 @@ namespace Ryujinx.Ava
if (result == UserResult.Yes)
{
var path = Environment.ProcessPath;
var proc = Process.Start(path, CommandLineState.Arguments);
_ = Process.Start(Environment.ProcessPath!, CommandLineState.Arguments);
desktop.Shutdown();
Environment.Exit(0);
}
@@ -113,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);
@@ -785,12 +785,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;
}
@@ -825,7 +824,7 @@ namespace Ryujinx.Ava
{
renderer = new VulkanRenderer(
Vk.GetApi(),
(RendererHost.EmbeddedWindow as EmbeddedWindowVulkan).CreateSurface,
(RendererHost.EmbeddedWindow as EmbeddedWindowVulkan)!.CreateSurface,
VulkanHelper.GetRequiredInstanceExtensions,
ConfigurationState.Instance.Graphics.PreferredGpu.Value);
}
@@ -845,36 +844,39 @@ namespace Ryujinx.Ava
Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend Threading ({threadingMode}): {isGALThreaded}");
// Initialize Configuration.
var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value ? MemoryConfiguration.MemoryConfiguration8GiB : MemoryConfiguration.MemoryConfiguration4GiB;
var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam
? MemoryConfiguration.MemoryConfiguration8GiB
: MemoryConfiguration.MemoryConfiguration4GiB;
HLEConfiguration configuration = new(VirtualFileSystem,
_viewModel.LibHacHorizonManager,
ContentManager,
_accountManager,
_userChannelPersistence,
renderer,
InitializeAudio(),
memoryConfiguration,
_viewModel.UiHandler,
(SystemLanguage)ConfigurationState.Instance.System.Language.Value,
(RegionCode)ConfigurationState.Instance.System.Region.Value,
ConfigurationState.Instance.Graphics.EnableVsync,
ConfigurationState.Instance.System.EnableDockedMode,
ConfigurationState.Instance.System.EnablePtc,
ConfigurationState.Instance.System.EnableInternetAccess,
ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None,
ConfigurationState.Instance.System.FsGlobalAccessLogMode,
ConfigurationState.Instance.System.SystemTimeOffset,
ConfigurationState.Instance.System.TimeZone,
ConfigurationState.Instance.System.MemoryManagerMode,
ConfigurationState.Instance.System.IgnoreMissingServices,
ConfigurationState.Instance.Graphics.AspectRatio,
ConfigurationState.Instance.System.AudioVolume,
ConfigurationState.Instance.System.UseHypervisor,
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value,
ConfigurationState.Instance.Multiplayer.Mode);
Device = new Switch(configuration);
Device = new Switch(new HLEConfiguration(
VirtualFileSystem,
_viewModel.LibHacHorizonManager,
ContentManager,
_accountManager,
_userChannelPersistence,
renderer,
InitializeAudio(),
memoryConfiguration,
_viewModel.UiHandler,
(SystemLanguage)ConfigurationState.Instance.System.Language.Value,
(RegionCode)ConfigurationState.Instance.System.Region.Value,
ConfigurationState.Instance.Graphics.EnableVsync,
ConfigurationState.Instance.System.EnableDockedMode,
ConfigurationState.Instance.System.EnablePtc,
ConfigurationState.Instance.System.EnableInternetAccess,
ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None,
ConfigurationState.Instance.System.FsGlobalAccessLogMode,
ConfigurationState.Instance.System.SystemTimeOffset,
ConfigurationState.Instance.System.TimeZone,
ConfigurationState.Instance.System.MemoryManagerMode,
ConfigurationState.Instance.System.IgnoreMissingServices,
ConfigurationState.Instance.Graphics.AspectRatio,
ConfigurationState.Instance.System.AudioVolume,
ConfigurationState.Instance.System.UseHypervisor,
ConfigurationState.Instance.Multiplayer.LanInterfaceId,
ConfigurationState.Instance.Multiplayer.Mode
)
);
}
private static IHardwareDeviceDriver InitializeAudio()

View File

@@ -6,33 +6,23 @@ using System;
namespace Ryujinx.Ava.Common.Locale
{
internal class LocaleExtension : MarkupExtension
internal class LocaleExtension(LocaleKeys key) : MarkupExtension
{
public LocaleExtension(LocaleKeys key)
{
Key = key;
}
public LocaleKeys Key { get; }
public LocaleKeys Key { get; } = key;
public override object ProvideValue(IServiceProvider serviceProvider)
{
LocaleKeys keyToUse = Key;
var builder = new CompiledBindingPathBuilder();
builder
.Property(new ClrPropertyInfo("Item",
obj => (LocaleManager.Instance[keyToUse]),
null,
typeof(string)), (weakRef, iPropInfo) =>
{
return PropertyInfoAccessorFactory.CreateInpcPropertyAccessor(weakRef, iPropInfo);
});
builder.Property(
new ClrPropertyInfo("Item",
_ => LocaleManager.Instance[Key],
null,
typeof(string)
),
PropertyInfoAccessorFactory.CreateInpcPropertyAccessor);
var path = builder.Build();
var binding = new CompiledBindingExtension(path)
var binding = new CompiledBindingExtension(builder.Build())
{
Source = LocaleManager.Instance
};

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

@@ -36,7 +36,7 @@ namespace Ryujinx.Ava
private const uint MbIconwarning = 0x30;
public static void Main(string[] args)
public static int Main(string[] args)
{
Version = ReleaseInformation.Version;
@@ -51,7 +51,7 @@ namespace Ryujinx.Ava
LoggerAdapter.Register();
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
return BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
public static AppBuilder BuildAvaloniaApp()
@@ -182,41 +182,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()

View File

@@ -5,25 +5,15 @@ using System;
namespace Ryujinx.Ava.UI.Applet
{
class AvaloniaHostUITheme : IHostUITheme
class AvaloniaHostUITheme(MainWindow parent) : IHostUITheme
{
public AvaloniaHostUITheme(MainWindow parent)
{
FontFamily = OperatingSystem.IsWindows() && OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000) ? "Segoe UI Variable" : parent.FontFamily.Name;
DefaultBackgroundColor = BrushToThemeColor(parent.Background);
DefaultForegroundColor = BrushToThemeColor(parent.Foreground);
DefaultBorderColor = BrushToThemeColor(parent.BorderBrush);
SelectionBackgroundColor = BrushToThemeColor(parent.ViewControls.SearchBox.SelectionBrush);
SelectionForegroundColor = BrushToThemeColor(parent.ViewControls.SearchBox.SelectionForegroundBrush);
}
public string FontFamily { get; } = OperatingSystem.IsWindows() && OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000) ? "Segoe UI Variable" : parent.FontFamily.Name;
public string FontFamily { get; }
public ThemeColor DefaultBackgroundColor { get; }
public ThemeColor DefaultForegroundColor { get; }
public ThemeColor DefaultBorderColor { get; }
public ThemeColor SelectionBackgroundColor { get; }
public ThemeColor SelectionForegroundColor { get; }
public ThemeColor DefaultBackgroundColor { get; } = BrushToThemeColor(parent.Background);
public ThemeColor DefaultForegroundColor { get; } = BrushToThemeColor(parent.Foreground);
public ThemeColor DefaultBorderColor { get; } = BrushToThemeColor(parent.BorderBrush);
public ThemeColor SelectionBackgroundColor { get; } = BrushToThemeColor(parent.ViewControls.SearchBox.SelectionBrush);
public ThemeColor SelectionForegroundColor { get; } = BrushToThemeColor(parent.ViewControls.SearchBox.SelectionForegroundBrush);
private static ThemeColor BrushToThemeColor(IBrush brush)
{

View File

@@ -1,22 +1,18 @@
using Ryujinx.Ava.Common.Locale;
using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Helper;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Helpers
{
internal class UserErrorDialog
{
private const string SetupGuideUrl = "https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide";
private static string GetErrorCode(UserError error)
{
return $"RYU-{(uint)error:X4}";
}
private static string GetErrorTitle(UserError error)
{
return error switch
private static string GetErrorTitle(UserError error) =>
error switch
{
UserError.NoKeys => LocaleManager.Instance[LocaleKeys.UserErrorNoKeys],
UserError.NoFirmware => LocaleManager.Instance[LocaleKeys.UserErrorNoFirmware],
@@ -25,11 +21,9 @@ namespace Ryujinx.Ava.UI.Helpers
UserError.Unknown => LocaleManager.Instance[LocaleKeys.UserErrorUnknown],
_ => LocaleManager.Instance[LocaleKeys.UserErrorUndefined],
};
}
private static string GetErrorDescription(UserError error)
{
return error switch
private static string GetErrorDescription(UserError error) =>
error switch
{
UserError.NoKeys => LocaleManager.Instance[LocaleKeys.UserErrorNoKeysDescription],
UserError.NoFirmware => LocaleManager.Instance[LocaleKeys.UserErrorNoFirmwareDescription],
@@ -38,53 +32,17 @@ namespace Ryujinx.Ava.UI.Helpers
UserError.Unknown => LocaleManager.Instance[LocaleKeys.UserErrorUnknownDescription],
_ => LocaleManager.Instance[LocaleKeys.UserErrorUndefinedDescription],
};
}
private static bool IsCoveredBySetupGuide(UserError error)
{
return error switch
{
UserError.NoKeys or
UserError.NoFirmware or
UserError.FirmwareParsingFailed => true,
_ => false,
};
}
private static string GetSetupGuideUrl(UserError error)
{
if (!IsCoveredBySetupGuide(error))
{
return null;
}
return error switch
{
UserError.NoKeys => SetupGuideUrl + "#initial-setup---placement-of-prodkeys",
UserError.NoFirmware => SetupGuideUrl + "#initial-setup-continued---installation-of-firmware",
_ => SetupGuideUrl,
};
}
public static async Task ShowUserErrorDialog(UserError error)
{
string errorCode = GetErrorCode(error);
bool isInSetupGuide = IsCoveredBySetupGuide(error);
string setupButtonLabel = isInSetupGuide ? LocaleManager.Instance[LocaleKeys.OpenSetupGuideMessage] : "";
var result = await ContentDialogHelper.CreateInfoDialog(
await ContentDialogHelper.CreateInfoDialog(
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogUserErrorDialogMessage, errorCode, GetErrorTitle(error)),
GetErrorDescription(error) + (isInSetupGuide
? LocaleManager.Instance[LocaleKeys.DialogUserErrorDialogInfoMessage]
: ""), setupButtonLabel, LocaleManager.Instance[LocaleKeys.InputDialogOk],
GetErrorDescription(error),
"",
LocaleManager.Instance[LocaleKeys.InputDialogOk],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogUserErrorDialogTitle, errorCode));
if (result == UserResult.Ok)
{
OpenHelper.OpenUrl(GetSetupGuideUrl(error));
}
}
}
}

View File

@@ -17,11 +17,8 @@ namespace Ryujinx.Ava.UI.ViewModels
{
private Bitmap _githubLogo;
private Bitmap _discordLogo;
private Bitmap _patreonLogo;
private Bitmap _twitterLogo;
private string _version;
private string _supporters;
public Bitmap GithubLogo
{
@@ -43,36 +40,6 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public Bitmap PatreonLogo
{
get => _patreonLogo;
set
{
_patreonLogo = value;
OnPropertyChanged();
}
}
public Bitmap TwitterLogo
{
get => _twitterLogo;
set
{
_twitterLogo = value;
OnPropertyChanged();
}
}
public string Supporters
{
get => _supporters;
set
{
_supporters = value;
OnPropertyChanged();
}
}
public string Version
{
get => _version;
@@ -83,13 +50,12 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public string Developers => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.AboutPageDeveloperListMore, "gdkchan, Ac_K, marysaka, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, GoffyDude, TSRBerry, IsaacMarovitz");
public string Developers => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.AboutPageDeveloperListMore, "gdkchan, Ac_K, marysaka, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, GoffyDude, TSRBerry, IsaacMarovitz, GreemDev");
public AboutWindowViewModel()
{
Version = Program.Version;
UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle.Value);
Dispatcher.UIThread.InvokeAsync(DownloadPatronsJson);
ThemeManager.ThemeChanged += ThemeManager_ThemeChanged;
}
@@ -108,8 +74,6 @@ namespace Ryujinx.Ava.UI.ViewModels
GithubLogo = LoadBitmap($"{basePath}Logo_GitHub_{themeSuffix}?assembly=Ryujinx.UI.Common");
DiscordLogo = LoadBitmap($"{basePath}Logo_Discord_{themeSuffix}?assembly=Ryujinx.UI.Common");
PatreonLogo = LoadBitmap($"{basePath}Logo_Patreon_{themeSuffix}?assembly=Ryujinx.UI.Common");
TwitterLogo = LoadBitmap($"{basePath}Logo_Twitter_{themeSuffix}?assembly=Ryujinx.UI.Common");
}
private Bitmap LoadBitmap(string uri)
@@ -122,28 +86,5 @@ namespace Ryujinx.Ava.UI.ViewModels
ThemeManager.ThemeChanged -= ThemeManager_ThemeChanged;
GC.SuppressFinalize(this);
}
private async Task DownloadPatronsJson()
{
if (!NetworkInterface.GetIsNetworkAvailable())
{
Supporters = LocaleManager.Instance[LocaleKeys.ConnectionError];
return;
}
HttpClient httpClient = new();
try
{
string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/");
Supporters = string.Join(", ", JsonHelper.Deserialize(patreonJsonString, CommonJsonContext.Default.StringArray)) + "\n\n";
}
catch
{
Supporters = LocaleManager.Instance[LocaleKeys.ApiError];
}
}
}
}

View File

@@ -24,6 +24,9 @@ namespace Ryujinx.Ava.UI.ViewModels
{
public class AmiiboWindowViewModel : BaseModel, IDisposable
{
// ReSharper disable once InconsistentNaming
private static bool _cachedUseRandomUuid;
private const string DefaultJson = "{ \"amiibo\": [] }";
private const float AmiiboImageSize = 350f;
@@ -41,7 +44,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private int _seriesSelectedIndex;
private bool _enableScanning;
private bool _showAllAmiibo;
private bool _useRandomUuid;
private bool _useRandomUuid = _cachedUseRandomUuid;
private string _usage;
private static readonly AmiiboJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
@@ -82,7 +85,7 @@ namespace Ryujinx.Ava.UI.ViewModels
get => _useRandomUuid;
set
{
_useRandomUuid = value;
_cachedUseRandomUuid = _useRandomUuid = value;
OnPropertyChanged();
}

View File

@@ -911,7 +911,8 @@ namespace Ryujinx.Ava.UI.ViewModels
public KeyGesture PauseKey
{
get => KeyGesture.Parse(_pauseKey); set
get => KeyGesture.Parse(_pauseKey);
set
{
_pauseKey = value.ToString();
@@ -1704,7 +1705,7 @@ namespace Ryujinx.Ava.UI.ViewModels
Dispatcher.UIThread.InvokeAsync(() =>
{
Title = $"Ryujinx {Program.Version}";
Title = App.FormatTitle();
});
}

View File

@@ -83,18 +83,13 @@
LineHeight="12"
Text="{Binding Version}"
TextAlignment="Center" />
<Button
Padding="5"
HorizontalAlignment="Center"
Background="Transparent"
Click="Button_OnClick"
Tag="https://github.com/Ryujinx/Ryujinx/wiki/Changelog#ryujinx-changelog">
<TextBlock
FontSize="10"
Text="{locale:Locale AboutChangelogButton}"
TextAlignment="Center"
ToolTip.Tip="{locale:Locale AboutChangelogButtonTooltipMessage}" />
</Button>
<Border
Height="1"
Margin="0,20, 0, 20"
HorizontalAlignment="Stretch"
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="0,1,0,0" />
</StackPanel>
<StackPanel
Grid.Row="2"
@@ -123,7 +118,7 @@
HorizontalAlignment="Center"
Orientation="Horizontal"
Spacing="10">
<Button
<Button Name="GitHubRepoButton"
MinWidth="30"
MinHeight="30"
MaxWidth="30"
@@ -132,20 +127,6 @@
Background="Transparent"
Click="Button_OnClick"
CornerRadius="15"
Tag="https://www.patreon.com/ryujinx"
ToolTip.Tip="{locale:Locale AboutPatreonUrlTooltipMessage}">
<Image Source="{Binding PatreonLogo}" />
</Button>
<Button
MinWidth="30"
MinHeight="30"
MaxWidth="30"
MaxHeight="30"
Padding="8"
Background="Transparent"
Click="Button_OnClick"
CornerRadius="15"
Tag="https://github.com/Ryujinx/Ryujinx"
ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}">
<Image Source="{Binding GithubLogo}" />
</Button>
@@ -158,36 +139,10 @@
Background="Transparent"
Click="Button_OnClick"
CornerRadius="15"
Tag="https://discordapp.com/invite/N2FmfVc"
Tag="https://discord.gg/dHPrkBkkyA"
ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}">
<Image Source="{Binding DiscordLogo}" />
</Button>
<Button
MinWidth="30"
MinHeight="30"
MaxWidth="30"
MaxHeight="30"
Padding="8"
Background="Transparent"
Click="Button_OnClick"
CornerRadius="15"
Tag="https://twitter.com/RyujinxEmu"
ToolTip.Tip="{locale:Locale AboutTwitterUrlTooltipMessage}">
<Image Source="{Binding TwitterLogo}" />
</Button>
<Button
MinWidth="30"
MinHeight="30"
MaxWidth="30"
MaxHeight="30"
Padding="8"
Background="Transparent"
Click="Button_OnClick"
CornerRadius="15"
Tag="https://www.ryujinx.org"
ToolTip.Tip="{locale:Locale AboutUrlTooltipMessage}">
<ui:SymbolIcon Foreground="{DynamicResource ThemeForegroundColor}" Symbol="Link" />
</Button>
</StackPanel>
</StackPanel>
</Grid>
@@ -205,7 +160,6 @@
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel
Grid.Row="0"
@@ -237,7 +191,7 @@
HorizontalAlignment="Left"
Background="Transparent"
Click="Button_OnClick"
Tag="https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a">
Tag="https://github.com/GreemDev/Ryujinx/graphs/contributors?type=a">
<TextBlock
FontSize="10"
Text="{locale:Locale AboutRyujinxContributorsButtonHeader}"
@@ -245,26 +199,6 @@
ToolTip.Tip="{locale:Locale AboutRyujinxMaintainersContentTooltipMessage}" />
</Button>
</StackPanel>
<StackPanel
Grid.Row="2"
Margin="0,10,0,0"
Spacing="2">
<TextBlock
FontSize="15"
FontWeight="Bold"
Text="{locale:Locale AboutRyujinxSupprtersTitle}" />
<ScrollViewer
Height="70"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Visible">
<TextBlock
Name="SupportersTextBlock"
VerticalAlignment="Top"
FontSize="10"
Text="{Binding Supporters}"
TextWrapping="Wrap" />
</ScrollViewer>
</StackPanel>
</Grid>
</Grid>
</UserControl>

View File

@@ -7,6 +7,7 @@ using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common;
using Ryujinx.UI.Common.Helper;
using System.Threading.Tasks;
using Button = Avalonia.Controls.Button;
@@ -20,6 +21,9 @@ namespace Ryujinx.Ava.UI.Windows
DataContext = new AboutWindowViewModel();
InitializeComponent();
GitHubRepoButton.Tag =
$"https://github.com/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}";
}
public static async Task Show()
@@ -46,9 +50,9 @@ namespace Ryujinx.Ava.UI.Windows
private void Button_OnClick(object sender, RoutedEventArgs e)
{
if (sender is Button button)
if (sender is Button { Tag: { } url })
{
OpenHelper.OpenUrl(button.Tag.ToString());
OpenHelper.OpenUrl(url.ToString());
}
}

View File

@@ -18,7 +18,7 @@ namespace Ryujinx.Ava.UI.Windows
InitializeComponent();
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance[LocaleKeys.Amiibo];
Title = App.FormatTitle(LocaleKeys.Amiibo);
}
public AmiiboWindow()
@@ -31,7 +31,7 @@ namespace Ryujinx.Ava.UI.Windows
if (Program.PreviewerDetached)
{
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance[LocaleKeys.Amiibo];
Title = App.FormatTitle(LocaleKeys.Amiibo);
}
}
@@ -43,8 +43,7 @@ namespace Ryujinx.Ava.UI.Windows
{
if (ViewModel.AmiiboSelectedIndex > -1)
{
AmiiboApi amiibo = ViewModel.AmiiboList[ViewModel.AmiiboSelectedIndex];
ScannedAmiibo = amiibo;
ScannedAmiibo = ViewModel.AmiiboList[ViewModel.AmiiboSelectedIndex];
IsScanned = true;
Close();
}

View File

@@ -30,7 +30,7 @@ namespace Ryujinx.Ava.UI.Windows
InitializeComponent();
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance[LocaleKeys.CheatWindowTitle];
Title = App.FormatTitle(LocaleKeys.CheatWindowTitle);
}
public CheatWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName, string titlePath)
@@ -51,7 +51,7 @@ namespace Ryujinx.Ava.UI.Windows
_enabledCheatsPath = Path.Combine(titleModsPath, "cheats", "enabled.txt");
string[] enabled = Array.Empty<string>();
string[] enabled = [];
if (File.Exists(_enabledCheatsPath))
{
@@ -95,30 +95,19 @@ namespace Ryujinx.Ava.UI.Windows
DataContext = this;
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance[LocaleKeys.CheatWindowTitle];
Title = App.FormatTitle(LocaleKeys.CheatWindowTitle);
}
public void Save()
{
if (NoCheatsFound)
{
return;
}
List<string> enabledCheats = new();
var enabledCheats = LoadedCheats.SelectMany(it => it.SubNodes)
.Where(it => it.IsEnabled)
.Select(it => it.BuildIdKey);
foreach (var cheats in LoadedCheats)
{
foreach (var cheat in cheats.SubNodes)
{
if (cheat.IsEnabled)
{
enabledCheats.Add(cheat.BuildIdKey);
}
}
}
Directory.CreateDirectory(Path.GetDirectoryName(_enabledCheatsPath));
Directory.CreateDirectory(Path.GetDirectoryName(_enabledCheatsPath)!);
File.WriteAllLines(_enabledCheatsPath, enabledCheats);

View File

@@ -79,7 +79,7 @@ namespace Ryujinx.Ava.UI.Windows
UiHandler = new AvaHostUIHandler(this);
ViewModel.Title = $"Ryujinx {Program.Version}";
ViewModel.Title = App.FormatTitle();
// NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point.
StatusBarHeight = StatusBarView.StatusBar.MinHeight;
@@ -443,10 +443,7 @@ namespace Ryujinx.Ava.UI.Windows
Initialize();
/// <summary>
/// Subscribe to the ColorValuesChanged event
/// </summary>
PlatformSettings.ColorValuesChanged += OnPlatformColorValuesChanged;
PlatformSettings!.ColorValuesChanged += OnPlatformColorValuesChanged;
ViewModel.Initialize(
ContentManager,
@@ -467,7 +464,7 @@ namespace Ryujinx.Ava.UI.Windows
_appLibraryAppsSubscription?.Dispose();
_appLibraryAppsSubscription = ApplicationLibrary.Applications
.Connect()
.ObserveOn(SynchronizationContext.Current)
.ObserveOn(SynchronizationContext.Current!)
.Bind(ViewModel.Applications)
.Subscribe();
@@ -656,28 +653,20 @@ namespace Ryujinx.Ava.UI.Windows
applicationLibraryThread.Start();
}
private Task ShowNewContentAddedDialog(int numDlcAdded, int numUpdatesAdded)
private void ShowNewContentAddedDialog(int numDlcAdded, int numUpdatesAdded)
{
var msg = "";
string msg = numDlcAdded > 0 && numUpdatesAdded > 0
? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAndUpdateAddedMessage], numDlcAdded, numUpdatesAdded)
: numDlcAdded > 0
? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAddedMessage], numDlcAdded)
: numUpdatesAdded > 0
? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadUpdateAddedMessage], numUpdatesAdded)
: null;
if (numDlcAdded > 0 && numUpdatesAdded > 0)
{
msg = string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAndUpdateAddedMessage], numDlcAdded, numUpdatesAdded);
}
else if (numDlcAdded > 0)
{
msg = string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAddedMessage], numDlcAdded);
}
else if (numUpdatesAdded > 0)
{
msg = string.Format(LocaleManager.Instance[LocaleKeys.AutoloadUpdateAddedMessage], numUpdatesAdded);
}
else
{
return Task.CompletedTask;
}
if (msg is null) return;
return Dispatcher.UIThread.InvokeAsync(async () =>
Dispatcher.UIThread.InvokeAsync(async () =>
{
await ContentDialogHelper.ShowTextDialog(LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle],
msg, "", "", "", LocaleManager.Instance[LocaleKeys.InputDialogOk], (int)Symbol.Checkmark);

View File

@@ -14,7 +14,7 @@ namespace Ryujinx.Ava.UI.Windows
public SettingsWindow(VirtualFileSystem virtualFileSystem, ContentManager contentManager)
{
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance[LocaleKeys.Settings]}";
Title = App.FormatTitle(LocaleKeys.Settings);
ViewModel = new SettingsViewModel(virtualFileSystem, contentManager);
DataContext = ViewModel;

View File

@@ -17,9 +17,9 @@ namespace Ryujinx.Ava.UI.Windows
public StyleableWindow()
{
WindowStartupLocation = WindowStartupLocation.CenterOwner;
TransparencyLevelHint = new[] { WindowTransparencyLevel.None };
TransparencyLevelHint = [WindowTransparencyLevel.None];
using Stream stream = Assembly.GetAssembly(typeof(ConfigurationState)).GetManifestResourceStream("Ryujinx.UI.Common.Resources.Logo_Ryujinx.png");
using Stream stream = Assembly.GetAssembly(typeof(ConfigurationState))!.GetManifestResourceStream("Ryujinx.UI.Common.Resources.Logo_Ryujinx.png")!;
Icon = new WindowIcon(stream);
stream.Position = 0;

View File

@@ -1,6 +1,4 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Interactivity;
using Avalonia.Styling;
using FluentAvalonia.UI.Controls;
@@ -64,21 +62,14 @@ namespace Ryujinx.Ava.UI.Windows
private void OpenLocation(object sender, RoutedEventArgs e)
{
if (sender is Button button)
{
if (button.DataContext is TitleUpdateModel model)
{
OpenHelper.LocateFile(model.Path);
}
}
if (sender is Button { DataContext: TitleUpdateModel model })
OpenHelper.LocateFile(model.Path);
}
private void RemoveUpdate(object sender, RoutedEventArgs e)
{
if (sender is Button button)
{
ViewModel.RemoveUpdate((TitleUpdateModel)button.DataContext);
}
if (sender is Button { DataContext: TitleUpdateModel model })
ViewModel.RemoveUpdate(model);
}
private void RemoveAll(object sender, RoutedEventArgs e)