Compare commits

..

15 Commits

Author SHA1 Message Date
gdkchan
a0af6e4d07 Use unique temporary variables for function call parameters on SPIR-V (#5757)
* Use unique temporary variables for function call parameters on SPIR-V

* Shader cache version bump
2023-10-04 19:46:11 -03:00
jcm
f61b7818c3 Avalonia: Add macOS check for Color Space Passthrough (#5754)
* add macOS check for color passthrough

* use existing IsMacOS property

---------

Co-authored-by: jcm <butt@butts.com>
2023-10-04 19:15:37 +02:00
gdkchan
a2a97e1b11 Implement textureSamples texture query shader instruction (#5750)
* Implement textureSamples texture query shader instruction

* Shader cache version bump
2023-10-03 22:43:11 +00:00
gdkchan
8b2625b0be Decrement nvmap reference count on surface flinger prealloc (#5753) 2023-10-02 22:13:29 +00:00
gdkchan
651e24fed9 Signal friends completion event and stub CheckBlockedUserListAvailability (#5743) 2023-09-29 13:24:44 +02:00
gdkchan
41b104d0fb Fix audio renderer compressor effect (#5742)
* Delete DecibelToLinearExtended and fix Log10 function

* Fix CopyBuffer and ClearBuffer

* Change effect states from class to struct + formatting

* Formatting

* Make UpdateLowPassFilter readonly

* More compressor fixes
2023-09-29 10:48:49 +00:00
dependabot[bot]
bc44b85b0b nuget: bump FluentAvaloniaUI from 2.0.1 to 2.0.4 (#5729)
* nuget: bump FluentAvaloniaUI from 2.0.1 to 2.0.4

Bumps [FluentAvaloniaUI](https://github.com/amwx/FluentAvalonia) from 2.0.1 to 2.0.4.
- [Commits](https://github.com/amwx/FluentAvalonia/commits)

---
updated-dependencies:
- dependency-name: FluentAvaloniaUI
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update Directory.Packages.props

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
2023-09-28 00:15:45 +02:00
gdkchan
01c2b8097c Implement NGC service (#5681)
* Implement NGC service

* Use raw byte arrays instead of string for _wordSeparators

* Silence IDE0230 for _wordSeparators

* Try to silence warning about _rangeValuesCount not being read on SparseSet

* Make AcType enum private

* Add abstract methods and one TODO that I forgot

* PR feedback

* More PR feedback

* More PR feedback
2023-09-27 19:21:26 +02:00
dependabot[bot]
4bd2ca3f0d nuget: bump System.IdentityModel.Tokens.Jwt from 6.31.0 to 7.0.0 (#5730)
Bumps [System.IdentityModel.Tokens.Jwt](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 6.31.0 to 7.0.0.
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/6.31.0...7.0.0)

---
updated-dependencies:
- dependency-name: System.IdentityModel.Tokens.Jwt
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-27 19:03:41 +02:00
riperiperi
e63157cc33 GPU: Don't create tracking handles for buffer textures (#5727)
* GPU: Don't create tracking handles for buffer textures

Buffer texture memory is handled by the buffer cache - the texture shouldn't create any tracking handles as they aren't used. This change simply makes them create and iterate 0 tracking handles, while keeping the rest of the texture group around.

This prevents a possible issue where many buffer textures are created as views of overlapping buffer ranges, and virtual regions have many dependant textures that don't actually contribute anything to handle state.

Should improve performance in Mortal Kombat 1, possibly certain UE4 games when FIFO raises to 100%.

* Fix interval tree bug

* Don't check view compatibility for buffer textures
2023-09-26 12:37:10 -03:00
Ac_K
7f2fb049f5 Ava: Fix regressions by rewriting CheckLaunchState (#5728) 2023-09-26 07:17:55 +02:00
gdkchan
4744bde0e5 Reduce the amount of descriptor pool allocations on Vulkan (#5673)
* Reduce the amount of descriptor pool allocations on Vulkan

* Formatting

* Slice can be simplified

* Make GetDescriptorPoolSizes static

* Adjust CanFit calculation so that TryAllocateDescriptorSets never fails

* Remove unused field
2023-09-26 02:00:02 +02:00
gdkchan
4a835bb2b9 Make Vulkan memory allocator actually thread safe (#5575)
* Make Vulkan memory allocator actually thread safe

* Make free thread safe too

* PR feedback
2023-09-26 01:50:06 +02:00
gdkchan
ddc9ae2a83 Add VTimer as alternative interrupt method on Apple Hypervisor (#5663)
* Add VTimer as alternative interrupt method on Apple Hypervisor

* Fix naming violations on TimeApi

* Fix timer interval (was 16us rather than 16ms)

* Fix delta ticks calculation

* Missing ThrowOnError call

* Add SupportedOSPlatform attribute on AppleHv classes
2023-09-26 01:18:32 +02:00
Isaac Marovitz
d6d3cdd573 Ava UI: Refactor async usage (#5516)
* Remove `async void`

* Async LoadApplications

* Formatting and such

* Remove async from InstallUpdate

* Update src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Cleanup LoadApplications()

* Cleanup

* Formatting

* Revert some stuff

* Cleanup

* Update src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Ack suggestions

* Whitespace

* Fix Peri suggestion

* Add missing trailing commas

* Remove redundant method override

* Remove Dispatcher.UIThread.InvokeAsync/Post where possible

---------

Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
2023-09-26 00:04:58 +02:00
122 changed files with 5607 additions and 627 deletions

View File

@@ -3,18 +3,18 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia" Version="11.0.3" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.3" />
<PackageVersion Include="Avalonia.Desktop" Version="11.0.3" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.3" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.3" />
<PackageVersion Include="Avalonia.Svg" Version="11.0.0" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0" />
<PackageVersion Include="Avalonia" Version="11.0.4" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.4" />
<PackageVersion Include="Avalonia.Desktop" Version="11.0.4" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.4" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.4" />
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.2" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.2" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="1.1.7" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageVersion Include="DynamicData" Version="7.14.2" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.1" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.4" />
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.3.0-beta.4" />
@@ -44,7 +44,7 @@
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
<PackageVersion Include="SPB" Version="0.0.4-build28" />
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.31.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="7.0.0" />
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
<PackageVersion Include="System.Management" Version="7.0.2" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />

View File

@@ -189,7 +189,7 @@ namespace ARMeilleure.Translation
{
if (start.CompareTo(node.End) < 0)
{
if (overlaps.Length >= overlapCount)
if (overlaps.Length <= overlapCount)
{
Array.Resize(ref overlaps, overlapCount + ArrayGrowthSize);
}

View File

@@ -31,9 +31,18 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public bool IsEffectEnabled { get; }
public AuxiliaryBufferCommand(uint bufferOffset, byte inputBufferOffset, byte outputBufferOffset,
ref AuxiliaryBufferAddresses sendBufferInfo, bool isEnabled, uint countMax,
CpuAddress outputBuffer, CpuAddress inputBuffer, uint updateCount, uint writeOffset, int nodeId)
public AuxiliaryBufferCommand(
uint bufferOffset,
byte inputBufferOffset,
byte outputBufferOffset,
ref AuxiliaryBufferAddresses sendBufferInfo,
bool isEnabled,
uint countMax,
CpuAddress outputBuffer,
CpuAddress inputBuffer,
uint updateCount,
uint writeOffset,
int nodeId)
{
Enabled = true;
NodeId = nodeId;

View File

@@ -21,7 +21,14 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
private BiquadFilterParameter _parameter;
public BiquadFilterCommand(int baseIndex, ref BiquadFilterParameter filter, Memory<BiquadFilterState> biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, bool needInitialization, int nodeId)
public BiquadFilterCommand(
int baseIndex,
ref BiquadFilterParameter filter,
Memory<BiquadFilterState> biquadFilterStateMemory,
int inputBufferOffset,
int outputBufferOffset,
bool needInitialization,
int nodeId)
{
_parameter = filter;
BiquadFilterState = biquadFilterStateMemory;

View File

@@ -77,7 +77,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void ClearBuffer(int index)
{
Unsafe.InitBlock((void*)GetBufferPointer(index), 0, SampleCount);
Unsafe.InitBlock((void*)GetBufferPointer(index), 0, SampleCount * sizeof(float));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -89,7 +89,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void CopyBuffer(int outputBufferIndex, int inputBufferIndex)
{
Unsafe.CopyBlock((void*)GetBufferPointer(outputBufferIndex), (void*)GetBufferPointer(inputBufferIndex), SampleCount);
Unsafe.CopyBlock((void*)GetBufferPointer(outputBufferIndex), (void*)GetBufferPointer(inputBufferIndex), SampleCount * sizeof(float));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@@ -94,18 +94,18 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float newMean = inputMovingAverage.Update(FloatingPointHelper.MeanSquare(channelInput), _parameter.InputGain);
float y = FloatingPointHelper.Log10(newMean) * 10.0f;
float z = 0.0f;
float z = 1.0f;
bool unknown10OutOfRange = false;
bool unknown10OutOfRange = y >= state.Unknown10;
if (newMean < 1.0e-10f)
{
z = 1.0f;
y = -100.0f;
unknown10OutOfRange = state.Unknown10 < -100.0f;
unknown10OutOfRange = state.Unknown10 <= -100.0f;
}
if (y >= state.Unknown10 || unknown10OutOfRange)
if (unknown10OutOfRange)
{
float tmpGain;
@@ -118,7 +118,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
tmpGain = (y - state.Unknown10) * ((y - state.Unknown10) * -state.CompressorGainReduction);
}
z = FloatingPointHelper.DecibelToLinearExtended(tmpGain);
z = FloatingPointHelper.DecibelToLinear(tmpGain);
}
float unknown4New = z;

View File

@@ -88,7 +88,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
Matrix2x2 delayFeedback = new(delayFeedbackBaseGain, delayFeedbackCrossGain,
delayFeedbackCrossGain, delayFeedbackBaseGain);
delayFeedbackCrossGain, delayFeedbackBaseGain);
for (int i = 0; i < sampleCount; i++)
{
@@ -125,9 +125,9 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
Matrix4x4 delayFeedback = new(delayFeedbackBaseGain, delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f,
delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain,
delayFeedbackCrossGain, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain,
0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain);
delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain,
delayFeedbackCrossGain, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain,
0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain);
for (int i = 0; i < sampleCount; i++)
@@ -172,11 +172,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
Matrix6x6 delayFeedback = new(delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain, 0.0f, delayFeedbackCrossGain, 0.0f,
0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain,
delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, feedbackGain, 0.0f, 0.0f,
delayFeedbackCrossGain, 0.0f, 0.0f, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain,
0.0f, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain, delayFeedbackBaseGain);
0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain,
delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, feedbackGain, 0.0f, 0.0f,
delayFeedbackCrossGain, 0.0f, 0.0f, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain,
0.0f, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain, delayFeedbackBaseGain);
for (int i = 0; i < sampleCount; i++)
{

View File

@@ -28,7 +28,14 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
private LimiterParameter _parameter;
public LimiterCommandVersion2(uint bufferOffset, LimiterParameter parameter, Memory<LimiterState> state, Memory<EffectResultState> resultState, bool isEnabled, ulong workBuffer, int nodeId)
public LimiterCommandVersion2(
uint bufferOffset,
LimiterParameter parameter,
Memory<LimiterState> state,
Memory<EffectResultState> resultState,
bool isEnabled,
ulong workBuffer,
int nodeId)
{
Enabled = true;
NodeId = nodeId;

View File

@@ -79,53 +79,57 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ProcessReverbMono(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
{
ProcessReverbGeneric(ref state,
outputBuffers,
inputBuffers,
sampleCount,
_outputEarlyIndicesTableMono,
_targetEarlyDelayLineIndicesTableMono,
_targetOutputFeedbackIndicesTableMono,
_outputIndicesTableMono);
ProcessReverbGeneric(
ref state,
outputBuffers,
inputBuffers,
sampleCount,
_outputEarlyIndicesTableMono,
_targetEarlyDelayLineIndicesTableMono,
_targetOutputFeedbackIndicesTableMono,
_outputIndicesTableMono);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ProcessReverbStereo(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
{
ProcessReverbGeneric(ref state,
outputBuffers,
inputBuffers,
sampleCount,
_outputEarlyIndicesTableStereo,
_targetEarlyDelayLineIndicesTableStereo,
_targetOutputFeedbackIndicesTableStereo,
_outputIndicesTableStereo);
ProcessReverbGeneric(
ref state,
outputBuffers,
inputBuffers,
sampleCount,
_outputEarlyIndicesTableStereo,
_targetEarlyDelayLineIndicesTableStereo,
_targetOutputFeedbackIndicesTableStereo,
_outputIndicesTableStereo);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ProcessReverbQuadraphonic(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
{
ProcessReverbGeneric(ref state,
outputBuffers,
inputBuffers,
sampleCount,
_outputEarlyIndicesTableQuadraphonic,
_targetEarlyDelayLineIndicesTableQuadraphonic,
_targetOutputFeedbackIndicesTableQuadraphonic,
_outputIndicesTableQuadraphonic);
ProcessReverbGeneric(
ref state,
outputBuffers,
inputBuffers,
sampleCount,
_outputEarlyIndicesTableQuadraphonic,
_targetEarlyDelayLineIndicesTableQuadraphonic,
_targetOutputFeedbackIndicesTableQuadraphonic,
_outputIndicesTableQuadraphonic);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ProcessReverbSurround(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
{
ProcessReverbGeneric(ref state,
outputBuffers,
inputBuffers,
sampleCount,
_outputEarlyIndicesTableSurround,
_targetEarlyDelayLineIndicesTableSurround,
_targetOutputFeedbackIndicesTableSurround,
_outputIndicesTableSurround);
ProcessReverbGeneric(
ref state,
outputBuffers,
inputBuffers,
sampleCount,
_outputEarlyIndicesTableSurround,
_targetEarlyDelayLineIndicesTableSurround,
_targetOutputFeedbackIndicesTableSurround,
_outputIndicesTableSurround);
}
private unsafe void ProcessReverbGeneric(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount, ReadOnlySpan<int> outputEarlyIndicesTable, ReadOnlySpan<int> targetEarlyDelayLineIndicesTable, ReadOnlySpan<int> targetOutputFeedbackIndicesTable, ReadOnlySpan<int> outputIndicesTable)

View File

@@ -52,7 +52,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
{
// NOTE: Nintendo uses an approximation of log10, we don't.
// As such, we support the same ranges as Nintendo to avoid unexpected behaviours.
return MathF.Pow(10, MathF.Max(x, 1.0e-10f));
return MathF.Log10(MathF.Max(x, 1.0e-10f));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -62,7 +62,8 @@ namespace Ryujinx.Audio.Renderer.Dsp
foreach (float input in inputs)
{
res += (input * input);
float normInput = input * (1f / 32768f);
res += normInput * normInput;
}
res /= inputs.Length;
@@ -81,19 +82,6 @@ namespace Ryujinx.Audio.Renderer.Dsp
return MathF.Pow(10.0f, db / 20.0f);
}
/// <summary>
/// Map decibel to linear in [0, 2] range.
/// </summary>
/// <param name="db">The decibel value to convert</param>
/// <returns>Converted linear value in [0, 2] range</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DecibelToLinearExtended(float db)
{
float tmp = MathF.Log2(DecibelToLinear(db));
return MathF.Truncate(tmp) + MathF.Pow(2.0f, tmp - MathF.Truncate(tmp));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DegreesToRadians(float degrees)
{

View File

@@ -3,7 +3,7 @@ using Ryujinx.Audio.Renderer.Parameter.Effect;
namespace Ryujinx.Audio.Renderer.Dsp.State
{
public class CompressorState
public struct CompressorState
{
public ExponentialMovingAverage InputMovingAverage;
public float Unknown4;
@@ -45,7 +45,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
CompressorGainReduction = (1.0f - ratio) / Constants.ChannelCountMax;
Unknown10 = threshold - 1.5f;
Unknown14 = threshold + 1.5f;
OutputGain = FloatingPointHelper.DecibelToLinearExtended(parameter.OutputGain + makeupGain);
OutputGain = FloatingPointHelper.DecibelToLinear(parameter.OutputGain + makeupGain);
}
}
}

View File

@@ -4,7 +4,7 @@ using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Dsp.State
{
public class DelayState
public struct DelayState
{
public DelayLine[] DelayLines { get; }
public float[] LowPassZ { get; set; }
@@ -53,7 +53,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
LowPassBaseGain = 1.0f - LowPassFeedbackGain;
}
public void UpdateLowPassFilter(ref float tempRawRef, uint channelCount)
public readonly void UpdateLowPassFilter(ref float tempRawRef, uint channelCount)
{
for (int i = 0; i < channelCount; i++)
{

View File

@@ -4,7 +4,7 @@ using System;
namespace Ryujinx.Audio.Renderer.Dsp.State
{
public class LimiterState
public struct LimiterState
{
public ExponentialMovingAverage[] DetectorAverage;
public ExponentialMovingAverage[] CompressionGainAverage;

View File

@@ -4,7 +4,7 @@ using System;
namespace Ryujinx.Audio.Renderer.Dsp.State
{
public class Reverb3dState
public struct Reverb3dState
{
private readonly float[] _fdnDelayMinTimes = new float[4] { 5.0f, 6.0f, 13.0f, 14.0f };
private readonly float[] _fdnDelayMaxTimes = new float[4] { 45.704f, 82.782f, 149.94f, 271.58f };

View File

@@ -5,7 +5,7 @@ using System;
namespace Ryujinx.Audio.Renderer.Dsp.State
{
public class ReverbState
public struct ReverbState
{
private static readonly float[] _fdnDelayTimes = new float[20]
{
@@ -54,7 +54,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
// Room
0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.68f, 0.68f,
// Chamber
0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.68f, 0.68f, 0.68f, 0.68f,
0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.68f, 0.68f, 0.68f, 0.68f,
// Hall
0.50f, 0.70f, 0.70f, 0.68f, 0.50f, 0.68f, 0.68f, 0.70f, 0.68f, 0.00f,
// Cathedral

View File

@@ -104,7 +104,7 @@ namespace Ryujinx.Ava
{
"Light" => ThemeVariant.Light,
"Dark" => ThemeVariant.Dark,
_ => ThemeVariant.Default
_ => ThemeVariant.Default,
};
if (enableCustomTheme)

View File

@@ -3,7 +3,6 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
using Avalonia.Rendering;
using Avalonia.Threading;
using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.Dummy;

View File

@@ -145,7 +145,7 @@ namespace Ryujinx.Ava.Common
var result = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle],
AllowMultiple = false
AllowMultiple = false,
});
if (result.Count == 0)

View File

@@ -1,6 +1,5 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Input;
using System;

View File

@@ -82,12 +82,9 @@ namespace Ryujinx.Modules
{
Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!");
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
});
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
_running = false;
@@ -114,10 +111,9 @@ namespace Ryujinx.Modules
{
if (showVersionUpToDate)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], "");
});
await ContentDialogHelper.CreateUpdaterInfoDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
"");
}
_running = false;
@@ -134,10 +130,9 @@ namespace Ryujinx.Modules
{
if (showVersionUpToDate)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], "");
});
await ContentDialogHelper.CreateUpdaterInfoDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
"");
}
_running = false;
@@ -149,10 +144,8 @@ namespace Ryujinx.Modules
{
Logger.Error?.Print(LogClass.Application, exception.Message);
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]);
});
await ContentDialogHelper.CreateErrorDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]);
_running = false;
@@ -167,12 +160,9 @@ namespace Ryujinx.Modules
{
Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!");
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
});
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
_running = false;
@@ -183,10 +173,9 @@ namespace Ryujinx.Modules
{
if (showVersionUpToDate)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], "");
});
await ContentDialogHelper.CreateUpdaterInfoDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
"");
}
_running = false;
@@ -212,7 +201,7 @@ namespace Ryujinx.Modules
_buildSize = -1;
}
Dispatcher.UIThread.Post(async () =>
await Dispatcher.UIThread.InvokeAsync(async () =>
{
// Show a message asking the user if they want to update
var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog(
@@ -222,7 +211,7 @@ namespace Ryujinx.Modules
if (shouldUpdate)
{
UpdateRyujinx(mainWindow, _buildUrl);
await UpdateRyujinx(mainWindow, _buildUrl);
}
else
{
@@ -241,7 +230,7 @@ namespace Ryujinx.Modules
return result;
}
private static async void UpdateRyujinx(Window parent, string downloadUrl)
private static async Task UpdateRyujinx(Window parent, string downloadUrl)
{
_updateSuccessful = false;
@@ -579,27 +568,24 @@ namespace Ryujinx.Modules
}
}
private static async void InstallUpdate(TaskDialog taskDialog, string updateFile)
private static void InstallUpdate(TaskDialog taskDialog, string updateFile)
{
// Extract Update
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting];
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
await Task.Run(() =>
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
ExtractTarGzipFile(taskDialog, updateFile, _updateDir);
}
else if (OperatingSystem.IsWindows())
{
ExtractZipFile(taskDialog, updateFile, _updateDir);
}
else
{
throw new NotSupportedException();
}
});
ExtractTarGzipFile(taskDialog, updateFile, _updateDir);
}
else if (OperatingSystem.IsWindows())
{
ExtractZipFile(taskDialog, updateFile, _updateDir);
}
else
{
throw new NotSupportedException();
}
// Delete downloaded zip
File.Delete(updateFile);
@@ -613,36 +599,33 @@ namespace Ryujinx.Modules
if (!OperatingSystem.IsMacOS())
{
// Replace old files
await Task.Run(() =>
double count = 0;
foreach (string file in allFiles)
{
double count = 0;
foreach (string file in allFiles)
count++;
try
{
count++;
try
{
File.Move(file, file + ".ryuold");
File.Move(file, file + ".ryuold");
Dispatcher.UIThread.Post(() =>
{
taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal);
});
}
catch
Dispatcher.UIThread.InvokeAsync(() =>
{
Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file));
}
taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal);
});
}
Dispatcher.UIThread.Post(() =>
catch
{
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles];
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
});
Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file));
}
}
MoveAllFilesOver(_updatePublishDir, _homeDir, taskDialog);
Dispatcher.UIThread.InvokeAsync(() =>
{
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles];
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
});
MoveAllFilesOver(_updatePublishDir, _homeDir, taskDialog);
Directory.Delete(_updateDir, true);
}
@@ -658,12 +641,11 @@ namespace Ryujinx.Modules
{
if (showWarnings)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateWarningDialog(
Dispatcher.UIThread.InvokeAsync(() =>
ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedSubMessage]);
});
LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedSubMessage])
);
}
return false;
@@ -673,12 +655,11 @@ namespace Ryujinx.Modules
{
if (showWarnings)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateWarningDialog(
Dispatcher.UIThread.InvokeAsync(() =>
ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage]);
});
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage])
);
}
return false;
@@ -688,12 +669,11 @@ namespace Ryujinx.Modules
{
if (showWarnings)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateWarningDialog(
Dispatcher.UIThread.InvokeAsync(() =>
ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]);
});
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage])
);
}
return false;
@@ -705,21 +685,19 @@ namespace Ryujinx.Modules
{
if (ReleaseInformation.IsFlatHubBuild())
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateWarningDialog(
Dispatcher.UIThread.InvokeAsync(() =>
ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle],
LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage]);
});
LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage])
);
}
else
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateWarningDialog(
Dispatcher.UIThread.InvokeAsync(() =>
ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle],
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]);
});
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage])
);
}
}

