Compare commits

...

10 Commits

59 changed files with 812 additions and 1102 deletions

View File

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

View File

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

View File

@ -102,9 +102,7 @@ jobs:
if: matrix.platform.os == 'windows-latest' if: matrix.platform.os == 'windows-latest'
run: | run: |
pushd publish_ava 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/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 popd
pushd publish_sdl2_headless pushd publish_sdl2_headless
@ -116,10 +114,8 @@ jobs:
if: matrix.platform.os == 'ubuntu-latest' if: matrix.platform.os == 'ubuntu-latest'
run: | run: |
pushd publish_ava pushd publish_ava
cp publish/Ryujinx publish/Ryujinx.Ava chmod +x publish/Ryujinx.sh publish/Ryujinx
chmod +x publish/Ryujinx.sh publish/Ryujinx.Ava publish/Ryujinx
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish tar -czvf ../release_output/ryujinx-${{ 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 popd
pushd publish_sdl2_headless pushd publish_sdl2_headless

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" gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz"
rm "$RELEASE_TAR_FILE_NAME" 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 popd
echo "Done" echo "Done"

View File

@ -309,7 +309,7 @@ namespace ARMeilleure.Translation.PTC
ReadOnlySpan<byte> infosBytes = new(stream.PositionPointer, innerHeader.InfosLength); ReadOnlySpan<byte> infosBytes = new(stream.PositionPointer, innerHeader.InfosLength);
stream.Seek(innerHeader.InfosLength, SeekOrigin.Current); stream.Seek(innerHeader.InfosLength, SeekOrigin.Current);
Hash128 infosHash = XXHash128.ComputeHash(infosBytes); Hash128 infosHash = Hash128.ComputeHash(infosBytes);
if (innerHeader.InfosHash != infosHash) 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; ReadOnlySpan<byte> codesBytes = (int)innerHeader.CodesLength > 0 ? new(stream.PositionPointer, (int)innerHeader.CodesLength) : ReadOnlySpan<byte>.Empty;
stream.Seek(innerHeader.CodesLength, SeekOrigin.Current); stream.Seek(innerHeader.CodesLength, SeekOrigin.Current);
Hash128 codesHash = XXHash128.ComputeHash(codesBytes); Hash128 codesHash = Hash128.ComputeHash(codesBytes);
if (innerHeader.CodesHash != codesHash) if (innerHeader.CodesHash != codesHash)
{ {
@ -333,7 +333,7 @@ namespace ARMeilleure.Translation.PTC
ReadOnlySpan<byte> relocsBytes = new(stream.PositionPointer, innerHeader.RelocsLength); ReadOnlySpan<byte> relocsBytes = new(stream.PositionPointer, innerHeader.RelocsLength);
stream.Seek(innerHeader.RelocsLength, SeekOrigin.Current); stream.Seek(innerHeader.RelocsLength, SeekOrigin.Current);
Hash128 relocsHash = XXHash128.ComputeHash(relocsBytes); Hash128 relocsHash = Hash128.ComputeHash(relocsBytes);
if (innerHeader.RelocsHash != relocsHash) if (innerHeader.RelocsHash != relocsHash)
{ {
@ -345,7 +345,7 @@ namespace ARMeilleure.Translation.PTC
ReadOnlySpan<byte> unwindInfosBytes = new(stream.PositionPointer, innerHeader.UnwindInfosLength); ReadOnlySpan<byte> unwindInfosBytes = new(stream.PositionPointer, innerHeader.UnwindInfosLength);
stream.Seek(innerHeader.UnwindInfosLength, SeekOrigin.Current); stream.Seek(innerHeader.UnwindInfosLength, SeekOrigin.Current);
Hash128 unwindInfosHash = XXHash128.ComputeHash(unwindInfosBytes); Hash128 unwindInfosHash = Hash128.ComputeHash(unwindInfosBytes);
if (innerHeader.UnwindInfosHash != unwindInfosHash) if (innerHeader.UnwindInfosHash != unwindInfosHash)
{ {
@ -478,10 +478,10 @@ namespace ARMeilleure.Translation.PTC
Debug.Assert(stream.Position == stream.Length); Debug.Assert(stream.Position == stream.Length);
innerHeader.InfosHash = XXHash128.ComputeHash(infosBytes); innerHeader.InfosHash = Hash128.ComputeHash(infosBytes);
innerHeader.CodesHash = XXHash128.ComputeHash(codesBytes); innerHeader.CodesHash = Hash128.ComputeHash(codesBytes);
innerHeader.RelocsHash = XXHash128.ComputeHash(relocsBytes); innerHeader.RelocsHash = Hash128.ComputeHash(relocsBytes);
innerHeader.UnwindInfosHash = XXHash128.ComputeHash(unwindInfosBytes); innerHeader.UnwindInfosHash = Hash128.ComputeHash(unwindInfosBytes);
innerHeader.SetHeaderHash(); innerHeader.SetHeaderHash();
@ -907,7 +907,7 @@ namespace ARMeilleure.Translation.PTC
public static Hash128 ComputeHash(IMemoryManager memory, ulong address, ulong guestSize) 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) 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); 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() public bool IsHeaderValid()
{ {
Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1); 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); 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() public bool IsHeaderValid()
{ {
Span<InnerHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1); 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 expectedHash = DeserializeStructure<Hash128>(stream);
Hash128 actualHash = XXHash128.ComputeHash(GetReadOnlySpan(stream)); Hash128 actualHash = Hash128.ComputeHash(GetReadOnlySpan(stream));
if (actualHash != expectedHash) if (actualHash != expectedHash)
{ {
@ -313,7 +313,7 @@ namespace ARMeilleure.Translation.PTC
Debug.Assert(stream.Position == stream.Length); Debug.Assert(stream.Position == stream.Length);
stream.Seek(Unsafe.SizeOf<Hash128>(), SeekOrigin.Begin); stream.Seek(Unsafe.SizeOf<Hash128>(), SeekOrigin.Begin);
Hash128 hash = XXHash128.ComputeHash(GetReadOnlySpan(stream)); Hash128 hash = Hash128.ComputeHash(GetReadOnlySpan(stream));
stream.Seek(0L, SeekOrigin.Begin); stream.Seek(0L, SeekOrigin.Begin);
SerializeStructure(stream, hash); SerializeStructure(stream, hash);
@ -374,14 +374,14 @@ namespace ARMeilleure.Translation.PTC
{ {
Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1); 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() public bool IsHeaderValid()
{ {
Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1); 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

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

View File

@ -1,48 +1,556 @@
using System; using System;
using System.Buffers.Binary;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.X86;
using System.Runtime.Intrinsics;
// ReSharper disable InconsistentNaming
namespace Ryujinx.Common namespace Ryujinx.Common
{ {
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public struct Hash128 : IEquatable<Hash128> public struct Hash128(ulong low, ulong high) : IEquatable<Hash128>
{ {
public ulong Low; public ulong Low = low;
public ulong High; 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; ulong high = Math.BigMul(lhs, rhs, out ulong low);
High = high;
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 public readonly struct Log
{ {
private static readonly string _homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); 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; 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.AccessLog : AccessLog = enabled ? new Log(LogLevel.AccessLog) : new Log?(); break;
case LogLevel.Stub : Stub = enabled ? new Log(LogLevel.Stub) : 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; 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 #pragma warning restore IDE0055
} }
} }

View File

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

View File

@ -8,7 +8,7 @@ namespace Ryujinx.Common
private const string FlatHubChannelOwner = "flathub"; private const string FlatHubChannelOwner = "flathub";
private const string BuildVersion = "%%RYUJINX_BUILD_VERSION%%"; 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 ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%";
private const string ConfigFileName = "%%RYUJINX_CONFIG_FILE_NAME%%"; private const string ConfigFileName = "%%RYUJINX_CONFIG_FILE_NAME%%";

View File

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

View File

@ -5,14 +5,16 @@ namespace Ryujinx.Common.Utilities
{ {
public static class UInt128Utils public static class UInt128Utils
{ {
public static UInt128 FromHex(string hex) public static UInt128 FromHex(string hex) =>
{ new(
return new UInt128(ulong.Parse(hex.AsSpan(0, 16), NumberStyles.HexNumber), ulong.Parse(hex.AsSpan(16), NumberStyles.HexNumber)); ulong.Parse(hex.AsSpan(0, 16), NumberStyles.HexNumber),
} ulong.Parse(hex.AsSpan(16), NumberStyles.HexNumber)
);
public static UInt128 CreateRandom() public static Int128 NextInt128(this Random rand) =>
{ new((ulong)rand.NextInt64(), (ulong)rand.NextInt64());
return new UInt128((ulong)Random.Shared.NextInt64(), (ulong)Random.Shared.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]; 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 (hash == entry.Hash)
{ {
if (IsMacroHLESupported(caps, entry.Name)) if (IsMacroHLESupported(caps, entry.Name))

View File

@ -223,7 +223,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.Blender
foreach (var entry in Table) 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]; 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]; currentCode = currentCode[..codeLength];
} }
Hash128 hash = XXHash128.ComputeHash(MemoryMarshal.Cast<uint, byte>(currentCode)); Hash128 hash = Hash128.ComputeHash(MemoryMarshal.Cast<uint, byte>(currentCode));
descriptor = default; descriptor = default;

View File

@ -453,7 +453,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// <returns>Hash of the data</returns> /// <returns>Hash of the data</returns>
private static uint CalcHash(ReadOnlySpan<byte> data) 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 // 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. // So, we check it early for a better user experience.
if (_virtualFileSystem.KeySet.HeaderKey.IsZeros()) if (_virtualFileSystem.KeySet.HeaderKey.IsZeros())
{
throw new MissingKeyException("HeaderKey is empty. Cannot decrypt NCA headers."); throw new MissingKeyException("HeaderKey is empty. Cannot decrypt NCA headers.");
}
Dictionary<ulong, List<(NcaContentType type, string path)>> updateNcas = new(); Dictionary<ulong, List<(NcaContentType type, string path)>> updateNcas = new();
if (Directory.Exists(firmwarePackage)) if (Directory.Exists(firmwarePackage))
{
return VerifyAndGetVersionDirectory(firmwarePackage); return VerifyAndGetVersionDirectory(firmwarePackage);
}
if (!File.Exists(firmwarePackage)) if (!File.Exists(firmwarePackage))
{
throw new FileNotFoundException("Firmware file does not exist."); throw new FileNotFoundException("Firmware file does not exist.");
}
FileInfo info = new(firmwarePackage); FileInfo info = new(firmwarePackage);
@ -612,30 +606,22 @@ namespace Ryujinx.HLE.FileSystem
{ {
case ".zip": case ".zip":
using (ZipArchive archive = ZipFile.OpenRead(firmwarePackage)) using (ZipArchive archive = ZipFile.OpenRead(firmwarePackage))
{
return VerifyAndGetVersionZip(archive); return VerifyAndGetVersionZip(archive);
}
case ".xci": case ".xci":
Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage()); Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage());
if (xci.HasPartition(XciPartitionType.Update)) if (!xci.HasPartition(XciPartitionType.Update))
{
XciPartition partition = xci.OpenPartition(XciPartitionType.Update);
return VerifyAndGetVersion(partition);
}
else
{
throw new InvalidFirmwarePackageException("Update not found in xci file."); throw new InvalidFirmwarePackageException("Update not found in xci file.");
}
default: XciPartition partition = xci.OpenPartition(XciPartitionType.Update);
break;
return VerifyAndGetVersion(partition);
} }
SystemVersion VerifyAndGetVersionDirectory(string firmwareDirectory) return null;
{
return VerifyAndGetVersion(new LocalFileSystem(firmwareDirectory)); SystemVersion VerifyAndGetVersionDirectory(string firmwareDirectory)
} => VerifyAndGetVersion(new LocalFileSystem(firmwareDirectory));
SystemVersion VerifyAndGetVersionZip(ZipArchive archive) SystemVersion VerifyAndGetVersionZip(ZipArchive archive)
{ {
@ -925,8 +911,6 @@ namespace Ryujinx.HLE.FileSystem
return systemVersion; return systemVersion;
} }
return null;
} }
public SystemVersion GetCurrentFirmwareVersion() public SystemVersion GetCurrentFirmwareVersion()

View File

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

View File

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

View File

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

View File

@ -105,7 +105,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pl
titleName = "Unknown"; 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 else

View File

@ -23,7 +23,7 @@ namespace Ryujinx.HLE.HOS.Services.Ssl
{ {
private const long CertStoreTitleId = 0x0100000000000800; 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; private static BuiltInCertificateManager _instance;

View File

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

View File

@ -36,7 +36,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
return new SteadyClockTimePoint return new SteadyClockTimePoint
{ {
TimePoint = 0, 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 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 VirtualFileSystem _virtualFileSystem;
private IntegrityCheckLevel _fsIntegrityCheckLevel; private IntegrityCheckLevel _fsIntegrityCheckLevel;

View File

@ -27,7 +27,7 @@ namespace Ryujinx.HLE
public TamperMachine TamperMachine { get; } public TamperMachine TamperMachine { get; }
public IHostUIHandler UIHandler { get; } public IHostUIHandler UIHandler { get; }
public bool EnableDeviceVsync { get; set; } = true; public bool EnableDeviceVsync { get; set; }
public bool IsFrameAvailable => Gpu.Window.IsFrameAvailable; public bool IsFrameAvailable => Gpu.Window.IsFrameAvailable;

View File

@ -1,6 +1,8 @@
using DiscordRPC; using DiscordRPC;
using Humanizer; using Humanizer;
using LibHac.Bcat;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.HLE.Loaders.Processes;
using Ryujinx.UI.App.Common; using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Configuration;
using System.Collections.Generic; using System.Collections.Generic;
@ -13,7 +15,7 @@ namespace Ryujinx.UI.Common
{ {
public static Timestamps StartedAt { get; set; } 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 string ApplicationId = "1293250299716173864";
private const int ApplicationByteLimit = 128; private const int ApplicationByteLimit = 128;
@ -29,7 +31,7 @@ namespace Ryujinx.UI.Common
Assets = new Assets Assets = new Assets
{ {
LargeImageKey = "ryujinx", LargeImageKey = "ryujinx",
LargeImageText = Description LargeImageText = TruncateToByteLength(_description)
}, },
Details = "Main Menu", Details = "Main Menu",
State = "Idling", State = "Idling",
@ -62,38 +64,34 @@ namespace Ryujinx.UI.Common
} }
} }
private static readonly string[] _discordGameAssets = [ public static void SwitchToPlayingState(ApplicationMetadata appMeta, ProcessResult procRes)
"0100f2c0115b6000", // Tears of the Kingdom
"0100744001588000", // Cars 3: Driven to Win
];
public static void SwitchToPlayingState(string titleId, ApplicationMetadata appMeta)
{ {
_discordClient?.SetPresence(new RichPresence _discordClient?.SetPresence(new RichPresence
{ {
Assets = new Assets Assets = new Assets
{ {
LargeImageKey = _discordGameAssets.Contains(titleId.ToLower()) ? titleId : "game", LargeImageKey = _discordGameAssetKeys.Contains(procRes.ProgramIdText.ToLower()) ? procRes.ProgramIdText : "game",
LargeImageText = TruncateToByteLength(appMeta.Title, ApplicationByteLimit), LargeImageText = TruncateToByteLength($"{appMeta.Title} | {procRes.DisplayVersion}"),
SmallImageKey = "ryujinx", SmallImageKey = "ryujinx",
SmallImageText = Description SmallImageText = TruncateToByteLength(_description)
}, },
Details = TruncateToByteLength($"Playing {appMeta.Title}", ApplicationByteLimit), Details = TruncateToByteLength($"Playing {appMeta.Title}"),
State = $"Total play time: {appMeta.TimePlayed.Humanize(2, false)}", State = appMeta.LastPlayed.HasValue
? $"Total play time: {appMeta.TimePlayed.Humanize(2, false)}"
: "Never played",
Timestamps = Timestamps.Now 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; return input;
} }
// Find the length to trim the string to guarantee we have space for the trailing ellipsis. // 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. // Make sure the string is long enough to perform the basic trim.
// Amount of bytes != Length of the string // Amount of bytes != Length of the string
@ -116,5 +114,54 @@ namespace Ryujinx.UI.Common
{ {
_discordClient?.Dispose(); _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 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")] [SupportedOSPlatform("linux")]
private static readonly string _mimeDbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "mime"); 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; 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(); string baseApplicationExtension = Path.GetExtension(baseApplicationPath).ToLowerInvariant();
// NOTE: We don't force homebrew developers to install a system firmware. // NOTE: We don't force homebrew developers to install a system firmware.
if (baseApplicationExtension == ".nro" || baseApplicationExtension == ".nso") if (baseApplicationExtension is not (".nro" or ".nso"))
{ return IsFirmwareValid(contentManager, out error);
error = UserError.Success;
return true; error = UserError.Success;
}
return IsFirmwareValid(contentManager, out error);
} }
error = UserError.ApplicationNotFound; error = UserError.ApplicationNotFound;
return false; return error is UserError.Success;
} }
} }
} }

View File

@ -140,11 +140,11 @@ namespace Ryujinx.UI.Common.Helper
argsList.Add($"\"{appFilePath}\""); argsList.Add($"\"{appFilePath}\"");
return String.Join(" ", argsList); return string.Join(" ", argsList);
} }
/// <summary> /// <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> /// </summary>
/// <param name="source">The source bitmap image that will be saved as an .ico file</param> /// <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> /// <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

@ -87,10 +87,7 @@ namespace Ryujinx.UI.Common.Helper
foreach (string path in titleUpdateMetadata.Paths) foreach (string path in titleUpdateMetadata.Paths)
{ {
if (!File.Exists(path)) if (!File.Exists(path)) continue;
{
continue;
}
try try
{ {
@ -99,22 +96,15 @@ namespace Ryujinx.UI.Common.Helper
Dictionary<ulong, ContentMetaData> updates = Dictionary<ulong, ContentMetaData> updates =
pfs.GetContentData(ContentMetaType.Patch, vfs, checkLevel); pfs.GetContentData(ContentMetaType.Patch, vfs, checkLevel);
Nca patchNca = null;
Nca controlNca = null;
if (!updates.TryGetValue(applicationIdBase, out ContentMetaData content)) if (!updates.TryGetValue(applicationIdBase, out ContentMetaData content))
{
continue; continue;
}
patchNca = content.GetNcaByType(vfs.KeySet, ContentType.Program); Nca patchNca = content.GetNcaByType(vfs.KeySet, ContentType.Program);
controlNca = content.GetNcaByType(vfs.KeySet, ContentType.Control); Nca controlNca = content.GetNcaByType(vfs.KeySet, ContentType.Control);
if (controlNca == null || patchNca == null) if (controlNca is null || patchNca is null)
{
continue; continue;
}
ApplicationControlProperty controlData = new(); ApplicationControlProperty controlData = new();
using UniqueRef<IFile> nacpFile = new(); using UniqueRef<IFile> nacpFile = new();
@ -138,7 +128,7 @@ namespace Ryujinx.UI.Common.Helper
catch (InvalidDataException) catch (InvalidDataException)
{ {
Logger.Warning?.Print(LogClass.Application, 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) catch (IOException exception)
{ {
@ -154,9 +144,7 @@ namespace Ryujinx.UI.Common.Helper
return result; return result;
} }
private static string PathToGameUpdatesJson(ulong applicationIdBase) private static string PathToGameUpdatesJson(ulong applicationIdBase)
{ => Path.Combine(AppDataManager.GamesDirPath, applicationIdBase.ToString("x16"), "updates.json");
return 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_Amiibo.png" />
<None Remove="Resources\Logo_Discord.png" /> <None Remove="Resources\Logo_Discord.png" />
<None Remove="Resources\Logo_GitHub.png" /> <None Remove="Resources\Logo_GitHub.png" />
<None Remove="Resources\Logo_Patreon.png" />
<None Remove="Resources\Logo_Ryujinx.png" /> <None Remove="Resources\Logo_Ryujinx.png" />
<None Remove="Resources\Logo_Twitter.png" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -39,10 +37,6 @@
<EmbeddedResource Include="Resources\Logo_Discord_Light.png" /> <EmbeddedResource Include="Resources\Logo_Discord_Light.png" />
<EmbeddedResource Include="Resources\Logo_GitHub_Dark.png" /> <EmbeddedResource Include="Resources\Logo_GitHub_Dark.png" />
<EmbeddedResource Include="Resources\Logo_GitHub_Light.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>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64' OR '$(RuntimeIdentifier)' == 'linux-arm64' OR '$(RuntimeIdentifier)' == ''"> <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() public static SystemInfo Gather()
{ {
if (OperatingSystem.IsWindows()) if (OperatingSystem.IsWindows())
{
return new WindowsSystemInfo(); return new WindowsSystemInfo();
}
else if (OperatingSystem.IsLinux()) if (OperatingSystem.IsLinux())
{
return new LinuxSystemInfo(); return new LinuxSystemInfo();
}
else if (OperatingSystem.IsMacOS()) if (OperatingSystem.IsMacOS())
{
return new MacOSSystemInfo(); return new MacOSSystemInfo();
}
Logger.Error?.Print(LogClass.Application, "SystemInfo unsupported on this platform"); 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)] [StructLayout(LayoutKind.Sequential)]

View File

@ -71,8 +71,7 @@ namespace Ryujinx.Ava
if (result == UserResult.Yes) if (result == UserResult.Yes)
{ {
var path = Environment.ProcessPath; _ = Process.Start(Environment.ProcessPath!, CommandLineState.Arguments);
var proc = Process.Start(path, CommandLineState.Arguments);
desktop.Shutdown(); desktop.Shutdown();
Environment.Exit(0); Environment.Exit(0);
} }
@ -113,7 +112,7 @@ namespace Ryujinx.Ava
} }
catch (Exception) 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(); ShowRestartDialog();
} }

View File

@ -367,12 +367,12 @@ namespace Ryujinx.Ava
} }
var colorType = e.IsBgra ? SKColorType.Bgra8888 : SKColorType.Rgba8888; 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); Marshal.Copy(e.Data, 0, bitmap.GetPixels(), e.Data.Length);
using SKBitmap bitmapToSave = new SKBitmap(bitmap.Width, bitmap.Height); using SKBitmap bitmapToSave = new(bitmap.Width, bitmap.Height);
using SKCanvas canvas = new SKCanvas(bitmapToSave); using SKCanvas canvas = new(bitmapToSave);
canvas.Clear(SKColors.Black); canvas.Clear(SKColors.Black);
@ -785,12 +785,11 @@ namespace Ryujinx.Ava
return false; return false;
} }
ApplicationMetadata appMeta = ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata => ApplicationMetadata appMeta = ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText,
{ appMetadata => appMetadata.UpdatePreGame()
appMetadata.UpdatePreGame(); );
});
DiscordIntegrationModule.SwitchToPlayingState(Device.Processes.ActiveApplication.ProgramIdText, appMeta); DiscordIntegrationModule.SwitchToPlayingState(appMeta, Device.Processes.ActiveApplication);
return true; return true;
} }
@ -825,7 +824,7 @@ namespace Ryujinx.Ava
{ {
renderer = new VulkanRenderer( renderer = new VulkanRenderer(
Vk.GetApi(), Vk.GetApi(),
(RendererHost.EmbeddedWindow as EmbeddedWindowVulkan).CreateSurface, (RendererHost.EmbeddedWindow as EmbeddedWindowVulkan)!.CreateSurface,
VulkanHelper.GetRequiredInstanceExtensions, VulkanHelper.GetRequiredInstanceExtensions,
ConfigurationState.Instance.Graphics.PreferredGpu.Value); ConfigurationState.Instance.Graphics.PreferredGpu.Value);
} }
@ -845,36 +844,39 @@ namespace Ryujinx.Ava
Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend Threading ({threadingMode}): {isGALThreaded}"); Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend Threading ({threadingMode}): {isGALThreaded}");
// Initialize Configuration. // 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, Device = new Switch(new HLEConfiguration(
_viewModel.LibHacHorizonManager, VirtualFileSystem,
ContentManager, _viewModel.LibHacHorizonManager,
_accountManager, ContentManager,
_userChannelPersistence, _accountManager,
renderer, _userChannelPersistence,
InitializeAudio(), renderer,
memoryConfiguration, InitializeAudio(),
_viewModel.UiHandler, memoryConfiguration,
(SystemLanguage)ConfigurationState.Instance.System.Language.Value, _viewModel.UiHandler,
(RegionCode)ConfigurationState.Instance.System.Region.Value, (SystemLanguage)ConfigurationState.Instance.System.Language.Value,
ConfigurationState.Instance.Graphics.EnableVsync, (RegionCode)ConfigurationState.Instance.System.Region.Value,
ConfigurationState.Instance.System.EnableDockedMode, ConfigurationState.Instance.Graphics.EnableVsync,
ConfigurationState.Instance.System.EnablePtc, ConfigurationState.Instance.System.EnableDockedMode,
ConfigurationState.Instance.System.EnableInternetAccess, ConfigurationState.Instance.System.EnablePtc,
ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None, ConfigurationState.Instance.System.EnableInternetAccess,
ConfigurationState.Instance.System.FsGlobalAccessLogMode, ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None,
ConfigurationState.Instance.System.SystemTimeOffset, ConfigurationState.Instance.System.FsGlobalAccessLogMode,
ConfigurationState.Instance.System.TimeZone, ConfigurationState.Instance.System.SystemTimeOffset,
ConfigurationState.Instance.System.MemoryManagerMode, ConfigurationState.Instance.System.TimeZone,
ConfigurationState.Instance.System.IgnoreMissingServices, ConfigurationState.Instance.System.MemoryManagerMode,
ConfigurationState.Instance.Graphics.AspectRatio, ConfigurationState.Instance.System.IgnoreMissingServices,
ConfigurationState.Instance.System.AudioVolume, ConfigurationState.Instance.Graphics.AspectRatio,
ConfigurationState.Instance.System.UseHypervisor, ConfigurationState.Instance.System.AudioVolume,
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value, ConfigurationState.Instance.System.UseHypervisor,
ConfigurationState.Instance.Multiplayer.Mode); ConfigurationState.Instance.Multiplayer.LanInterfaceId,
ConfigurationState.Instance.Multiplayer.Mode
Device = new Switch(configuration); )
);
} }
private static IHardwareDeviceDriver InitializeAudio() private static IHardwareDeviceDriver InitializeAudio()

