Compare commits

...

9 Commits

Author SHA1 Message Date
9ba73ffbe5 Prefetch capabilities before spawning translation threads. (#3338)
* Prefetch capabilities before spawning translation threads.

The Backend Multithreading only expects one thread to submit commands at a time. When compiling shaders, the translator may request the host GPU capabilities from the backend. It's possible for a bunch of translators to do this at the same time.

There's a caching mechanism in place so that the capabilities are only fetched once. By triggering this before spawning the thread, the async translation threads no longer try to queue onto the backend queue all at the same time.

The Capabilities do need to be checked from the GPU thread, due to OpenGL needing a context to check them, so it's not possible to call the underlying backend directly.

* Initialize the capabilities when setting the GPU thread + missing call in headless

* Remove private variables
2022-05-14 11:58:33 -03:00
43b4b34376 Implement Viewport Transform Disable (#3328)
* Initial implementation (no specialization)

* Use specialization

* Fix render scale, increase code gen version

* Revert accidental change

* Address Feedback
2022-05-12 10:47:13 -03:00
92ca1cb0cb hid: Various fixes and cleanup (#3326)
* hid: Various fix and cleanup

* Add IsValidNpadIdType

* remove ()
2022-05-08 00:28:54 +02:00
50d7ecf76d Add alternative "GL" enum values for StencilOp (#3321)
This PR adds the alternative enum values for StencilOp. Similar to the other enums, I added these with the same names but with Gl added to the end. These are used by homebrew using Nouveau, though they might be used by games with the official Vulkan driver.

39d90be897/rnndb/graph/nv_3ddefs.xml (L77)

Fixes some broken graphics in Citra, such as missing shadows in Mario Kart 7. Likely fixes other homebrew.
2022-05-05 21:16:58 +02:00
42a2a80b87 Enable JIT service LLE (#2959)
* Enable JIT service LLE

* Force disable PPTC when using the JIT service

PPTC does not support multiple guest processes

* Fix build

* Make SM service registration per emulation context rather than global

* Address PR feedback
2022-05-05 15:23:30 -03:00
54deded929 Fix shared memory leak on Windows (#3319)
* Fix shared memory leak on Windows

* Fix memory leak caused by RO session disposal not decrementing the memory manager ref count

* Fix UnmapViewInternal deadlock

* Was not supposed to add those back
2022-05-05 14:58:59 -03:00
39bdf6d41e infra: Warn about support drop of old Windows versions (#3299)
* infra: Warn about support drop of old Windows versions

See #3298.

* Address comment
2022-05-04 20:21:27 +02:00
074190e03c Remove AddProtection count > 0 assert (#3315) 2022-05-04 14:07:10 -03:00
256514c7c9 Update the artifact build's version number (#3297) 2022-05-03 23:35:12 +02:00
59 changed files with 844 additions and 266 deletions

View File

@ -46,6 +46,7 @@ jobs:
env:
POWERSHELL_TELEMETRY_OPTOUT: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1
RYUJINX_BASE_VERSION: "1.1.0"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-dotnet@v1
@ -59,24 +60,24 @@ jobs:
- name: Clear
run: dotnet clean && dotnet nuget locals all --clear
- name: Build
run: dotnet build -c "${{ matrix.configuration }}" /p:Version="1.1.0" /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER
run: dotnet build -c "${{ matrix.configuration }}" /p:Version="${{ env.RYUJINX_BASE_VERSION }}" /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER
- name: Test
run: dotnet test -c "${{ matrix.configuration }}"
- name: Publish Ryujinx
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish /p:Version="1.1.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx --self-contained
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish /p:Version="${{ env.RYUJINX_BASE_VERSION }}" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx --self-contained
if: github.event_name == 'pull_request'
- name: Publish Ryujinx.Headless.SDL2
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless /p:Version="1.1.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Headless.SDL2 --self-contained
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless /p:Version="${{ env.RYUJINX_BASE_VERSION }}" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Headless.SDL2 --self-contained
if: github.event_name == 'pull_request'
- name: Upload Ryujinx artifact
uses: actions/upload-artifact@v2
with:
name: ryujinx-${{ matrix.configuration }}-1.0.0+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
path: publish
if: github.event_name == 'pull_request'
- name: Upload Ryujinx.Headless.SDL2 artifact
uses: actions/upload-artifact@v2
with:
name: ryujinx-headless-sdl2-${{ matrix.configuration }}-1.0.0+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
name: ryujinx-headless-sdl2-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
path: publish_sdl2_headless
if: github.event_name == 'pull_request'

View File

@ -94,7 +94,7 @@ namespace Ryujinx.Graphics.GAL
void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs);
void SetVertexBuffers(ReadOnlySpan<VertexBufferDescriptor> vertexBuffers);
void SetViewports(int first, ReadOnlySpan<Viewport> viewports);
void SetViewports(int first, ReadOnlySpan<Viewport> viewports, bool disableTransform);
void TextureBarrier();
void TextureBarrierTiled();

View File

@ -9,17 +9,19 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
public CommandType CommandType => CommandType.SetViewports;
private int _first;
private SpanRef<Viewport> _viewports;
private bool _disableTransform;
public void Set(int first, SpanRef<Viewport> viewports)
public void Set(int first, SpanRef<Viewport> viewports, bool disableTransform)
{
_first = first;
_viewports = viewports;
_disableTransform = disableTransform;
}
public static void Run(ref SetViewportsCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
ReadOnlySpan<Viewport> viewports = command._viewports.Get(threaded);
renderer.Pipeline.SetViewports(command._first, viewports);
renderer.Pipeline.SetViewports(command._first, viewports, command._disableTransform);
command._viewports.Dispose(threaded);
}
}

View File

@ -304,9 +304,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand();
}
public void SetViewports(int first, ReadOnlySpan<Viewport> viewports)
public void SetViewports(int first, ReadOnlySpan<Viewport> viewports, bool disableTransform)
{
_renderer.New<SetViewportsCommand>().Set(first, _renderer.CopySpan(viewports));
_renderer.New<SetViewportsCommand>().Set(first, _renderer.CopySpan(viewports), disableTransform);
_renderer.QueueCommand();
}

View File

@ -9,6 +9,15 @@ namespace Ryujinx.Graphics.GAL
DecrementAndClamp,
Invert,
IncrementAndWrap,
DecrementAndWrap
DecrementAndWrap,
ZeroGl = 0x0,
InvertGl = 0x150a,
KeepGl = 0x1e00,
ReplaceGl = 0x1e01,
IncrementAndClampGl = 0x1e02,
DecrementAndClampGl = 0x1e03,
IncrementAndWrapGl = 0x8507,
DecrementAndWrapGl = 0x8508
}
}

View File

@ -72,6 +72,13 @@ namespace Ryujinx.Graphics.GAL
UpdateGenericField(SupportBuffer.FragmentIsBgraOffset, data, Data.FragmentIsBgra.ToSpan(), offset, count);
}
public void UpdateViewportInverse(Vector4<float> data)
{
Data.ViewportInverse = data;
MarkDirty(SupportBuffer.ViewportInverseOffset, SupportBuffer.FieldSize);
}
public void Commit()
{
if (_startOffset != -1)

View File

@ -113,7 +113,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
nameof(ThreedClassState.DepthMode),
nameof(ThreedClassState.ViewportTransform),
nameof(ThreedClassState.ViewportExtents),
nameof(ThreedClassState.YControl)),
nameof(ThreedClassState.YControl),
nameof(ThreedClassState.ViewportTransformEnable)),
new StateUpdateCallbackEntry(UpdatePolygonMode,
nameof(ThreedClassState.PolygonModeFront),
@ -200,7 +201,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
// of the shader for the new state.
if (_shaderSpecState != null)
{
if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState()))
if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState(), GetGraphicsState()))
{
ForceShaderUpdate();
}
@ -568,6 +569,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
var yControl = _state.State.YControl;
var face = _state.State.FaceState;
bool disableTransform = _state.State.ViewportTransformEnable == 0;
UpdateFrontFace(yControl, face.FrontFace);
UpdateDepthMode();
@ -577,6 +580,17 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
for (int index = 0; index < Constants.TotalViewports; index++)
{
if (disableTransform)
{
ref var scissor = ref _state.State.ScreenScissorState;
float rScale = _channel.TextureManager.RenderTargetScale;
var scissorRect = new RectangleF(0, 0, (scissor.X + scissor.Width) * rScale, (scissor.Y + scissor.Height) * rScale);
viewports[index] = new Viewport(scissorRect, ViewportSwizzle.PositiveX, ViewportSwizzle.PositiveY, ViewportSwizzle.PositiveZ, ViewportSwizzle.PositiveW, 0, 1);
continue;
}
ref var transform = ref _state.State.ViewportTransform[index];
ref var extents = ref _state.State.ViewportExtents[index];
@ -628,7 +642,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
viewports[index] = new Viewport(region, swizzleX, swizzleY, swizzleZ, swizzleW, depthNear, depthFar);
}
_context.Renderer.Pipeline.SetViewports(0, viewports);
_context.Renderer.Pipeline.SetViewports(0, viewports, disableTransform);
}
/// <summary>
@ -1194,7 +1208,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
return new GpuChannelGraphicsState(
_state.State.EarlyZForce,
_drawState.Topology,
_state.State.TessMode);
_state.State.TessMode,
_state.State.ViewportTransformEnable == 0);
}
/// <summary>

View File

@ -82,27 +82,13 @@ namespace Ryujinx.Graphics.Gpu
/// <summary>
/// Host hardware capabilities.
/// </summary>
internal ref Capabilities Capabilities
{
get
{
if (!_capsLoaded)
{
_caps = Renderer.GetCapabilities();
_capsLoaded = true;
}
return ref _caps;
}
}
internal Capabilities Capabilities { get; private set; }
/// <summary>
/// Event for signalling shader cache loading progress.
/// </summary>
public event Action<ShaderCacheState, int, int> ShaderCacheStateChanged;
private bool _capsLoaded;
private Capabilities _caps;
private Thread _gpuThread;
/// <summary>
@ -254,6 +240,8 @@ namespace Ryujinx.Graphics.Gpu
public void SetGpuThread()
{
_gpuThread = Thread.CurrentThread;
Capabilities = Renderer.GetCapabilities();
}
/// <summary>

View File

@ -166,7 +166,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
GpuChannelGraphicsState graphicsState = new GpuChannelGraphicsState(
accessorHeader.StateFlags.HasFlag(GuestGpuStateFlags.EarlyZForce),
topology,
tessMode);
tessMode,
false);
TransformFeedbackDescriptor[] tfdNew = null;

View File

