Compare commits

..

9 Commits

Author SHA1 Message Date
riperiperi
ece36b274d GAL: Send all buffer assignments at once rather than individually (#3881)
* GAL: Send all buffer assignments at once rather than individually

The `(int first, BufferRange[] ranges)` method call has very significant performance implications when the bindings are spread out, which they generally always are in Vulkan. This change makes it so that these methods are only called a maximum of one time per draw.

Significantly improves GPU thread performance in Pokemon Scarlet/Violet.

* Address Feedback

Removed SetUniformBuffers(int first, ReadOnlySpan<BufferRange> buffers)
2022-11-24 07:50:59 +00:00
riperiperi
f3cc2e5703 GPU: Access non-prefetch command buffers directly (#3882)
* GPU: Access non-prefetch command buffers directly

Saves allocating new arrays for them constantly - they can be quite small so it can be very wasteful. About 0.4% of GPU thread in SMO, but was a bit higher in S/V when I checked.

Assumes that non-prefetch command buffers won't be randomly clobbered before they finish executing, though that's probably a safe bet.

* Small change while I'm here

* Address feedback
2022-11-24 01:56:55 +00:00
riperiperi
5a39d3c4a1 GPU: Relax locking on Buffer Cache (#3883)
I did this on ncbuffer2 when we were using it for LDN 3, but I noticed that it can apply to the current buffer manager too, and it's an easy performance win.

The only buffer access that can come from another thread is the overlap search for buffers that have been unmapped. Everything else, including modifications, come from the main GPU thread. That means we only need to lock the range list when it's being modified, as that's the only time where we'll cause a race with the unmapped handler.

This has a significant performance improvements in situations where FIFO is high, like the other two PRs. Joined together they give a nice boost (73.6 master -> 79 -> 83 fps in SMO).
2022-11-24 01:41:16 +00:00
dependabot[bot]
cc51a03af9 nuget: bump Avalonia from 0.10.15 to 0.10.18 (#3817)
Bumps [Avalonia](https://github.com/AvaloniaUI/Avalonia) from 0.10.15 to 0.10.18.
- [Release notes](https://github.com/AvaloniaUI/Avalonia/releases)
- [Commits](https://github.com/AvaloniaUI/Avalonia/compare/0.10.15...0.10.18)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-24 01:26:53 +00:00
Ac_K
567c64e149 ava: Fix JsonSerializer warnings (#3884)
Since we move to .NET7, JsonSerializer now needs to have explicit options as arguments, which leads to some warnings in Avalonia project. This is fixed by using our `JsonHelper` class.
2022-11-23 17:55:26 +00:00
Alex Barney
36f00985d3 Update to LibHac 0.17.0 (#3878)
* Update to LibHac 0.17.0

* Don't clear SD card saves when starting the emulator

This was an old workaround for errors that happened when a user's SD card encryption seed changed. SD card saves have been unencrypted for over a year, so we should be fine to remove the workaround.
2022-11-23 18:32:35 +01:00
WilliamWsyHK
748d87adcc Stub IFriendService: 1 (Cancel) (#3841)
* Add friend/Cancel. Closes #3824

* Update according to review comments.

* Add comment base on request

* Update Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2022-11-23 16:25:49 +01:00
Emmanuel Hansen
0fd47ff490 remove redundant logs (#3877) 2022-11-23 11:28:46 +01:00
gdkchan
f088c3d344 Do not update shader state for DrawTextures (#3876) 2022-11-21 18:16:00 +01:00
25 changed files with 170 additions and 147 deletions

View File

@@ -1,5 +1,6 @@
using Ryujinx.Ava.Ui.ViewModels; using Ryujinx.Ava.Ui.ViewModels;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Utilities;
using Ryujinx.Ui.Common.Configuration; using Ryujinx.Ui.Common.Configuration;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@@ -93,7 +94,7 @@ namespace Ryujinx.Ava.Common.Locale
return; return;
} }
var strings = JsonSerializer.Deserialize<Dictionary<string, string>>(languageJson); var strings = JsonHelper.Deserialize<Dictionary<string, string>>(languageJson);
foreach (var item in strings) foreach (var item in strings)
{ {

View File

@@ -18,7 +18,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.15" /> <PackageReference Include="Avalonia" Version="0.10.18" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.15" /> <PackageReference Include="Avalonia.Desktop" Version="0.10.15" />
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.15" /> <PackageReference Include="Avalonia.Diagnostics" Version="0.10.15" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.15" /> <PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.15" />

View File

@@ -8,6 +8,7 @@ using Ryujinx.Ava.Ui.Models;
using Ryujinx.Ava.Ui.Windows; using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Utilities;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@@ -189,7 +190,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
amiiboJsonString = File.ReadAllText(_amiiboJsonPath); amiiboJsonString = File.ReadAllText(_amiiboJsonPath);
if (await NeedsUpdate(JsonSerializer.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).LastUpdated)) if (await NeedsUpdate(JsonHelper.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).LastUpdated))
{ {
amiiboJsonString = await DownloadAmiiboJson(); amiiboJsonString = await DownloadAmiiboJson();
} }
@@ -206,7 +207,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
} }
} }
_amiiboList = JsonSerializer.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).Amiibo; _amiiboList = JsonHelper.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).Amiibo;
_amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList(); _amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
ParseAmiiboData(); ParseAmiiboData();

View File

@@ -460,8 +460,6 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
using IGamepad gamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id); using IGamepad gamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id);
Logger.Info?.Print(LogClass.Configuration, $"{GetShortGamepadName(gamepad.Name)} has been connected with ID: {gamepad.Id}");
if (gamepad != null) if (gamepad != null)
{ {
Devices.Add((DeviceType.Keyboard, id, $"{GetShortGamepadName(gamepad.Name)}")); Devices.Add((DeviceType.Keyboard, id, $"{GetShortGamepadName(gamepad.Name)}"));
@@ -472,8 +470,6 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
using IGamepad gamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id); using IGamepad gamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id);
Logger.Info?.Print(LogClass.Configuration, $"{GetShortGamepadName(gamepad.Name)} has been connected with ID: {gamepad.Id}");
if (gamepad != null) if (gamepad != null)
{ {
if (Devices.Any(controller => GetShortGamepadId(controller.Id) == GetShortGamepadId(gamepad.Id))) if (Devices.Any(controller => GetShortGamepadId(controller.Id) == GetShortGamepadId(gamepad.Id)))

View File

@@ -0,0 +1,14 @@
namespace Ryujinx.Graphics.GAL
{
public struct BufferAssignment
{
public readonly int Binding;
public readonly BufferRange Range;
public BufferAssignment(int binding, BufferRange range)
{
Binding = binding;
Range = range;
}
}
}

View File

@@ -86,12 +86,12 @@ namespace Ryujinx.Graphics.GAL
void SetStencilTest(StencilTestDescriptor stencilTest); void SetStencilTest(StencilTestDescriptor stencilTest);
void SetStorageBuffers(int first, ReadOnlySpan<BufferRange> buffers); void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers);
void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler); void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler);
void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers); void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers);
void SetUniformBuffers(int first, ReadOnlySpan<BufferRange> buffers); void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers);
void SetUserClipDistance(int index, bool enableClip); void SetUserClipDistance(int index, bool enableClip);