View File

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

View File

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

View File

@ -36,7 +36,7 @@ namespace Ryujinx.Ava
private const uint MbIconwarning = 0x30; private const uint MbIconwarning = 0x30;
public static void Main(string[] args) public static int Main(string[] args)
{ {
Version = ReleaseInformation.Version; Version = ReleaseInformation.Version;
@ -51,7 +51,7 @@ namespace Ryujinx.Ava
LoggerAdapter.Register(); LoggerAdapter.Register();
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); return BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
} }
public static AppBuilder BuildAvaloniaApp() public static AppBuilder BuildAvaloniaApp()
@ -182,41 +182,33 @@ namespace Ryujinx.Ava
UseHardwareAcceleration = ConfigurationState.Instance.EnableHardwareAcceleration.Value; UseHardwareAcceleration = ConfigurationState.Instance.EnableHardwareAcceleration.Value;
// Check if graphics backend was overridden // Check if graphics backend was overridden
if (CommandLineState.OverrideGraphicsBackend != null) if (CommandLineState.OverrideGraphicsBackend is not null)
{ ConfigurationState.Instance.Graphics.GraphicsBackend.Value = CommandLineState.OverrideGraphicsBackend.ToLower() switch
if (CommandLineState.OverrideGraphicsBackend.ToLower() == "opengl")
{ {
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.OpenGl; "opengl" => GraphicsBackend.OpenGl,
} "vulkan" => GraphicsBackend.Vulkan,
else if (CommandLineState.OverrideGraphicsBackend.ToLower() == "vulkan") _ => ConfigurationState.Instance.Graphics.GraphicsBackend
{ };
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.Vulkan;
}
}
// Check if docked mode was overriden. // Check if docked mode was overriden.
if (CommandLineState.OverrideDockedMode.HasValue) if (CommandLineState.OverrideDockedMode.HasValue)
{
ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value; ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value;
}
// Check if HideCursor was overridden. // Check if HideCursor was overridden.
if (CommandLineState.OverrideHideCursor is not null) 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, "never" => HideCursorMode.Never,
"onidle" => HideCursorMode.OnIdle, "onidle" => HideCursorMode.OnIdle,
"always" => HideCursorMode.Always, "always" => HideCursorMode.Always,
_ => ConfigurationState.Instance.HideCursor.Value, _ => ConfigurationState.Instance.HideCursor,
}; };
}
// Check if hardware-acceleration was overridden. // Check if hardware-acceleration was overridden.
if (CommandLineState.OverrideHardwareAcceleration != null) if (CommandLineState.OverrideHardwareAcceleration != null)
{
UseHardwareAcceleration = CommandLineState.OverrideHardwareAcceleration.Value; UseHardwareAcceleration = CommandLineState.OverrideHardwareAcceleration.Value;
}
} }
private static void PrintSystemInfo() private static void PrintSystemInfo()