@ -185,6 +185,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return _oldSpecState.GraphicsState.EarlyZForce;
}
/// <inheritdoc/>
public bool QueryViewportTransformDisable()
{
return _oldSpecState.GraphicsState.ViewportTransformDisable;
}
/// <inheritdoc/>
public void RegisterTexture(int handle, int cbufSlot)
{

View File

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

View File

@ -217,6 +217,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
return _state.GraphicsState.EarlyZForce;
}
/// <inheritdoc/>
public bool QueryViewportTransformDisable()
{
return _state.GraphicsState.ViewportTransformDisable;
}
/// <inheritdoc/>
public void RegisterTexture(int handle, int cbufSlot)
{

View File

@ -25,17 +25,24 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary>
public readonly TessMode TessellationMode;
/// <summary>
/// Indicates whenever the viewport transform is disabled.
/// </summary>
public readonly bool ViewportTransformDisable;
/// <summary>
/// Creates a new GPU graphics state.
/// </summary>
/// <param name="earlyZForce">Early Z force enable</param>
/// <param name="topology">Primitive topology</param>
/// <param name="tessellationMode">Tessellation mode</param>
public GpuChannelGraphicsState(bool earlyZForce, PrimitiveTopology topology, TessMode tessellationMode)
/// <param name="viewportTransformDisable">Indicates whenever the viewport transform is disabled</param>
public GpuChannelGraphicsState(bool earlyZForce, PrimitiveTopology topology, TessMode tessellationMode, bool viewportTransformDisable)
{
EarlyZForce = earlyZForce;
Topology = topology;
TessellationMode = tessellationMode;
ViewportTransformDisable = viewportTransformDisable;
}
}
}

View File