View File

@@ -142,6 +142,30 @@ namespace Ryujinx.Graphics.GAL.Multithreading
return ranges; return ranges;
} }
internal Span<BufferAssignment> MapBufferRanges(Span<BufferAssignment> ranges)
{
// Rewrite the buffer ranges to point to the mapped handles.
lock (_bufferMap)
{
for (int i = 0; i < ranges.Length; i++)
{
ref BufferAssignment assignment = ref ranges[i];
BufferRange range = assignment.Range;
BufferHandle result;
if (!_bufferMap.TryGetValue(range.Handle, out result))
{
result = BufferHandle.Null;
}
assignment = new BufferAssignment(ranges[i].Binding, new BufferRange(result, range.Offset, range.Size));
}
}
return ranges;
}
internal Span<VertexBufferDescriptor> MapBufferRanges(Span<VertexBufferDescriptor> ranges) internal Span<VertexBufferDescriptor> MapBufferRanges(Span<VertexBufferDescriptor> ranges)
{ {
// Rewrite the buffer ranges to point to the mapped handles. // Rewrite the buffer ranges to point to the mapped handles.

View File

@@ -6,19 +6,17 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
struct SetStorageBuffersCommand : IGALCommand struct SetStorageBuffersCommand : IGALCommand
{ {
public CommandType CommandType => CommandType.SetStorageBuffers; public CommandType CommandType => CommandType.SetStorageBuffers;
private int _first; private SpanRef<BufferAssignment> _buffers;
private SpanRef<BufferRange> _buffers;
public void Set(int first, SpanRef<BufferRange> buffers) public void Set(SpanRef<BufferAssignment> buffers)
{ {
_first = first;
_buffers = buffers; _buffers = buffers;
} }
public static void Run(ref SetStorageBuffersCommand command, ThreadedRenderer threaded, IRenderer renderer) public static void Run(ref SetStorageBuffersCommand command, ThreadedRenderer threaded, IRenderer renderer)
{ {
Span<BufferRange> buffers = command._buffers.Get(threaded); Span<BufferAssignment> buffers = command._buffers.Get(threaded);
renderer.Pipeline.SetStorageBuffers(command._first, threaded.Buffers.MapBufferRanges(buffers)); renderer.Pipeline.SetStorageBuffers(threaded.Buffers.MapBufferRanges(buffers));
command._buffers.Dispose(threaded); command._buffers.Dispose(threaded);
} }
} }

View File

@@ -6,19 +6,17 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
struct SetUniformBuffersCommand : IGALCommand struct SetUniformBuffersCommand : IGALCommand
{ {
public CommandType CommandType => CommandType.SetUniformBuffers; public CommandType CommandType => CommandType.SetUniformBuffers;
private int _first; private SpanRef<BufferAssignment> _buffers;
private SpanRef<BufferRange> _buffers;
public void Set(int first, SpanRef<BufferRange> buffers) public void Set(SpanRef<BufferAssignment> buffers)
{ {
_first = first;
_buffers = buffers; _buffers = buffers;
} }
public static void Run(ref SetUniformBuffersCommand command, ThreadedRenderer threaded, IRenderer renderer) public static void Run(ref SetUniformBuffersCommand command, ThreadedRenderer threaded, IRenderer renderer)
{ {
Span<BufferRange> buffers = command._buffers.Get(threaded); Span<BufferAssignment> buffers = command._buffers.Get(threaded);
renderer.Pipeline.SetUniformBuffers(command._first, threaded.Buffers.MapBufferRanges(buffers)); renderer.Pipeline.SetUniformBuffers(threaded.Buffers.MapBufferRanges(buffers));
command._buffers.Dispose(threaded); command._buffers.Dispose(threaded);
} }
} }

View File

@@ -275,9 +275,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public void SetStorageBuffers(int first, ReadOnlySpan<BufferRange> buffers) public void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers)
{ {
_renderer.New<SetStorageBuffersCommand>().Set(first, _renderer.CopySpan(buffers)); _renderer.New<SetStorageBuffersCommand>().Set(_renderer.CopySpan(buffers));
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
@@ -293,9 +293,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public void SetUniformBuffers(int first, ReadOnlySpan<BufferRange> buffers) public void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers)
{ {
_renderer.New<SetUniformBuffersCommand>().Set(first, _renderer.CopySpan(buffers)); _renderer.New<SetUniformBuffersCommand>().Set(_renderer.CopySpan(buffers));
_renderer.QueueCommand(); _renderer.QueueCommand();
} }

View File

@@ -51,16 +51,35 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
/// </summary> /// </summary>
public uint EntryCount; public uint EntryCount;
/// <summary>
/// Get the entries for the command buffer from memory.
/// </summary>
/// <param name="memoryManager">The memory manager used to fetch the data</param>
/// <param name="flush">If true, flushes potential GPU written data before reading the command buffer</param>
/// <returns>The fetched data</returns>
private ReadOnlySpan<int> GetWords(MemoryManager memoryManager, bool flush)
{
return MemoryMarshal.Cast<byte, int>(memoryManager.GetSpan(EntryAddress, (int)EntryCount * 4, flush));
}
/// <summary>
/// Prefetch the command buffer.
/// </summary>
/// <param name="memoryManager">The memory manager used to fetch the data</param>
public void Prefetch(MemoryManager memoryManager)
{
Words = GetWords(memoryManager, true).ToArray();
}
/// <summary> /// <summary>
/// Fetch the command buffer. /// Fetch the command buffer.
/// </summary> /// </summary>
/// <param name="memoryManager">The memory manager used to fetch the data</param>
/// <param name="flush">If true, flushes potential GPU written data before reading the command buffer</param> /// <param name="flush">If true, flushes potential GPU written data before reading the command buffer</param>
public void Fetch(MemoryManager memoryManager, bool flush = true) /// <returns>The command buffer words</returns>
public ReadOnlySpan<int> Fetch(MemoryManager memoryManager, bool flush)
{ {
if (Words == null) return Words ?? GetWords(memoryManager, flush);
{
Words = MemoryMarshal.Cast<byte, int>(memoryManager.GetSpan(EntryAddress, (int)EntryCount * 4, flush)).ToArray();
}
} }
} }
@@ -158,7 +177,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
if (beforeBarrier && commandBuffer.Type == CommandBufferType.Prefetch) if (beforeBarrier && commandBuffer.Type == CommandBufferType.Prefetch)
{ {
commandBuffer.Fetch(processor.MemoryManager); commandBuffer.Prefetch(processor.MemoryManager);
} }
if (commandBuffer.Type == CommandBufferType.NoPrefetch) if (commandBuffer.Type == CommandBufferType.NoPrefetch)
@@ -199,7 +218,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
} }
_currentCommandBuffer = entry; _currentCommandBuffer = entry;
_currentCommandBuffer.Fetch(entry.Processor.MemoryManager, flushCommandBuffer); ReadOnlySpan<int> words = entry.Fetch(entry.Processor.MemoryManager, flushCommandBuffer);
// If we are changing the current channel, // If we are changing the current channel,
// we need to force all the host state to be updated. // we need to force all the host state to be updated.
@@ -209,7 +228,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
entry.Processor.ForceAllDirty(); entry.Processor.ForceAllDirty();
} }
entry.Processor.Process(entry.EntryAddress, _currentCommandBuffer.Words); entry.Processor.Process(entry.EntryAddress, words);
} }
_interrupt = false; _interrupt = false;