View File

@@ -106,7 +106,7 @@ namespace Ryujinx.Ava.UI.Applet
bool error = false;
string inputText = args.InitialText ?? "";
Dispatcher.UIThread.Post(async () =>
Dispatcher.UIThread.InvokeAsync(async () =>
{
try
{
@@ -149,7 +149,7 @@ namespace Ryujinx.Ava.UI.Applet
bool showDetails = false;
Dispatcher.UIThread.Post(async () =>
Dispatcher.UIThread.InvokeAsync(async () =>
{
try
{

View File

@@ -54,7 +54,7 @@ namespace Ryujinx.Ava.UI.Controls
{
if (sender is MenuItem { DataContext: MainWindowViewModel viewModel })
{
OpenSaveDirectory(viewModel, SaveDataType.Account, userId: new UserId((ulong)viewModel.AccountManager.LastOpenedUser.UserId.High, (ulong)viewModel.AccountManager.LastOpenedUser.UserId.Low));
OpenSaveDirectory(viewModel, SaveDataType.Account, new UserId((ulong)viewModel.AccountManager.LastOpenedUser.UserId.High, (ulong)viewModel.AccountManager.LastOpenedUser.UserId.Low));
}
}
@@ -62,14 +62,14 @@ namespace Ryujinx.Ava.UI.Controls
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
OpenSaveDirectory(viewModel, SaveDataType.Device, userId: default);
OpenSaveDirectory(viewModel, SaveDataType.Device, default);
}
public void OpenBcatSaveDirectory_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
OpenSaveDirectory(viewModel, SaveDataType.Bcat, userId: default);
OpenSaveDirectory(viewModel, SaveDataType.Bcat, default);
}
private static void OpenSaveDirectory(MainWindowViewModel viewModel, SaveDataType saveDataType, UserId userId)
@@ -158,11 +158,12 @@ namespace Ryujinx.Ava.UI.Controls
if (viewModel?.SelectedApplication != null)
{
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DialogWarning],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.TitleName),
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogWarning],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.TitleName),
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
if (result == UserResult.Yes)
{
@@ -205,11 +206,12 @@ namespace Ryujinx.Ava.UI.Controls
if (viewModel?.SelectedApplication != null)
{
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DialogWarning],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.TitleName),
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogWarning],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.TitleName),
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
if (result == UserResult.Yes)
{
@@ -335,13 +337,13 @@ namespace Ryujinx.Ava.UI.Controls
}
}
public void RunApplication_Click(object sender, RoutedEventArgs args)
public async void RunApplication_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
viewModel.LoadApplication(viewModel.SelectedApplication.Path);
await viewModel.LoadApplication(viewModel.SelectedApplication.Path);
}
}
}

View File

@@ -212,9 +212,9 @@ namespace Ryujinx.Ava.UI.ViewModels
{
Patterns = new[] { "*.nsp" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" },
MimeTypes = new[] { "application/x-nx-nsp" }
}
}
MimeTypes = new[] { "application/x-nx-nsp" },
},
},
});
foreach (var file in result)

View File

@@ -26,6 +26,7 @@ using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.Ui;
using Ryujinx.Input.HLE;
using Ryujinx.Modules;
using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common;
@@ -39,7 +40,6 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Image = SixLabors.ImageSharp.Image;
using InputManager = Ryujinx.Input.HLE.InputManager;
using Key = Ryujinx.Input.Key;
using MissingKeyException = LibHac.Common.Keys.MissingKeyException;
using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState;
@@ -1068,9 +1068,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
Logger.Error?.Print(LogClass.Application, ex.ToString());
static async void Action() => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys);
Dispatcher.UIThread.Post(Action);
await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys);
}
}
catch (Exception ex)
@@ -1163,16 +1161,13 @@ namespace Ryujinx.Ava.UI.ViewModels
AppHost?.DisposeContext();
}
private void HandleRelaunch()
private async Task HandleRelaunch()
{
if (UserChannelPersistence.PreviousIndex != -1 && UserChannelPersistence.ShouldRestart)
{
UserChannelPersistence.ShouldRestart = false;
Dispatcher.UIThread.Post(() =>
{
LoadApplication(_currentEmulatedGamePath);
});
await LoadApplication(_currentEmulatedGamePath);
}
else
{
@@ -1191,7 +1186,7 @@ namespace Ryujinx.Ava.UI.ViewModels
Application.Current.Styles.TryGetResource(args.VSyncEnabled
? "VsyncEnabled"
: "VsyncDisabled",
Avalonia.Application.Current.ActualThemeVariant,
Application.Current.ActualThemeVariant,
out object color);
if (color is not null)
@@ -1283,7 +1278,7 @@ namespace Ryujinx.Ava.UI.ViewModels
Glyph = Glyph.Grid;
}
public async void InstallFirmwareFromFile()
public async Task InstallFirmwareFromFile()
{
var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
@@ -1294,21 +1289,21 @@ namespace Ryujinx.Ava.UI.ViewModels
{
Patterns = new[] { "*.xci", "*.zip" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci", "public.zip-archive" },
MimeTypes = new[] { "application/x-nx-xci", "application/zip" }
MimeTypes = new[] { "application/x-nx-xci", "application/zip" },
},
new("XCI")
{
Patterns = new[] { "*.xci" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci" },
MimeTypes = new[] { "application/x-nx-xci" }
MimeTypes = new[] { "application/x-nx-xci" },
},
new("ZIP")
{
Patterns = new[] { "*.zip" },
AppleUniformTypeIdentifiers = new[] { "public.zip-archive" },
MimeTypes = new[] { "application/zip" }
MimeTypes = new[] { "application/zip" },
},
}
},
});
if (result.Count > 0)
@@ -1317,11 +1312,11 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public async void InstallFirmwareFromFolder()
public async Task InstallFirmwareFromFolder()
{
var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
AllowMultiple = false
AllowMultiple = false,
});
if (result.Count > 0)
@@ -1352,7 +1347,7 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public async void ExitCurrentState()
public async Task ExitCurrentState()
{
if (WindowState == WindowState.FullScreen)
{
@@ -1377,7 +1372,7 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public async void ManageProfiles()
public async Task ManageProfiles()
{
await NavigationDialogHost.Show(AccountManager, ContentManager, VirtualFileSystem, LibHacHorizonManager.RyujinxClient);
}
@@ -1387,7 +1382,7 @@ namespace Ryujinx.Ava.UI.ViewModels
AppHost.Device.System.SimulateWakeUpMessage();
}
public async void OpenFile()
public async Task OpenFile()
{
var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
@@ -1404,7 +1399,7 @@ namespace Ryujinx.Ava.UI.ViewModels
"com.ryujinx.xci",
"com.ryujinx.nca",
"com.ryujinx.nro",
"com.ryujinx.nso"
"com.ryujinx.nso",
},
MimeTypes = new[]
{
@@ -1412,63 +1407,63 @@ namespace Ryujinx.Ava.UI.ViewModels
"application/x-nx-xci",
"application/x-nx-nca",
"application/x-nx-nro",
"application/x-nx-nso"
}
"application/x-nx-nso",
},
},
new("NSP")
{
Patterns = new[] { "*.nsp" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" },
MimeTypes = new[] { "application/x-nx-nsp" }
MimeTypes = new[] { "application/x-nx-nsp" },
},
new("XCI")
{
Patterns = new[] { "*.xci" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci" },
MimeTypes = new[] { "application/x-nx-xci" }
MimeTypes = new[] { "application/x-nx-xci" },
},
new("NCA")
{
Patterns = new[] { "*.nca" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nca" },
MimeTypes = new[] { "application/x-nx-nca" }
MimeTypes = new[] { "application/x-nx-nca" },
},
new("NRO")
{
Patterns = new[] { "*.nro" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nro" },
MimeTypes = new[] { "application/x-nx-nro" }
MimeTypes = new[] { "application/x-nx-nro" },
},
new("NSO")
{
Patterns = new[] { "*.nso" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nso" },
MimeTypes = new[] { "application/x-nx-nso" }
MimeTypes = new[] { "application/x-nx-nso" },
},
}
},
});
if (result.Count > 0)
{
LoadApplication(result[0].Path.LocalPath);
await LoadApplication(result[0].Path.LocalPath);
}
}
public async void OpenFolder()
public async Task OpenFolder()
{
var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.OpenFolderDialogTitle],
AllowMultiple = false
AllowMultiple = false,
});
if (result.Count > 0)
{
LoadApplication(result[0].Path.LocalPath);
await LoadApplication(result[0].Path.LocalPath);
}
}
public async void LoadApplication(string path, bool startFullscreen = false, string titleName = "")
public async Task LoadApplication(string path, bool startFullscreen = false, string titleName = "")
{
if (AppHost != null)
{
@@ -1505,35 +1500,30 @@ namespace Ryujinx.Ava.UI.ViewModels
this,
TopLevel);
async void Action()
if (!await AppHost.LoadGuestApplication())
{
if (!await AppHost.LoadGuestApplication())
{
AppHost.DisposeContext();
AppHost = null;
AppHost.DisposeContext();
AppHost = null;
return;
}
CanUpdate = false;
LoadHeading = TitleName = titleName;
if (string.IsNullOrWhiteSpace(titleName))
{
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);
TitleName = AppHost.Device.Processes.ActiveApplication.Name;
}
SwitchToRenderer(startFullscreen);
_currentEmulatedGamePath = path;
Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" };
gameThread.Start();
return;
}
Dispatcher.UIThread.Post(Action);
CanUpdate = false;
LoadHeading = TitleName = titleName;
if (string.IsNullOrWhiteSpace(titleName))
{
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);
TitleName = AppHost.Device.Processes.ActiveApplication.Name;
}
SwitchToRenderer(startFullscreen);
_currentEmulatedGamePath = path;
Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" };
gameThread.Start();
}
public void SwitchToRenderer(bool startFullscreen)
@@ -1596,7 +1586,7 @@ namespace Ryujinx.Ava.UI.ViewModels
IsGameRunning = false;
Dispatcher.UIThread.InvokeAsync(() =>
Dispatcher.UIThread.InvokeAsync(async () =>
{
ShowMenuAndStatusBar = true;
ShowContent = true;
@@ -1609,7 +1599,7 @@ namespace Ryujinx.Ava.UI.ViewModels
AppHost = null;
HandleRelaunch();
await HandleRelaunch();
});
RendererHostControl.WindowCreated -= RendererHost_Created;

View File

@@ -78,14 +78,13 @@ namespace Ryujinx.Ava.UI.ViewModels
if (_graphicsBackendMultithreadingIndex != (int)ConfigurationState.Instance.Graphics.BackendThreading.Value)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningMessage],
Dispatcher.UIThread.InvokeAsync(() =>
ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningMessage],
"",
"",
LocaleManager.Instance[LocaleKeys.InputDialogOk],
LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningTitle]);
});
LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningTitle])
);
}
OnPropertyChanged();
@@ -148,6 +147,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool EnableTextureRecompression { get; set; }
public bool EnableMacroHLE { get; set; }
public bool EnableColorSpacePassthrough { get; set; }
public bool ColorSpacePassthroughAvailable => IsMacOS;
public bool EnableFileLog { get; set; }
public bool EnableStub { get; set; }
public bool EnableInfo { get; set; }

View File

@@ -22,6 +22,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Path = System.IO.Path;
using SpanHelpers = LibHac.Common.SpanHelpers;
@@ -184,18 +185,12 @@ namespace Ryujinx.Ava.UI.ViewModels
}
else
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]);
});
Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]));
}
}
catch (Exception ex)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path));
});
Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path)));
}
}
}
@@ -207,7 +202,7 @@ namespace Ryujinx.Ava.UI.ViewModels
SortUpdates();
}
public async void Add()
public async Task Add()
{
var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
@@ -218,9 +213,9 @@ namespace Ryujinx.Ava.UI.ViewModels
{
Patterns = new[] { "*.nsp" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" },
MimeTypes = new[] { "application/x-nx-nsp" }
}
}
MimeTypes = new[] { "application/x-nx-nsp" },
},
},
});
foreach (var file in result)

View File

@@ -17,7 +17,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Views.Main
{
@@ -107,20 +106,14 @@ namespace Ryujinx.Ava.UI.Views.Main
await Window.ViewModel.AppHost?.ShowExitPrompt();
}
private async void PauseEmulation_Click(object sender, RoutedEventArgs e)
private void PauseEmulation_Click(object sender, RoutedEventArgs e)
{
await Task.Run(() =>
{
Window.ViewModel.AppHost?.Pause();
});
Window.ViewModel.AppHost?.Pause();
}
private async void ResumeEmulation_Click(object sender, RoutedEventArgs e)
private void ResumeEmulation_Click(object sender, RoutedEventArgs e)
{
await Task.Run(() =>
{
Window.ViewModel.AppHost?.Resume();
});
Window.ViewModel.AppHost?.Resume();
}
public async void OpenSettings(object sender, RoutedEventArgs e)
@@ -132,13 +125,13 @@ namespace Ryujinx.Ava.UI.Views.Main
ViewModel.LoadConfigurableHotKeys();
}
public void OpenMiiApplet(object sender, RoutedEventArgs e)
public async void OpenMiiApplet(object sender, RoutedEventArgs e)
{
string contentPath = ViewModel.ContentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program);
if (!string.IsNullOrEmpty(contentPath))
{
ViewModel.LoadApplication(contentPath, false, "Mii Applet");
await ViewModel.LoadApplication(contentPath, false, "Mii Applet");
}
}
@@ -196,8 +189,7 @@ namespace Ryujinx.Ava.UI.Views.Main
{
if (FileAssociationHelper.Install())
{
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesSuccessMessage],
string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty);
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty);
}
else
{
@@ -209,8 +201,7 @@ namespace Ryujinx.Ava.UI.Views.Main
{
if (FileAssociationHelper.Uninstall())
{
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesSuccessMessage],
string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty);
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty);
}
else
{

View File

@@ -73,6 +73,7 @@
<TextBlock Text="{locale:Locale SettingsEnableMacroHLE}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableColorSpacePassthrough}"
IsVisible="{Binding ColorSpacePassthroughAvailable}"
ToolTip.Tip="{locale:Locale SettingsEnableColorSpacePassthroughTooltip}">
<TextBlock Text="{locale:Locale SettingsEnableColorSpacePassthrough}" />
</CheckBox>
@@ -296,4 +297,4 @@
</StackPanel>
</Border>
</ScrollViewer>
</UserControl>
</UserControl>

View File

@@ -34,7 +34,7 @@ namespace Ryujinx.Ava.UI.Views.Settings
{
var result = await window.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
AllowMultiple = false
AllowMultiple = false,
});
if (result.Count > 0)
@@ -75,9 +75,9 @@ namespace Ryujinx.Ava.UI.Views.Settings
{
Patterns = new[] { "*.xaml" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xaml" },
MimeTypes = new[] { "application/xaml+xml" }
}
}
MimeTypes = new[] { "application/xaml+xml" },
},
},
});
if (result.Count > 0)

View File

@@ -75,9 +75,9 @@ namespace Ryujinx.Ava.UI.Views.User
{
Patterns = new[] { "*.jpg", "*.jpeg", "*.png", "*.bmp" },
AppleUniformTypeIdentifiers = new[] { "public.jpeg", "public.png", "com.microsoft.bmp" },
MimeTypes = new[] { "image/jpeg", "image/png", "image/bmp" }
}
}
MimeTypes = new[] { "image/jpeg", "image/png", "image/bmp" },
},
},
});
if (result.Count > 0)

View File