@ -249,12 +249,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
GpuChannelGraphicsState graphicsState,
ShaderAddresses addresses)
{
if (_gpPrograms.TryGetValue(addresses, out var gpShaders) && IsShaderEqual(channel, poolState, gpShaders, addresses))
if (_gpPrograms.TryGetValue(addresses, out var gpShaders) && IsShaderEqual(channel, poolState, graphicsState, gpShaders, addresses))
{
return gpShaders;
}
if (_graphicsShaderCache.TryFind(channel, poolState, addresses, out gpShaders, out var cachedGuestCode))
if (_graphicsShaderCache.TryFind(channel, poolState, graphicsState, addresses, out gpShaders, out var cachedGuestCode))
{
_gpPrograms[addresses] = gpShaders;
return gpShaders;
@ -429,12 +429,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary>
/// <param name="channel">GPU channel using the shader</param>
/// <param name="poolState">GPU channel state to verify shader compatibility</param>
/// <param name="graphicsState">GPU channel graphics state to verify shader compatibility</param>
/// <param name="gpShaders">Cached graphics shaders</param>
/// <param name="addresses">GPU virtual addresses of all enabled shader stages</param>
/// <returns>True if the code is different, false otherwise</returns>
private static bool IsShaderEqual(
GpuChannel channel,
GpuChannelPoolState poolState,
GpuChannelGraphicsState graphicsState,
CachedShaderProgram gpShaders,
ShaderAddresses addresses)
{
@ -452,7 +454,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
}
return gpShaders.SpecializationState.MatchesGraphics(channel, poolState);
return gpShaders.SpecializationState.MatchesGraphics(channel, poolState, graphicsState);
}
/// <summary>

View File

@ -208,6 +208,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </remarks>
/// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</param>
/// <param name="graphicsState">Graphics state</param>
/// <param name="addresses">Guest addresses of the shaders to find</param>
/// <param name="program">Cached host program for the given state, if found</param>
/// <param name="guestCode">Cached guest code, if any found</param>
@ -215,6 +216,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
public bool TryFind(
GpuChannel channel,
GpuChannelPoolState poolState,
GpuChannelGraphicsState graphicsState,
ShaderAddresses addresses,
out CachedShaderProgram program,
out CachedGraphicsGuestCode guestCode)
@ -234,7 +236,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
if (found && _shaderPrograms.TryGetValue(idTable, out ShaderSpecializationList specList))
{
return specList.TryFindForGraphics(channel, poolState, out program);
return specList.TryFindForGraphics(channel, poolState, graphicsState, out program);
}
return false;

View File

@ -24,13 +24,18 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary>
/// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</param>
/// <param name="graphicsState">Graphics state</param>
/// <param name="program">Cached program, if found</param>
/// <returns>True if a compatible program is found, false otherwise</returns>
public bool TryFindForGraphics(GpuChannel channel, GpuChannelPoolState poolState, out CachedShaderProgram program)
public bool TryFindForGraphics(
GpuChannel channel,
GpuChannelPoolState poolState,
GpuChannelGraphicsState graphicsState,
out CachedShaderProgram program)
{
foreach (var entry in _entries)
{
if (entry.SpecializationState.MatchesGraphics(channel, poolState))
if (entry.SpecializationState.MatchesGraphics(channel, poolState, graphicsState))
{
program = entry;
return true;

View File

@ -395,9 +395,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary>
/// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</param>
/// <param name="graphicsState">Graphics state</param>
/// <returns>True if the state matches, false otherwise</returns>
public bool MatchesGraphics(GpuChannel channel, GpuChannelPoolState poolState)
public bool MatchesGraphics(GpuChannel channel, GpuChannelPoolState poolState, GpuChannelGraphicsState graphicsState)
{
if (graphicsState.ViewportTransformDisable != GraphicsState.ViewportTransformDisable)
{
return false;
}
return Matches(channel, poolState, isCompute: false);
}

View File

@ -379,20 +379,28 @@ namespace Ryujinx.Graphics.OpenGL
switch (op)
{
case GAL.StencilOp.Keep:
case GAL.StencilOp.KeepGl:
return OpenTK.Graphics.OpenGL.StencilOp.Keep;
case GAL.StencilOp.Zero:
case GAL.StencilOp.ZeroGl:
return OpenTK.Graphics.OpenGL.StencilOp.Zero;
case GAL.StencilOp.Replace:
case GAL.StencilOp.ReplaceGl:
return OpenTK.Graphics.OpenGL.StencilOp.Replace;
case GAL.StencilOp.IncrementAndClamp:
case GAL.StencilOp.IncrementAndClampGl:
return OpenTK.Graphics.OpenGL.StencilOp.Incr;
case GAL.StencilOp.DecrementAndClamp:
case GAL.StencilOp.DecrementAndClampGl:
return OpenTK.Graphics.OpenGL.StencilOp.Decr;
case GAL.StencilOp.Invert:
case GAL.StencilOp.InvertGl:
return OpenTK.Graphics.OpenGL.StencilOp.Invert;
case GAL.StencilOp.IncrementAndWrap:
case GAL.StencilOp.IncrementAndWrapGl:
return OpenTK.Graphics.OpenGL.StencilOp.IncrWrap;
case GAL.StencilOp.DecrementAndWrap:
case GAL.StencilOp.DecrementAndWrapGl:
return OpenTK.Graphics.OpenGL.StencilOp.DecrWrap;
}

View File

@ -1266,7 +1266,7 @@ namespace Ryujinx.Graphics.OpenGL
_vertexArray.SetVertexBuffers(vertexBuffers);
}
public void SetViewports(int first, ReadOnlySpan<Viewport> viewports)
public void SetViewports(int first, ReadOnlySpan<Viewport> viewports, bool disableTransform)
{
Array.Resize(ref _viewportArray, viewports.Length * 4);
Array.Resize(ref _depthRangeArray, viewports.Length * 2);
@ -1305,6 +1305,19 @@ namespace Ryujinx.Graphics.OpenGL
GL.ViewportArray(first, viewports.Length, viewportArray);
GL.DepthRangeArray(first, viewports.Length, depthRangeArray);
float disableTransformF = disableTransform ? 1.0f : 0.0f;
if (_supportBuffer.Data.ViewportInverse.W != disableTransformF || disableTransform)
{
float scale = _renderScale[0].X;
_supportBuffer.UpdateViewportInverse(new Vector4<float>
{
X = scale * 2f / viewports[first].Region.Width,
Y = scale * 2f / viewports[first].Region.Height,
Z = 1,
W = disableTransformF
});
}
}
public void TextureBarrier()

View File

@ -249,7 +249,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine();
}
}
else if (isFragment)
else if (isFragment || context.Config.Stage == ShaderStage.Vertex)
{
DeclareSupportUniformBlock(context, context.Config.Stage, 0);
}
@ -615,8 +615,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
private static void DeclareSupportUniformBlock(CodeGenContext context, ShaderStage stage, int scaleElements)
{
bool isFragment = stage == ShaderStage.Fragment;
if (!isFragment && scaleElements == 0)
bool needsSupportBlock = stage == ShaderStage.Fragment ||
(context.Config.LastInVertexPipeline && context.Config.GpuAccessor.QueryViewportTransformDisable());
if (!needsSupportBlock && scaleElements == 0)
{
return;
}
@ -630,6 +632,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
case ShaderStage.Vertex:
context.AppendLine($"uint {DefaultNames.SupportBlockAlphaTestName};");
context.AppendLine($"bool {DefaultNames.SupportBlockIsBgraName}[{SupportBuffer.FragmentIsBgraCount}];");
context.AppendLine($"vec4 {DefaultNames.SupportBlockViewportInverse};");
context.AppendLine($"int {DefaultNames.SupportBlockFragmentScaleCount};");
break;
case ShaderStage.Compute:

View File

@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
public const string SupportBlockName = "support_block";
public const string SupportBlockAlphaTestName = "s_alpha_test";
public const string SupportBlockIsBgraName = "s_is_bgra";
public const string SupportBlockViewportInverse = "s_viewport_inverse";
public const string SupportBlockFragmentScaleCount = "s_frag_scale_count";
public const string SupportBlockRenderScaleName = "s_render_scale";

View File

@ -84,7 +84,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{ AttributeConsts.FragmentOutputIsBgraBase + 16, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[4]", VariableType.Bool) },
{ AttributeConsts.FragmentOutputIsBgraBase + 20, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[5]", VariableType.Bool) },
{ AttributeConsts.FragmentOutputIsBgraBase + 24, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[6]", VariableType.Bool) },
{ AttributeConsts.FragmentOutputIsBgraBase + 28, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[7]", VariableType.Bool) }
{ AttributeConsts.FragmentOutputIsBgraBase + 28, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[7]", VariableType.Bool) },
{ AttributeConsts.SupportBlockViewInverseX, new BuiltInAttribute($"{DefaultNames.SupportBlockViewportInverse}.x", VariableType.F32) },
{ AttributeConsts.SupportBlockViewInverseY, new BuiltInAttribute($"{DefaultNames.SupportBlockViewportInverse}.y", VariableType.F32) }
};
private Dictionary<AstOperand, string> _locals;

View File

@ -329,6 +329,15 @@ namespace Ryujinx.Graphics.Shader
return false;
}
/// <summary>
/// Queries if host state disables the viewport transform.
/// </summary>
/// <returns>True if the viewport transform is disabled</returns>
bool QueryViewportTransformDisable()
{
return false;
}
/// <summary>
/// Registers a texture used by the shader.
/// </summary>

View File

@ -206,7 +206,33 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (emit)
{
context.EmitVertex();
if (context.Config.LastInVertexPipeline)
{
context.PrepareForVertexReturn(out var tempXLocal, out var tempYLocal, out var tempZLocal);
context.EmitVertex();
// Restore output position value before transformation.
if (tempXLocal != null)
{
context.Copy(Attribute(AttributeConsts.PositionX), tempXLocal);
}
if (tempYLocal != null)
{
context.Copy(Attribute(AttributeConsts.PositionY), tempYLocal);
}
if (tempZLocal != null)
{
context.Copy(Attribute(AttributeConsts.PositionZ), tempZLocal);
}
}
else
{
context.EmitVertex();
}
}
if (cut)

View File

@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Shader
public static int FragmentAlphaTestOffset;
public static int FragmentIsBgraOffset;
public static int ViewportInverseOffset;
public static int FragmentRenderScaleCountOffset;
public static int GraphicsRenderScaleOffset;
public static int ComputeRenderScaleOffset;
@ -40,6 +41,7 @@ namespace Ryujinx.Graphics.Shader
FragmentAlphaTestOffset = OffsetOf(ref instance, ref instance.FragmentAlphaTest);
FragmentIsBgraOffset = OffsetOf(ref instance, ref instance.FragmentIsBgra);
ViewportInverseOffset = OffsetOf(ref instance, ref instance.ViewportInverse);
FragmentRenderScaleCountOffset = OffsetOf(ref instance, ref instance.FragmentRenderScaleCount);
GraphicsRenderScaleOffset = OffsetOf(ref instance, ref instance.RenderScale);
ComputeRenderScaleOffset = GraphicsRenderScaleOffset + FieldSize;
@ -47,6 +49,7 @@ namespace Ryujinx.Graphics.Shader
public Vector4<int> FragmentAlphaTest;
public Array8<Vector4<int>> FragmentIsBgra;
public Vector4<float> ViewportInverse;
public Vector4<int> FragmentRenderScaleCount;
// Render scale max count: 1 + 32 + 8. First scale is fragment output scale, others are textures/image inputs.

View File

@ -67,6 +67,9 @@ namespace Ryujinx.Graphics.Shader.Translation
public const int FragmentOutputIsBgraBase = 0x1000100;
public const int FragmentOutputIsBgraEnd = FragmentOutputIsBgraBase + 8 * 4;
public const int SupportBlockViewInverseX = 0x1000200;
public const int SupportBlockViewInverseY = 0x1000204;
public const int ThreadIdX = 0x2000000;
public const int ThreadIdY = 0x2000004;
public const int ThreadIdZ = 0x2000008;

View File

@ -154,9 +154,56 @@ namespace Ryujinx.Graphics.Shader.Translation
return label;
}
public void PrepareForVertexReturn()
{
if (Config.GpuAccessor.QueryViewportTransformDisable())
{
Operand x = Attribute(AttributeConsts.PositionX | AttributeConsts.LoadOutputMask);
Operand y = Attribute(AttributeConsts.PositionY | AttributeConsts.LoadOutputMask);
Operand xScale = Attribute(AttributeConsts.SupportBlockViewInverseX);
Operand yScale = Attribute(AttributeConsts.SupportBlockViewInverseY);
Operand negativeOne = ConstF(-1.0f);
this.Copy(Attribute(AttributeConsts.PositionX), this.FPFusedMultiplyAdd(x, xScale, negativeOne));
this.Copy(Attribute(AttributeConsts.PositionY), this.FPFusedMultiplyAdd(y, yScale, negativeOne));
}
}
public void PrepareForVertexReturn(out Operand oldXLocal, out Operand oldYLocal, out Operand oldZLocal)
{
if (Config.GpuAccessor.QueryViewportTransformDisable())
{
oldXLocal = Local();
this.Copy(oldXLocal, Attribute(AttributeConsts.PositionX | AttributeConsts.LoadOutputMask));
oldYLocal = Local();
this.Copy(oldYLocal, Attribute(AttributeConsts.PositionY | AttributeConsts.LoadOutputMask));
}
else
{
oldXLocal = null;
oldYLocal = null;
}
// Will be used by Vulkan backend for depth mode emulation.
oldZLocal = null;
PrepareForVertexReturn();
}
public void PrepareForReturn()
{
if (!IsNonMain && Config.Stage == ShaderStage.Fragment)
if (IsNonMain)
{
return;
}
if (Config.LastInVertexPipeline &&
(Config.Stage == ShaderStage.Vertex || Config.Stage == ShaderStage.TessellationEvaluation) &&
(Config.Options.Flags & TranslationFlags.VertexA) == 0)
{
PrepareForVertexReturn();
}
else if (Config.Stage == ShaderStage.Fragment)
{
if (Config.OmapDepth)
{

View File

@ -14,6 +14,7 @@ namespace Ryujinx.Graphics.Shader.Translation
public ShaderStage Stage { get; }
public bool GpPassthrough { get; }
public bool LastInVertexPipeline { get; private set; }
public int ThreadsPerInputPrimitive { get; }
@ -135,6 +136,7 @@ namespace Ryujinx.Graphics.Shader.Translation
OmapSampleMask = header.OmapSampleMask;
OmapDepth = header.OmapDepth;
TransformFeedbackEnabled = gpuAccessor.QueryTransformFeedbackEnabled();
LastInVertexPipeline = header.Stage < ShaderStage.Fragment;
}
public int GetDepthRegister()
@ -274,6 +276,11 @@ namespace Ryujinx.Graphics.Shader.Translation
NextInputAttributesPerPatchComponents = config.ThisInputAttributesPerPatchComponents;
NextUsesFixedFuncAttributes = config.UsedFeatures.HasFlag(FeatureFlags.FixedFuncAttr);
MergeOutputUserAttributes(config.UsedInputAttributes, config.UsedInputAttributesPerPatch);
if (config.Stage != ShaderStage.Fragment)
{
LastInVertexPipeline = false;
}
}
public void MergeOutputUserAttributes(int mask, int maskPerPatch)

View File

@ -24,6 +24,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using static LibHac.Fs.ApplicationSaveDataManagement;
using static Ryujinx.HLE.HOS.ModLoader;
@ -308,6 +309,94 @@ namespace Ryujinx.HLE.HOS
LoadNca(nca, null, null);
}
public void LoadServiceNca(string ncaFile)
{
// Disable PPTC here as it does not support multiple processes running.
// TODO: This should be eventually removed and it should stop using global state and
// instead manage the cache per process.
Ptc.Close();
PtcProfiler.Stop();
FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read);
Nca mainNca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false));
if (mainNca.Header.ContentType != NcaContentType.Program)
{
Logger.Error?.Print(LogClass.Loader, "Selected NCA is not a \"Program\" NCA");
return;
}
IFileSystem codeFs = null;
if (mainNca.CanOpenSection(NcaSectionType.Code))
{
codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, _device.System.FsIntegrityCheckLevel);
}
if (codeFs == null)
{
Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA");
return;
}
using var npdmFile = new UniqueRef<IFile>();
Result result = codeFs.OpenFile(ref npdmFile.Ref(), "/main.npdm".ToU8Span(), OpenMode.Read);
MetaLoader metaData;
npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure();
var npdmBuffer = new byte[fileSize];
npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure();
metaData = new MetaLoader();
metaData.Load(npdmBuffer).ThrowIfFailure();
NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length];
for (int i = 0; i < nsos.Length; i++)
{
string name = ExeFsPrefixes[i];
if (!codeFs.FileExists($"/{name}"))
{
continue; // File doesn't exist, skip.
}
Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
using var nsoFile = new UniqueRef<IFile>();
codeFs.OpenFile(ref nsoFile.Ref(), $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name);
}
// Collect the nsos, ignoring ones that aren't used.
NsoExecutable[] programs = nsos.Where(x => x != null).ToArray();
MemoryManagerMode memoryManagerMode = _device.Configuration.MemoryManagerMode;
if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible))
{
memoryManagerMode = MemoryManagerMode.SoftwarePageTable;
}
metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
ProgramInfo programInfo = new ProgramInfo(in npdm, allowCodeMemoryForJit: false);
ProgramLoader.LoadNsos(_device.System.KernelContext, out _, metaData, programInfo, executables: programs);
string titleIdText = npdm.Aci.Value.ProgramId.Value.ToString("x16");
bool titleIs64Bit = (npdm.Meta.Value.Flags & 1) != 0;
string programName = Encoding.ASCII.GetString(npdm.Meta.Value.ProgramName).TrimEnd('\0');
Logger.Info?.Print(LogClass.Loader, $"Service Loaded: {programName} [{titleIdText}] [{(titleIs64Bit ? "64-bit" : "32-bit")}]");
}
private void LoadNca(Nca mainNca, Nca patchNca, Nca controlNca)
{
if (mainNca.Header.ContentType != NcaContentType.Program)
@ -508,7 +597,7 @@ namespace Ryujinx.HLE.HOS
{
if (_device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(TitleId, ref codeFs))
{
metaData = null; //TODO: Check if we should retain old npdm
metaData = null; // TODO: Check if we should retain old npdm.
}
metaData ??= ReadNpdm(codeFs);
@ -521,7 +610,7 @@ namespace Ryujinx.HLE.HOS
if (!codeFs.FileExists($"/{name}"))
{
continue; // file doesn't exist, skip
continue; // File doesn't exist, skip.
}
Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
@ -533,13 +622,13 @@ namespace Ryujinx.HLE.HOS
nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name);
}
// ExeFs file replacements
// ExeFs file replacements.
ModLoadResult modLoadResult = _device.Configuration.VirtualFileSystem.ModLoader.ApplyExefsMods(TitleId, nsos);
// collect the nsos, ignoring ones that aren't used
// Collect the nsos, ignoring ones that aren't used.
NsoExecutable[] programs = nsos.Where(x => x != null).ToArray();
// take the npdm from mods if present
// Take the npdm from mods if present.
if (modLoadResult.Npdm != null)
{
metaData = modLoadResult.Npdm;
@ -598,7 +687,7 @@ namespace Ryujinx.HLE.HOS
executable = obj;
// homebrew NRO can actually have some data after the actual NRO
// Homebrew NRO can actually have some data after the actual NRO.
if (input.Length > obj.FileSize)
{
input.Position = obj.FileSize;
@ -677,7 +766,7 @@ namespace Ryujinx.HLE.HOS
TitleIs64Bit = (npdm.Meta.Value.Flags & 1) != 0;
_device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId);
// Explicitly null titleid to disable the shader cache
// Explicitly null titleid to disable the shader cache.
Graphics.Gpu.GraphicsConfig.TitleId = null;
_device.Gpu.HostInitalized.Set();

View File

@ -72,6 +72,8 @@ namespace Ryujinx.HLE.HOS
internal List<NfpDevice> NfpDevices { get; private set; }
internal SmRegistry SmRegistry { get; private set; }
internal ServerBase SmServer { get; private set; }
internal ServerBase BsdServer { get; private set; }
internal ServerBase AudRenServer { get; private set; }
@ -291,7 +293,8 @@ namespace Ryujinx.HLE.HOS
private void InitializeServices()
{
SmServer = new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext));
SmRegistry = new SmRegistry();
SmServer = new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext, SmRegistry));
// Wait until SM server thread is done with initialization,
// only then doing connections to SM is safe.
@ -468,7 +471,7 @@ namespace Ryujinx.HLE.HOS
AudioRendererManager.Dispose();
LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure();
KernelContext.Dispose();
}
}