View File

@@ -372,7 +372,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
float srcX1 = ((float)_state.State.DrawTextureDuDx / (1UL << 32)) * dstWidth + srcX0; float srcX1 = ((float)_state.State.DrawTextureDuDx / (1UL << 32)) * dstWidth + srcX0;
float srcY1 = ((float)_state.State.DrawTextureDvDy / (1UL << 32)) * dstHeight + srcY0; float srcY1 = ((float)_state.State.DrawTextureDvDy / (1UL << 32)) * dstHeight + srcY0;
engine.UpdateState(); engine.UpdateState(ulong.MaxValue & ~(1UL << StateUpdater.ShaderStateIndex));
_channel.TextureManager.UpdateRenderTargets();
int textureId = _state.State.DrawTextureTextureId; int textureId = _state.State.DrawTextureTextureId;
int samplerId = _state.State.DrawTextureSamplerId; int samplerId = _state.State.DrawTextureSamplerId;

View File

@@ -22,6 +22,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
private readonly GpuContext _context; private readonly GpuContext _context;
private readonly PhysicalMemory _physicalMemory; private readonly PhysicalMemory _physicalMemory;
/// <remarks>
/// Only modified from the GPU thread. Must lock for add/remove.
/// Must lock for any access from other threads.
/// </remarks>
private readonly RangeList<Buffer> _buffers; private readonly RangeList<Buffer> _buffers;
private Buffer[] _bufferOverlaps; private Buffer[] _bufferOverlaps;
@@ -200,12 +204,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="size">Size in bytes of the buffer</param> /// <param name="size">Size in bytes of the buffer</param>
private void CreateBufferAligned(ulong address, ulong size) private void CreateBufferAligned(ulong address, ulong size)
{ {
int overlapsCount; int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref _bufferOverlaps);
lock (_buffers)
{
overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref _bufferOverlaps);
}
if (overlapsCount != 0) if (overlapsCount != 0)
{ {
@@ -409,11 +408,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
Buffer buffer; Buffer buffer;
if (size != 0) if (size != 0)
{
lock (_buffers)
{ {
buffer = _buffers.FindFirstOverlap(address, size); buffer = _buffers.FindFirstOverlap(address, size);
}
buffer.SynchronizeMemory(address, size); buffer.SynchronizeMemory(address, size);
@@ -423,12 +419,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
} }
else else
{
lock (_buffers)
{ {
buffer = _buffers.FindFirstOverlap(address, 1); buffer = _buffers.FindFirstOverlap(address, 1);
} }
}
return buffer; return buffer;
} }
@@ -442,12 +435,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
if (size != 0) if (size != 0)
{ {
Buffer buffer; Buffer buffer = _buffers.FindFirstOverlap(address, size);
lock (_buffers)
{
buffer = _buffers.FindFirstOverlap(address, size);
}
buffer.SynchronizeMemory(address, size); buffer.SynchronizeMemory(address, size);
} }