@@ -25,6 +25,7 @@ using Ryujinx.Ui.Common.Helper;
using System;
using System.IO;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Windows
@@ -79,35 +80,11 @@ namespace Ryujinx.Ava.UI.Windows
if (Program.PreviewerDetached)
{
Initialize();
InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL2GamepadDriver());
ViewModel.Initialize(
ContentManager,
StorageProvider,
ApplicationLibrary,
VirtualFileSystem,
AccountManager,
InputManager,
_userChannelPersistence,
LibHacHorizonManager,
UiHandler,
ShowLoading,
SwitchToGameControl,
SetMainContent,
this);
ViewModel.RefreshFirmwareStatus();
LoadGameList();
this.GetObservable(IsActiveProperty).Subscribe(IsActiveChanged);
this.ScalingChanged += OnScalingChanged;
}
ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
@@ -122,36 +99,17 @@ namespace Ryujinx.Ava.UI.Windows
ViewModel.IsActive = obj;
}
public void LoadGameList()
{
if (_isLoading)
{
return;
}
_isLoading = true;
LoadApplications();
_isLoading = false;
}
private void OnScalingChanged(object sender, EventArgs e)
{
Program.DesktopScaleFactor = this.RenderScaling;
}
public void AddApplication(ApplicationData applicationData)
{
Dispatcher.UIThread.InvokeAsync(() =>
{
ViewModel.Applications.Add(applicationData);
});
}
private void ApplicationLibrary_ApplicationAdded(object sender, ApplicationAddedEventArgs e)
{
AddApplication(e.AppData);
Dispatcher.UIThread.Post(() =>
{
ViewModel.Applications.Add(e.AppData);
});
}
private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e)
@@ -183,7 +141,7 @@ namespace Ryujinx.Ava.UI.Windows
string path = new FileInfo(args.Application.Path).FullName;
ViewModel.LoadApplication(path);
ViewModel.LoadApplication(path).Wait();
}
args.Handled = true;
@@ -202,13 +160,10 @@ namespace Ryujinx.Ava.UI.Windows
ViewModel.ShowContent = true;
ViewModel.IsLoadingIndeterminate = false;
Dispatcher.UIThread.InvokeAsync(() =>
if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen)
{
if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen)
{
ViewModel.ToggleFullscreen();
}
});
ViewModel.ToggleFullscreen();
}
}
public void ShowLoading(bool startFullscreen = false)
@@ -217,13 +172,10 @@ namespace Ryujinx.Ava.UI.Windows
ViewModel.ShowLoadProgress = true;
ViewModel.IsLoadingIndeterminate = true;
Dispatcher.UIThread.InvokeAsync(() =>
if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen)
{
if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen)
{
ViewModel.ToggleFullscreen();
}
});
ViewModel.ToggleFullscreen();
}
}
private void Initialize()
@@ -255,7 +207,7 @@ namespace Ryujinx.Ava.UI.Windows
}
[SupportedOSPlatform("linux")]
private static async void ShowVmMaxMapCountWarning()
private static async Task ShowVmMaxMapCountWarning()
{
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountWarningTextSecondary,
LinuxHelper.VmMaxMapCount, LinuxHelper.RecommendedVmMaxMapCount);
@@ -267,7 +219,7 @@ namespace Ryujinx.Ava.UI.Windows
}
[SupportedOSPlatform("linux")]
private static async void ShowVmMaxMapCountDialog()
private static async Task ShowVmMaxMapCountDialog()
{
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountDialogTextPrimary,
LinuxHelper.RecommendedVmMaxMapCount);
@@ -313,33 +265,46 @@ namespace Ryujinx.Ava.UI.Windows
private void CheckLaunchState()
{
if (ShowKeyErrorOnLoad)
{
ShowKeyErrorOnLoad = false;
Dispatcher.UIThread.Post(async () => await
UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys));
}
if (OperatingSystem.IsLinux() && LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount)
{
Logger.Warning?.Print(LogClass.Application, $"The value of vm.max_map_count is lower than {LinuxHelper.RecommendedVmMaxMapCount}. ({LinuxHelper.VmMaxMapCount})");
if (LinuxHelper.PkExecPath is not null)
{
Dispatcher.UIThread.Post(ShowVmMaxMapCountDialog);
Dispatcher.UIThread.Post(async () =>
{
if (OperatingSystem.IsLinux())
{
await ShowVmMaxMapCountDialog();
}
});
}
else
{
Dispatcher.UIThread.Post(ShowVmMaxMapCountWarning);
Dispatcher.UIThread.Post(async () =>
{
if (OperatingSystem.IsLinux())
{
await ShowVmMaxMapCountWarning();
}
});
}
}
if (_deferLoad)
if (!ShowKeyErrorOnLoad)
{
_deferLoad = false;
if (_deferLoad)
{
_deferLoad = false;
ViewModel.LoadApplication(_launchPath, _startFullscreen);
ViewModel.LoadApplication(_launchPath, _startFullscreen).Wait();
}
}
else
{
ShowKeyErrorOnLoad = false;
Dispatcher.UIThread.Post(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys));
}
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
@@ -372,7 +337,7 @@ namespace Ryujinx.Ava.UI.Windows
ViewModel.WindowHeight = ConfigurationState.Instance.Ui.WindowStartup.WindowSizeHeight * Program.WindowScaleFactor;
ViewModel.WindowWidth = ConfigurationState.Instance.Ui.WindowStartup.WindowSizeWidth * Program.WindowScaleFactor;
ViewModel.WindowState = ConfigurationState.Instance.Ui.WindowStartup.WindowMaximized.Value is true ? WindowState.Maximized : WindowState.Normal;
ViewModel.WindowState = ConfigurationState.Instance.Ui.WindowStartup.WindowMaximized.Value ? WindowState.Maximized : WindowState.Normal;
if (CheckScreenBounds(savedPoint))
{
@@ -415,6 +380,30 @@ namespace Ryujinx.Ava.UI.Windows
{
base.OnOpened(e);
Initialize();
ViewModel.Initialize(
ContentManager,
StorageProvider,
ApplicationLibrary,
VirtualFileSystem,
AccountManager,
InputManager,
_userChannelPersistence,
LibHacHorizonManager,
UiHandler,
ShowLoading,
SwitchToGameControl,
SetMainContent,
this);
ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
ViewModel.RefreshFirmwareStatus();
LoadApplications();
CheckLaunchState();
}
@@ -514,18 +503,15 @@ namespace Ryujinx.Ava.UI.Windows
});
}
public async void LoadApplications()
public void LoadApplications()
{
await Dispatcher.UIThread.InvokeAsync(() =>
{
ViewModel.Applications.Clear();
ViewModel.Applications.Clear();
StatusBarView.LoadProgressBar.IsVisible = true;
ViewModel.StatusBarProgressMaximum = 0;
ViewModel.StatusBarProgressValue = 0;
StatusBarView.LoadProgressBar.IsVisible = true;
ViewModel.StatusBarProgressMaximum = 0;
ViewModel.StatusBarProgressValue = 0;
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0);
});
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0);
ReloadGameList();
}
@@ -558,9 +544,17 @@ namespace Ryujinx.Ava.UI.Windows
_isLoading = true;
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs.Value, ConfigurationState.Instance.System.Language);
Thread applicationLibraryThread = new(() =>
{
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs, ConfigurationState.Instance.System.Language);
_isLoading = false;
_isLoading = false;
})
{
Name = "GUI.ApplicationLibraryThread",
IsBackground = true,
};
applicationLibraryThread.Start();
}
}
}

View File

@@ -3,7 +3,6 @@ using Avalonia.Controls.Primitives;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Ryujinx.Ui.Common.Configuration;
using System;
using System.IO;
using System.Reflection;
@@ -25,11 +24,6 @@ namespace Ryujinx.Ava.UI.Windows
IconImage = new Bitmap(stream);
}
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);

View File

