Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
4e2bb13080 | ||
|
ac4f2c1e70 | ||
|
e40470bbe1 | ||
|
f460ecc182 | ||
|
086564c3c8 | ||
|
b6ac45d36d | ||
|
7afae8c699 | ||
|
7835968214 | ||
|
0aceb534cb | ||
|
a0af6e4d07 | ||
|
f61b7818c3 | ||
|
a2a97e1b11 | ||
|
8b2625b0be | ||
|
651e24fed9 | ||
|
41b104d0fb | ||
|
bc44b85b0b | ||
|
01c2b8097c | ||
|
4bd2ca3f0d | ||
|
e63157cc33 | ||
|
7f2fb049f5 | ||
|
4744bde0e5 |
@@ -3,24 +3,24 @@
|
||||
<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" />
|
||||
<PackageVersion Include="LibHac" Version="0.18.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.6.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||
@@ -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" />
|
||||
|
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace ARMeilleure.Diagnostics
|
||||
{
|
||||
@@ -33,7 +34,6 @@ namespace ARMeilleure.Diagnostics
|
||||
|
||||
public static string Get(ulong address)
|
||||
{
|
||||
|
||||
if (_symbols.TryGetValue(address, out string result))
|
||||
{
|
||||
return result;
|
||||
@@ -48,13 +48,15 @@ namespace ARMeilleure.Diagnostics
|
||||
ulong diff = address - symbol.Start;
|
||||
ulong rem = diff % symbol.ElementSize;
|
||||
|
||||
result = symbol.Name + "_" + diff / symbol.ElementSize;
|
||||
StringBuilder resultBuilder = new();
|
||||
resultBuilder.Append($"{symbol.Name}_{diff / symbol.ElementSize}");
|
||||
|
||||
if (rem != 0)
|
||||
{
|
||||
result += "+" + rem;
|
||||
resultBuilder.Append($"+{rem}");
|
||||
}
|
||||
|
||||
result = resultBuilder.ToString();
|
||||
_symbols.TryAdd(address, result);
|
||||
|
||||
return result;
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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)]
|
||||
|
@@ -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;
|
||||
|
@@ -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++)
|
||||
{
|
||||
|
@@ -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;
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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++)
|
||||
{
|
||||
|
@@ -4,7 +4,7 @@ using System;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||
{
|
||||
public class LimiterState
|
||||
public struct LimiterState
|
||||
{
|
||||
public ExponentialMovingAverage[] DetectorAverage;
|
||||
public ExponentialMovingAverage[] CompressionGainAverage;
|
||||
|
@@ -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 };
|
||||
|
@@ -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
|
||||
|
@@ -327,7 +327,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
string imageUrl = _amiiboList.Find(amiibo => amiibo.Equals(selected)).Image;
|
||||
|
||||
string usageString = "";
|
||||
StringBuilder usageStringBuilder = new();
|
||||
|
||||
for (int i = 0; i < _amiiboList.Count; i++)
|
||||
{
|
||||
@@ -341,20 +341,19 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
foreach (AmiiboApiUsage usageItem in item.AmiiboUsage)
|
||||
{
|
||||
usageString += Environment.NewLine +
|
||||
$"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}";
|
||||
usageStringBuilder.Append($"{Environment.NewLine}- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}");
|
||||
|
||||
writable = usageItem.Write;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (usageString.Length == 0)
|
||||
if (usageStringBuilder.Length == 0)
|
||||
{
|
||||
usageString = LocaleManager.Instance[LocaleKeys.Unknown] + ".";
|
||||
usageStringBuilder.Append($"{LocaleManager.Instance[LocaleKeys.Unknown]}.");
|
||||
}
|
||||
|
||||
Usage = $"{LocaleManager.Instance[LocaleKeys.Usage]} {(writable ? $" ({LocaleManager.Instance[LocaleKeys.Writable]})" : "")} : {usageString}";
|
||||
Usage = $"{LocaleManager.Instance[LocaleKeys.Usage]} {(writable ? $" ({LocaleManager.Instance[LocaleKeys.Writable]})" : "")} : {usageStringBuilder}";
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1278,6 +1278,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
Glyph = Glyph.Grid;
|
||||
}
|
||||
|
||||
public void SetAspectRatio(AspectRatio aspectRatio)
|
||||
{
|
||||
ConfigurationState.Instance.Graphics.AspectRatio.Value = aspectRatio;
|
||||
}
|
||||
|
||||
public async Task InstallFirmwareFromFile()
|
||||
{
|
||||
var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||
|
@@ -147,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; }
|
||||
|
@@ -6,6 +6,7 @@
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:config="clr-namespace:Ryujinx.Common.Configuration;assembly=Ryujinx.Common"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Ryujinx.Ava.UI.Views.Main.MainStatusBarView"
|
||||
x:DataType="viewModels:MainWindowViewModel">
|
||||
@@ -112,15 +113,52 @@
|
||||
Background="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<TextBlock
|
||||
<SplitButton
|
||||
Name="AspectRatioStatus"
|
||||
Margin="5,0,5,0"
|
||||
Padding="5,0,5,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
CornerRadius="0"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
PointerReleased="AspectRatioStatus_PointerReleased"
|
||||
Text="{Binding AspectRatioStatusText}"
|
||||
TextAlignment="Left" />
|
||||
Content="{Binding AspectRatioStatusText}"
|
||||
Click="AspectRatioStatus_OnClick"
|
||||
ToolTip.Tip="{locale:Locale AspectRatioTooltip}">
|
||||
<SplitButton.Styles>
|
||||
<Style Selector="Border#SeparatorBorder">
|
||||
<Setter Property="Opacity" Value="0" />
|
||||
</Style>
|
||||
</SplitButton.Styles>
|
||||
<SplitButton.Flyout>
|
||||
<MenuFlyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway">
|
||||
<MenuItem
|
||||
Header="{locale:Locale SettingsTabGraphicsAspectRatio4x3}"
|
||||
Command="{Binding SetAspectRatio}"
|
||||
CommandParameter="{x:Static config:AspectRatio.Fixed4x3}"/>
|
||||
<MenuItem
|
||||
Header="{locale:Locale SettingsTabGraphicsAspectRatio16x9}"
|
||||
Command="{Binding SetAspectRatio}"
|
||||
CommandParameter="{x:Static config:AspectRatio.Fixed16x9}"/>
|
||||
<MenuItem
|
||||
Header="{locale:Locale SettingsTabGraphicsAspectRatio16x10}"
|
||||
Command="{Binding SetAspectRatio}"
|
||||
CommandParameter="{x:Static config:AspectRatio.Fixed16x10}"/>
|
||||
<MenuItem
|
||||
Header="{locale:Locale SettingsTabGraphicsAspectRatio21x9}"
|
||||
Command="{Binding SetAspectRatio}"
|
||||
CommandParameter="{x:Static config:AspectRatio.Fixed21x9}"/>
|
||||
<MenuItem
|
||||
Header="{locale:Locale SettingsTabGraphicsAspectRatio32x9}"
|
||||
Command="{Binding SetAspectRatio}"
|
||||
CommandParameter="{x:Static config:AspectRatio.Fixed32x9}"/>
|
||||
<MenuItem
|
||||
Header="{locale:Locale SettingsTabGraphicsAspectRatioStretch}"
|
||||
Command="{Binding SetAspectRatio}"
|
||||
CommandParameter="{x:Static config:AspectRatio.Stretched}"/>
|
||||
</MenuFlyout>
|
||||
</SplitButton.Flyout>
|
||||
</SplitButton>
|
||||
<Border
|
||||
Width="2"
|
||||
Height="12"
|
||||
|
@@ -43,10 +43,9 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value;
|
||||
}
|
||||
|
||||
private void AspectRatioStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
|
||||
private void AspectRatioStatus_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AspectRatio aspectRatio = ConfigurationState.Instance.Graphics.AspectRatio.Value;
|
||||
|
||||
ConfigurationState.Instance.Graphics.AspectRatio.Value = (int)aspectRatio + 1 > Enum.GetNames(typeof(AspectRatio)).Length - 1 ? AspectRatio.Fixed4x3 : aspectRatio + 1;
|
||||
}
|
||||
|
||||
|
@@ -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>
|
||||
|
@@ -265,32 +265,46 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
|
||||
private void CheckLaunchState()
|
||||
{
|
||||
if (ShowKeyErrorOnLoad)
|
||||
{
|
||||
ShowKeyErrorOnLoad = false;
|
||||
|
||||
UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys).Wait();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
ShowVmMaxMapCountDialog().Wait();
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
await ShowVmMaxMapCountDialog();
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowVmMaxMapCountWarning().Wait();
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
await ShowVmMaxMapCountWarning();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (_deferLoad)
|
||||
if (!ShowKeyErrorOnLoad)
|
||||
{
|
||||
_deferLoad = false;
|
||||
if (_deferLoad)
|
||||
{
|
||||
_deferLoad = false;
|
||||
|
||||
ViewModel.LoadApplication(_launchPath, _startFullscreen).Wait();
|
||||
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))
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -756,6 +756,18 @@ namespace Ryujinx.Common.Memory
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
|
||||
}
|
||||
|
||||
public struct Array96<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
T _e0;
|
||||
Array64<T> _other;
|
||||
Array31<T> _other2;
|
||||
public readonly int Length => 96;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
|
||||
[Pure]
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
|
||||
}
|
||||
|
||||
public struct Array127<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
T _e0;
|
||||
|
@@ -32,6 +32,11 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
||||
/// </summary>
|
||||
public ref TState State => ref _state.State;
|
||||
|
||||
/// <summary>
|
||||
/// Current shadow state.
|
||||
/// </summary>
|
||||
public ref TState ShadowState => ref _shadowState.State;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the device state, with shadow state.
|
||||
/// </summary>
|
||||
|
@@ -1,7 +1,10 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.Device;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
@@ -15,9 +18,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
private const int ColorLayerCountOffset = 0x818;
|
||||
private const int ColorStructSize = 0x40;
|
||||
private const int ZetaLayerCountOffset = 0x1230;
|
||||
private const int UniformBufferBindVertexOffset = 0x2410;
|
||||
private const int FirstVertexOffset = 0x1434;
|
||||
|
||||
private const int IndirectIndexedDataEntrySize = 0x14;
|
||||
|
||||
private const int LogicOpOffset = 0x19c4;
|
||||
private const int ShaderIdScratchOffset = 0x3470;
|
||||
private const int ShaderAddressScratchOffset = 0x3488;
|
||||
private const int UpdateConstantBufferAddressesBase = 0x34a8;
|
||||
private const int UpdateConstantBufferSizesBase = 0x34bc;
|
||||
private const int UpdateConstantBufferAddressCbu = 0x3460;
|
||||
|
||||
private readonly GPFifoProcessor _processor;
|
||||
private readonly MacroHLEFunctionName _functionName;
|
||||
|
||||
@@ -49,6 +61,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
{
|
||||
switch (_functionName)
|
||||
{
|
||||
case MacroHLEFunctionName.BindShaderProgram:
|
||||
BindShaderProgram(state, arg0);
|
||||
break;
|
||||
case MacroHLEFunctionName.ClearColor:
|
||||
ClearColor(state, arg0);
|
||||
break;
|
||||
@@ -58,6 +73,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
case MacroHLEFunctionName.DrawArraysInstanced:
|
||||
DrawArraysInstanced(state, arg0);
|
||||
break;
|
||||
case MacroHLEFunctionName.DrawElements:
|
||||
DrawElements(state, arg0);
|
||||
break;
|
||||
case MacroHLEFunctionName.DrawElementsInstanced:
|
||||
DrawElementsInstanced(state, arg0);
|
||||
break;
|
||||
@@ -67,6 +85,21 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
case MacroHLEFunctionName.MultiDrawElementsIndirectCount:
|
||||
MultiDrawElementsIndirectCount(state, arg0);
|
||||
break;
|
||||
case MacroHLEFunctionName.UpdateBlendState:
|
||||
UpdateBlendState(state, arg0);
|
||||
break;
|
||||
case MacroHLEFunctionName.UpdateColorMasks:
|
||||
UpdateColorMasks(state, arg0);
|
||||
break;
|
||||
case MacroHLEFunctionName.UpdateUniformBufferState:
|
||||
UpdateUniformBufferState(state, arg0);
|
||||
break;
|
||||
case MacroHLEFunctionName.UpdateUniformBufferStateCbu:
|
||||
UpdateUniformBufferStateCbu(state, arg0);
|
||||
break;
|
||||
case MacroHLEFunctionName.UpdateUniformBufferStateCbuV2:
|
||||
UpdateUniformBufferStateCbuV2(state, arg0);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException(_functionName.ToString());
|
||||
}
|
||||
@@ -75,6 +108,149 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
Fifo.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a shader program with the index in arg0.
|
||||
/// </summary>
|
||||
/// <param name="state">GPU state at the time of the call</param>
|
||||
/// <param name="arg0">First argument of the call</param>
|
||||
private void BindShaderProgram(IDeviceState state, int arg0)
|
||||
{
|
||||
int scratchOffset = ShaderIdScratchOffset + arg0 * 4;
|
||||
|
||||
int lastId = state.Read(scratchOffset);
|
||||
int id = FetchParam().Word;
|
||||
int offset = FetchParam().Word;
|
||||
|
||||
if (lastId == id)
|
||||
{
|
||||
FetchParam();
|
||||
FetchParam();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_processor.ThreedClass.SetShaderOffset(arg0, (uint)offset);
|
||||
|
||||
// Removes overflow on the method address into the increment portion.
|
||||
// Present in the original macro.
|
||||
int addrMask = unchecked((int)0xfffc0fff) << 2;
|
||||
|
||||
state.Write(scratchOffset & addrMask, id);
|
||||
state.Write((ShaderAddressScratchOffset + arg0 * 4) & addrMask, offset);
|
||||
|
||||
int stage = FetchParam().Word;
|
||||
uint cbAddress = (uint)FetchParam().Word;
|
||||
|
||||
_processor.ThreedClass.UpdateUniformBufferState(65536, cbAddress >> 24, cbAddress << 8);
|
||||
|
||||
int stageOffset = (stage & 0x7f) << 3;
|
||||
|
||||
state.Write((UniformBufferBindVertexOffset + stageOffset * 4) & addrMask, 17);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates uniform buffer state for update or bind.
|
||||
/// </summary>
|
||||
/// <param name="state">GPU state at the time of the call</param>
|
||||
/// <param name="arg0">First argument of the call</param>
|
||||
private void UpdateUniformBufferState(IDeviceState state, int arg0)
|
||||
{
|
||||
uint address = (uint)state.Read(UpdateConstantBufferAddressesBase + arg0 * 4);
|
||||
int size = state.Read(UpdateConstantBufferSizesBase + arg0 * 4);
|
||||
|
||||
_processor.ThreedClass.UpdateUniformBufferState(size, address >> 24, address << 8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates uniform buffer state for update.
|
||||
/// </summary>
|
||||
/// <param name="state">GPU state at the time of the call</param>
|
||||
/// <param name="arg0">First argument of the call</param>
|
||||
private void UpdateUniformBufferStateCbu(IDeviceState state, int arg0)
|
||||
{
|
||||
uint address = (uint)state.Read(UpdateConstantBufferAddressCbu);
|
||||
|
||||
UniformBufferState ubState = new()
|
||||
{
|
||||
Address = new()
|
||||
{
|
||||
High = address >> 24,
|
||||
Low = address << 8
|
||||
},
|
||||
Size = 24320,
|
||||
Offset = arg0 << 2
|
||||
};
|
||||
|
||||
_processor.ThreedClass.UpdateUniformBufferState(ubState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates uniform buffer state for update.
|
||||
/// </summary>
|
||||
/// <param name="state">GPU state at the time of the call</param>
|
||||
/// <param name="arg0">First argument of the call</param>
|
||||
private void UpdateUniformBufferStateCbuV2(IDeviceState state, int arg0)
|
||||
{
|
||||
uint address = (uint)state.Read(UpdateConstantBufferAddressCbu);
|
||||
|
||||
UniformBufferState ubState = new()
|
||||
{
|
||||
Address = new()
|
||||
{
|
||||
High = address >> 24,
|
||||
Low = address << 8
|
||||
},
|
||||
Size = 28672,
|
||||
Offset = arg0 << 2
|
||||
};
|
||||
|
||||
_processor.ThreedClass.UpdateUniformBufferState(ubState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates blend enable using the given argument.
|
||||
/// </summary>
|
||||
/// <param name="state">GPU state at the time of the call</param>
|
||||
/// <param name="arg0">First argument of the call</param>
|
||||
private void UpdateBlendState(IDeviceState state, int arg0)
|
||||
{
|
||||
state.Write(LogicOpOffset, 0);
|
||||
|
||||
Array8<Boolean32> enable = new();
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
enable[i] = new Boolean32((uint)(arg0 >> (i + 8)) & 1);
|
||||
}
|
||||
|
||||
_processor.ThreedClass.UpdateBlendEnable(ref enable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates color masks using the given argument and three pushed arguments.
|
||||
/// </summary>
|
||||
/// <param name="state">GPU state at the time of the call</param>
|
||||
/// <param name="arg0">First argument of the call</param>
|
||||
private void UpdateColorMasks(IDeviceState state, int arg0)
|
||||
{
|
||||
Array8<RtColorMask> masks = new();
|
||||
|
||||
int index = 0;
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
masks[index++] = new RtColorMask((uint)arg0 & 0x1fff);
|
||||
masks[index++] = new RtColorMask(((uint)arg0 >> 16) & 0x1fff);
|
||||
|
||||
if (i != 3)
|
||||
{
|
||||
arg0 = FetchParam().Word;
|
||||
}
|
||||
}
|
||||
|
||||
_processor.ThreedClass.UpdateColorMasks(ref masks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears one bound color target.
|
||||
/// </summary>
|
||||
@@ -129,6 +305,36 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
indexed: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indexed draw.
|
||||
/// </summary>
|
||||
/// <param name="state">GPU state at the time of the call</param>
|
||||
/// <param name="arg0">First argument of the call</param>
|
||||
private void DrawElements(IDeviceState state, int arg0)
|
||||
{
|
||||
var topology = (PrimitiveTopology)arg0;
|
||||
|
||||
var indexAddressHigh = FetchParam();
|
||||
var indexAddressLow = FetchParam();
|
||||
var indexType = FetchParam();
|
||||
var firstIndex = 0;
|
||||
var indexCount = FetchParam();
|
||||
|
||||
_processor.ThreedClass.UpdateIndexBuffer(
|
||||
(uint)indexAddressHigh.Word,
|
||||
(uint)indexAddressLow.Word,
|
||||
(IndexType)indexType.Word);
|
||||
|
||||
_processor.ThreedClass.Draw(
|
||||
topology,
|
||||
indexCount.Word,
|
||||
1,
|
||||
firstIndex,
|
||||
state.Read(FirstVertexOffset),
|
||||
0,
|
||||
indexed: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indexed draw.
|
||||
/// </summary>
|
||||
|
@@ -6,11 +6,19 @@
|
||||
enum MacroHLEFunctionName
|
||||
{
|
||||
None,
|
||||
BindShaderProgram,
|
||||
ClearColor,
|
||||
ClearDepthStencil,
|
||||
DrawArraysInstanced,
|
||||
DrawElements,
|
||||
DrawElementsInstanced,
|
||||
DrawElementsIndirect,
|
||||
MultiDrawElementsIndirectCount,
|
||||
|
||||
UpdateBlendState,
|
||||
UpdateColorMasks,
|
||||
UpdateUniformBufferState,
|
||||
UpdateUniformBufferStateCbu,
|
||||
UpdateUniformBufferStateCbuV2
|
||||
}
|
||||
}
|
||||
|
@@ -46,12 +46,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
|
||||
private static readonly TableEntry[] _table = new TableEntry[]
|
||||
{
|
||||
new(MacroHLEFunctionName.BindShaderProgram, new Hash128(0x5d5efb912369f60b, 0x69131ed5019f08ef), 0x68),
|
||||
new(MacroHLEFunctionName.ClearColor, new Hash128(0xA9FB28D1DC43645A, 0xB177E5D2EAE67FB0), 0x28),
|
||||
new(MacroHLEFunctionName.ClearDepthStencil, new Hash128(0x1B96CB77D4879F4F, 0x8557032FE0C965FB), 0x24),
|
||||
new(MacroHLEFunctionName.DrawArraysInstanced, new Hash128(0x197FB416269DBC26, 0x34288C01DDA82202), 0x48),
|
||||
new(MacroHLEFunctionName.DrawElements, new Hash128(0x3D7F32AE6C2702A7, 0x9353C9F41C1A244D), 0x20),
|
||||
new(MacroHLEFunctionName.DrawElementsInstanced, new Hash128(0x1A501FD3D54EC8E0, 0x6CF570CF79DA74D6), 0x5c),
|
||||
new(MacroHLEFunctionName.DrawElementsIndirect, new Hash128(0x86A3E8E903AF8F45, 0xD35BBA07C23860A4), 0x7c),
|
||||
new(MacroHLEFunctionName.MultiDrawElementsIndirectCount, new Hash128(0x890AF57ED3FB1C37, 0x35D0C95C61F5386F), 0x19C),
|
||||
new(MacroHLEFunctionName.UpdateBlendState, new Hash128(0x40F6D4E7B08D7640, 0x82167BEEAECB959F), 0x28),
|
||||
new(MacroHLEFunctionName.UpdateColorMasks, new Hash128(0x9EE32420B8441DFD, 0x6E7724759A57333E), 0x24),
|
||||
new(MacroHLEFunctionName.UpdateUniformBufferState, new Hash128(0x8EE66706049CB0B0, 0x51C1CF906EC86F7C), 0x20),
|
||||
new(MacroHLEFunctionName.UpdateUniformBufferStateCbu, new Hash128(0xA4592676A3E581A0, 0xA39E77FE19FE04AC), 0x18),
|
||||
new(MacroHLEFunctionName.UpdateUniformBufferStateCbuV2, new Hash128(0x392FA750489983D4, 0x35BACE455155D2C3), 0x18)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -62,18 +69,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
/// <returns>True if the host supports the HLE macro, false otherwise</returns>
|
||||
private static bool IsMacroHLESupported(Capabilities caps, MacroHLEFunctionName name)
|
||||
{
|
||||
if (name == MacroHLEFunctionName.ClearColor ||
|
||||
name == MacroHLEFunctionName.ClearDepthStencil ||
|
||||
name == MacroHLEFunctionName.DrawArraysInstanced ||
|
||||
name == MacroHLEFunctionName.DrawElementsInstanced ||
|
||||
name == MacroHLEFunctionName.DrawElementsIndirect)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (name == MacroHLEFunctionName.MultiDrawElementsIndirectCount)
|
||||
if (name == MacroHLEFunctionName.MultiDrawElementsIndirectCount)
|
||||
{
|
||||
return caps.SupportsIndirectParameters;
|
||||
}
|
||||
else if (name != MacroHLEFunctionName.None)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@@ -10,4 +10,22 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
||||
MethodPassthrough = 2,
|
||||
MethodReplay = 3,
|
||||
}
|
||||
|
||||
static class SetMmeShadowRamControlModeExtensions
|
||||
{
|
||||
public static bool IsTrack(this SetMmeShadowRamControlMode mode)
|
||||
{
|
||||
return mode == SetMmeShadowRamControlMode.MethodTrack || mode == SetMmeShadowRamControlMode.MethodTrackWithFilter;
|
||||
}
|
||||
|
||||
public static bool IsPassthrough(this SetMmeShadowRamControlMode mode)
|
||||
{
|
||||
return mode == SetMmeShadowRamControlMode.MethodPassthrough;
|
||||
}
|
||||
|
||||
public static bool IsReplay(this SetMmeShadowRamControlMode mode)
|
||||
{
|
||||
return mode == SetMmeShadowRamControlMode.MethodReplay;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -17,9 +17,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
class StateUpdater
|
||||
{
|
||||
public const int ShaderStateIndex = 26;
|
||||
public const int RtColorMaskIndex = 14;
|
||||
public const int RasterizerStateIndex = 15;
|
||||
public const int ScissorStateIndex = 16;
|
||||
public const int VertexBufferStateIndex = 0;
|
||||
public const int BlendStateIndex = 2;
|
||||
public const int IndexBufferStateIndex = 23;
|
||||
public const int PrimitiveRestartStateIndex = 12;
|
||||
public const int RenderTargetStateIndex = 27;
|
||||
|
@@ -1,12 +1,15 @@
|
||||
using Ryujinx.Graphics.Device;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.Device;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
||||
using Ryujinx.Graphics.Gpu.Engine.InlineToMemory;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed.Blender;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Synchronization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
@@ -26,6 +29,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
private readonly ConstantBufferUpdater _cbUpdater;
|
||||
private readonly StateUpdater _stateUpdater;
|
||||
|
||||
private SetMmeShadowRamControlMode ShadowMode => _state.State.SetMmeShadowRamControlMode;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the 3D engine class.
|
||||
/// </summary>
|
||||
@@ -228,6 +233,206 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
_cbUpdater.Update(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test if two 32 byte structs are equal.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the 32-byte struct</typeparam>
|
||||
/// <param name="lhs">First struct</param>
|
||||
/// <param name="rhs">Second struct</param>
|
||||
/// <returns>True if equal, false otherwise</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool UnsafeEquals32Byte<T>(ref T lhs, ref T rhs) where T : unmanaged
|
||||
{
|
||||
if (Vector256.IsHardwareAccelerated)
|
||||
{
|
||||
return Vector256.EqualsAll(
|
||||
Unsafe.As<T, Vector256<uint>>(ref lhs),
|
||||
Unsafe.As<T, Vector256<uint>>(ref rhs)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
ref var lhsVec = ref Unsafe.As<T, Vector128<uint>>(ref lhs);
|
||||
ref var rhsVec = ref Unsafe.As<T, Vector128<uint>>(ref rhs);
|
||||
|
||||
return Vector128.EqualsAll(lhsVec, rhsVec) &&
|
||||
Vector128.EqualsAll(Unsafe.Add(ref lhsVec, 1), Unsafe.Add(ref rhsVec, 1));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates blend enable. Respects current shadow mode.
|
||||
/// </summary>
|
||||
/// <param name="masks">Blend enable</param>
|
||||
public void UpdateBlendEnable(ref Array8<Boolean32> enable)
|
||||
{
|
||||
var shadow = ShadowMode;
|
||||
ref var state = ref _state.State.BlendEnable;
|
||||
|
||||
if (shadow.IsReplay())
|
||||
{
|
||||
enable = _state.ShadowState.BlendEnable;
|
||||
}
|
||||
|
||||
if (!UnsafeEquals32Byte(ref enable, ref state))
|
||||
{
|
||||
state = enable;
|
||||
|
||||
_stateUpdater.ForceDirty(StateUpdater.BlendStateIndex);
|
||||
}
|
||||
|
||||
if (shadow.IsTrack())
|
||||
{
|
||||
_state.ShadowState.BlendEnable = enable;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates color masks. Respects current shadow mode.
|
||||
/// </summary>
|
||||
/// <param name="masks">Color masks</param>
|
||||
public void UpdateColorMasks(ref Array8<RtColorMask> masks)
|
||||
{
|
||||
var shadow = ShadowMode;
|
||||
ref var state = ref _state.State.RtColorMask;
|
||||
|
||||
if (shadow.IsReplay())
|
||||
{
|
||||
masks = _state.ShadowState.RtColorMask;
|
||||
}
|
||||
|
||||
if (!UnsafeEquals32Byte(ref masks, ref state))
|
||||
{
|
||||
state = masks;
|
||||
|
||||
_stateUpdater.ForceDirty(StateUpdater.RtColorMaskIndex);
|
||||
}
|
||||
|
||||
if (shadow.IsTrack())
|
||||
{
|
||||
_state.ShadowState.RtColorMask = masks;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates index buffer state for an indexed draw. Respects current shadow mode.
|
||||
/// </summary>
|
||||
/// <param name="addrHigh">High part of the address</param>
|
||||
/// <param name="addrLow">Low part of the address</param>
|
||||
/// <param name="type">Type of the binding</param>
|
||||
public void UpdateIndexBuffer(uint addrHigh, uint addrLow, IndexType type)
|
||||
{
|
||||
var shadow = ShadowMode;
|
||||
ref var state = ref _state.State.IndexBufferState;
|
||||
|
||||
if (shadow.IsReplay())
|
||||
{
|
||||
ref var shadowState = ref _state.ShadowState.IndexBufferState;
|
||||
addrHigh = shadowState.Address.High;
|
||||
addrLow = shadowState.Address.Low;
|
||||
type = shadowState.Type;
|
||||
}
|
||||
|
||||
if (state.Address.High != addrHigh || state.Address.Low != addrLow || state.Type != type)
|
||||
{
|
||||
state.Address.High = addrHigh;
|
||||
state.Address.Low = addrLow;
|
||||
state.Type = type;
|
||||
|
||||
_stateUpdater.ForceDirty(StateUpdater.IndexBufferStateIndex);
|
||||
}
|
||||
|
||||
if (shadow.IsTrack())
|
||||
{
|
||||
ref var shadowState = ref _state.ShadowState.IndexBufferState;
|
||||
shadowState.Address.High = addrHigh;
|
||||
shadowState.Address.Low = addrLow;
|
||||
shadowState.Type = type;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates uniform buffer state for update or bind. Respects current shadow mode.
|
||||
/// </summary>
|
||||
/// <param name="size">Size of the binding</param>
|
||||
/// <param name="addrHigh">High part of the addrsss</param>
|
||||
/// <param name="addrLow">Low part of the address</param>
|
||||
public void UpdateUniformBufferState(int size, uint addrHigh, uint addrLow)
|
||||
{
|
||||
var shadow = ShadowMode;
|
||||
ref var state = ref _state.State.UniformBufferState;
|
||||
|
||||
if (shadow.IsReplay())
|
||||
{
|
||||
ref var shadowState = ref _state.ShadowState.UniformBufferState;
|
||||
size = shadowState.Size;
|
||||
addrHigh = shadowState.Address.High;
|
||||
addrLow = shadowState.Address.Low;
|
||||
}
|
||||
|
||||
state.Size = size;
|
||||
state.Address.High = addrHigh;
|
||||
state.Address.Low = addrLow;
|
||||
|
||||
if (shadow.IsTrack())
|
||||
{
|
||||
ref var shadowState = ref _state.ShadowState.UniformBufferState;
|
||||
shadowState.Size = size;
|
||||
shadowState.Address.High = addrHigh;
|
||||
shadowState.Address.Low = addrLow;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a shader offset. Respects current shadow mode.
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the shader to update</param>
|
||||
/// <param name="offset">Offset to update with</param>
|
||||
public void SetShaderOffset(int index, uint offset)
|
||||
{
|
||||
var shadow = ShadowMode;
|
||||
ref var shaderState = ref _state.State.ShaderState[index];
|
||||
|
||||
if (shadow.IsReplay())
|
||||
{
|
||||
offset = _state.ShadowState.ShaderState[index].Offset;
|
||||
}
|
||||
|
||||
if (shaderState.Offset != offset)
|
||||
{
|
||||
shaderState.Offset = offset;
|
||||
|
||||
_stateUpdater.ForceDirty(StateUpdater.ShaderStateIndex);
|
||||
}
|
||||
|
||||
if (shadow.IsTrack())
|
||||
{
|
||||
_state.ShadowState.ShaderState[index].Offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates uniform buffer state for update. Respects current shadow mode.
|
||||
/// </summary>
|
||||
/// <param name="ubState">Uniform buffer state</param>
|
||||
public void UpdateUniformBufferState(UniformBufferState ubState)
|
||||
{
|
||||
var shadow = ShadowMode;
|
||||
ref var state = ref _state.State.UniformBufferState;
|
||||
|
||||
if (shadow.IsReplay())
|
||||
{
|
||||
ubState = _state.ShadowState.UniformBufferState;
|
||||
}
|
||||
|
||||
state = ubState;
|
||||
|
||||
if (shadow.IsTrack())
|
||||
{
|
||||
_state.ShadowState.UniformBufferState = ubState;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Launches the Inline-to-Memory DMA copy operation.
|
||||
/// </summary>
|
||||
|
@@ -590,9 +590,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
struct RtColorMask
|
||||
{
|
||||
#pragma warning disable CS0649 // Field is never assigned to
|
||||
public uint Packed;
|
||||
#pragma warning restore CS0649
|
||||
|
||||
public RtColorMask(uint packed)
|
||||
{
|
||||
Packed = packed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks red channel enable.
|
||||
|
@@ -5,9 +5,12 @@
|
||||
/// </summary>
|
||||
readonly struct Boolean32
|
||||
{
|
||||
#pragma warning disable CS0649 // Field is never assigned to
|
||||
private readonly uint _value;
|
||||
#pragma warning restore CS0649
|
||||
|
||||
public Boolean32(uint value)
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
|
||||
public static implicit operator bool(Boolean32 value)
|
||||
{
|
||||
|
@@ -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)
|
||||
|
@@ -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];
|
||||
|
@@ -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 = 5767;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
|
@@ -92,14 +92,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
|
||||
private static string GetIndentation(int level)
|
||||
{
|
||||
string indentation = string.Empty;
|
||||
StringBuilder indentationBuilder = new();
|
||||
|
||||
for (int index = 0; index < level; index++)
|
||||
{
|
||||
indentation += Tab;
|
||||
indentationBuilder.Append(Tab);
|
||||
}
|
||||
|
||||
return indentation;
|
||||
return indentationBuilder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||
using Ryujinx.Graphics.Shader.StructuredIr;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenBallot;
|
||||
using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenCall;
|
||||
@@ -67,11 +68,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
|
||||
int arity = (int)(info.Type & InstType.ArityMask);
|
||||
|
||||
string args = string.Empty;
|
||||
StringBuilder builder = new();
|
||||
|
||||
if (atomic && (operation.StorageKind == StorageKind.StorageBuffer || operation.StorageKind == StorageKind.SharedMemory))
|
||||
{
|
||||
args = GenerateLoadOrStore(context, operation, isStore: false);
|
||||
builder.Append(GenerateLoadOrStore(context, operation, isStore: false));
|
||||
|
||||
AggregateType dstType = operation.Inst == Instruction.AtomicMaxS32 || operation.Inst == Instruction.AtomicMinS32
|
||||
? AggregateType.S32
|
||||
@@ -79,7 +80,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
|
||||
for (int argIndex = operation.SourcesCount - arity + 2; argIndex < operation.SourcesCount; argIndex++)
|
||||
{
|
||||
args += ", " + GetSoureExpr(context, operation.GetSource(argIndex), dstType);
|
||||
builder.Append($", {GetSoureExpr(context, operation.GetSource(argIndex), dstType)}");
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -88,16 +89,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
{
|
||||
if (argIndex != 0)
|
||||
{
|
||||
args += ", ";
|
||||
builder.Append(", ");
|
||||
}
|
||||
|
||||
AggregateType dstType = GetSrcVarType(inst, argIndex);
|
||||
|
||||
args += GetSoureExpr(context, operation.GetSource(argIndex), dstType);
|
||||
builder.Append(GetSoureExpr(context, operation.GetSource(argIndex), dstType));
|
||||
}
|
||||
}
|
||||
|
||||
return info.OpName + '(' + args + ')';
|
||||
return $"{info.OpName}({builder})";
|
||||
}
|
||||
else if ((info.Type & InstType.Op) != 0)
|
||||
{
|
||||
@@ -184,8 +185,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);
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
||||
@@ -753,17 +779,18 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
|
||||
private static string GetMaskMultiDest(int mask)
|
||||
{
|
||||
string swizzle = ".";
|
||||
StringBuilder swizzleBuilder = new();
|
||||
swizzleBuilder.Append('.');
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
if ((mask & (1 << i)) != 0)
|
||||
{
|
||||
swizzle += "xyzw"[i];
|
||||
swizzleBuilder.Append("xyzw"[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return swizzle;
|
||||
return swizzleBuilder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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];
|
||||
|
@@ -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);
|
||||
|
@@ -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,26 +311,14 @@ 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++)
|
||||
{
|
||||
var operand = operation.GetSource(i + 1);
|
||||
|
||||
if (i >= function.InArguments.Length)
|
||||
{
|
||||
args[i] = context.GetLocalPointer((AstOperand)operand);
|
||||
}
|
||||
else
|
||||
{
|
||||
var type = function.GetArgumentType(i);
|
||||
var value = context.Get(type, operand);
|
||||
var spvLocal = spvLocals[i];
|
||||
|
||||
context.Store(spvLocal, value);
|
||||
|
||||
args[i] = spvLocal;
|
||||
}
|
||||
AstOperand local = (AstOperand)operand;
|
||||
Debug.Assert(local.Type == OperandType.LocalVariable);
|
||||
args[i] = context.GetLocalPointer(local);
|
||||
}
|
||||
|
||||
var retType = function.ReturnType;
|
||||
@@ -1492,7 +1481,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;
|
||||
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -2,17 +2,22 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
|
||||
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 +28,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 +63,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||
}
|
||||
else
|
||||
{
|
||||
AddOperation(context, operation);
|
||||
AddOperation(context, operation, targetLanguage, functions);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,7 +78,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, IReadOnlyList<Function> functions)
|
||||
{
|
||||
Instruction inst = operation.Inst;
|
||||
StorageKind storageKind = operation.StorageKind;
|
||||
@@ -114,9 +119,43 @@ 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.
|
||||
Operand funcIndexOperand = operation.GetSource(0);
|
||||
Debug.Assert(funcIndexOperand.Type == OperandType.Constant);
|
||||
int funcIndex = funcIndexOperand.Value;
|
||||
|
||||
sources[0] = new AstOperand(OperandType.Constant, funcIndex);
|
||||
|
||||
int inArgsCount = functions[funcIndex].InArgumentsCount;
|
||||
|
||||
// Remaining ones are parameters, copy them to a temp local variable.
|
||||
for (int index = 1; index < operation.SourcesCount; index++)
|
||||
{
|
||||
IAstNode source = context.GetOperandOrCbLoad(operation.GetSource(index));
|
||||
|
||||
if (index - 1 < inArgsCount)
|
||||
{
|
||||
AstOperand argTemp = context.NewTemp(FuncParameterType);
|
||||
context.AddNode(new AstAssignment(argTemp, source));
|
||||
sources[index] = argTemp;
|
||||
}
|
||||
else
|
||||
{
|
||||
sources[index] = source;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int index = 0; index < operation.SourcesCount; index++)
|
||||
{
|
||||
sources[index] = context.GetOperandOrCbLoad(operation.GetSource(index));
|
||||
}
|
||||
}
|
||||
|
||||
for (int index = 0; index < outDestsCount; index++)
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -2,6 +2,7 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
@@ -785,30 +786,31 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
|
||||
private static string GetFunctionName(Operation baseOp, bool isMultiTarget, IReadOnlyList<uint> targetCbs)
|
||||
{
|
||||
string name = baseOp.Inst.ToString();
|
||||
StringBuilder nameBuilder = new();
|
||||
nameBuilder.Append(baseOp.Inst.ToString());
|
||||
|
||||
name += baseOp.StorageKind switch
|
||||
nameBuilder.Append(baseOp.StorageKind switch
|
||||
{
|
||||
StorageKind.GlobalMemoryS8 => "S8",
|
||||
StorageKind.GlobalMemoryS16 => "S16",
|
||||
StorageKind.GlobalMemoryU8 => "U8",
|
||||
StorageKind.GlobalMemoryU16 => "U16",
|
||||
_ => string.Empty,
|
||||
};
|
||||
});
|
||||
|
||||
if (isMultiTarget)
|
||||
{
|
||||
name += "Multi";
|
||||
nameBuilder.Append("Multi");
|
||||
}
|
||||
|
||||
foreach (uint targetCb in targetCbs)
|
||||
{
|
||||
(int sbCbSlot, int sbCbOffset) = UnpackCbSlotAndOffset(targetCb);
|
||||
|
||||
name += $"_c{sbCbSlot}o{sbCbOffset}";
|
||||
nameBuilder.Append($"_c{sbCbSlot}o{sbCbOffset}");
|
||||
}
|
||||
|
||||
return name;
|
||||
return nameBuilder.ToString();
|
||||
}
|
||||
|
||||
private static bool TryGenerateStorageOp(
|
||||
|
@@ -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)
|
||||
|
@@ -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,
|
||||
|
@@ -329,6 +329,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
attributeUsage,
|
||||
definitions,
|
||||
resourceManager,
|
||||
Options.TargetLanguage,
|
||||
Options.Flags.HasFlag(TranslationFlags.DebugMode));
|
||||
|
||||
int geometryVerticesPerPrimitive = Definitions.OutputTopology switch
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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();
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -19,6 +19,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
namespace Ryujinx.HLE.FileSystem
|
||||
@@ -817,13 +818,13 @@ namespace Ryujinx.HLE.FileSystem
|
||||
|
||||
if (updateNcas.Count > 0)
|
||||
{
|
||||
string extraNcas = string.Empty;
|
||||
StringBuilder extraNcas = new();
|
||||
|
||||
foreach (var entry in updateNcas)
|
||||
{
|
||||
foreach (var (type, path) in entry.Value)
|
||||
{
|
||||
extraNcas += path + Environment.NewLine;
|
||||
extraNcas.AppendLine(path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -954,13 +955,13 @@ namespace Ryujinx.HLE.FileSystem
|
||||
|
||||
if (updateNcas.Count > 0)
|
||||
{
|
||||
string extraNcas = string.Empty;
|
||||
StringBuilder extraNcas = new();
|
||||
|
||||
foreach (var entry in updateNcas)
|
||||
{
|
||||
foreach (var (type, path) in entry.Value)
|
||||
{
|
||||
extraNcas += path + Environment.NewLine;
|
||||
extraNcas.AppendLine(path);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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)
|
||||
{
|
||||
|
119
src/Ryujinx.HLE/HOS/HorizonFsClient.cs
Normal file
119
src/Ryujinx.HLE/HOS/HorizonFsClient.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
@@ -436,14 +436,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
|
||||
uint nameIndex = sym.NameOffset;
|
||||
|
||||
string name = string.Empty;
|
||||
StringBuilder nameBuilder = new();
|
||||
|
||||
for (int chr; (chr = memory.Read<byte>(strTblAddr + nameIndex++)) != 0;)
|
||||
{
|
||||
name += (char)chr;
|
||||
nameBuilder.Append((char)chr);
|
||||
}
|
||||
|
||||
return new ElfSymbol(name, sym.Info, sym.Other, sym.SectionIndex, sym.ValueAddress, sym.Size);
|
||||
return new ElfSymbol(nameBuilder.ToString(), sym.Info, sym.Other, sym.SectionIndex, sym.ValueAddress, sym.Size);
|
||||
}
|
||||
|
||||
private static ElfSymbol GetSymbol32(IVirtualMemoryManager memory, ulong address, ulong strTblAddr)
|
||||
@@ -452,14 +452,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
|
||||
uint nameIndex = sym.NameOffset;
|
||||
|
||||
string name = string.Empty;
|
||||
StringBuilder nameBuilder = new();
|
||||
|
||||
for (int chr; (chr = memory.Read<byte>(strTblAddr + nameIndex++)) != 0;)
|
||||
{
|
||||
name += (char)chr;
|
||||
nameBuilder.Append((char)chr);
|
||||
}
|
||||
|
||||
return new ElfSymbol(name, sym.Info, sym.Other, sym.SectionIndex, sym.ValueAddress, sym.Size);
|
||||
return new ElfSymbol(nameBuilder.ToString(), sym.Info, sym.Other, sym.SectionIndex, sym.ValueAddress, sym.Size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -29,6 +29,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
private const bool IsDevelopment = false;
|
||||
|
||||
private readonly KEvent _stateChangeEvent;
|
||||
private int _stateChangeEventHandle;
|
||||
|
||||
private NetworkState _state;
|
||||
private DisconnectReason _disconnectReason;
|
||||
@@ -277,12 +278,12 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
// AttachStateChangeEvent() -> handle<copy>
|
||||
public ResultCode AttachStateChangeEvent(ServiceCtx context)
|
||||
{
|
||||
if (context.Process.HandleTable.GenerateHandle(_stateChangeEvent.ReadableEvent, out int stateChangeEventHandle) != Result.Success)
|
||||
if (_stateChangeEventHandle == 0 && context.Process.HandleTable.GenerateHandle(_stateChangeEvent.ReadableEvent, out _stateChangeEventHandle) != Result.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(stateChangeEventHandle);
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_stateChangeEventHandle);
|
||||
|
||||
// Returns ResultCode.InvalidArgument if handle is null, doesn't occur in our case since we already throw an Exception.
|
||||
|
||||
@@ -964,6 +965,12 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
SetDisconnectReason(DisconnectReason.None);
|
||||
}
|
||||
|
||||
if (_stateChangeEventHandle != 0)
|
||||
{
|
||||
context.Process.HandleTable.CloseHandle(_stateChangeEventHandle);
|
||||
_stateChangeEventHandle = 0;
|
||||
}
|
||||
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
@@ -1021,7 +1028,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
|
||||
SetState(NetworkState.None);
|
||||
|
||||
NetworkClient?.DisconnectAndStop();
|
||||
NetworkClient?.Dispose();
|
||||
NetworkClient = null;
|
||||
|
||||
return ResultCode.Success;
|
||||
@@ -1072,7 +1079,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
}
|
||||
else
|
||||
{
|
||||
// NOTE: Service returns differents ResultCode here related to the nifm ResultCode.
|
||||
// NOTE: Service returns different ResultCode here related to the nifm ResultCode.
|
||||
resultCode = ResultCode.DeviceDisabled;
|
||||
_nifmResultCode = resultCode;
|
||||
}
|
||||
@@ -1084,14 +1091,13 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (NetworkClient != null)
|
||||
{
|
||||
_station?.Dispose();
|
||||
_accessPoint?.Dispose();
|
||||
_station?.Dispose();
|
||||
_station = null;
|
||||
|
||||
NetworkClient.DisconnectAndStop();
|
||||
}
|
||||
_accessPoint?.Dispose();
|
||||
_accessPoint = null;
|
||||
|
||||
NetworkClient?.Dispose();
|
||||
NetworkClient = null;
|
||||
}
|
||||
}
|
||||
|
@@ -290,7 +290,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii
|
||||
{
|
||||
coreData = new CoreData();
|
||||
|
||||
if (charInfo.IsValid())
|
||||
if (!charInfo.IsValid())
|
||||
{
|
||||
return ResultCode.InvalidCharInfo;
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Ryujinx.HLE.HOS.Services.Mii.Types.RandomMiiConstants;
|
||||
@@ -10,9 +11,9 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
|
||||
{
|
||||
public const int Size = 0x30;
|
||||
|
||||
private byte _storage;
|
||||
private Array48<byte> _storage;
|
||||
|
||||
public Span<byte> Storage => MemoryMarshal.CreateSpan(ref _storage, Size);
|
||||
public Span<byte> Storage => _storage.AsSpan();
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0x18)]
|
||||
public struct ElementInfo
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
@@ -10,12 +11,12 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
|
||||
public const int CharCount = 10;
|
||||
private const int SizeConst = (CharCount + 1) * 2;
|
||||
|
||||
private byte _storage;
|
||||
private Array22<byte> _storage;
|
||||
|
||||
public static Nickname Default => FromString("no name");
|
||||
public static Nickname Question => FromString("???");
|
||||
|
||||
public Span<byte> Raw => MemoryMarshal.CreateSpan(ref _storage, SizeConst);
|
||||
public Span<byte> Raw => _storage.AsSpan();
|
||||
|
||||
private ReadOnlySpan<ushort> Characters => MemoryMarshal.Cast<byte, ushort>(Raw);
|
||||
|
||||
|
@@ -62,7 +62,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
|
||||
|
||||
private ushort CalculateDataCrc()
|
||||
{
|
||||
return Helper.CalculateCrc16(AsSpanWithoutDeviceCrc(), 0, true);
|
||||
return Helper.CalculateCrc16(AsSpanWithoutCrcs(), 0, true);
|
||||
}
|
||||
|
||||
private ushort CalculateDeviceCrc()
|
||||
@@ -71,7 +71,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
|
||||
|
||||
ushort deviceIdCrc16 = Helper.CalculateCrc16(SpanHelpers.AsByteSpan(ref deviceId), 0, false);
|
||||
|
||||
return Helper.CalculateCrc16(AsSpan(), deviceIdCrc16, true);
|
||||
return Helper.CalculateCrc16(AsSpanWithoutDeviceCrc(), deviceIdCrc16, true);
|
||||
}
|
||||
|
||||
private ReadOnlySpan<byte> AsSpan()
|
||||
@@ -84,6 +84,11 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
|
||||
return AsSpan()[..(Size - 2)];
|
||||
}
|
||||
|
||||
private ReadOnlySpan<byte> AsSpanWithoutCrcs()
|
||||
{
|
||||
return AsSpan()[..(Size - 4)];
|
||||
}
|
||||
|
||||
public static StoreData BuildDefault(UtilityImpl utilImpl, uint index)
|
||||
{
|
||||
StoreData result = new()
|
||||
|
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Mii.Types
|
||||
@@ -8,9 +10,9 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
|
||||
{
|
||||
public const int Size = 0x60;
|
||||
|
||||
private byte _storage;
|
||||
private Array96<byte> _storage;
|
||||
|
||||
public Span<byte> Storage => MemoryMarshal.CreateSpan(ref _storage, Size);
|
||||
public Span<byte> Storage => _storage.AsSpan();
|
||||
|
||||
// TODO: define all getters/setters
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Sm
|
||||
{
|
||||
@@ -235,7 +236,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm
|
||||
|
||||
private static string ReadName(ServiceCtx context)
|
||||
{
|
||||
string name = string.Empty;
|
||||
StringBuilder nameBuilder = new();
|
||||
|
||||
for (int index = 0; index < 8 &&
|
||||
context.RequestData.BaseStream.Position <
|
||||
@@ -245,11 +246,11 @@ namespace Ryujinx.HLE.HOS.Services.Sm
|
||||
|
||||
if (chr >= 0x20 && chr < 0x7f)
|
||||
{
|
||||
name += (char)chr;
|
||||
nameBuilder.Append((char)chr);
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
return nameBuilder.ToString();
|
||||
}
|
||||
|
||||
public override void DestroyAtExit()
|
||||
|
@@ -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;
|
||||
|
@@ -142,7 +142,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
|
||||
// OpenDisplay(nn::vi::DisplayName) -> u64 display_id
|
||||
public ResultCode OpenDisplay(ServiceCtx context)
|
||||
{
|
||||
string name = "";
|
||||
StringBuilder nameBuilder = new();
|
||||
|
||||
for (int index = 0; index < 8 && context.RequestData.BaseStream.Position < context.RequestData.BaseStream.Length; index++)
|
||||
{
|
||||
@@ -150,11 +150,11 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
|
||||
|
||||
if (chr >= 0x20 && chr < 0x7f)
|
||||
{
|
||||
name += (char)chr;
|
||||
nameBuilder.Append((char)chr);
|
||||
}
|
||||
}
|
||||
|
||||
return OpenDisplayImpl(context, name);
|
||||
return OpenDisplayImpl(context, nameBuilder.ToString());
|
||||
}
|
||||
|
||||
[CommandCmif(1011)]
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Ryujinx.Horizon
|
||||
{
|
||||
internal static class LibHacResultExtensions
|
||||
public static class LibHacResultExtensions
|
||||
{
|
||||
public static Result ToHorizonResult(this LibHac.Result result)
|
||||
{
|
||||
|
@@ -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;
|
||||
|
64
src/Ryujinx.Horizon/Ngc/Ipc/Service.cs
Normal file
64
src/Ryujinx.Horizon/Ngc/Ipc/Service.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
51
src/Ryujinx.Horizon/Ngc/NgcIpcServer.cs
Normal file
51
src/Ryujinx.Horizon/Ngc/NgcIpcServer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
21
src/Ryujinx.Horizon/Ngc/NgcMain.cs
Normal file
21
src/Ryujinx.Horizon/Ngc/NgcMain.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
13
src/Ryujinx.Horizon/Sdk/Fs/FileHandle.cs
Normal file
13
src/Ryujinx.Horizon/Sdk/Fs/FileHandle.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Fs
|
||||
{
|
||||
public readonly struct FileHandle
|
||||
{
|
||||
public object Value { get; }
|
||||
|
||||
public FileHandle(object value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
}
|
13
src/Ryujinx.Horizon/Sdk/Fs/FsResult.cs
Normal file
13
src/Ryujinx.Horizon/Sdk/Fs/FsResult.cs
Normal 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);
|
||||
}
|
||||
}
|
16
src/Ryujinx.Horizon/Sdk/Fs/IFsClient.cs
Normal file
16
src/Ryujinx.Horizon/Sdk/Fs/IFsClient.cs
Normal 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);
|
||||
}
|
||||
}
|
14
src/Ryujinx.Horizon/Sdk/Fs/OpenMode.cs
Normal file
14
src/Ryujinx.Horizon/Sdk/Fs/OpenMode.cs
Normal 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,
|
||||
}
|
||||
}
|
251
src/Ryujinx.Horizon/Sdk/Ngc/Detail/AhoCorasick.cs
Normal file
251
src/Ryujinx.Horizon/Sdk/Ngc/Detail/AhoCorasick.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
63
src/Ryujinx.Horizon/Sdk/Ngc/Detail/BinaryReader.cs
Normal file
63
src/Ryujinx.Horizon/Sdk/Ngc/Detail/BinaryReader.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
78
src/Ryujinx.Horizon/Sdk/Ngc/Detail/BitVector32.cs
Normal file
78
src/Ryujinx.Horizon/Sdk/Ngc/Detail/BitVector32.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
54
src/Ryujinx.Horizon/Sdk/Ngc/Detail/Bp.cs
Normal file
54
src/Ryujinx.Horizon/Sdk/Ngc/Detail/Bp.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
241
src/Ryujinx.Horizon/Sdk/Ngc/Detail/BpNode.cs
Normal file
241
src/Ryujinx.Horizon/Sdk/Ngc/Detail/BpNode.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
100
src/Ryujinx.Horizon/Sdk/Ngc/Detail/CompressedArray.cs
Normal file
100
src/Ryujinx.Horizon/Sdk/Ngc/Detail/CompressedArray.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
404
src/Ryujinx.Horizon/Sdk/Ngc/Detail/ContentsReader.cs
Normal file
404
src/Ryujinx.Horizon/Sdk/Ngc/Detail/ContentsReader.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
266
src/Ryujinx.Horizon/Sdk/Ngc/Detail/EmbeddedTries.cs
Normal file
266
src/Ryujinx.Horizon/Sdk/Ngc/Detail/EmbeddedTries.cs
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
16
src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchCheckState.cs
Normal file
16
src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchCheckState.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
24
src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchDelimitedState.cs
Normal file
24
src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchDelimitedState.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
113
src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchRangeList.cs
Normal file
113
src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchRangeList.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Ngc.Detail
|
||||
{
|
||||
readonly struct MatchRange
|
||||
{
|
||||
public readonly int StartOffset;
|
||||
public readonly int EndOffset;
|
||||
|
||||
public MatchRange(int startOffset, int endOffset)
|
||||
{
|
||||
StartOffset = startOffset;
|
||||
EndOffset = endOffset;
|
||||
}
|
||||
}
|
||||
|
||||
struct MatchRangeList
|
||||
{
|
||||
private int _capacity;
|
||||
private int _count;
|
||||
private MatchRange[] _ranges;
|
||||
|
||||
public readonly int Count => _count;
|
||||
|
||||
public readonly MatchRange this[int index] => _ranges[index];
|
||||
|
||||
public MatchRangeList()
|
||||
{
|
||||
_capacity = 0;
|
||||
_count = 0;
|
||||
_ranges = Array.Empty<MatchRange>();
|
||||
}
|
||||
|
||||
public void Add(int startOffset, int endOffset)
|
||||
{
|
||||
if (_count == _capacity)
|
||||
{
|
||||
int newCapacity = _count * 2;
|
||||
|
||||
if (newCapacity == 0)
|
||||
{
|
||||
newCapacity = 1;
|
||||
}
|
||||
|
||||
Array.Resize(ref _ranges, newCapacity);
|
||||
|
||||
_capacity = newCapacity;
|
||||
}
|
||||
|
||||
_ranges[_count++] = new(startOffset, endOffset);
|
||||
}
|
||||
|
||||
public readonly MatchRangeList Deduplicate()
|
||||
{
|
||||
MatchRangeList output = new();
|
||||
|
||||
if (_count != 0)
|
||||
{
|
||||
int prevStartOffset = _ranges[0].StartOffset;
|
||||
int prevEndOffset = _ranges[0].EndOffset;
|
||||
|
||||
for (int index = 1; index < _count; index++)
|
||||
{
|
||||
int currStartOffset = _ranges[index].StartOffset;
|
||||
int currEndOffset = _ranges[index].EndOffset;
|
||||
|
||||
if (prevStartOffset == currStartOffset)
|
||||
{
|
||||
if (prevEndOffset <= currEndOffset)
|
||||
{
|
||||
prevEndOffset = currEndOffset;
|
||||
}
|
||||
}
|
||||
else if (prevEndOffset <= currStartOffset)
|
||||
{
|
||||
output.Add(prevStartOffset, prevEndOffset);
|
||||
|
||||
prevStartOffset = currStartOffset;
|
||||
prevEndOffset = currEndOffset;
|
||||
}
|
||||
}
|
||||
|
||||
output.Add(prevStartOffset, prevEndOffset);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public readonly int Find(int startOffset, int endOffset)
|
||||
{
|
||||
int baseIndex = 0;
|
||||
int range = _count;
|
||||
|
||||
while (range != 0)
|
||||
{
|
||||
MatchRange currRange = _ranges[baseIndex + (range / 2)];
|
||||
|
||||
if (currRange.StartOffset < startOffset || (currRange.StartOffset == startOffset && currRange.EndOffset < endOffset))
|
||||
{
|
||||
int nextHalf = (range / 2) + 1;
|
||||
baseIndex += nextHalf;
|
||||
range -= nextHalf;
|
||||
}
|
||||
else
|
||||
{
|
||||
range /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
return baseIndex;
|
||||
}
|
||||
}
|
||||
}
|
21
src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchRangeListState.cs
Normal file
21
src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchRangeListState.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Ngc.Detail
|
||||
{
|
||||
struct MatchRangeListState
|
||||
{
|
||||
public MatchRangeList MatchRanges;
|
||||
|
||||
public MatchRangeListState()
|
||||
{
|
||||
MatchRanges = new();
|
||||
}
|
||||
|
||||
public static bool AddMatch(ReadOnlySpan<byte> text, int startOffset, int endOffset, int nodeId, ref MatchRangeListState state)
|
||||
{
|
||||
state.MatchRanges.Add(startOffset, endOffset);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
18
src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchSimilarFormState.cs
Normal file
18
src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchSimilarFormState.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace Ryujinx.Horizon.Sdk.Ngc.Detail
|
||||
{
|
||||
struct MatchSimilarFormState
|
||||
{
|
||||
public MatchRangeList MatchRanges;
|
||||
public SimilarFormTable SimilarFormTable;
|
||||
public Utf8Text CanonicalText;
|
||||
public int ReplaceEndOffset;
|
||||
|
||||
public MatchSimilarFormState(MatchRangeList matchRanges, SimilarFormTable similarFormTable)
|
||||
{
|
||||
MatchRanges = matchRanges;
|
||||
SimilarFormTable = similarFormTable;
|
||||
CanonicalText = new();
|
||||
ReplaceEndOffset = 0;
|
||||
}
|
||||
}
|
||||
}
|
49
src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchState.cs
Normal file
49
src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchState.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Ngc.Detail
|
||||
{
|
||||
readonly ref struct MatchState
|
||||
{
|
||||
public readonly Span<byte> OriginalText;
|
||||
public readonly Span<byte> ConvertedText;
|
||||
public readonly ReadOnlySpan<sbyte> DeltaTable;
|
||||
public readonly ref int MaskedCount;
|
||||
public readonly MaskMode MaskMode;
|
||||
public readonly Sbv NoSeparatorMap;
|
||||
public readonly AhoCorasick DelimitedWordsTrie;
|
||||
|
||||
public MatchState(
|
||||
Span<byte> originalText,
|
||||
Span<byte> convertedText,
|
||||
ReadOnlySpan<sbyte> deltaTable,
|
||||
ref int maskedCount,
|
||||
MaskMode maskMode,
|
||||
Sbv noSeparatorMap = null,
|
||||
AhoCorasick delimitedWordsTrie = null)
|
||||
{
|
||||
OriginalText = originalText;
|
||||
ConvertedText = convertedText;
|
||||
DeltaTable = deltaTable;
|
||||
MaskedCount = ref maskedCount;
|
||||
MaskMode = maskMode;
|
||||
NoSeparatorMap = noSeparatorMap;
|
||||
DelimitedWordsTrie = delimitedWordsTrie;
|
||||
}
|
||||
|
||||
public readonly (int, int) GetOriginalRange(int convertedStartOffest, int convertedEndOffset)
|
||||
{
|
||||
int originalStartOffset = 0;
|
||||
int originalEndOffset = 0;
|
||||
|
||||
for (int index = 0; index < convertedEndOffset; index++)
|
||||
{
|
||||
int byteLength = Math.Abs(DeltaTable[index]);
|
||||
|
||||
originalStartOffset += index < convertedStartOffest ? byteLength : 0;
|
||||
originalEndOffset += byteLength;
|
||||
}
|
||||
|
||||
return (originalStartOffset, originalEndOffset);
|
||||
}
|
||||
}
|
||||
}
|
886
src/Ryujinx.Horizon/Sdk/Ngc/Detail/ProfanityFilter.cs
Normal file
886
src/Ryujinx.Horizon/Sdk/Ngc/Detail/ProfanityFilter.cs
Normal file
@@ -0,0 +1,886 @@
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Horizon.Sdk.Fs;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Ngc.Detail
|
||||
{
|
||||
class ProfanityFilter : ProfanityFilterBase, IDisposable
|
||||
{
|
||||
private const int MaxBufferLength = 0x800;
|
||||
private const int MaxUtf8CharacterLength = 4;
|
||||
private const int MaxUtf8Characters = MaxBufferLength / MaxUtf8CharacterLength;
|
||||
private const int RegionsCount = 16;
|
||||
private const int MountCacheSize = 0x2000;
|
||||
|
||||
private readonly ContentsReader _contentsReader;
|
||||
|
||||
public ProfanityFilter(IFsClient fsClient)
|
||||
{
|
||||
_contentsReader = new(fsClient);
|
||||
}
|
||||
|
||||
public Result Initialize()
|
||||
{
|
||||
return _contentsReader.Initialize(MountCacheSize);
|
||||
}
|
||||
|
||||
public override Result Reload()
|
||||
{
|
||||
return _contentsReader.Reload();
|
||||
}
|
||||
|
||||
public override Result GetContentVersion(out uint version)
|
||||
{
|
||||
version = 0;
|
||||
|
||||
Result result = _contentsReader.GetVersionDataSize(out long size);
|
||||
if (result.IsFailure && size != 4)
|
||||
{
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
Span<byte> data = stackalloc byte[4];
|
||||
result = _contentsReader.GetVersionData(data);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
version = BinaryPrimitives.ReadUInt32BigEndian(data);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public override Result CheckProfanityWords(out uint checkMask, ReadOnlySpan<byte> word, uint regionMask, ProfanityFilterOption option)
|
||||
{
|
||||
checkMask = 0;
|
||||
|
||||
int length = word.IndexOf((byte)0);
|
||||
if (length >= 0)
|
||||
{
|
||||
word = word[..length];
|
||||
}
|
||||
|
||||
UTF8Encoding encoding = new(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
|
||||
|
||||
string decodedWord;
|
||||
|
||||
try
|
||||
{
|
||||
decodedWord = encoding.GetString(word);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return NgcResult.InvalidUtf8Encoding;
|
||||
}
|
||||
|
||||
return CheckProfanityWordsMultiRegionImpl(ref checkMask, decodedWord, regionMask, option);
|
||||
}
|
||||
|
||||
private Result CheckProfanityWordsMultiRegionImpl(ref uint checkMask, string word, uint regionMask, ProfanityFilterOption option)
|
||||
{
|
||||
// Check using common dictionary.
|
||||
Result result = CheckProfanityWordsImpl(ref checkMask, word, 0, option);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (checkMask != 0)
|
||||
{
|
||||
checkMask = (ushort)(regionMask | option.SystemRegionMask);
|
||||
}
|
||||
|
||||
// Check using region specific dictionaries if needed.
|
||||
for (int regionIndex = 0; regionIndex < RegionsCount; regionIndex++)
|
||||
{
|
||||
if (((regionMask | option.SystemRegionMask) & (1 << regionIndex)) != 0)
|
||||
{
|
||||
result = CheckProfanityWordsImpl(ref checkMask, word, 1u << regionIndex, option);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result CheckProfanityWordsImpl(ref uint checkMask, string word, uint regionMask, ProfanityFilterOption option)
|
||||
{
|
||||
ConvertUserInputForWord(out string convertedWord, word);
|
||||
|
||||
if (IsIncludesAtSign(convertedWord))
|
||||
{
|
||||
checkMask |= regionMask != 0 ? regionMask : option.SystemRegionMask;
|
||||
}
|
||||
|
||||
byte[] utf8Text = Encoding.UTF8.GetBytes(convertedWord);
|
||||
byte[] convertedText = new byte[utf8Text.Length + 5];
|
||||
|
||||
utf8Text.CopyTo(convertedText.AsSpan().Slice(2, utf8Text.Length));
|
||||
|
||||
convertedText[0] = (byte)'\\';
|
||||
convertedText[1] = (byte)'b';
|
||||
convertedText[2 + utf8Text.Length] = (byte)'\\';
|
||||
convertedText[3 + utf8Text.Length] = (byte)'b';
|
||||
convertedText[4 + utf8Text.Length] = 0;
|
||||
|
||||
int regionIndex = (ushort)regionMask != 0 ? BitOperations.TrailingZeroCount(regionMask) : -1;
|
||||
|
||||
Result result = _contentsReader.ReadDictionaries(out AhoCorasick partialWordsTrie, out _, out AhoCorasick delimitedWordsTrie, regionIndex);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if ((checkMask & regionMask) == 0)
|
||||
{
|
||||
MatchCheckState state = new(checkMask, regionMask, option);
|
||||
|
||||
partialWordsTrie.Match(convertedText, MatchCheck, ref state);
|
||||
delimitedWordsTrie.Match(convertedText, MatchCheck, ref state);
|
||||
|
||||
checkMask = state.CheckMask;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public override Result MaskProfanityWordsInText(out int maskedWordsCount, Span<byte> text, uint regionMask, ProfanityFilterOption option)
|
||||
{
|
||||
maskedWordsCount = 0;
|
||||
|
||||
Span<byte> output = text;
|
||||
Span<byte> convertedText = new byte[MaxBufferLength];
|
||||
Span<sbyte> deltaTable = new sbyte[MaxBufferLength];
|
||||
|
||||
int nullTerminatorIndex = GetUtf8Length(out _, text, MaxUtf8Characters);
|
||||
|
||||
// Ensure that the text has a null terminator if we can.
|
||||
// If the text is too long, it will be truncated.
|
||||
byte replacedCharacter = 0;
|
||||
|
||||
if (nullTerminatorIndex > 0 && nullTerminatorIndex < text.Length)
|
||||
{
|
||||
replacedCharacter = text[nullTerminatorIndex];
|
||||
text[nullTerminatorIndex] = 0;
|
||||
}
|
||||
|
||||
// Truncate the text if needed.
|
||||
int length = text.IndexOf((byte)0);
|
||||
if (length >= 0)
|
||||
{
|
||||
text = text[..length];
|
||||
}
|
||||
|
||||
// If requested, mask e-mail addresses.
|
||||
if (option.SkipAtSignCheck == SkipMode.DoNotSkip)
|
||||
{
|
||||
maskedWordsCount += FilterAtSign(text, option.MaskMode);
|
||||
text = MaskText(text);
|
||||
}
|
||||
|
||||
// Convert the text to lower case, required for string matching.
|
||||
ConvertUserInputForText(convertedText, deltaTable, text);
|
||||
|
||||
// Mask words for common and requested regions.
|
||||
Result result = MaskProfanityWordsInTextMultiRegion(ref maskedWordsCount, ref text, ref convertedText, deltaTable, regionMask, option);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// If requested, also try to match and mask the canonicalized string.
|
||||
if (option.Flags != ProfanityFilterFlags.None)
|
||||
{
|
||||
result = MaskProfanityWordsInTextCanonicalizedMultiRegion(ref maskedWordsCount, text, regionMask, option);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// If we received more text than we can process, copy unprocessed portion to the end of the new text.
|
||||
if (replacedCharacter != 0)
|
||||
{
|
||||
length = text.IndexOf((byte)0);
|
||||
|
||||
if (length < 0)
|
||||
{
|
||||
length = text.Length;
|
||||
}
|
||||
|
||||
output[length++] = replacedCharacter;
|
||||
int unprocessedLength = output.Length - nullTerminatorIndex - 1;
|
||||
output.Slice(nullTerminatorIndex + 1, unprocessedLength).CopyTo(output.Slice(length, unprocessedLength));
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result MaskProfanityWordsInTextMultiRegion(
|
||||
ref int maskedWordsCount,
|
||||
ref Span<byte> originalText,
|
||||
ref Span<byte> convertedText,
|
||||
Span<sbyte> deltaTable,
|
||||
uint regionMask,
|
||||
ProfanityFilterOption option)
|
||||
{
|
||||
// Filter using common dictionary.
|
||||
Result result = MaskProfanityWordsInTextImpl(ref maskedWordsCount, ref originalText, ref convertedText, deltaTable, -1, option);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// Filter using region specific dictionaries if needed.
|
||||
for (int regionIndex = 0; regionIndex < RegionsCount; regionIndex++)
|
||||
{
|
||||
if (((regionMask | option.SystemRegionMask) & (1 << regionIndex)) != 0)
|
||||
{
|
||||
result = MaskProfanityWordsInTextImpl(ref maskedWordsCount, ref originalText, ref convertedText, deltaTable, regionIndex, option);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result MaskProfanityWordsInTextImpl(
|
||||
ref int maskedWordsCount,
|
||||
ref Span<byte> originalText,
|
||||
ref Span<byte> convertedText,
|
||||
Span<sbyte> deltaTable,
|
||||
int regionIndex,
|
||||
ProfanityFilterOption option)
|
||||
{
|
||||
Result result = _contentsReader.ReadDictionaries(
|
||||
out AhoCorasick partialWordsTrie,
|
||||
out AhoCorasick completeWordsTrie,
|
||||
out AhoCorasick delimitedWordsTrie,
|
||||
regionIndex);
|
||||
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// Match single words.
|
||||
|
||||
MatchState state = new(originalText, convertedText, deltaTable, ref maskedWordsCount, option.MaskMode);
|
||||
|
||||
partialWordsTrie.Match(convertedText, MatchSingleWord, ref state);
|
||||
|
||||
MaskText(ref originalText, ref convertedText, deltaTable);
|
||||
|
||||
// Match single words and phrases.
|
||||
// We remove word separators on the string used for the match.
|
||||
|
||||
Span<byte> noSeparatorText = new byte[originalText.Length];
|
||||
Sbv noSeparatorMap = new(convertedText.Length);
|
||||
noSeparatorText = RemoveWordSeparators(noSeparatorText, convertedText, noSeparatorMap);
|
||||
|
||||
state = new(
|
||||
originalText,
|
||||
convertedText,
|
||||
deltaTable,
|
||||
ref maskedWordsCount,
|
||||
option.MaskMode,
|
||||
noSeparatorMap,
|
||||
delimitedWordsTrie);
|
||||
|
||||
partialWordsTrie.Match(noSeparatorText, MatchMultiWord, ref state);
|
||||
|
||||
MaskText(ref originalText, ref convertedText, deltaTable);
|
||||
|
||||
// Match whole words, which must be surrounded by word separators.
|
||||
|
||||
noSeparatorText = new byte[originalText.Length];
|
||||
noSeparatorMap = new(convertedText.Length);
|
||||
noSeparatorText = RemoveWordSeparators(noSeparatorText, convertedText, noSeparatorMap);
|
||||
|
||||
state = new(
|
||||
originalText,
|
||||
convertedText,
|
||||
deltaTable,
|
||||
ref maskedWordsCount,
|
||||
option.MaskMode,
|
||||
noSeparatorMap,
|
||||
delimitedWordsTrie);
|
||||
|
||||
completeWordsTrie.Match(noSeparatorText, MatchDelimitedWord, ref state);
|
||||
|
||||
MaskText(ref originalText, ref convertedText, deltaTable);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private static void MaskText(ref Span<byte> originalText, ref Span<byte> convertedText, Span<sbyte> deltaTable)
|
||||
{
|
||||
originalText = MaskText(originalText);
|
||||
UpdateDeltaTable(deltaTable, convertedText);
|
||||
convertedText = MaskText(convertedText);
|
||||
}
|
||||
|
||||
private Result MaskProfanityWordsInTextCanonicalizedMultiRegion(ref int maskedWordsCount, Span<byte> text, uint regionMask, ProfanityFilterOption option)
|
||||
{
|
||||
// Filter using common dictionary.
|
||||
Result result = MaskProfanityWordsInTextCanonicalized(ref maskedWordsCount, text, 0, option);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// Filter using region specific dictionaries if needed.
|
||||
for (int index = 0; index < RegionsCount; index++)
|
||||
{
|
||||
if ((((regionMask | option.SystemRegionMask) >> index) & 1) != 0)
|
||||
{
|
||||
result = MaskProfanityWordsInTextCanonicalized(ref maskedWordsCount, text, 1u << index, option);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result MaskProfanityWordsInTextCanonicalized(ref int maskedWordsCount, Span<byte> text, uint regionMask, ProfanityFilterOption option)
|
||||
{
|
||||
Utf8Text maskedText = new();
|
||||
Utf8ParseResult parseResult = Utf8Text.Create(out Utf8Text inputText, text);
|
||||
if (parseResult != Utf8ParseResult.Success)
|
||||
{
|
||||
return NgcResult.InvalidUtf8Encoding;
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> prevCharacter = ReadOnlySpan<byte>.Empty;
|
||||
|
||||
int charStartIndex = 0;
|
||||
|
||||
for (int charEndIndex = 1; charStartIndex < inputText.CharacterCount;)
|
||||
{
|
||||
ReadOnlySpan<byte> nextCharacter = charEndIndex < inputText.CharacterCount
|
||||
? inputText.AsSubstring(charEndIndex, charEndIndex + 1)
|
||||
: ReadOnlySpan<byte>.Empty;
|
||||
|
||||
Result result = CheckProfanityWordsInTextCanonicalized(
|
||||
out bool matched,
|
||||
inputText.AsSubstring(charStartIndex, charEndIndex),
|
||||
prevCharacter,
|
||||
nextCharacter,
|
||||
regionMask,
|
||||
option);
|
||||
|
||||
if (result.IsFailure && result != NgcResult.InvalidSize)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (matched)
|
||||
{
|
||||
// We had a match, we know where it ends, now we need to find where it starts.
|
||||
|
||||
int previousCharStartIndex = charStartIndex;
|
||||
|
||||
for (; charStartIndex < charEndIndex; charStartIndex++)
|
||||
{
|
||||
result = CheckProfanityWordsInTextCanonicalized(
|
||||
out matched,
|
||||
inputText.AsSubstring(charStartIndex, charEndIndex),
|
||||
prevCharacter,
|
||||
nextCharacter,
|
||||
regionMask,
|
||||
option);
|
||||
|
||||
if (result.IsFailure && result != NgcResult.InvalidSize)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// When we get past the start of the matched substring, the match will fail,
|
||||
// so that's when we know we found the start.
|
||||
if (!matched)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Append substring before the match start.
|
||||
maskedText = maskedText.Append(inputText.AsSubstring(previousCharStartIndex, charStartIndex - 1));
|
||||
|
||||
// Mask matched substring with asterisks.
|
||||
if (option.MaskMode == MaskMode.ReplaceByOneCharacter)
|
||||
{
|
||||
maskedText = maskedText.Append("*"u8);
|
||||
prevCharacter = "*"u8;
|
||||
}
|
||||
else if (option.MaskMode == MaskMode.Overwrite && charStartIndex <= charEndIndex)
|
||||
{
|
||||
int maskLength = charEndIndex - charStartIndex + 1;
|
||||
|
||||
while (maskLength-- > 0)
|
||||
{
|
||||
maskedText = maskedText.Append("*"u8);
|
||||
}
|
||||
|
||||
prevCharacter = "*"u8;
|
||||
}
|
||||
|
||||
charStartIndex = charEndIndex;
|
||||
maskedWordsCount++;
|
||||
}
|
||||
|
||||
if (charEndIndex < inputText.CharacterCount)
|
||||
{
|
||||
charEndIndex++;
|
||||
}
|
||||
else if (charStartIndex < inputText.CharacterCount)
|
||||
{
|
||||
prevCharacter = inputText.AsSubstring(charStartIndex, charStartIndex + 1);
|
||||
maskedText = maskedText.Append(prevCharacter);
|
||||
charStartIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// Replace text with the masked text.
|
||||
maskedText.CopyTo(text);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result CheckProfanityWordsInTextCanonicalized(
|
||||
out bool matched,
|
||||
ReadOnlySpan<byte> text,
|
||||
ReadOnlySpan<byte> prevCharacter,
|
||||
ReadOnlySpan<byte> nextCharacter,
|
||||
uint regionMask,
|
||||
ProfanityFilterOption option)
|
||||
{
|
||||
matched = false;
|
||||
|
||||
Span<byte> convertedText = new byte[MaxBufferLength + 1];
|
||||
text.CopyTo(convertedText[..text.Length]);
|
||||
|
||||
Result result;
|
||||
|
||||
if (text.Length > 0)
|
||||
{
|
||||
// If requested, normalize.
|
||||
// This will convert different encodings for the same character in their canonical encodings.
|
||||
if (option.Flags.HasFlag(ProfanityFilterFlags.MatchNormalizedFormKC))
|
||||
{
|
||||
Utf8ParseResult parseResult = Utf8Util.NormalizeFormKC(convertedText, convertedText);
|
||||
|
||||
if (parseResult != Utf8ParseResult.Success)
|
||||
{
|
||||
return NgcResult.InvalidUtf8Encoding;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to lower case.
|
||||
ConvertUserInputForText(convertedText, Span<sbyte>.Empty, convertedText);
|
||||
|
||||
// If requested, also try to replace similar characters with their canonical form.
|
||||
// For example, vv is similar to w, and 1 or | is similar to i.
|
||||
if (option.Flags.HasFlag(ProfanityFilterFlags.MatchSimilarForm))
|
||||
{
|
||||
result = ConvertInputTextFromSimilarForm(convertedText, convertedText);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
int length = convertedText.IndexOf((byte)0);
|
||||
if (length >= 0)
|
||||
{
|
||||
convertedText = convertedText[..length];
|
||||
}
|
||||
}
|
||||
|
||||
int regionIndex = (ushort)regionMask != 0 ? BitOperations.TrailingZeroCount(regionMask) : -1;
|
||||
|
||||
result = _contentsReader.ReadDictionaries(
|
||||
out AhoCorasick partialWordsTrie,
|
||||
out AhoCorasick completeWordsTrie,
|
||||
out AhoCorasick delimitedWordsTrie,
|
||||
regionIndex);
|
||||
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result = ContentsReader.ReadNotSeparatorDictionary(out AhoCorasick notSeparatorTrie);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// Match single words.
|
||||
|
||||
bool trieMatched = false;
|
||||
|
||||
partialWordsTrie.Match(convertedText, MatchSimple, ref trieMatched);
|
||||
|
||||
if (trieMatched)
|
||||
{
|
||||
matched = true;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
// Match single words and phrases.
|
||||
// We remove word separators on the string used for the match.
|
||||
|
||||
Span<byte> noSeparatorText = new byte[text.Length];
|
||||
Sbv noSeparatorMap = new(convertedText.Length);
|
||||
noSeparatorText = RemoveWordSeparators(noSeparatorText, convertedText, noSeparatorMap, notSeparatorTrie);
|
||||
|
||||
trieMatched = false;
|
||||
|
||||
partialWordsTrie.Match(noSeparatorText, MatchSimple, ref trieMatched);
|
||||
|
||||
if (trieMatched)
|
||||
{
|
||||
matched = true;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
// Match whole words, which must be surrounded by word separators.
|
||||
|
||||
bool prevCharIsWordSeparator = prevCharacter.Length == 0 || IsWordSeparator(prevCharacter, notSeparatorTrie);
|
||||
bool nextCharIsWordSeparator = nextCharacter.Length == 0 || IsWordSeparator(nextCharacter, notSeparatorTrie);
|
||||
|
||||
MatchDelimitedState state = new(prevCharIsWordSeparator, nextCharIsWordSeparator, noSeparatorMap, delimitedWordsTrie);
|
||||
|
||||
completeWordsTrie.Match(noSeparatorText, MatchDelimitedWordSimple, ref state);
|
||||
|
||||
if (state.Matched)
|
||||
{
|
||||
matched = true;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result ConvertInputTextFromSimilarForm(Span<byte> convertedText, ReadOnlySpan<byte> text)
|
||||
{
|
||||
int length = text.IndexOf((byte)0);
|
||||
if (length >= 0)
|
||||
{
|
||||
text = text[..length];
|
||||
}
|
||||
|
||||
Result result = _contentsReader.ReadSimilarFormDictionary(out AhoCorasick similarFormTrie);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result = _contentsReader.ReadSimilarFormTable(out SimilarFormTable similarFormTable);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// Find all characters that have a similar form.
|
||||
MatchRangeListState listState = new();
|
||||
|
||||
similarFormTrie.Match(text, MatchRangeListState.AddMatch, ref listState);
|
||||
|
||||
// Filter found match ranges.
|
||||
// Because some similar form strings are a subset of others, we need to remove overlapping matches.
|
||||
// For example, | can be replaced with i, but |-| can be replaced with h.
|
||||
// We prefer the latter match (|-|) because it is more specific.
|
||||
MatchRangeList deduplicatedMatches = listState.MatchRanges.Deduplicate();
|
||||
|
||||
MatchSimilarFormState state = new(deduplicatedMatches, similarFormTable);
|
||||
|
||||
similarFormTrie.Match(text, MatchAndReplace, ref state);
|
||||
|
||||
// Append remaining characters.
|
||||
state.CanonicalText = state.CanonicalText.Append(text[state.ReplaceEndOffset..]);
|
||||
|
||||
// Set canonical text to output.
|
||||
ReadOnlySpan<byte> canonicalText = state.CanonicalText.AsSpan();
|
||||
canonicalText.CopyTo(convertedText[..canonicalText.Length]);
|
||||
convertedText[canonicalText.Length] = 0;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private static bool MatchCheck(ReadOnlySpan<byte> text, int matchStartOffset, int matchEndOffset, int nodeId, ref MatchCheckState state)
|
||||
{
|
||||
state.CheckMask |= state.RegionMask != 0 ? state.RegionMask : state.Option.SystemRegionMask;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool MatchSingleWord(ReadOnlySpan<byte> text, int matchStartOffset, int matchEndOffset, int nodeId, ref MatchState state)
|
||||
{
|
||||
MatchCommon(ref state, matchStartOffset, matchEndOffset);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool MatchMultiWord(ReadOnlySpan<byte> text, int matchStartOffset, int matchEndOffset, int nodeId, ref MatchState state)
|
||||
{
|
||||
int convertedStartOffset = state.NoSeparatorMap.Set.Select0(matchStartOffset);
|
||||
int convertedEndOffset = state.NoSeparatorMap.Set.Select0(matchEndOffset);
|
||||
|
||||
if (convertedEndOffset < 0)
|
||||
{
|
||||
convertedEndOffset = state.NoSeparatorMap.Set.BitVector.BitLength;
|
||||
}
|
||||
|
||||
int endOffsetBeforeSeparator = TrimEnd(state.ConvertedText, convertedEndOffset);
|
||||
|
||||
MatchCommon(ref state, convertedStartOffset, endOffsetBeforeSeparator);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool MatchDelimitedWord(ReadOnlySpan<byte> text, int matchStartOffset, int matchEndOffset, int nodeId, ref MatchState state)
|
||||
{
|
||||
int convertedStartOffset = state.NoSeparatorMap.Set.Select0(matchStartOffset);
|
||||
int convertedEndOffset = state.NoSeparatorMap.Set.Select0(matchEndOffset);
|
||||
|
||||
if (convertedEndOffset < 0)
|
||||
{
|
||||
convertedEndOffset = state.NoSeparatorMap.Set.BitVector.BitLength;
|
||||
}
|
||||
|
||||
int endOffsetBeforeSeparator = TrimEnd(state.ConvertedText, convertedEndOffset);
|
||||
|
||||
Span<byte> delimitedText = new byte[64];
|
||||
|
||||
// If the word is prefixed by a word separator, insert "\b" delimiter, otherwise insert "a" delimitar.
|
||||
// The start of the string is also considered a "word separator".
|
||||
|
||||
bool startIsPrefixedByWordSeparator =
|
||||
convertedStartOffset == 0 ||
|
||||
IsPrefixedByWordSeparator(state.ConvertedText, convertedStartOffset);
|
||||
|
||||
int delimitedTextOffset = 0;
|
||||
|
||||
if (startIsPrefixedByWordSeparator)
|
||||
{
|
||||
delimitedText[delimitedTextOffset++] = (byte)'\\';
|
||||
delimitedText[delimitedTextOffset++] = (byte)'b';
|
||||
}
|
||||
else
|
||||
{
|
||||
delimitedText[delimitedTextOffset++] = (byte)'a';
|
||||
}
|
||||
|
||||
// Copy the word to our temporary buffer used for the next match.
|
||||
|
||||
int matchLength = matchEndOffset - matchStartOffset;
|
||||
|
||||
text.Slice(matchStartOffset, matchLength).CopyTo(delimitedText.Slice(delimitedTextOffset, matchLength));
|
||||
|
||||
delimitedTextOffset += matchLength;
|
||||
|
||||
// If the word is suffixed by a word separator, insert "\b" delimiter, otherwise insert "a" delimiter.
|
||||
// The end of the string is also considered a "word separator".
|
||||
|
||||
bool endIsSuffixedByWordSeparator =
|
||||
endOffsetBeforeSeparator == state.NoSeparatorMap.Set.BitVector.BitLength ||
|
||||
state.ConvertedText[endOffsetBeforeSeparator] == 0 ||
|
||||
IsWordSeparator(state.ConvertedText, endOffsetBeforeSeparator);
|
||||
|
||||
if (endIsSuffixedByWordSeparator)
|
||||
{
|
||||
delimitedText[delimitedTextOffset++] = (byte)'\\';
|
||||
delimitedText[delimitedTextOffset++] = (byte)'b';
|
||||
}
|
||||
else
|
||||
{
|
||||
delimitedText[delimitedTextOffset++] = (byte)'a';
|
||||
}
|
||||
|
||||
// Create our temporary match state for the next match.
|
||||
bool matched = false;
|
||||
|
||||
// Insert the null terminator.
|
||||
delimitedText[delimitedTextOffset] = 0;
|
||||
|
||||
// Check if the delimited word is on the dictionary.
|
||||
state.DelimitedWordsTrie.Match(delimitedText, MatchSimple, ref matched);
|
||||
|
||||
// If we have a match, mask the word.
|
||||
if (matched)
|
||||
{
|
||||
MatchCommon(ref state, convertedStartOffset, endOffsetBeforeSeparator);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void MatchCommon(ref MatchState state, int matchStartOffset, int matchEndOffset)
|
||||
{
|
||||
// If length is zero or negative, there was no match.
|
||||
if (matchStartOffset >= matchEndOffset)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Span<byte> convertedText = state.ConvertedText;
|
||||
Span<byte> originalText = state.OriginalText;
|
||||
|
||||
int matchLength = matchEndOffset - matchStartOffset;
|
||||
int characterCount = Encoding.UTF8.GetCharCount(state.ConvertedText.Slice(matchStartOffset, matchLength));
|
||||
|
||||
// Exit early if there are no character, or if we matched past the end of the string.
|
||||
if (characterCount == 0 ||
|
||||
(matchStartOffset > 0 && convertedText[matchStartOffset - 1] == 0) ||
|
||||
(matchStartOffset > 1 && convertedText[matchStartOffset - 2] == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
state.MaskedCount++;
|
||||
|
||||
(int originalStartOffset, int originalEndOffset) = state.GetOriginalRange(matchStartOffset, matchEndOffset);
|
||||
|
||||
PreMaskCharacterRange(convertedText, matchStartOffset, matchEndOffset, state.MaskMode, characterCount);
|
||||
PreMaskCharacterRange(originalText, originalStartOffset, originalEndOffset, state.MaskMode, characterCount);
|
||||
}
|
||||
|
||||
private static bool MatchDelimitedWordSimple(ReadOnlySpan<byte> text, int matchStartOffset, int matchEndOffset, int nodeId, ref MatchDelimitedState state)
|
||||
{
|
||||
int convertedStartOffset = state.NoSeparatorMap.Set.Select0(matchStartOffset);
|
||||
|
||||
Span<byte> delimitedText = new byte[64];
|
||||
|
||||
// If the word is prefixed by a word separator, insert "\b" delimiter, otherwise insert "a" delimitar.
|
||||
// The start of the string is also considered a "word separator".
|
||||
|
||||
bool startIsPrefixedByWordSeparator =
|
||||
(convertedStartOffset == 0 && state.PrevCharIsWordSeparator) ||
|
||||
state.NoSeparatorMap.Set.Has(convertedStartOffset - 1);
|
||||
|
||||
int delimitedTextOffset = 0;
|
||||
|
||||
if (startIsPrefixedByWordSeparator)
|
||||
{
|
||||
delimitedText[delimitedTextOffset++] = (byte)'\\';
|
||||
delimitedText[delimitedTextOffset++] = (byte)'b';
|
||||
}
|
||||
else
|
||||
{
|
||||
delimitedText[delimitedTextOffset++] = (byte)'a';
|
||||
}
|
||||
|
||||
// Copy the word to our temporary buffer used for the next match.
|
||||
|
||||
int matchLength = matchEndOffset - matchStartOffset;
|
||||
|
||||
text.Slice(matchStartOffset, matchLength).CopyTo(delimitedText.Slice(delimitedTextOffset, matchLength));
|
||||
|
||||
delimitedTextOffset += matchLength;
|
||||
|
||||
// If the word is suffixed by a word separator, insert "\b" delimiter, otherwise insert "a" delimiter.
|
||||
// The end of the string is also considered a "word separator".
|
||||
|
||||
int convertedEndOffset = state.NoSeparatorMap.Set.Select0(matchEndOffset);
|
||||
|
||||
bool endIsSuffixedByWordSeparator =
|
||||
(convertedEndOffset < 0 && state.NextCharIsWordSeparator) ||
|
||||
state.NoSeparatorMap.Set.Has(convertedEndOffset - 1);
|
||||
|
||||
if (endIsSuffixedByWordSeparator)
|
||||
{
|
||||
delimitedText[delimitedTextOffset++] = (byte)'\\';
|
||||
delimitedText[delimitedTextOffset++] = (byte)'b';
|
||||
}
|
||||
else
|
||||
{
|
||||
delimitedText[delimitedTextOffset++] = (byte)'a';
|
||||
}
|
||||
|
||||
// Create our temporary match state for the next match.
|
||||
bool matched = false;
|
||||
|
||||
// Insert the null terminator.
|
||||
delimitedText[delimitedTextOffset] = 0;
|
||||
|
||||
// Check if the delimited word is on the dictionary.
|
||||
state.DelimitedWordsTrie.Match(delimitedText, MatchSimple, ref matched);
|
||||
|
||||
// If we have a match, mask the word.
|
||||
if (matched)
|
||||
{
|
||||
state.Matched = true;
|
||||
}
|
||||
|
||||
return !matched;
|
||||
}
|
||||
|
||||
private static bool MatchAndReplace(ReadOnlySpan<byte> text, int matchStartOffset, int matchEndOffset, int nodeId, ref MatchSimilarFormState state)
|
||||
{
|
||||
if (matchStartOffset < state.ReplaceEndOffset || state.MatchRanges.Count == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if the match range exists on our list of ranges.
|
||||
int rangeIndex = state.MatchRanges.Find(matchStartOffset, matchEndOffset);
|
||||
|
||||
if ((uint)rangeIndex >= (uint)state.MatchRanges.Count)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
MatchRange range = state.MatchRanges[rangeIndex];
|
||||
|
||||
// We only replace if the match has the same size or is larger than an existing match on the list.
|
||||
if (range.StartOffset <= matchStartOffset &&
|
||||
(range.StartOffset != matchStartOffset || range.EndOffset <= matchEndOffset))
|
||||
{
|
||||
// Copy all characters since the last match to the output.
|
||||
int endOffset = state.ReplaceEndOffset;
|
||||
|
||||
if (endOffset < matchStartOffset)
|
||||
{
|
||||
state.CanonicalText = state.CanonicalText.Append(text[endOffset..matchStartOffset]);
|
||||
}
|
||||
|
||||
// Get canonical character from the similar one, and append it.
|
||||
// For example, |-| is replaced with h, vv is replaced with w, etc.
|
||||
ReadOnlySpan<byte> matchText = text[matchStartOffset..matchEndOffset];
|
||||
state.CanonicalText = state.CanonicalText.AppendNullTerminated(state.SimilarFormTable.FindCanonicalString(matchText));
|
||||
state.ReplaceEndOffset = matchEndOffset;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_contentsReader.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user