View File

@@ -24,7 +24,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
private readonly VertexBuffer[] _vertexBuffers; private readonly VertexBuffer[] _vertexBuffers;
private readonly BufferBounds[] _transformFeedbackBuffers; private readonly BufferBounds[] _transformFeedbackBuffers;
private readonly List<BufferTextureBinding> _bufferTextures; private readonly List<BufferTextureBinding> _bufferTextures;
private readonly BufferRange[] _ranges; private readonly BufferAssignment[] _ranges;
/// <summary> /// <summary>
/// Holds shader stage buffer state and binding information. /// Holds shader stage buffer state and binding information.
@@ -134,7 +134,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
_bufferTextures = new List<BufferTextureBinding>(); _bufferTextures = new List<BufferTextureBinding>();
_ranges = new BufferRange[Constants.TotalGpUniformBuffers * Constants.ShaderStages]; _ranges = new BufferAssignment[Constants.TotalGpUniformBuffers * Constants.ShaderStages];
} }
@@ -618,10 +618,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void BindBuffers(BufferCache bufferCache, BuffersPerStage[] bindings, bool isStorage) private void BindBuffers(BufferCache bufferCache, BuffersPerStage[] bindings, bool isStorage)
{ {
int rangesFirst = 0;
int rangesCount = 0; int rangesCount = 0;
Span<BufferRange> ranges = _ranges; Span<BufferAssignment> ranges = _ranges;
for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++) for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++)
{ {
@@ -640,25 +639,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
? bufferCache.GetBufferRangeTillEnd(bounds.Address, bounds.Size, isWrite) ? bufferCache.GetBufferRangeTillEnd(bounds.Address, bounds.Size, isWrite)
: bufferCache.GetBufferRange(bounds.Address, bounds.Size); : bufferCache.GetBufferRange(bounds.Address, bounds.Size);
if (rangesCount == 0) ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
{
rangesFirst = bindingInfo.Binding;
}
else if (bindingInfo.Binding != rangesFirst + rangesCount)
{
SetHostBuffers(ranges, rangesFirst, rangesCount, isStorage);
rangesFirst = bindingInfo.Binding;
rangesCount = 0;
}
ranges[rangesCount++] = range;
} }
} }
} }
if (rangesCount != 0) if (rangesCount != 0)
{ {
SetHostBuffers(ranges, rangesFirst, rangesCount, isStorage); SetHostBuffers(ranges, rangesCount, isStorage);
} }
} }
@@ -671,10 +659,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void BindBuffers(BufferCache bufferCache, BuffersPerStage buffers, bool isStorage) private void BindBuffers(BufferCache bufferCache, BuffersPerStage buffers, bool isStorage)
{ {
int rangesFirst = 0;
int rangesCount = 0; int rangesCount = 0;
Span<BufferRange> ranges = _ranges; Span<BufferAssignment> ranges = _ranges;
for (int index = 0; index < buffers.Count; index++) for (int index = 0; index < buffers.Count; index++)
{ {
@@ -689,24 +676,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
? bufferCache.GetBufferRangeTillEnd(bounds.Address, bounds.Size, isWrite) ? bufferCache.GetBufferRangeTillEnd(bounds.Address, bounds.Size, isWrite)
: bufferCache.GetBufferRange(bounds.Address, bounds.Size); : bufferCache.GetBufferRange(bounds.Address, bounds.Size);
if (rangesCount == 0) ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
{
rangesFirst = bindingInfo.Binding;
}
else if (bindingInfo.Binding != rangesFirst + rangesCount)
{
SetHostBuffers(ranges, rangesFirst, rangesCount, isStorage);
rangesFirst = bindingInfo.Binding;
rangesCount = 0;
}
ranges[rangesCount++] = range;
} }
} }
if (rangesCount != 0) if (rangesCount != 0)
{ {
SetHostBuffers(ranges, rangesFirst, rangesCount, isStorage); SetHostBuffers(ranges, rangesCount, isStorage);
} }
} }
@@ -718,15 +694,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="count">Number of bindings</param> /// <param name="count">Number of bindings</param>
/// <param name="isStorage">Indicates if the buffers are storage or uniform buffers</param> /// <param name="isStorage">Indicates if the buffers are storage or uniform buffers</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetHostBuffers(ReadOnlySpan<BufferRange> ranges, int first, int count, bool isStorage) private void SetHostBuffers(ReadOnlySpan<BufferAssignment> ranges, int count, bool isStorage)
{ {
if (isStorage) if (isStorage)
{ {
_context.Renderer.Pipeline.SetStorageBuffers(first, ranges.Slice(0, count)); _context.Renderer.Pipeline.SetStorageBuffers(ranges.Slice(0, count));
} }
else else
{ {
_context.Renderer.Pipeline.SetUniformBuffers(first, ranges.Slice(0, count)); _context.Renderer.Pipeline.SetUniformBuffers(ranges.Slice(0, count));
} }
} }