View File

@ -2,9 +2,12 @@ using LibHac;
using LibHac.Account;
using LibHac.Common;
using LibHac.Fs;
using LibHac.FsSystem;
using LibHac.Ncm;
using LibHac.Ns;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Memory;
@ -15,6 +18,7 @@ using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService;
using Ryujinx.HLE.HOS.SystemState;
using System;
using System.Numerics;
using System.Threading;
using static LibHac.Fs.ApplicationSaveDataManagement;
using AccountUid = Ryujinx.HLE.HOS.Services.Account.Acc.UserId;
@ -37,6 +41,10 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
private int _notificationStorageChannelEventHandle;
private int _healthWarningDisappearedSystemEventHandle;
private bool _gamePlayRecordingState;
private int _jitLoaded;
private HorizonClient _horizon;
public IApplicationFunctions(Horizon system)
@ -342,6 +350,15 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
return ResultCode.Success;
}
[CommandHipc(65)] // 3.0.0+
// IsGamePlayRecordingSupported() -> u8
public ResultCode IsGamePlayRecordingSupported(ServiceCtx context)
{
context.ResponseData.Write(_gamePlayRecordingState);
return ResultCode.Success;
}
[CommandHipc(66)] // 3.0.0+
// InitializeGamePlayRecording(u64, handle<copy>)
public ResultCode InitializeGamePlayRecording(ServiceCtx context)
@ -355,9 +372,9 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
// SetGamePlayRecordingState(u32)
public ResultCode SetGamePlayRecordingState(ServiceCtx context)
{
int state = context.RequestData.ReadInt32();
_gamePlayRecordingState = context.RequestData.ReadInt32() != 0;
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { state });
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { _gamePlayRecordingState });
return ResultCode.Success;
}
@ -631,5 +648,31 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
return ResultCode.Success;
}
[CommandHipc(1001)] // 10.0.0+
// PrepareForJit()
public ResultCode PrepareForJit(ServiceCtx context)
{
if (Interlocked.Exchange(ref _jitLoaded, 1) == 0)
{
string jitPath = context.Device.System.ContentManager.GetInstalledContentPath(0x010000000000003B, StorageId.BuiltInSystem, NcaContentType.Program);
string filePath = context.Device.FileSystem.SwitchPathToSystemPath(jitPath);
if (string.IsNullOrWhiteSpace(filePath))
{
throw new InvalidSystemResourceException($"JIT (010000000000003B) system title not found! The JIT will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx#requirements for more information)");
}
context.Device.Application.LoadServiceNca(filePath);
// FIXME: Most likely not how this should be done?
while (!context.Device.System.SmRegistry.IsServiceRegistered("jit:u"))
{
context.Device.System.SmRegistry.WaitForServiceRegistration();
}
}
return ResultCode.Success;
}
}
}

View File

@ -21,7 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
private ControllerType[] _configuredTypes;
private KEvent[] _styleSetUpdateEvents;
private bool[] _supportedPlayers;
private static HidVibrationValue _neutralVibrationValue = new HidVibrationValue
private static VibrationValue _neutralVibrationValue = new VibrationValue
{
AmplitudeLow = 0f,
FrequencyLow = 160f,
@ -33,8 +33,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
internal bool SixAxisActive = false; // TODO: link to hidserver when implemented
internal ControllerType SupportedStyleSets { get; set; }
public Dictionary<PlayerIndex, ConcurrentQueue<(HidVibrationValue, HidVibrationValue)>> RumbleQueues = new Dictionary<PlayerIndex, ConcurrentQueue<(HidVibrationValue, HidVibrationValue)>>();
public Dictionary<PlayerIndex, (HidVibrationValue, HidVibrationValue)> LastVibrationValues = new Dictionary<PlayerIndex, (HidVibrationValue, HidVibrationValue)>();
public Dictionary<PlayerIndex, ConcurrentQueue<(VibrationValue, VibrationValue)>> RumbleQueues = new Dictionary<PlayerIndex, ConcurrentQueue<(VibrationValue, VibrationValue)>>();
public Dictionary<PlayerIndex, (VibrationValue, VibrationValue)> LastVibrationValues = new Dictionary<PlayerIndex, (VibrationValue, VibrationValue)>();
public NpadDevices(Switch device, bool active = true) : base(device, active)
{
@ -588,21 +588,21 @@ namespace Ryujinx.HLE.HOS.Services.Hid
WriteNewSixInputEntry(ref currentNpad.JoyRightSixAxisSensor, ref newState);
}
public void UpdateRumbleQueue(PlayerIndex index, Dictionary<byte, HidVibrationValue> dualVibrationValues)
public void UpdateRumbleQueue(PlayerIndex index, Dictionary<byte, VibrationValue> dualVibrationValues)
{
if (RumbleQueues.TryGetValue(index, out ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> currentQueue))
if (RumbleQueues.TryGetValue(index, out ConcurrentQueue<(VibrationValue, VibrationValue)> currentQueue))
{
if (!dualVibrationValues.TryGetValue(0, out HidVibrationValue leftVibrationValue))
if (!dualVibrationValues.TryGetValue(0, out VibrationValue leftVibrationValue))
{
leftVibrationValue = _neutralVibrationValue;
}
if (!dualVibrationValues.TryGetValue(1, out HidVibrationValue rightVibrationValue))
if (!dualVibrationValues.TryGetValue(1, out VibrationValue rightVibrationValue))
{
rightVibrationValue = _neutralVibrationValue;
}
if (!LastVibrationValues.TryGetValue(index, out (HidVibrationValue, HidVibrationValue) dualVibrationValue) || !leftVibrationValue.Equals(dualVibrationValue.Item1) || !rightVibrationValue.Equals(dualVibrationValue.Item2))
if (!LastVibrationValues.TryGetValue(index, out (VibrationValue, VibrationValue) dualVibrationValue) || !leftVibrationValue.Equals(dualVibrationValue.Item1) || !rightVibrationValue.Equals(dualVibrationValue.Item2))
{
currentQueue.Enqueue((leftVibrationValue, rightVibrationValue));
@ -611,9 +611,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid
}
}
public HidVibrationValue GetLastVibrationValue(PlayerIndex index, byte position)
public VibrationValue GetLastVibrationValue(PlayerIndex index, byte position)
{
if (!LastVibrationValues.TryGetValue(index, out (HidVibrationValue, HidVibrationValue) dualVibrationValue))
if (!LastVibrationValues.TryGetValue(index, out (VibrationValue, VibrationValue) dualVibrationValue))
{
return _neutralVibrationValue;
}
@ -621,11 +621,11 @@ namespace Ryujinx.HLE.HOS.Services.Hid
return (position == 0) ? dualVibrationValue.Item1 : dualVibrationValue.Item2;
}
public ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> GetRumbleQueue(PlayerIndex index)
public ConcurrentQueue<(VibrationValue, VibrationValue)> GetRumbleQueue(PlayerIndex index)
{
if (!RumbleQueues.TryGetValue(index, out ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> rumbleQueue))
if (!RumbleQueues.TryGetValue(index, out ConcurrentQueue<(VibrationValue, VibrationValue)> rumbleQueue))
{
rumbleQueue = new ConcurrentQueue<(HidVibrationValue, HidVibrationValue)>();
rumbleQueue = new ConcurrentQueue<(VibrationValue, VibrationValue)>();
_device.Hid.Npads.RumbleQueues[index] = rumbleQueue;
}

View File

@ -35,5 +35,10 @@ namespace Ryujinx.HLE.HOS.Services.Hid.HidServer
PlayerIndex.Unknown => NpadIdType.Unknown,
_ => throw new ArgumentOutOfRangeException(nameof(index))
};
public static bool IsValidNpadIdType(NpadIdType npadIdType)
{
return npadIdType <= NpadIdType.Player8 || npadIdType == NpadIdType.Handheld || npadIdType == NpadIdType.Unknown;
}
}
}

View File

@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public enum HidNpadJoyAssignmentMode
{
Dual,
Single
}
}

View File