View File

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

View File

@ -1,22 +1,18 @@
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.UI.Common; using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Helper;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Helpers namespace Ryujinx.Ava.UI.Helpers
{ {
internal class UserErrorDialog internal class UserErrorDialog
{ {
private const string SetupGuideUrl = "https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide";
private static string GetErrorCode(UserError error) private static string GetErrorCode(UserError error)
{ {
return $"RYU-{(uint)error:X4}"; return $"RYU-{(uint)error:X4}";
} }
private static string GetErrorTitle(UserError error) private static string GetErrorTitle(UserError error) =>
{ error switch
return error switch
{ {
UserError.NoKeys => LocaleManager.Instance[LocaleKeys.UserErrorNoKeys], UserError.NoKeys => LocaleManager.Instance[LocaleKeys.UserErrorNoKeys],
UserError.NoFirmware => LocaleManager.Instance[LocaleKeys.UserErrorNoFirmware], UserError.NoFirmware => LocaleManager.Instance[LocaleKeys.UserErrorNoFirmware],
@ -25,11 +21,9 @@ namespace Ryujinx.Ava.UI.Helpers
UserError.Unknown => LocaleManager.Instance[LocaleKeys.UserErrorUnknown], UserError.Unknown => LocaleManager.Instance[LocaleKeys.UserErrorUnknown],
_ => LocaleManager.Instance[LocaleKeys.UserErrorUndefined], _ => LocaleManager.Instance[LocaleKeys.UserErrorUndefined],
}; };
}
private static string GetErrorDescription(UserError error) private static string GetErrorDescription(UserError error) =>
{ error switch
return error switch
{ {
UserError.NoKeys => LocaleManager.Instance[LocaleKeys.UserErrorNoKeysDescription], UserError.NoKeys => LocaleManager.Instance[LocaleKeys.UserErrorNoKeysDescription],
UserError.NoFirmware => LocaleManager.Instance[LocaleKeys.UserErrorNoFirmwareDescription], UserError.NoFirmware => LocaleManager.Instance[LocaleKeys.UserErrorNoFirmwareDescription],
@ -38,53 +32,17 @@ namespace Ryujinx.Ava.UI.Helpers
UserError.Unknown => LocaleManager.Instance[LocaleKeys.UserErrorUnknownDescription], UserError.Unknown => LocaleManager.Instance[LocaleKeys.UserErrorUnknownDescription],
_ => LocaleManager.Instance[LocaleKeys.UserErrorUndefinedDescription], _ => 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) public static async Task ShowUserErrorDialog(UserError error)
{ {
string errorCode = GetErrorCode(error); string errorCode = GetErrorCode(error);
bool isInSetupGuide = IsCoveredBySetupGuide(error); await ContentDialogHelper.CreateInfoDialog(
string setupButtonLabel = isInSetupGuide ? LocaleManager.Instance[LocaleKeys.OpenSetupGuideMessage] : "";
var result = await ContentDialogHelper.CreateInfoDialog(
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogUserErrorDialogMessage, errorCode, GetErrorTitle(error)), LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogUserErrorDialogMessage, errorCode, GetErrorTitle(error)),
GetErrorDescription(error) + (isInSetupGuide GetErrorDescription(error),
? LocaleManager.Instance[LocaleKeys.DialogUserErrorDialogInfoMessage] "",
: ""), setupButtonLabel, LocaleManager.Instance[LocaleKeys.InputDialogOk], LocaleManager.Instance[LocaleKeys.InputDialogOk],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogUserErrorDialogTitle, errorCode)); 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 _githubLogo;
private Bitmap _discordLogo; private Bitmap _discordLogo;
private Bitmap _patreonLogo;
private Bitmap _twitterLogo;
private string _version; private string _version;
private string _supporters;
public Bitmap GithubLogo 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 public string Version
{ {
get => _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() public AboutWindowViewModel()
{ {
Version = Program.Version; Version = Program.Version;
UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle.Value); UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle.Value);
Dispatcher.UIThread.InvokeAsync(DownloadPatronsJson);
ThemeManager.ThemeChanged += ThemeManager_ThemeChanged; ThemeManager.ThemeChanged += ThemeManager_ThemeChanged;
} }
@ -108,8 +74,6 @@ namespace Ryujinx.Ava.UI.ViewModels
GithubLogo = LoadBitmap($"{basePath}Logo_GitHub_{themeSuffix}?assembly=Ryujinx.UI.Common"); GithubLogo = LoadBitmap($"{basePath}Logo_GitHub_{themeSuffix}?assembly=Ryujinx.UI.Common");
DiscordLogo = LoadBitmap($"{basePath}Logo_Discord_{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) private Bitmap LoadBitmap(string uri)
@ -122,28 +86,5 @@ namespace Ryujinx.Ava.UI.ViewModels
ThemeManager.ThemeChanged -= ThemeManager_ThemeChanged; ThemeManager.ThemeChanged -= ThemeManager_ThemeChanged;
GC.SuppressFinalize(this); 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 public class AmiiboWindowViewModel : BaseModel, IDisposable
{ {
// ReSharper disable once InconsistentNaming
private static bool _cachedUseRandomUuid;
private const string DefaultJson = "{ \"amiibo\": [] }"; private const string DefaultJson = "{ \"amiibo\": [] }";
private const float AmiiboImageSize = 350f; private const float AmiiboImageSize = 350f;
@ -41,7 +44,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private int _seriesSelectedIndex; private int _seriesSelectedIndex;
private bool _enableScanning; private bool _enableScanning;
private bool _showAllAmiibo; private bool _showAllAmiibo;
private bool _useRandomUuid; private bool _useRandomUuid = _cachedUseRandomUuid;
private string _usage; private string _usage;
private static readonly AmiiboJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); private static readonly AmiiboJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
@ -82,7 +85,7 @@ namespace Ryujinx.Ava.UI.ViewModels
get => _useRandomUuid; get => _useRandomUuid;
set set
{ {
_useRandomUuid = value; _cachedUseRandomUuid = _useRandomUuid = value;
OnPropertyChanged(); OnPropertyChanged();
} }

View File

@ -911,7 +911,8 @@ namespace Ryujinx.Ava.UI.ViewModels
public KeyGesture PauseKey public KeyGesture PauseKey
{ {
get => KeyGesture.Parse(_pauseKey); set get => KeyGesture.Parse(_pauseKey);
set
{ {
_pauseKey = value.ToString(); _pauseKey = value.ToString();

View File

@ -83,18 +83,13 @@
LineHeight="12" LineHeight="12"
Text="{Binding Version}" Text="{Binding Version}"
TextAlignment="Center" /> TextAlignment="Center" />
<Button <Border
Padding="5"
HorizontalAlignment="Center" Height="1"
Background="Transparent" Margin="0,20, 0, 20"
Click="Button_OnClick" HorizontalAlignment="Stretch"
Tag="https://github.com/Ryujinx/Ryujinx/wiki/Changelog#ryujinx-changelog"> BorderBrush="{DynamicResource ThemeControlBorderColor}"
<TextBlock BorderThickness="0,1,0,0" />
FontSize="10"
Text="{locale:Locale AboutChangelogButton}"
TextAlignment="Center"
ToolTip.Tip="{locale:Locale AboutChangelogButtonTooltipMessage}" />
</Button>
</StackPanel> </StackPanel>
<StackPanel <StackPanel
Grid.Row="2" Grid.Row="2"
@ -123,7 +118,7 @@
HorizontalAlignment="Center" HorizontalAlignment="Center"
Orientation="Horizontal" Orientation="Horizontal"
Spacing="10"> Spacing="10">
<Button <Button Name="GitHubRepoButton"
MinWidth="30" MinWidth="30"
MinHeight="30" MinHeight="30"
MaxWidth="30" MaxWidth="30"
@ -132,20 +127,6 @@
Background="Transparent" Background="Transparent"
Click="Button_OnClick" Click="Button_OnClick"
CornerRadius="15" 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}"> ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}">
<Image Source="{Binding GithubLogo}" /> <Image Source="{Binding GithubLogo}" />
</Button> </Button>
@ -158,36 +139,10 @@
Background="Transparent" Background="Transparent"
Click="Button_OnClick" Click="Button_OnClick"
CornerRadius="15" CornerRadius="15"
Tag="https://discordapp.com/invite/N2FmfVc" Tag="https://discord.gg/dHPrkBkkyA"
ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}"> ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}">
<Image Source="{Binding DiscordLogo}" /> <Image Source="{Binding DiscordLogo}" />
</Button> </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>
</StackPanel> </StackPanel>
</Grid> </Grid>
@ -205,7 +160,6 @@
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<StackPanel <StackPanel
Grid.Row="0" Grid.Row="0"
@ -237,7 +191,7 @@
HorizontalAlignment="Left" HorizontalAlignment="Left"
Background="Transparent" Background="Transparent"
Click="Button_OnClick" Click="Button_OnClick"
Tag="https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a"> Tag="https://github.com/GreemDev/Ryujinx/graphs/contributors?type=a">
<TextBlock <TextBlock
FontSize="10" FontSize="10"
Text="{locale:Locale AboutRyujinxContributorsButtonHeader}" Text="{locale:Locale AboutRyujinxContributorsButtonHeader}"
@ -245,26 +199,6 @@
ToolTip.Tip="{locale:Locale AboutRyujinxMaintainersContentTooltipMessage}" /> ToolTip.Tip="{locale:Locale AboutRyujinxMaintainersContentTooltipMessage}" />
</Button> </Button>
</StackPanel> </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>
</Grid> </Grid>
</UserControl> </UserControl>

View File

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

@ -43,8 +43,7 @@ namespace Ryujinx.Ava.UI.Windows
{ {
if (ViewModel.AmiiboSelectedIndex > -1) if (ViewModel.AmiiboSelectedIndex > -1)
{ {
AmiiboApi amiibo = ViewModel.AmiiboList[ViewModel.AmiiboSelectedIndex]; ScannedAmiibo = ViewModel.AmiiboList[ViewModel.AmiiboSelectedIndex];
ScannedAmiibo = amiibo;
IsScanned = true; IsScanned = true;
Close(); Close();
} }

View File

@ -51,7 +51,7 @@ namespace Ryujinx.Ava.UI.Windows
_enabledCheatsPath = Path.Combine(titleModsPath, "cheats", "enabled.txt"); _enabledCheatsPath = Path.Combine(titleModsPath, "cheats", "enabled.txt");
string[] enabled = Array.Empty<string>(); string[] enabled = [];
if (File.Exists(_enabledCheatsPath)) if (File.Exists(_enabledCheatsPath))
{ {
@ -101,24 +101,13 @@ namespace Ryujinx.Ava.UI.Windows
public void Save() public void Save()
{ {
if (NoCheatsFound) if (NoCheatsFound)
{
return; 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) Directory.CreateDirectory(Path.GetDirectoryName(_enabledCheatsPath)!);
{
foreach (var cheat in cheats.SubNodes)
{
if (cheat.IsEnabled)
{
enabledCheats.Add(cheat.BuildIdKey);
}
}
}
Directory.CreateDirectory(Path.GetDirectoryName(_enabledCheatsPath));
File.WriteAllLines(_enabledCheatsPath, enabledCheats); File.WriteAllLines(_enabledCheatsPath, enabledCheats);

View File

@ -443,10 +443,7 @@ namespace Ryujinx.Ava.UI.Windows
Initialize(); Initialize();
/// <summary> PlatformSettings!.ColorValuesChanged += OnPlatformColorValuesChanged;
/// Subscribe to the ColorValuesChanged event
/// </summary>
PlatformSettings.ColorValuesChanged += OnPlatformColorValuesChanged;
ViewModel.Initialize( ViewModel.Initialize(
ContentManager, ContentManager,
@ -467,7 +464,7 @@ namespace Ryujinx.Ava.UI.Windows
_appLibraryAppsSubscription?.Dispose(); _appLibraryAppsSubscription?.Dispose();
_appLibraryAppsSubscription = ApplicationLibrary.Applications _appLibraryAppsSubscription = ApplicationLibrary.Applications
.Connect() .Connect()
.ObserveOn(SynchronizationContext.Current) .ObserveOn(SynchronizationContext.Current!)
.Bind(ViewModel.Applications) .Bind(ViewModel.Applications)
.Subscribe(); .Subscribe();
@ -656,28 +653,20 @@ namespace Ryujinx.Ava.UI.Windows
applicationLibraryThread.Start(); 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) if (msg is null) return;
{
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;
}
return Dispatcher.UIThread.InvokeAsync(async () => Dispatcher.UIThread.InvokeAsync(async () =>
{ {
await ContentDialogHelper.ShowTextDialog(LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle], await ContentDialogHelper.ShowTextDialog(LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle],
msg, "", "", "", LocaleManager.Instance[LocaleKeys.InputDialogOk], (int)Symbol.Checkmark); msg, "", "", "", LocaleManager.Instance[LocaleKeys.InputDialogOk], (int)Symbol.Checkmark);

View File

@ -17,9 +17,9 @@ namespace Ryujinx.Ava.UI.Windows
public StyleableWindow() public StyleableWindow()
{ {
WindowStartupLocation = WindowStartupLocation.CenterOwner; 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); Icon = new WindowIcon(stream);
stream.Position = 0; stream.Position = 0;