View File

@@ -1296,9 +1296,9 @@ namespace Ryujinx.Graphics.OpenGL
_stencilFrontMask = stencilTest.FrontMask; _stencilFrontMask = stencilTest.FrontMask;
} }
public void SetStorageBuffers(int first, ReadOnlySpan<BufferRange> buffers) public void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers)
{ {
SetBuffers(first, buffers, isStorage: true); SetBuffers(buffers, isStorage: true);
} }
public void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler) public void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler)
@@ -1366,9 +1366,9 @@ namespace Ryujinx.Graphics.OpenGL
} }
} }
public void SetUniformBuffers(int first, ReadOnlySpan<BufferRange> buffers) public void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers)
{ {
SetBuffers(first, buffers, isStorage: false); SetBuffers(buffers, isStorage: false);
} }
public void SetUserClipDistance(int index, bool enableClip) public void SetUserClipDistance(int index, bool enableClip)
@@ -1460,21 +1460,22 @@ namespace Ryujinx.Graphics.OpenGL
GL.MemoryBarrier(MemoryBarrierFlags.TextureFetchBarrierBit); GL.MemoryBarrier(MemoryBarrierFlags.TextureFetchBarrierBit);
} }
private void SetBuffers(int first, ReadOnlySpan<BufferRange> buffers, bool isStorage) private void SetBuffers(ReadOnlySpan<BufferAssignment> buffers, bool isStorage)
{ {
BufferRangeTarget target = isStorage ? BufferRangeTarget.ShaderStorageBuffer : BufferRangeTarget.UniformBuffer; BufferRangeTarget target = isStorage ? BufferRangeTarget.ShaderStorageBuffer : BufferRangeTarget.UniformBuffer;
for (int index = 0; index < buffers.Length; index++) for (int index = 0; index < buffers.Length; index++)
{ {
BufferRange buffer = buffers[index]; BufferAssignment assignment = buffers[index];
BufferRange buffer = assignment.Range;
if (buffer.Handle == BufferHandle.Null) if (buffer.Handle == BufferHandle.Null)
{ {
GL.BindBufferRange(target, first + index, 0, IntPtr.Zero, 0); GL.BindBufferRange(target, assignment.Binding, 0, IntPtr.Zero, 0);
continue; continue;
} }
GL.BindBufferRange(target, first + index, buffer.Handle.ToInt32(), (IntPtr)buffer.Offset, buffer.Size); GL.BindBufferRange(target, assignment.Binding, buffer.Handle.ToInt32(), (IntPtr)buffer.Offset, buffer.Size);
} }
} }

View File