@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public enum HidNpadHandheldActivationMode
public enum NpadHandheldActivationMode
{
Dual,
Single,

View File

@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public enum HidNpadJoyDeviceType
public enum NpadJoyDeviceType
{
Left,
Right

View File

@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public struct HidAccelerometerParameters
public struct AccelerometerParameters
{
public float X;
public float Y;

View File

@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public enum HidGyroscopeZeroDriftMode
public enum GyroscopeZeroDriftMode
{
Loose,
Standard,

View File

@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public struct HidSensorFusionParameters
public struct SensorFusionParameters
{
public float RevisePower;
public float ReviseRange;

View File

@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public struct HidVibrationDeviceValue
{
public HidVibrationDeviceType DeviceType;
public HidVibrationDevicePosition Position;
}
}

View File

@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public struct HidVibrationDeviceHandle
public struct VibrationDeviceHandle
{
public byte DeviceType;
public byte PlayerId;

View File

@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public enum HidVibrationDevicePosition
public enum VibrationDevicePosition
{
None,
Left,

View File

@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public enum HidVibrationDeviceType
public enum VibrationDeviceType
{
None,
LinearResonantActuator,

View File

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public struct VibrationDeviceValue
{
public VibrationDeviceType DeviceType;
public VibrationDevicePosition Position;
}
}

View File

@ -1,9 +1,8 @@
using Ryujinx.HLE.HOS.Tamper;
using System;
using System;
namespace Ryujinx.HLE.HOS.Services.Hid
{
public struct HidVibrationValue
public struct VibrationValue
{
public float AmplitudeLow;
public float FrequencyLow;
@ -12,7 +11,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
public override bool Equals(object obj)
{
return obj is HidVibrationValue value &&
return obj is VibrationValue value &&
AmplitudeLow == value.AmplitudeLow &&
AmplitudeHigh == value.AmplitudeHigh;
}

View File

@ -5,6 +5,7 @@ using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Hid.HidServer;
using Ryujinx.HLE.HOS.Services.Hid.Types;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
@ -26,9 +27,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
private bool _isFirmwareUpdateAvailableForSixAxisSensor;
private bool _isSixAxisSensorUnalteredPassthroughEnabled;
private HidNpadJoyAssignmentMode _npadJoyAssignmentMode;
private HidNpadHandheldActivationMode _npadHandheldActivationMode;
private HidGyroscopeZeroDriftMode _gyroscopeZeroDriftMode;
private NpadHandheldActivationMode _npadHandheldActivationMode;
private GyroscopeZeroDriftMode _gyroscopeZeroDriftMode;
private long _npadCommunicationMode;
private uint _accelerometerPlayMode;
@ -37,22 +37,21 @@ namespace Ryujinx.HLE.HOS.Services.Hid
#pragma warning restore CS0649
private float _sevenSixAxisSensorFusionStrength;
private HidSensorFusionParameters _sensorFusionParams;
private HidAccelerometerParameters _accelerometerParams;
private SensorFusionParameters _sensorFusionParams;
private AccelerometerParameters _accelerometerParams;
public IHidServer(ServiceCtx context) : base(context.Device.System.HidServer)
{
_xpadIdEvent = new KEvent(context.Device.System.KernelContext);
_palmaOperationCompleteEvent = new KEvent(context.Device.System.KernelContext);
_npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Dual;
_npadHandheldActivationMode = HidNpadHandheldActivationMode.Dual;
_gyroscopeZeroDriftMode = HidGyroscopeZeroDriftMode.Standard;
_npadHandheldActivationMode = NpadHandheldActivationMode.Dual;
_gyroscopeZeroDriftMode = GyroscopeZeroDriftMode.Standard;
_isFirmwareUpdateAvailableForSixAxisSensor = false;
_sensorFusionParams = new HidSensorFusionParameters();
_accelerometerParams = new HidAccelerometerParameters();
_sensorFusionParams = new SensorFusionParameters();
_accelerometerParams = new AccelerometerParameters();
// TODO: signal event at right place
_xpadIdEvent.ReadableEvent.Signal();
@ -393,7 +392,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
int sixAxisSensorHandle = context.RequestData.ReadInt32();
context.RequestData.BaseStream.Position += 4; // Padding
_sensorFusionParams = new HidSensorFusionParameters
_sensorFusionParams = new SensorFusionParameters
{
RevisePower = context.RequestData.ReadInt32(),
ReviseRange = context.RequestData.ReadInt32()
@ -445,7 +444,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
int sixAxisSensorHandle = context.RequestData.ReadInt32();
context.RequestData.BaseStream.Position += 4; // Padding
_accelerometerParams = new HidAccelerometerParameters
_accelerometerParams = new AccelerometerParameters
{
X = context.RequestData.ReadInt32(),
Y = context.RequestData.ReadInt32()
@ -539,7 +538,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
public ResultCode SetGyroscopeZeroDriftMode(ServiceCtx context)
{
int sixAxisSensorHandle = context.RequestData.ReadInt32();
_gyroscopeZeroDriftMode = (HidGyroscopeZeroDriftMode)context.RequestData.ReadInt32();
_gyroscopeZeroDriftMode = (GyroscopeZeroDriftMode)context.RequestData.ReadInt32();
long appletResourceUserId = context.RequestData.ReadInt64();
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode });
@ -570,7 +569,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
context.RequestData.BaseStream.Position += 4; // Padding
long appletResourceUserId = context.RequestData.ReadInt64();
_gyroscopeZeroDriftMode = HidGyroscopeZeroDriftMode.Standard;
_gyroscopeZeroDriftMode = GyroscopeZeroDriftMode.Standard;
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode });
@ -909,54 +908,63 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// SetNpadJoyAssignmentModeSingleByDefault(uint HidControllerId, nn::applet::AppletResourceUserId)
public ResultCode SetNpadJoyAssignmentModeSingleByDefault(ServiceCtx context)
{
PlayerIndex hidControllerId = (PlayerIndex)context.RequestData.ReadInt32();
long appletResourceUserId = context.RequestData.ReadInt64();
NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32();
context.RequestData.BaseStream.Position += 4; // Padding
long appletResourceUserId = context.RequestData.ReadInt64();
_npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Single;
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, hidControllerId, _npadJoyAssignmentMode });
if (HidUtils.IsValidNpadIdType(npadIdType))
{
context.Device.Hid.SharedMemory.Npads[(int)HidUtils.GetIndexFromNpadIdType(npadIdType)].InternalState.JoyAssignmentMode = NpadJoyAssignmentMode.Single;
}
return ResultCode.Success;
}
[CommandHipc(123)]
// SetNpadJoyAssignmentModeSingle(uint HidControllerId, nn::applet::AppletResourceUserId, long HidNpadJoyDeviceType)
// SetNpadJoyAssignmentModeSingle(uint npadIdType, nn::applet::AppletResourceUserId, uint npadJoyDeviceType)
public ResultCode SetNpadJoyAssignmentModeSingle(ServiceCtx context)
{
PlayerIndex hidControllerId = (PlayerIndex)context.RequestData.ReadInt32();
long appletResourceUserId = context.RequestData.ReadInt64();
HidNpadJoyDeviceType hidNpadJoyDeviceType = (HidNpadJoyDeviceType)context.RequestData.ReadInt64();
NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32();
context.RequestData.BaseStream.Position += 4; // Padding
long appletResourceUserId = context.RequestData.ReadInt64();
NpadJoyDeviceType npadJoyDeviceType = (NpadJoyDeviceType)context.RequestData.ReadUInt32();
_npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Single;
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, hidControllerId, hidNpadJoyDeviceType, _npadJoyAssignmentMode });
if (HidUtils.IsValidNpadIdType(npadIdType))
{
SetNpadJoyAssignmentModeSingleWithDestinationImpl(context, npadIdType, appletResourceUserId, npadJoyDeviceType, out _, out _);
}
return ResultCode.Success;
}
[CommandHipc(124)]
// SetNpadJoyAssignmentModeDual(uint HidControllerId, nn::applet::AppletResourceUserId)
// SetNpadJoyAssignmentModeDual(uint npadIdType, nn::applet::AppletResourceUserId)
public ResultCode SetNpadJoyAssignmentModeDual(ServiceCtx context)
{
PlayerIndex hidControllerId = HidUtils.GetIndexFromNpadIdType((NpadIdType)context.RequestData.ReadInt32());
long appletResourceUserId = context.RequestData.ReadInt64();
NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32();
context.RequestData.BaseStream.Position += 4; // Padding
long appletResourceUserId = context.RequestData.ReadInt64();
_npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Dual;
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, hidControllerId, _npadJoyAssignmentMode });
if (HidUtils.IsValidNpadIdType(npadIdType))
{
context.Device.Hid.SharedMemory.Npads[(int)HidUtils.GetIndexFromNpadIdType(npadIdType)].InternalState.JoyAssignmentMode = NpadJoyAssignmentMode.Dual;
}
return ResultCode.Success;
}
[CommandHipc(125)]
// MergeSingleJoyAsDualJoy(uint SingleJoyId0, uint SingleJoyId1, nn::applet::AppletResourceUserId)
// MergeSingleJoyAsDualJoy(uint npadIdType0, uint npadIdType1, nn::applet::AppletResourceUserId)
public ResultCode MergeSingleJoyAsDualJoy(ServiceCtx context)
{
long singleJoyId0 = context.RequestData.ReadInt32();
long singleJoyId1 = context.RequestData.ReadInt32();
long appletResourceUserId = context.RequestData.ReadInt64();
NpadIdType npadIdType0 = (NpadIdType)context.RequestData.ReadUInt32();
NpadIdType npadIdType1 = (NpadIdType)context.RequestData.ReadUInt32();
long appletResourceUserId = context.RequestData.ReadInt64();
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, singleJoyId0, singleJoyId1 });
if (HidUtils.IsValidNpadIdType(npadIdType0) && HidUtils.IsValidNpadIdType(npadIdType1))
{
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, npadIdType0, npadIdType1 });
}
return ResultCode.Success;
}
@ -988,7 +996,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
public ResultCode SetNpadHandheldActivationMode(ServiceCtx context)
{
long appletResourceUserId = context.RequestData.ReadInt64();
_npadHandheldActivationMode = (HidNpadHandheldActivationMode)context.RequestData.ReadInt64();
_npadHandheldActivationMode = (NpadHandheldActivationMode)context.RequestData.ReadInt64();
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadHandheldActivationMode });
@ -1049,37 +1057,47 @@ namespace Ryujinx.HLE.HOS.Services.Hid
}
[CommandHipc(133)] // 5.0.0+
// SetNpadJoyAssignmentModeSingleWithDestination(uint HidControllerId, long HidNpadJoyDeviceType, nn::applet::AppletResourceUserId) -> bool Unknown0, uint Unknown1
// SetNpadJoyAssignmentModeSingleWithDestination(uint npadIdType, uint npadJoyDeviceType, nn::applet::AppletResourceUserId) -> bool npadIdTypeIsSet, uint npadIdTypeSet
public ResultCode SetNpadJoyAssignmentModeSingleWithDestination(ServiceCtx context)
{
PlayerIndex hidControllerId = (PlayerIndex)context.RequestData.ReadInt32();
HidNpadJoyDeviceType hidNpadJoyDeviceType = (HidNpadJoyDeviceType)context.RequestData.ReadInt64();
long appletResourceUserId = context.RequestData.ReadInt64();
NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadInt32();
NpadJoyDeviceType npadJoyDeviceType = (NpadJoyDeviceType)context.RequestData.ReadInt32();
context.RequestData.BaseStream.Position += 4; // Padding
long appletResourceUserId = context.RequestData.ReadInt64();
_npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Single;
if (HidUtils.IsValidNpadIdType(npadIdType))
{
SetNpadJoyAssignmentModeSingleWithDestinationImpl(context, npadIdType, appletResourceUserId, npadJoyDeviceType, out NpadIdType npadIdTypeSet, out bool npadIdTypeIsSet);
context.ResponseData.Write(0); //Unknown0
context.ResponseData.Write(0); //Unknown1
Logger.Stub?.PrintStub(LogClass.ServiceHid, new {
appletResourceUserId,
hidControllerId,
hidNpadJoyDeviceType,
_npadJoyAssignmentMode,
Unknown0 = 0,
Unknown1 = 0
});
if (npadIdTypeIsSet)
{
context.ResponseData.Write(npadIdTypeIsSet);
context.ResponseData.Write((uint)npadIdTypeSet);
}
}
return ResultCode.Success;
}
private void SetNpadJoyAssignmentModeSingleWithDestinationImpl(ServiceCtx context, NpadIdType npadIdType, long appletResourceUserId, NpadJoyDeviceType npadJoyDeviceType, out NpadIdType npadIdTypeSet, out bool npadIdTypeIsSet)
{
npadIdTypeSet = default;
npadIdTypeIsSet = false;
context.Device.Hid.SharedMemory.Npads[(int)HidUtils.GetIndexFromNpadIdType(npadIdType)].InternalState.JoyAssignmentMode = NpadJoyAssignmentMode.Single;
// TODO: Service seems to use the npadJoyDeviceType to find the nearest other Npad available and merge them to dual.
// If one is found, it returns the npadIdType of the other Npad and a bool.
// If not, it returns nothing.
}
[CommandHipc(200)]
// GetVibrationDeviceInfo(nn::hid::VibrationDeviceHandle) -> nn::hid::VibrationDeviceInfo
public ResultCode GetVibrationDeviceInfo(ServiceCtx context)
{
HidVibrationDeviceHandle deviceHandle = context.RequestData.ReadStruct<HidVibrationDeviceHandle>();
NpadStyleIndex deviceType = (NpadStyleIndex)deviceHandle.DeviceType;
NpadIdType npadIdType = (NpadIdType)deviceHandle.PlayerId;
VibrationDeviceHandle deviceHandle = context.RequestData.ReadStruct<VibrationDeviceHandle>();
NpadStyleIndex deviceType = (NpadStyleIndex)deviceHandle.DeviceType;
NpadIdType npadIdType = (NpadIdType)deviceHandle.PlayerId;
if (deviceType < NpadStyleIndex.System || deviceType >= NpadStyleIndex.FullKey)
{
@ -1093,28 +1111,28 @@ namespace Ryujinx.HLE.HOS.Services.Hid
return ResultCode.InvalidDeviceIndex;
}
HidVibrationDeviceType vibrationDeviceType = HidVibrationDeviceType.None;
VibrationDeviceType vibrationDeviceType = VibrationDeviceType.None;
if (Enum.IsDefined<NpadStyleIndex>(deviceType))
if (Enum.IsDefined(deviceType))
{
vibrationDeviceType = HidVibrationDeviceType.LinearResonantActuator;
vibrationDeviceType = VibrationDeviceType.LinearResonantActuator;
}
else if ((uint)deviceType == 8)
{
vibrationDeviceType = HidVibrationDeviceType.GcErm;
vibrationDeviceType = VibrationDeviceType.GcErm;
}
HidVibrationDevicePosition vibrationDevicePosition = HidVibrationDevicePosition.None;
VibrationDevicePosition vibrationDevicePosition = VibrationDevicePosition.None;
if (vibrationDeviceType == HidVibrationDeviceType.LinearResonantActuator)
if (vibrationDeviceType == VibrationDeviceType.LinearResonantActuator)
{
if (deviceHandle.Position == 0)
{
vibrationDevicePosition = HidVibrationDevicePosition.Left;
vibrationDevicePosition = VibrationDevicePosition.Left;
}
else if (deviceHandle.Position == 1)
{
vibrationDevicePosition = HidVibrationDevicePosition.Right;
vibrationDevicePosition = VibrationDevicePosition.Right;
}
else
{
@ -1122,7 +1140,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
}
}
HidVibrationDeviceValue deviceInfo = new HidVibrationDeviceValue
VibrationDeviceValue deviceInfo = new VibrationDeviceValue
{
DeviceType = vibrationDeviceType,
Position = vibrationDevicePosition
@ -1140,7 +1158,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// SendVibrationValue(nn::hid::VibrationDeviceHandle, nn::hid::VibrationValue, nn::applet::AppletResourceUserId)
public ResultCode SendVibrationValue(ServiceCtx context)
{
HidVibrationDeviceHandle deviceHandle = new HidVibrationDeviceHandle
VibrationDeviceHandle deviceHandle = new VibrationDeviceHandle
{
DeviceType = context.RequestData.ReadByte(),
PlayerId = context.RequestData.ReadByte(),
@ -1148,7 +1166,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
Reserved = context.RequestData.ReadByte()
};
HidVibrationValue vibrationValue = new HidVibrationValue
VibrationValue vibrationValue = new VibrationValue
{
AmplitudeLow = context.RequestData.ReadSingle(),
FrequencyLow = context.RequestData.ReadSingle(),
@ -1158,7 +1176,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
long appletResourceUserId = context.RequestData.ReadInt64();
Dictionary<byte, HidVibrationValue> dualVibrationValues = new Dictionary<byte, HidVibrationValue>();
Dictionary<byte, VibrationValue> dualVibrationValues = new Dictionary<byte, VibrationValue>();
dualVibrationValues[deviceHandle.Position] = vibrationValue;
@ -1171,7 +1189,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// GetActualVibrationValue(nn::hid::VibrationDeviceHandle, nn::applet::AppletResourceUserId) -> nn::hid::VibrationValue
public ResultCode GetActualVibrationValue(ServiceCtx context)
{
HidVibrationDeviceHandle deviceHandle = new HidVibrationDeviceHandle
VibrationDeviceHandle deviceHandle = new VibrationDeviceHandle
{
DeviceType = context.RequestData.ReadByte(),
PlayerId = context.RequestData.ReadByte(),
@ -1181,7 +1199,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
long appletResourceUserId = context.RequestData.ReadInt64();
HidVibrationValue vibrationValue = context.Device.Hid.Npads.GetLastVibrationValue((PlayerIndex)deviceHandle.PlayerId, deviceHandle.Position);
VibrationValue vibrationValue = context.Device.Hid.Npads.GetLastVibrationValue((PlayerIndex)deviceHandle.PlayerId, deviceHandle.Position);
context.ResponseData.Write(vibrationValue.AmplitudeLow);
context.ResponseData.Write(vibrationValue.FrequencyLow);
@ -1234,12 +1252,12 @@ namespace Ryujinx.HLE.HOS.Services.Hid
context.Memory.Read(context.Request.PtrBuff[1].Position, vibrationValueBuffer);
Span<HidVibrationDeviceHandle> deviceHandles = MemoryMarshal.Cast<byte, HidVibrationDeviceHandle>(vibrationDeviceHandleBuffer);
Span<HidVibrationValue> vibrationValues = MemoryMarshal.Cast<byte, HidVibrationValue>(vibrationValueBuffer);
Span<VibrationDeviceHandle> deviceHandles = MemoryMarshal.Cast<byte, VibrationDeviceHandle>(vibrationDeviceHandleBuffer);
Span<VibrationValue> vibrationValues = MemoryMarshal.Cast<byte, VibrationValue>(vibrationValueBuffer);
if (!deviceHandles.IsEmpty && vibrationValues.Length == deviceHandles.Length)
{
Dictionary<byte, HidVibrationValue> dualVibrationValues = new Dictionary<byte, HidVibrationValue>();
Dictionary<byte, VibrationValue> dualVibrationValues = new Dictionary<byte, VibrationValue>();
PlayerIndex currentIndex = (PlayerIndex)deviceHandles[0].PlayerId;
for (int deviceCounter = 0; deviceCounter < deviceHandles.Length; deviceCounter++)
@ -1250,7 +1268,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
if (index != currentIndex || dualVibrationValues.Count == 2)
{
context.Device.Hid.Npads.UpdateRumbleQueue(currentIndex, dualVibrationValues);
dualVibrationValues = new Dictionary<byte, HidVibrationValue>();
dualVibrationValues = new Dictionary<byte, VibrationValue>();
}
dualVibrationValues[position] = vibrationValues[deviceCounter];

View File

@ -30,6 +30,7 @@ namespace Ryujinx.HLE.HOS.Services.Ro
private List<NroInfo> _nroInfos;
private KProcess _owner;
private IVirtualMemoryManager _ownerMm;
private static Random _random = new Random();
@ -38,6 +39,7 @@ namespace Ryujinx.HLE.HOS.Services.Ro
_nrrInfos = new List<NrrInfo>(MaxNrr);
_nroInfos = new List<NroInfo>(MaxNro);
_owner = null;
_ownerMm = null;
}
private ResultCode ParseNrr(out NrrInfo nrrInfo, ServiceCtx context, ulong nrrAddress, ulong nrrSize)
@ -564,10 +566,12 @@ namespace Ryujinx.HLE.HOS.Services.Ro
return ResultCode.InvalidSession;
}
_owner = context.Process.HandleTable.GetKProcess(context.Request.HandleDesc.ToCopy[0]);
context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
int processHandle = context.Request.HandleDesc.ToCopy[0];
_owner = context.Process.HandleTable.GetKProcess(processHandle);
_ownerMm = _owner?.CpuMemory;
context.Device.System.KernelContext.Syscall.CloseHandle(processHandle);
if (_owner?.CpuMemory is IRefCounted rc)
if (_ownerMm is IRefCounted rc)
{
rc.IncrementReferenceCount();
}
@ -575,6 +579,15 @@ namespace Ryujinx.HLE.HOS.Services.Ro
return ResultCode.Success;
}
[CommandHipc(10)]
// LoadNrr2(u64, u64, u64, pid)
public ResultCode LoadNrr2(ServiceCtx context)
{
context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
return LoadNrr(context);
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
@ -586,7 +599,7 @@ namespace Ryujinx.HLE.HOS.Services.Ro
_nroInfos.Clear();
if (_owner?.CpuMemory is IRefCounted rc)
if (_ownerMm is IRefCounted rc)
{
rc.DecrementReferenceCount();
}

View File

@ -4,7 +4,6 @@ using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Ipc;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Sm;
using System;
using System.Buffers.Binary;
using System.Collections.Generic;

View File

@ -222,7 +222,7 @@ namespace Ryujinx.HLE.HOS.Services.Settings
return ResultCode.Success;
}
[CommandHipc(60)]
[CommandHipc(60)]
// IsUserSystemClockAutomaticCorrectionEnabled() -> bool
public ResultCode IsUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context)
{
@ -234,6 +234,17 @@ namespace Ryujinx.HLE.HOS.Services.Settings
return ResultCode.Success;
}
[CommandHipc(62)]
// GetDebugModeFlag() -> bool
public ResultCode GetDebugModeFlag(ServiceCtx context)
{
context.ResponseData.Write(false);
Logger.Stub?.PrintStub(LogClass.ServiceSet);
return ResultCode.Success;
}
[CommandHipc(77)]
// GetDeviceNickName() -> buffer<nn::settings::system::DeviceNickName, 0x16>
public ResultCode GetDeviceNickName(ServiceCtx context)

View File

@ -1,11 +1,9 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Ipc;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -17,21 +15,19 @@ namespace Ryujinx.HLE.HOS.Services.Sm
{
private static Dictionary<string, Type> _services;
private static readonly ConcurrentDictionary<string, KPort> _registeredServices;
private readonly SmRegistry _registry;
private readonly ServerBase _commonServer;
private bool _isInitialized;
public IUserInterface(KernelContext context)
public IUserInterface(KernelContext context, SmRegistry registry)
{
_commonServer = new ServerBase(context, "CommonServer");
_registry = registry;
}
static IUserInterface()
{
_registeredServices = new ConcurrentDictionary<string, KPort>();
_services = Assembly.GetExecutingAssembly().GetTypes()
.SelectMany(type => type.GetCustomAttributes(typeof(ServiceAttribute), true)
.Select(service => (((ServiceAttribute)service).Name, type)))
@ -74,7 +70,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm
KSession session = new KSession(context.Device.System.KernelContext);
if (_registeredServices.TryGetValue(name, out KPort port))
if (_registry.TryGetService(name, out KPort port))
{
KernelResult result = port.EnqueueIncomingSession(session.ServerSession);
@ -82,6 +78,15 @@ namespace Ryujinx.HLE.HOS.Services.Sm
{
throw new InvalidOperationException($"Session enqueue on port returned error \"{result}\".");
}
if (context.Process.HandleTable.GenerateHandle(session.ClientSession, out int handle) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
}
session.ClientSession.DecrementReferenceCount();
context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle);
}
else
{
@ -107,18 +112,18 @@ namespace Ryujinx.HLE.HOS.Services.Sm
throw new NotImplementedException(name);
}
}
if (context.Process.HandleTable.GenerateHandle(session.ClientSession, out int handle) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
}
session.ServerSession.DecrementReferenceCount();
session.ClientSession.DecrementReferenceCount();
context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle);
}
if (context.Process.HandleTable.GenerateHandle(session.ClientSession, out int handle) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
}
session.ServerSession.DecrementReferenceCount();
session.ClientSession.DecrementReferenceCount();
context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle);
return ResultCode.Success;
}
@ -179,7 +184,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm
KPort port = new KPort(context.Device.System.KernelContext, maxSessions, isLight, 0);
if (!_registeredServices.TryAdd(name, port))
if (!_registry.TryRegister(name, port))
{
return ResultCode.AlreadyRegistered;
}
@ -219,7 +224,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm
return ResultCode.InvalidName;
}
if (!_registeredServices.TryRemove(name, out _))
if (!_registry.Unregister(name))
{
return ResultCode.NotRegistered;
}