@@ -192,7 +192,7 @@ namespace Ryujinx.Common.Collections
{
if (start.CompareTo(overlap.End) < 0)
{
if (overlaps.Length >= overlapCount)
if (overlaps.Length <= overlapCount)
{
Array.Resize(ref overlaps, overlapCount + ArrayGrowthSize);
}

View File

@@ -1,9 +1,11 @@
using Ryujinx.Cpu.AppleHv.Arm;
using Ryujinx.Memory;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv
{
[SupportedOSPlatform("macos")]
class HvAddressSpace : IDisposable
{
private const ulong KernelRegionBase = unchecked((ulong)-(1L << 39));

View File

@@ -2,10 +2,12 @@ using Ryujinx.Cpu.AppleHv.Arm;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading;
namespace Ryujinx.Cpu.AppleHv
{
[SupportedOSPlatform("macos")]
class HvAddressSpaceRange : IDisposable
{
private const ulong AllocationGranule = 1UL << 14;

View File

@@ -1,5 +1,6 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv
{
@@ -12,10 +13,18 @@ namespace Ryujinx.Cpu.AppleHv
#pragma warning restore CS0649
}
enum HvExitReason : uint
{
Canceled,
Exception,
VTimerActivated,
Unknown,
}
struct HvVcpuExit
{
#pragma warning disable CS0649 // Field is never assigned to
public uint Reason;
public HvExitReason Reason;
public HvVcpuExitException Exception;
#pragma warning restore CS0649
}
@@ -255,6 +264,7 @@ namespace Ryujinx.Cpu.AppleHv
}
}
[SupportedOSPlatform("macos")]
static partial class HvApi
{
public const string LibraryName = "/System/Library/Frameworks/Hypervisor.framework/Hypervisor";

View File

@@ -1,7 +1,9 @@
using ARMeilleure.Memory;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv
{
[SupportedOSPlatform("macos")]
class HvCpuContext : ICpuContext
{
private readonly ITickSource _tickSource;

View File

@@ -1,7 +1,9 @@
using ARMeilleure.Memory;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv
{
[SupportedOSPlatform("macos")]
public class HvEngine : ICpuEngine
{
private readonly ITickSource _tickSource;

View File

@@ -2,9 +2,12 @@ using ARMeilleure.State;
using Ryujinx.Cpu.AppleHv.Arm;
using Ryujinx.Memory.Tracking;
using System;
using System.Runtime.Versioning;
using System.Threading;
namespace Ryujinx.Cpu.AppleHv
{
[SupportedOSPlatform("macos")]
class HvExecutionContext : IExecutionContext
{
/// <inheritdoc/>
@@ -67,6 +70,8 @@ namespace Ryujinx.Cpu.AppleHv
private readonly ExceptionCallbacks _exceptionCallbacks;
private int _interruptRequested;
public HvExecutionContext(ICounter counter, ExceptionCallbacks exceptionCallbacks)
{
_counter = counter;
@@ -111,7 +116,15 @@ namespace Ryujinx.Cpu.AppleHv
/// <inheritdoc/>
public void RequestInterrupt()
{
_impl.RequestInterrupt();
if (Interlocked.Exchange(ref _interruptRequested, 1) == 0 && _impl is HvExecutionContextVcpu impl)
{
impl.RequestInterrupt();
}
}
private bool GetAndClearInterruptRequested()
{
return Interlocked.Exchange(ref _interruptRequested, 0) != 0;
}
/// <inheritdoc/>
@@ -131,9 +144,9 @@ namespace Ryujinx.Cpu.AppleHv
{
HvApi.hv_vcpu_run(vcpu.Handle).ThrowOnError();
uint reason = vcpu.ExitInfo->Reason;
HvExitReason reason = vcpu.ExitInfo->Reason;
if (reason == 1)
if (reason == HvExitReason.Exception)
{
uint hvEsr = (uint)vcpu.ExitInfo->Exception.Syndrome;
ExceptionClass hvEc = (ExceptionClass)(hvEsr >> 26);
@@ -146,14 +159,22 @@ namespace Ryujinx.Cpu.AppleHv
address = SynchronousException(memoryManager, ref vcpu);
HvApi.hv_vcpu_set_reg(vcpu.Handle, HvReg.PC, address).ThrowOnError();
}
else if (reason == 0)
else if (reason == HvExitReason.Canceled || reason == HvExitReason.VTimerActivated)
{
if (_impl.GetAndClearInterruptRequested())
if (GetAndClearInterruptRequested())
{
ReturnToPool(vcpu);
InterruptHandler();
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
}
if (reason == HvExitReason.VTimerActivated)
{
vcpu.EnableAndUpdateVTimer();
// Unmask VTimer interrupts.
HvApi.hv_vcpu_set_vtimer_mask(vcpu.Handle, false).ThrowOnError();
}
}
else
{

View File

@@ -46,14 +46,5 @@ namespace Ryujinx.Cpu.AppleHv
{
_v[index] = value;
}
public void RequestInterrupt()
{
}
public bool GetAndClearInterruptRequested()
{
return false;
}
}
}

View File

@@ -2,10 +2,11 @@ using ARMeilleure.State;
using Ryujinx.Memory;
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv
{
[SupportedOSPlatform("macos")]
class HvExecutionContextVcpu : IHvExecutionContext
{
private static readonly MemoryBlock _setSimdFpRegFuncMem;
@@ -135,7 +136,6 @@ namespace Ryujinx.Cpu.AppleHv
}
private readonly ulong _vcpu;
private int _interruptRequested;
public HvExecutionContextVcpu(ulong vcpu)
{
@@ -181,16 +181,8 @@ namespace Ryujinx.Cpu.AppleHv
public void RequestInterrupt()
{
if (Interlocked.Exchange(ref _interruptRequested, 1) == 0)
{
ulong vcpu = _vcpu;
HvApi.hv_vcpus_exit(ref vcpu, 1);
}
}
public bool GetAndClearInterruptRequested()
{
return Interlocked.Exchange(ref _interruptRequested, 0) != 0;
ulong vcpu = _vcpu;
HvApi.hv_vcpus_exit(ref vcpu, 1);
}
}
}

View File

@@ -1,8 +1,10 @@
using Ryujinx.Memory;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv
{
[SupportedOSPlatform("macos")]
readonly struct HvMemoryBlockAllocation : IDisposable
{
private readonly HvMemoryBlockAllocator _owner;

View File

@@ -1,7 +1,9 @@
using Ryujinx.Memory;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv
{
[SupportedOSPlatform("macos")]
class HvMemoryBlockAllocator : PrivateMemoryAllocatorImpl<HvMemoryBlockAllocator.Block>
{
public class Block : PrivateMemoryAllocator.Block

View File

@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading;
namespace Ryujinx.Cpu.AppleHv
@@ -14,6 +15,7 @@ namespace Ryujinx.Cpu.AppleHv
/// <summary>
/// Represents a CPU memory manager which maps guest virtual memory directly onto the Hypervisor page table.
/// </summary>
[SupportedOSPlatform("macos")]
public class HvMemoryManager : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
{
public const int PageBits = 12;

View File

@@ -1,7 +1,15 @@
using System.Diagnostics;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv
{
[SupportedOSPlatform("macos")]
unsafe class HvVcpu
{
private const ulong InterruptIntervalNs = 16 * 1000000; // 16 ms
private static ulong _interruptTimeDeltaTicks = 0;
public readonly ulong Handle;
public readonly HvVcpuExit* ExitInfo;
public readonly IHvExecutionContext ShadowContext;
@@ -21,5 +29,28 @@ namespace Ryujinx.Cpu.AppleHv
NativeContext = nativeContext;
IsEphemeral = isEphemeral;
}
public void EnableAndUpdateVTimer()
{
// We need to ensure interrupts will be serviced,
// and for that we set up the VTime to trigger an interrupt at fixed intervals.
ulong deltaTicks = _interruptTimeDeltaTicks;
if (deltaTicks == 0)
{
// Calculate our time delta in ticks based on the current clock frequency.
int result = TimeApi.mach_timebase_info(out var timeBaseInfo);
Debug.Assert(result == 0);
deltaTicks = ((InterruptIntervalNs * timeBaseInfo.Denom) + (timeBaseInfo.Numer - 1)) / timeBaseInfo.Numer;
_interruptTimeDeltaTicks = deltaTicks;
}
HvApi.hv_vcpu_set_sys_reg(Handle, HvSysReg.CNTV_CTL_EL0, 1).ThrowOnError();
HvApi.hv_vcpu_set_sys_reg(Handle, HvSysReg.CNTV_CVAL_EL0, TimeApi.mach_absolute_time() + deltaTicks).ThrowOnError();
}
}
}

View File

@@ -1,8 +1,10 @@
using System;
using System.Runtime.Versioning;
using System.Threading;
namespace Ryujinx.Cpu.AppleHv
{
[SupportedOSPlatform("macos")]
class HvVcpuPool
{
// Since there's a limit on the number of VCPUs we can create,
@@ -81,6 +83,8 @@ namespace Ryujinx.Cpu.AppleHv
HvVcpu vcpu = new(vcpuHandle, exitInfo, shadowContext, nativeContext, isEphemeral);
vcpu.EnableAndUpdateVTimer();
return vcpu;
}

View File

@@ -1,8 +1,10 @@
using Ryujinx.Memory;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv
{
[SupportedOSPlatform("macos")]
static class HvVm
{
// This alignment allows us to use larger blocks on the page table.

View File

@@ -2,7 +2,7 @@ using ARMeilleure.State;
namespace Ryujinx.Cpu.AppleHv
{
public interface IHvExecutionContext
interface IHvExecutionContext
{
ulong Pc { get; set; }
ulong ElrEl1 { get; set; }
@@ -39,8 +39,5 @@ namespace Ryujinx.Cpu.AppleHv
SetV(i, context.GetV(i));
}
}
void RequestInterrupt();
bool GetAndClearInterruptRequested();
}
}

View File

@@ -0,0 +1,21 @@
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv
{
struct MachTimebaseInfo
{
public uint Numer;
public uint Denom;
}
[SupportedOSPlatform("macos")]
static partial class TimeApi
{
[LibraryImport("libc", SetLastError = true)]
public static partial ulong mach_absolute_time();
[LibraryImport("libc", SetLastError = true)]
public static partial int mach_timebase_info(out MachTimebaseInfo info);
}
}

View File

@@ -696,11 +696,14 @@ namespace Ryujinx.Graphics.Gpu.Image
}
// Find view compatible matches.
int overlapsCount;
int overlapsCount = 0;
lock (_textures)
if (info.Target != Target.TextureBuffer)
{
overlapsCount = _textures.FindOverlaps(range.Value, ref _textureOverlaps);
lock (_textures)
{
overlapsCount = _textures.FindOverlaps(range.Value, ref _textureOverlaps);
}
}
if (_overlapInfo.Length != _textureOverlaps.Length)

View File

@@ -79,6 +79,7 @@ namespace Ryujinx.Graphics.Gpu.Image
private int[] _allOffsets;
private int[] _sliceSizes;
private readonly bool _is3D;
private readonly bool _isBuffer;
private bool _hasMipViews;
private bool _hasLayerViews;
private readonly int _layers;
@@ -118,6 +119,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_physicalMemory = physicalMemory;
_is3D = storage.Info.Target == Target.Texture3D;
_isBuffer = storage.Info.Target == Target.TextureBuffer;
_layers = storage.Info.GetSlices();
_levels = storage.Info.Levels;
@@ -794,7 +796,11 @@ namespace Ryujinx.Graphics.Gpu.Image
int targetLayerHandles = _hasLayerViews ? slices : 1;
int targetLevelHandles = _hasMipViews ? levels : 1;
if (_is3D)
if (_isBuffer)
{
return;
}
else if (_is3D)
{
// Future mip levels come after all layers of the last mip level. Each mipmap has less layers (depth) than the last.
@@ -1327,7 +1333,11 @@ namespace Ryujinx.Graphics.Gpu.Image
{
TextureGroupHandle[] handles;
if (!(_hasMipViews || _hasLayerViews))
if (_isBuffer)
{
handles = Array.Empty<TextureGroupHandle>();
}
else if (!(_hasMipViews || _hasLayerViews))
{
// Single dirty region.
var cpuRegionHandles = new RegionHandle[TextureRange.Count];

View File

@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
private const uint CodeGenVersion = 5682;
private const uint CodeGenVersion = 5757;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";

View File

@@ -184,8 +184,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
case Instruction.TextureSample:
return TextureSample(context, operation);
case Instruction.TextureSize:
return TextureSize(context, operation);
case Instruction.TextureQuerySamples:
return TextureQuerySamples(context, operation);
case Instruction.TextureQuerySize:
return TextureQuerySize(context, operation);
case Instruction.UnpackDouble2x32:
return UnpackDouble2x32(context, operation);

View File

@@ -118,7 +118,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
Add(Instruction.Subtract, InstType.OpBinary, "-", 2);
Add(Instruction.SwizzleAdd, InstType.CallTernary, HelperFunctionNames.SwizzleAdd);
Add(Instruction.TextureSample, InstType.Special);
Add(Instruction.TextureSize, InstType.Special);
Add(Instruction.TextureQuerySamples, InstType.Special);
Add(Instruction.TextureQuerySize, InstType.Special);
Add(Instruction.Truncate, InstType.CallUnary, "trunc");
Add(Instruction.UnpackDouble2x32, InstType.Special);
Add(Instruction.UnpackHalf2x16, InstType.Special);

View File

@@ -517,7 +517,33 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return texCall;
}
public static string TextureSize(CodeGenContext context, AstOperation operation)
public static string TextureQuerySamples(CodeGenContext context, AstOperation operation)
{
AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
// TODO: Bindless texture support. For now we just return 0.
if (isBindless)
{
return NumberFormatter.FormatInt(0);
}
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
string indexExpr = null;
if (isIndexed)
{
indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32);
}
string samplerName = GetSamplerName(context.Properties, texOp, indexExpr);
return $"textureSamples({samplerName})";
}
public static string TextureQuerySize(CodeGenContext context, AstOperation operation)
{
AstTextureOperation texOp = (AstTextureOperation)operation;

View File

@@ -44,7 +44,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public StructuredFunction CurrentFunction { get; set; }
private readonly Dictionary<AstOperand, Instruction> _locals = new();
private readonly Dictionary<int, Instruction[]> _localForArgs = new();
private readonly Dictionary<int, Instruction> _funcArgs = new();
private readonly Dictionary<int, (StructuredFunction, Instruction)> _functions = new();
@@ -112,7 +111,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
IsMainFunction = isMainFunction;
MayHaveReturned = false;
_locals.Clear();
_localForArgs.Clear();
_funcArgs.Clear();
}
@@ -169,11 +167,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
_locals.Add(local, spvLocal);
}
public void DeclareLocalForArgs(int funcIndex, Instruction[] spvLocals)
{
_localForArgs.Add(funcIndex, spvLocals);
}
public void DeclareArgument(int argIndex, Instruction spvLocal)
{
_funcArgs.Add(argIndex, spvLocal);
@@ -278,11 +271,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return _locals[local];
}
public Instruction[] GetLocalForArgsPointers(int funcIndex)
{
return _localForArgs[funcIndex];
}
public Instruction GetArgumentPointer(AstOperand funcArg)
{
return _funcArgs[funcArg.Value];

View File

@@ -41,28 +41,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
}
public static void DeclareLocalForArgs(CodeGenContext context, List<StructuredFunction> functions)
{
for (int funcIndex = 0; funcIndex < functions.Count; funcIndex++)
{
StructuredFunction function = functions[funcIndex];
SpvInstruction[] locals = new SpvInstruction[function.InArguments.Length];
for (int i = 0; i < function.InArguments.Length; i++)
{
var type = function.GetArgumentType(i);
var localPointerType = context.TypePointer(StorageClass.Function, context.GetType(type));
var spvLocal = context.Variable(localPointerType, StorageClass.Function);
context.AddLocalVariable(spvLocal);
locals[i] = spvLocal;
}
context.DeclareLocalForArgs(funcIndex, locals);
}
}
public static void DeclareAll(CodeGenContext context, StructuredProgramInfo info)
{
DeclareConstantBuffers(context, context.Properties.ConstantBuffers.Values);

View File

@@ -134,7 +134,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
Add(Instruction.Subtract, GenerateSubtract);
Add(Instruction.SwizzleAdd, GenerateSwizzleAdd);
Add(Instruction.TextureSample, GenerateTextureSample);
Add(Instruction.TextureSize, GenerateTextureSize);
Add(Instruction.TextureQuerySamples, GenerateTextureQuerySamples);
Add(Instruction.TextureQuerySize, GenerateTextureQuerySize);
Add(Instruction.Truncate, GenerateTruncate);
Add(Instruction.UnpackDouble2x32, GenerateUnpackDouble2x32);
Add(Instruction.UnpackHalf2x16, GenerateUnpackHalf2x16);
@@ -310,7 +311,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var (function, spvFunc) = context.GetFunction(funcId.Value);
var args = new SpvInstruction[operation.SourcesCount - 1];
var spvLocals = context.GetLocalForArgsPointers(funcId.Value);
for (int i = 0; i < args.Length; i++)
{
@@ -323,12 +323,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
else
{
var type = function.GetArgumentType(i);
var value = context.Get(type, operand);
var spvLocal = spvLocals[i];
context.Store(spvLocal, value);
args[i] = spvLocal;
args[i] = context.Get(type, operand);
}
}
@@ -1492,7 +1488,36 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return new OperationResult(swizzledResultType, result);
}
private static OperationResult GenerateTextureSize(CodeGenContext context, AstOperation operation)
private static OperationResult GenerateTextureQuerySamples(CodeGenContext context, AstOperation operation)
{
AstTextureOperation texOp = (AstTextureOperation)operation;
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
// TODO: Bindless texture support. For now we just return 0.
if (isBindless)
{
return new OperationResult(AggregateType.S32, context.Constant(context.TypeS32(), 0));
}
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
if (isIndexed)
{
context.GetS32(texOp.GetSource(0));
}
(var imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding];
var image = context.Load(sampledImageType, sampledImageVariable);
image = context.Image(imageType, image);
SpvInstruction result = context.ImageQuerySamples(context.TypeS32(), image);
return new OperationResult(AggregateType.S32, result);
}
private static OperationResult GenerateTextureQuerySize(CodeGenContext context, AstOperation operation)
{
AstTextureOperation texOp = (AstTextureOperation)operation;

View File

@@ -161,7 +161,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
context.EnterBlock(function.MainBlock);
Declarations.DeclareLocals(context, function);
Declarations.DeclareLocalForArgs(context, info.Functions);
Generate(context, function.MainBlock);

View File

@@ -1094,7 +1094,14 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (isBindless)
{
type = (componentMask & 4) != 0 ? SamplerType.Texture3D : SamplerType.Texture2D;
if (query == TexQuery.TexHeaderTextureType)
{
type = SamplerType.Texture2D | SamplerType.Multisample;
}
else
{
type = (componentMask & 4) != 0 ? SamplerType.Texture3D : SamplerType.Texture2D;
}
}
else
{
@@ -1102,31 +1109,69 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
TextureFlags flags = isBindless ? TextureFlags.Bindless : TextureFlags.None;
int binding;
int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
Instruction.TextureSize,
type,
TextureFormat.Unknown,
flags,
TextureOperation.DefaultCbufSlot,
imm);
for (int compMask = componentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++)
switch (query)
{
if ((compMask & 1) != 0)
{
Operand d = GetDest();
case TexQuery.TexHeaderDimension:
binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
Instruction.TextureQuerySize,
type,
TextureFormat.Unknown,
flags,
TextureOperation.DefaultCbufSlot,
imm);
if (d == null)
for (int compMask = componentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++)
{
break;
if ((compMask & 1) != 0)
{
Operand d = GetDest();
if (d == null)
{
break;
}
context.Copy(d, context.TextureQuerySize(type, flags, binding, compIndex, sources));
}
}
break;
// TODO: Validate and use query parameter.
Operand res = context.TextureSize(type, flags, binding, compIndex, sources);
case TexQuery.TexHeaderTextureType:
binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
Instruction.TextureQuerySamples,
type,
TextureFormat.Unknown,
flags,
TextureOperation.DefaultCbufSlot,
imm);
context.Copy(d, res);
}
if ((componentMask & 4) != 0)
{
// Skip first 2 components if necessary.
if ((componentMask & 1) != 0)
{
GetDest();
}
if ((componentMask & 2) != 0)
{
GetDest();
}
Operand d = GetDest();
if (d != null)
{
context.Copy(d, context.TextureQuerySamples(type, flags, binding, sources));
}
}
break;
default:
context.TranslatorContext.GpuAccessor.Log($"Invalid or unsupported query type \"{query}\".");
break;
}
}

View File

@@ -1,10 +1,8 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
{
[Flags]
[SuppressMessage("Design", "CA1069: Enums values should not be duplicated")]
enum Instruction
{
Absolute = 1,
@@ -118,7 +116,8 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
Subtract,
SwizzleAdd,
TextureSample,
TextureSize,
TextureQuerySamples,
TextureQuerySize,
Truncate,
UnpackDouble2x32,
UnpackHalf2x16,
@@ -160,7 +159,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
public static bool IsTextureQuery(this Instruction inst)
{
inst &= Instruction.Mask;
return inst == Instruction.Lod || inst == Instruction.TextureSize;
return inst == Instruction.Lod || inst == Instruction.TextureQuerySamples || inst == Instruction.TextureQuerySize;
}
}
}

View File

@@ -124,7 +124,8 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
Add(Instruction.Subtract, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar);
Add(Instruction.SwizzleAdd, AggregateType.FP32, AggregateType.FP32, AggregateType.FP32, AggregateType.S32);
Add(Instruction.TextureSample, AggregateType.FP32);
Add(Instruction.TextureSize, AggregateType.S32, AggregateType.S32, AggregateType.S32);
Add(Instruction.TextureQuerySamples, AggregateType.S32, AggregateType.S32);
Add(Instruction.TextureQuerySize, AggregateType.S32, AggregateType.S32, AggregateType.S32);
Add(Instruction.Truncate, AggregateType.Scalar, AggregateType.Scalar);
Add(Instruction.UnpackDouble2x32, AggregateType.U32, AggregateType.FP64);
Add(Instruction.UnpackHalf2x16, AggregateType.FP32, AggregateType.U32);

View File

@@ -8,11 +8,15 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
static class StructuredProgram
{
// TODO: Eventually it should be possible to specify the parameter types for the function instead of using S32 for everything.
private const AggregateType FuncParameterType = AggregateType.S32;
public static StructuredProgramInfo MakeStructuredProgram(
IReadOnlyList<Function> functions,
AttributeUsage attributeUsage,
ShaderDefinitions definitions,
ResourceManager resourceManager,
TargetLanguage targetLanguage,
bool debugMode)
{
StructuredProgramContext context = new(attributeUsage, definitions, resourceManager, debugMode);
@@ -23,19 +27,19 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
BasicBlock[] blocks = function.Blocks;
AggregateType returnType = function.ReturnsValue ? AggregateType.S32 : AggregateType.Void;
AggregateType returnType = function.ReturnsValue ? FuncParameterType : AggregateType.Void;
AggregateType[] inArguments = new AggregateType[function.InArgumentsCount];
AggregateType[] outArguments = new AggregateType[function.OutArgumentsCount];
for (int i = 0; i < inArguments.Length; i++)
{
inArguments[i] = AggregateType.S32;
inArguments[i] = FuncParameterType;
}
for (int i = 0; i < outArguments.Length; i++)
{
outArguments[i] = AggregateType.S32;
outArguments[i] = FuncParameterType;
}
context.EnterFunction(blocks.Length, function.Name, returnType, inArguments, outArguments);
@@ -58,7 +62,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
}
else
{
AddOperation(context, operation);
AddOperation(context, operation, targetLanguage);
}
}
}
@@ -73,7 +77,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
return context.Info;
}
private static void AddOperation(StructuredProgramContext context, Operation operation)
private static void AddOperation(StructuredProgramContext context, Operation operation, TargetLanguage targetLanguage)
{
Instruction inst = operation.Inst;
StorageKind storageKind = operation.StorageKind;
@@ -114,9 +118,28 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
IAstNode[] sources = new IAstNode[sourcesCount + outDestsCount];
for (int index = 0; index < operation.SourcesCount; index++)
if (inst == Instruction.Call && targetLanguage == TargetLanguage.Spirv)
{
sources[index] = context.GetOperandOrCbLoad(operation.GetSource(index));
// SPIR-V requires that all function parameters are copied to a local variable before the call
// (or at least that's what the Khronos compiler does).
// First one is the function index.
sources[0] = context.GetOperandOrCbLoad(operation.GetSource(0));
// Remaining ones are parameters, copy them to a temp local variable.
for (int index = 1; index < operation.SourcesCount; index++)
{
AstOperand argTemp = context.NewTemp(FuncParameterType);
context.AddNode(new AstAssignment(argTemp, context.GetOperandOrCbLoad(operation.GetSource(index))));
sources[index] = argTemp;
}
}
else
{
for (int index = 0; index < operation.SourcesCount; index++)
{
sources[index] = context.GetOperandOrCbLoad(operation.GetSource(index));
}
}
for (int index = 0; index < outDestsCount; index++)

View File

@@ -897,7 +897,21 @@ namespace Ryujinx.Graphics.Shader.Translation
context.Add(new TextureOperation(Instruction.TextureSample, type, TextureFormat.Unknown, flags, binding, compMask, dests, sources));
}
public static Operand TextureSize(
public static Operand TextureQuerySamples(
this EmitterContext context,
SamplerType type,
TextureFlags flags,
int binding,
Operand[] sources)
{
Operand dest = Local();
context.Add(new TextureOperation(Instruction.TextureQuerySamples, type, TextureFormat.Unknown, flags, binding, 0, new[] { dest }, sources));
return dest;
}
public static Operand TextureQuerySize(
this EmitterContext context,
SamplerType type,
TextureFlags flags,
@@ -907,7 +921,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{
Operand dest = Local();
context.Add(new TextureOperation(Instruction.TextureSize, type, TextureFormat.Unknown, flags, binding, compIndex, new[] { dest }, sources));
context.Add(new TextureOperation(Instruction.TextureQuerySize, type, TextureFormat.Unknown, flags, binding, compIndex, new[] { dest }, sources));
return dest;
}

View File

@@ -27,9 +27,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
continue;
}
if (texOp.Inst == Instruction.Lod ||
texOp.Inst == Instruction.TextureSample ||
texOp.Inst == Instruction.TextureSize)
if (texOp.Inst == Instruction.TextureSample || texOp.Inst.IsTextureQuery())
{
Operand bindlessHandle = Utils.FindLastOperation(texOp.GetSource(0), block);
@@ -40,7 +38,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
// as long bindless elimination is successful and we know where the texture descriptor is located.
bool rewriteSamplerType =
texOp.Type == SamplerType.TextureBuffer ||
texOp.Inst == Instruction.TextureSize;
texOp.Inst == Instruction.TextureQuerySamples ||
texOp.Inst == Instruction.TextureQuerySize;
if (bindlessHandle.Type == OperandType.ConstantBuffer)
{

View File

@@ -232,8 +232,8 @@ namespace Ryujinx.Graphics.Shader.Translation
inst &= Instruction.Mask;
bool isImage = inst == Instruction.ImageLoad || inst == Instruction.ImageStore || inst == Instruction.ImageAtomic;
bool isWrite = inst == Instruction.ImageStore || inst == Instruction.ImageAtomic;
bool accurateType = inst != Instruction.Lod && inst != Instruction.TextureSize;
bool intCoords = isImage || flags.HasFlag(TextureFlags.IntCoords) || inst == Instruction.TextureSize;
bool accurateType = !inst.IsTextureQuery();
bool intCoords = isImage || flags.HasFlag(TextureFlags.IntCoords) || inst == Instruction.TextureQuerySize;
bool coherent = flags.HasFlag(TextureFlags.Coherent);
if (!isImage)

View File

@@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
{
node = InsertCoordNormalization(context.Hfm, node, context.ResourceManager, context.GpuAccessor, context.Stage);
node = InsertCoordGatherBias(node, context.ResourceManager, context.GpuAccessor);
node = InsertConstOffsets(node, context.ResourceManager, context.GpuAccessor);
node = InsertConstOffsets(node, context.GpuAccessor, context.Stage);
if (texOp.Type == SamplerType.TextureBuffer && !context.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat())
{
@@ -99,7 +99,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
if (texOp.Inst == Instruction.TextureSize &&
if (texOp.Inst == Instruction.TextureQuerySize &&
texOp.Index < 2 &&
!isBindless &&
!isIndexed &&
@@ -190,7 +190,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
}
LinkedListNode<INode> textureSizeNode = node.List.AddBefore(node, new TextureOperation(
Instruction.TextureSize,
Instruction.TextureQuerySize,
texOp.Type,
texOp.Format,
texOp.Flags,
@@ -259,7 +259,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
}
node.List.AddBefore(node, new TextureOperation(
Instruction.TextureSize,
Instruction.TextureQuerySize,
texOp.Type,
texOp.Format,
texOp.Flags,
@@ -287,7 +287,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
return node;
}
private static LinkedListNode<INode> InsertConstOffsets(LinkedListNode<INode> node, ResourceManager resourceManager, IGpuAccessor gpuAccessor)
private static LinkedListNode<INode> InsertConstOffsets(LinkedListNode<INode> node, IGpuAccessor gpuAccessor, ShaderStage stage)
{
// Non-constant texture offsets are not allowed (according to the spec),
// however some GPUs does support that.
@@ -440,7 +440,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
sources.CopyTo(newSources, 0);
Operand[] texSizes = InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount);
Operand[] texSizes = InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount, stage);
int destIndex = 0;
@@ -502,7 +502,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
}
else
{
Operand[] texSizes = InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount);
Operand[] texSizes = InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount, stage);
for (int index = 0; index < coordsCount; index++)
{
@@ -554,21 +554,31 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
TextureOperation texOp,
Operand[] lodSources,
Operand bindlessHandle,
int coordsCount)
int coordsCount,
ShaderStage stage)
{
Operand[] texSizes = new Operand[coordsCount];
Operand lod = Local();
Operand lod;
node.List.AddBefore(node, new TextureOperation(
Instruction.Lod,
texOp.Type,
texOp.Format,
texOp.Flags,
texOp.Binding,
0,
new[] { lod },
lodSources));
if (stage == ShaderStage.Fragment)
{
lod = Local();
node.List.AddBefore(node, new TextureOperation(
Instruction.Lod,
texOp.Type,
texOp.Format,
texOp.Flags,
texOp.Binding,
0,
new[] { lod },
lodSources));
}
else
{
lod = Const(0);
}
for (int index = 0; index < coordsCount; index++)
{
@@ -586,7 +596,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
}
node.List.AddBefore(node, new TextureOperation(
Instruction.TextureSize,
Instruction.TextureQuerySize,
texOp.Type,
texOp.Format,
texOp.Flags,

View File

@@ -329,6 +329,7 @@ namespace Ryujinx.Graphics.Shader.Translation
attributeUsage,
definitions,
resourceManager,
Options.TargetLanguage,
Options.Flags.HasFlag(TranslationFlags.DebugMode));
int geometryVerticesPerPrimitive = Definitions.OutputTopology switch

View File

@@ -27,6 +27,7 @@ namespace Ryujinx.Graphics.Vulkan
{
public bool InUse;
public bool InConsumption;
public int SubmissionCount;
public CommandBuffer CommandBuffer;
public FenceHolder Fence;
public SemaphoreHolder Semaphore;
@@ -193,6 +194,11 @@ namespace Ryujinx.Graphics.Vulkan
return _commandBuffers[cbIndex].Fence;
}
public int GetSubmissionCount(int cbIndex)
{
return _commandBuffers[cbIndex].SubmissionCount;
}
private int FreeConsumed(bool wait)
{
int freeEntry = 0;
@@ -282,6 +288,7 @@ namespace Ryujinx.Graphics.Vulkan
Debug.Assert(entry.CommandBuffer.Handle == cbs.CommandBuffer.Handle);
entry.InUse = false;
entry.InConsumption = true;
entry.SubmissionCount++;
_inUseCount--;
var commandBuffer = entry.CommandBuffer;

View File

@@ -6,7 +6,7 @@ namespace Ryujinx.Graphics.Vulkan
{
class DescriptorSetManager : IDisposable
{
private const uint DescriptorPoolMultiplier = 16;
public const uint MaxSets = 16;
public class DescriptorPoolHolder : IDisposable
{
@@ -14,36 +14,28 @@ namespace Ryujinx.Graphics.Vulkan
public Device Device { get; }
private readonly DescriptorPool _pool;
private readonly uint _capacity;
private int _freeDescriptors;
private int _totalSets;
private int _setsInUse;
private bool _done;
public unsafe DescriptorPoolHolder(Vk api, Device device)
public unsafe DescriptorPoolHolder(Vk api, Device device, ReadOnlySpan<DescriptorPoolSize> poolSizes, bool updateAfterBind)
{
Api = api;
Device = device;
var poolSizes = new[]
foreach (var poolSize in poolSizes)
{
new DescriptorPoolSize(DescriptorType.UniformBuffer, (1 + Constants.MaxUniformBufferBindings) * DescriptorPoolMultiplier),
new DescriptorPoolSize(DescriptorType.StorageBuffer, Constants.MaxStorageBufferBindings * DescriptorPoolMultiplier),
new DescriptorPoolSize(DescriptorType.CombinedImageSampler, Constants.MaxTextureBindings * DescriptorPoolMultiplier),
new DescriptorPoolSize(DescriptorType.StorageImage, Constants.MaxImageBindings * DescriptorPoolMultiplier),
new DescriptorPoolSize(DescriptorType.UniformTexelBuffer, Constants.MaxTextureBindings * DescriptorPoolMultiplier),
new DescriptorPoolSize(DescriptorType.StorageTexelBuffer, Constants.MaxImageBindings * DescriptorPoolMultiplier),
};
uint maxSets = (uint)poolSizes.Length * DescriptorPoolMultiplier;
_capacity = maxSets;
_freeDescriptors += (int)poolSize.DescriptorCount;
}
fixed (DescriptorPoolSize* pPoolsSize = poolSizes)
{
var descriptorPoolCreateInfo = new DescriptorPoolCreateInfo
{
SType = StructureType.DescriptorPoolCreateInfo,
MaxSets = maxSets,
Flags = updateAfterBind ? DescriptorPoolCreateFlags.UpdateAfterBindBit : DescriptorPoolCreateFlags.None,
MaxSets = MaxSets,
PoolSizeCount = (uint)poolSizes.Length,
PPoolSizes = pPoolsSize,
};
@@ -52,18 +44,22 @@ namespace Ryujinx.Graphics.Vulkan
}
}
public DescriptorSetCollection AllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts)
public unsafe DescriptorSetCollection AllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, int consumedDescriptors)
{
TryAllocateDescriptorSets(layouts, isTry: false, out var dsc);
TryAllocateDescriptorSets(layouts, consumedDescriptors, isTry: false, out var dsc);
return dsc;
}
public bool TryAllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, out DescriptorSetCollection dsc)
public bool TryAllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, int consumedDescriptors, out DescriptorSetCollection dsc)
{
return TryAllocateDescriptorSets(layouts, isTry: true, out dsc);
return TryAllocateDescriptorSets(layouts, consumedDescriptors, isTry: true, out dsc);
}
private unsafe bool TryAllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, bool isTry, out DescriptorSetCollection dsc)
private unsafe bool TryAllocateDescriptorSets(
ReadOnlySpan<DescriptorSetLayout> layouts,
int consumedDescriptors,
bool isTry,
out DescriptorSetCollection dsc)
{
Debug.Assert(!_done);
@@ -84,7 +80,7 @@ namespace Ryujinx.Graphics.Vulkan
var result = Api.AllocateDescriptorSets(Device, &descriptorSetAllocateInfo, pDescriptorSets);
if (isTry && result == Result.ErrorOutOfPoolMemory)
{
_totalSets = (int)_capacity;
_totalSets = (int)MaxSets;
_done = true;
DestroyIfDone();
dsc = default;
@@ -95,6 +91,7 @@ namespace Ryujinx.Graphics.Vulkan
}
}
_freeDescriptors -= consumedDescriptors;
_totalSets += layouts.Length;
_setsInUse += layouts.Length;
@@ -109,9 +106,15 @@ namespace Ryujinx.Graphics.Vulkan
DestroyIfDone();
}
public bool CanFit(int count)
public bool CanFit(int setsCount, int descriptorsCount)
{
if (_totalSets + count <= _capacity)
// Try to determine if an allocation with the given parameters will succeed.
// An allocation may fail if the sets count or descriptors count exceeds the available counts
// of the pool.
// Not getting that right is not fatal, it will just create a new pool and try again,
// but it is less efficient.
if (_totalSets + setsCount <= MaxSets && _freeDescriptors >= descriptorsCount)
{
return true;
}
@@ -148,46 +151,74 @@ namespace Ryujinx.Graphics.Vulkan
}
private readonly Device _device;
private DescriptorPoolHolder _currentPool;
private readonly DescriptorPoolHolder[] _currentPools;
public DescriptorSetManager(Device device)
public DescriptorSetManager(Device device, int poolCount)
{
_device = device;
_currentPools = new DescriptorPoolHolder[poolCount];
}
public Auto<DescriptorSetCollection> AllocateDescriptorSet(Vk api, DescriptorSetLayout layout)
public Auto<DescriptorSetCollection> AllocateDescriptorSet(
Vk api,
DescriptorSetLayout layout,
ReadOnlySpan<DescriptorPoolSize> poolSizes,
int poolIndex,
int consumedDescriptors,
bool updateAfterBind)
{
Span<DescriptorSetLayout> layouts = stackalloc DescriptorSetLayout[1];
layouts[0] = layout;
return AllocateDescriptorSets(api, layouts);
return AllocateDescriptorSets(api, layouts, poolSizes, poolIndex, consumedDescriptors, updateAfterBind);
}
public Auto<DescriptorSetCollection> AllocateDescriptorSets(Vk api, ReadOnlySpan<DescriptorSetLayout> layouts)
public Auto<DescriptorSetCollection> AllocateDescriptorSets(
Vk api,
ReadOnlySpan<DescriptorSetLayout> layouts,
ReadOnlySpan<DescriptorPoolSize> poolSizes,
int poolIndex,
int consumedDescriptors,
bool updateAfterBind)
{
// If we fail the first time, just create a new pool and try again.
if (!GetPool(api, layouts.Length).TryAllocateDescriptorSets(layouts, out var dsc))
var pool = GetPool(api, poolSizes, poolIndex, layouts.Length, consumedDescriptors, updateAfterBind);
if (!pool.TryAllocateDescriptorSets(layouts, consumedDescriptors, out var dsc))
{
dsc = GetPool(api, layouts.Length).AllocateDescriptorSets(layouts);
pool = GetPool(api, poolSizes, poolIndex, layouts.Length, consumedDescriptors, updateAfterBind);
dsc = pool.AllocateDescriptorSets(layouts, consumedDescriptors);
}
return new Auto<DescriptorSetCollection>(dsc);
}
private DescriptorPoolHolder GetPool(Vk api, int requiredCount)
private DescriptorPoolHolder GetPool(
Vk api,
ReadOnlySpan<DescriptorPoolSize> poolSizes,
int poolIndex,
int setsCount,
int descriptorsCount,
bool updateAfterBind)
{
if (_currentPool == null || !_currentPool.CanFit(requiredCount))
ref DescriptorPoolHolder currentPool = ref _currentPools[poolIndex];
if (currentPool == null || !currentPool.CanFit(setsCount, descriptorsCount))
{
_currentPool = new DescriptorPoolHolder(api, _device);
currentPool = new DescriptorPoolHolder(api, _device, poolSizes, updateAfterBind);
}
return _currentPool;
return currentPool;
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_currentPool?.Dispose();
for (int index = 0; index < _currentPools.Length; index++)
{
_currentPools[index]?.Dispose();
_currentPools[index] = null;
}
}
}

View File

@@ -59,6 +59,8 @@ namespace Ryujinx.Graphics.Vulkan
private BitMapStruct<Array2<long>> _uniformMirrored;
private BitMapStruct<Array2<long>> _storageMirrored;
private bool _updateDescriptorCacheCbIndex;
[Flags]
private enum DirtyFlags
{
@@ -218,6 +220,7 @@ namespace Ryujinx.Graphics.Vulkan
public void SetProgram(ShaderCollection program)
{
_program = program;
_updateDescriptorCacheCbIndex = true;
_dirty = DirtyFlags.All;
}
@@ -490,7 +493,13 @@ namespace Ryujinx.Graphics.Vulkan
var dummyBuffer = _dummyBuffer?.GetBuffer();
var dsc = program.GetNewDescriptorSetCollection(_gd, cbs.CommandBufferIndex, setIndex, out var isNew).Get(cbs);
if (_updateDescriptorCacheCbIndex)
{
_updateDescriptorCacheCbIndex = false;
program.UpdateDescriptorCacheCommandBufferIndex(cbs.CommandBufferIndex);
}
var dsc = program.GetNewDescriptorSetCollection(setIndex, out var isNew).Get(cbs);
if (!program.HasMinimalLayout)
{
@@ -697,6 +706,7 @@ namespace Ryujinx.Graphics.Vulkan
public void SignalCommandBufferChange()
{
_updateDescriptorCacheCbIndex = true;
_dirty = DirtyFlags.All;
_uniformSet.Clear();

View File

@@ -1,6 +1,7 @@
using Silk.NET.Vulkan;
using System;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Graphics.Vulkan
{
@@ -13,6 +14,7 @@ namespace Ryujinx.Graphics.Vulkan
private readonly Device _device;
private readonly List<MemoryAllocatorBlockList> _blockLists;
private readonly int _blockAlignment;
private readonly ReaderWriterLockSlim _lock;
public MemoryAllocator(Vk api, VulkanPhysicalDevice physicalDevice, Device device)
{
@@ -21,6 +23,7 @@ namespace Ryujinx.Graphics.Vulkan
_device = device;
_blockLists = new List<MemoryAllocatorBlockList>();
_blockAlignment = (int)Math.Min(int.MaxValue, MaxDeviceMemoryUsageEstimate / _physicalDevice.PhysicalDeviceProperties.Limits.MaxMemoryAllocationCount);
_lock = new(LockRecursionPolicy.NoRecursion);
}
public MemoryAllocation AllocateDeviceMemory(
@@ -40,21 +43,37 @@ namespace Ryujinx.Graphics.Vulkan
private MemoryAllocation Allocate(int memoryTypeIndex, ulong size, ulong alignment, bool map, bool isBuffer)
{
for (int i = 0; i < _blockLists.Count; i++)
_lock.EnterReadLock();
try
{
var bl = _blockLists[i];
if (bl.MemoryTypeIndex == memoryTypeIndex && bl.ForBuffer == isBuffer)
for (int i = 0; i < _blockLists.Count; i++)
{
lock (bl)
var bl = _blockLists[i];
if (bl.MemoryTypeIndex == memoryTypeIndex && bl.ForBuffer == isBuffer)
{
return bl.Allocate(size, alignment, map);
}
}
}
finally
{
_lock.ExitReadLock();
}
var newBl = new MemoryAllocatorBlockList(_api, _device, memoryTypeIndex, _blockAlignment, isBuffer);
_blockLists.Add(newBl);
return newBl.Allocate(size, alignment, map);
_lock.EnterWriteLock();
try
{
var newBl = new MemoryAllocatorBlockList(_api, _device, memoryTypeIndex, _blockAlignment, isBuffer);
_blockLists.Add(newBl);
return newBl.Allocate(size, alignment, map);
}
finally
{
_lock.ExitWriteLock();
}
}
internal int FindSuitableMemoryTypeIndex(

View File

@@ -3,6 +3,7 @@ using Silk.NET.Vulkan;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
namespace Ryujinx.Graphics.Vulkan
{
@@ -166,6 +167,8 @@ namespace Ryujinx.Graphics.Vulkan
private readonly int _blockAlignment;
private readonly ReaderWriterLockSlim _lock;
public MemoryAllocatorBlockList(Vk api, Device device, int memoryTypeIndex, int blockAlignment, bool forBuffer)
{
_blocks = new List<Block>();
@@ -174,6 +177,7 @@ namespace Ryujinx.Graphics.Vulkan
MemoryTypeIndex = memoryTypeIndex;
ForBuffer = forBuffer;
_blockAlignment = blockAlignment;
_lock = new(LockRecursionPolicy.NoRecursion);
}
public unsafe MemoryAllocation Allocate(ulong size, ulong alignment, bool map)
@@ -184,19 +188,28 @@ namespace Ryujinx.Graphics.Vulkan
throw new ArgumentOutOfRangeException(nameof(alignment), $"Invalid alignment 0x{alignment:X}.");
}
for (int i = 0; i < _blocks.Count; i++)
{
var block = _blocks[i];
_lock.EnterReadLock();
if (block.Mapped == map && block.Size >= size)
try
{
for (int i = 0; i < _blocks.Count; i++)
{
ulong offset = block.Allocate(size, alignment);
if (offset != InvalidOffset)
var block = _blocks[i];
if (block.Mapped == map && block.Size >= size)
{
return new MemoryAllocation(this, block, block.Memory, GetHostPointer(block, offset), offset, size);
ulong offset = block.Allocate(size, alignment);
if (offset != InvalidOffset)
{
return new MemoryAllocation(this, block, block.Memory, GetHostPointer(block, offset), offset, size);
}
}
}
}
finally
{
_lock.ExitReadLock();
}
ulong blockAlignedSize = BitUtils.AlignUp(size, (ulong)_blockAlignment);
@@ -244,14 +257,23 @@ namespace Ryujinx.Graphics.Vulkan
if (block.IsTotallyFree())
{
for (int i = 0; i < _blocks.Count; i++)
_lock.EnterWriteLock();
try
{
if (_blocks[i] == block)
for (int i = 0; i < _blocks.Count; i++)
{
_blocks.RemoveAt(i);
break;
if (_blocks[i] == block)
{
_blocks.RemoveAt(i);
break;
}
}
}
finally
{
_lock.ExitWriteLock();
}
block.Destroy(_api, _device);
}
@@ -259,13 +281,22 @@ namespace Ryujinx.Graphics.Vulkan
private void InsertBlock(Block block)
{
int index = _blocks.BinarySearch(block);
if (index < 0)
{
index = ~index;
}
_lock.EnterWriteLock();
_blocks.Insert(index, block);
try
{
int index = _blocks.BinarySearch(block);
if (index < 0)
{
index = ~index;
}
_blocks.Insert(index, block);
}
finally
{
_lock.ExitWriteLock();
}
}
public void Dispose()

View File

@@ -1,5 +1,6 @@
using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -7,15 +8,28 @@ namespace Ryujinx.Graphics.Vulkan
{
class PipelineLayoutCacheEntry
{
// Those were adjusted based on current descriptor usage and the descriptor counts usually used on pipeline layouts.
// It might be a good idea to tweak them again if those change, or maybe find a way to calculate an optimal value dynamically.
private const uint DefaultUniformBufferPoolCapacity = 19 * DescriptorSetManager.MaxSets;
private const uint DefaultStorageBufferPoolCapacity = 16 * DescriptorSetManager.MaxSets;
private const uint DefaultTexturePoolCapacity = 128 * DescriptorSetManager.MaxSets;
private const uint DefaultImagePoolCapacity = 8 * DescriptorSetManager.MaxSets;
private const int MaxPoolSizesPerSet = 2;
private readonly VulkanRenderer _gd;
private readonly Device _device;
public DescriptorSetLayout[] DescriptorSetLayouts { get; }
public PipelineLayout PipelineLayout { get; }
private readonly int[] _consumedDescriptorsPerSet;
private readonly List<Auto<DescriptorSetCollection>>[][] _dsCache;
private List<Auto<DescriptorSetCollection>>[] _currentDsCache;
private readonly int[] _dsCacheCursor;
private int _dsLastCbIndex;
private int _dsLastSubmissionCount;
private PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, int setsCount)
{
@@ -44,29 +58,55 @@ namespace Ryujinx.Graphics.Vulkan
bool usePushDescriptors) : this(gd, device, setDescriptors.Count)
{
(DescriptorSetLayouts, PipelineLayout) = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors);
_consumedDescriptorsPerSet = new int[setDescriptors.Count];
for (int setIndex = 0; setIndex < setDescriptors.Count; setIndex++)
{
int count = 0;
foreach (var descriptor in setDescriptors[setIndex].Descriptors)
{
count += descriptor.Count;
}
_consumedDescriptorsPerSet[setIndex] = count;
}
}
public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(
VulkanRenderer gd,
int commandBufferIndex,
int setIndex,
out bool isNew)
public void UpdateCommandBufferIndex(int commandBufferIndex)
{
if (_dsLastCbIndex != commandBufferIndex)
int submissionCount = _gd.CommandBufferPool.GetSubmissionCount(commandBufferIndex);
if (_dsLastCbIndex != commandBufferIndex || _dsLastSubmissionCount != submissionCount)
{
_dsLastCbIndex = commandBufferIndex;
for (int i = 0; i < _dsCacheCursor.Length; i++)
{
_dsCacheCursor[i] = 0;
}
_dsLastSubmissionCount = submissionCount;
Array.Clear(_dsCacheCursor);
}
var list = _dsCache[commandBufferIndex][setIndex];
_currentDsCache = _dsCache[commandBufferIndex];
}
public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(int setIndex, out bool isNew)
{
var list = _currentDsCache[setIndex];
int index = _dsCacheCursor[setIndex]++;
if (index == list.Count)
{
var dsc = gd.DescriptorSetManager.AllocateDescriptorSet(gd.Api, DescriptorSetLayouts[setIndex]);
Span<DescriptorPoolSize> poolSizes = stackalloc DescriptorPoolSize[MaxPoolSizesPerSet];
poolSizes = GetDescriptorPoolSizes(poolSizes, setIndex);
int consumedDescriptors = _consumedDescriptorsPerSet[setIndex];
var dsc = _gd.DescriptorSetManager.AllocateDescriptorSet(
_gd.Api,
DescriptorSetLayouts[setIndex],
poolSizes,
setIndex,
consumedDescriptors,
false);
list.Add(dsc);
isNew = true;
return dsc;
@@ -76,6 +116,33 @@ namespace Ryujinx.Graphics.Vulkan
return list[index];
}
private static Span<DescriptorPoolSize> GetDescriptorPoolSizes(Span<DescriptorPoolSize> output, int setIndex)
{
int count = 1;
switch (setIndex)
{
case PipelineBase.UniformSetIndex:
output[0] = new(DescriptorType.UniformBuffer, DefaultUniformBufferPoolCapacity);
break;
case PipelineBase.StorageSetIndex:
output[0] = new(DescriptorType.StorageBuffer, DefaultStorageBufferPoolCapacity);
break;
case PipelineBase.TextureSetIndex:
output[0] = new(DescriptorType.CombinedImageSampler, DefaultTexturePoolCapacity);
output[1] = new(DescriptorType.UniformTexelBuffer, DefaultTexturePoolCapacity);
count = 2;
break;
case PipelineBase.ImageSetIndex:
output[0] = new(DescriptorType.StorageImage, DefaultImagePoolCapacity);
output[1] = new(DescriptorType.StorageTexelBuffer, DefaultImagePoolCapacity);
count = 2;
break;
}
return output[..count];
}
protected virtual unsafe void Dispose(bool disposing)
{
if (disposing)

View File

@@ -464,13 +464,14 @@ namespace Ryujinx.Graphics.Vulkan
return true;
}
public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(
VulkanRenderer gd,
int commandBufferIndex,
int setIndex,
out bool isNew)
public void UpdateDescriptorCacheCommandBufferIndex(int commandBufferIndex)
{
return _plce.GetNewDescriptorSetCollection(gd, commandBufferIndex, setIndex, out isNew);
_plce.UpdateCommandBufferIndex(commandBufferIndex);
}
public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(int setIndex, out bool isNew)
{
return _plce.GetNewDescriptorSetCollection(setIndex, out isNew);
}
protected virtual void Dispose(bool disposing)

View File

@@ -347,7 +347,7 @@ namespace Ryujinx.Graphics.Vulkan
CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex);
DescriptorSetManager = new DescriptorSetManager(_device);
DescriptorSetManager = new DescriptorSetManager(_device, PipelineBase.DescriptorSetLayouts);
PipelineLayoutCache = new PipelineLayoutCache();

View File

@@ -327,8 +327,10 @@ namespace Ryujinx.HLE.HOS
private void StartNewServices()
{
HorizonFsClient fsClient = new(this);
ServiceTable = new ServiceTable();
var services = ServiceTable.GetServices(new HorizonOptions(Device.Configuration.IgnoreMissingServices, LibHacHorizonManager.BcatClient));
var services = ServiceTable.GetServices(new HorizonOptions(Device.Configuration.IgnoreMissingServices, LibHacHorizonManager.BcatClient, fsClient));
foreach (var service in services)
{

View File

@@ -0,0 +1,119 @@
using LibHac.Common;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Ncm;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.HLE.FileSystem;
using Ryujinx.Horizon;
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Fs;
using System;
using System.Collections.Concurrent;
using System.IO;
namespace Ryujinx.HLE.HOS
{
class HorizonFsClient : IFsClient
{
private readonly Horizon _system;
private readonly LibHac.Fs.FileSystemClient _fsClient;
private readonly ConcurrentDictionary<string, LocalStorage> _mountedStorages;
public HorizonFsClient(Horizon system)
{
_system = system;
_fsClient = _system.LibHacHorizonManager.FsClient.Fs;
_mountedStorages = new();
}
public void CloseFile(FileHandle handle)
{
_fsClient.CloseFile((LibHac.Fs.FileHandle)handle.Value);
}
public Result GetFileSize(out long size, FileHandle handle)
{
return _fsClient.GetFileSize(out size, (LibHac.Fs.FileHandle)handle.Value).ToHorizonResult();
}
public Result MountSystemData(string mountName, ulong dataId)
{
string contentPath = _system.ContentManager.GetInstalledContentPath(dataId, StorageId.BuiltInSystem, NcaContentType.PublicData);
string installPath = VirtualFileSystem.SwitchPathToSystemPath(contentPath);
if (!string.IsNullOrWhiteSpace(installPath))
{
string ncaPath = installPath;
if (File.Exists(ncaPath))
{
LocalStorage ncaStorage = null;
try
{
ncaStorage = new LocalStorage(ncaPath, FileAccess.Read, FileMode.Open);
Nca nca = new(_system.KeySet, ncaStorage);
using var ncaFileSystem = nca.OpenFileSystem(NcaSectionType.Data, _system.FsIntegrityCheckLevel);
using var ncaFsRef = new UniqueRef<IFileSystem>(ncaFileSystem);
Result result = _fsClient.Register(mountName.ToU8Span(), ref ncaFsRef.Ref).ToHorizonResult();
if (result.IsFailure)
{
ncaStorage.Dispose();
}
else
{
_mountedStorages.TryAdd(mountName, ncaStorage);
}
return result;
}
catch (HorizonResultException ex)
{
ncaStorage?.Dispose();
return ex.ResultValue.ToHorizonResult();
}
}
}
// TODO: Return correct result here, this is likely wrong.
return LibHac.Fs.ResultFs.TargetNotFound.Handle().ToHorizonResult();
}
public Result OpenFile(out FileHandle handle, string path, OpenMode openMode)
{
var result = _fsClient.OpenFile(out var libhacHandle, path.ToU8Span(), (LibHac.Fs.OpenMode)openMode);
handle = new(libhacHandle);
return result.ToHorizonResult();
}
public Result QueryMountSystemDataCacheSize(out long size, ulong dataId)
{
// TODO.
size = 0;
return Result.Success;
}
public Result ReadFile(FileHandle handle, long offset, Span<byte> destination)
{
return _fsClient.ReadFile((LibHac.Fs.FileHandle)handle.Value, offset, destination).ToHorizonResult();
}
public void Unmount(string mountName)
{
if (_mountedStorages.TryRemove(mountName, out LocalStorage ncaStorage))
{
ncaStorage.Dispose();
}
_fsClient.Unmount(mountName.ToU8Span());
}
}
}

View File

@@ -36,6 +36,8 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
throw new InvalidOperationException("Out of handles!");
}
_completionEvent.WritableEvent.Signal();
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(completionEventHandle);
return ResultCode.Success;
@@ -187,6 +189,20 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
return ResultCode.Success;
}
[CommandCmif(10420)]
// nn::friends::CheckBlockedUserListAvailability(nn::account::Uid userId) -> bool
public ResultCode CheckBlockedUserListAvailability(ServiceCtx context)
{
UserId userId = context.RequestData.ReadStruct<UserId>();
// Yes, it is available.
context.ResponseData.Write(true);
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() });
return ResultCode.Success;
}
[CommandCmif(10600)]
// nn::friends::DeclareOpenOnlinePlaySession(nn::account::Uid userId)
public ResultCode DeclareOpenOnlinePlaySession(ServiceCtx context)

View File

@@ -669,6 +669,12 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
lock (Core.Lock)
{
// If we are replacing a buffer that has already been queued, make sure we release the references.
if (Core.Slots[slot].BufferState == BufferState.Queued)
{
Core.Slots[slot].GraphicBuffer.Object.DecrementNvMapHandleRefCount(Core.Owner);
}
Core.Slots[slot].BufferState = BufferState.Free;
Core.Slots[slot].Fence = AndroidFence.NoFence;
Core.Slots[slot].RequestBufferCalled = false;

View File

@@ -1,4 +1,5 @@
using LibHac;
using Ryujinx.Horizon.Sdk.Fs;
namespace Ryujinx.Horizon
{
@@ -8,12 +9,14 @@ namespace Ryujinx.Horizon
public bool ThrowOnInvalidCommandIds { get; }
public HorizonClient BcatClient { get; }
public IFsClient FsClient { get; }
public HorizonOptions(bool ignoreMissingServices, HorizonClient bcatClient)
public HorizonOptions(bool ignoreMissingServices, HorizonClient bcatClient, IFsClient fsClient)
{
IgnoreMissingServices = ignoreMissingServices;
ThrowOnInvalidCommandIds = true;
BcatClient = bcatClient;
FsClient = fsClient;
}
}
}

View File

@@ -2,7 +2,7 @@
namespace Ryujinx.Horizon
{
internal static class LibHacResultExtensions
public static class LibHacResultExtensions
{
public static Result ToHorizonResult(this LibHac.Result result)
{

View File

@@ -11,7 +11,7 @@ namespace Ryujinx.Horizon.LogManager.Ipc
[CmifCommand(0)]
public Result OpenLogger(out LmLogger logger, [ClientProcessId] ulong pid)
{
// NOTE: Internal name is Logger, but we rename it LmLogger to avoid name clash with Ryujinx.Common.Logging logger.
// NOTE: Internal name is Logger, but we rename it to LmLogger to avoid name clash with Ryujinx.Common.Logging logger.
logger = new LmLogger(this, pid);
return Result.Success;

View File

@@ -0,0 +1,64 @@
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Ngc;
using Ryujinx.Horizon.Sdk.Ngc.Detail;
using Ryujinx.Horizon.Sdk.Sf;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using System;
namespace Ryujinx.Horizon.Ngc.Ipc
{
partial class Service : INgcService
{
private readonly ProfanityFilter _profanityFilter;
public Service(ProfanityFilter profanityFilter)
{
_profanityFilter = profanityFilter;
}
[CmifCommand(0)]
public Result GetContentVersion(out uint version)
{
lock (_profanityFilter)
{
return _profanityFilter.GetContentVersion(out version);
}
}
[CmifCommand(1)]
public Result Check(out uint checkMask, ReadOnlySpan<byte> text, uint regionMask, ProfanityFilterOption option)
{
lock (_profanityFilter)
{
return _profanityFilter.CheckProfanityWords(out checkMask, text, regionMask, option);
}
}
[CmifCommand(2)]
public Result Mask(
out int maskedWordsCount,
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> filteredText,
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> text,
uint regionMask,
ProfanityFilterOption option)
{
lock (_profanityFilter)
{
int length = Math.Min(filteredText.Length, text.Length);
text[..length].CopyTo(filteredText[..length]);
return _profanityFilter.MaskProfanityWordsInText(out maskedWordsCount, filteredText, regionMask, option);
}
}
[CmifCommand(3)]
public Result Reload()
{
lock (_profanityFilter)
{
return _profanityFilter.Reload();
}
}
}
}

View File

@@ -0,0 +1,51 @@
using Ryujinx.Horizon.Ngc.Ipc;
using Ryujinx.Horizon.Sdk.Fs;
using Ryujinx.Horizon.Sdk.Ngc.Detail;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using Ryujinx.Horizon.Sdk.Sm;
using System;
namespace Ryujinx.Horizon.Ngc
{
class NgcIpcServer
{
private const int MaxSessionsCount = 4;
private const int PointerBufferSize = 0;
private const int MaxDomains = 0;
private const int MaxDomainObjects = 0;
private const int MaxPortsCount = 1;
private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
private SmApi _sm;
private ServerManager _serverManager;
private ProfanityFilter _profanityFilter;
public void Initialize(IFsClient fsClient)
{
HeapAllocator allocator = new();
_sm = new SmApi();
_sm.Initialize().AbortOnFailure();
_profanityFilter = new(fsClient);
_profanityFilter.Initialize().AbortOnFailure();
_serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, MaxSessionsCount);
_serverManager.RegisterObjectForServer(new Service(_profanityFilter), ServiceName.Encode("ngc:u"), MaxSessionsCount);
}
public void ServiceRequests()
{
_serverManager.ServiceRequests();
}
public void Shutdown()
{
_serverManager.Dispose();
_profanityFilter.Dispose();
}
}
}

View File

@@ -0,0 +1,21 @@
namespace Ryujinx.Horizon.Ngc
{
class NgcMain : IService
{
public static void Main(ServiceTable serviceTable)
{
NgcIpcServer ipcServer = new();
ipcServer.Initialize(HorizonStatic.Options.FsClient);
// TODO: Notification thread, requires implementing OpenSystemDataUpdateEventNotifier on FS.
// The notification thread seems to wait until the event returned by OpenSystemDataUpdateEventNotifier is signalled
// in a loop. When it receives the signal, it calls ContentsReader.Reload and then waits for the next signal.
serviceTable.SignalServiceReady();
ipcServer.ServiceRequests();
ipcServer.Shutdown();
}
}
}

View File

@@ -0,0 +1,13 @@
namespace Ryujinx.Horizon.Sdk.Fs
{
public readonly struct FileHandle
{
public object Value { get; }
public FileHandle(object value)
{
Value = value;
}
}
}

View File

@@ -0,0 +1,13 @@
using Ryujinx.Horizon.Common;
namespace Ryujinx.Horizon.Sdk.Fs
{
static class FsResult
{
private const int ModuleId = 2;
public static Result PathNotFound => new(ModuleId, 1);
public static Result PathAlreadyExists => new(ModuleId, 2);
public static Result TargetNotFound => new(ModuleId, 1002);
}
}

View File

@@ -0,0 +1,16 @@
using Ryujinx.Horizon.Common;
using System;
namespace Ryujinx.Horizon.Sdk.Fs
{
public interface IFsClient
{
Result QueryMountSystemDataCacheSize(out long size, ulong dataId);
Result MountSystemData(string mountName, ulong dataId);
Result OpenFile(out FileHandle handle, string path, OpenMode openMode);
Result ReadFile(FileHandle handle, long offset, Span<byte> destination);
Result GetFileSize(out long size, FileHandle handle);
void CloseFile(FileHandle handle);
void Unmount(string mountName);
}
}

View File

@@ -0,0 +1,14 @@
using System;
namespace Ryujinx.Horizon.Sdk.Fs
{
[Flags]
public enum OpenMode
{
Read = 1,
Write = 2,
AllowAppend = 4,
ReadWrite = 3,
All = 7,
}
}

View File

@@ -0,0 +1,251 @@
using System;
using System.Diagnostics;
using System.Text;
namespace Ryujinx.Horizon.Sdk.Ngc.Detail
{
class AhoCorasick
{
public delegate bool MatchCallback(ReadOnlySpan<byte> text, int matchStartOffset, int matchEndOffset, int nodeId, ref MatchState state);
public delegate bool MatchCallback<T>(ReadOnlySpan<byte> text, int matchStartOffset, int matchEndOffset, int nodeId, ref T state);
private readonly SparseSet _wordMap = new();
private readonly CompressedArray _wordLengths = new();
private readonly SparseSet _multiWordMap = new();
private readonly CompressedArray _multiWordIndices = new();
private readonly SparseSet _nodeMap = new();
private uint _nodesPerCharacter;
private readonly Bp _bp = new();
public bool Import(ref BinaryReader reader)
{
if (!_wordLengths.Import(ref reader) ||
!_wordMap.Import(ref reader) ||
!_multiWordIndices.Import(ref reader) ||
!_multiWordMap.Import(ref reader))
{
return false;
}
if (!reader.Read(out _nodesPerCharacter))
{
return false;
}
return _nodeMap.Import(ref reader) && _bp.Import(ref reader);
}
public void Match(ReadOnlySpan<byte> utf8Text, MatchCallback callback, ref MatchState state)
{
int nodeId = 0;
for (int index = 0; index < utf8Text.Length; index++)
{
long c = utf8Text[index];
while (true)
{
long nodeSparseIndex = _nodesPerCharacter * c + (uint)nodeId;
int nodePlainIndex = _nodeMap.Rank1(nodeSparseIndex);
if (nodePlainIndex != 0)
{
long foundNodeSparseIndex = _nodeMap.Select1Ex(nodePlainIndex - 1);
if (foundNodeSparseIndex > 0 && foundNodeSparseIndex == nodeSparseIndex)
{
nodeId = nodePlainIndex;
if (callback != null)
{
// Match full word.
if (_wordMap.Has(nodePlainIndex))
{
int wordLength = _wordLengths[_wordMap.Rank1((uint)nodePlainIndex) - 1];
int startIndex = index + 1 - wordLength;
if (!callback(utf8Text, startIndex, index + 1, nodeId, ref state))
{
return;
}
}
// If this is a phrase composed of multiple words, also match each sub-word.
while (_multiWordMap.Has(nodePlainIndex))
{
nodePlainIndex = _multiWordIndices[_multiWordMap.Rank1((uint)nodePlainIndex) - 1];
int wordLength = _wordMap.Has(nodePlainIndex) ? _wordLengths[_wordMap.Rank1(nodePlainIndex) - 1] : 0;
int startIndex = index + 1 - wordLength;
if (!callback(utf8Text, startIndex, index + 1, nodePlainIndex, ref state))
{
return;
}
}
}
break;
}
}
if (nodeId == 0)
{
break;
}
int nodePos = _bp.ToPos(nodeId);
nodePos = _bp.Enclose(nodePos);
if (nodePos < 0)
{
return;
}
nodeId = _bp.ToNodeId(nodePos);
}
}
}
public void Match<T>(ReadOnlySpan<byte> utf8Text, MatchCallback<T> callback, ref T state)
{
int nodeId = 0;
for (int index = 0; index < utf8Text.Length; index++)
{
long c = utf8Text[index];
while (true)
{
long nodeSparseIndex = _nodesPerCharacter * c + (uint)nodeId;
int nodePlainIndex = _nodeMap.Rank1(nodeSparseIndex);
if (nodePlainIndex != 0)
{
long foundNodeSparseIndex = _nodeMap.Select1Ex(nodePlainIndex - 1);
if (foundNodeSparseIndex > 0 && foundNodeSparseIndex == nodeSparseIndex)
{
nodeId = nodePlainIndex;
if (callback != null)
{
// Match full word.
if (_wordMap.Has(nodePlainIndex))
{
int wordLength = _wordLengths[_wordMap.Rank1((uint)nodePlainIndex) - 1];
int startIndex = index + 1 - wordLength;
if (!callback(utf8Text, startIndex, index + 1, nodeId, ref state))
{
return;
}
}
// If this is a phrase composed of multiple words, also match each sub-word.
while (_multiWordMap.Has(nodePlainIndex))
{
nodePlainIndex = _multiWordIndices[_multiWordMap.Rank1((uint)nodePlainIndex) - 1];
int wordLength = _wordMap.Has(nodePlainIndex) ? _wordLengths[_wordMap.Rank1(nodePlainIndex) - 1] : 0;
int startIndex = index + 1 - wordLength;
if (!callback(utf8Text, startIndex, index + 1, nodePlainIndex, ref state))
{
return;
}
}
}
break;
}
}
if (nodeId == 0)
{
break;
}
int nodePos = _bp.ToPos(nodeId);
nodePos = _bp.Enclose(nodePos);
if (nodePos < 0)
{
return;
}
nodeId = _bp.ToNodeId(nodePos);
}
}
}
public string GetWordList(bool includeMultiWord = true)
{
// Storage must be large enough to fit the largest word in the dictionary.
// Since this is only used for debugging, it's fine to increase the size manually if needed.
StringBuilder sb = new();
Span<byte> storage = new byte[1024];
// Traverse trie from the root.
GetWord(sb, storage, 0, 0, includeMultiWord);
return sb.ToString();
}
private void GetWord(StringBuilder sb, Span<byte> storage, int storageOffset, int nodeId, bool includeMultiWord)
{
int characters = (int)((_nodeMap.RangeEndValue + _nodesPerCharacter - 1) / _nodesPerCharacter);
for (int c = 0; c < characters; c++)
{
long nodeSparseIndex = _nodesPerCharacter * c + (uint)nodeId;
int nodePlainIndex = _nodeMap.Rank1(nodeSparseIndex);
if (nodePlainIndex != 0)
{
long foundNodeSparseIndex = _nodeMap.Select1Ex(nodePlainIndex - 1);
if (foundNodeSparseIndex > 0 && foundNodeSparseIndex == nodeSparseIndex)
{
storage[storageOffset] = (byte)c;
int nextNodeId = nodePlainIndex;
if (_wordMap.Has(nodePlainIndex))
{
sb.AppendLine(Encoding.UTF8.GetString(storage[..(storageOffset + 1)]));
// Some basic validation to ensure we imported the dictionary properly.
int wordLength = _wordLengths[_wordMap.Rank1((uint)nodePlainIndex) - 1];
Debug.Assert(storageOffset + 1 == wordLength);
}
if (includeMultiWord)
{
int lastMultiWordIndex = 0;
string multiWord = "";
while (_multiWordMap.Has(nodePlainIndex))
{
nodePlainIndex = _multiWordIndices[_multiWordMap.Rank1((uint)nodePlainIndex) - 1];
int wordLength = _wordMap.Has(nodePlainIndex) ? _wordLengths[_wordMap.Rank1(nodePlainIndex) - 1] : 0;
int startIndex = storageOffset + 1 - wordLength;
multiWord += Encoding.UTF8.GetString(storage[lastMultiWordIndex..startIndex]) + " ";
lastMultiWordIndex = startIndex;
}
if (lastMultiWordIndex != 0)
{
multiWord += Encoding.UTF8.GetString(storage[lastMultiWordIndex..(storageOffset + 1)]);
sb.AppendLine(multiWord);
}
}
GetWord(sb, storage, storageOffset + 1, nextNodeId, includeMultiWord);
}
}
}
}
}
}

View File

@@ -0,0 +1,63 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Ngc.Detail
{
ref struct BinaryReader
{
private readonly ReadOnlySpan<byte> _data;
private int _offset;
public BinaryReader(ReadOnlySpan<byte> data)
{
_data = data;
}
public bool Read<T>(out T value) where T : unmanaged
{
int byteLength = Unsafe.SizeOf<T>();
if ((uint)(_offset + byteLength) <= (uint)_data.Length)
{
value = MemoryMarshal.Cast<byte, T>(_data[_offset..])[0];
_offset += byteLength;
return true;
}
value = default;
return false;
}
public int AllocateAndReadArray<T>(ref T[] array, int length, int maxLengthExclusive) where T : unmanaged
{
return AllocateAndReadArray(ref array, Math.Min(length, maxLengthExclusive));
}
public int AllocateAndReadArray<T>(ref T[] array, int length) where T : unmanaged
{
array = new T[length];
return ReadArray(array);
}
public int ReadArray<T>(T[] array) where T : unmanaged
{
if (array != null)
{
int byteLength = array.Length * Unsafe.SizeOf<T>();
byteLength = Math.Min(byteLength, _data.Length - _offset);
MemoryMarshal.Cast<byte, T>(_data.Slice(_offset, byteLength)).CopyTo(array);
_offset += byteLength;
return byteLength / Unsafe.SizeOf<T>();
}
return 0;
}
}
}

View File

@@ -0,0 +1,78 @@
namespace Ryujinx.Horizon.Sdk.Ngc.Detail
{
class BitVector32
{
private const int BitsPerWord = Set.BitsPerWord;
private int _bitLength;
private uint[] _array;
public int BitLength => _bitLength;
public uint[] Array => _array;
public BitVector32()
{
_bitLength = 0;
_array = null;
}
public BitVector32(int length)
{
_bitLength = length;
_array = new uint[(length + BitsPerWord - 1) / BitsPerWord];
}
public bool Has(int index)
{
if ((uint)index < (uint)_bitLength)
{
int wordIndex = index / BitsPerWord;
int wordBitOffset = index % BitsPerWord;
return ((_array[wordIndex] >> wordBitOffset) & 1u) != 0;
}
return false;
}
public bool TurnOn(int index, int count)
{
for (int bit = 0; bit < count; bit++)
{
if (!TurnOn(index + bit))
{
return false;
}
}
return true;
}
public bool TurnOn(int index)
{
if ((uint)index < (uint)_bitLength)
{
int wordIndex = index / BitsPerWord;
int wordBitOffset = index % BitsPerWord;
_array[wordIndex] |= 1u << wordBitOffset;
return true;
}
return false;
}
public bool Import(ref BinaryReader reader)
{
if (!reader.Read(out _bitLength))
{
return false;
}
int arrayLength = (_bitLength + BitsPerWord - 1) / BitsPerWord;
return reader.AllocateAndReadArray(ref _array, arrayLength) == arrayLength;
}
}
}

View File

@@ -0,0 +1,54 @@
namespace Ryujinx.Horizon.Sdk.Ngc.Detail
{
class Bp
{
private readonly BpNode _firstNode = new();
private readonly SbvSelect _sbvSelect = new();
public bool Import(ref BinaryReader reader)
{
return _firstNode.Import(ref reader) && _sbvSelect.Import(ref reader);
}
public int ToPos(int index)
{
return _sbvSelect.Select(_firstNode.Set, index);
}
public int Enclose(int index)
{
if ((uint)index < (uint)_firstNode.Set.BitVector.BitLength)
{
if (!_firstNode.Set.Has(index))
{
index = _firstNode.FindOpen(index);
}
if (index > 0)
{
return _firstNode.Enclose(index);
}
}
return -1;
}
public int ToNodeId(int index)
{
if ((uint)index < (uint)_firstNode.Set.BitVector.BitLength)
{
if (!_firstNode.Set.Has(index))
{
index = _firstNode.FindOpen(index);
}
if (index >= 0)
{
return _firstNode.Set.Rank1(index) - 1;
}
}
return -1;
}
}
}

View File

@@ -0,0 +1,241 @@
namespace Ryujinx.Horizon.Sdk.Ngc.Detail
{
class BpNode
{
private readonly Set _set = new();
private SparseSet _sparseSet;
private BpNode _nextNode;
public Set Set => _set;
public bool Import(ref BinaryReader reader)
{
if (!_set.Import(ref reader))
{
return false;
}
if (!reader.Read(out byte hasNext))
{
return false;
}
if (hasNext == 0)
{
return true;
}
_sparseSet = new();
_nextNode = new();
return _sparseSet.Import(ref reader) && _nextNode.Import(ref reader);
}
public int FindOpen(int index)
{
uint membershipBits = _set.BitVector.Array[index / Set.BitsPerWord];
int wordBitOffset = index % Set.BitsPerWord;
int unsetBits = 1;
for (int bit = wordBitOffset - 1; bit >= 0; bit--)
{
if (((membershipBits >> bit) & 1) != 0)
{
if (--unsetBits == 0)
{
return (index & ~(Set.BitsPerWord - 1)) | bit;
}
}
else
{
unsetBits++;
}
}
int plainIndex = _sparseSet.Rank1(index);
if (plainIndex == 0)
{
return -1;
}
int newIndex = index;
if (!_sparseSet.Has(index))
{
if (plainIndex == 0 || _nextNode == null)
{
return -1;
}
newIndex = _sparseSet.Select1(plainIndex);
if (newIndex < 0)
{
return -1;
}
}
else
{
plainIndex--;
}
int openIndex = _nextNode.FindOpen(plainIndex);
if (openIndex < 0)
{
return -1;
}
int openSparseIndex = _sparseSet.Select1(openIndex);
if (openSparseIndex < 0)
{
return -1;
}
if (newIndex != index)
{
unsetBits = 1;
for (int bit = newIndex % Set.BitsPerWord - 1; bit > wordBitOffset; bit--)
{
unsetBits += ((membershipBits >> bit) & 1) != 0 ? -1 : 1;
}
int bestCandidate = -1;
membershipBits = _set.BitVector.Array[openSparseIndex / Set.BitsPerWord];
for (int bit = openSparseIndex % Set.BitsPerWord + 1; bit < Set.BitsPerWord; bit++)
{
if (unsetBits - 1 == 0)
{
bestCandidate = bit;
}
unsetBits += ((membershipBits >> bit) & 1) != 0 ? -1 : 1;
}
return (openSparseIndex & ~(Set.BitsPerWord - 1)) | bestCandidate;
}
else
{
return openSparseIndex;
}
}
public int Enclose(int index)
{
uint membershipBits = _set.BitVector.Array[index / Set.BitsPerWord];
int unsetBits = 1;
for (int bit = index % Set.BitsPerWord - 1; bit >= 0; bit--)
{
if (((membershipBits >> bit) & 1) != 0)
{
if (--unsetBits == 0)
{
return (index & ~(Set.BitsPerWord - 1)) + bit;
}
}
else
{
unsetBits++;
}
}
int setBits = 2;
for (int bit = index % Set.BitsPerWord + 1; bit < Set.BitsPerWord; bit++)
{
if (((membershipBits >> bit) & 1) != 0)
{
setBits++;
}
else
{
if (--setBits == 0)
{
return FindOpen((index & ~(Set.BitsPerWord - 1)) + bit);
}
}
}
int newIndex = index;
if (!_sparseSet.Has(index))
{
newIndex = _sparseSet.Select1(_sparseSet.Rank1(index));
if (newIndex < 0)
{
return -1;
}
}
if (!_set.Has(newIndex))
{
newIndex = FindOpen(newIndex);
if (newIndex < 0)
{
return -1;
}
}
else
{
newIndex = _nextNode.Enclose(_sparseSet.Rank1(newIndex) - 1);
if (newIndex < 0)
{
return -1;
}
newIndex = _sparseSet.Select1(newIndex);
}
int nearestIndex = _sparseSet.Select1(_sparseSet.Rank1(newIndex));
if (nearestIndex < 0)
{
return -1;
}
setBits = 0;
membershipBits = _set.BitVector.Array[newIndex / Set.BitsPerWord];
if ((newIndex / Set.BitsPerWord) == (nearestIndex / Set.BitsPerWord))
{
for (int bit = nearestIndex % Set.BitsPerWord - 1; bit >= newIndex % Set.BitsPerWord; bit--)
{
if (((membershipBits >> bit) & 1) != 0)
{
if (++setBits > 0)
{
return (newIndex & ~(Set.BitsPerWord - 1)) + bit;
}
}
else
{
setBits--;
}
}
}
else
{
for (int bit = Set.BitsPerWord - 1; bit >= newIndex % Set.BitsPerWord; bit--)
{
if (((membershipBits >> bit) & 1) != 0)
{
if (++setBits > 0)
{
return (newIndex & ~(Set.BitsPerWord - 1)) + bit;
}
}
else
{
setBits--;
}
}
}
return -1;
}
}
}

View File

@@ -0,0 +1,100 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Ngc.Detail
{
class CompressedArray
{
private const int MaxUncompressedEntries = 64;
private const int CompressedEntriesPerBlock = 64;
private const int BitsPerWord = Set.BitsPerWord;
private readonly struct BitfieldRange
{
private readonly uint _range;
private readonly int _baseValue;
public int BitfieldIndex => (int)(_range & 0x7ffffff);
public int BitfieldLength => (int)(_range >> 27) + 1;
public int BaseValue => _baseValue;
public BitfieldRange(uint range, int baseValue)
{
_range = range;
_baseValue = baseValue;
}
}
private uint[] _bitfieldRanges;
private uint[] _bitfields;
private int[] _uncompressedArray;
public int Length => (_bitfieldRanges.Length / 2) * CompressedEntriesPerBlock + _uncompressedArray.Length;
public int this[int index]
{
get
{
var ranges = GetBitfieldRanges();
int rangeBlockIndex = index / CompressedEntriesPerBlock;
if (rangeBlockIndex < ranges.Length)
{
var range = ranges[rangeBlockIndex];
int bitfieldLength = range.BitfieldLength;
int bitfieldOffset = (index % CompressedEntriesPerBlock) * bitfieldLength;
int bitfieldIndex = range.BitfieldIndex + (bitfieldOffset / BitsPerWord);
int bitOffset = bitfieldOffset % BitsPerWord;
ulong bitfieldValue = _bitfields[bitfieldIndex];
// If the bit fields crosses the word boundary, let's load the next one to ensure we
// have access to the full value.
if (bitOffset + bitfieldLength > BitsPerWord)
{
bitfieldValue |= (ulong)_bitfields[bitfieldIndex + 1] << 32;
}
int value = (int)(bitfieldValue >> bitOffset) & ((1 << bitfieldLength) - 1);
// Sign-extend.
int remainderBits = BitsPerWord - bitfieldLength;
value <<= remainderBits;
value >>= remainderBits;
return value + range.BaseValue;
}
else if (rangeBlockIndex < _uncompressedArray.Length + _bitfieldRanges.Length * BitsPerWord)
{
return _uncompressedArray[index % MaxUncompressedEntries];
}
return 0;
}
}
private ReadOnlySpan<BitfieldRange> GetBitfieldRanges()
{
return MemoryMarshal.Cast<uint, BitfieldRange>(_bitfieldRanges);
}
public bool Import(ref BinaryReader reader)
{
if (!reader.Read(out int bitfieldRangesCount) ||
reader.AllocateAndReadArray(ref _bitfieldRanges, bitfieldRangesCount) != bitfieldRangesCount)
{
return false;
}
if (!reader.Read(out int bitfieldsCount) || reader.AllocateAndReadArray(ref _bitfields, bitfieldsCount) != bitfieldsCount)
{
return false;
}
return reader.Read(out byte uncompressedArrayLength) &&
reader.AllocateAndReadArray(ref _uncompressedArray, uncompressedArrayLength, MaxUncompressedEntries) == uncompressedArrayLength;
}
}
}

View File

@@ -0,0 +1,404 @@
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Fs;
using System;
using System.IO;
using System.IO.Compression;
namespace Ryujinx.Horizon.Sdk.Ngc.Detail
{
class ContentsReader : IDisposable
{
private const string MountName = "NgWord";
private const string VersionFilePath = $"{MountName}:/version.dat";
private const ulong DataId = 0x100000000000823UL;
private enum AcType
{
AcNotB,
AcB1,
AcB2,
AcSimilarForm,
TableSimilarForm,
}
private readonly IFsClient _fsClient;
private readonly object _lock;
private bool _intialized;
private ulong _cacheSize;
public ContentsReader(IFsClient fsClient)
{
_lock = new();
_fsClient = fsClient;
}
private static void MakeMountPoint(out string path, AcType type, int regionIndex)
{
path = null;
switch (type)
{
case AcType.AcNotB:
if (regionIndex < 0)
{
path = $"{MountName}:/ac_common_not_b_nx";
}
else
{
path = $"{MountName}:/ac_{regionIndex}_not_b_nx";
}
break;
case AcType.AcB1:
if (regionIndex < 0)
{
path = $"{MountName}:/ac_common_b1_nx";
}
else
{
path = $"{MountName}:/ac_{regionIndex}_b1_nx";
}
break;
case AcType.AcB2:
if (regionIndex < 0)
{
path = $"{MountName}:/ac_common_b2_nx";
}
else
{
path = $"{MountName}:/ac_{regionIndex}_b2_nx";
}
break;
case AcType.AcSimilarForm:
path = $"{MountName}:/ac_similar_form_nx";
break;
case AcType.TableSimilarForm:
path = $"{MountName}:/table_similar_form_nx";
break;
}
}
public Result Initialize(ulong cacheSize)
{
lock (_lock)
{
if (_intialized)
{
return Result.Success;
}
Result result = _fsClient.QueryMountSystemDataCacheSize(out long dataCacheSize, DataId);
if (result.IsFailure)
{
return result;
}
if (cacheSize < (ulong)dataCacheSize)
{
return NgcResult.InvalidSize;
}
result = _fsClient.MountSystemData(MountName, DataId);
if (result.IsFailure)
{
// Official firmware would return the result here,
// we don't to support older firmware where the archive didn't exist yet.
return Result.Success;
}
_cacheSize = cacheSize;
_intialized = true;
return Result.Success;
}
}
public Result Reload()
{
lock (_lock)
{
if (!_intialized)
{
return Result.Success;
}
_fsClient.Unmount(MountName);
Result result = Result.Success;
try
{
result = _fsClient.QueryMountSystemDataCacheSize(out long cacheSize, DataId);
if (result.IsFailure)
{
return result;
}
if (_cacheSize < (ulong)cacheSize)
{
result = NgcResult.InvalidSize;
return NgcResult.InvalidSize;
}
result = _fsClient.MountSystemData(MountName, DataId);
if (result.IsFailure)
{
return result;
}
}
finally
{
if (result.IsFailure)
{
_intialized = false;
_cacheSize = 0;
}
}
}
return Result.Success;
}
private Result GetFileSize(out long size, string filePath)
{
size = 0;
lock (_lock)
{
Result result = _fsClient.OpenFile(out FileHandle handle, filePath, OpenMode.Read);
if (result.IsFailure)
{
return result;
}
try
{
result = _fsClient.GetFileSize(out size, handle);
if (result.IsFailure)
{
return result;
}
}
finally
{
_fsClient.CloseFile(handle);
}
}
return Result.Success;
}
private Result GetFileContent(Span<byte> destination, string filePath)
{
lock (_lock)
{
Result result = _fsClient.OpenFile(out FileHandle handle, filePath, OpenMode.Read);
if (result.IsFailure)
{
return result;
}
try
{
result = _fsClient.ReadFile(handle, 0, destination);
if (result.IsFailure)
{
return result;
}
}
finally
{
_fsClient.CloseFile(handle);
}
}
return Result.Success;
}
public Result GetVersionDataSize(out long size)
{
return GetFileSize(out size, VersionFilePath);
}
public Result GetVersionData(Span<byte> destination)
{
return GetFileContent(destination, VersionFilePath);
}
public Result ReadDictionaries(out AhoCorasick partialWordsTrie, out AhoCorasick completeWordsTrie, out AhoCorasick delimitedWordsTrie, int regionIndex)
{
completeWordsTrie = null;
delimitedWordsTrie = null;
MakeMountPoint(out string partialWordsTriePath, AcType.AcNotB, regionIndex);
MakeMountPoint(out string completeWordsTriePath, AcType.AcB1, regionIndex);
MakeMountPoint(out string delimitedWordsTriePath, AcType.AcB2, regionIndex);
Result result = ReadDictionary(out partialWordsTrie, partialWordsTriePath);
if (result.IsFailure)
{
return NgcResult.DataAccessError;
}
result = ReadDictionary(out completeWordsTrie, completeWordsTriePath);
if (result.IsFailure)
{
return NgcResult.DataAccessError;
}
return ReadDictionary(out delimitedWordsTrie, delimitedWordsTriePath);
}
public Result ReadSimilarFormDictionary(out AhoCorasick similarFormTrie)
{
MakeMountPoint(out string similarFormTriePath, AcType.AcSimilarForm, 0);
return ReadDictionary(out similarFormTrie, similarFormTriePath);
}
public Result ReadSimilarFormTable(out SimilarFormTable similarFormTable)
{
similarFormTable = null;
MakeMountPoint(out string similarFormTablePath, AcType.TableSimilarForm, 0);
Result result = ReadGZipCompressedArchive(out byte[] data, similarFormTablePath);
if (result.IsFailure)
{
return result;
}
BinaryReader reader = new(data);
SimilarFormTable table = new();
if (!table.Import(ref reader))
{
// Official firmware doesn't return an error here and just assumes the import was successful.
return NgcResult.DataAccessError;
}
similarFormTable = table;
return Result.Success;
}
public static Result ReadNotSeparatorDictionary(out AhoCorasick notSeparatorTrie)
{
notSeparatorTrie = null;
BinaryReader reader = new(EmbeddedTries.NotSeparatorTrie);
AhoCorasick ac = new();
if (!ac.Import(ref reader))
{
// Official firmware doesn't return an error here and just assumes the import was successful.
return NgcResult.DataAccessError;
}
notSeparatorTrie = ac;
return Result.Success;
}
private Result ReadDictionary(out AhoCorasick trie, string path)
{
trie = null;
Result result = ReadGZipCompressedArchive(out byte[] data, path);
if (result.IsFailure)
{
return result;
}
BinaryReader reader = new(data);
AhoCorasick ac = new();
if (!ac.Import(ref reader))
{
// Official firmware doesn't return an error here and just assumes the import was successful.
return NgcResult.DataAccessError;
}
trie = ac;
return Result.Success;
}
private Result ReadGZipCompressedArchive(out byte[] data, string filePath)
{
data = null;
Result result = _fsClient.OpenFile(out FileHandle handle, filePath, OpenMode.Read);
if (result.IsFailure)
{
return result;
}
try
{
result = _fsClient.GetFileSize(out long fileSize, handle);
if (result.IsFailure)
{
return result;
}
data = new byte[fileSize];
result = _fsClient.ReadFile(handle, 0, data.AsSpan());
if (result.IsFailure)
{
return result;
}
}
finally
{
_fsClient.CloseFile(handle);
}
try
{
data = DecompressGZipCompressedStream(data);
}
catch (InvalidDataException)
{
// Official firmware returns a different error, but it is translated to this error on the caller.
return NgcResult.DataAccessError;
}
return Result.Success;
}
private static byte[] DecompressGZipCompressedStream(byte[] data)
{
using MemoryStream input = new(data);
using GZipStream gZipStream = new(input, CompressionMode.Decompress);
using MemoryStream output = new();
gZipStream.CopyTo(output);
return output.ToArray();
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
lock (_lock)
{
if (!_intialized)
{
return;
}
_fsClient.Unmount(MountName);
_intialized = false;
}
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View File

@@ -0,0 +1,266 @@
using System;
namespace Ryujinx.Horizon.Sdk.Ngc.Detail
{
static class EmbeddedTries
{
public static ReadOnlySpan<byte> NotSeparatorTrie => new byte[]
{
0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
0xE9, 0xFF, 0xE9, 0xFF, 0xF4, 0xFF, 0xFA, 0xBF, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x5F, 0xFF, 0xAF,
0xFF, 0xEB, 0xFF, 0xFA, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFB, 0x7F, 0xFF, 0xEF, 0xFF, 0xFD,
0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xF7, 0xFF, 0xE8, 0xFF, 0xE9, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xFC, 0x3F, 0xFF, 0xCF, 0xFF, 0xF3, 0xFF, 0xFA, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xE7, 0xFF,
0xFC, 0x9F, 0xFF, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x9F, 0xFF, 0xE7, 0xFF, 0xF9, 0x7F,
0x00, 0x00, 0x00, 0x00, 0xFE, 0x5F, 0xFF, 0xCF, 0xFF, 0xF3, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00,
0x3F, 0xFF, 0xCF, 0xFF, 0xF3, 0xFF, 0xFC, 0x3F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xCF, 0xFF, 0xF3,
0xFF, 0xFC, 0x3F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xCF, 0xFF, 0xFB, 0x7F, 0xFE, 0x9F, 0xFF, 0xF3,
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x9F, 0xFF, 0xF3, 0xFF, 0xFC, 0x9F, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xF3, 0x7F, 0xFE, 0xCF, 0xFF, 0xF5, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x03, 0x00, 0x00,
0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x03, 0x00, 0x00, 0x00, 0x8A, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6E, 0x03, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0x54, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
0x55, 0x55, 0x55, 0xA9, 0x52, 0x55, 0x55, 0xA9, 0xAA, 0xAA, 0xAA, 0x54, 0x55, 0x55, 0x55, 0x55,
0x55, 0x55, 0xAA, 0x54, 0x55, 0xA5, 0x4A, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0x52, 0x55, 0x55,
0x95, 0xAA, 0xAA, 0xAA, 0x54, 0x55, 0x55, 0xA5, 0xAA, 0xAA, 0x2A, 0x55, 0x55, 0x55, 0x55, 0x55,
0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xA5, 0xAA, 0xAA, 0xAA, 0x54, 0x55, 0x55, 0x55, 0x55, 0x55,
0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x4A,
0x55, 0x55, 0x55, 0xA9, 0xAA, 0xAA, 0x52, 0x55, 0x55, 0xA5, 0xAA, 0xAA, 0x4A, 0x55, 0x55, 0x05,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x7D, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x77, 0x01, 0x00,
0x00, 0xF7, 0x01, 0x00, 0x00, 0x77, 0x02, 0x00, 0x00, 0xF7, 0x02, 0x00, 0x00, 0x6E, 0x03, 0x00,
0x00, 0x6E, 0x03, 0x00, 0x00, 0x6E, 0x03, 0x00, 0x00, 0x6E, 0x03, 0x00, 0x00, 0x6E, 0x03, 0x00,
0x00, 0x6E, 0x03, 0x00, 0x00, 0x6E, 0x03, 0x00, 0x00, 0x6E, 0x03, 0x00, 0x00, 0x6E, 0x03, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x1F, 0x2F, 0x3F, 0x4E, 0x5E, 0x6D, 0x00, 0x0F, 0x1E,
0x2E, 0x3D, 0x4C, 0x5C, 0x6B, 0x00, 0x10, 0x20, 0x2F, 0x3F, 0x4F, 0x5F, 0x6F, 0x00, 0x10, 0x20,
0x30, 0x40, 0x50, 0x60, 0x70, 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x00, 0x10, 0x20,
0x30, 0x40, 0x50, 0x60, 0x70, 0x00, 0x10, 0x20, 0x30, 0x3F, 0x4F, 0x5E, 0x6D, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6E, 0x03,
0x00, 0x00, 0x01, 0x00, 0x01, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20,
0x00, 0x20, 0x00, 0x10, 0x00, 0x08, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01,
0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00,
0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00,
0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00,
0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00,
0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
0x21, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F,
0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E,
0x00, 0x02, 0x04, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8A, 0x03, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x8A, 0x03, 0x00, 0x00, 0x00, 0x8A, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4F, 0xC5, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x51, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x03, 0x00,
0x00, 0x07, 0x00, 0x00, 0x00, 0xC6, 0x00, 0x00, 0x00, 0xCF, 0xED, 0x81, 0x61, 0xD9, 0xDC, 0x8A,
0xD3, 0xF0, 0xBB, 0x05, 0x6E, 0xEB, 0x0D, 0x88, 0x6C, 0x39, 0x62, 0x01, 0x95, 0x82, 0xCF, 0xEE,
0x3A, 0x7F, 0x53, 0xDF, 0x09, 0x90, 0xF7, 0x06, 0xA4, 0x7A, 0x2D, 0xB3, 0xE7, 0xFA, 0x20, 0x48,
0x0F, 0x38, 0x34, 0xED, 0xBC, 0x8A, 0x96, 0xAB, 0x8E, 0xE3, 0xFF, 0xC6, 0xD2, 0xBF, 0xC0, 0x90,
0x06, 0x34, 0xDF, 0xF0, 0xDB, 0xDE, 0x27, 0x2E, 0xD5, 0x3C, 0xA2, 0x22, 0x72, 0xBD, 0x02, 0x0D,
0x1F, 0xB2, 0x99, 0xBE, 0x17, 0x26, 0xA1, 0xEF, 0x40, 0xF2, 0x61, 0xE1, 0x16, 0x17, 0xA4, 0xF4,
0x3A, 0x0F, 0x3C, 0x3A, 0xAB, 0x74, 0x83, 0x93, 0xB2, 0x09, 0x43, 0x52, 0x6E, 0xB8, 0xBF, 0xC8,
0x9C, 0x6A, 0x73, 0xD3, 0x0C, 0xC8, 0x5C, 0x71, 0xCD, 0x87, 0xCA, 0x28, 0xF6, 0xEB, 0x87, 0x60,
0x3D, 0xA5, 0x15, 0x9B, 0xAA, 0x99, 0x23, 0x9F, 0xD6, 0x2E, 0x79, 0x58, 0xE9, 0x8E, 0x54, 0xB0,
0xF8, 0x07, 0x6F, 0x6C, 0x52, 0xB7, 0xE2, 0x34, 0x42, 0x8C, 0x7A, 0xD5, 0xEC, 0xA4, 0xFE, 0x52,
0x9A, 0x05, 0x9F, 0xDD, 0x8D, 0x73, 0x8B, 0xA6, 0xDB, 0xA7, 0x84, 0xD0, 0xAB, 0xB7, 0xCC, 0x9E,
0x4B, 0xD8, 0xB2, 0xDC, 0x0F, 0xE8, 0x3A, 0x56, 0xB9, 0x63, 0x75, 0x1C, 0x7F, 0x89, 0xDF, 0x7C,
0x84, 0xE2, 0x8C, 0xA9, 0x0D, 0xA3, 0xDF, 0xF6, 0x3E, 0xC7, 0xCE, 0x1B, 0x24, 0x94, 0xB8, 0xE8,
0xD7, 0xDC, 0xA6, 0xEF, 0x85, 0xA1, 0x7D, 0x00, 0xE1, 0x78, 0xD4, 0x8B, 0x13, 0xCB, 0xB6, 0x4B,
0x5E, 0xCB, 0xF3, 0xC0, 0xA3, 0x09, 0x68, 0x68, 0x4C, 0xF4, 0x98, 0x0D, 0x38, 0x0D, 0xBF, 0xFB,
0x8B, 0xCC, 0x55, 0x71, 0x21, 0xC1, 0xFC, 0x3B, 0x60, 0x77, 0x9D, 0x3F, 0x54, 0x46, 0x61, 0x4A,
0xC8, 0xA5, 0xDB, 0x21, 0x8A, 0xCA, 0x73, 0x7D, 0x10, 0xF9, 0xB4, 0xD6, 0x9E, 0x15, 0x8E, 0x58,
0x94, 0x3C, 0xA9, 0xF1, 0x7F, 0x63, 0x93, 0xBA, 0xD5, 0x51, 0x35, 0xA1, 0x93, 0x93, 0xF5, 0xEE,
0x13, 0x97, 0xD2, 0x2C, 0xF8, 0x97, 0xFD, 0x98, 0x58, 0xD3, 0x6A, 0x8C, 0x2E, 0x4C, 0x42, 0xAF,
0xDE, 0x32, 0xC1, 0x4B, 0x5A, 0x61, 0x6D, 0xF9, 0xA3, 0xB3, 0xCA, 0x1D, 0xAB, 0x13, 0xE3, 0x14,
0xAC, 0xBB, 0xF3, 0x33, 0xA7, 0xDA, 0x30, 0xFA, 0xED, 0x40, 0xBB, 0x6A, 0x62, 0xC0, 0x30, 0x8A,
0xFD, 0x9A, 0xDB, 0xF4, 0x49, 0x7B, 0xA6, 0x3B, 0x17, 0x90, 0xD6, 0x2E, 0x79, 0x2D, 0xCF, 0x63,
0xE4, 0xB8, 0x1F, 0x5B, 0xD1, 0xDC, 0x8A, 0xD3, 0xF0, 0xBB, 0xBF, 0x73, 0xEF, 0x11, 0xE2, 0x0F,
0x29, 0xF8, 0xEC, 0xAE, 0xF3, 0x07, 0x5B, 0x11, 0x5F, 0x90, 0xB0, 0x53, 0xAE, 0x65, 0xF6, 0x5C,
0x1F, 0x44, 0x80, 0x4F, 0xC1, 0x83, 0x63, 0x9F, 0xE1, 0xAA, 0xE3, 0xF8, 0xBF, 0xB1, 0x51, 0x66,
0x19, 0x19, 0x13, 0xA0, 0xF7, 0x6D, 0xEF, 0x13, 0x97, 0x12, 0x75, 0xAC, 0xB7, 0x8C, 0x60, 0x3F,
0xC5, 0x71, 0x9B, 0xBE, 0x17, 0x26, 0xA1, 0x97, 0xB7, 0x0D, 0x6A, 0xE9, 0x28, 0x99, 0x68, 0x79,
0x1E, 0x78, 0x74, 0x56, 0x39, 0xF4, 0x5D, 0x75, 0x23, 0x7A, 0xB6, 0xEF, 0xFE, 0x22, 0x73, 0xAA,
0x0D, 0xE5, 0x01, 0x5A, 0xD0, 0x89, 0x2A, 0xE7, 0x0F, 0x95, 0x51, 0xEC, 0xD7, 0xE4, 0x2F, 0x7C,
0x4B, 0xAC, 0xEC, 0x3D, 0x88, 0x7C, 0x5A, 0xBB, 0xE4, 0xD5, 0x50, 0x41, 0x56, 0xC5, 0xBC, 0x7C,
0x63, 0x93, 0xBA, 0x15, 0xA7, 0x61, 0xC8, 0x47, 0xFA, 0x65, 0x1B, 0x07, 0x97, 0xD2, 0x2C, 0xF8,
0xEC, 0xAE, 0x35, 0x29, 0x6E, 0xDA, 0x0E, 0x6D, 0x84, 0x5E, 0xBD, 0x65, 0xF6, 0x5C, 0x27, 0xCD,
0xCC, 0x73, 0x80, 0xF6, 0xB2, 0xCA, 0x1D, 0xAB, 0xE3, 0xF8, 0xDF, 0xD5, 0x83, 0xF7, 0x15, 0xE4,
0x50, 0x6D, 0x18, 0xFD, 0xB6, 0xF7, 0x09, 0xDC, 0x51, 0x7F, 0xA0, 0xB8, 0x57, 0xB0, 0x5F, 0x73,
0x9B, 0xBE, 0x17, 0x26, 0x42, 0x42, 0xC4, 0x83, 0xAF, 0xE9, 0x92, 0xD7, 0xF2, 0x3C, 0xF0, 0xE8,
0x30, 0x1D, 0x1B, 0x94, 0xE0, 0x47, 0x9C, 0x86, 0xDF, 0xFD, 0x45, 0xE6, 0x64, 0xC5, 0x94, 0x64,
0x8C, 0xA4, 0xB3, 0xBB, 0xCE, 0x1F, 0x2A, 0xA3, 0x18, 0x58, 0xF4, 0xE2, 0x59, 0xA6, 0xD8, 0x73,
0x7D, 0x10, 0xF9, 0xB4, 0x76, 0x6A, 0x56, 0xCE, 0xD8, 0x15, 0xC7, 0xFF, 0x8D, 0x4D, 0xEA, 0x56,
0xA4, 0xDB, 0x86, 0x50, 0xD5, 0x99, 0xBD, 0x4F, 0x5C, 0x4A, 0xB3, 0xE0, 0xD3, 0x0F, 0x6C, 0x6A,
0x69, 0x71, 0x7B, 0x21, 0xF4, 0xEA, 0x2D, 0xB3, 0x08, 0xE5, 0x95, 0xEC, 0xDB, 0x03, 0x1E, 0xAB,
0xDC, 0xB1, 0x3A, 0x96, 0x50, 0xC3, 0x6E, 0x64, 0x41, 0x91, 0xA9, 0x0D, 0xA3, 0xDF, 0x36, 0x27,
0xEA, 0x5D, 0xE3, 0xA5, 0x0F, 0xCA, 0xE8, 0xD7, 0xDC, 0xA6, 0xEF, 0x26, 0x74, 0x5D, 0xC0, 0xCD,
0x78, 0x5A, 0xC9, 0x6B, 0x79, 0x1E, 0x80, 0xC9, 0xFF, 0x8C, 0x96, 0x79, 0x84, 0xBA, 0x4D, 0xC3,
0xEF, 0xFE, 0x42, 0xC7, 0x4F, 0x58, 0xE0, 0x2D, 0x59, 0xB0, 0xBB, 0xCE, 0x1F, 0x2A, 0x44, 0xC3,
0x04, 0xA4, 0xBF, 0xF1, 0x96, 0xE7, 0xFA, 0x20, 0xF2, 0x71, 0x42, 0x3A, 0x2A, 0x42, 0xD0, 0x58,
0x8D, 0xFF, 0x1B, 0x9B, 0x14, 0x56, 0x73, 0xA2, 0x39, 0x96, 0xD0, 0xEF, 0x3E, 0x71, 0x29, 0xCD,
0xC4, 0xA4, 0x98, 0x6F, 0x89, 0xE9, 0x54, 0xB5, 0xE9, 0xC2, 0x24, 0xF4, 0xEA, 0xB1, 0x5D, 0x3B,
0x64, 0x55, 0x44, 0x9E, 0x3F, 0x3A, 0xAB, 0xDC, 0xD1, 0x8E, 0x2B, 0x4A, 0xBF, 0x2C, 0x77, 0x3F,
0x73, 0xAA, 0x0D, 0xA3, 0x00, 0xE1, 0x93, 0x9B, 0xB6, 0xE1, 0x0F, 0xA3, 0xD8, 0xAF, 0xB9, 0x55,
0x30, 0xB3, 0xE6, 0x39, 0x50, 0xD0, 0xDA, 0x25, 0xAF, 0x65, 0x8A, 0x75, 0x0C, 0xEF, 0x53, 0xBD,
0x60, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEC, 0xBF, 0x70, 0xEF, 0xBF, 0xB0, 0xFB, 0x37, 0xF4, 0xFD,
0x0D, 0xDD, 0xDF, 0x85, 0xEF, 0xEF, 0x89, 0xF7, 0xFB, 0xC4, 0xFB, 0x3E, 0x78, 0xF7, 0x13, 0xDF,
0x7D, 0xC5, 0xB7, 0x5F, 0xF8, 0xF6, 0x0B, 0x5F, 0x7F, 0xE1, 0xED, 0x2F, 0xDC, 0xFD, 0x85, 0xBD,
0xDF, 0xD0, 0xF7, 0xDF, 0xC1, 0xF7, 0x77, 0xF0, 0x7D, 0x0F, 0xBE, 0xEF, 0x83, 0xEF, 0xFB, 0xE0,
0xBD, 0x1F, 0xBC, 0xF7, 0x0B, 0x77, 0xBF, 0x70, 0xF7, 0x0B, 0xD7, 0xBF, 0x70, 0xFD, 0x0B, 0xD7,
0xBF, 0xB0, 0xFD, 0x1D, 0xBA, 0xDF, 0x83, 0xF7, 0x7B, 0x70, 0xDF, 0x87, 0xDE, 0xF7, 0x83, 0xFB,
0xFE, 0xE0, 0xDE, 0x2F, 0xDC, 0xFD, 0x85, 0xDB, 0xDF, 0x70, 0xFB, 0x1B, 0xAE, 0x7F, 0xC3, 0xF5,
0x6F, 0xD8, 0xFE, 0x0D, 0xDB, 0xDF, 0xA1, 0xFB, 0x3B, 0x78, 0xBF, 0x07, 0xF7, 0xF7, 0xE0, 0x7E,
0x1F, 0xDC, 0xF7, 0x83, 0x7B, 0x3F, 0xB8, 0xF7, 0x07, 0x77, 0xBF, 0x70, 0xFB, 0x0B, 0xD7, 0xBF,
0xF0, 0xFA, 0x17, 0xB6, 0xBF, 0x61, 0xF7, 0x37, 0x74, 0xBF, 0x83, 0xF7, 0x3D, 0xB8, 0xDF, 0x83,
0xFB, 0x3E, 0x78, 0xDF, 0x0F, 0xDE, 0xFD, 0xE0, 0xDD, 0x17, 0xDE, 0x7E, 0xE1, 0xF5, 0x0B, 0x0F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0xA8, 0x01, 0x00,
0x00, 0x53, 0x02, 0x00, 0x00, 0xFD, 0x02, 0x00, 0x00, 0x86, 0x03, 0x00, 0x00, 0x88, 0x03, 0x00,
0x00, 0x89, 0x03, 0x00, 0x00, 0x89, 0x03, 0x00, 0x00, 0x89, 0x03, 0x00, 0x00, 0x89, 0x03, 0x00,
0x00, 0x89, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x27, 0x3B, 0x00, 0x18, 0x2B, 0x42, 0x57, 0x6D, 0x81,
0x98, 0x00, 0x17, 0x2B, 0x42, 0x56, 0x6D, 0x80, 0x97, 0x00, 0x17, 0x2B, 0x43, 0x56, 0x6D, 0x80,
0x97, 0x00, 0x16, 0x2B, 0x40, 0x55, 0x69, 0x80, 0x94, 0x00, 0x13, 0x29, 0x3E, 0x52, 0x68, 0x7C,
0x89, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1C, 0x00,
0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2D, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x89, 0x03, 0x00, 0x00, 0x01, 0x80,
0x00, 0x00, 0x80, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00,
0x10, 0x00, 0x00, 0x02, 0x00, 0x80, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x40, 0x00, 0x00,
0x20, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x40, 0x00, 0x00,
0x20, 0x00, 0x00, 0x01, 0x00, 0x80, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x40, 0x00, 0x00,
0x20, 0x00, 0x00, 0x01, 0x00, 0x80, 0x00, 0x00, 0x08, 0x00, 0x00, 0x02, 0x00, 0x40, 0x00, 0x00,
0x08, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x80, 0x00, 0x00, 0x20, 0x00, 0x00,
0x01, 0x00, 0x40, 0x00, 0x00, 0x08, 0x00, 0x80, 0x00, 0x00, 0x20, 0x00, 0x00, 0x02, 0x40, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00,
0x19, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, 0x05, 0x07, 0x08, 0x0A, 0x0B,
0x00, 0x01, 0x02, 0x04, 0x06, 0x07, 0x09, 0x0A, 0x00, 0x01, 0x03, 0x04, 0x06, 0x07, 0x09, 0x0A,
0x00, 0x01, 0x03, 0x04, 0x06, 0x14, 0x07, 0x00, 0x00, 0xAB, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x02, 0x00, 0x00, 0x00, 0x00,
0x00, 0x81, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x81, 0x01, 0x00, 0x00, 0x01, 0x02, 0x00,
0x00, 0x81, 0x02, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0x81, 0x03, 0x00, 0x00, 0x00, 0x11, 0x21,
0x31, 0x41, 0x51, 0x61, 0x71, 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x00, 0x10, 0x20,
0x30, 0x40, 0x50, 0x60, 0x70, 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x00, 0x10, 0x20,
0x30, 0x40, 0x50, 0x60, 0x70, 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x00, 0x10, 0x20,
0x30, 0x40, 0x50, 0x60, 0x70, 0x00, 0x01, 0x14, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72,
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x38, 0x8E, 0xE3, 0x38, 0x8E,
0xE3, 0x38, 0x8E, 0xE3, 0x38, 0x8E, 0xE3, 0x38, 0x8E, 0xE3, 0x38, 0x8E, 0xE3, 0x38, 0x8E, 0xE3,
0x38, 0x8E, 0xE3, 0x38, 0x8E, 0xE3, 0x38, 0x8E, 0xE3, 0x38, 0x8E, 0xE3, 0x38, 0x8E, 0xE3, 0x38,
0x8E, 0xE3, 0x38, 0x8E, 0xE3, 0x18, 0x00, 0x00, 0x02, 0x00, 0x00, 0x51, 0x14, 0x45, 0x51, 0x14,
0x45, 0x51, 0x14, 0x45, 0x51, 0x14, 0x45, 0x51, 0x14, 0x45, 0x51, 0x14, 0x45, 0x51, 0x14, 0x45,
0x51, 0x14, 0x45, 0x51, 0x14, 0x45, 0x51, 0x14, 0x45, 0x51, 0x14, 0x45, 0x51, 0x14, 0x45, 0x51,
0x14, 0x45, 0x51, 0x14, 0x45, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x15, 0x20, 0x2B, 0x35, 0x40, 0x4B, 0x00,
0x0B, 0x16, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00,
0x01, 0x08, 0x20, 0x00, 0x01, 0x08, 0x20, 0x00, 0x01, 0x08, 0x20, 0x00, 0x01, 0x08, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06, 0x09, 0x72, 0x00, 0x00,
0x00, 0xAB, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x21, 0x31, 0x01, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x38, 0x8E,
0x23, 0x00, 0x20, 0x00, 0x00, 0x00, 0x51, 0x14, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x8A, 0x03, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00,
0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00,
0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00,
0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00,
0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00,
0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00,
0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00,
0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x30, 0x00,
0x00, 0x00, 0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x00, 0x02, 0x04, 0x06, 0x08, 0x0A,
0x0C, 0x0E, 0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x00, 0x02, 0x04, 0x06, 0x08,
};
}
}

View File

@@ -0,0 +1,16 @@
namespace Ryujinx.Horizon.Sdk.Ngc.Detail
{
struct MatchCheckState
{
public uint CheckMask;
public readonly uint RegionMask;
public readonly ProfanityFilterOption Option;
public MatchCheckState(uint checkMask, uint regionMask, ProfanityFilterOption option)
{
CheckMask = checkMask;
RegionMask = regionMask;
Option = option;
}
}
}

View File

@@ -0,0 +1,24 @@
namespace Ryujinx.Horizon.Sdk.Ngc.Detail
{
struct MatchDelimitedState
{
public bool Matched;
public readonly bool PrevCharIsWordSeparator;
public readonly bool NextCharIsWordSeparator;
public readonly Sbv NoSeparatorMap;
public readonly AhoCorasick DelimitedWordsTrie;
public MatchDelimitedState(
bool prevCharIsWordSeparator,
bool nextCharIsWordSeparator,
Sbv noSeparatorMap,
AhoCorasick delimitedWordsTrie)
{
Matched = false;
PrevCharIsWordSeparator = prevCharIsWordSeparator;
NextCharIsWordSeparator = nextCharIsWordSeparator;
NoSeparatorMap = noSeparatorMap;
DelimitedWordsTrie = delimitedWordsTrie;
}
}
}

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