@@ -163,12 +163,13 @@ namespace Ryujinx.Graphics.Vulkan
SignalDirty(DirtyFlags.Image); SignalDirty(DirtyFlags.Image);
} }
public void SetStorageBuffers(CommandBuffer commandBuffer, int first, ReadOnlySpan<BufferRange> buffers) public void SetStorageBuffers(CommandBuffer commandBuffer, ReadOnlySpan<BufferAssignment> buffers)
{ {
for (int i = 0; i < buffers.Length; i++) for (int i = 0; i < buffers.Length; i++)
{ {
var buffer = buffers[i]; var assignment = buffers[i];
int index = first + i; var buffer = assignment.Range;
int index = assignment.Binding;
Auto<DisposableBuffer> vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false); Auto<DisposableBuffer> vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false);
ref Auto<DisposableBuffer> currentVkBuffer = ref _storageBufferRefs[index]; ref Auto<DisposableBuffer> currentVkBuffer = ref _storageBufferRefs[index];
@@ -243,12 +244,13 @@ namespace Ryujinx.Graphics.Vulkan
SignalDirty(DirtyFlags.Texture); SignalDirty(DirtyFlags.Texture);
} }
public void SetUniformBuffers(CommandBuffer commandBuffer, int first, ReadOnlySpan<BufferRange> buffers) public void SetUniformBuffers(CommandBuffer commandBuffer, ReadOnlySpan<BufferAssignment> buffers)
{ {
for (int i = 0; i < buffers.Length; i++) for (int i = 0; i < buffers.Length; i++)
{ {
var buffer = buffers[i]; var assignment = buffers[i];
int index = first + i; var buffer = assignment.Range;
int index = assignment.Binding;
Auto<DisposableBuffer> vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false); Auto<DisposableBuffer> vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false);
ref Auto<DisposableBuffer> currentVkBuffer = ref _uniformBufferRefs[index]; ref Auto<DisposableBuffer> currentVkBuffer = ref _uniformBufferRefs[index];

View File

@@ -177,7 +177,7 @@ namespace Ryujinx.Graphics.Vulkan
gd.BufferManager.SetData<float>(bufferHandle, 0, region); gd.BufferManager.SetData<float>(bufferHandle, 0, region);
_pipeline.SetUniformBuffers(1, stackalloc[] { new BufferRange(bufferHandle, 0, RegionBufferSize) }); _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, RegionBufferSize)) });
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1]; Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
@@ -240,7 +240,7 @@ namespace Ryujinx.Graphics.Vulkan
gd.BufferManager.SetData<float>(bufferHandle, 0, clearColor); gd.BufferManager.SetData<float>(bufferHandle, 0, clearColor);
_pipeline.SetUniformBuffers(1, stackalloc[] { new BufferRange(bufferHandle, 0, ClearColorBufferSize) }); _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, ClearColorBufferSize)) });
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1]; Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
@@ -302,7 +302,7 @@ namespace Ryujinx.Graphics.Vulkan
gd.BufferManager.SetData<float>(bufferHandle, 0, region); gd.BufferManager.SetData<float>(bufferHandle, 0, region);
pipeline.SetUniformBuffers(1, stackalloc[] { new BufferRange(bufferHandle, 0, RegionBufferSize) }); pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, RegionBufferSize)) });
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1]; Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
@@ -380,7 +380,7 @@ namespace Ryujinx.Graphics.Vulkan
_pipeline.SetCommandBuffer(cbs); _pipeline.SetCommandBuffer(cbs);
_pipeline.SetUniformBuffers(0, stackalloc[] { new BufferRange(bufferHandle, 0, ParamsBufferSize) }); _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(bufferHandle, 0, ParamsBufferSize)) });
Span<Auto<DisposableBuffer>> sbRanges = new Auto<DisposableBuffer>[2]; Span<Auto<DisposableBuffer>> sbRanges = new Auto<DisposableBuffer>[2];
@@ -571,7 +571,7 @@ namespace Ryujinx.Graphics.Vulkan
int conversionType = srcIsMs ? src.Info.BytesPerPixel : -src.Info.BytesPerPixel; int conversionType = srcIsMs ? src.Info.BytesPerPixel : -src.Info.BytesPerPixel;
_pipeline.Specialize(conversionType); _pipeline.Specialize(conversionType);
_pipeline.SetUniformBuffers(0, stackalloc[] { new BufferRange(bufferHandle, 0, ParamsBufferSize) }); _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(bufferHandle, 0, ParamsBufferSize)) });
if (src.Info.Target == Target.Texture2DMultisampleArray || if (src.Info.Target == Target.Texture2DMultisampleArray ||
dst.Info.Target == Target.Texture2DMultisampleArray) dst.Info.Target == Target.Texture2DMultisampleArray)
@@ -776,7 +776,7 @@ namespace Ryujinx.Graphics.Vulkan
srcIndirectBufferOffset, srcIndirectBufferOffset,
indirectDataSize); indirectDataSize);
_pipeline.SetUniformBuffers(0, stackalloc[] { drawCountBufferAligned }); _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, drawCountBufferAligned) });
_pipeline.SetStorageBuffers(1, new[] { srcIndirectBuffer.GetBuffer(), dstIndirectBuffer.GetBuffer(), patternBuffer.GetBuffer() }); _pipeline.SetStorageBuffers(1, new[] { srcIndirectBuffer.GetBuffer(), dstIndirectBuffer.GetBuffer(), patternBuffer.GetBuffer() });
_pipeline.SetProgram(_programConvertIndirectData); _pipeline.SetProgram(_programConvertIndirectData);
@@ -804,7 +804,7 @@ namespace Ryujinx.Graphics.Vulkan
0, 0,
convertedCount * outputIndexSize); convertedCount * outputIndexSize);
_pipeline.SetUniformBuffers(0, stackalloc[] { new BufferRange(patternBufferHandle, 0, ParamsBufferSize) }); _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(patternBufferHandle, 0, ParamsBufferSize)) });
_pipeline.SetStorageBuffers(1, new[] { srcIndexBuffer.GetBuffer(), dstIndexBuffer.GetBuffer() }); _pipeline.SetStorageBuffers(1, new[] { srcIndexBuffer.GetBuffer(), dstIndexBuffer.GetBuffer() });
_pipeline.SetProgram(_programConvertIndexBuffer); _pipeline.SetProgram(_programConvertIndexBuffer);

View File

@@ -973,9 +973,9 @@ namespace Ryujinx.Graphics.Vulkan
SignalStateChange(); SignalStateChange();
} }
public void SetStorageBuffers(int first, ReadOnlySpan<BufferRange> buffers) public void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers)
{ {
_descriptorSetUpdater.SetStorageBuffers(CommandBuffer, first, buffers); _descriptorSetUpdater.SetStorageBuffers(CommandBuffer, buffers);
} }
public void SetStorageBuffers(int first, ReadOnlySpan<Auto<DisposableBuffer>> buffers) public void SetStorageBuffers(int first, ReadOnlySpan<Auto<DisposableBuffer>> buffers)
@@ -1013,9 +1013,9 @@ namespace Ryujinx.Graphics.Vulkan
} }
} }
public void SetUniformBuffers(int first, ReadOnlySpan<BufferRange> buffers) public void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers)
{ {
_descriptorSetUpdater.SetUniformBuffers(CommandBuffer, first, buffers); _descriptorSetUpdater.SetUniformBuffers(CommandBuffer, buffers);
} }
public void SetUserClipDistance(int index, bool enableClip) public void SetUserClipDistance(int index, bool enableClip)