View File

@ -0,0 +1,49 @@
using Ryujinx.HLE.HOS.Kernel.Ipc;
using System.Collections.Concurrent;
using System.Threading;
namespace Ryujinx.HLE.HOS.Services.Sm
{
class SmRegistry
{
private readonly ConcurrentDictionary<string, KPort> _registeredServices;
private readonly AutoResetEvent _serviceRegistrationEvent;
public SmRegistry()
{
_registeredServices = new ConcurrentDictionary<string, KPort>();
_serviceRegistrationEvent = new AutoResetEvent(false);
}
public bool TryGetService(string name, out KPort port)
{
return _registeredServices.TryGetValue(name, out port);
}
public bool TryRegister(string name, KPort port)
{
if (_registeredServices.TryAdd(name, port))
{
_serviceRegistrationEvent.Set();
return true;
}
return false;
}
public bool Unregister(string name)
{
return _registeredServices.TryRemove(name, out _);
}
public bool IsServiceRegistered(string name)
{
return _registeredServices.TryGetValue(name, out _);
}
public void WaitForServiceRegistration()
{
_serviceRegistrationEvent.WaitOne();
}
}
}

View File

@ -164,6 +164,7 @@ namespace Ryujinx.Headless.SDL2
Device.Gpu.Renderer.RunLoop(() =>
{
Device.Gpu.SetGpuThread();
Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
Translator.IsReadyForTranslation.Set();

View File

@ -543,14 +543,14 @@ namespace Ryujinx.Input.HLE
Dispose(true);
}
public void UpdateRumble(ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> queue)
public void UpdateRumble(ConcurrentQueue<(VibrationValue, VibrationValue)> queue)
{
if (queue.TryDequeue(out (HidVibrationValue, HidVibrationValue) dualVibrationValue))
if (queue.TryDequeue(out (VibrationValue, VibrationValue) dualVibrationValue))
{
if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Rumble.EnableRumble)
{
HidVibrationValue leftVibrationValue = dualVibrationValue.Item1;
HidVibrationValue rightVibrationValue = dualVibrationValue.Item2;
VibrationValue leftVibrationValue = dualVibrationValue.Item1;
VibrationValue rightVibrationValue = dualVibrationValue.Item2;
float low = Math.Min(1f, (float)((rightVibrationValue.AmplitudeLow * 0.85 + rightVibrationValue.AmplitudeHigh * 0.15) * controllerConfig.Rumble.StrongRumble));
float high = Math.Min(1f, (float)((leftVibrationValue.AmplitudeLow * 0.15 + leftVibrationValue.AmplitudeHigh * 0.85) * controllerConfig.Rumble.WeakRumble));

View File

@ -48,7 +48,7 @@ namespace Ryujinx.Memory
{
_viewCompatible = flags.HasFlag(MemoryAllocationFlags.ViewCompatible);
_forceWindows4KBView = flags.HasFlag(MemoryAllocationFlags.ForceWindows4KBViewMapping);
_pointer = MemoryManagement.Reserve(size, _viewCompatible);
_pointer = MemoryManagement.Reserve(size, _viewCompatible, _forceWindows4KBView);
}
else
{
@ -404,7 +404,7 @@ namespace Ryujinx.Memory
}
else
{
MemoryManagement.Free(ptr);
MemoryManagement.Free(ptr, Size, _forceWindows4KBView);
}
foreach (MemoryBlock viewStorage in _viewStorages.Keys)

View File

@ -8,9 +8,7 @@ namespace Ryujinx.Memory
{
if (OperatingSystem.IsWindows())
{
IntPtr sizeNint = new IntPtr((long)size);
return MemoryManagementWindows.Allocate(sizeNint);
return MemoryManagementWindows.Allocate((IntPtr)size);
}
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
@ -22,13 +20,11 @@ namespace Ryujinx.Memory
}
}
public static IntPtr Reserve(ulong size, bool viewCompatible)
public static IntPtr Reserve(ulong size, bool viewCompatible, bool force4KBMap)
{
if (OperatingSystem.IsWindows())
{
IntPtr sizeNint = new IntPtr((long)size);
return MemoryManagementWindows.Reserve(sizeNint, viewCompatible);
return MemoryManagementWindows.Reserve((IntPtr)size, viewCompatible, force4KBMap);
}
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
@ -44,9 +40,7 @@ namespace Ryujinx.Memory
{
if (OperatingSystem.IsWindows())
{
IntPtr sizeNint = new IntPtr((long)size);
return MemoryManagementWindows.Commit(address, sizeNint);
return MemoryManagementWindows.Commit(address, (IntPtr)size);
}
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
@ -62,9 +56,7 @@ namespace Ryujinx.Memory
{
if (OperatingSystem.IsWindows())
{
IntPtr sizeNint = new IntPtr((long)size);
return MemoryManagementWindows.Decommit(address, sizeNint);
return MemoryManagementWindows.Decommit(address, (IntPtr)size);
}
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
@ -80,15 +72,13 @@ namespace Ryujinx.Memory
{
if (OperatingSystem.IsWindows())
{
IntPtr sizeNint = new IntPtr((long)size);
if (force4KBMap)
{
MemoryManagementWindows.MapView4KB(sharedMemory, srcOffset, address, sizeNint);
MemoryManagementWindows.MapView4KB(sharedMemory, srcOffset, address, (IntPtr)size);
}
else
{
MemoryManagementWindows.MapView(sharedMemory, srcOffset, address, sizeNint);
MemoryManagementWindows.MapView(sharedMemory, srcOffset, address, (IntPtr)size);
}
}
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
@ -105,15 +95,13 @@ namespace Ryujinx.Memory
{
if (OperatingSystem.IsWindows())
{
IntPtr sizeNint = new IntPtr((long)size);
if (force4KBMap)
{
MemoryManagementWindows.UnmapView4KB(address, sizeNint);
MemoryManagementWindows.UnmapView4KB(address, (IntPtr)size);
}
else
{
MemoryManagementWindows.UnmapView(sharedMemory, address, sizeNint);
MemoryManagementWindows.UnmapView(sharedMemory, address, (IntPtr)size);
}
}
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
@ -132,15 +120,13 @@ namespace Ryujinx.Memory
if (OperatingSystem.IsWindows())
{
IntPtr sizeNint = new IntPtr((long)size);
if (forView && force4KBMap)
{
result = MemoryManagementWindows.Reprotect4KB(address, sizeNint, permission, forView);
result = MemoryManagementWindows.Reprotect4KB(address, (IntPtr)size, permission, forView);
}
else
{
result = MemoryManagementWindows.Reprotect(address, sizeNint, permission, forView);
result = MemoryManagementWindows.Reprotect(address, (IntPtr)size, permission, forView);
}
}
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
@ -158,11 +144,11 @@ namespace Ryujinx.Memory
}
}
public static bool Free(IntPtr address)
public static bool Free(IntPtr address, ulong size, bool force4KBMap)
{
if (OperatingSystem.IsWindows())
{
return MemoryManagementWindows.Free(address);
return MemoryManagementWindows.Free(address, (IntPtr)size, force4KBMap);
}
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
@ -178,9 +164,7 @@ namespace Ryujinx.Memory
{
if (OperatingSystem.IsWindows())
{
IntPtr sizeNint = new IntPtr((long)size);
return MemoryManagementWindows.CreateSharedMemory(sizeNint, reserve);
return MemoryManagementWindows.CreateSharedMemory((IntPtr)size, reserve);
}
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{

View File

@ -7,21 +7,27 @@ namespace Ryujinx.Memory
[SupportedOSPlatform("windows")]
static class MemoryManagementWindows
{
private const int PageSize = 0x1000;
public const int PageSize = 0x1000;
private static readonly PlaceholderManager _placeholders = new PlaceholderManager();
private static readonly PlaceholderManager4KB _placeholders4KB = new PlaceholderManager4KB();
public static IntPtr Allocate(IntPtr size)
{
return AllocateInternal(size, AllocationType.Reserve | AllocationType.Commit);
}
public static IntPtr Reserve(IntPtr size, bool viewCompatible)
public static IntPtr Reserve(IntPtr size, bool viewCompatible, bool force4KBMap)
{
if (viewCompatible)
{
IntPtr baseAddress = AllocateInternal2(size, AllocationType.Reserve | AllocationType.ReservePlaceholder);
_placeholders.ReserveRange((ulong)baseAddress, (ulong)size);
if (!force4KBMap)
{
_placeholders.ReserveRange((ulong)baseAddress, (ulong)size);
}
return baseAddress;
}
@ -69,6 +75,8 @@ namespace Ryujinx.Memory
public static void MapView4KB(IntPtr sharedMemory, ulong srcOffset, IntPtr location, IntPtr size)
{
_placeholders4KB.UnmapAndMarkRangeAsMapped(location, size);
ulong uaddress = (ulong)location;
ulong usize = (ulong)size;
IntPtr endLocation = (IntPtr)(uaddress + usize);
@ -105,20 +113,7 @@ namespace Ryujinx.Memory
public static void UnmapView4KB(IntPtr location, IntPtr size)
{
ulong uaddress = (ulong)location;
ulong usize = (ulong)size;
IntPtr endLocation = (IntPtr)(uaddress + usize);
while (location != endLocation)
{
bool result = WindowsApi.UnmapViewOfFile2(WindowsApi.CurrentProcessHandle, location, 2);
if (!result)
{
throw new WindowsApiException("UnmapViewOfFile2");
}
location += PageSize;
}
_placeholders4KB.UnmapView(location, size);
}
public static bool Reprotect(IntPtr address, IntPtr size, MemoryPermission permission, bool forView)
@ -151,8 +146,17 @@ namespace Ryujinx.Memory
return true;
}
public static bool Free(IntPtr address)
public static bool Free(IntPtr address, IntPtr size, bool force4KBMap)
{
if (force4KBMap)
{
_placeholders4KB.UnmapRange(address, size);
}
else
{
_placeholders.UnmapView(IntPtr.Zero, address, size);
}
return WindowsApi.VirtualFree(address, IntPtr.Zero, AllocationType.Release);
}

View File

@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
using System.Runtime.Versioning;
using System.Threading;
namespace Ryujinx.Memory.WindowsShared
@ -7,6 +8,7 @@ namespace Ryujinx.Memory.WindowsShared
/// <summary>
/// Windows memory placeholder manager.
/// </summary>
[SupportedOSPlatform("windows")]
class PlaceholderManager
{
private const ulong MinimumPageSize = 0x1000;
@ -203,7 +205,7 @@ namespace Ryujinx.Memory.WindowsShared
ulong endAddress = startAddress + unmapSize;
var overlaps = Array.Empty<IntervalTreeNode<ulong, ulong>>();
int count = 0;
int count;
lock (_mappings)
{
@ -226,8 +228,11 @@ namespace Ryujinx.Memory.WindowsShared
ulong overlapEnd = overlap.End;
ulong overlapValue = overlap.Value;
_mappings.Remove(overlap);
_mappings.Add(overlapStart, overlapEnd, ulong.MaxValue);
lock (_mappings)
{
_mappings.Remove(overlap);
_mappings.Add(overlapStart, overlapEnd, ulong.MaxValue);
}
bool overlapStartsBefore = overlapStart < startAddress;
bool overlapEndsAfter = overlapEnd > endAddress;
@ -364,7 +369,7 @@ namespace Ryujinx.Memory.WindowsShared
ulong endAddress = reprotectAddress + reprotectSize;
var overlaps = Array.Empty<IntervalTreeNode<ulong, ulong>>();
int count = 0;
int count;
lock (_mappings)
{
@ -469,14 +474,12 @@ namespace Ryujinx.Memory.WindowsShared
{
ulong endAddress = address + size;
var overlaps = Array.Empty<IntervalTreeNode<ulong, MemoryPermission>>();
int count = 0;
int count;
lock (_protections)
{
count = _protections.Get(address, endAddress, ref overlaps);
Debug.Assert(count > 0);
if (count == 1 &&
overlaps[0].Start <= address &&
overlaps[0].End >= endAddress &&
@ -536,7 +539,7 @@ namespace Ryujinx.Memory.WindowsShared
{
ulong endAddress = address + size;
var overlaps = Array.Empty<IntervalTreeNode<ulong, MemoryPermission>>();
int count = 0;
int count;
lock (_protections)
{
@ -574,7 +577,7 @@ namespace Ryujinx.Memory.WindowsShared
{
ulong endAddress = address + size;
var overlaps = Array.Empty<IntervalTreeNode<ulong, MemoryPermission>>();
int count = 0;
int count;
lock (_protections)
{

View File

@ -0,0 +1,170 @@
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Memory.WindowsShared
{
/// <summary>
/// Windows 4KB memory placeholder manager.
/// </summary>
[SupportedOSPlatform("windows")]
class PlaceholderManager4KB
{
private const int PageSize = MemoryManagementWindows.PageSize;
private readonly IntervalTree<ulong, byte> _mappings;
/// <summary>
/// Creates a new instance of the Windows 4KB memory placeholder manager.
/// </summary>
public PlaceholderManager4KB()
{
_mappings = new IntervalTree<ulong, byte>();
}
/// <summary>
/// Unmaps the specified range of memory and marks it as mapped internally.
/// </summary>
/// <remarks>
/// Since this marks the range as mapped, the expectation is that the range will be mapped after calling this method.
/// </remarks>
/// <param name="location">Memory address to unmap and mark as mapped</param>
/// <param name="size">Size of the range in bytes</param>
public void UnmapAndMarkRangeAsMapped(IntPtr location, IntPtr size)
{
ulong startAddress = (ulong)location;
ulong unmapSize = (ulong)size;
ulong endAddress = startAddress + unmapSize;
var overlaps = Array.Empty<IntervalTreeNode<ulong, byte>>();
int count = 0;
lock (_mappings)
{
count = _mappings.Get(startAddress, endAddress, ref overlaps);
}
for (int index = 0; index < count; index++)
{
var overlap = overlaps[index];
// Tree operations might modify the node start/end values, so save a copy before we modify the tree.
ulong overlapStart = overlap.Start;
ulong overlapEnd = overlap.End;
ulong overlapValue = overlap.Value;
_mappings.Remove(overlap);
ulong unmapStart = Math.Max(overlapStart, startAddress);
ulong unmapEnd = Math.Min(overlapEnd, endAddress);
if (overlapStart < startAddress)
{
startAddress = overlapStart;
}
if (overlapEnd > endAddress)
{
endAddress = overlapEnd;
}
ulong currentAddress = unmapStart;
while (currentAddress < unmapEnd)
{
WindowsApi.UnmapViewOfFile2(WindowsApi.CurrentProcessHandle, (IntPtr)currentAddress, 2);
currentAddress += PageSize;
}
}
_mappings.Add(startAddress, endAddress, 0);
}
/// <summary>
/// Unmaps views at the specified memory range.
/// </summary>
/// <param name="location">Address of the range</param>
/// <param name="size">Size of the range in bytes</param>
public void UnmapView(IntPtr location, IntPtr size)
{
ulong startAddress = (ulong)location;
ulong unmapSize = (ulong)size;
ulong endAddress = startAddress + unmapSize;
var overlaps = Array.Empty<IntervalTreeNode<ulong, byte>>();
int count = 0;
lock (_mappings)
{
count = _mappings.Get(startAddress, endAddress, ref overlaps);
}
for (int index = 0; index < count; index++)
{
var overlap = overlaps[index];
// Tree operations might modify the node start/end values, so save a copy before we modify the tree.
ulong overlapStart = overlap.Start;
ulong overlapEnd = overlap.End;
_mappings.Remove(overlap);
if (overlapStart < startAddress)
{
_mappings.Add(overlapStart, startAddress, 0);
}
if (overlapEnd > endAddress)
{
_mappings.Add(endAddress, overlapEnd, 0);
}
ulong unmapStart = Math.Max(overlapStart, startAddress);
ulong unmapEnd = Math.Min(overlapEnd, endAddress);
ulong currentAddress = unmapStart;
while (currentAddress < unmapEnd)
{
WindowsApi.UnmapViewOfFile2(WindowsApi.CurrentProcessHandle, (IntPtr)currentAddress, 2);
currentAddress += PageSize;
}
}
}
/// <summary>
/// Unmaps mapped memory at a given range.
/// </summary>
/// <param name="location">Address of the range</param>
/// <param name="size">Size of the range in bytes</param>
public void UnmapRange(IntPtr location, IntPtr size)
{
ulong startAddress = (ulong)location;
ulong unmapSize = (ulong)size;
ulong endAddress = startAddress + unmapSize;
var overlaps = Array.Empty<IntervalTreeNode<ulong, byte>>();
int count = 0;
lock (_mappings)
{
count = _mappings.Get(startAddress, endAddress, ref overlaps);
}
for (int index = 0; index < count; index++)
{
var overlap = overlaps[index];
// Tree operations might modify the node start/end values, so save a copy before we modify the tree.
ulong unmapStart = Math.Max(overlap.Start, startAddress);
ulong unmapEnd = Math.Min(overlap.End, endAddress);
_mappings.Remove(overlap);
ulong currentAddress = unmapStart;
while (currentAddress < unmapEnd)
{
WindowsApi.UnmapViewOfFile2(WindowsApi.CurrentProcessHandle, (IntPtr)currentAddress, 2);
currentAddress += PageSize;
}
}
}
}
}

View File

@ -32,8 +32,20 @@ namespace Ryujinx
[DllImport("libX11")]
private extern static int XInitThreads();
[DllImport("user32.dll", SetLastError = true)]
public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type);
private const uint MB_ICONWARNING = 0x30;
static void Main(string[] args)
{
{
Version = ReleaseInformations.GetVersion();
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
{
MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MB_ICONWARNING);
}
// Parse Arguments.
string launchPathArg = null;
string baseDirPathArg = null;
@ -82,8 +94,6 @@ namespace Ryujinx
// Delete backup files after updating.
Task.Run(Updater.CleanupUpdate);
Version = ReleaseInformations.GetVersion();
Console.Title = $"Ryujinx Console {Version}";
// NOTE: GTK3 doesn't init X11 in a multi threaded way.