View File

@@ -172,9 +172,11 @@ namespace Ryujinx.HLE.FileSystem
fsServerClient = horizon.CreatePrivilegedHorizonClient(); fsServerClient = horizon.CreatePrivilegedHorizonClient();
var fsServer = new FileSystemServer(fsServerClient); var fsServer = new FileSystemServer(fsServerClient);
DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet, fsServer); RandomDataGenerator randomGenerator = buffer => Random.Shared.NextBytes(buffer);
// Use our own encrypted fs creator that always uses all-zero keys DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet, fsServer, randomGenerator);
// Use our own encrypted fs creator that doesn't actually do any encryption
fsServerObjects.FsCreators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator(); fsServerObjects.FsCreators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator();
GameCard = fsServerObjects.GameCard; GameCard = fsServerObjects.GameCard;
@@ -186,7 +188,8 @@ namespace Ryujinx.HLE.FileSystem
{ {
DeviceOperator = fsServerObjects.DeviceOperator, DeviceOperator = fsServerObjects.DeviceOperator,
ExternalKeySet = KeySet.ExternalKeySet, ExternalKeySet = KeySet.ExternalKeySet,
FsCreators = fsServerObjects.FsCreators FsCreators = fsServerObjects.FsCreators,
RandomGenerator = randomGenerator
}; };
FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, fsServerConfig); FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, fsServerConfig);

View File

@@ -850,7 +850,7 @@ namespace Ryujinx.HLE.HOS
for (int i = 0; i < programCount; i++) for (int i = 0; i < programCount; i++)
{ {
mapInfo[i].ProgramId = new ProgramId(applicationId + (uint)i); mapInfo[i].ProgramId = new ProgramId(applicationId + (uint)i);
mapInfo[i].MainProgramId = new ProgramId(applicationId); mapInfo[i].MainProgramId = new ApplicationId(applicationId);
mapInfo[i].ProgramIndex = (byte)i; mapInfo[i].ProgramIndex = (byte)i;
} }

View File

@@ -60,8 +60,6 @@ namespace Ryujinx.HLE.HOS
virtualFileSystem.InitializeFsServer(Server, out var fsClient); virtualFileSystem.InitializeFsServer(Server, out var fsClient);
FsClient = fsClient; FsClient = fsClient;
CleanSdCardDirectory();
} }
public void InitializeSystemClients() public void InitializeSystemClients()
@@ -78,27 +76,6 @@ namespace Ryujinx.HLE.HOS
ApplicationClient = Server.CreateHorizonClient(new ProgramLocation(programId, StorageId.BuiltInUser), npdm.FsAccessControlData, npdm.FsAccessControlDescriptor); ApplicationClient = Server.CreateHorizonClient(new ProgramLocation(programId, StorageId.BuiltInUser), npdm.FsAccessControlData, npdm.FsAccessControlDescriptor);
} }
// This function was added to avoid errors that come from a user's keys or SD encryption seed changing.
// Catching these errors and recreating the file ended up not working because of the different ways
// applications respond to a file suddenly containing all zeros or having a length of zero.
// Clearing the SD card save directory was determined to be the best option for the moment since
// the saves on the SD card are meant as caches that can be deleted at any time.
private void CleanSdCardDirectory()
{
Result rc = RyujinxClient.Fs.MountSdCard("sdcard".ToU8Span());
if (rc.IsFailure()) return;
try
{
RyujinxClient.Fs.CleanDirectoryRecursively("sdcard:/Nintendo/save".ToU8Span()).IgnoreResult();
RyujinxClient.Fs.DeleteDirectoryRecursively("sdcard:/save".ToU8Span()).IgnoreResult();
}
finally
{
RyujinxClient.Fs.Unmount("sdcard".ToU8Span());
}
}
private static AccessControlBits.Bits AccountFsPermissions => AccessControlBits.Bits.SystemSaveData | private static AccessControlBits.Bits AccountFsPermissions => AccessControlBits.Bits.SystemSaveData |
AccessControlBits.Bits.GameCard | AccessControlBits.Bits.GameCard |
AccessControlBits.Bits.SaveDataMeta | AccessControlBits.Bits.SaveDataMeta |

View File

@@ -43,6 +43,16 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
return ResultCode.Success; return ResultCode.Success;
} }
[CommandHipc(1)]
// nn::friends::Cancel()
public ResultCode Cancel(ServiceCtx context)
{
// TODO: Original service sets an internal field to 1 here. Determine usage.
Logger.Stub?.PrintStub(LogClass.ServiceFriend);
return ResultCode.Success;
}
[CommandHipc(10100)] [CommandHipc(10100)]
// nn::friends::GetFriendListIds(int offset, nn::account::Uid userId, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid) // nn::friends::GetFriendListIds(int offset, nn::account::Uid userId, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid)
// -> int outCount, array<nn::account::NetworkServiceAccountId, 0xa> // -> int outCount, array<nn::account::NetworkServiceAccountId, 0xa>

View File

@@ -1,6 +1,7 @@
using LibHac; using LibHac;
using LibHac.Common; using LibHac.Common;
using LibHac.Fs;
using GameCardHandle = System.UInt32;
namespace Ryujinx.HLE.HOS.Services.Fs namespace Ryujinx.HLE.HOS.Services.Fs
{ {
@@ -41,7 +42,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
{ {
Result result = _baseOperator.Get.GetGameCardHandle(out GameCardHandle handle); Result result = _baseOperator.Get.GetGameCardHandle(out GameCardHandle handle);
context.ResponseData.Write(handle.Value); context.ResponseData.Write(handle);
return (ResultCode)result.Value; return (ResultCode)result.Value;
} }

View File

@@ -19,6 +19,7 @@ using static Ryujinx.HLE.Utilities.StringUtils;
using IFileSystem = LibHac.FsSrv.Sf.IFileSystem; using IFileSystem = LibHac.FsSrv.Sf.IFileSystem;
using IStorage = LibHac.FsSrv.Sf.IStorage; using IStorage = LibHac.FsSrv.Sf.IStorage;
using RightsId = LibHac.Fs.RightsId; using RightsId = LibHac.Fs.RightsId;
using GameCardHandle = System.UInt32;
namespace Ryujinx.HLE.HOS.Services.Fs namespace Ryujinx.HLE.HOS.Services.Fs
{ {
@@ -239,7 +240,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
// OpenGameCardStorage(u32 handle, u32 partitionId) -> object<nn::fssrv::sf::IStorage> // OpenGameCardStorage(u32 handle, u32 partitionId) -> object<nn::fssrv::sf::IStorage>
public ResultCode OpenGameCardStorage(ServiceCtx context) public ResultCode OpenGameCardStorage(ServiceCtx context)
{ {
GameCardHandle handle = new GameCardHandle(context.RequestData.ReadInt32()); GameCardHandle handle = context.RequestData.ReadUInt32();
GameCardPartitionRaw partitionId = (GameCardPartitionRaw)context.RequestData.ReadInt32(); GameCardPartitionRaw partitionId = (GameCardPartitionRaw)context.RequestData.ReadInt32();
using var storage = new SharedRef<IStorage>(); using var storage = new SharedRef<IStorage>();
@@ -255,7 +256,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
// OpenGameCardFileSystem(u32 handle, u32 partitionId) -> object<nn::fssrv::sf::IFileSystem> // OpenGameCardFileSystem(u32 handle, u32 partitionId) -> object<nn::fssrv::sf::IFileSystem>
public ResultCode OpenGameCardFileSystem(ServiceCtx context) public ResultCode OpenGameCardFileSystem(ServiceCtx context)
{ {
GameCardHandle handle = new GameCardHandle(context.RequestData.ReadInt32()); GameCardHandle handle = context.RequestData.ReadUInt32();
GameCardPartition partitionId = (GameCardPartition)context.RequestData.ReadInt32(); GameCardPartition partitionId = (GameCardPartition)context.RequestData.ReadInt32();
using var fileSystem = new SharedRef<IFileSystem>(); using var fileSystem = new SharedRef<IFileSystem>();
@@ -315,6 +316,17 @@ namespace Ryujinx.HLE.HOS.Services.Fs
return (ResultCode)_baseFileSystemProxy.Get.CreateSaveDataFileSystemWithHashSalt(in attribute, in creationInfo, in metaCreateInfo, in hashSalt).Value; return (ResultCode)_baseFileSystemProxy.Get.CreateSaveDataFileSystemWithHashSalt(in attribute, in creationInfo, in metaCreateInfo, in hashSalt).Value;
} }
[CommandHipc(37)] // 14.0.0+
// CreateSaveDataFileSystemWithCreationInfo2(buffer<nn::fs::SaveDataCreationInfo2, 25> creationInfo) -> ()
public ResultCode CreateSaveDataFileSystemWithCreationInfo2(ServiceCtx context)
{
byte[] creationInfoBuffer = new byte[context.Request.SendBuff[0].Size];
context.Memory.Read(context.Request.SendBuff[0].Position, creationInfoBuffer);
ref readonly SaveDataCreationInfo2 creationInfo = ref SpanHelpers.AsReadOnlyStruct<SaveDataCreationInfo2>(creationInfoBuffer);
return (ResultCode)_baseFileSystemProxy.Get.CreateSaveDataFileSystemWithCreationInfo2(in creationInfo).Value;
}
[CommandHipc(51)] [CommandHipc(51)]
// OpenSaveDataFileSystem(u8 spaceId, nn::fs::SaveDataAttribute attribute) -> object<nn::fssrv::sf::IFileSystem> saveDataFs // OpenSaveDataFileSystem(u8 spaceId, nn::fs::SaveDataAttribute attribute) -> object<nn::fssrv::sf::IFileSystem> saveDataFs
public ResultCode OpenSaveDataFileSystem(ServiceCtx context) public ResultCode OpenSaveDataFileSystem(ServiceCtx context)

View File

@@ -22,7 +22,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Concentus" Version="1.1.7" /> <PackageReference Include="Concentus" Version="1.1.7" />
<PackageReference Include="LibHac" Version="0.16.1" /> <PackageReference Include="LibHac" Version="0.17.0" />
<PackageReference Include="MsgPack.Cli" Version="1.0.1" /> <PackageReference Include="MsgPack.Cli" Version="1.0.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" /> <PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" /> <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />