mirror of
https://git.naxdy.org/Mirror/Ryujinx.git
synced 2024-12-27 02:53:04 +00:00
Merge branch 'master' into MoreDynamicStatesPartOne
This commit is contained in:
commit
afe4d581ee
73 changed files with 2103 additions and 1347 deletions
|
@ -74,13 +74,15 @@ namespace Ryujinx.Graphics.GAL
|
||||||
public int ArrayLength { get; }
|
public int ArrayLength { get; }
|
||||||
public ResourceType Type { get; }
|
public ResourceType Type { get; }
|
||||||
public ResourceStages Stages { get; }
|
public ResourceStages Stages { get; }
|
||||||
|
public bool Write { get; }
|
||||||
|
|
||||||
public ResourceUsage(int binding, int arrayLength, ResourceType type, ResourceStages stages)
|
public ResourceUsage(int binding, int arrayLength, ResourceType type, ResourceStages stages, bool write)
|
||||||
{
|
{
|
||||||
Binding = binding;
|
Binding = binding;
|
||||||
ArrayLength = arrayLength;
|
ArrayLength = arrayLength;
|
||||||
Type = type;
|
Type = type;
|
||||||
Stages = stages;
|
Stages = stages;
|
||||||
|
Write = write;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
|
|
|
@ -78,9 +78,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
ResourceStages stages = vertexAsCompute ? ResourceStages.Compute | ResourceStages.Vertex : VtgStages;
|
ResourceStages stages = vertexAsCompute ? ResourceStages.Compute | ResourceStages.Vertex : VtgStages;
|
||||||
|
|
||||||
PopulateDescriptorAndUsages(stages, ResourceType.UniformBuffer, uniformSetIndex, 1, rrc.ReservedConstantBuffers - 1);
|
PopulateDescriptorAndUsages(stages, ResourceType.UniformBuffer, uniformSetIndex, 1, rrc.ReservedConstantBuffers - 1);
|
||||||
PopulateDescriptorAndUsages(stages, ResourceType.StorageBuffer, storageSetIndex, 0, rrc.ReservedStorageBuffers);
|
PopulateDescriptorAndUsages(stages, ResourceType.StorageBuffer, storageSetIndex, 0, rrc.ReservedStorageBuffers, true);
|
||||||
PopulateDescriptorAndUsages(stages, ResourceType.BufferTexture, textureSetIndex, 0, rrc.ReservedTextures);
|
PopulateDescriptorAndUsages(stages, ResourceType.BufferTexture, textureSetIndex, 0, rrc.ReservedTextures);
|
||||||
PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, imageSetIndex, 0, rrc.ReservedImages);
|
PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, imageSetIndex, 0, rrc.ReservedImages, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -91,10 +91,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// <param name="setIndex">Resource set index where the resources are used</param>
|
/// <param name="setIndex">Resource set index where the resources are used</param>
|
||||||
/// <param name="start">First binding number</param>
|
/// <param name="start">First binding number</param>
|
||||||
/// <param name="count">Amount of bindings</param>
|
/// <param name="count">Amount of bindings</param>
|
||||||
private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, int setIndex, int start, int count)
|
/// <param name="write">True if the binding is written from the shader, false otherwise</param>
|
||||||
|
private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, int setIndex, int start, int count, bool write = false)
|
||||||
{
|
{
|
||||||
AddDescriptor(stages, type, setIndex, start, count);
|
AddDescriptor(stages, type, setIndex, start, count);
|
||||||
AddUsage(stages, type, setIndex, start, count);
|
AddUsage(stages, type, setIndex, start, count, write);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -216,11 +217,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// <param name="setIndex">Descriptor set number where the resource will be bound</param>
|
/// <param name="setIndex">Descriptor set number where the resource will be bound</param>
|
||||||
/// <param name="binding">Binding number where the resource will be bound</param>
|
/// <param name="binding">Binding number where the resource will be bound</param>
|
||||||
/// <param name="count">Number of resources bound at the binding location</param>
|
/// <param name="count">Number of resources bound at the binding location</param>
|
||||||
private void AddUsage(ResourceStages stages, ResourceType type, int setIndex, int binding, int count)
|
/// <param name="write">True if the binding is written from the shader, false otherwise</param>
|
||||||
|
private void AddUsage(ResourceStages stages, ResourceType type, int setIndex, int binding, int count, bool write = false)
|
||||||
{
|
{
|
||||||
for (int index = 0; index < count; index++)
|
for (int index = 0; index < count; index++)
|
||||||
{
|
{
|
||||||
_resourceUsages[setIndex].Add(new ResourceUsage(binding + index, 1, type, stages));
|
_resourceUsages[setIndex].Add(new ResourceUsage(binding + index, 1, type, stages, write));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,7 +240,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
buffer.Binding,
|
buffer.Binding,
|
||||||
1,
|
1,
|
||||||
isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer,
|
isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer,
|
||||||
stages));
|
stages,
|
||||||
|
buffer.Flags.HasFlag(BufferUsageFlags.Write)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,7 +257,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
{
|
{
|
||||||
ResourceType type = GetTextureResourceType(texture, isImage);
|
ResourceType type = GetTextureResourceType(texture, isImage);
|
||||||
|
|
||||||
GetUsages(texture.Set).Add(new ResourceUsage(texture.Binding, texture.ArrayLength, type, stages));
|
GetUsages(texture.Set).Add(new ResourceUsage(
|
||||||
|
texture.Binding,
|
||||||
|
texture.ArrayLength,
|
||||||
|
type,
|
||||||
|
stages,
|
||||||
|
texture.Flags.HasFlag(TextureUsageFlags.ImageStore)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
lock (queueLock)
|
lock (queueLock)
|
||||||
{
|
{
|
||||||
_pool = new CommandBufferPool(_gd.Api, _device, queue, queueLock, _gd.QueueFamilyIndex, isLight: true);
|
_pool = new CommandBufferPool(
|
||||||
|
_gd.Api,
|
||||||
|
_device,
|
||||||
|
queue,
|
||||||
|
queueLock,
|
||||||
|
_gd.QueueFamilyIndex,
|
||||||
|
_gd.IsQualcommProprietary,
|
||||||
|
isLight: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
|
@ -8,22 +9,64 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
private const int MaxBarriersPerCall = 16;
|
private const int MaxBarriersPerCall = 16;
|
||||||
|
|
||||||
|
private const AccessFlags BaseAccess = AccessFlags.ShaderReadBit | AccessFlags.ShaderWriteBit;
|
||||||
|
private const AccessFlags BufferAccess = AccessFlags.IndexReadBit | AccessFlags.VertexAttributeReadBit | AccessFlags.UniformReadBit;
|
||||||
|
private const AccessFlags CommandBufferAccess = AccessFlags.IndirectCommandReadBit;
|
||||||
|
|
||||||
private readonly VulkanRenderer _gd;
|
private readonly VulkanRenderer _gd;
|
||||||
|
|
||||||
private readonly NativeArray<MemoryBarrier> _memoryBarrierBatch = new(MaxBarriersPerCall);
|
private readonly NativeArray<MemoryBarrier> _memoryBarrierBatch = new(MaxBarriersPerCall);
|
||||||
private readonly NativeArray<BufferMemoryBarrier> _bufferBarrierBatch = new(MaxBarriersPerCall);
|
private readonly NativeArray<BufferMemoryBarrier> _bufferBarrierBatch = new(MaxBarriersPerCall);
|
||||||
private readonly NativeArray<ImageMemoryBarrier> _imageBarrierBatch = new(MaxBarriersPerCall);
|
private readonly NativeArray<ImageMemoryBarrier> _imageBarrierBatch = new(MaxBarriersPerCall);
|
||||||
|
|
||||||
private readonly List<BarrierWithStageFlags<MemoryBarrier>> _memoryBarriers = new();
|
private readonly List<BarrierWithStageFlags<MemoryBarrier, int>> _memoryBarriers = new();
|
||||||
private readonly List<BarrierWithStageFlags<BufferMemoryBarrier>> _bufferBarriers = new();
|
private readonly List<BarrierWithStageFlags<BufferMemoryBarrier, int>> _bufferBarriers = new();
|
||||||
private readonly List<BarrierWithStageFlags<ImageMemoryBarrier>> _imageBarriers = new();
|
private readonly List<BarrierWithStageFlags<ImageMemoryBarrier, TextureStorage>> _imageBarriers = new();
|
||||||
private int _queuedBarrierCount;
|
private int _queuedBarrierCount;
|
||||||
|
|
||||||
|
private enum IncoherentBarrierType
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Texture,
|
||||||
|
All,
|
||||||
|
CommandBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
private PipelineStageFlags _incoherentBufferWriteStages;
|
||||||
|
private PipelineStageFlags _incoherentTextureWriteStages;
|
||||||
|
private PipelineStageFlags _extraStages;
|
||||||
|
private IncoherentBarrierType _queuedIncoherentBarrier;
|
||||||
|
|
||||||
public BarrierBatch(VulkanRenderer gd)
|
public BarrierBatch(VulkanRenderer gd)
|
||||||
{
|
{
|
||||||
_gd = gd;
|
_gd = gd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static (AccessFlags Access, PipelineStageFlags Stages) GetSubpassAccessSuperset(VulkanRenderer gd)
|
||||||
|
{
|
||||||
|
AccessFlags access = BufferAccess;
|
||||||
|
PipelineStageFlags stages = PipelineStageFlags.AllGraphicsBit;
|
||||||
|
|
||||||
|
if (gd.TransformFeedbackApi != null)
|
||||||
|
{
|
||||||
|
access |= AccessFlags.TransformFeedbackWriteBitExt;
|
||||||
|
stages |= PipelineStageFlags.TransformFeedbackBitExt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gd.IsTBDR)
|
||||||
|
{
|
||||||
|
// Desktop GPUs can transform image barriers into memory barriers.
|
||||||
|
|
||||||
|
access |= AccessFlags.DepthStencilAttachmentWriteBit | AccessFlags.ColorAttachmentWriteBit;
|
||||||
|
access |= AccessFlags.DepthStencilAttachmentReadBit | AccessFlags.ColorAttachmentReadBit;
|
||||||
|
|
||||||
|
stages |= PipelineStageFlags.EarlyFragmentTestsBit | PipelineStageFlags.LateFragmentTestsBit;
|
||||||
|
stages |= PipelineStageFlags.ColorAttachmentOutputBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (access, stages);
|
||||||
|
}
|
||||||
|
|
||||||
private readonly record struct StageFlags : IEquatable<StageFlags>
|
private readonly record struct StageFlags : IEquatable<StageFlags>
|
||||||
{
|
{
|
||||||
public readonly PipelineStageFlags Source;
|
public readonly PipelineStageFlags Source;
|
||||||
|
@ -36,47 +79,130 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly struct BarrierWithStageFlags<T> where T : unmanaged
|
private readonly struct BarrierWithStageFlags<T, T2> where T : unmanaged
|
||||||
{
|
{
|
||||||
public readonly StageFlags Flags;
|
public readonly StageFlags Flags;
|
||||||
public readonly T Barrier;
|
public readonly T Barrier;
|
||||||
|
public readonly T2 Resource;
|
||||||
|
|
||||||
public BarrierWithStageFlags(StageFlags flags, T barrier)
|
public BarrierWithStageFlags(StageFlags flags, T barrier)
|
||||||
{
|
{
|
||||||
Flags = flags;
|
Flags = flags;
|
||||||
Barrier = barrier;
|
Barrier = barrier;
|
||||||
|
Resource = default;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BarrierWithStageFlags(PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags, T barrier)
|
public BarrierWithStageFlags(PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags, T barrier, T2 resource)
|
||||||
{
|
{
|
||||||
Flags = new StageFlags(srcStageFlags, dstStageFlags);
|
Flags = new StageFlags(srcStageFlags, dstStageFlags);
|
||||||
Barrier = barrier;
|
Barrier = barrier;
|
||||||
|
Resource = resource;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void QueueBarrier<T>(List<BarrierWithStageFlags<T>> list, T barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags) where T : unmanaged
|
private void QueueBarrier<T, T2>(List<BarrierWithStageFlags<T, T2>> list, T barrier, T2 resource, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags) where T : unmanaged
|
||||||
{
|
{
|
||||||
list.Add(new BarrierWithStageFlags<T>(srcStageFlags, dstStageFlags, barrier));
|
list.Add(new BarrierWithStageFlags<T, T2>(srcStageFlags, dstStageFlags, barrier, resource));
|
||||||
_queuedBarrierCount++;
|
_queuedBarrierCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void QueueBarrier(MemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
public void QueueBarrier(MemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
||||||
{
|
{
|
||||||
QueueBarrier(_memoryBarriers, barrier, srcStageFlags, dstStageFlags);
|
QueueBarrier(_memoryBarriers, barrier, default, srcStageFlags, dstStageFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void QueueBarrier(BufferMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
public void QueueBarrier(BufferMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
||||||
{
|
{
|
||||||
QueueBarrier(_bufferBarriers, barrier, srcStageFlags, dstStageFlags);
|
QueueBarrier(_bufferBarriers, barrier, default, srcStageFlags, dstStageFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void QueueBarrier(ImageMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
public void QueueBarrier(ImageMemoryBarrier barrier, TextureStorage resource, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
||||||
{
|
{
|
||||||
QueueBarrier(_imageBarriers, barrier, srcStageFlags, dstStageFlags);
|
QueueBarrier(_imageBarriers, barrier, resource, srcStageFlags, dstStageFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe void Flush(CommandBuffer cb, bool insideRenderPass, Action endRenderPass)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public unsafe void FlushMemoryBarrier(ShaderCollection program, bool inRenderPass)
|
||||||
{
|
{
|
||||||
|
if (_queuedIncoherentBarrier > IncoherentBarrierType.None)
|
||||||
|
{
|
||||||
|
// We should emit a memory barrier if there's a write access in the program (current program, or program since last barrier)
|
||||||
|
bool hasTextureWrite = _incoherentTextureWriteStages != PipelineStageFlags.None;
|
||||||
|
bool hasBufferWrite = _incoherentBufferWriteStages != PipelineStageFlags.None;
|
||||||
|
bool hasBufferBarrier = _queuedIncoherentBarrier > IncoherentBarrierType.Texture;
|
||||||
|
|
||||||
|
if (hasTextureWrite || (hasBufferBarrier && hasBufferWrite))
|
||||||
|
{
|
||||||
|
AccessFlags access = BaseAccess;
|
||||||
|
|
||||||
|
PipelineStageFlags stages = inRenderPass ? PipelineStageFlags.AllGraphicsBit : PipelineStageFlags.AllCommandsBit;
|
||||||
|
|
||||||
|
if (hasBufferBarrier && hasBufferWrite)
|
||||||
|
{
|
||||||
|
access |= BufferAccess;
|
||||||
|
|
||||||
|
if (_gd.TransformFeedbackApi != null)
|
||||||
|
{
|
||||||
|
access |= AccessFlags.TransformFeedbackWriteBitExt;
|
||||||
|
stages |= PipelineStageFlags.TransformFeedbackBitExt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_queuedIncoherentBarrier == IncoherentBarrierType.CommandBuffer)
|
||||||
|
{
|
||||||
|
access |= CommandBufferAccess;
|
||||||
|
stages |= PipelineStageFlags.DrawIndirectBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryBarrier barrier = new MemoryBarrier()
|
||||||
|
{
|
||||||
|
SType = StructureType.MemoryBarrier,
|
||||||
|
SrcAccessMask = access,
|
||||||
|
DstAccessMask = access
|
||||||
|
};
|
||||||
|
|
||||||
|
QueueBarrier(barrier, stages, stages);
|
||||||
|
|
||||||
|
_incoherentTextureWriteStages = program?.IncoherentTextureWriteStages ?? PipelineStageFlags.None;
|
||||||
|
|
||||||
|
if (_queuedIncoherentBarrier > IncoherentBarrierType.Texture)
|
||||||
|
{
|
||||||
|
if (program != null)
|
||||||
|
{
|
||||||
|
_incoherentBufferWriteStages = program.IncoherentBufferWriteStages | _extraStages;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_incoherentBufferWriteStages = PipelineStageFlags.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_queuedIncoherentBarrier = IncoherentBarrierType.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Flush(CommandBufferScoped cbs, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass)
|
||||||
|
{
|
||||||
|
Flush(cbs, null, inRenderPass, rpHolder, endRenderPass);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Flush(CommandBufferScoped cbs, ShaderCollection program, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass)
|
||||||
|
{
|
||||||
|
if (program != null)
|
||||||
|
{
|
||||||
|
_incoherentBufferWriteStages |= program.IncoherentBufferWriteStages | _extraStages;
|
||||||
|
_incoherentTextureWriteStages |= program.IncoherentTextureWriteStages;
|
||||||
|
}
|
||||||
|
|
||||||
|
FlushMemoryBarrier(program, inRenderPass);
|
||||||
|
|
||||||
|
if (!inRenderPass && rpHolder != null)
|
||||||
|
{
|
||||||
|
// Render pass is about to begin. Queue any fences that normally interrupt the pass.
|
||||||
|
rpHolder.InsertForcedFences(cbs);
|
||||||
|
}
|
||||||
|
|
||||||
while (_queuedBarrierCount > 0)
|
while (_queuedBarrierCount > 0)
|
||||||
{
|
{
|
||||||
int memoryCount = 0;
|
int memoryCount = 0;
|
||||||
|
@ -86,20 +212,20 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
bool hasBarrier = false;
|
bool hasBarrier = false;
|
||||||
StageFlags flags = default;
|
StageFlags flags = default;
|
||||||
|
|
||||||
static void AddBarriers<T>(
|
static void AddBarriers<T, T2>(
|
||||||
Span<T> target,
|
Span<T> target,
|
||||||
ref int queuedBarrierCount,
|
ref int queuedBarrierCount,
|
||||||
ref bool hasBarrier,
|
ref bool hasBarrier,
|
||||||
ref StageFlags flags,
|
ref StageFlags flags,
|
||||||
ref int count,
|
ref int count,
|
||||||
List<BarrierWithStageFlags<T>> list) where T : unmanaged
|
List<BarrierWithStageFlags<T, T2>> list) where T : unmanaged
|
||||||
{
|
{
|
||||||
int firstMatch = -1;
|
int firstMatch = -1;
|
||||||
int end = list.Count;
|
int end = list.Count;
|
||||||
|
|
||||||
for (int i = 0; i < list.Count; i++)
|
for (int i = 0; i < list.Count; i++)
|
||||||
{
|
{
|
||||||
BarrierWithStageFlags<T> barrier = list[i];
|
BarrierWithStageFlags<T, T2> barrier = list[i];
|
||||||
|
|
||||||
if (!hasBarrier)
|
if (!hasBarrier)
|
||||||
{
|
{
|
||||||
|
@ -162,21 +288,60 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (insideRenderPass)
|
if (inRenderPass && _imageBarriers.Count > 0)
|
||||||
{
|
{
|
||||||
// Image barriers queued in the batch are meant to be globally scoped,
|
// Image barriers queued in the batch are meant to be globally scoped,
|
||||||
// but inside a render pass they're scoped to just the range of the render pass.
|
// but inside a render pass they're scoped to just the range of the render pass.
|
||||||
|
|
||||||
// On MoltenVK, we just break the rules and always use image barrier.
|
// On MoltenVK, we just break the rules and always use image barrier.
|
||||||
// On desktop GPUs, all barriers are globally scoped, so we just replace it with a generic memory barrier.
|
// On desktop GPUs, all barriers are globally scoped, so we just replace it with a generic memory barrier.
|
||||||
// TODO: On certain GPUs, we need to split render pass so the barrier scope is global. When this is done,
|
// Generally, we want to avoid this from happening in the future, so flag the texture to immediately
|
||||||
// notify the resource that it should add a barrier as soon as a render pass ends to avoid this in future.
|
// emit a barrier whenever the current render pass is bound again.
|
||||||
|
|
||||||
if (!_gd.IsMoltenVk)
|
bool anyIsNonAttachment = false;
|
||||||
|
|
||||||
|
foreach (BarrierWithStageFlags<ImageMemoryBarrier, TextureStorage> barrier in _imageBarriers)
|
||||||
{
|
{
|
||||||
|
// If the binding is an attachment, don't add it as a forced fence.
|
||||||
|
bool isAttachment = rpHolder.ContainsAttachment(barrier.Resource);
|
||||||
|
|
||||||
|
if (!isAttachment)
|
||||||
|
{
|
||||||
|
rpHolder.AddForcedFence(barrier.Resource, barrier.Flags.Dest);
|
||||||
|
anyIsNonAttachment = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_gd.IsTBDR)
|
||||||
|
{
|
||||||
|
if (!_gd.IsMoltenVk)
|
||||||
|
{
|
||||||
|
if (!anyIsNonAttachment)
|
||||||
|
{
|
||||||
|
// This case is a feedback loop. To prevent this from causing an absolute performance disaster,
|
||||||
|
// remove the barriers entirely.
|
||||||
|
// If this is not here, there will be a lot of single draw render passes.
|
||||||
|
// TODO: explicit handling for feedback loops, likely outside this class.
|
||||||
|
|
||||||
|
_queuedBarrierCount -= _imageBarriers.Count;
|
||||||
|
_imageBarriers.Clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TBDR GPUs are sensitive to barriers, so we need to end the pass to ensure the data is available.
|
||||||
|
// Metal already has hazard tracking so MVK doesn't need this.
|
||||||
|
endRenderPass();
|
||||||
|
inRenderPass = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Generic pipeline memory barriers will work for desktop GPUs.
|
||||||
|
// They do require a few more access flags on the subpass dependency, though.
|
||||||
foreach (var barrier in _imageBarriers)
|
foreach (var barrier in _imageBarriers)
|
||||||
{
|
{
|
||||||
_memoryBarriers.Add(new BarrierWithStageFlags<MemoryBarrier>(
|
_memoryBarriers.Add(new BarrierWithStageFlags<MemoryBarrier, int>(
|
||||||
barrier.Flags,
|
barrier.Flags,
|
||||||
new MemoryBarrier()
|
new MemoryBarrier()
|
||||||
{
|
{
|
||||||
|
@ -190,6 +355,22 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (inRenderPass && _memoryBarriers.Count > 0)
|
||||||
|
{
|
||||||
|
PipelineStageFlags allFlags = PipelineStageFlags.None;
|
||||||
|
|
||||||
|
foreach (var barrier in _memoryBarriers)
|
||||||
|
{
|
||||||
|
allFlags |= barrier.Flags.Dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allFlags.HasFlag(PipelineStageFlags.DrawIndirectBit) || !_gd.SupportsRenderPassBarrier(allFlags))
|
||||||
|
{
|
||||||
|
endRenderPass();
|
||||||
|
inRenderPass = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AddBarriers(_memoryBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref memoryCount, _memoryBarriers);
|
AddBarriers(_memoryBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref memoryCount, _memoryBarriers);
|
||||||
AddBarriers(_bufferBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref bufferCount, _bufferBarriers);
|
AddBarriers(_bufferBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref bufferCount, _bufferBarriers);
|
||||||
AddBarriers(_imageBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref imageCount, _imageBarriers);
|
AddBarriers(_imageBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref imageCount, _imageBarriers);
|
||||||
|
@ -198,14 +379,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
PipelineStageFlags srcStageFlags = flags.Source;
|
PipelineStageFlags srcStageFlags = flags.Source;
|
||||||
|
|
||||||
if (insideRenderPass)
|
if (inRenderPass)
|
||||||
{
|
{
|
||||||
// Inside a render pass, barrier stages can only be from rasterization.
|
// Inside a render pass, barrier stages can only be from rasterization.
|
||||||
srcStageFlags &= ~PipelineStageFlags.ComputeShaderBit;
|
srcStageFlags &= ~PipelineStageFlags.ComputeShaderBit;
|
||||||
}
|
}
|
||||||
|
|
||||||
_gd.Api.CmdPipelineBarrier(
|
_gd.Api.CmdPipelineBarrier(
|
||||||
cb,
|
cbs.CommandBuffer,
|
||||||
srcStageFlags,
|
srcStageFlags,
|
||||||
flags.Dest,
|
flags.Dest,
|
||||||
0,
|
0,
|
||||||
|
@ -219,6 +400,41 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void QueueIncoherentBarrier(IncoherentBarrierType type)
|
||||||
|
{
|
||||||
|
if (type > _queuedIncoherentBarrier)
|
||||||
|
{
|
||||||
|
_queuedIncoherentBarrier = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void QueueTextureBarrier()
|
||||||
|
{
|
||||||
|
QueueIncoherentBarrier(IncoherentBarrierType.Texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void QueueMemoryBarrier()
|
||||||
|
{
|
||||||
|
QueueIncoherentBarrier(IncoherentBarrierType.All);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void QueueCommandBufferBarrier()
|
||||||
|
{
|
||||||
|
QueueIncoherentBarrier(IncoherentBarrierType.CommandBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EnableTfbBarriers(bool enable)
|
||||||
|
{
|
||||||
|
if (enable)
|
||||||
|
{
|
||||||
|
_extraStages |= PipelineStageFlags.TransformFeedbackBitExt;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_extraStages &= ~PipelineStageFlags.TransformFeedbackBitExt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_memoryBarrierBatch.Dispose();
|
_memoryBarrierBatch.Dispose();
|
||||||
|
|
|
@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
private readonly Device _device;
|
private readonly Device _device;
|
||||||
private readonly Queue _queue;
|
private readonly Queue _queue;
|
||||||
private readonly object _queueLock;
|
private readonly object _queueLock;
|
||||||
|
private readonly bool _concurrentFenceWaitUnsupported;
|
||||||
private readonly CommandPool _pool;
|
private readonly CommandPool _pool;
|
||||||
private readonly Thread _owner;
|
private readonly Thread _owner;
|
||||||
|
|
||||||
|
@ -30,11 +31,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
public int SubmissionCount;
|
public int SubmissionCount;
|
||||||
public CommandBuffer CommandBuffer;
|
public CommandBuffer CommandBuffer;
|
||||||
public FenceHolder Fence;
|
public FenceHolder Fence;
|
||||||
public SemaphoreHolder Semaphore;
|
|
||||||
|
|
||||||
public List<IAuto> Dependants;
|
public List<IAuto> Dependants;
|
||||||
public List<MultiFenceHolder> Waitables;
|
public List<MultiFenceHolder> Waitables;
|
||||||
public HashSet<SemaphoreHolder> Dependencies;
|
|
||||||
|
|
||||||
public void Initialize(Vk api, Device device, CommandPool pool)
|
public void Initialize(Vk api, Device device, CommandPool pool)
|
||||||
{
|
{
|
||||||
|
@ -50,7 +49,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
Dependants = new List<IAuto>();
|
Dependants = new List<IAuto>();
|
||||||
Waitables = new List<MultiFenceHolder>();
|
Waitables = new List<MultiFenceHolder>();
|
||||||
Dependencies = new HashSet<SemaphoreHolder>();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,12 +59,20 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
private int _queuedCount;
|
private int _queuedCount;
|
||||||
private int _inUseCount;
|
private int _inUseCount;
|
||||||
|
|
||||||
public unsafe CommandBufferPool(Vk api, Device device, Queue queue, object queueLock, uint queueFamilyIndex, bool isLight = false)
|
public unsafe CommandBufferPool(
|
||||||
|
Vk api,
|
||||||
|
Device device,
|
||||||
|
Queue queue,
|
||||||
|
object queueLock,
|
||||||
|
uint queueFamilyIndex,
|
||||||
|
bool concurrentFenceWaitUnsupported,
|
||||||
|
bool isLight = false)
|
||||||
{
|
{
|
||||||
_api = api;
|
_api = api;
|
||||||
_device = device;
|
_device = device;
|
||||||
_queue = queue;
|
_queue = queue;
|
||||||
_queueLock = queueLock;
|
_queueLock = queueLock;
|
||||||
|
_concurrentFenceWaitUnsupported = concurrentFenceWaitUnsupported;
|
||||||
_owner = Thread.CurrentThread;
|
_owner = Thread.CurrentThread;
|
||||||
|
|
||||||
var commandPoolCreateInfo = new CommandPoolCreateInfo
|
var commandPoolCreateInfo = new CommandPoolCreateInfo
|
||||||
|
@ -134,14 +140,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddDependency(int cbIndex, CommandBufferScoped dependencyCbs)
|
|
||||||
{
|
|
||||||
Debug.Assert(_commandBuffers[cbIndex].InUse);
|
|
||||||
var semaphoreHolder = _commandBuffers[dependencyCbs.CommandBufferIndex].Semaphore;
|
|
||||||
semaphoreHolder.Get();
|
|
||||||
_commandBuffers[cbIndex].Dependencies.Add(semaphoreHolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddWaitable(int cbIndex, MultiFenceHolder waitable)
|
public void AddWaitable(int cbIndex, MultiFenceHolder waitable)
|
||||||
{
|
{
|
||||||
ref var entry = ref _commandBuffers[cbIndex];
|
ref var entry = ref _commandBuffers[cbIndex];
|
||||||
|
@ -345,19 +343,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
waitable.RemoveBufferUses(cbIndex);
|
waitable.RemoveBufferUses(cbIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var dependency in entry.Dependencies)
|
|
||||||
{
|
|
||||||
dependency.Put();
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.Dependants.Clear();
|
entry.Dependants.Clear();
|
||||||
entry.Waitables.Clear();
|
entry.Waitables.Clear();
|
||||||
entry.Dependencies.Clear();
|
|
||||||
entry.Fence?.Dispose();
|
entry.Fence?.Dispose();
|
||||||
|
|
||||||
if (refreshFence)
|
if (refreshFence)
|
||||||
{
|
{
|
||||||
entry.Fence = new FenceHolder(_api, _device);
|
entry.Fence = new FenceHolder(_api, _device, _concurrentFenceWaitUnsupported);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -26,11 +26,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
_pool.AddWaitable(CommandBufferIndex, waitable);
|
_pool.AddWaitable(CommandBufferIndex, waitable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddDependency(CommandBufferScoped dependencyCbs)
|
|
||||||
{
|
|
||||||
_pool.AddDependency(CommandBufferIndex, dependencyCbs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FenceHolder GetFence()
|
public FenceHolder GetFence()
|
||||||
{
|
{
|
||||||
return _pool.GetFence(CommandBufferIndex);
|
return _pool.GetFence(CommandBufferIndex);
|
||||||
|
|
|
@ -73,7 +73,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
private readonly VulkanRenderer _gd;
|
private readonly VulkanRenderer _gd;
|
||||||
private readonly Device _device;
|
private readonly Device _device;
|
||||||
private readonly PipelineBase _pipeline;
|
|
||||||
private ShaderCollection _program;
|
private ShaderCollection _program;
|
||||||
|
|
||||||
private readonly BufferRef[] _uniformBufferRefs;
|
private readonly BufferRef[] _uniformBufferRefs;
|
||||||
|
@ -125,11 +124,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
private readonly TextureView _dummyTexture;
|
private readonly TextureView _dummyTexture;
|
||||||
private readonly SamplerHolder _dummySampler;
|
private readonly SamplerHolder _dummySampler;
|
||||||
|
|
||||||
public DescriptorSetUpdater(VulkanRenderer gd, Device device, PipelineBase pipeline)
|
public DescriptorSetUpdater(VulkanRenderer gd, Device device)
|
||||||
{
|
{
|
||||||
_gd = gd;
|
_gd = gd;
|
||||||
_device = device;
|
_device = device;
|
||||||
_pipeline = pipeline;
|
|
||||||
|
|
||||||
// Some of the bindings counts needs to be multiplied by 2 because we have buffer and
|
// Some of the bindings counts needs to be multiplied by 2 because we have buffer and
|
||||||
// regular textures/images interleaved on the same descriptor set.
|
// regular textures/images interleaved on the same descriptor set.
|
||||||
|
@ -684,7 +682,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
if (_dirty.HasFlag(DirtyFlags.Texture))
|
if (_dirty.HasFlag(DirtyFlags.Texture))
|
||||||
{
|
{
|
||||||
UpdateAndBind(cbs, program, PipelineBase.TextureSetIndex, pbp);
|
if (program.UpdateTexturesWithoutTemplate)
|
||||||
|
{
|
||||||
|
UpdateAndBindTexturesWithoutTemplate(cbs, program, pbp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UpdateAndBind(cbs, program, PipelineBase.TextureSetIndex, pbp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_dirty.HasFlag(DirtyFlags.Image))
|
if (_dirty.HasFlag(DirtyFlags.Image))
|
||||||
|
@ -918,31 +923,84 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
_gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan<uint>.Empty);
|
_gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan<uint>.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void UpdateBuffers(
|
private void UpdateAndBindTexturesWithoutTemplate(CommandBufferScoped cbs, ShaderCollection program, PipelineBindPoint pbp)
|
||||||
CommandBufferScoped cbs,
|
|
||||||
PipelineBindPoint pbp,
|
|
||||||
int baseBinding,
|
|
||||||
ReadOnlySpan<DescriptorBufferInfo> bufferInfo,
|
|
||||||
DescriptorType type)
|
|
||||||
{
|
{
|
||||||
if (bufferInfo.Length == 0)
|
int setIndex = PipelineBase.TextureSetIndex;
|
||||||
|
var bindingSegments = program.BindingSegments[setIndex];
|
||||||
|
|
||||||
|
if (bindingSegments.Length == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fixed (DescriptorBufferInfo* pBufferInfo = bufferInfo)
|
if (_updateDescriptorCacheCbIndex)
|
||||||
{
|
{
|
||||||
var writeDescriptorSet = new WriteDescriptorSet
|
_updateDescriptorCacheCbIndex = false;
|
||||||
{
|
program.UpdateDescriptorCacheCommandBufferIndex(cbs.CommandBufferIndex);
|
||||||
SType = StructureType.WriteDescriptorSet,
|
|
||||||
DstBinding = (uint)baseBinding,
|
|
||||||
DescriptorType = type,
|
|
||||||
DescriptorCount = (uint)bufferInfo.Length,
|
|
||||||
PBufferInfo = pBufferInfo,
|
|
||||||
};
|
|
||||||
|
|
||||||
_gd.PushDescriptorApi.CmdPushDescriptorSet(cbs.CommandBuffer, pbp, _program.PipelineLayout, 0, 1, &writeDescriptorSet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dsc = program.GetNewDescriptorSetCollection(setIndex, out _).Get(cbs);
|
||||||
|
|
||||||
|
foreach (ResourceBindingSegment segment in bindingSegments)
|
||||||
|
{
|
||||||
|
int binding = segment.Binding;
|
||||||
|
int count = segment.Count;
|
||||||
|
|
||||||
|
if (!segment.IsArray)
|
||||||
|
{
|
||||||
|
if (segment.Type != ResourceType.BufferTexture)
|
||||||
|
{
|
||||||
|
Span<DescriptorImageInfo> textures = _textures;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
ref var texture = ref textures[i];
|
||||||
|
ref var refs = ref _textureRefs[binding + i];
|
||||||
|
|
||||||
|
texture.ImageView = refs.View?.Get(cbs).Value ?? default;
|
||||||
|
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
|
||||||
|
|
||||||
|
if (texture.ImageView.Handle == 0)
|
||||||
|
{
|
||||||
|
texture.ImageView = _dummyTexture.GetImageView().Get(cbs).Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (texture.Sampler.Handle == 0)
|
||||||
|
{
|
||||||
|
texture.Sampler = _dummySampler.GetSampler().Get(cbs).Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dsc.UpdateImages(0, binding, textures[..count], DescriptorType.CombinedImageSampler);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Span<BufferView> bufferTextures = _bufferTextures;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs, false) ?? default;
|
||||||
|
}
|
||||||
|
|
||||||
|
dsc.UpdateBufferImages(0, binding, bufferTextures[..count], DescriptorType.UniformTexelBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (segment.Type != ResourceType.BufferTexture)
|
||||||
|
{
|
||||||
|
dsc.UpdateImages(0, binding, _textureArrayRefs[binding].Array.GetImageInfos(_gd, cbs, _dummyTexture, _dummySampler), DescriptorType.CombinedImageSampler);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dsc.UpdateBufferImages(0, binding, _textureArrayRefs[binding].Array.GetBufferViews(cbs), DescriptorType.UniformTexelBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sets = dsc.GetSets();
|
||||||
|
|
||||||
|
_gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan<uint>.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|
|
@ -59,14 +59,14 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
||||||
var scalingResourceLayout = new ResourceLayoutBuilder()
|
var scalingResourceLayout = new ResourceLayoutBuilder()
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||||
|
|
||||||
var sharpeningResourceLayout = new ResourceLayoutBuilder()
|
var sharpeningResourceLayout = new ResourceLayoutBuilder()
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 3)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 3)
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 4)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 4)
|
||||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||||
|
|
||||||
_sampler = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
_sampler = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
||||||
var resourceLayout = new ResourceLayoutBuilder()
|
var resourceLayout = new ResourceLayoutBuilder()
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||||
|
|
||||||
_samplerLinear = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
_samplerLinear = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
||||||
|
|
||||||
|
|
|
@ -81,20 +81,20 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
||||||
var edgeResourceLayout = new ResourceLayoutBuilder()
|
var edgeResourceLayout = new ResourceLayoutBuilder()
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||||
|
|
||||||
var blendResourceLayout = new ResourceLayoutBuilder()
|
var blendResourceLayout = new ResourceLayoutBuilder()
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3)
|
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3)
|
||||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 4)
|
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 4)
|
||||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||||
|
|
||||||
var neighbourResourceLayout = new ResourceLayoutBuilder()
|
var neighbourResourceLayout = new ResourceLayoutBuilder()
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3)
|
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3)
|
||||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||||
|
|
||||||
_samplerLinear = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
_samplerLinear = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
||||||
|
|
||||||
|
|
|
@ -10,12 +10,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
private readonly Device _device;
|
private readonly Device _device;
|
||||||
private Fence _fence;
|
private Fence _fence;
|
||||||
private int _referenceCount;
|
private int _referenceCount;
|
||||||
|
private int _lock;
|
||||||
|
private readonly bool _concurrentWaitUnsupported;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
public unsafe FenceHolder(Vk api, Device device)
|
public unsafe FenceHolder(Vk api, Device device, bool concurrentWaitUnsupported)
|
||||||
{
|
{
|
||||||
_api = api;
|
_api = api;
|
||||||
_device = device;
|
_device = device;
|
||||||
|
_concurrentWaitUnsupported = concurrentWaitUnsupported;
|
||||||
|
|
||||||
var fenceCreateInfo = new FenceCreateInfo
|
var fenceCreateInfo = new FenceCreateInfo
|
||||||
{
|
{
|
||||||
|
@ -47,6 +50,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
while (Interlocked.CompareExchange(ref _referenceCount, lastValue + 1, lastValue) != lastValue);
|
while (Interlocked.CompareExchange(ref _referenceCount, lastValue + 1, lastValue) != lastValue);
|
||||||
|
|
||||||
|
if (_concurrentWaitUnsupported)
|
||||||
|
{
|
||||||
|
AcquireLock();
|
||||||
|
}
|
||||||
|
|
||||||
fence = _fence;
|
fence = _fence;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -57,6 +65,16 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
return _fence;
|
return _fence;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void PutLock()
|
||||||
|
{
|
||||||
|
Put();
|
||||||
|
|
||||||
|
if (_concurrentWaitUnsupported)
|
||||||
|
{
|
||||||
|
ReleaseLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Put()
|
public void Put()
|
||||||
{
|
{
|
||||||
if (Interlocked.Decrement(ref _referenceCount) == 0)
|
if (Interlocked.Decrement(ref _referenceCount) == 0)
|
||||||
|
@ -66,24 +84,67 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AcquireLock()
|
||||||
|
{
|
||||||
|
while (!TryAcquireLock())
|
||||||
|
{
|
||||||
|
Thread.SpinWait(32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryAcquireLock()
|
||||||
|
{
|
||||||
|
return Interlocked.Exchange(ref _lock, 1) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReleaseLock()
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref _lock, 0);
|
||||||
|
}
|
||||||
|
|
||||||
public void Wait()
|
public void Wait()
|
||||||
{
|
{
|
||||||
Span<Fence> fences = stackalloc Fence[]
|
if (_concurrentWaitUnsupported)
|
||||||
{
|
{
|
||||||
_fence,
|
AcquireLock();
|
||||||
};
|
|
||||||
|
|
||||||
FenceHelper.WaitAllIndefinitely(_api, _device, fences);
|
try
|
||||||
|
{
|
||||||
|
FenceHelper.WaitAllIndefinitely(_api, _device, stackalloc Fence[] { _fence });
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ReleaseLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FenceHelper.WaitAllIndefinitely(_api, _device, stackalloc Fence[] { _fence });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsSignaled()
|
public bool IsSignaled()
|
||||||
{
|
{
|
||||||
Span<Fence> fences = stackalloc Fence[]
|
if (_concurrentWaitUnsupported)
|
||||||
{
|
{
|
||||||
_fence,
|
if (!TryAcquireLock())
|
||||||
};
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return FenceHelper.AllSignaled(_api, _device, fences);
|
try
|
||||||
|
{
|
||||||
|
return FenceHelper.AllSignaled(_api, _device, stackalloc Fence[] { _fence });
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ReleaseLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return FenceHelper.AllSignaled(_api, _device, stackalloc Fence[] { _fence });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
|
@ -286,10 +286,23 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
_depthStencil?.Storage?.QueueLoadOpBarrier(cbs, true);
|
_depthStencil?.Storage?.QueueLoadOpBarrier(cbs, true);
|
||||||
|
|
||||||
gd.Barriers.Flush(cbs.CommandBuffer, false, null);
|
gd.Barriers.Flush(cbs, false, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public (Auto<DisposableRenderPass> renderPass, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
|
public void AddStoreOpUsage()
|
||||||
|
{
|
||||||
|
if (_colors != null)
|
||||||
|
{
|
||||||
|
foreach (var color in _colors)
|
||||||
|
{
|
||||||
|
color.Storage?.AddStoreOpUsage(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_depthStencil?.Storage?.AddStoreOpUsage(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public (RenderPassHolder rpHolder, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
|
||||||
VulkanRenderer gd,
|
VulkanRenderer gd,
|
||||||
Device device,
|
Device device,
|
||||||
CommandBufferScoped cbs)
|
CommandBufferScoped cbs)
|
||||||
|
|
|
@ -115,7 +115,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
var strideChangeResourceLayout = new ResourceLayoutBuilder()
|
var strideChangeResourceLayout = new ResourceLayoutBuilder()
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
||||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
|
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
|
||||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2).Build();
|
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true).Build();
|
||||||
|
|
||||||
_programStrideChange = gd.CreateProgramWithMinimalLayout(new[]
|
_programStrideChange = gd.CreateProgramWithMinimalLayout(new[]
|
||||||
{
|
{
|
||||||
|
@ -125,7 +125,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
var colorCopyResourceLayout = new ResourceLayoutBuilder()
|
var colorCopyResourceLayout = new ResourceLayoutBuilder()
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
||||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 0)
|
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 0)
|
||||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||||
|
|
||||||
_programColorCopyShortening = gd.CreateProgramWithMinimalLayout(new[]
|
_programColorCopyShortening = gd.CreateProgramWithMinimalLayout(new[]
|
||||||
{
|
{
|
||||||
|
@ -155,7 +155,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
var convertD32S8ToD24S8ResourceLayout = new ResourceLayoutBuilder()
|
var convertD32S8ToD24S8ResourceLayout = new ResourceLayoutBuilder()
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
||||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
|
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
|
||||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2).Build();
|
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true).Build();
|
||||||
|
|
||||||
_programConvertD32S8ToD24S8 = gd.CreateProgramWithMinimalLayout(new[]
|
_programConvertD32S8ToD24S8 = gd.CreateProgramWithMinimalLayout(new[]
|
||||||
{
|
{
|
||||||
|
@ -165,7 +165,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
var convertIndexBufferResourceLayout = new ResourceLayoutBuilder()
|
var convertIndexBufferResourceLayout = new ResourceLayoutBuilder()
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
||||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
|
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
|
||||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2).Build();
|
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true).Build();
|
||||||
|
|
||||||
_programConvertIndexBuffer = gd.CreateProgramWithMinimalLayout(new[]
|
_programConvertIndexBuffer = gd.CreateProgramWithMinimalLayout(new[]
|
||||||
{
|
{
|
||||||
|
@ -175,7 +175,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
var convertIndirectDataResourceLayout = new ResourceLayoutBuilder()
|
var convertIndirectDataResourceLayout = new ResourceLayoutBuilder()
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
||||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
|
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
|
||||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2)
|
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true)
|
||||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 3).Build();
|
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 3).Build();
|
||||||
|
|
||||||
_programConvertIndirectData = gd.CreateProgramWithMinimalLayout(new[]
|
_programConvertIndirectData = gd.CreateProgramWithMinimalLayout(new[]
|
||||||
|
|
|
@ -196,18 +196,23 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
bool signaled = true;
|
bool signaled = true;
|
||||||
|
|
||||||
if (hasTimeout)
|
try
|
||||||
{
|
{
|
||||||
signaled = FenceHelper.AllSignaled(api, device, fences[..fenceCount], timeout);
|
if (hasTimeout)
|
||||||
|
{
|
||||||
|
signaled = FenceHelper.AllSignaled(api, device, fences[..fenceCount], timeout);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FenceHelper.WaitAllIndefinitely(api, device, fences[..fenceCount]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
finally
|
||||||
{
|
{
|
||||||
FenceHelper.WaitAllIndefinitely(api, device, fences[..fenceCount]);
|
for (int i = 0; i < fenceCount; i++)
|
||||||
}
|
{
|
||||||
|
fenceHolders[i].PutLock();
|
||||||
for (int i = 0; i < fenceCount; i++)
|
}
|
||||||
{
|
|
||||||
fenceHolders[i].Put();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return signaled;
|
return signaled;
|
||||||
|
|
|
@ -56,6 +56,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
protected FramebufferParams FramebufferParams;
|
protected FramebufferParams FramebufferParams;
|
||||||
private Auto<DisposableFramebuffer> _framebuffer;
|
private Auto<DisposableFramebuffer> _framebuffer;
|
||||||
|
private RenderPassHolder _rpHolder;
|
||||||
private Auto<DisposableRenderPass> _renderPass;
|
private Auto<DisposableRenderPass> _renderPass;
|
||||||
private RenderPassHolder _nullRenderPass;
|
private RenderPassHolder _nullRenderPass;
|
||||||
private int _writtenAttachmentCount;
|
private int _writtenAttachmentCount;
|
||||||
|
@ -89,8 +90,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
private readonly bool _supportExtDynamic2;
|
private readonly bool _supportExtDynamic2;
|
||||||
|
|
||||||
private readonly PipelineColorBlendAttachmentState[] _storedBlend;
|
private readonly PipelineColorBlendAttachmentState[] _storedBlend;
|
||||||
|
|
||||||
private ulong _drawCountSinceBarrier;
|
|
||||||
public ulong DrawCount { get; private set; }
|
public ulong DrawCount { get; private set; }
|
||||||
public bool RenderPassActive { get; private set; }
|
public bool RenderPassActive { get; private set; }
|
||||||
|
|
||||||
|
@ -109,7 +108,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
gd.Api.CreatePipelineCache(device, pipelineCacheCreateInfo, null, out PipelineCache).ThrowOnError();
|
gd.Api.CreatePipelineCache(device, pipelineCacheCreateInfo, null, out PipelineCache).ThrowOnError();
|
||||||
|
|
||||||
_descriptorSetUpdater = new DescriptorSetUpdater(gd, device, this);
|
_descriptorSetUpdater = new DescriptorSetUpdater(gd, device);
|
||||||
_vertexBufferUpdater = new VertexBufferUpdater(gd);
|
_vertexBufferUpdater = new VertexBufferUpdater(gd);
|
||||||
|
|
||||||
_transformFeedbackBuffers = new BufferState[Constants.MaxTransformFeedbackBuffers];
|
_transformFeedbackBuffers = new BufferState[Constants.MaxTransformFeedbackBuffers];
|
||||||
|
@ -143,48 +142,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
public unsafe void Barrier()
|
public unsafe void Barrier()
|
||||||
{
|
{
|
||||||
if (_drawCountSinceBarrier != DrawCount)
|
Gd.Barriers.QueueMemoryBarrier();
|
||||||
{
|
|
||||||
_drawCountSinceBarrier = DrawCount;
|
|
||||||
|
|
||||||
// Barriers are not supported inside a render pass on Apple GPUs.
|
|
||||||
// As a workaround, end the render pass.
|
|
||||||
if (Gd.Vendor == Vendor.Apple)
|
|
||||||
{
|
|
||||||
EndRenderPass();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MemoryBarrier memoryBarrier = new()
|
|
||||||
{
|
|
||||||
SType = StructureType.MemoryBarrier,
|
|
||||||
SrcAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
|
|
||||||
DstAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
|
|
||||||
};
|
|
||||||
|
|
||||||
PipelineStageFlags pipelineStageFlags = PipelineStageFlags.VertexShaderBit | PipelineStageFlags.FragmentShaderBit;
|
|
||||||
|
|
||||||
if (Gd.Capabilities.SupportsGeometryShader)
|
|
||||||
{
|
|
||||||
pipelineStageFlags |= PipelineStageFlags.GeometryShaderBit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Gd.Capabilities.SupportsTessellationShader)
|
|
||||||
{
|
|
||||||
pipelineStageFlags |= PipelineStageFlags.TessellationControlShaderBit | PipelineStageFlags.TessellationEvaluationShaderBit;
|
|
||||||
}
|
|
||||||
|
|
||||||
Gd.Api.CmdPipelineBarrier(
|
|
||||||
CommandBuffer,
|
|
||||||
pipelineStageFlags,
|
|
||||||
pipelineStageFlags,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
memoryBarrier,
|
|
||||||
0,
|
|
||||||
null,
|
|
||||||
0,
|
|
||||||
null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ComputeBarrier()
|
public void ComputeBarrier()
|
||||||
|
@ -211,6 +169,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
public void BeginTransformFeedback(PrimitiveTopology topology)
|
public void BeginTransformFeedback(PrimitiveTopology topology)
|
||||||
{
|
{
|
||||||
|
Gd.Barriers.EnableTfbBarriers(true);
|
||||||
_tfEnabled = true;
|
_tfEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,7 +216,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
CreateRenderPass();
|
CreateRenderPass();
|
||||||
}
|
}
|
||||||
|
|
||||||
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
|
Gd.Barriers.Flush(Cbs, RenderPassActive, _rpHolder, EndRenderPassDelegate);
|
||||||
|
|
||||||
BeginRenderPass();
|
BeginRenderPass();
|
||||||
|
|
||||||
|
@ -295,7 +254,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
CreateRenderPass();
|
CreateRenderPass();
|
||||||
}
|
}
|
||||||
|
|
||||||
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
|
Gd.Barriers.Flush(Cbs, RenderPassActive, _rpHolder, EndRenderPassDelegate);
|
||||||
|
|
||||||
BeginRenderPass();
|
BeginRenderPass();
|
||||||
|
|
||||||
|
@ -307,24 +266,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
public unsafe void CommandBufferBarrier()
|
public unsafe void CommandBufferBarrier()
|
||||||
{
|
{
|
||||||
MemoryBarrier memoryBarrier = new()
|
Gd.Barriers.QueueCommandBufferBarrier();
|
||||||
{
|
|
||||||
SType = StructureType.MemoryBarrier,
|
|
||||||
SrcAccessMask = BufferHolder.DefaultAccessFlags,
|
|
||||||
DstAccessMask = AccessFlags.IndirectCommandReadBit,
|
|
||||||
};
|
|
||||||
|
|
||||||
Gd.Api.CmdPipelineBarrier(
|
|
||||||
CommandBuffer,
|
|
||||||
PipelineStageFlags.AllCommandsBit,
|
|
||||||
PipelineStageFlags.DrawIndirectBit,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
memoryBarrier,
|
|
||||||
0,
|
|
||||||
null,
|
|
||||||
0,
|
|
||||||
null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size)
|
public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size)
|
||||||
|
@ -758,6 +700,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
public void EndTransformFeedback()
|
public void EndTransformFeedback()
|
||||||
{
|
{
|
||||||
|
Gd.Barriers.EnableTfbBarriers(false);
|
||||||
PauseTransformFeedbackInternal();
|
PauseTransformFeedbackInternal();
|
||||||
_tfEnabled = false;
|
_tfEnabled = false;
|
||||||
}
|
}
|
||||||
|
@ -1207,6 +1150,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
DynamicState.SetRasterizerDiscard(discard);
|
DynamicState.SetRasterizerDiscard(discard);
|
||||||
}
|
}
|
||||||
SignalStateChange();
|
SignalStateChange();
|
||||||
|
|
||||||
|
if (!discard && Gd.IsQualcommProprietary)
|
||||||
|
{
|
||||||
|
// On Adreno, enabling rasterizer discard somehow corrupts the viewport state.
|
||||||
|
// Force it to be updated on next use to work around this bug.
|
||||||
|
DynamicState.ForceAllDirty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask)
|
public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask)
|
||||||
|
@ -1610,24 +1560,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
public unsafe void TextureBarrier()
|
public unsafe void TextureBarrier()
|
||||||
{
|
{
|
||||||
MemoryBarrier memoryBarrier = new()
|
Gd.Barriers.QueueTextureBarrier();
|
||||||
{
|
|
||||||
SType = StructureType.MemoryBarrier,
|
|
||||||
SrcAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
|
|
||||||
DstAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
|
|
||||||
};
|
|
||||||
|
|
||||||
Gd.Api.CmdPipelineBarrier(
|
|
||||||
CommandBuffer,
|
|
||||||
PipelineStageFlags.FragmentShaderBit,
|
|
||||||
PipelineStageFlags.FragmentShaderBit,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
memoryBarrier,
|
|
||||||
0,
|
|
||||||
null,
|
|
||||||
0,
|
|
||||||
null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TextureBarrierTiled()
|
public void TextureBarrierTiled()
|
||||||
|
@ -1734,12 +1667,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
// Use the null framebuffer.
|
// Use the null framebuffer.
|
||||||
_nullRenderPass ??= new RenderPassHolder(Gd, Device, new RenderPassCacheKey(), FramebufferParams);
|
_nullRenderPass ??= new RenderPassHolder(Gd, Device, new RenderPassCacheKey(), FramebufferParams);
|
||||||
|
|
||||||
|
_rpHolder = _nullRenderPass;
|
||||||
_renderPass = _nullRenderPass.GetRenderPass();
|
_renderPass = _nullRenderPass.GetRenderPass();
|
||||||
_framebuffer = _nullRenderPass.GetFramebuffer(Gd, Cbs, FramebufferParams);
|
_framebuffer = _nullRenderPass.GetFramebuffer(Gd, Cbs, FramebufferParams);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
(_renderPass, _framebuffer) = FramebufferParams.GetPassAndFramebuffer(Gd, Device, Cbs);
|
(_rpHolder, _framebuffer) = FramebufferParams.GetPassAndFramebuffer(Gd, Device, Cbs);
|
||||||
|
|
||||||
|
_renderPass = _rpHolder.GetRenderPass();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1766,7 +1702,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
|
Gd.Barriers.Flush(Cbs, _program, RenderPassActive, _rpHolder, EndRenderPassDelegate);
|
||||||
|
|
||||||
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Compute);
|
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Compute);
|
||||||
}
|
}
|
||||||
|
@ -1831,7 +1767,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
|
Gd.Barriers.Flush(Cbs, _program, RenderPassActive, _rpHolder, EndRenderPassDelegate);
|
||||||
|
|
||||||
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Graphics);
|
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Graphics);
|
||||||
|
|
||||||
|
@ -1910,6 +1846,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
if (RenderPassActive)
|
if (RenderPassActive)
|
||||||
{
|
{
|
||||||
|
FramebufferParams.AddStoreOpUsage();
|
||||||
|
|
||||||
PauseTransformFeedbackInternal();
|
PauseTransformFeedbackInternal();
|
||||||
Gd.Api.CmdEndRenderPass(CommandBuffer);
|
Gd.Api.CmdEndRenderPass(CommandBuffer);
|
||||||
SignalRenderPassEnd();
|
SignalRenderPassEnd();
|
||||||
|
|
|
@ -9,13 +9,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
static class PipelineConverter
|
static class PipelineConverter
|
||||||
{
|
{
|
||||||
private const AccessFlags SubpassAccessMask =
|
|
||||||
AccessFlags.MemoryReadBit |
|
|
||||||
AccessFlags.MemoryWriteBit |
|
|
||||||
AccessFlags.ShaderReadBit |
|
|
||||||
AccessFlags.ColorAttachmentWriteBit |
|
|
||||||
AccessFlags.DepthStencilAttachmentWriteBit;
|
|
||||||
|
|
||||||
public static unsafe DisposableRenderPass ToRenderPass(this ProgramPipelineState state, VulkanRenderer gd, Device device)
|
public static unsafe DisposableRenderPass ToRenderPass(this ProgramPipelineState state, VulkanRenderer gd, Device device)
|
||||||
{
|
{
|
||||||
const int MaxAttachments = Constants.MaxRenderTargets + 1;
|
const int MaxAttachments = Constants.MaxRenderTargets + 1;
|
||||||
|
@ -108,7 +101,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var subpassDependency = CreateSubpassDependency();
|
var subpassDependency = CreateSubpassDependency(gd);
|
||||||
|
|
||||||
fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs)
|
fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs)
|
||||||
{
|
{
|
||||||
|
@ -129,29 +122,33 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SubpassDependency CreateSubpassDependency()
|
public static SubpassDependency CreateSubpassDependency(VulkanRenderer gd)
|
||||||
{
|
{
|
||||||
|
var (access, stages) = BarrierBatch.GetSubpassAccessSuperset(gd);
|
||||||
|
|
||||||
return new SubpassDependency(
|
return new SubpassDependency(
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
PipelineStageFlags.AllGraphicsBit,
|
stages,
|
||||||
PipelineStageFlags.AllGraphicsBit,
|
stages,
|
||||||
SubpassAccessMask,
|
access,
|
||||||
SubpassAccessMask,
|
access,
|
||||||
0);
|
0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe static SubpassDependency2 CreateSubpassDependency2()
|
public unsafe static SubpassDependency2 CreateSubpassDependency2(VulkanRenderer gd)
|
||||||
{
|
{
|
||||||
|
var (access, stages) = BarrierBatch.GetSubpassAccessSuperset(gd);
|
||||||
|
|
||||||
return new SubpassDependency2(
|
return new SubpassDependency2(
|
||||||
StructureType.SubpassDependency2,
|
StructureType.SubpassDependency2,
|
||||||
null,
|
null,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
PipelineStageFlags.AllGraphicsBit,
|
stages,
|
||||||
PipelineStageFlags.AllGraphicsBit,
|
stages,
|
||||||
SubpassAccessMask,
|
access,
|
||||||
SubpassAccessMask,
|
access,
|
||||||
0);
|
0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,10 +47,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (componentMask != 0xf)
|
if (componentMask != 0xf || Gd.IsQualcommProprietary)
|
||||||
{
|
{
|
||||||
// We can't use CmdClearAttachments if not writing all components,
|
// We can't use CmdClearAttachments if not writing all components,
|
||||||
// because on Vulkan, the pipeline state does not affect clears.
|
// because on Vulkan, the pipeline state does not affect clears.
|
||||||
|
// On proprietary Adreno drivers, CmdClearAttachments appears to execute out of order, so it's better to not use it at all.
|
||||||
var dstTexture = FramebufferParams.GetColorView(index);
|
var dstTexture = FramebufferParams.GetColorView(index);
|
||||||
if (dstTexture == null)
|
if (dstTexture == null)
|
||||||
{
|
{
|
||||||
|
@ -87,10 +88,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stencilMask != 0 && stencilMask != 0xff)
|
if ((stencilMask != 0 && stencilMask != 0xff) || Gd.IsQualcommProprietary)
|
||||||
{
|
{
|
||||||
// We can't use CmdClearAttachments if not clearing all (mask is all ones, 0xFF) or none (mask is 0) of the stencil bits,
|
// We can't use CmdClearAttachments if not clearing all (mask is all ones, 0xFF) or none (mask is 0) of the stencil bits,
|
||||||
// because on Vulkan, the pipeline state does not affect clears.
|
// because on Vulkan, the pipeline state does not affect clears.
|
||||||
|
// On proprietary Adreno drivers, CmdClearAttachments appears to execute out of order, so it's better to not use it at all.
|
||||||
var dstTexture = FramebufferParams.GetDepthStencilView();
|
var dstTexture = FramebufferParams.GetDepthStencilView();
|
||||||
if (dstTexture == null)
|
if (dstTexture == null)
|
||||||
{
|
{
|
||||||
|
@ -255,7 +257,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
PreloadCbs = null;
|
PreloadCbs = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Gd.Barriers.Flush(Cbs.CommandBuffer, false, null);
|
Gd.Barriers.Flush(Cbs, false, null, null);
|
||||||
CommandBuffer = (Cbs = Gd.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer;
|
CommandBuffer = (Cbs = Gd.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer;
|
||||||
Gd.RegisterFlush();
|
Gd.RegisterFlush();
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
|
@ -29,10 +31,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly record struct ForcedFence(TextureStorage Texture, PipelineStageFlags StageFlags);
|
||||||
|
|
||||||
private readonly TextureView[] _textures;
|
private readonly TextureView[] _textures;
|
||||||
private readonly Auto<DisposableRenderPass> _renderPass;
|
private readonly Auto<DisposableRenderPass> _renderPass;
|
||||||
private readonly HashTableSlim<FramebufferCacheKey, Auto<DisposableFramebuffer>> _framebuffers;
|
private readonly HashTableSlim<FramebufferCacheKey, Auto<DisposableFramebuffer>> _framebuffers;
|
||||||
private readonly RenderPassCacheKey _key;
|
private readonly RenderPassCacheKey _key;
|
||||||
|
private readonly List<ForcedFence> _forcedFences;
|
||||||
|
|
||||||
public unsafe RenderPassHolder(VulkanRenderer gd, Device device, RenderPassCacheKey key, FramebufferParams fb)
|
public unsafe RenderPassHolder(VulkanRenderer gd, Device device, RenderPassCacheKey key, FramebufferParams fb)
|
||||||
{
|
{
|
||||||
|
@ -105,7 +110,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var subpassDependency = PipelineConverter.CreateSubpassDependency();
|
var subpassDependency = PipelineConverter.CreateSubpassDependency(gd);
|
||||||
|
|
||||||
fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs)
|
fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs)
|
||||||
{
|
{
|
||||||
|
@ -138,6 +143,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
_textures = textures;
|
_textures = textures;
|
||||||
_key = key;
|
_key = key;
|
||||||
|
|
||||||
|
_forcedFences = new List<ForcedFence>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Auto<DisposableFramebuffer> GetFramebuffer(VulkanRenderer gd, CommandBufferScoped cbs, FramebufferParams fb)
|
public Auto<DisposableFramebuffer> GetFramebuffer(VulkanRenderer gd, CommandBufferScoped cbs, FramebufferParams fb)
|
||||||
|
@ -159,6 +166,37 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
return _renderPass;
|
return _renderPass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddForcedFence(TextureStorage storage, PipelineStageFlags stageFlags)
|
||||||
|
{
|
||||||
|
if (!_forcedFences.Any(fence => fence.Texture == storage))
|
||||||
|
{
|
||||||
|
_forcedFences.Add(new ForcedFence(storage, stageFlags));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InsertForcedFences(CommandBufferScoped cbs)
|
||||||
|
{
|
||||||
|
if (_forcedFences.Count > 0)
|
||||||
|
{
|
||||||
|
_forcedFences.RemoveAll((entry) =>
|
||||||
|
{
|
||||||
|
if (entry.Texture.Disposed)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.Texture.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, entry.StageFlags);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsAttachment(TextureStorage storage)
|
||||||
|
{
|
||||||
|
return _textures.Any(view => view.Storage == storage);
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
// Dispose all framebuffers.
|
// Dispose all framebuffers.
|
||||||
|
|
|
@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResourceLayoutBuilder Add(ResourceStages stages, ResourceType type, int binding)
|
public ResourceLayoutBuilder Add(ResourceStages stages, ResourceType type, int binding, bool write = false)
|
||||||
{
|
{
|
||||||
int setIndex = type switch
|
int setIndex = type switch
|
||||||
{
|
{
|
||||||
|
@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
};
|
};
|
||||||
|
|
||||||
_resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding, 1, type, stages));
|
_resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding, 1, type, stages));
|
||||||
_resourceUsages[setIndex].Add(new ResourceUsage(binding, 1, type, stages));
|
_resourceUsages[setIndex].Add(new ResourceUsage(binding, 1, type, stages, write));
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
using Silk.NET.Vulkan;
|
|
||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using VkSemaphore = Silk.NET.Vulkan.Semaphore;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
|
||||||
{
|
|
||||||
class SemaphoreHolder : IDisposable
|
|
||||||
{
|
|
||||||
private readonly Vk _api;
|
|
||||||
private readonly Device _device;
|
|
||||||
private VkSemaphore _semaphore;
|
|
||||||
private int _referenceCount;
|
|
||||||
private bool _disposed;
|
|
||||||
|
|
||||||
public unsafe SemaphoreHolder(Vk api, Device device)
|
|
||||||
{
|
|
||||||
_api = api;
|
|
||||||
_device = device;
|
|
||||||
|
|
||||||
var semaphoreCreateInfo = new SemaphoreCreateInfo
|
|
||||||
{
|
|
||||||
SType = StructureType.SemaphoreCreateInfo,
|
|
||||||
};
|
|
||||||
|
|
||||||
api.CreateSemaphore(device, in semaphoreCreateInfo, null, out _semaphore).ThrowOnError();
|
|
||||||
|
|
||||||
_referenceCount = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public VkSemaphore GetUnsafe()
|
|
||||||
{
|
|
||||||
return _semaphore;
|
|
||||||
}
|
|
||||||
|
|
||||||
public VkSemaphore Get()
|
|
||||||
{
|
|
||||||
Interlocked.Increment(ref _referenceCount);
|
|
||||||
return _semaphore;
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe void Put()
|
|
||||||
{
|
|
||||||
if (Interlocked.Decrement(ref _referenceCount) == 0)
|
|
||||||
{
|
|
||||||
_api.DestroySemaphore(_device, _semaphore, null);
|
|
||||||
_semaphore = default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (!_disposed)
|
|
||||||
{
|
|
||||||
Put();
|
|
||||||
_disposed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -23,8 +23,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
public bool IsCompute { get; }
|
public bool IsCompute { get; }
|
||||||
public bool HasTessellationControlShader => (Stages & (1u << 3)) != 0;
|
public bool HasTessellationControlShader => (Stages & (1u << 3)) != 0;
|
||||||
|
|
||||||
|
public bool UpdateTexturesWithoutTemplate { get; }
|
||||||
|
|
||||||
public uint Stages { get; }
|
public uint Stages { get; }
|
||||||
|
|
||||||
|
public PipelineStageFlags IncoherentBufferWriteStages { get; }
|
||||||
|
public PipelineStageFlags IncoherentTextureWriteStages { get; }
|
||||||
|
|
||||||
public ResourceBindingSegment[][] ClearSegments { get; }
|
public ResourceBindingSegment[][] ClearSegments { get; }
|
||||||
public ResourceBindingSegment[][] BindingSegments { get; }
|
public ResourceBindingSegment[][] BindingSegments { get; }
|
||||||
public DescriptorSetTemplate[] Templates { get; }
|
public DescriptorSetTemplate[] Templates { get; }
|
||||||
|
@ -127,8 +132,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
Stages = stages;
|
Stages = stages;
|
||||||
|
|
||||||
ClearSegments = BuildClearSegments(sets);
|
ClearSegments = BuildClearSegments(sets);
|
||||||
BindingSegments = BuildBindingSegments(resourceLayout.SetUsages);
|
BindingSegments = BuildBindingSegments(resourceLayout.SetUsages, out bool usesBufferTextures);
|
||||||
Templates = BuildTemplates(usePushDescriptors);
|
Templates = BuildTemplates(usePushDescriptors);
|
||||||
|
(IncoherentBufferWriteStages, IncoherentTextureWriteStages) = BuildIncoherentStages(resourceLayout.SetUsages);
|
||||||
|
|
||||||
|
// Updating buffer texture bindings using template updates crashes the Adreno driver on Windows.
|
||||||
|
UpdateTexturesWithoutTemplate = gd.IsQualcommProprietary && usesBufferTextures;
|
||||||
|
|
||||||
_compileTask = Task.CompletedTask;
|
_compileTask = Task.CompletedTask;
|
||||||
_firstBackgroundUse = false;
|
_firstBackgroundUse = false;
|
||||||
|
@ -280,8 +289,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
return segments;
|
return segments;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ResourceBindingSegment[][] BuildBindingSegments(ReadOnlyCollection<ResourceUsageCollection> setUsages)
|
private static ResourceBindingSegment[][] BuildBindingSegments(ReadOnlyCollection<ResourceUsageCollection> setUsages, out bool usesBufferTextures)
|
||||||
{
|
{
|
||||||
|
usesBufferTextures = false;
|
||||||
|
|
||||||
ResourceBindingSegment[][] segments = new ResourceBindingSegment[setUsages.Count][];
|
ResourceBindingSegment[][] segments = new ResourceBindingSegment[setUsages.Count][];
|
||||||
|
|
||||||
for (int setIndex = 0; setIndex < setUsages.Count; setIndex++)
|
for (int setIndex = 0; setIndex < setUsages.Count; setIndex++)
|
||||||
|
@ -295,6 +306,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
ResourceUsage usage = setUsages[setIndex].Usages[index];
|
ResourceUsage usage = setUsages[setIndex].Usages[index];
|
||||||
|
|
||||||
|
if (usage.Type == ResourceType.BufferTexture)
|
||||||
|
{
|
||||||
|
usesBufferTextures = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (currentUsage.Binding + currentCount != usage.Binding ||
|
if (currentUsage.Binding + currentCount != usage.Binding ||
|
||||||
currentUsage.Type != usage.Type ||
|
currentUsage.Type != usage.Type ||
|
||||||
currentUsage.Stages != usage.Stages ||
|
currentUsage.Stages != usage.Stages ||
|
||||||
|
@ -365,6 +381,73 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
return templates;
|
return templates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PipelineStageFlags GetPipelineStages(ResourceStages stages)
|
||||||
|
{
|
||||||
|
PipelineStageFlags result = 0;
|
||||||
|
|
||||||
|
if ((stages & ResourceStages.Compute) != 0)
|
||||||
|
{
|
||||||
|
result |= PipelineStageFlags.ComputeShaderBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((stages & ResourceStages.Vertex) != 0)
|
||||||
|
{
|
||||||
|
result |= PipelineStageFlags.VertexShaderBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((stages & ResourceStages.Fragment) != 0)
|
||||||
|
{
|
||||||
|
result |= PipelineStageFlags.FragmentShaderBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((stages & ResourceStages.Geometry) != 0)
|
||||||
|
{
|
||||||
|
result |= PipelineStageFlags.GeometryShaderBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((stages & ResourceStages.TessellationControl) != 0)
|
||||||
|
{
|
||||||
|
result |= PipelineStageFlags.TessellationControlShaderBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((stages & ResourceStages.TessellationEvaluation) != 0)
|
||||||
|
{
|
||||||
|
result |= PipelineStageFlags.TessellationEvaluationShaderBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private (PipelineStageFlags Buffer, PipelineStageFlags Texture) BuildIncoherentStages(ReadOnlyCollection<ResourceUsageCollection> setUsages)
|
||||||
|
{
|
||||||
|
PipelineStageFlags buffer = PipelineStageFlags.None;
|
||||||
|
PipelineStageFlags texture = PipelineStageFlags.None;
|
||||||
|
|
||||||
|
foreach (var set in setUsages)
|
||||||
|
{
|
||||||
|
foreach (var range in set.Usages)
|
||||||
|
{
|
||||||
|
if (range.Write)
|
||||||
|
{
|
||||||
|
PipelineStageFlags stages = GetPipelineStages(range.Stages);
|
||||||
|
|
||||||
|
switch (range.Type)
|
||||||
|
{
|
||||||
|
case ResourceType.Image:
|
||||||
|
texture |= stages;
|
||||||
|
break;
|
||||||
|
case ResourceType.StorageBuffer:
|
||||||
|
case ResourceType.BufferImage:
|
||||||
|
buffer |= stages;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (buffer, texture);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task BackgroundCompilation()
|
private async Task BackgroundCompilation()
|
||||||
{
|
{
|
||||||
await Task.WhenAll(_shaders.Select(shader => shader.CompileTask));
|
await Task.WhenAll(_shaders.Select(shader => shader.CompileTask));
|
||||||
|
|
|
@ -407,7 +407,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
ImageLayout.General,
|
ImageLayout.General,
|
||||||
ImageLayout.General);
|
ImageLayout.General);
|
||||||
|
|
||||||
var subpassDependency = PipelineConverter.CreateSubpassDependency2();
|
var subpassDependency = PipelineConverter.CreateSubpassDependency2(gd);
|
||||||
|
|
||||||
fixed (AttachmentDescription2* pAttachmentDescs = attachmentDescs)
|
fixed (AttachmentDescription2* pAttachmentDescs = attachmentDescs)
|
||||||
{
|
{
|
||||||
|
|
|
@ -38,6 +38,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
public TextureCreateInfo Info => _info;
|
public TextureCreateInfo Info => _info;
|
||||||
|
|
||||||
|
public bool Disposed { get; private set; }
|
||||||
|
|
||||||
private readonly Image _image;
|
private readonly Image _image;
|
||||||
private readonly Auto<DisposableImage> _imageAuto;
|
private readonly Auto<DisposableImage> _imageAuto;
|
||||||
private readonly Auto<MemoryAllocation> _allocationAuto;
|
private readonly Auto<MemoryAllocation> _allocationAuto;
|
||||||
|
@ -433,6 +435,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
return FormatCapabilities.IsD24S8(Info.Format) && VkFormat == VkFormat.D32SfloatS8Uint;
|
return FormatCapabilities.IsD24S8(Info.Format) && VkFormat == VkFormat.D32SfloatS8Uint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddStoreOpUsage(bool depthStencil)
|
||||||
|
{
|
||||||
|
_lastModificationStage = depthStencil ?
|
||||||
|
PipelineStageFlags.LateFragmentTestsBit :
|
||||||
|
PipelineStageFlags.ColorAttachmentOutputBit;
|
||||||
|
|
||||||
|
_lastModificationAccess = depthStencil ?
|
||||||
|
AccessFlags.DepthStencilAttachmentWriteBit :
|
||||||
|
AccessFlags.ColorAttachmentWriteBit;
|
||||||
|
}
|
||||||
|
|
||||||
public void QueueLoadOpBarrier(CommandBufferScoped cbs, bool depthStencil)
|
public void QueueLoadOpBarrier(CommandBufferScoped cbs, bool depthStencil)
|
||||||
{
|
{
|
||||||
PipelineStageFlags srcStageFlags = _lastReadStage | _lastModificationStage;
|
PipelineStageFlags srcStageFlags = _lastReadStage | _lastModificationStage;
|
||||||
|
@ -458,7 +471,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
_info.GetLayers(),
|
_info.GetLayers(),
|
||||||
_info.Levels);
|
_info.Levels);
|
||||||
|
|
||||||
_gd.Barriers.QueueBarrier(barrier, srcStageFlags, dstStageFlags);
|
_gd.Barriers.QueueBarrier(barrier, this, srcStageFlags, dstStageFlags);
|
||||||
|
|
||||||
_lastReadStage = PipelineStageFlags.None;
|
_lastReadStage = PipelineStageFlags.None;
|
||||||
_lastReadAccess = AccessFlags.None;
|
_lastReadAccess = AccessFlags.None;
|
||||||
|
@ -491,7 +504,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
_info.GetLayers(),
|
_info.GetLayers(),
|
||||||
_info.Levels);
|
_info.Levels);
|
||||||
|
|
||||||
_gd.Barriers.QueueBarrier(barrier, _lastModificationStage, dstStageFlags);
|
_gd.Barriers.QueueBarrier(barrier, this, _lastModificationStage, dstStageFlags);
|
||||||
|
|
||||||
_lastModificationAccess = AccessFlags.None;
|
_lastModificationAccess = AccessFlags.None;
|
||||||
}
|
}
|
||||||
|
@ -514,6 +527,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
Disposed = true;
|
||||||
|
|
||||||
if (_aliasedStorages != null)
|
if (_aliasedStorages != null)
|
||||||
{
|
{
|
||||||
foreach (var storage in _aliasedStorages.Values)
|
foreach (var storage in _aliasedStorages.Values)
|
||||||
|
|
|
@ -993,7 +993,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public (Auto<DisposableRenderPass> renderPass, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
|
public (RenderPassHolder rpHolder, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
|
||||||
VulkanRenderer gd,
|
VulkanRenderer gd,
|
||||||
Device device,
|
Device device,
|
||||||
CommandBufferScoped cbs,
|
CommandBufferScoped cbs,
|
||||||
|
@ -1006,7 +1006,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
rpHolder = new RenderPassHolder(gd, device, key, fb);
|
rpHolder = new RenderPassHolder(gd, device, key, fb);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (rpHolder.GetRenderPass(), rpHolder.GetFramebuffer(gd, cbs, fb));
|
return (rpHolder, rpHolder.GetFramebuffer(gd, cbs, fb));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddRenderPass(RenderPassCacheKey key, RenderPassHolder renderPass)
|
public void AddRenderPass(RenderPassCacheKey key, RenderPassHolder renderPass)
|
||||||
|
|
|
@ -69,27 +69,32 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
DriverId.AmdProprietary => "AMD",
|
DriverId.AmdProprietary => "AMD",
|
||||||
DriverId.AmdOpenSource => "AMD (Open)",
|
DriverId.AmdOpenSource => "AMD (Open)",
|
||||||
DriverId.ArmProprietary => "ARM",
|
|
||||||
DriverId.BroadcomProprietary => "Broadcom",
|
|
||||||
DriverId.CoreaviProprietary => "CoreAVI",
|
|
||||||
DriverId.GgpProprietary => "GGP",
|
|
||||||
DriverId.GoogleSwiftshader => "SwiftShader",
|
|
||||||
DriverId.ImaginationProprietary => "Imagination",
|
|
||||||
DriverId.IntelOpenSourceMesa => "Intel (Open)",
|
|
||||||
DriverId.IntelProprietaryWindows => "Intel",
|
|
||||||
DriverId.JuiceProprietary => "Juice",
|
|
||||||
DriverId.MesaDozen => "Dozen",
|
|
||||||
DriverId.MesaLlvmpipe => "LLVMpipe",
|
|
||||||
DriverId.MesaPanvk => "PanVK",
|
|
||||||
DriverId.MesaRadv => "RADV",
|
DriverId.MesaRadv => "RADV",
|
||||||
|
DriverId.NvidiaProprietary => "NVIDIA",
|
||||||
|
DriverId.IntelProprietaryWindows => "Intel",
|
||||||
|
DriverId.IntelOpenSourceMesa => "Intel (Open)",
|
||||||
|
DriverId.ImaginationProprietary => "Imagination",
|
||||||
|
DriverId.QualcommProprietary => "Qualcomm",
|
||||||
|
DriverId.ArmProprietary => "ARM",
|
||||||
|
DriverId.GoogleSwiftshader => "SwiftShader",
|
||||||
|
DriverId.GgpProprietary => "GGP",
|
||||||
|
DriverId.BroadcomProprietary => "Broadcom",
|
||||||
|
DriverId.MesaLlvmpipe => "LLVMpipe",
|
||||||
|
DriverId.Moltenvk => "MoltenVK",
|
||||||
|
DriverId.CoreaviProprietary => "CoreAVI",
|
||||||
|
DriverId.JuiceProprietary => "Juice",
|
||||||
|
DriverId.VerisiliconProprietary => "Verisilicon",
|
||||||
DriverId.MesaTurnip => "Turnip",
|
DriverId.MesaTurnip => "Turnip",
|
||||||
DriverId.MesaV3DV => "V3DV",
|
DriverId.MesaV3DV => "V3DV",
|
||||||
DriverId.MesaVenus => "Venus",
|
DriverId.MesaPanvk => "PanVK",
|
||||||
DriverId.Moltenvk => "MoltenVK",
|
|
||||||
DriverId.NvidiaProprietary => "NVIDIA",
|
|
||||||
DriverId.QualcommProprietary => "Qualcomm",
|
|
||||||
DriverId.SamsungProprietary => "Samsung",
|
DriverId.SamsungProprietary => "Samsung",
|
||||||
DriverId.VerisiliconProprietary => "Verisilicon",
|
DriverId.MesaVenus => "Venus",
|
||||||
|
DriverId.MesaDozen => "Dozen",
|
||||||
|
|
||||||
|
// TODO: Use real enum when we have an up to date Silk.NET.
|
||||||
|
(DriverId)24 => "NVK",
|
||||||
|
(DriverId)25 => "Imagination (Open)",
|
||||||
|
(DriverId)26 => "Honeykrisp",
|
||||||
_ => id.ToString(),
|
_ => id.ToString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,9 +88,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
internal bool IsAmdGcn { get; private set; }
|
internal bool IsAmdGcn { get; private set; }
|
||||||
internal bool IsNvidiaPreTuring { get; private set; }
|
internal bool IsNvidiaPreTuring { get; private set; }
|
||||||
internal bool IsIntelArc { get; private set; }
|
internal bool IsIntelArc { get; private set; }
|
||||||
|
internal bool IsQualcommProprietary { get; private set; }
|
||||||
internal bool IsMoltenVk { get; private set; }
|
internal bool IsMoltenVk { get; private set; }
|
||||||
internal bool IsTBDR { get; private set; }
|
internal bool IsTBDR { get; private set; }
|
||||||
internal bool IsSharedMemory { get; private set; }
|
internal bool IsSharedMemory { get; private set; }
|
||||||
|
|
||||||
public string GpuVendor { get; private set; }
|
public string GpuVendor { get; private set; }
|
||||||
public string GpuDriver { get; private set; }
|
public string GpuDriver { get; private set; }
|
||||||
public string GpuRenderer { get; private set; }
|
public string GpuRenderer { get; private set; }
|
||||||
|
@ -353,7 +355,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
IsNvidiaPreTuring = gpuNumber < 2000;
|
IsNvidiaPreTuring = gpuNumber < 2000;
|
||||||
}
|
}
|
||||||
else if (GpuDriver.Contains("TITAN") && !GpuDriver.Contains("RTX"))
|
else if (GpuRenderer.Contains("TITAN") && !GpuRenderer.Contains("RTX"))
|
||||||
{
|
{
|
||||||
IsNvidiaPreTuring = true;
|
IsNvidiaPreTuring = true;
|
||||||
}
|
}
|
||||||
|
@ -363,6 +365,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
IsIntelArc = GpuRenderer.StartsWith("Intel(R) Arc(TM)");
|
IsIntelArc = GpuRenderer.StartsWith("Intel(R) Arc(TM)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IsQualcommProprietary = hasDriverProperties && driverProperties.DriverID == DriverId.QualcommProprietary;
|
||||||
|
|
||||||
ulong minResourceAlignment = Math.Max(
|
ulong minResourceAlignment = Math.Max(
|
||||||
Math.Max(
|
Math.Max(
|
||||||
properties.Limits.MinStorageBufferOffsetAlignment,
|
properties.Limits.MinStorageBufferOffsetAlignment,
|
||||||
|
@ -422,7 +426,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtExternalMemoryHost hostMemoryApi);
|
Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtExternalMemoryHost hostMemoryApi);
|
||||||
HostMemoryAllocator = new HostMemoryAllocator(MemoryAllocator, Api, hostMemoryApi, _device);
|
HostMemoryAllocator = new HostMemoryAllocator(MemoryAllocator, Api, hostMemoryApi, _device);
|
||||||
|
|
||||||
CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex);
|
CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex, IsQualcommProprietary);
|
||||||
|
|
||||||
PipelineLayoutCache = new PipelineLayoutCache();
|
PipelineLayoutCache = new PipelineLayoutCache();
|
||||||
|
|
||||||
|
@ -701,7 +705,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
GpuVendor,
|
GpuVendor,
|
||||||
memoryType: memoryType,
|
memoryType: memoryType,
|
||||||
hasFrontFacingBug: IsIntelWindows,
|
hasFrontFacingBug: IsIntelWindows,
|
||||||
hasVectorIndexingBug: Vendor == Vendor.Qualcomm,
|
hasVectorIndexingBug: IsQualcommProprietary,
|
||||||
needsFragmentOutputSpecialization: IsMoltenVk,
|
needsFragmentOutputSpecialization: IsMoltenVk,
|
||||||
reduceShaderPrecision: IsMoltenVk,
|
reduceShaderPrecision: IsMoltenVk,
|
||||||
supportsAstcCompression: features2.Features.TextureCompressionAstcLdr && supportsAstcFormats,
|
supportsAstcCompression: features2.Features.TextureCompressionAstcLdr && supportsAstcFormats,
|
||||||
|
@ -948,6 +952,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
ScreenCaptured?.Invoke(this, bitmap);
|
ScreenCaptured?.Invoke(this, bitmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool SupportsRenderPassBarrier(PipelineStageFlags flags)
|
||||||
|
{
|
||||||
|
return !(IsMoltenVk || IsQualcommProprietary);
|
||||||
|
}
|
||||||
|
|
||||||
public unsafe void Dispose()
|
public unsafe void Dispose()
|
||||||
{
|
{
|
||||||
if (!_initialized)
|
if (!_initialized)
|
||||||
|
|
|
@ -623,7 +623,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
public override void SetSize(int width, int height)
|
public override void SetSize(int width, int height)
|
||||||
{
|
{
|
||||||
// Not needed as we can get the size from the surface.
|
// We don't need to use width and height as we can get the size from the surface.
|
||||||
|
_swapchainIsDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ChangeVSyncMode(bool vsyncEnabled)
|
public override void ChangeVSyncMode(bool vsyncEnabled)
|
||||||
|
|
|
@ -7,6 +7,7 @@ using Ryujinx.Common.SystemInterop;
|
||||||
using Ryujinx.Modules;
|
using Ryujinx.Modules;
|
||||||
using Ryujinx.SDL2.Common;
|
using Ryujinx.SDL2.Common;
|
||||||
using Ryujinx.UI;
|
using Ryujinx.UI;
|
||||||
|
using Ryujinx.UI.App.Common;
|
||||||
using Ryujinx.UI.Common;
|
using Ryujinx.UI.Common;
|
||||||
using Ryujinx.UI.Common.Configuration;
|
using Ryujinx.UI.Common.Configuration;
|
||||||
using Ryujinx.UI.Common.Helper;
|
using Ryujinx.UI.Common.Helper;
|
||||||
|
@ -322,7 +323,35 @@ namespace Ryujinx
|
||||||
|
|
||||||
if (CommandLineState.LaunchPathArg != null)
|
if (CommandLineState.LaunchPathArg != null)
|
||||||
{
|
{
|
||||||
mainWindow.RunApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg);
|
if (mainWindow.ApplicationLibrary.TryGetApplicationsFromFile(CommandLineState.LaunchPathArg, out List<ApplicationData> applications))
|
||||||
|
{
|
||||||
|
ApplicationData applicationData;
|
||||||
|
|
||||||
|
if (CommandLineState.LaunchApplicationId != null)
|
||||||
|
{
|
||||||
|
applicationData = applications.Find(application => application.IdString == CommandLineState.LaunchApplicationId);
|
||||||
|
|
||||||
|
if (applicationData != null)
|
||||||
|
{
|
||||||
|
mainWindow.RunApplication(applicationData, CommandLineState.StartFullscreenArg);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, $"Couldn't find requested application id '{CommandLineState.LaunchApplicationId}' in '{CommandLineState.LaunchPathArg}'.");
|
||||||
|
UserErrorDialog.CreateUserErrorDialog(UserError.ApplicationNotFound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
applicationData = applications[0];
|
||||||
|
mainWindow.RunApplication(applicationData, CommandLineState.StartFullscreenArg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, $"Couldn't find any application in '{CommandLineState.LaunchPathArg}'.");
|
||||||
|
UserErrorDialog.CreateUserErrorDialog(UserError.ApplicationNotFound);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
|
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
|
||||||
|
|
|
@ -37,7 +37,9 @@ using Ryujinx.UI.Windows;
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using SPB.Graphics.Vulkan;
|
using SPB.Graphics.Vulkan;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -60,7 +62,6 @@ namespace Ryujinx.UI
|
||||||
|
|
||||||
private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution;
|
private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution;
|
||||||
|
|
||||||
private readonly ApplicationLibrary _applicationLibrary;
|
|
||||||
private readonly GtkHostUIHandler _uiHandler;
|
private readonly GtkHostUIHandler _uiHandler;
|
||||||
private readonly AutoResetEvent _deviceExitStatus;
|
private readonly AutoResetEvent _deviceExitStatus;
|
||||||
private readonly ListStore _tableStore;
|
private readonly ListStore _tableStore;
|
||||||
|
@ -69,11 +70,12 @@ namespace Ryujinx.UI
|
||||||
private bool _gameLoaded;
|
private bool _gameLoaded;
|
||||||
private bool _ending;
|
private bool _ending;
|
||||||
|
|
||||||
private string _currentEmulatedGamePath = null;
|
private ApplicationData _currentApplicationData = null;
|
||||||
|
|
||||||
private string _lastScannedAmiiboId = "";
|
private string _lastScannedAmiiboId = "";
|
||||||
private bool _lastScannedAmiiboShowAll = false;
|
private bool _lastScannedAmiiboShowAll = false;
|
||||||
|
|
||||||
|
public readonly ApplicationLibrary ApplicationLibrary;
|
||||||
public RendererWidgetBase RendererWidget;
|
public RendererWidgetBase RendererWidget;
|
||||||
public InputManager InputManager;
|
public InputManager InputManager;
|
||||||
|
|
||||||
|
@ -180,8 +182,12 @@ namespace Ryujinx.UI
|
||||||
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, CommandLineState.Profile);
|
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, CommandLineState.Profile);
|
||||||
_userChannelPersistence = new UserChannelPersistence();
|
_userChannelPersistence = new UserChannelPersistence();
|
||||||
|
|
||||||
|
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||||
|
? IntegrityCheckLevel.ErrorOnInvalid
|
||||||
|
: IntegrityCheckLevel.None;
|
||||||
|
|
||||||
// Instantiate GUI objects.
|
// Instantiate GUI objects.
|
||||||
_applicationLibrary = new ApplicationLibrary(_virtualFileSystem);
|
ApplicationLibrary = new ApplicationLibrary(_virtualFileSystem, checkLevel);
|
||||||
_uiHandler = new GtkHostUIHandler(this);
|
_uiHandler = new GtkHostUIHandler(this);
|
||||||
_deviceExitStatus = new AutoResetEvent(false);
|
_deviceExitStatus = new AutoResetEvent(false);
|
||||||
|
|
||||||
|
@ -190,8 +196,8 @@ namespace Ryujinx.UI
|
||||||
FocusInEvent += MainWindow_FocusInEvent;
|
FocusInEvent += MainWindow_FocusInEvent;
|
||||||
FocusOutEvent += MainWindow_FocusOutEvent;
|
FocusOutEvent += MainWindow_FocusOutEvent;
|
||||||
|
|
||||||
_applicationLibrary.ApplicationAdded += Application_Added;
|
ApplicationLibrary.ApplicationAdded += Application_Added;
|
||||||
_applicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated;
|
ApplicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated;
|
||||||
|
|
||||||
_fileMenu.StateChanged += FileMenu_StateChanged;
|
_fileMenu.StateChanged += FileMenu_StateChanged;
|
||||||
_actionMenu.StateChanged += ActionMenu_StateChanged;
|
_actionMenu.StateChanged += ActionMenu_StateChanged;
|
||||||
|
@ -732,7 +738,7 @@ namespace Ryujinx.UI
|
||||||
|
|
||||||
Thread applicationLibraryThread = new(() =>
|
Thread applicationLibraryThread = new(() =>
|
||||||
{
|
{
|
||||||
_applicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language);
|
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language);
|
||||||
|
|
||||||
_updatingGameTable = false;
|
_updatingGameTable = false;
|
||||||
})
|
})
|
||||||
|
@ -783,7 +789,7 @@ namespace Ryujinx.UI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool LoadApplication(string path, bool isFirmwareTitle)
|
private bool LoadApplication(string path, ulong applicationId, bool isFirmwareTitle)
|
||||||
{
|
{
|
||||||
SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
|
SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
|
||||||
|
|
||||||
|
@ -857,7 +863,7 @@ namespace Ryujinx.UI
|
||||||
case ".xci":
|
case ".xci":
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
|
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
|
||||||
|
|
||||||
return _emulationContext.LoadXci(path);
|
return _emulationContext.LoadXci(path, applicationId);
|
||||||
case ".nca":
|
case ".nca":
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
|
Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
|
||||||
|
|
||||||
|
@ -866,7 +872,7 @@ namespace Ryujinx.UI
|
||||||
case ".pfs0":
|
case ".pfs0":
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
|
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
|
||||||
|
|
||||||
return _emulationContext.LoadNsp(path);
|
return _emulationContext.LoadNsp(path, applicationId);
|
||||||
default:
|
default:
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
|
Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
|
||||||
try
|
try
|
||||||
|
@ -887,7 +893,7 @@ namespace Ryujinx.UI
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RunApplication(string path, bool startFullscreen = false)
|
public void RunApplication(ApplicationData application, bool startFullscreen = false)
|
||||||
{
|
{
|
||||||
if (_gameLoaded)
|
if (_gameLoaded)
|
||||||
{
|
{
|
||||||
|
@ -909,14 +915,14 @@ namespace Ryujinx.UI
|
||||||
|
|
||||||
bool isFirmwareTitle = false;
|
bool isFirmwareTitle = false;
|
||||||
|
|
||||||
if (path.StartsWith("@SystemContent"))
|
if (application.Path.StartsWith("@SystemContent"))
|
||||||
{
|
{
|
||||||
path = VirtualFileSystem.SwitchPathToSystemPath(path);
|
application.Path = VirtualFileSystem.SwitchPathToSystemPath(application.Path);
|
||||||
|
|
||||||
isFirmwareTitle = true;
|
isFirmwareTitle = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!LoadApplication(path, isFirmwareTitle))
|
if (!LoadApplication(application.Path, application.Id, isFirmwareTitle))
|
||||||
{
|
{
|
||||||
_emulationContext.Dispose();
|
_emulationContext.Dispose();
|
||||||
SwitchToGameTable();
|
SwitchToGameTable();
|
||||||
|
@ -926,7 +932,7 @@ namespace Ryujinx.UI
|
||||||
|
|
||||||
SetupProgressUIHandlers();
|
SetupProgressUIHandlers();
|
||||||
|
|
||||||
_currentEmulatedGamePath = path;
|
_currentApplicationData = application;
|
||||||
|
|
||||||
_deviceExitStatus.Reset();
|
_deviceExitStatus.Reset();
|
||||||
|
|
||||||
|
@ -1165,7 +1171,7 @@ namespace Ryujinx.UI
|
||||||
_tableStore.AppendValues(
|
_tableStore.AppendValues(
|
||||||
args.AppData.Favorite,
|
args.AppData.Favorite,
|
||||||
new Gdk.Pixbuf(args.AppData.Icon, 75, 75),
|
new Gdk.Pixbuf(args.AppData.Icon, 75, 75),
|
||||||
$"{args.AppData.TitleName}\n{args.AppData.TitleId.ToUpper()}",
|
$"{args.AppData.Name}\n{args.AppData.IdString.ToUpper()}",
|
||||||
args.AppData.Developer,
|
args.AppData.Developer,
|
||||||
args.AppData.Version,
|
args.AppData.Version,
|
||||||
args.AppData.TimePlayedString,
|
args.AppData.TimePlayedString,
|
||||||
|
@ -1253,9 +1259,22 @@ namespace Ryujinx.UI
|
||||||
{
|
{
|
||||||
_gameTableSelection.GetSelected(out TreeIter treeIter);
|
_gameTableSelection.GetSelected(out TreeIter treeIter);
|
||||||
|
|
||||||
string path = (string)_tableStore.GetValue(treeIter, 9);
|
ApplicationData application = new()
|
||||||
|
{
|
||||||
|
Favorite = (bool)_tableStore.GetValue(treeIter, 0),
|
||||||
|
Name = ((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[0],
|
||||||
|
Id = ulong.Parse(((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[1], NumberStyles.HexNumber),
|
||||||
|
Developer = (string)_tableStore.GetValue(treeIter, 3),
|
||||||
|
Version = (string)_tableStore.GetValue(treeIter, 4),
|
||||||
|
TimePlayed = ValueFormatUtils.ParseTimeSpan((string)_tableStore.GetValue(treeIter, 5)),
|
||||||
|
LastPlayed = ValueFormatUtils.ParseDateTime((string)_tableStore.GetValue(treeIter, 6)),
|
||||||
|
FileExtension = (string)_tableStore.GetValue(treeIter, 7),
|
||||||
|
FileSize = ValueFormatUtils.ParseFileSize((string)_tableStore.GetValue(treeIter, 8)),
|
||||||
|
Path = (string)_tableStore.GetValue(treeIter, 9),
|
||||||
|
ControlHolder = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10),
|
||||||
|
};
|
||||||
|
|
||||||
RunApplication(path);
|
RunApplication(application);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void VSyncStatus_Clicked(object sender, ButtonReleaseEventArgs args)
|
private void VSyncStatus_Clicked(object sender, ButtonReleaseEventArgs args)
|
||||||
|
@ -1313,13 +1332,22 @@ namespace Ryujinx.UI
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string titleFilePath = _tableStore.GetValue(treeIter, 9).ToString();
|
ApplicationData application = new()
|
||||||
string titleName = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[0];
|
{
|
||||||
string titleId = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[1].ToLower();
|
Favorite = (bool)_tableStore.GetValue(treeIter, 0),
|
||||||
|
Name = ((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[0],
|
||||||
|
Id = ulong.Parse(((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[1], NumberStyles.HexNumber),
|
||||||
|
Developer = (string)_tableStore.GetValue(treeIter, 3),
|
||||||
|
Version = (string)_tableStore.GetValue(treeIter, 4),
|
||||||
|
TimePlayed = ValueFormatUtils.ParseTimeSpan((string)_tableStore.GetValue(treeIter, 5)),
|
||||||
|
LastPlayed = ValueFormatUtils.ParseDateTime((string)_tableStore.GetValue(treeIter, 6)),
|
||||||
|
FileExtension = (string)_tableStore.GetValue(treeIter, 7),
|
||||||
|
FileSize = ValueFormatUtils.ParseFileSize((string)_tableStore.GetValue(treeIter, 8)),
|
||||||
|
Path = (string)_tableStore.GetValue(treeIter, 9),
|
||||||
|
ControlHolder = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10),
|
||||||
|
};
|
||||||
|
|
||||||
BlitStruct<ApplicationControlProperty> controlData = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10);
|
_ = new GameTableContextMenu(this, _virtualFileSystem, _accountManager, _libHacHorizonManager.RyujinxClient, application);
|
||||||
|
|
||||||
_ = new GameTableContextMenu(this, _virtualFileSystem, _accountManager, _libHacHorizonManager.RyujinxClient, titleFilePath, titleName, titleId, controlData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Load_Application_File(object sender, EventArgs args)
|
private void Load_Application_File(object sender, EventArgs args)
|
||||||
|
@ -1341,7 +1369,15 @@ namespace Ryujinx.UI
|
||||||
|
|
||||||
if (fileChooser.Run() == (int)ResponseType.Accept)
|
if (fileChooser.Run() == (int)ResponseType.Accept)
|
||||||
{
|
{
|
||||||
RunApplication(fileChooser.Filename);
|
if (ApplicationLibrary.TryGetApplicationsFromFile(fileChooser.Filename,
|
||||||
|
out List<ApplicationData> applications))
|
||||||
|
{
|
||||||
|
RunApplication(applications[0]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GtkDialog.CreateErrorDialog("No applications found in selected file.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1351,7 +1387,13 @@ namespace Ryujinx.UI
|
||||||
|
|
||||||
if (fileChooser.Run() == (int)ResponseType.Accept)
|
if (fileChooser.Run() == (int)ResponseType.Accept)
|
||||||
{
|
{
|
||||||
RunApplication(fileChooser.Filename);
|
ApplicationData applicationData = new()
|
||||||
|
{
|
||||||
|
Name = System.IO.Path.GetFileNameWithoutExtension(fileChooser.Filename),
|
||||||
|
Path = fileChooser.Filename,
|
||||||
|
};
|
||||||
|
|
||||||
|
RunApplication(applicationData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1366,7 +1408,14 @@ namespace Ryujinx.UI
|
||||||
{
|
{
|
||||||
string contentPath = _contentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program);
|
string contentPath = _contentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program);
|
||||||
|
|
||||||
RunApplication(contentPath);
|
ApplicationData applicationData = new()
|
||||||
|
{
|
||||||
|
Name = "miiEdit",
|
||||||
|
Id = 0x0100000000001009ul,
|
||||||
|
Path = contentPath,
|
||||||
|
};
|
||||||
|
|
||||||
|
RunApplication(applicationData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Open_Ryu_Folder(object sender, EventArgs args)
|
private void Open_Ryu_Folder(object sender, EventArgs args)
|
||||||
|
@ -1646,13 +1695,13 @@ namespace Ryujinx.UI
|
||||||
{
|
{
|
||||||
_userChannelPersistence.ShouldRestart = false;
|
_userChannelPersistence.ShouldRestart = false;
|
||||||
|
|
||||||
RunApplication(_currentEmulatedGamePath);
|
RunApplication(_currentApplicationData);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// otherwise, clear state.
|
// otherwise, clear state.
|
||||||
_userChannelPersistence = new UserChannelPersistence();
|
_userChannelPersistence = new UserChannelPersistence();
|
||||||
_currentEmulatedGamePath = null;
|
_currentApplicationData = null;
|
||||||
_actionMenu.Sensitive = false;
|
_actionMenu.Sensitive = false;
|
||||||
_firmwareInstallFile.Sensitive = true;
|
_firmwareInstallFile.Sensitive = true;
|
||||||
_firmwareInstallDirectory.Sensitive = true;
|
_firmwareInstallDirectory.Sensitive = true;
|
||||||
|
@ -1714,7 +1763,7 @@ namespace Ryujinx.UI
|
||||||
_emulationContext.Processes.ActiveApplication.ProgramId,
|
_emulationContext.Processes.ActiveApplication.ProgramId,
|
||||||
_emulationContext.Processes.ActiveApplication.ApplicationControlProperties
|
_emulationContext.Processes.ActiveApplication.ApplicationControlProperties
|
||||||
.Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString(),
|
.Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString(),
|
||||||
_currentEmulatedGamePath);
|
_currentApplicationData.Path);
|
||||||
|
|
||||||
window.Destroyed += CheatWindow_Destroyed;
|
window.Destroyed += CheatWindow_Destroyed;
|
||||||
window.Show();
|
window.Show();
|
||||||
|
|
|
@ -16,6 +16,8 @@ using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
|
using Ryujinx.HLE.Utilities;
|
||||||
using Ryujinx.UI.App.Common;
|
using Ryujinx.UI.App.Common;
|
||||||
using Ryujinx.UI.Common.Configuration;
|
using Ryujinx.UI.Common.Configuration;
|
||||||
using Ryujinx.UI.Common.Helper;
|
using Ryujinx.UI.Common.Helper;
|
||||||
|
@ -23,7 +25,6 @@ using Ryujinx.UI.Windows;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -36,17 +37,13 @@ namespace Ryujinx.UI.Widgets
|
||||||
private readonly VirtualFileSystem _virtualFileSystem;
|
private readonly VirtualFileSystem _virtualFileSystem;
|
||||||
private readonly AccountManager _accountManager;
|
private readonly AccountManager _accountManager;
|
||||||
private readonly HorizonClient _horizonClient;
|
private readonly HorizonClient _horizonClient;
|
||||||
private readonly BlitStruct<ApplicationControlProperty> _controlData;
|
|
||||||
|
|
||||||
private readonly string _titleFilePath;
|
private readonly ApplicationData _applicationData;
|
||||||
private readonly string _titleName;
|
|
||||||
private readonly string _titleIdText;
|
|
||||||
private readonly ulong _titleId;
|
|
||||||
|
|
||||||
private MessageDialog _dialog;
|
private MessageDialog _dialog;
|
||||||
private bool _cancel;
|
private bool _cancel;
|
||||||
|
|
||||||
public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient, string titleFilePath, string titleName, string titleId, BlitStruct<ApplicationControlProperty> controlData)
|
public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient, ApplicationData applicationData)
|
||||||
{
|
{
|
||||||
_parent = parent;
|
_parent = parent;
|
||||||
|
|
||||||
|
@ -55,23 +52,22 @@ namespace Ryujinx.UI.Widgets
|
||||||
_virtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
_accountManager = accountManager;
|
_accountManager = accountManager;
|
||||||
_horizonClient = horizonClient;
|
_horizonClient = horizonClient;
|
||||||
_titleFilePath = titleFilePath;
|
_applicationData = applicationData;
|
||||||
_titleName = titleName;
|
|
||||||
_titleIdText = titleId;
|
|
||||||
_controlData = controlData;
|
|
||||||
|
|
||||||
if (!ulong.TryParse(_titleIdText, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out _titleId))
|
if (!_applicationData.ControlHolder.ByteSpan.IsZeros())
|
||||||
{
|
{
|
||||||
GtkDialog.CreateErrorDialog("The selected game did not have a valid Title Id");
|
_openSaveUserDirMenuItem.Sensitive = _applicationData.ControlHolder.Value.UserAccountSaveDataSize > 0;
|
||||||
|
_openSaveDeviceDirMenuItem.Sensitive = _applicationData.ControlHolder.Value.DeviceSaveDataSize > 0;
|
||||||
return;
|
_openSaveBcatDirMenuItem.Sensitive = _applicationData.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_openSaveUserDirMenuItem.Sensitive = false;
|
||||||
|
_openSaveDeviceDirMenuItem.Sensitive = false;
|
||||||
|
_openSaveBcatDirMenuItem.Sensitive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_openSaveUserDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0;
|
string fileExt = System.IO.Path.GetExtension(_applicationData.Path).ToLower();
|
||||||
_openSaveDeviceDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0;
|
|
||||||
_openSaveBcatDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0;
|
|
||||||
|
|
||||||
string fileExt = System.IO.Path.GetExtension(_titleFilePath).ToLower();
|
|
||||||
bool hasNca = fileExt == ".nca" || fileExt == ".nsp" || fileExt == ".pfs0" || fileExt == ".xci";
|
bool hasNca = fileExt == ".nca" || fileExt == ".nsp" || fileExt == ".pfs0" || fileExt == ".xci";
|
||||||
|
|
||||||
_extractRomFsMenuItem.Sensitive = hasNca;
|
_extractRomFsMenuItem.Sensitive = hasNca;
|
||||||
|
@ -137,7 +133,7 @@ namespace Ryujinx.UI.Widgets
|
||||||
|
|
||||||
private void OpenSaveDir(in SaveDataFilter saveDataFilter)
|
private void OpenSaveDir(in SaveDataFilter saveDataFilter)
|
||||||
{
|
{
|
||||||
if (!TryFindSaveData(_titleName, _titleId, _controlData, in saveDataFilter, out ulong saveDataId))
|
if (!TryFindSaveData(_applicationData.Name, _applicationData.Id, _applicationData.ControlHolder, in saveDataFilter, out ulong saveDataId))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -190,7 +186,7 @@ namespace Ryujinx.UI.Widgets
|
||||||
{
|
{
|
||||||
Title = "Ryujinx - NCA Section Extractor",
|
Title = "Ryujinx - NCA Section Extractor",
|
||||||
Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Gtk3.UI.Common.Resources.Logo_Ryujinx.png"),
|
Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Gtk3.UI.Common.Resources.Logo_Ryujinx.png"),
|
||||||
SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(_titleFilePath)}...",
|
SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(_applicationData.Path)}...",
|
||||||
WindowPosition = WindowPosition.Center,
|
WindowPosition = WindowPosition.Center,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -202,29 +198,16 @@ namespace Ryujinx.UI.Widgets
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
using FileStream file = new(_titleFilePath, FileMode.Open, FileAccess.Read);
|
using FileStream file = new(_applicationData.Path, FileMode.Open, FileAccess.Read);
|
||||||
|
|
||||||
Nca mainNca = null;
|
Nca mainNca = null;
|
||||||
Nca patchNca = null;
|
Nca patchNca = null;
|
||||||
|
|
||||||
if ((System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".nsp") ||
|
if ((System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".nsp") ||
|
||||||
(System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".pfs0") ||
|
(System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".pfs0") ||
|
||||||
(System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".xci"))
|
(System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".xci"))
|
||||||
{
|
{
|
||||||
IFileSystem pfs;
|
IFileSystem pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(_applicationData.Path, _virtualFileSystem);
|
||||||
|
|
||||||
if (System.IO.Path.GetExtension(_titleFilePath) == ".xci")
|
|
||||||
{
|
|
||||||
Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage());
|
|
||||||
|
|
||||||
pfs = xci.OpenPartition(XciPartitionType.Secure);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var pfsTemp = new PartitionFileSystem();
|
|
||||||
pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
|
|
||||||
pfs = pfsTemp;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||||
{
|
{
|
||||||
|
@ -249,7 +232,7 @@ namespace Ryujinx.UI.Widgets
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".nca")
|
else if (System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".nca")
|
||||||
{
|
{
|
||||||
mainNca = new Nca(_virtualFileSystem.KeySet, file.AsStorage());
|
mainNca = new Nca(_virtualFileSystem.KeySet, file.AsStorage());
|
||||||
}
|
}
|
||||||
|
@ -266,7 +249,11 @@ namespace Ryujinx.UI.Widgets
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
(Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
|
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||||
|
? IntegrityCheckLevel.ErrorOnInvalid
|
||||||
|
: IntegrityCheckLevel.None;
|
||||||
|
|
||||||
|
(Nca updatePatchNca, _) = mainNca.GetUpdateData(_virtualFileSystem, checkLevel, programIndex, out _);
|
||||||
|
|
||||||
if (updatePatchNca != null)
|
if (updatePatchNca != null)
|
||||||
{
|
{
|
||||||
|
@ -460,44 +447,44 @@ namespace Ryujinx.UI.Widgets
|
||||||
private void OpenSaveUserDir_Clicked(object sender, EventArgs args)
|
private void OpenSaveUserDir_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
var userId = new LibHac.Fs.UserId((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low);
|
var userId = new LibHac.Fs.UserId((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low);
|
||||||
var saveDataFilter = SaveDataFilter.Make(_titleId, saveType: default, userId, saveDataId: default, index: default);
|
var saveDataFilter = SaveDataFilter.Make(_applicationData.Id, saveType: default, userId, saveDataId: default, index: default);
|
||||||
|
|
||||||
OpenSaveDir(in saveDataFilter);
|
OpenSaveDir(in saveDataFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenSaveDeviceDir_Clicked(object sender, EventArgs args)
|
private void OpenSaveDeviceDir_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
var saveDataFilter = SaveDataFilter.Make(_titleId, SaveDataType.Device, userId: default, saveDataId: default, index: default);
|
var saveDataFilter = SaveDataFilter.Make(_applicationData.Id, SaveDataType.Device, userId: default, saveDataId: default, index: default);
|
||||||
|
|
||||||
OpenSaveDir(in saveDataFilter);
|
OpenSaveDir(in saveDataFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenSaveBcatDir_Clicked(object sender, EventArgs args)
|
private void OpenSaveBcatDir_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
var saveDataFilter = SaveDataFilter.Make(_titleId, SaveDataType.Bcat, userId: default, saveDataId: default, index: default);
|
var saveDataFilter = SaveDataFilter.Make(_applicationData.Id, SaveDataType.Bcat, userId: default, saveDataId: default, index: default);
|
||||||
|
|
||||||
OpenSaveDir(in saveDataFilter);
|
OpenSaveDir(in saveDataFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ManageTitleUpdates_Clicked(object sender, EventArgs args)
|
private void ManageTitleUpdates_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
new TitleUpdateWindow(_parent, _virtualFileSystem, _titleIdText, _titleName).Show();
|
new TitleUpdateWindow(_parent, _virtualFileSystem, _applicationData).Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ManageDlc_Clicked(object sender, EventArgs args)
|
private void ManageDlc_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
new DlcWindow(_virtualFileSystem, _titleIdText, _titleName).Show();
|
new DlcWindow(_virtualFileSystem, _applicationData.IdString, _applicationData).Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ManageCheats_Clicked(object sender, EventArgs args)
|
private void ManageCheats_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
new CheatWindow(_virtualFileSystem, _titleId, _titleName, _titleFilePath).Show();
|
new CheatWindow(_virtualFileSystem, _applicationData.Id, _applicationData.Name, _applicationData.Path).Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenTitleModDir_Clicked(object sender, EventArgs args)
|
private void OpenTitleModDir_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
string modsBasePath = ModLoader.GetModsBasePath();
|
string modsBasePath = ModLoader.GetModsBasePath();
|
||||||
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, _titleIdText);
|
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, _applicationData.IdString);
|
||||||
|
|
||||||
OpenHelper.OpenFolder(titleModsPath);
|
OpenHelper.OpenFolder(titleModsPath);
|
||||||
}
|
}
|
||||||
|
@ -505,7 +492,7 @@ namespace Ryujinx.UI.Widgets
|
||||||
private void OpenTitleSdModDir_Clicked(object sender, EventArgs args)
|
private void OpenTitleSdModDir_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
string sdModsBasePath = ModLoader.GetSdModsBasePath();
|
string sdModsBasePath = ModLoader.GetSdModsBasePath();
|
||||||
string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, _titleIdText);
|
string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, _applicationData.IdString);
|
||||||
|
|
||||||
OpenHelper.OpenFolder(titleModsPath);
|
OpenHelper.OpenFolder(titleModsPath);
|
||||||
}
|
}
|
||||||
|
@ -527,7 +514,7 @@ namespace Ryujinx.UI.Widgets
|
||||||
|
|
||||||
private void OpenPtcDir_Clicked(object sender, EventArgs args)
|
private void OpenPtcDir_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
string ptcDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu");
|
string ptcDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "cpu");
|
||||||
|
|
||||||
string mainPath = System.IO.Path.Combine(ptcDir, "0");
|
string mainPath = System.IO.Path.Combine(ptcDir, "0");
|
||||||
string backupPath = System.IO.Path.Combine(ptcDir, "1");
|
string backupPath = System.IO.Path.Combine(ptcDir, "1");
|
||||||
|
@ -544,7 +531,7 @@ namespace Ryujinx.UI.Widgets
|
||||||
|
|
||||||
private void OpenShaderCacheDir_Clicked(object sender, EventArgs args)
|
private void OpenShaderCacheDir_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
string shaderCacheDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "shader");
|
string shaderCacheDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "shader");
|
||||||
|
|
||||||
if (!Directory.Exists(shaderCacheDir))
|
if (!Directory.Exists(shaderCacheDir))
|
||||||
{
|
{
|
||||||
|
@ -556,10 +543,10 @@ namespace Ryujinx.UI.Widgets
|
||||||
|
|
||||||
private void PurgePtcCache_Clicked(object sender, EventArgs args)
|
private void PurgePtcCache_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
DirectoryInfo mainDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "0"));
|
DirectoryInfo mainDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "cpu", "0"));
|
||||||
DirectoryInfo backupDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "1"));
|
DirectoryInfo backupDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "cpu", "1"));
|
||||||
|
|
||||||
MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to queue a PPTC rebuild on the next boot of:\n\n<b>{_titleName}</b>\n\nAre you sure you want to proceed?");
|
MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to queue a PPTC rebuild on the next boot of:\n\n<b>{_applicationData.Name}</b>\n\nAre you sure you want to proceed?");
|
||||||
|
|
||||||
List<FileInfo> cacheFiles = new();
|
List<FileInfo> cacheFiles = new();
|
||||||
|
|
||||||
|
@ -593,9 +580,9 @@ namespace Ryujinx.UI.Widgets
|
||||||
|
|
||||||
private void PurgeShaderCache_Clicked(object sender, EventArgs args)
|
private void PurgeShaderCache_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
DirectoryInfo shaderCacheDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "shader"));
|
DirectoryInfo shaderCacheDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "shader"));
|
||||||
|
|
||||||
using MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to delete the shader cache for :\n\n<b>{_titleName}</b>\n\nAre you sure you want to proceed?");
|
using MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to delete the shader cache for :\n\n<b>{_applicationData.Name}</b>\n\nAre you sure you want to proceed?");
|
||||||
|
|
||||||
List<DirectoryInfo> oldCacheDirectories = new();
|
List<DirectoryInfo> oldCacheDirectories = new();
|
||||||
List<FileInfo> newCacheFiles = new();
|
List<FileInfo> newCacheFiles = new();
|
||||||
|
@ -637,8 +624,11 @@ namespace Ryujinx.UI.Widgets
|
||||||
|
|
||||||
private void CreateShortcut_Clicked(object sender, EventArgs args)
|
private void CreateShortcut_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
byte[] appIcon = new ApplicationLibrary(_virtualFileSystem).GetApplicationIcon(_titleFilePath, ConfigurationState.Instance.System.Language);
|
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||||
ShortcutHelper.CreateAppShortcut(_titleFilePath, _titleName, _titleIdText, appIcon);
|
? IntegrityCheckLevel.ErrorOnInvalid
|
||||||
|
: IntegrityCheckLevel.None;
|
||||||
|
byte[] appIcon = new ApplicationLibrary(_virtualFileSystem, checkLevel).GetApplicationIcon(_applicationData.Path, ConfigurationState.Instance.System.Language, _applicationData.Id);
|
||||||
|
ShortcutHelper.CreateAppShortcut(_applicationData.Path, _applicationData.Name, _applicationData.IdString, appIcon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
using Gtk;
|
using Gtk;
|
||||||
|
using LibHac.Tools.FsSystem;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
using Ryujinx.UI.App.Common;
|
using Ryujinx.UI.App.Common;
|
||||||
|
using Ryujinx.UI.Common.Configuration;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -27,8 +29,13 @@ namespace Ryujinx.UI.Windows
|
||||||
private CheatWindow(Builder builder, VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : base(builder.GetRawOwnedObject("_cheatWindow"))
|
private CheatWindow(Builder builder, VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : base(builder.GetRawOwnedObject("_cheatWindow"))
|
||||||
{
|
{
|
||||||
builder.Autoconnect(this);
|
builder.Autoconnect(this);
|
||||||
|
|
||||||
|
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||||
|
? IntegrityCheckLevel.ErrorOnInvalid
|
||||||
|
: IntegrityCheckLevel.None;
|
||||||
|
|
||||||
_baseTitleInfoLabel.Text = $"Cheats Available for {titleName} [{titleId:X16}]";
|
_baseTitleInfoLabel.Text = $"Cheats Available for {titleName} [{titleId:X16}]";
|
||||||
_buildIdTextView.Buffer.Text = $"BuildId: {ApplicationData.GetApplicationBuildId(virtualFileSystem, titlePath)}";
|
_buildIdTextView.Buffer.Text = $"BuildId: {ApplicationData.GetBuildId(virtualFileSystem, checkLevel, titlePath)}";
|
||||||
|
|
||||||
string modsBasePath = ModLoader.GetModsBasePath();
|
string modsBasePath = ModLoader.GetModsBasePath();
|
||||||
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, titleId.ToString("X16"));
|
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, titleId.ToString("X16"));
|
||||||
|
|
|
@ -2,17 +2,21 @@ using Gtk;
|
||||||
using LibHac.Common;
|
using LibHac.Common;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Fs.Fsa;
|
using LibHac.Fs.Fsa;
|
||||||
using LibHac.FsSystem;
|
|
||||||
using LibHac.Tools.Fs;
|
using LibHac.Tools.Fs;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
|
using Ryujinx.HLE.Utilities;
|
||||||
|
using Ryujinx.UI.App.Common;
|
||||||
using Ryujinx.UI.Widgets;
|
using Ryujinx.UI.Widgets;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using GUI = Gtk.Builder.ObjectAttribute;
|
using GUI = Gtk.Builder.ObjectAttribute;
|
||||||
|
|
||||||
namespace Ryujinx.UI.Windows
|
namespace Ryujinx.UI.Windows
|
||||||
|
@ -20,7 +24,7 @@ namespace Ryujinx.UI.Windows
|
||||||
public class DlcWindow : Window
|
public class DlcWindow : Window
|
||||||
{
|
{
|
||||||
private readonly VirtualFileSystem _virtualFileSystem;
|
private readonly VirtualFileSystem _virtualFileSystem;
|
||||||
private readonly string _titleId;
|
private readonly string _applicationId;
|
||||||
private readonly string _dlcJsonPath;
|
private readonly string _dlcJsonPath;
|
||||||
private readonly List<DownloadableContentContainer> _dlcContainerList;
|
private readonly List<DownloadableContentContainer> _dlcContainerList;
|
||||||
|
|
||||||
|
@ -32,16 +36,16 @@ namespace Ryujinx.UI.Windows
|
||||||
[GUI] TreeSelection _dlcTreeSelection;
|
[GUI] TreeSelection _dlcTreeSelection;
|
||||||
#pragma warning restore CS0649, IDE0044
|
#pragma warning restore CS0649, IDE0044
|
||||||
|
|
||||||
public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Gtk3.UI.Windows.DlcWindow.glade"), virtualFileSystem, titleId, titleName) { }
|
public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, ApplicationData applicationData) : this(new Builder("Ryujinx.Gtk3.UI.Windows.DlcWindow.glade"), virtualFileSystem, titleId, applicationData) { }
|
||||||
|
|
||||||
private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_dlcWindow"))
|
private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string applicationId, ApplicationData applicationData) : base(builder.GetRawOwnedObject("_dlcWindow"))
|
||||||
{
|
{
|
||||||
builder.Autoconnect(this);
|
builder.Autoconnect(this);
|
||||||
|
|
||||||
_titleId = titleId;
|
_applicationId = applicationId;
|
||||||
_virtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
_dlcJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleId, "dlc.json");
|
_dlcJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationId, "dlc.json");
|
||||||
_baseTitleInfoLabel.Text = $"DLC Available for {titleName} [{titleId.ToUpper()}]";
|
_baseTitleInfoLabel.Text = $"DLC Available for {applicationData.Name} [{applicationId.ToUpper()}]";
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -72,7 +76,7 @@ namespace Ryujinx.UI.Windows
|
||||||
};
|
};
|
||||||
|
|
||||||
_dlcTreeView.AppendColumn("Enabled", enableToggle, "active", 0);
|
_dlcTreeView.AppendColumn("Enabled", enableToggle, "active", 0);
|
||||||
_dlcTreeView.AppendColumn("TitleId", new CellRendererText(), "text", 1);
|
_dlcTreeView.AppendColumn("ApplicationId", new CellRendererText(), "text", 1);
|
||||||
_dlcTreeView.AppendColumn("Path", new CellRendererText(), "text", 2);
|
_dlcTreeView.AppendColumn("Path", new CellRendererText(), "text", 2);
|
||||||
|
|
||||||
foreach (DownloadableContentContainer dlcContainer in _dlcContainerList)
|
foreach (DownloadableContentContainer dlcContainer in _dlcContainerList)
|
||||||
|
@ -86,18 +90,18 @@ namespace Ryujinx.UI.Windows
|
||||||
bool areAllContentPacksEnabled = dlcContainer.DownloadableContentNcaList.TrueForAll((nca) => nca.Enabled);
|
bool areAllContentPacksEnabled = dlcContainer.DownloadableContentNcaList.TrueForAll((nca) => nca.Enabled);
|
||||||
TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(areAllContentPacksEnabled, "", dlcContainer.ContainerPath);
|
TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(areAllContentPacksEnabled, "", dlcContainer.ContainerPath);
|
||||||
|
|
||||||
using FileStream containerFile = File.OpenRead(dlcContainer.ContainerPath);
|
using IFileSystem partitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(dlcContainer.ContainerPath, _virtualFileSystem, false);
|
||||||
|
|
||||||
PartitionFileSystem pfs = new();
|
if (partitionFileSystem == null)
|
||||||
pfs.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
{
|
||||||
|
continue;
|
||||||
_virtualFileSystem.ImportTickets(pfs);
|
}
|
||||||
|
|
||||||
foreach (DownloadableContentNca dlcNca in dlcContainer.DownloadableContentNcaList)
|
foreach (DownloadableContentNca dlcNca in dlcContainer.DownloadableContentNcaList)
|
||||||
{
|
{
|
||||||
using var ncaFile = new UniqueRef<IFile>();
|
using var ncaFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
pfs.OpenFile(ref ncaFile.Ref, dlcNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
partitionFileSystem.OpenFile(ref ncaFile.Ref, dlcNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), dlcContainer.ContainerPath);
|
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), dlcContainer.ContainerPath);
|
||||||
|
|
||||||
if (nca != null)
|
if (nca != null)
|
||||||
|
@ -112,6 +116,9 @@ namespace Ryujinx.UI.Windows
|
||||||
TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(false, "", $"(MISSING) {dlcContainer.ContainerPath}");
|
TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(false, "", $"(MISSING) {dlcContainer.ContainerPath}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: Try to load downloadable contents from PFS last to preserve enabled state.
|
||||||
|
AddDlc(applicationData.Path, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Nca TryCreateNca(IStorage ncaStorage, string containerPath)
|
private Nca TryCreateNca(IStorage ncaStorage, string containerPath)
|
||||||
|
@ -128,6 +135,52 @@ namespace Ryujinx.UI.Windows
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddDlc(string path, bool ignoreNotFound = false)
|
||||||
|
{
|
||||||
|
if (!File.Exists(path) || _dlcContainerList.Any(x => x.ContainerPath == path))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using IFileSystem partitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(path, _virtualFileSystem);
|
||||||
|
|
||||||
|
bool containsDlc = false;
|
||||||
|
|
||||||
|
TreeIter? parentIter = null;
|
||||||
|
|
||||||
|
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
|
||||||
|
{
|
||||||
|
using var ncaFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
partitionFileSystem.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), path);
|
||||||
|
|
||||||
|
if (nca == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nca.Header.ContentType == NcaContentType.PublicData)
|
||||||
|
{
|
||||||
|
if (nca.GetProgramIdBase() != (ulong.Parse(_applicationId, NumberStyles.HexNumber) & ~0x1FFFUL))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
parentIter ??= ((TreeStore)_dlcTreeView.Model).AppendValues(true, "", path);
|
||||||
|
|
||||||
|
((TreeStore)_dlcTreeView.Model).AppendValues(parentIter.Value, true, nca.Header.TitleId.ToString("X16"), fileEntry.FullPath);
|
||||||
|
containsDlc = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!containsDlc && !ignoreNotFound)
|
||||||
|
{
|
||||||
|
GtkDialog.CreateErrorDialog("The specified file does not contain DLC for the selected title!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void AddButton_Clicked(object sender, EventArgs args)
|
private void AddButton_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
FileChooserNative fileChooser = new("Select DLC files", this, FileChooserAction.Open, "Add", "Cancel")
|
FileChooserNative fileChooser = new("Select DLC files", this, FileChooserAction.Open, "Add", "Cancel")
|
||||||
|
@ -147,52 +200,7 @@ namespace Ryujinx.UI.Windows
|
||||||
{
|
{
|
||||||
foreach (string containerPath in fileChooser.Filenames)
|
foreach (string containerPath in fileChooser.Filenames)
|
||||||
{
|
{
|
||||||
if (!File.Exists(containerPath))
|
AddDlc(containerPath);
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using FileStream containerFile = File.OpenRead(containerPath);
|
|
||||||
|
|
||||||
PartitionFileSystem pfs = new();
|
|
||||||
pfs.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
|
||||||
bool containsDlc = false;
|
|
||||||
|
|
||||||
_virtualFileSystem.ImportTickets(pfs);
|
|
||||||
|
|
||||||
TreeIter? parentIter = null;
|
|
||||||
|
|
||||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
|
||||||
{
|
|
||||||
using var ncaFile = new UniqueRef<IFile>();
|
|
||||||
|
|
||||||
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
|
|
||||||
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), containerPath);
|
|
||||||
|
|
||||||
if (nca == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nca.Header.ContentType == NcaContentType.PublicData)
|
|
||||||
{
|
|
||||||
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000).ToString("x16") != _titleId)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
parentIter ??= ((TreeStore)_dlcTreeView.Model).AppendValues(true, "", containerPath);
|
|
||||||
|
|
||||||
((TreeStore)_dlcTreeView.Model).AppendValues(parentIter.Value, true, nca.Header.TitleId.ToString("X16"), fileEntry.FullPath);
|
|
||||||
containsDlc = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!containsDlc)
|
|
||||||
{
|
|
||||||
GtkDialog.CreateErrorDialog("The specified file does not contain DLC for the selected title!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,17 @@ using Gtk;
|
||||||
using LibHac.Common;
|
using LibHac.Common;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Fs.Fsa;
|
using LibHac.Fs.Fsa;
|
||||||
using LibHac.FsSystem;
|
using LibHac.Ncm;
|
||||||
using LibHac.Ns;
|
using LibHac.Ns;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
|
using Ryujinx.HLE.Utilities;
|
||||||
using Ryujinx.UI.App.Common;
|
using Ryujinx.UI.App.Common;
|
||||||
|
using Ryujinx.UI.Common.Configuration;
|
||||||
using Ryujinx.UI.Widgets;
|
using Ryujinx.UI.Widgets;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -24,7 +27,7 @@ namespace Ryujinx.UI.Windows
|
||||||
{
|
{
|
||||||
private readonly MainWindow _parent;
|
private readonly MainWindow _parent;
|
||||||
private readonly VirtualFileSystem _virtualFileSystem;
|
private readonly VirtualFileSystem _virtualFileSystem;
|
||||||
private readonly string _titleId;
|
private readonly ApplicationData _applicationData;
|
||||||
private readonly string _updateJsonPath;
|
private readonly string _updateJsonPath;
|
||||||
|
|
||||||
private TitleUpdateMetadata _titleUpdateWindowData;
|
private TitleUpdateMetadata _titleUpdateWindowData;
|
||||||
|
@ -38,17 +41,17 @@ namespace Ryujinx.UI.Windows
|
||||||
[GUI] RadioButton _noUpdateRadioButton;
|
[GUI] RadioButton _noUpdateRadioButton;
|
||||||
#pragma warning restore CS0649, IDE0044
|
#pragma warning restore CS0649, IDE0044
|
||||||
|
|
||||||
public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Gtk3.UI.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, titleId, titleName) { }
|
public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ApplicationData applicationData) : this(new Builder("Ryujinx.Gtk3.UI.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, applicationData) { }
|
||||||
|
|
||||||
private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_titleUpdateWindow"))
|
private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, ApplicationData applicationData) : base(builder.GetRawOwnedObject("_titleUpdateWindow"))
|
||||||
{
|
{
|
||||||
_parent = parent;
|
_parent = parent;
|
||||||
|
|
||||||
builder.Autoconnect(this);
|
builder.Autoconnect(this);
|
||||||
|
|
||||||
_titleId = titleId;
|
_applicationData = applicationData;
|
||||||
_virtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
_updateJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleId, "updates.json");
|
_updateJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, applicationData.IdString, "updates.json");
|
||||||
_radioButtonToPathDictionary = new Dictionary<RadioButton, string>();
|
_radioButtonToPathDictionary = new Dictionary<RadioButton, string>();
|
||||||
|
|
||||||
try
|
try
|
||||||
|
@ -64,7 +67,10 @@ namespace Ryujinx.UI.Windows
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_baseTitleInfoLabel.Text = $"Updates Available for {titleName} [{titleId.ToUpper()}]";
|
_baseTitleInfoLabel.Text = $"Updates Available for {applicationData.Name} [{applicationData.IdString}]";
|
||||||
|
|
||||||
|
// Try to get updates from PFS first
|
||||||
|
AddUpdate(_applicationData.Path, true);
|
||||||
|
|
||||||
foreach (string path in _titleUpdateWindowData.Paths)
|
foreach (string path in _titleUpdateWindowData.Paths)
|
||||||
{
|
{
|
||||||
|
@ -84,46 +90,68 @@ namespace Ryujinx.UI.Windows
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddUpdate(string path)
|
private void AddUpdate(string path, bool ignoreNotFound = false)
|
||||||
{
|
{
|
||||||
if (File.Exists(path))
|
if (!File.Exists(path) || _radioButtonToPathDictionary.ContainsValue(path))
|
||||||
{
|
{
|
||||||
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
PartitionFileSystem nsp = new();
|
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||||
nsp.Initialize(file.AsStorage()).ThrowIfFailure();
|
? IntegrityCheckLevel.ErrorOnInvalid
|
||||||
|
: IntegrityCheckLevel.None;
|
||||||
|
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
using IFileSystem pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(path, _virtualFileSystem);
|
||||||
|
|
||||||
|
Dictionary<ulong, ContentMetaData> updates = pfs.GetContentData(ContentMetaType.Patch, _virtualFileSystem, checkLevel);
|
||||||
|
|
||||||
|
Nca patchNca = null;
|
||||||
|
Nca controlNca = null;
|
||||||
|
|
||||||
|
if (updates.TryGetValue(_applicationData.Id, out ContentMetaData update))
|
||||||
{
|
{
|
||||||
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(_virtualFileSystem, nsp, _titleId, 0);
|
patchNca = update.GetNcaByType(_virtualFileSystem.KeySet, LibHac.Ncm.ContentType.Program);
|
||||||
|
controlNca = update.GetNcaByType(_virtualFileSystem.KeySet, LibHac.Ncm.ContentType.Control);
|
||||||
|
}
|
||||||
|
|
||||||
if (controlNca != null && patchNca != null)
|
if (controlNca != null && patchNca != null)
|
||||||
|
{
|
||||||
|
ApplicationControlProperty controlData = new();
|
||||||
|
|
||||||
|
using var nacpFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
||||||
|
|
||||||
|
string radioLabel = $"Version {controlData.DisplayVersionString.ToString()} - {path}";
|
||||||
|
|
||||||
|
if (System.IO.Path.GetExtension(path).ToLower() == ".xci")
|
||||||
{
|
{
|
||||||
ApplicationControlProperty controlData = new();
|
radioLabel = "Bundled: " + radioLabel;
|
||||||
|
|
||||||
using var nacpFile = new UniqueRef<IFile>();
|
|
||||||
|
|
||||||
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
|
||||||
|
|
||||||
RadioButton radioButton = new($"Version {controlData.DisplayVersionString.ToString()} - {path}");
|
|
||||||
radioButton.JoinGroup(_noUpdateRadioButton);
|
|
||||||
|
|
||||||
_availableUpdatesBox.Add(radioButton);
|
|
||||||
_radioButtonToPathDictionary.Add(radioButton, path);
|
|
||||||
|
|
||||||
radioButton.Show();
|
|
||||||
radioButton.Active = true;
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
RadioButton radioButton = new(radioLabel);
|
||||||
|
radioButton.JoinGroup(_noUpdateRadioButton);
|
||||||
|
|
||||||
|
_availableUpdatesBox.Add(radioButton);
|
||||||
|
_radioButtonToPathDictionary.Add(radioButton, path);
|
||||||
|
|
||||||
|
radioButton.Show();
|
||||||
|
radioButton.Active = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!ignoreNotFound)
|
||||||
{
|
{
|
||||||
GtkDialog.CreateErrorDialog("The specified file does not contain an update for the selected title!");
|
GtkDialog.CreateErrorDialog("The specified file does not contain an update for the selected title!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
}
|
||||||
{
|
catch (Exception exception)
|
||||||
GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {path}");
|
{
|
||||||
}
|
GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {path}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.Exceptions;
|
using Ryujinx.HLE.Exceptions;
|
||||||
using Ryujinx.HLE.HOS.Services.Ssl;
|
using Ryujinx.HLE.HOS.Services.Ssl;
|
||||||
using Ryujinx.HLE.HOS.Services.Time;
|
using Ryujinx.HLE.HOS.Services.Time;
|
||||||
|
using Ryujinx.HLE.Utilities;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -184,41 +185,6 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fs must contain AOC nca files in its root
|
|
||||||
public void AddAocData(IFileSystem fs, string containerPath, ulong aocBaseId, IntegrityCheckLevel integrityCheckLevel)
|
|
||||||
{
|
|
||||||
_virtualFileSystem.ImportTickets(fs);
|
|
||||||
|
|
||||||
foreach (var ncaPath in fs.EnumerateEntries("*.cnmt.nca", SearchOptions.Default))
|
|
||||||
{
|
|
||||||
using var ncaFile = new UniqueRef<IFile>();
|
|
||||||
|
|
||||||
fs.OpenFile(ref ncaFile.Ref, ncaPath.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
var nca = new Nca(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
|
|
||||||
if (nca.Header.ContentType != NcaContentType.Meta)
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Application, $"{ncaPath} is not a valid metadata file");
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var pfs0 = nca.OpenFileSystem(0, integrityCheckLevel);
|
|
||||||
using var cnmtFile = new UniqueRef<IFile>();
|
|
||||||
|
|
||||||
pfs0.OpenFile(ref cnmtFile.Ref, pfs0.EnumerateEntries().Single().FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
|
|
||||||
var cnmt = new Cnmt(cnmtFile.Get.AsStream());
|
|
||||||
if (cnmt.Type != ContentMetaType.AddOnContent || (cnmt.TitleId & 0xFFFFFFFFFFFFE000) != aocBaseId)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
string ncaId = Convert.ToHexString(cnmt.ContentEntries[0].NcaId).ToLower();
|
|
||||||
|
|
||||||
AddAocItem(cnmt.TitleId, containerPath, $"/{ncaId}.nca", true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddAocItem(ulong titleId, string containerPath, string ncaPath, bool mergedToContainer = false)
|
public void AddAocItem(ulong titleId, string containerPath, string ncaPath, bool mergedToContainer = false)
|
||||||
{
|
{
|
||||||
// TODO: Check Aoc version.
|
// TODO: Check Aoc version.
|
||||||
|
@ -232,11 +198,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
|
|
||||||
if (!mergedToContainer)
|
if (!mergedToContainer)
|
||||||
{
|
{
|
||||||
using FileStream fileStream = File.OpenRead(containerPath);
|
using var pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(containerPath, _virtualFileSystem);
|
||||||
using PartitionFileSystem partitionFileSystem = new();
|
|
||||||
partitionFileSystem.Initialize(fileStream.AsStorage()).ThrowIfFailure();
|
|
||||||
|
|
||||||
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
61
src/Ryujinx.HLE/FileSystem/ContentMetaData.cs
Normal file
61
src/Ryujinx.HLE/FileSystem/ContentMetaData.cs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
using LibHac.Common.Keys;
|
||||||
|
using LibHac.Fs.Fsa;
|
||||||
|
using LibHac.Ncm;
|
||||||
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
|
using LibHac.Tools.Ncm;
|
||||||
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.FileSystem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Thin wrapper around <see cref="Cnmt"/>
|
||||||
|
/// </summary>
|
||||||
|
public class ContentMetaData
|
||||||
|
{
|
||||||
|
private readonly IFileSystem _pfs;
|
||||||
|
private readonly Cnmt _cnmt;
|
||||||
|
|
||||||
|
public ulong Id => _cnmt.TitleId;
|
||||||
|
public TitleVersion Version => _cnmt.TitleVersion;
|
||||||
|
public ContentMetaType Type => _cnmt.Type;
|
||||||
|
public ulong ApplicationId => _cnmt.ApplicationTitleId;
|
||||||
|
public ulong PatchId => _cnmt.PatchTitleId;
|
||||||
|
public TitleVersion RequiredSystemVersion => _cnmt.MinimumSystemVersion;
|
||||||
|
public TitleVersion RequiredApplicationVersion => _cnmt.MinimumApplicationVersion;
|
||||||
|
public byte[] Digest => _cnmt.Hash;
|
||||||
|
|
||||||
|
public ulong ProgramBaseId => Id & ~0x1FFFUL;
|
||||||
|
public bool IsSystemTitle => _cnmt.Type < ContentMetaType.Application;
|
||||||
|
|
||||||
|
public ContentMetaData(IFileSystem pfs, Cnmt cnmt)
|
||||||
|
{
|
||||||
|
_pfs = pfs;
|
||||||
|
_cnmt = cnmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Nca GetNcaByType(KeySet keySet, ContentType type, int programIndex = 0)
|
||||||
|
{
|
||||||
|
// TODO: Replace this with a check for IdOffset as soon as LibHac supports it:
|
||||||
|
// && entry.IdOffset == programIndex
|
||||||
|
|
||||||
|
foreach (var entry in _cnmt.ContentEntries)
|
||||||
|
{
|
||||||
|
if (entry.Type != type)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string ncaId = BitConverter.ToString(entry.NcaId).Replace("-", null).ToLower();
|
||||||
|
Nca nca = _pfs.GetNca(keySet, $"/{ncaId}.nca");
|
||||||
|
|
||||||
|
if (nca.GetProgramIndex() == programIndex)
|
||||||
|
{
|
||||||
|
return nca;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -474,9 +474,9 @@ namespace Ryujinx.HLE.HOS.Services
|
||||||
{
|
{
|
||||||
const int MessageSize = 0x100;
|
const int MessageSize = 0x100;
|
||||||
|
|
||||||
using IMemoryOwner<byte> reqDataOwner = ByteMemoryPool.Rent(MessageSize);
|
using SpanOwner<byte> reqDataOwner = SpanOwner<byte>.Rent(MessageSize);
|
||||||
|
|
||||||
Span<byte> reqDataSpan = reqDataOwner.Memory.Span;
|
Span<byte> reqDataSpan = reqDataOwner.Span;
|
||||||
|
|
||||||
_selfProcess.CpuMemory.Read(_selfThread.TlsAddress, reqDataSpan);
|
_selfProcess.CpuMemory.Read(_selfThread.TlsAddress, reqDataSpan);
|
||||||
|
|
||||||
|
|
|
@ -85,9 +85,9 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||||
|
|
||||||
ReadOnlySpan<byte> inputParcel = context.Memory.GetSpan(dataPos, (int)dataSize);
|
ReadOnlySpan<byte> inputParcel = context.Memory.GetSpan(dataPos, (int)dataSize);
|
||||||
|
|
||||||
using IMemoryOwner<byte> outputParcelOwner = ByteMemoryPool.RentCleared(replySize);
|
using SpanOwner<byte> outputParcelOwner = SpanOwner<byte>.RentCleared(checked((int)replySize));
|
||||||
|
|
||||||
Span<byte> outputParcel = outputParcelOwner.Memory.Span;
|
Span<byte> outputParcel = outputParcelOwner.Span;
|
||||||
|
|
||||||
ResultCode result = OnTransact(binderId, code, flags, inputParcel, outputParcel);
|
ResultCode result = OnTransact(binderId, code, flags, inputParcel, outputParcel);
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
|
using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
@ -13,7 +12,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||||
{
|
{
|
||||||
sealed class Parcel : IDisposable
|
sealed class Parcel : IDisposable
|
||||||
{
|
{
|
||||||
private readonly IMemoryOwner<byte> _rawDataOwner;
|
private readonly MemoryOwner<byte> _rawDataOwner;
|
||||||
|
|
||||||
private Span<byte> Raw => _rawDataOwner.Memory.Span;
|
private Span<byte> Raw => _rawDataOwner.Memory.Span;
|
||||||
|
|
||||||
|
@ -30,7 +29,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||||
|
|
||||||
public Parcel(ReadOnlySpan<byte> data)
|
public Parcel(ReadOnlySpan<byte> data)
|
||||||
{
|
{
|
||||||
_rawDataOwner = ByteMemoryPool.RentCopy(data);
|
_rawDataOwner = MemoryOwner<byte>.RentCopy(data);
|
||||||
|
|
||||||
_payloadPosition = 0;
|
_payloadPosition = 0;
|
||||||
_objectPosition = 0;
|
_objectPosition = 0;
|
||||||
|
@ -40,7 +39,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||||
{
|
{
|
||||||
uint headerSize = (uint)Unsafe.SizeOf<ParcelHeader>();
|
uint headerSize = (uint)Unsafe.SizeOf<ParcelHeader>();
|
||||||
|
|
||||||
_rawDataOwner = ByteMemoryPool.RentCleared(BitUtils.AlignUp<uint>(headerSize + payloadSize + objectsSize, 4));
|
_rawDataOwner = MemoryOwner<byte>.RentCleared(checked((int)BitUtils.AlignUp<uint>(headerSize + payloadSize + objectsSize, 4)));
|
||||||
|
|
||||||
Header.PayloadSize = payloadSize;
|
Header.PayloadSize = payloadSize;
|
||||||
Header.ObjectsSize = objectsSize;
|
Header.ObjectsSize = objectsSize;
|
||||||
|
|
|
@ -3,7 +3,6 @@ using LibHac.FsSystem;
|
||||||
using LibHac.Loader;
|
using LibHac.Loader;
|
||||||
using LibHac.Ncm;
|
using LibHac.Ncm;
|
||||||
using LibHac.Ns;
|
using LibHac.Ns;
|
||||||
using Ryujinx.HLE.HOS;
|
|
||||||
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.Loaders.Processes
|
namespace Ryujinx.HLE.Loaders.Processes
|
||||||
|
|
|
@ -7,16 +7,25 @@ using LibHac.Ncm;
|
||||||
using LibHac.Ns;
|
using LibHac.Ns;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
|
using LibHac.Tools.Ncm;
|
||||||
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
|
using Ryujinx.HLE.Utilities;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using ApplicationId = LibHac.Ncm.ApplicationId;
|
using ApplicationId = LibHac.Ncm.ApplicationId;
|
||||||
|
using ContentType = LibHac.Ncm.ContentType;
|
||||||
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||||
{
|
{
|
||||||
static class NcaExtensions
|
public static class NcaExtensions
|
||||||
{
|
{
|
||||||
|
private static readonly TitleUpdateMetadataJsonSerializerContext _applicationSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
|
|
||||||
public static ProcessResult Load(this Nca nca, Switch device, Nca patchNca, Nca controlNca)
|
public static ProcessResult Load(this Nca nca, Switch device, Nca patchNca, Nca controlNca)
|
||||||
{
|
{
|
||||||
// Extract RomFs and ExeFs from NCA.
|
// Extract RomFs and ExeFs from NCA.
|
||||||
|
@ -47,7 +56,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||||
nacpData = controlNca.GetNacp(device);
|
nacpData = controlNca.GetNacp(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: Rework this since it's wrong and doesn't work as it takes the DisplayVersion from a "potential" inexistant update.
|
/* TODO: Rework this since it's wrong and doesn't work as it takes the DisplayVersion from a "potential" non-existent update.
|
||||||
|
|
||||||
// Load program 0 control NCA as we are going to need it for display version.
|
// Load program 0 control NCA as we are going to need it for display version.
|
||||||
(_, Nca updateProgram0ControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _);
|
(_, Nca updateProgram0ControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _);
|
||||||
|
@ -86,6 +95,11 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||||
return processResult;
|
return processResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ulong GetProgramIdBase(this Nca nca)
|
||||||
|
{
|
||||||
|
return nca.Header.TitleId & ~0x1FFFUL;
|
||||||
|
}
|
||||||
|
|
||||||
public static int GetProgramIndex(this Nca nca)
|
public static int GetProgramIndex(this Nca nca)
|
||||||
{
|
{
|
||||||
return (int)(nca.Header.TitleId & 0xF);
|
return (int)(nca.Header.TitleId & 0xF);
|
||||||
|
@ -96,6 +110,11 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||||
return nca.Header.ContentType == NcaContentType.Program;
|
return nca.Header.ContentType == NcaContentType.Program;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsMain(this Nca nca)
|
||||||
|
{
|
||||||
|
return nca.IsProgram() && !nca.IsPatch();
|
||||||
|
}
|
||||||
|
|
||||||
public static bool IsPatch(this Nca nca)
|
public static bool IsPatch(this Nca nca)
|
||||||
{
|
{
|
||||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||||
|
@ -108,6 +127,43 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||||
return nca.Header.ContentType == NcaContentType.Control;
|
return nca.Header.ContentType == NcaContentType.Control;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static (Nca, Nca) GetUpdateData(this Nca mainNca, VirtualFileSystem fileSystem, IntegrityCheckLevel checkLevel, int programIndex, out string updatePath)
|
||||||
|
{
|
||||||
|
updatePath = null;
|
||||||
|
|
||||||
|
// Load Update NCAs.
|
||||||
|
Nca updatePatchNca = null;
|
||||||
|
Nca updateControlNca = null;
|
||||||
|
|
||||||
|
// Clear the program index part.
|
||||||
|
ulong titleIdBase = mainNca.GetProgramIdBase();
|
||||||
|
|
||||||
|
// Load update information if exists.
|
||||||
|
string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "updates.json");
|
||||||
|
if (File.Exists(titleUpdateMetadataPath))
|
||||||
|
{
|
||||||
|
updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, _applicationSerializerContext.TitleUpdateMetadata).Selected;
|
||||||
|
if (File.Exists(updatePath))
|
||||||
|
{
|
||||||
|
IFileSystem updatePartitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(updatePath, fileSystem);
|
||||||
|
|
||||||
|
foreach ((ulong applicationTitleId, ContentMetaData content) in updatePartitionFileSystem.GetContentData(ContentMetaType.Patch, fileSystem, checkLevel))
|
||||||
|
{
|
||||||
|
if ((applicationTitleId & ~0x1FFFUL) != titleIdBase)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePatchNca = content.GetNcaByType(fileSystem.KeySet, ContentType.Program, programIndex);
|
||||||
|
updateControlNca = content.GetNcaByType(fileSystem.KeySet, ContentType.Control, programIndex);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (updatePatchNca, updateControlNca);
|
||||||
|
}
|
||||||
|
|
||||||
public static IFileSystem GetExeFs(this Nca nca, Switch device, Nca patchNca = null)
|
public static IFileSystem GetExeFs(this Nca nca, Switch device, Nca patchNca = null)
|
||||||
{
|
{
|
||||||
IFileSystem exeFs = null;
|
IFileSystem exeFs = null;
|
||||||
|
@ -172,5 +228,31 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||||
|
|
||||||
return nacpData;
|
return nacpData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Cnmt GetCnmt(this Nca cnmtNca, IntegrityCheckLevel checkLevel, ContentMetaType metaType)
|
||||||
|
{
|
||||||
|
string path = $"/{metaType}_{cnmtNca.Header.TitleId:x16}.cnmt";
|
||||||
|
using var cnmtFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Result result = cnmtNca.OpenFileSystem(0, checkLevel)
|
||||||
|
.OpenFile(ref cnmtFile.Ref, path.ToU8Span(), OpenMode.Read);
|
||||||
|
|
||||||
|
if (result.IsSuccess())
|
||||||
|
{
|
||||||
|
return new Cnmt(cnmtFile.Release().AsStream());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (HorizonResultException ex)
|
||||||
|
{
|
||||||
|
if (!ResultFs.PathNotFound.Includes(ex.ResultValue))
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"Failed get CNMT for '{cnmtNca.Header.TitleId:x16}' from NCA: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,58 @@
|
||||||
using LibHac.Common;
|
using LibHac.Common;
|
||||||
|
using LibHac.Common.Keys;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Fs.Fsa;
|
using LibHac.Fs.Fsa;
|
||||||
using LibHac.FsSystem;
|
using LibHac.FsSystem;
|
||||||
|
using LibHac.Ncm;
|
||||||
using LibHac.Tools.Fs;
|
using LibHac.Tools.Fs;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
|
using LibHac.Tools.Ncm;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using ContentType = LibHac.Ncm.ContentType;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||||
{
|
{
|
||||||
public static class PartitionFileSystemExtensions
|
public static class PartitionFileSystemExtensions
|
||||||
{
|
{
|
||||||
private static readonly DownloadableContentJsonSerializerContext _contentSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
private static readonly DownloadableContentJsonSerializerContext _contentSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
private static readonly TitleUpdateMetadataJsonSerializerContext _titleSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
|
||||||
|
|
||||||
internal static (bool, ProcessResult) TryLoad<TMetaData, TFormat, THeader, TEntry>(this PartitionFileSystemCore<TMetaData, TFormat, THeader, TEntry> partitionFileSystem, Switch device, string path, out string errorMessage)
|
public static Dictionary<ulong, ContentMetaData> GetContentData(this IFileSystem partitionFileSystem,
|
||||||
|
ContentMetaType contentType, VirtualFileSystem fileSystem, IntegrityCheckLevel checkLevel)
|
||||||
|
{
|
||||||
|
fileSystem.ImportTickets(partitionFileSystem);
|
||||||
|
|
||||||
|
var programs = new Dictionary<ulong, ContentMetaData>();
|
||||||
|
|
||||||
|
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.cnmt.nca"))
|
||||||
|
{
|
||||||
|
Cnmt cnmt = partitionFileSystem.GetNca(fileSystem.KeySet, fileEntry.FullPath).GetCnmt(checkLevel, contentType);
|
||||||
|
|
||||||
|
if (cnmt == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentMetaData content = new(partitionFileSystem, cnmt);
|
||||||
|
|
||||||
|
if (content.Type != contentType)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
programs.TryAdd(content.ApplicationId, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
return programs;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static (bool, ProcessResult) TryLoad<TMetaData, TFormat, THeader, TEntry>(this PartitionFileSystemCore<TMetaData, TFormat, THeader, TEntry> partitionFileSystem, Switch device, string path, ulong applicationId, out string errorMessage)
|
||||||
where TMetaData : PartitionFileSystemMetaCore<TFormat, THeader, TEntry>, new()
|
where TMetaData : PartitionFileSystemMetaCore<TFormat, THeader, TEntry>, new()
|
||||||
where TFormat : IPartitionFileSystemFormat
|
where TFormat : IPartitionFileSystemFormat
|
||||||
where THeader : unmanaged, IPartitionFileSystemHeader
|
where THeader : unmanaged, IPartitionFileSystemHeader
|
||||||
|
@ -35,31 +67,22 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
device.Configuration.VirtualFileSystem.ImportTickets(partitionFileSystem);
|
Dictionary<ulong, ContentMetaData> applications = partitionFileSystem.GetContentData(ContentMetaType.Application, device.FileSystem, device.System.FsIntegrityCheckLevel);
|
||||||
|
|
||||||
// TODO: To support multi-games container, this should use CNMT NCA instead.
|
if (applicationId == 0)
|
||||||
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
|
|
||||||
{
|
{
|
||||||
Nca nca = partitionFileSystem.GetNca(device, fileEntry.FullPath);
|
foreach ((ulong _, ContentMetaData content) in applications)
|
||||||
|
|
||||||
if (nca.GetProgramIndex() != device.Configuration.UserChannelPersistence.Index)
|
|
||||||
{
|
{
|
||||||
continue;
|
mainNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Program, device.Configuration.UserChannelPersistence.Index);
|
||||||
}
|
controlNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Control, device.Configuration.UserChannelPersistence.Index);
|
||||||
|
break;
|
||||||
if (nca.IsPatch())
|
|
||||||
{
|
|
||||||
patchNca = nca;
|
|
||||||
}
|
|
||||||
else if (nca.IsProgram())
|
|
||||||
{
|
|
||||||
mainNca = nca;
|
|
||||||
}
|
|
||||||
else if (nca.IsControl())
|
|
||||||
{
|
|
||||||
controlNca = nca;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (applications.TryGetValue(applicationId, out ContentMetaData content))
|
||||||
|
{
|
||||||
|
mainNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Program, device.Configuration.UserChannelPersistence.Index);
|
||||||
|
controlNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Control, device.Configuration.UserChannelPersistence.Index);
|
||||||
|
}
|
||||||
|
|
||||||
ProcessLoaderHelper.RegisterProgramMapInfo(device, partitionFileSystem).ThrowIfFailure();
|
ProcessLoaderHelper.RegisterProgramMapInfo(device, partitionFileSystem).ThrowIfFailure();
|
||||||
}
|
}
|
||||||
|
@ -79,54 +102,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||||
return (false, ProcessResult.Failed);
|
return (false, ProcessResult.Failed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load Update NCAs.
|
(Nca updatePatchNca, Nca updateControlNca) = mainNca.GetUpdateData(device.FileSystem, device.System.FsIntegrityCheckLevel, device.Configuration.UserChannelPersistence.Index, out string _);
|
||||||
Nca updatePatchNca = null;
|
|
||||||
Nca updateControlNca = null;
|
|
||||||
|
|
||||||
if (ulong.TryParse(mainNca.Header.TitleId.ToString("x16"), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase))
|
|
||||||
{
|
|
||||||
// Clear the program index part.
|
|
||||||
titleIdBase &= ~0xFUL;
|
|
||||||
|
|
||||||
// Load update information if exists.
|
|
||||||
string titleUpdateMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json");
|
|
||||||
if (File.Exists(titleUpdateMetadataPath))
|
|
||||||
{
|
|
||||||
string updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, _titleSerializerContext.TitleUpdateMetadata).Selected;
|
|
||||||
if (File.Exists(updatePath))
|
|
||||||
{
|
|
||||||
PartitionFileSystem updatePartitionFileSystem = new();
|
|
||||||
updatePartitionFileSystem.Initialize(new FileStream(updatePath, FileMode.Open, FileAccess.Read).AsStorage()).ThrowIfFailure();
|
|
||||||
|
|
||||||
device.Configuration.VirtualFileSystem.ImportTickets(updatePartitionFileSystem);
|
|
||||||
|
|
||||||
// TODO: This should use CNMT NCA instead.
|
|
||||||
foreach (DirectoryEntryEx fileEntry in updatePartitionFileSystem.EnumerateEntries("/", "*.nca"))
|
|
||||||
{
|
|
||||||
Nca nca = updatePartitionFileSystem.GetNca(device, fileEntry.FullPath);
|
|
||||||
|
|
||||||
if (nca.GetProgramIndex() != device.Configuration.UserChannelPersistence.Index)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleIdBase.ToString("x16"))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nca.IsProgram())
|
|
||||||
{
|
|
||||||
updatePatchNca = nca;
|
|
||||||
}
|
|
||||||
else if (nca.IsControl())
|
|
||||||
{
|
|
||||||
updateControlNca = nca;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updatePatchNca != null)
|
if (updatePatchNca != null)
|
||||||
{
|
{
|
||||||
|
@ -138,10 +114,8 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||||
controlNca = updateControlNca;
|
controlNca = updateControlNca;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load contained DownloadableContents.
|
|
||||||
// TODO: If we want to support multi-processes in future, we shouldn't clear AddOnContent data here.
|
// TODO: If we want to support multi-processes in future, we shouldn't clear AddOnContent data here.
|
||||||
device.Configuration.ContentManager.ClearAocData();
|
device.Configuration.ContentManager.ClearAocData();
|
||||||
device.Configuration.ContentManager.AddAocData(partitionFileSystem, path, mainNca.Header.TitleId, device.Configuration.FsIntegrityCheckLevel);
|
|
||||||
|
|
||||||
// Load DownloadableContents.
|
// Load DownloadableContents.
|
||||||
string addOnContentMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "dlc.json");
|
string addOnContentMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "dlc.json");
|
||||||
|
@ -153,9 +127,12 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||||
{
|
{
|
||||||
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
|
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
|
||||||
{
|
{
|
||||||
if (File.Exists(downloadableContentContainer.ContainerPath) && downloadableContentNca.Enabled)
|
if (File.Exists(downloadableContentContainer.ContainerPath))
|
||||||
{
|
{
|
||||||
device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, downloadableContentNca.FullPath);
|
if (downloadableContentNca.Enabled)
|
||||||
|
{
|
||||||
|
device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, downloadableContentNca.FullPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -168,18 +145,18 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||||
return (true, mainNca.Load(device, patchNca, controlNca));
|
return (true, mainNca.Load(device, patchNca, controlNca));
|
||||||
}
|
}
|
||||||
|
|
||||||
errorMessage = "Unable to load: Could not find Main NCA";
|
errorMessage = $"Unable to load: Could not find Main NCA for title \"{applicationId:X16}\"";
|
||||||
|
|
||||||
return (false, ProcessResult.Failed);
|
return (false, ProcessResult.Failed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Nca GetNca(this IFileSystem fileSystem, Switch device, string path)
|
public static Nca GetNca(this IFileSystem fileSystem, KeySet keySet, string path)
|
||||||
{
|
{
|
||||||
using var ncaFile = new UniqueRef<IFile>();
|
using var ncaFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
fileSystem.OpenFile(ref ncaFile.Ref, path.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
fileSystem.OpenFile(ref ncaFile.Ref, path.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
return new Nca(device.Configuration.VirtualFileSystem.KeySet, ncaFile.Release().AsStorage());
|
return new Nca(keySet, ncaFile.Release().AsStorage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||||
_processesByPid = new ConcurrentDictionary<ulong, ProcessResult>();
|
_processesByPid = new ConcurrentDictionary<ulong, ProcessResult>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool LoadXci(string path)
|
public bool LoadXci(string path, ulong applicationId)
|
||||||
{
|
{
|
||||||
FileStream stream = new(path, FileMode.Open, FileAccess.Read);
|
FileStream stream = new(path, FileMode.Open, FileAccess.Read);
|
||||||
Xci xci = new(_device.Configuration.VirtualFileSystem.KeySet, stream.AsStorage());
|
Xci xci = new(_device.Configuration.VirtualFileSystem.KeySet, stream.AsStorage());
|
||||||
|
@ -44,7 +44,7 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
(bool success, ProcessResult processResult) = xci.OpenPartition(XciPartitionType.Secure).TryLoad(_device, path, out string errorMessage);
|
(bool success, ProcessResult processResult) = xci.OpenPartition(XciPartitionType.Secure).TryLoad(_device, path, applicationId, out string errorMessage);
|
||||||
|
|
||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
|
@ -66,13 +66,13 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool LoadNsp(string path)
|
public bool LoadNsp(string path, ulong applicationId)
|
||||||
{
|
{
|
||||||
FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
||||||
PartitionFileSystem partitionFileSystem = new();
|
PartitionFileSystem partitionFileSystem = new();
|
||||||
partitionFileSystem.Initialize(file.AsStorage()).ThrowIfFailure();
|
partitionFileSystem.Initialize(file.AsStorage()).ThrowIfFailure();
|
||||||
|
|
||||||
(bool success, ProcessResult processResult) = partitionFileSystem.TryLoad(_device, path, out string errorMessage);
|
(bool success, ProcessResult processResult) = partitionFileSystem.TryLoad(_device, path, applicationId, out string errorMessage);
|
||||||
|
|
||||||
if (processResult.ProcessId == 0)
|
if (processResult.ProcessId == 0)
|
||||||
{
|
{
|
||||||
|
|
|
@ -43,15 +43,14 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||||
|
|
||||||
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
|
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
|
||||||
{
|
{
|
||||||
Nca nca = partitionFileSystem.GetNca(device, fileEntry.FullPath);
|
Nca nca = partitionFileSystem.GetNca(device.FileSystem.KeySet, fileEntry.FullPath);
|
||||||
|
|
||||||
if (!nca.IsProgram() && nca.IsPatch())
|
if (!nca.IsProgram())
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong currentProgramId = nca.Header.TitleId;
|
ulong currentMainProgramId = nca.GetProgramIdBase();
|
||||||
ulong currentMainProgramId = currentProgramId & ~0xFFFul;
|
|
||||||
|
|
||||||
if (applicationId == 0 && currentMainProgramId != 0)
|
if (applicationId == 0 && currentMainProgramId != 0)
|
||||||
{
|
{
|
||||||
|
@ -68,7 +67,7 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasIndex[(int)(currentProgramId & 0xF)] = true;
|
hasIndex[nca.GetProgramIndex()] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (programCount == 0)
|
if (programCount == 0)
|
||||||
|
|
|
@ -73,9 +73,9 @@ namespace Ryujinx.HLE
|
||||||
return Processes.LoadUnpackedNca(exeFsDir, romFsFile);
|
return Processes.LoadUnpackedNca(exeFsDir, romFsFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool LoadXci(string xciFile)
|
public bool LoadXci(string xciFile, ulong applicationId = 0)
|
||||||
{
|
{
|
||||||
return Processes.LoadXci(xciFile);
|
return Processes.LoadXci(xciFile, applicationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool LoadNca(string ncaFile)
|
public bool LoadNca(string ncaFile)
|
||||||
|
@ -83,9 +83,9 @@ namespace Ryujinx.HLE
|
||||||
return Processes.LoadNca(ncaFile);
|
return Processes.LoadNca(ncaFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool LoadNsp(string nspFile)
|
public bool LoadNsp(string nspFile, ulong applicationId = 0)
|
||||||
{
|
{
|
||||||
return Processes.LoadNsp(nspFile);
|
return Processes.LoadNsp(nspFile, applicationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool LoadProgram(string fileName)
|
public bool LoadProgram(string fileName)
|
||||||
|
|
45
src/Ryujinx.HLE/Utilities/PartitionFileSystemUtils.cs
Normal file
45
src/Ryujinx.HLE/Utilities/PartitionFileSystemUtils.cs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
using LibHac;
|
||||||
|
using LibHac.Fs.Fsa;
|
||||||
|
using LibHac.FsSystem;
|
||||||
|
using LibHac.Tools.Fs;
|
||||||
|
using LibHac.Tools.FsSystem;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.Utilities
|
||||||
|
{
|
||||||
|
public static class PartitionFileSystemUtils
|
||||||
|
{
|
||||||
|
public static IFileSystem OpenApplicationFileSystem(string path, VirtualFileSystem fileSystem, bool throwOnFailure = true)
|
||||||
|
{
|
||||||
|
FileStream file = File.OpenRead(path);
|
||||||
|
|
||||||
|
IFileSystem partitionFileSystem;
|
||||||
|
|
||||||
|
if (Path.GetExtension(path).ToLower() == ".xci")
|
||||||
|
{
|
||||||
|
partitionFileSystem = new Xci(fileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var pfsTemp = new PartitionFileSystem();
|
||||||
|
Result initResult = pfsTemp.Initialize(file.AsStorage());
|
||||||
|
|
||||||
|
if (throwOnFailure)
|
||||||
|
{
|
||||||
|
initResult.ThrowIfFailure();
|
||||||
|
}
|
||||||
|
else if (initResult.IsFailure())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
partitionFileSystem = pfsTemp;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileSystem.ImportTickets(partitionFileSystem);
|
||||||
|
|
||||||
|
return partitionFileSystem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,9 +9,11 @@ using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
using Ryujinx.UI.Common.Helper;
|
using Ryujinx.UI.Common.Helper;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Ryujinx.UI.App.Common
|
namespace Ryujinx.UI.App.Common
|
||||||
{
|
{
|
||||||
|
@ -19,10 +21,10 @@ namespace Ryujinx.UI.App.Common
|
||||||
{
|
{
|
||||||
public bool Favorite { get; set; }
|
public bool Favorite { get; set; }
|
||||||
public byte[] Icon { get; set; }
|
public byte[] Icon { get; set; }
|
||||||
public string TitleName { get; set; }
|
public string Name { get; set; } = "Unknown";
|
||||||
public string TitleId { get; set; }
|
public ulong Id { get; set; }
|
||||||
public string Developer { get; set; }
|
public string Developer { get; set; } = "Unknown";
|
||||||
public string Version { get; set; }
|
public string Version { get; set; } = "0";
|
||||||
public TimeSpan TimePlayed { get; set; }
|
public TimeSpan TimePlayed { get; set; }
|
||||||
public DateTime? LastPlayed { get; set; }
|
public DateTime? LastPlayed { get; set; }
|
||||||
public string FileExtension { get; set; }
|
public string FileExtension { get; set; }
|
||||||
|
@ -36,7 +38,11 @@ namespace Ryujinx.UI.App.Common
|
||||||
|
|
||||||
public string FileSizeString => ValueFormatUtils.FormatFileSize(FileSize);
|
public string FileSizeString => ValueFormatUtils.FormatFileSize(FileSize);
|
||||||
|
|
||||||
public static string GetApplicationBuildId(VirtualFileSystem virtualFileSystem, string titleFilePath)
|
[JsonIgnore] public string IdString => Id.ToString("x16");
|
||||||
|
|
||||||
|
[JsonIgnore] public ulong IdBase => Id & ~0x1FFFUL;
|
||||||
|
|
||||||
|
public static string GetBuildId(VirtualFileSystem virtualFileSystem, IntegrityCheckLevel checkLevel, string titleFilePath)
|
||||||
{
|
{
|
||||||
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
|
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
|
||||||
|
|
||||||
|
@ -105,7 +111,7 @@ namespace Ryujinx.UI.App.Common
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
(Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _);
|
(Nca updatePatchNca, _) = mainNca.GetUpdateData(virtualFileSystem, checkLevel, 0, out string _);
|
||||||
|
|
||||||
if (updatePatchNca != null)
|
if (updatePatchNca != null)
|
||||||
{
|
{
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -14,6 +14,7 @@ namespace Ryujinx.UI.Common.Helper
|
||||||
public static string BaseDirPathArg { get; private set; }
|
public static string BaseDirPathArg { get; private set; }
|
||||||
public static string Profile { get; private set; }
|
public static string Profile { get; private set; }
|
||||||
public static string LaunchPathArg { get; private set; }
|
public static string LaunchPathArg { get; private set; }
|
||||||
|
public static string LaunchApplicationId { get; private set; }
|
||||||
public static bool StartFullscreenArg { get; private set; }
|
public static bool StartFullscreenArg { get; private set; }
|
||||||
|
|
||||||
public static void ParseArguments(string[] args)
|
public static void ParseArguments(string[] args)
|
||||||
|
@ -72,6 +73,10 @@ namespace Ryujinx.UI.Common.Helper
|
||||||
|
|
||||||
OverrideGraphicsBackend = args[++i];
|
OverrideGraphicsBackend = args[++i];
|
||||||
break;
|
break;
|
||||||
|
case "-i":
|
||||||
|
case "--application-id":
|
||||||
|
LaunchApplicationId = args[++i];
|
||||||
|
break;
|
||||||
case "--docked-mode":
|
case "--docked-mode":
|
||||||
OverrideDockedMode = true;
|
OverrideDockedMode = true;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -15,7 +15,7 @@ namespace Ryujinx.UI.Common.Helper
|
||||||
public static class ShortcutHelper
|
public static class ShortcutHelper
|
||||||
{
|
{
|
||||||
[SupportedOSPlatform("windows")]
|
[SupportedOSPlatform("windows")]
|
||||||
private static void CreateShortcutWindows(string applicationFilePath, byte[] iconData, string iconPath, string cleanedAppName, string desktopPath)
|
private static void CreateShortcutWindows(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string cleanedAppName, string desktopPath)
|
||||||
{
|
{
|
||||||
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName + ".exe");
|
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName + ".exe");
|
||||||
iconPath += ".ico";
|
iconPath += ".ico";
|
||||||
|
@ -25,13 +25,13 @@ namespace Ryujinx.UI.Common.Helper
|
||||||
image.Mutate(x => x.Resize(128, 128));
|
image.Mutate(x => x.Resize(128, 128));
|
||||||
SaveBitmapAsIcon(image, iconPath);
|
SaveBitmapAsIcon(image, iconPath);
|
||||||
|
|
||||||
var shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath), iconPath, 0);
|
var shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath, applicationId), iconPath, 0);
|
||||||
shortcut.StringData.NameString = cleanedAppName;
|
shortcut.StringData.NameString = cleanedAppName;
|
||||||
shortcut.WriteToFile(Path.Combine(desktopPath, cleanedAppName + ".lnk"));
|
shortcut.WriteToFile(Path.Combine(desktopPath, cleanedAppName + ".lnk"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[SupportedOSPlatform("linux")]
|
[SupportedOSPlatform("linux")]
|
||||||
private static void CreateShortcutLinux(string applicationFilePath, byte[] iconData, string iconPath, string desktopPath, string cleanedAppName)
|
private static void CreateShortcutLinux(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string desktopPath, string cleanedAppName)
|
||||||
{
|
{
|
||||||
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.sh");
|
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.sh");
|
||||||
var desktopFile = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-template.desktop");
|
var desktopFile = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-template.desktop");
|
||||||
|
@ -41,11 +41,11 @@ namespace Ryujinx.UI.Common.Helper
|
||||||
image.SaveAsPng(iconPath);
|
image.SaveAsPng(iconPath);
|
||||||
|
|
||||||
using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop"));
|
using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop"));
|
||||||
outputFile.Write(desktopFile, cleanedAppName, iconPath, $"{basePath} {GetArgsString(applicationFilePath)}");
|
outputFile.Write(desktopFile, cleanedAppName, iconPath, $"{basePath} {GetArgsString(applicationFilePath, applicationId)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
private static void CreateShortcutMacos(string appFilePath, byte[] iconData, string desktopPath, string cleanedAppName)
|
private static void CreateShortcutMacos(string appFilePath, string applicationId, byte[] iconData, string desktopPath, string cleanedAppName)
|
||||||
{
|
{
|
||||||
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx");
|
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx");
|
||||||
var plistFile = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-template.plist");
|
var plistFile = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-template.plist");
|
||||||
|
@ -64,7 +64,7 @@ namespace Ryujinx.UI.Common.Helper
|
||||||
string scriptPath = Path.Combine(scriptFolderPath, ScriptName);
|
string scriptPath = Path.Combine(scriptFolderPath, ScriptName);
|
||||||
using StreamWriter scriptFile = new(scriptPath);
|
using StreamWriter scriptFile = new(scriptPath);
|
||||||
|
|
||||||
scriptFile.Write(shortcutScript, basePath, GetArgsString(appFilePath));
|
scriptFile.Write(shortcutScript, basePath, GetArgsString(appFilePath, applicationId));
|
||||||
|
|
||||||
// Set execute permission
|
// Set execute permission
|
||||||
FileInfo fileInfo = new(scriptPath);
|
FileInfo fileInfo = new(scriptPath);
|
||||||
|
@ -95,7 +95,7 @@ namespace Ryujinx.UI.Common.Helper
|
||||||
{
|
{
|
||||||
string iconPath = Path.Combine(AppDataManager.BaseDirPath, "games", applicationId, "app");
|
string iconPath = Path.Combine(AppDataManager.BaseDirPath, "games", applicationId, "app");
|
||||||
|
|
||||||
CreateShortcutWindows(applicationFilePath, iconData, iconPath, cleanedAppName, desktopPath);
|
CreateShortcutWindows(applicationFilePath, applicationId, iconData, iconPath, cleanedAppName, desktopPath);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -105,14 +105,14 @@ namespace Ryujinx.UI.Common.Helper
|
||||||
string iconPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "icons", "Ryujinx");
|
string iconPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "icons", "Ryujinx");
|
||||||
|
|
||||||
Directory.CreateDirectory(iconPath);
|
Directory.CreateDirectory(iconPath);
|
||||||
CreateShortcutLinux(applicationFilePath, iconData, Path.Combine(iconPath, applicationId), desktopPath, cleanedAppName);
|
CreateShortcutLinux(applicationFilePath, applicationId, iconData, Path.Combine(iconPath, applicationId), desktopPath, cleanedAppName);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (OperatingSystem.IsMacOS())
|
if (OperatingSystem.IsMacOS())
|
||||||
{
|
{
|
||||||
CreateShortcutMacos(applicationFilePath, iconData, desktopPath, cleanedAppName);
|
CreateShortcutMacos(applicationFilePath, applicationId, iconData, desktopPath, cleanedAppName);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -120,7 +120,7 @@ namespace Ryujinx.UI.Common.Helper
|
||||||
throw new NotImplementedException("Shortcut support has not been implemented yet for this OS.");
|
throw new NotImplementedException("Shortcut support has not been implemented yet for this OS.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetArgsString(string appFilePath)
|
private static string GetArgsString(string appFilePath, string applicationId)
|
||||||
{
|
{
|
||||||
// args are first defined as a list, for easier adjustments in the future
|
// args are first defined as a list, for easier adjustments in the future
|
||||||
var argsList = new List<string>();
|
var argsList = new List<string>();
|
||||||
|
@ -131,6 +131,12 @@ namespace Ryujinx.UI.Common.Helper
|
||||||
argsList.Add($"\"{CommandLineState.BaseDirPathArg}\"");
|
argsList.Add($"\"{CommandLineState.BaseDirPathArg}\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (appFilePath.ToLower().EndsWith(".xci"))
|
||||||
|
{
|
||||||
|
argsList.Add("--application-id");
|
||||||
|
argsList.Add($"\"{applicationId}\"");
|
||||||
|
}
|
||||||
|
|
||||||
argsList.Add($"\"{appFilePath}\"");
|
argsList.Add($"\"{appFilePath}\"");
|
||||||
|
|
||||||
return String.Join(" ", argsList);
|
return String.Join(" ", argsList);
|
||||||
|
|
|
@ -40,20 +40,17 @@ using Ryujinx.UI.Common;
|
||||||
using Ryujinx.UI.Common.Configuration;
|
using Ryujinx.UI.Common.Configuration;
|
||||||
using Ryujinx.UI.Common.Helper;
|
using Ryujinx.UI.Common.Helper;
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using SixLabors.ImageSharp;
|
using SkiaSharp;
|
||||||
using SixLabors.ImageSharp.Formats.Png;
|
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
|
||||||
using SixLabors.ImageSharp.Processing;
|
|
||||||
using SPB.Graphics.Vulkan;
|
using SPB.Graphics.Vulkan;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
||||||
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
|
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
|
||||||
using Image = SixLabors.ImageSharp.Image;
|
|
||||||
using InputManager = Ryujinx.Input.HLE.InputManager;
|
using InputManager = Ryujinx.Input.HLE.InputManager;
|
||||||
using IRenderer = Ryujinx.Graphics.GAL.IRenderer;
|
using IRenderer = Ryujinx.Graphics.GAL.IRenderer;
|
||||||
using Key = Ryujinx.Input.Key;
|
using Key = Ryujinx.Input.Key;
|
||||||
|
@ -135,12 +132,14 @@ namespace Ryujinx.Ava
|
||||||
public int Width { get; private set; }
|
public int Width { get; private set; }
|
||||||
public int Height { get; private set; }
|
public int Height { get; private set; }
|
||||||
public string ApplicationPath { get; private set; }
|
public string ApplicationPath { get; private set; }
|
||||||
|
public ulong ApplicationId { get; private set; }
|
||||||
public bool ScreenshotRequested { get; set; }
|
public bool ScreenshotRequested { get; set; }
|
||||||
|
|
||||||
public AppHost(
|
public AppHost(
|
||||||
RendererHost renderer,
|
RendererHost renderer,
|
||||||
InputManager inputManager,
|
InputManager inputManager,
|
||||||
string applicationPath,
|
string applicationPath,
|
||||||
|
ulong applicationId,
|
||||||
VirtualFileSystem virtualFileSystem,
|
VirtualFileSystem virtualFileSystem,
|
||||||
ContentManager contentManager,
|
ContentManager contentManager,
|
||||||
AccountManager accountManager,
|
AccountManager accountManager,
|
||||||
|
@ -164,6 +163,7 @@ namespace Ryujinx.Ava
|
||||||
NpadManager = _inputManager.CreateNpadManager();
|
NpadManager = _inputManager.CreateNpadManager();
|
||||||
TouchScreenManager = _inputManager.CreateTouchScreenManager();
|
TouchScreenManager = _inputManager.CreateTouchScreenManager();
|
||||||
ApplicationPath = applicationPath;
|
ApplicationPath = applicationPath;
|
||||||
|
ApplicationId = applicationId;
|
||||||
VirtualFileSystem = virtualFileSystem;
|
VirtualFileSystem = virtualFileSystem;
|
||||||
ContentManager = contentManager;
|
ContentManager = contentManager;
|
||||||
|
|
||||||
|
@ -366,25 +366,33 @@ namespace Ryujinx.Ava
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Image image = e.IsBgra ? Image.LoadPixelData<Bgra32>(e.Data, e.Width, e.Height)
|
var colorType = e.IsBgra ? SKColorType.Bgra8888 : SKColorType.Rgba8888;
|
||||||
: Image.LoadPixelData<Rgba32>(e.Data, e.Width, e.Height);
|
using var bitmap = new SKBitmap(new SKImageInfo(e.Width, e.Height, colorType, SKAlphaType.Premul));
|
||||||
|
|
||||||
if (e.FlipX)
|
Marshal.Copy(e.Data, 0, bitmap.GetPixels(), e.Data.Length);
|
||||||
|
|
||||||
|
SKBitmap bitmapToSave = null;
|
||||||
|
|
||||||
|
if (e.FlipX || e.FlipY)
|
||||||
{
|
{
|
||||||
image.Mutate(x => x.Flip(FlipMode.Horizontal));
|
bitmapToSave = new SKBitmap(bitmap.Width, bitmap.Height);
|
||||||
|
|
||||||
|
using var canvas = new SKCanvas(bitmapToSave);
|
||||||
|
|
||||||
|
canvas.Clear(SKColors.Transparent);
|
||||||
|
|
||||||
|
float scaleX = e.FlipX ? -1 : 1;
|
||||||
|
float scaleY = e.FlipY ? -1 : 1;
|
||||||
|
|
||||||
|
var matrix = SKMatrix.CreateScale(scaleX, scaleY, bitmap.Width / 2f, bitmap.Height / 2f);
|
||||||
|
|
||||||
|
canvas.SetMatrix(matrix);
|
||||||
|
|
||||||
|
canvas.DrawBitmap(bitmap, new SKPoint(e.FlipX ? -bitmap.Width : 0, e.FlipY ? -bitmap.Height : 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.FlipY)
|
SaveBitmapAsPng(bitmapToSave ?? bitmap, path);
|
||||||
{
|
bitmapToSave?.Dispose();
|
||||||
image.Mutate(x => x.Flip(FlipMode.Vertical));
|
|
||||||
}
|
|
||||||
|
|
||||||
image.SaveAsPng(path, new PngEncoder
|
|
||||||
{
|
|
||||||
ColorType = PngColorType.Rgb,
|
|
||||||
});
|
|
||||||
|
|
||||||
image.Dispose();
|
|
||||||
|
|
||||||
Logger.Notice.Print(LogClass.Application, $"Screenshot saved to {path}", "Screenshot");
|
Logger.Notice.Print(LogClass.Application, $"Screenshot saved to {path}", "Screenshot");
|
||||||
}
|
}
|
||||||
|
@ -396,6 +404,14 @@ namespace Ryujinx.Ava
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SaveBitmapAsPng(SKBitmap bitmap, string path)
|
||||||
|
{
|
||||||
|
using var data = bitmap.Encode(SKEncodedImageFormat.Png, 100);
|
||||||
|
using var stream = File.OpenWrite(path);
|
||||||
|
|
||||||
|
data.SaveTo(stream);
|
||||||
|
}
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
|
@ -706,7 +722,7 @@ namespace Ryujinx.Ava
|
||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
|
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
|
||||||
|
|
||||||
if (!Device.LoadXci(ApplicationPath))
|
if (!Device.LoadXci(ApplicationPath, ApplicationId))
|
||||||
{
|
{
|
||||||
Device.Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
|
@ -733,7 +749,7 @@ namespace Ryujinx.Ava
|
||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
|
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
|
||||||
|
|
||||||
if (!Device.LoadNsp(ApplicationPath))
|
if (!Device.LoadNsp(ApplicationPath, ApplicationId))
|
||||||
{
|
{
|
||||||
Device.Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
"SettingsTabSystemUseHypervisor": "Use Hypervisor",
|
"SettingsTabSystemUseHypervisor": "Use Hypervisor",
|
||||||
"MenuBarFile": "_File",
|
"MenuBarFile": "_File",
|
||||||
"MenuBarFileOpenFromFile": "_Load Application From File",
|
"MenuBarFileOpenFromFile": "_Load Application From File",
|
||||||
|
"MenuBarFileOpenFromFileError": "No applications found in selected file.",
|
||||||
"MenuBarFileOpenUnpacked": "Load _Unpacked Game",
|
"MenuBarFileOpenUnpacked": "Load _Unpacked Game",
|
||||||
"MenuBarFileOpenEmuFolder": "Open Ryujinx Folder",
|
"MenuBarFileOpenEmuFolder": "Open Ryujinx Folder",
|
||||||
"MenuBarFileOpenLogsFolder": "Open Logs Folder",
|
"MenuBarFileOpenLogsFolder": "Open Logs Folder",
|
||||||
|
@ -649,6 +650,8 @@
|
||||||
"OpenSetupGuideMessage": "Open the Setup Guide",
|
"OpenSetupGuideMessage": "Open the Setup Guide",
|
||||||
"NoUpdate": "No Update",
|
"NoUpdate": "No Update",
|
||||||
"TitleUpdateVersionLabel": "Version {0}",
|
"TitleUpdateVersionLabel": "Version {0}",
|
||||||
|
"TitleBundledUpdateVersionLabel": "Bundled: Version {0}",
|
||||||
|
"TitleBundledDlcLabel": "Bundled:",
|
||||||
"RyujinxInfo": "Ryujinx - Info",
|
"RyujinxInfo": "Ryujinx - Info",
|
||||||
"RyujinxConfirm": "Ryujinx - Confirmation",
|
"RyujinxConfirm": "Ryujinx - Confirmation",
|
||||||
"FileDialogAllTypes": "All types",
|
"FileDialogAllTypes": "All types",
|
||||||
|
|
|
@ -18,7 +18,8 @@ using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
using Ryujinx.UI.App.Common;
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
|
using Ryujinx.UI.Common.Configuration;
|
||||||
using Ryujinx.UI.Common.Helper;
|
using Ryujinx.UI.Common.Helper;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
|
@ -226,7 +227,11 @@ namespace Ryujinx.Ava.Common
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
(Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
|
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||||
|
? IntegrityCheckLevel.ErrorOnInvalid
|
||||||
|
: IntegrityCheckLevel.None;
|
||||||
|
|
||||||
|
(Nca updatePatchNca, _) = mainNca.GetUpdateData(_virtualFileSystem, checkLevel, programIndex, out _);
|
||||||
if (updatePatchNca != null)
|
if (updatePatchNca != null)
|
||||||
{
|
{
|
||||||
patchNca = updatePatchNca;
|
patchNca = updatePatchNca;
|
||||||
|
|
|
@ -125,7 +125,7 @@ namespace Ryujinx.Ava
|
||||||
|
|
||||||
if (CommandLineState.LaunchPathArg != null)
|
if (CommandLineState.LaunchPathArg != null)
|
||||||
{
|
{
|
||||||
MainWindow.DeferLoadApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg);
|
MainWindow.DeferLoadApplication(CommandLineState.LaunchPathArg, CommandLineState.LaunchApplicationId, CommandLineState.StartFullscreenArg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,6 @@
|
||||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
|
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
|
||||||
<PackageReference Include="SPB" />
|
<PackageReference Include="SPB" />
|
||||||
<PackageReference Include="SharpZipLib" />
|
<PackageReference Include="SharpZipLib" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.Threading;
|
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Ava.Common;
|
using Ryujinx.Ava.Common;
|
||||||
|
@ -15,7 +14,6 @@ using Ryujinx.UI.App.Common;
|
||||||
using Ryujinx.UI.Common.Helper;
|
using Ryujinx.UI.Common.Helper;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
|
@ -41,7 +39,7 @@ namespace Ryujinx.Ava.UI.Controls
|
||||||
{
|
{
|
||||||
viewModel.SelectedApplication.Favorite = !viewModel.SelectedApplication.Favorite;
|
viewModel.SelectedApplication.Favorite = !viewModel.SelectedApplication.Favorite;
|
||||||
|
|
||||||
ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.TitleId, appMetadata =>
|
ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.IdString, appMetadata =>
|
||||||
{
|
{
|
||||||
appMetadata.Favorite = viewModel.SelectedApplication.Favorite;
|
appMetadata.Favorite = viewModel.SelectedApplication.Favorite;
|
||||||
});
|
});
|
||||||
|
@ -76,19 +74,9 @@ namespace Ryujinx.Ava.UI.Controls
|
||||||
{
|
{
|
||||||
if (viewModel?.SelectedApplication != null)
|
if (viewModel?.SelectedApplication != null)
|
||||||
{
|
{
|
||||||
if (!ulong.TryParse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
|
var saveDataFilter = SaveDataFilter.Make(viewModel.SelectedApplication.Id, saveDataType, userId, saveDataId: default, index: default);
|
||||||
{
|
|
||||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
|
||||||
{
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogRyujinxErrorMessage], LocaleManager.Instance[LocaleKeys.DialogInvalidTitleIdErrorMessage]);
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
ApplicationHelper.OpenSaveDir(in saveDataFilter, viewModel.SelectedApplication.Id, viewModel.SelectedApplication.ControlHolder, viewModel.SelectedApplication.Name);
|
||||||
}
|
|
||||||
|
|
||||||
var saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveDataType, userId, saveDataId: default, index: default);
|
|
||||||
|
|
||||||
ApplicationHelper.OpenSaveDir(in saveDataFilter, titleIdNumber, viewModel.SelectedApplication.ControlHolder, viewModel.SelectedApplication.TitleName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +86,7 @@ namespace Ryujinx.Ava.UI.Controls
|
||||||
|
|
||||||
if (viewModel?.SelectedApplication != null)
|
if (viewModel?.SelectedApplication != null)
|
||||||
{
|
{
|
||||||
await TitleUpdateWindow.Show(viewModel.VirtualFileSystem, ulong.Parse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber), viewModel.SelectedApplication.TitleName);
|
await TitleUpdateWindow.Show(viewModel.VirtualFileSystem, viewModel.SelectedApplication);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +96,7 @@ namespace Ryujinx.Ava.UI.Controls
|
||||||
|
|
||||||
if (viewModel?.SelectedApplication != null)
|
if (viewModel?.SelectedApplication != null)
|
||||||
{
|
{
|
||||||
await DownloadableContentManagerWindow.Show(viewModel.VirtualFileSystem, ulong.Parse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber), viewModel.SelectedApplication.TitleName);
|
await DownloadableContentManagerWindow.Show(viewModel.VirtualFileSystem, viewModel.SelectedApplication);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,8 +108,8 @@ namespace Ryujinx.Ava.UI.Controls
|
||||||
{
|
{
|
||||||
await new CheatWindow(
|
await new CheatWindow(
|
||||||
viewModel.VirtualFileSystem,
|
viewModel.VirtualFileSystem,
|
||||||
viewModel.SelectedApplication.TitleId,
|
viewModel.SelectedApplication.IdString,
|
||||||
viewModel.SelectedApplication.TitleName,
|
viewModel.SelectedApplication.Name,
|
||||||
viewModel.SelectedApplication.Path).ShowDialog(viewModel.TopLevel as Window);
|
viewModel.SelectedApplication.Path).ShowDialog(viewModel.TopLevel as Window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,7 +121,7 @@ namespace Ryujinx.Ava.UI.Controls
|
||||||
if (viewModel?.SelectedApplication != null)
|
if (viewModel?.SelectedApplication != null)
|
||||||
{
|
{
|
||||||
string modsBasePath = ModLoader.GetModsBasePath();
|
string modsBasePath = ModLoader.GetModsBasePath();
|
||||||
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, viewModel.SelectedApplication.TitleId);
|
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, viewModel.SelectedApplication.IdString);
|
||||||
|
|
||||||
OpenHelper.OpenFolder(titleModsPath);
|
OpenHelper.OpenFolder(titleModsPath);
|
||||||
}
|
}
|
||||||
|
@ -146,7 +134,7 @@ namespace Ryujinx.Ava.UI.Controls
|
||||||
if (viewModel?.SelectedApplication != null)
|
if (viewModel?.SelectedApplication != null)
|
||||||
{
|
{
|
||||||
string sdModsBasePath = ModLoader.GetSdModsBasePath();
|
string sdModsBasePath = ModLoader.GetSdModsBasePath();
|
||||||
string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, viewModel.SelectedApplication.TitleId);
|
string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, viewModel.SelectedApplication.IdString);
|
||||||
|
|
||||||
OpenHelper.OpenFolder(titleModsPath);
|
OpenHelper.OpenFolder(titleModsPath);
|
||||||
}
|
}
|
||||||
|
@ -158,7 +146,7 @@ namespace Ryujinx.Ava.UI.Controls
|
||||||
|
|
||||||
if (viewModel?.SelectedApplication != null)
|
if (viewModel?.SelectedApplication != null)
|
||||||
{
|
{
|
||||||
await ModManagerWindow.Show(ulong.Parse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber), viewModel.SelectedApplication.TitleName);
|
await ModManagerWindow.Show(viewModel.SelectedApplication.Id, viewModel.SelectedApplication.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,15 +158,15 @@ namespace Ryujinx.Ava.UI.Controls
|
||||||
{
|
{
|
||||||
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
|
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
|
||||||
LocaleManager.Instance[LocaleKeys.DialogWarning],
|
LocaleManager.Instance[LocaleKeys.DialogWarning],
|
||||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.TitleName),
|
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.Name),
|
||||||
LocaleManager.Instance[LocaleKeys.InputDialogYes],
|
LocaleManager.Instance[LocaleKeys.InputDialogYes],
|
||||||
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
||||||
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
|
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
|
||||||
|
|
||||||
if (result == UserResult.Yes)
|
if (result == UserResult.Yes)
|
||||||
{
|
{
|
||||||
DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "cpu", "0"));
|
DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "0"));
|
||||||
DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "cpu", "1"));
|
DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "1"));
|
||||||
|
|
||||||
List<FileInfo> cacheFiles = new();
|
List<FileInfo> cacheFiles = new();
|
||||||
|
|
||||||
|
@ -218,14 +206,14 @@ namespace Ryujinx.Ava.UI.Controls
|
||||||
{
|
{
|
||||||
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
|
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
|
||||||
LocaleManager.Instance[LocaleKeys.DialogWarning],
|
LocaleManager.Instance[LocaleKeys.DialogWarning],
|
||||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.TitleName),
|
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.Name),
|
||||||
LocaleManager.Instance[LocaleKeys.InputDialogYes],
|
LocaleManager.Instance[LocaleKeys.InputDialogYes],
|
||||||
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
||||||
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
|
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
|
||||||
|
|
||||||
if (result == UserResult.Yes)
|
if (result == UserResult.Yes)
|
||||||
{
|
{
|
||||||
DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "shader"));
|
DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "shader"));
|
||||||
|
|
||||||
List<DirectoryInfo> oldCacheDirectories = new();
|
List<DirectoryInfo> oldCacheDirectories = new();
|
||||||
List<FileInfo> newCacheFiles = new();
|
List<FileInfo> newCacheFiles = new();
|
||||||
|
@ -273,7 +261,7 @@ namespace Ryujinx.Ava.UI.Controls
|
||||||
|
|
||||||
if (viewModel?.SelectedApplication != null)
|
if (viewModel?.SelectedApplication != null)
|
||||||
{
|
{
|
||||||
string ptcDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "cpu");
|
string ptcDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu");
|
||||||
string mainDir = Path.Combine(ptcDir, "0");
|
string mainDir = Path.Combine(ptcDir, "0");
|
||||||
string backupDir = Path.Combine(ptcDir, "1");
|
string backupDir = Path.Combine(ptcDir, "1");
|
||||||
|
|
||||||
|
@ -294,7 +282,7 @@ namespace Ryujinx.Ava.UI.Controls
|
||||||
|
|
||||||
if (viewModel?.SelectedApplication != null)
|
if (viewModel?.SelectedApplication != null)
|
||||||
{
|
{
|
||||||
string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "shader");
|
string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "shader");
|
||||||
|
|
||||||
if (!Directory.Exists(shaderCacheDir))
|
if (!Directory.Exists(shaderCacheDir))
|
||||||
{
|
{
|
||||||
|
@ -315,7 +303,7 @@ namespace Ryujinx.Ava.UI.Controls
|
||||||
viewModel.StorageProvider,
|
viewModel.StorageProvider,
|
||||||
NcaSectionType.Code,
|
NcaSectionType.Code,
|
||||||
viewModel.SelectedApplication.Path,
|
viewModel.SelectedApplication.Path,
|
||||||
viewModel.SelectedApplication.TitleName);
|
viewModel.SelectedApplication.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,7 +317,7 @@ namespace Ryujinx.Ava.UI.Controls
|
||||||
viewModel.StorageProvider,
|
viewModel.StorageProvider,
|
||||||
NcaSectionType.Data,
|
NcaSectionType.Data,
|
||||||
viewModel.SelectedApplication.Path,
|
viewModel.SelectedApplication.Path,
|
||||||
viewModel.SelectedApplication.TitleName);
|
viewModel.SelectedApplication.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,7 +331,7 @@ namespace Ryujinx.Ava.UI.Controls
|
||||||
viewModel.StorageProvider,
|
viewModel.StorageProvider,
|
||||||
NcaSectionType.Logo,
|
NcaSectionType.Logo,
|
||||||
viewModel.SelectedApplication.Path,
|
viewModel.SelectedApplication.Path,
|
||||||
viewModel.SelectedApplication.TitleName);
|
viewModel.SelectedApplication.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,7 +342,7 @@ namespace Ryujinx.Ava.UI.Controls
|
||||||
if (viewModel?.SelectedApplication != null)
|
if (viewModel?.SelectedApplication != null)
|
||||||
{
|
{
|
||||||
ApplicationData selectedApplication = viewModel.SelectedApplication;
|
ApplicationData selectedApplication = viewModel.SelectedApplication;
|
||||||
ShortcutHelper.CreateAppShortcut(selectedApplication.Path, selectedApplication.TitleName, selectedApplication.TitleId, selectedApplication.Icon);
|
ShortcutHelper.CreateAppShortcut(selectedApplication.Path, selectedApplication.Name, selectedApplication.IdString, selectedApplication.Icon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,7 +352,7 @@ namespace Ryujinx.Ava.UI.Controls
|
||||||
|
|
||||||
if (viewModel?.SelectedApplication != null)
|
if (viewModel?.SelectedApplication != null)
|
||||||
{
|
{
|
||||||
await viewModel.LoadApplication(viewModel.SelectedApplication.Path);
|
await viewModel.LoadApplication(viewModel.SelectedApplication);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,7 +80,7 @@
|
||||||
<TextBlock
|
<TextBlock
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{Binding TitleName}"
|
Text="{Binding Name}"
|
||||||
TextAlignment="Center"
|
TextAlignment="Center"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
|
@ -85,7 +85,7 @@
|
||||||
<TextBlock
|
<TextBlock
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
FontWeight="Bold"
|
FontWeight="Bold"
|
||||||
Text="{Binding TitleName}"
|
Text="{Binding Name}"
|
||||||
TextAlignment="Start"
|
TextAlignment="Start"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
|
@ -109,7 +109,7 @@
|
||||||
Spacing="5">
|
Spacing="5">
|
||||||
<TextBlock
|
<TextBlock
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Text="{Binding TitleId}"
|
Text="{Binding Id, StringFormat=X16}"
|
||||||
TextAlignment="Start"
|
TextAlignment="Start"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
|
@ -24,6 +25,9 @@ namespace Ryujinx.Ava.UI.Models
|
||||||
|
|
||||||
public string FileName => Path.GetFileName(ContainerPath);
|
public string FileName => Path.GetFileName(ContainerPath);
|
||||||
|
|
||||||
|
public string Label =>
|
||||||
|
Path.GetExtension(FileName)?.ToLower() == ".xci" ? $"{LocaleManager.Instance[LocaleKeys.TitleBundledDlcLabel]} {FileName}" : FileName;
|
||||||
|
|
||||||
public DownloadableContentModel(string titleId, string containerPath, string fullPath, bool enabled)
|
public DownloadableContentModel(string titleId, string containerPath, string fullPath, bool enabled)
|
||||||
{
|
{
|
||||||
TitleId = titleId;
|
TitleId = titleId;
|
||||||
|
|
|
@ -46,14 +46,14 @@ namespace Ryujinx.Ava.UI.Models
|
||||||
TitleId = info.ProgramId;
|
TitleId = info.ProgramId;
|
||||||
UserId = info.UserId;
|
UserId = info.UserId;
|
||||||
|
|
||||||
var appData = MainWindow.MainWindowViewModel.Applications.FirstOrDefault(x => x.TitleId.ToUpper() == TitleIdString);
|
var appData = MainWindow.MainWindowViewModel.Applications.FirstOrDefault(x => x.IdString.ToUpper() == TitleIdString);
|
||||||
|
|
||||||
InGameList = appData != null;
|
InGameList = appData != null;
|
||||||
|
|
||||||
if (InGameList)
|
if (InGameList)
|
||||||
{
|
{
|
||||||
Icon = appData.Icon;
|
Icon = appData.Icon;
|
||||||
Title = appData.TitleName;
|
Title = appData.Name;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,7 +8,10 @@ namespace Ryujinx.Ava.UI.Models
|
||||||
public ApplicationControlProperty Control { get; }
|
public ApplicationControlProperty Control { get; }
|
||||||
public string Path { get; }
|
public string Path { get; }
|
||||||
|
|
||||||
public string Label => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleUpdateVersionLabel, Control.DisplayVersionString.ToString());
|
public string Label => LocaleManager.Instance.UpdateAndGetDynamicValue(
|
||||||
|
System.IO.Path.GetExtension(Path)?.ToLower() == ".xci" ? LocaleKeys.TitleBundledUpdateVersionLabel : LocaleKeys.TitleUpdateVersionLabel,
|
||||||
|
Control.DisplayVersionString.ToString()
|
||||||
|
);
|
||||||
|
|
||||||
public TitleUpdateModel(ApplicationControlProperty control, string path)
|
public TitleUpdateModel(ApplicationControlProperty control, string path)
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,7 +6,6 @@ using DynamicData;
|
||||||
using LibHac.Common;
|
using LibHac.Common;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Fs.Fsa;
|
using LibHac.Fs.Fsa;
|
||||||
using LibHac.FsSystem;
|
|
||||||
using LibHac.Tools.Fs;
|
using LibHac.Tools.Fs;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
|
@ -17,11 +16,13 @@ using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
|
using Ryujinx.HLE.Utilities;
|
||||||
|
using Ryujinx.UI.App.Common;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Application = Avalonia.Application;
|
using Application = Avalonia.Application;
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
|
@ -38,7 +39,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
private AvaloniaList<DownloadableContentModel> _selectedDownloadableContents = new();
|
private AvaloniaList<DownloadableContentModel> _selectedDownloadableContents = new();
|
||||||
|
|
||||||
private string _search;
|
private string _search;
|
||||||
private readonly ulong _titleId;
|
private readonly ApplicationData _applicationData;
|
||||||
private readonly IStorageProvider _storageProvider;
|
private readonly IStorageProvider _storageProvider;
|
||||||
|
|
||||||
private static readonly DownloadableContentJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
private static readonly DownloadableContentJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
|
@ -91,18 +92,25 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
get => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count);
|
get => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ulong titleId)
|
public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
|
||||||
{
|
{
|
||||||
_virtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
|
|
||||||
_titleId = titleId;
|
_applicationData = applicationData;
|
||||||
|
|
||||||
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
_storageProvider = desktop.MainWindow.StorageProvider;
|
_storageProvider = desktop.MainWindow.StorageProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
|
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, applicationData.IdString, "dlc.json");
|
||||||
|
|
||||||
|
if (!File.Exists(_downloadableContentJsonPath))
|
||||||
|
{
|
||||||
|
_downloadableContentContainerList = new List<DownloadableContentContainer>();
|
||||||
|
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -123,12 +131,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
{
|
{
|
||||||
if (File.Exists(downloadableContentContainer.ContainerPath))
|
if (File.Exists(downloadableContentContainer.ContainerPath))
|
||||||
{
|
{
|
||||||
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
|
using IFileSystem partitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(downloadableContentContainer.ContainerPath, _virtualFileSystem);
|
||||||
|
|
||||||
PartitionFileSystem partitionFileSystem = new();
|
|
||||||
partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
|
||||||
|
|
||||||
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
|
||||||
|
|
||||||
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
|
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
|
||||||
{
|
{
|
||||||
|
@ -157,6 +160,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: Try to load downloadable contents from PFS last to preserve enabled state.
|
||||||
|
AddDownloadableContent(_applicationData.Path);
|
||||||
|
|
||||||
// NOTE: Save the list again to remove leftovers.
|
// NOTE: Save the list again to remove leftovers.
|
||||||
Save();
|
Save();
|
||||||
Sort();
|
Sort();
|
||||||
|
@ -219,25 +225,23 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
|
||||||
foreach (var file in result)
|
foreach (var file in result)
|
||||||
{
|
{
|
||||||
await AddDownloadableContent(file.Path.LocalPath);
|
if (!AddDownloadableContent(file.Path.LocalPath))
|
||||||
|
{
|
||||||
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AddDownloadableContent(string path)
|
private bool AddDownloadableContent(string path)
|
||||||
{
|
{
|
||||||
if (!File.Exists(path) || DownloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null)
|
if (!File.Exists(path) || _downloadableContentContainerList.Any(x => x.ContainerPath == path))
|
||||||
{
|
{
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
using FileStream containerFile = File.OpenRead(path);
|
using IFileSystem partitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(path, _virtualFileSystem);
|
||||||
|
|
||||||
PartitionFileSystem partitionFileSystem = new();
|
|
||||||
partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
|
||||||
bool containsDownloadableContent = false;
|
|
||||||
|
|
||||||
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
|
||||||
|
|
||||||
|
bool success = false;
|
||||||
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
|
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
|
||||||
{
|
{
|
||||||
using var ncaFile = new UniqueRef<IFile>();
|
using var ncaFile = new UniqueRef<IFile>();
|
||||||
|
@ -252,26 +256,26 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
|
||||||
if (nca.Header.ContentType == NcaContentType.PublicData)
|
if (nca.Header.ContentType == NcaContentType.PublicData)
|
||||||
{
|
{
|
||||||
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != _titleId)
|
if (nca.GetProgramIdBase() != _applicationData.IdBase)
|
||||||
{
|
{
|
||||||
break;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var content = new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true);
|
var content = new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true);
|
||||||
DownloadableContents.Add(content);
|
DownloadableContents.Add(content);
|
||||||
SelectedDownloadableContents.Add(content);
|
SelectedDownloadableContents.Add(content);
|
||||||
|
|
||||||
OnPropertyChanged(nameof(UpdateCount));
|
success = true;
|
||||||
Sort();
|
|
||||||
|
|
||||||
containsDownloadableContent = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!containsDownloadableContent)
|
if (success)
|
||||||
{
|
{
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]);
|
OnPropertyChanged(nameof(UpdateCount));
|
||||||
|
Sort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Remove(DownloadableContentModel model)
|
public void Remove(DownloadableContentModel model)
|
||||||
|
|
|
@ -32,7 +32,7 @@ using Ryujinx.UI.App.Common;
|
||||||
using Ryujinx.UI.Common;
|
using Ryujinx.UI.Common;
|
||||||
using Ryujinx.UI.Common.Configuration;
|
using Ryujinx.UI.Common.Configuration;
|
||||||
using Ryujinx.UI.Common.Helper;
|
using Ryujinx.UI.Common.Helper;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SkiaSharp;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
@ -40,7 +40,6 @@ using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Image = SixLabors.ImageSharp.Image;
|
|
||||||
using Key = Ryujinx.Input.Key;
|
using Key = Ryujinx.Input.Key;
|
||||||
using MissingKeyException = LibHac.Common.Keys.MissingKeyException;
|
using MissingKeyException = LibHac.Common.Keys.MissingKeyException;
|
||||||
using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState;
|
using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState;
|
||||||
|
@ -97,7 +96,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
private bool _canUpdate = true;
|
private bool _canUpdate = true;
|
||||||
private Cursor _cursor;
|
private Cursor _cursor;
|
||||||
private string _title;
|
private string _title;
|
||||||
private string _currentEmulatedGamePath;
|
private ApplicationData _currentApplicationData;
|
||||||
private readonly AutoResetEvent _rendererWaitEvent;
|
private readonly AutoResetEvent _rendererWaitEvent;
|
||||||
private WindowState _windowState;
|
private WindowState _windowState;
|
||||||
private double _windowWidth;
|
private double _windowWidth;
|
||||||
|
@ -109,7 +108,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
public ApplicationData ListSelectedApplication;
|
public ApplicationData ListSelectedApplication;
|
||||||
public ApplicationData GridSelectedApplication;
|
public ApplicationData GridSelectedApplication;
|
||||||
|
|
||||||
private string TitleName { get; set; }
|
|
||||||
internal AppHost AppHost { get; set; }
|
internal AppHost AppHost { get; set; }
|
||||||
|
|
||||||
public MainWindowViewModel()
|
public MainWindowViewModel()
|
||||||
|
@ -955,8 +953,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
return SortMode switch
|
return SortMode switch
|
||||||
{
|
{
|
||||||
#pragma warning disable IDE0055 // Disable formatting
|
#pragma warning disable IDE0055 // Disable formatting
|
||||||
ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName)
|
ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Name)
|
||||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName),
|
: SortExpressionComparer<ApplicationData>.Descending(app => app.Name),
|
||||||
ApplicationSort.Developer => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer)
|
ApplicationSort.Developer => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer)
|
||||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.Developer),
|
: SortExpressionComparer<ApplicationData>.Descending(app => app.Developer),
|
||||||
ApplicationSort.LastPlayed => new LastPlayedSortComparer(IsAscending),
|
ApplicationSort.LastPlayed => new LastPlayedSortComparer(IsAscending),
|
||||||
|
@ -1000,7 +998,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
|
||||||
CompareInfo compareInfo = CultureInfo.CurrentCulture.CompareInfo;
|
CompareInfo compareInfo = CultureInfo.CurrentCulture.CompareInfo;
|
||||||
|
|
||||||
return compareInfo.IndexOf(app.TitleName, _searchText, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace) >= 0;
|
return compareInfo.IndexOf(app.Name, _searchText, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -1129,7 +1127,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
IsLoadingIndeterminate = false;
|
IsLoadingIndeterminate = false;
|
||||||
break;
|
break;
|
||||||
case LoadState.Loaded:
|
case LoadState.Loaded:
|
||||||
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, TitleName);
|
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, _currentApplicationData.Name);
|
||||||
IsLoadingIndeterminate = true;
|
IsLoadingIndeterminate = true;
|
||||||
CacheLoadStatus = "";
|
CacheLoadStatus = "";
|
||||||
break;
|
break;
|
||||||
|
@ -1149,7 +1147,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
IsLoadingIndeterminate = false;
|
IsLoadingIndeterminate = false;
|
||||||
break;
|
break;
|
||||||
case ShaderCacheLoadingState.Loaded:
|
case ShaderCacheLoadingState.Loaded:
|
||||||
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, TitleName);
|
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, _currentApplicationData.Name);
|
||||||
IsLoadingIndeterminate = true;
|
IsLoadingIndeterminate = true;
|
||||||
CacheLoadStatus = "";
|
CacheLoadStatus = "";
|
||||||
break;
|
break;
|
||||||
|
@ -1164,17 +1162,17 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
private void PrepareLoadScreen()
|
private void PrepareLoadScreen()
|
||||||
{
|
{
|
||||||
using MemoryStream stream = new(SelectedIcon);
|
using MemoryStream stream = new(SelectedIcon);
|
||||||
using var gameIconBmp = Image.Load<Bgra32>(stream);
|
using var gameIconBmp = SKBitmap.Decode(stream);
|
||||||
|
|
||||||
var dominantColor = IconColorPicker.GetFilteredColor(gameIconBmp).ToPixel<Bgra32>();
|
var dominantColor = IconColorPicker.GetFilteredColor(gameIconBmp);
|
||||||
|
|
||||||
const float ColorMultiple = 0.5f;
|
const float ColorMultiple = 0.5f;
|
||||||
|
|
||||||
Color progressFgColor = Color.FromRgb(dominantColor.R, dominantColor.G, dominantColor.B);
|
Color progressFgColor = Color.FromRgb(dominantColor.Red, dominantColor.Green, dominantColor.Blue);
|
||||||
Color progressBgColor = Color.FromRgb(
|
Color progressBgColor = Color.FromRgb(
|
||||||
(byte)(dominantColor.R * ColorMultiple),
|
(byte)(dominantColor.Red * ColorMultiple),
|
||||||
(byte)(dominantColor.G * ColorMultiple),
|
(byte)(dominantColor.Green * ColorMultiple),
|
||||||
(byte)(dominantColor.B * ColorMultiple));
|
(byte)(dominantColor.Blue * ColorMultiple));
|
||||||
|
|
||||||
ProgressBarForegroundColor = new SolidColorBrush(progressFgColor);
|
ProgressBarForegroundColor = new SolidColorBrush(progressFgColor);
|
||||||
ProgressBarBackgroundColor = new SolidColorBrush(progressBgColor);
|
ProgressBarBackgroundColor = new SolidColorBrush(progressBgColor);
|
||||||
|
@ -1201,13 +1199,13 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
{
|
{
|
||||||
UserChannelPersistence.ShouldRestart = false;
|
UserChannelPersistence.ShouldRestart = false;
|
||||||
|
|
||||||
await LoadApplication(_currentEmulatedGamePath);
|
await LoadApplication(_currentApplicationData);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Otherwise, clear state.
|
// Otherwise, clear state.
|
||||||
UserChannelPersistence = new UserChannelPersistence();
|
UserChannelPersistence = new UserChannelPersistence();
|
||||||
_currentEmulatedGamePath = null;
|
_currentApplicationData = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1494,7 +1492,15 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
|
||||||
if (result.Count > 0)
|
if (result.Count > 0)
|
||||||
{
|
{
|
||||||
await LoadApplication(result[0].Path.LocalPath);
|
if (ApplicationLibrary.TryGetApplicationsFromFile(result[0].Path.LocalPath,
|
||||||
|
out List<ApplicationData> applications))
|
||||||
|
{
|
||||||
|
await LoadApplication(applications[0]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.MenuBarFileOpenFromFileError]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1508,11 +1514,17 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
|
||||||
if (result.Count > 0)
|
if (result.Count > 0)
|
||||||
{
|
{
|
||||||
await LoadApplication(result[0].Path.LocalPath);
|
ApplicationData applicationData = new()
|
||||||
|
{
|
||||||
|
Name = Path.GetFileNameWithoutExtension(result[0].Path.LocalPath),
|
||||||
|
Path = result[0].Path.LocalPath,
|
||||||
|
};
|
||||||
|
|
||||||
|
await LoadApplication(applicationData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task LoadApplication(string path, bool startFullscreen = false, string titleName = "")
|
public async Task LoadApplication(ApplicationData application, bool startFullscreen = false)
|
||||||
{
|
{
|
||||||
if (AppHost != null)
|
if (AppHost != null)
|
||||||
{
|
{
|
||||||
|
@ -1532,7 +1544,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
|
||||||
Logger.RestartTime();
|
Logger.RestartTime();
|
||||||
|
|
||||||
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(path, ConfigurationState.Instance.System.Language);
|
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(application.Path, ConfigurationState.Instance.System.Language, application.Id);
|
||||||
|
|
||||||
PrepareLoadScreen();
|
PrepareLoadScreen();
|
||||||
|
|
||||||
|
@ -1541,7 +1553,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
AppHost = new AppHost(
|
AppHost = new AppHost(
|
||||||
RendererHostControl,
|
RendererHostControl,
|
||||||
InputManager,
|
InputManager,
|
||||||
path,
|
application.Path,
|
||||||
|
application.Id,
|
||||||
VirtualFileSystem,
|
VirtualFileSystem,
|
||||||
ContentManager,
|
ContentManager,
|
||||||
AccountManager,
|
AccountManager,
|
||||||
|
@ -1559,17 +1572,17 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
|
||||||
CanUpdate = false;
|
CanUpdate = false;
|
||||||
|
|
||||||
LoadHeading = TitleName = titleName;
|
LoadHeading = application.Name;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(titleName))
|
if (string.IsNullOrWhiteSpace(application.Name))
|
||||||
{
|
{
|
||||||
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);
|
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);
|
||||||
TitleName = AppHost.Device.Processes.ActiveApplication.Name;
|
application.Name = AppHost.Device.Processes.ActiveApplication.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
SwitchToRenderer(startFullscreen);
|
SwitchToRenderer(startFullscreen);
|
||||||
|
|
||||||
_currentEmulatedGamePath = path;
|
_currentApplicationData = application;
|
||||||
|
|
||||||
Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" };
|
Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" };
|
||||||
gameThread.Start();
|
gameThread.Start();
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
using Avalonia;
|
|
||||||
using Avalonia.Collections;
|
using Avalonia.Collections;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Platform.Storage;
|
using Avalonia.Platform.Storage;
|
||||||
|
@ -6,7 +5,7 @@ using Avalonia.Threading;
|
||||||
using LibHac.Common;
|
using LibHac.Common;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Fs.Fsa;
|
using LibHac.Fs.Fsa;
|
||||||
using LibHac.FsSystem;
|
using LibHac.Ncm;
|
||||||
using LibHac.Ns;
|
using LibHac.Ns;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
|
@ -17,12 +16,17 @@ using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
|
using Ryujinx.HLE.Utilities;
|
||||||
using Ryujinx.UI.App.Common;
|
using Ryujinx.UI.App.Common;
|
||||||
|
using Ryujinx.UI.Common.Configuration;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Application = Avalonia.Application;
|
||||||
|
using ContentType = LibHac.Ncm.ContentType;
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
using SpanHelpers = LibHac.Common.SpanHelpers;
|
using SpanHelpers = LibHac.Common.SpanHelpers;
|
||||||
|
|
||||||
|
@ -33,7 +37,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
public TitleUpdateMetadata TitleUpdateWindowData;
|
public TitleUpdateMetadata TitleUpdateWindowData;
|
||||||
public readonly string TitleUpdateJsonPath;
|
public readonly string TitleUpdateJsonPath;
|
||||||
private VirtualFileSystem VirtualFileSystem { get; }
|
private VirtualFileSystem VirtualFileSystem { get; }
|
||||||
private ulong TitleId { get; }
|
private ApplicationData ApplicationData { get; }
|
||||||
|
|
||||||
private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
|
private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
|
||||||
private AvaloniaList<object> _views = new();
|
private AvaloniaList<object> _views = new();
|
||||||
|
@ -73,18 +77,18 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
|
||||||
public IStorageProvider StorageProvider;
|
public IStorageProvider StorageProvider;
|
||||||
|
|
||||||
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId)
|
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
|
||||||
{
|
{
|
||||||
VirtualFileSystem = virtualFileSystem;
|
VirtualFileSystem = virtualFileSystem;
|
||||||
|
|
||||||
TitleId = titleId;
|
ApplicationData = applicationData;
|
||||||
|
|
||||||
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
StorageProvider = desktop.MainWindow.StorageProvider;
|
StorageProvider = desktop.MainWindow.StorageProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
TitleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
|
TitleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, ApplicationData.IdString, "updates.json");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -92,7 +96,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {TitleId} at {TitleUpdateJsonPath}");
|
Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {ApplicationData.IdString} at {TitleUpdateJsonPath}");
|
||||||
|
|
||||||
TitleUpdateWindowData = new TitleUpdateMetadata
|
TitleUpdateWindowData = new TitleUpdateMetadata
|
||||||
{
|
{
|
||||||
|
@ -108,6 +112,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
|
||||||
private void LoadUpdates()
|
private void LoadUpdates()
|
||||||
{
|
{
|
||||||
|
// Try to load updates from PFS first
|
||||||
|
AddUpdate(ApplicationData.Path, true);
|
||||||
|
|
||||||
foreach (string path in TitleUpdateWindowData.Paths)
|
foreach (string path in TitleUpdateWindowData.Paths)
|
||||||
{
|
{
|
||||||
AddUpdate(path);
|
AddUpdate(path);
|
||||||
|
@ -162,38 +169,54 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddUpdate(string path)
|
private void AddUpdate(string path, bool ignoreNotFound = false)
|
||||||
{
|
{
|
||||||
if (File.Exists(path) && TitleUpdates.All(x => x.Path != path))
|
if (!File.Exists(path) || TitleUpdates.Any(x => x.Path == path))
|
||||||
{
|
{
|
||||||
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||||
|
? IntegrityCheckLevel.ErrorOnInvalid
|
||||||
|
: IntegrityCheckLevel.None;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using IFileSystem pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(path, VirtualFileSystem);
|
||||||
|
|
||||||
|
Dictionary<ulong, ContentMetaData> updates = pfs.GetContentData(ContentMetaType.Patch, VirtualFileSystem, checkLevel);
|
||||||
|
|
||||||
|
Nca patchNca = null;
|
||||||
|
Nca controlNca = null;
|
||||||
|
|
||||||
|
if (updates.TryGetValue(ApplicationData.Id, out ContentMetaData content))
|
||||||
{
|
{
|
||||||
var pfs = new PartitionFileSystem();
|
patchNca = content.GetNcaByType(VirtualFileSystem.KeySet, ContentType.Program);
|
||||||
pfs.Initialize(file.AsStorage()).ThrowIfFailure();
|
controlNca = content.GetNcaByType(VirtualFileSystem.KeySet, ContentType.Control);
|
||||||
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(VirtualFileSystem, pfs, TitleId.ToString("x16"), 0);
|
}
|
||||||
|
|
||||||
if (controlNca != null && patchNca != null)
|
if (controlNca != null && patchNca != null)
|
||||||
{
|
{
|
||||||
ApplicationControlProperty controlData = new();
|
ApplicationControlProperty controlData = new();
|
||||||
|
|
||||||
using UniqueRef<IFile> nacpFile = new();
|
using UniqueRef<IFile> nacpFile = new();
|
||||||
|
|
||||||
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
||||||
|
|
||||||
TitleUpdates.Add(new TitleUpdateModel(controlData, path));
|
TitleUpdates.Add(new TitleUpdateModel(controlData, path));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
if (!ignoreNotFound)
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]));
|
Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
}
|
||||||
{
|
catch (Exception ex)
|
||||||
Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadFileErrorMessage, ex.Message, path)));
|
{
|
||||||
}
|
Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadFileErrorMessage, ex.Message, path)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,14 +9,14 @@ using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using SixLabors.ImageSharp;
|
using SkiaSharp;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Color = Avalonia.Media.Color;
|
using Color = Avalonia.Media.Color;
|
||||||
|
using Image = SkiaSharp.SKImage;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.ViewModels
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
{
|
{
|
||||||
|
@ -130,9 +130,12 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
|
||||||
stream.Position = 0;
|
stream.Position = 0;
|
||||||
|
|
||||||
Image avatarImage = Image.LoadPixelData<Rgba32>(DecompressYaz0(stream), 256, 256);
|
Image avatarImage = Image.FromPixelCopy(new SKImageInfo(256, 256, SKColorType.Rgba8888, SKAlphaType.Premul), DecompressYaz0(stream));
|
||||||
|
|
||||||
avatarImage.SaveAsPng(streamPng);
|
using (SKData data = avatarImage.Encode(SKEncodedImageFormat.Png, 100))
|
||||||
|
{
|
||||||
|
data.SaveTo(streamPng);
|
||||||
|
}
|
||||||
|
|
||||||
_avatarStore.Add(item.FullPath, streamPng.ToArray());
|
_avatarStore.Add(item.FullPath, streamPng.ToArray());
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.Modules;
|
using Ryujinx.Modules;
|
||||||
|
using Ryujinx.UI.App.Common;
|
||||||
using Ryujinx.UI.Common;
|
using Ryujinx.UI.Common;
|
||||||
using Ryujinx.UI.Common.Configuration;
|
using Ryujinx.UI.Common.Configuration;
|
||||||
using Ryujinx.UI.Common.Helper;
|
using Ryujinx.UI.Common.Helper;
|
||||||
|
@ -134,7 +135,14 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(contentPath))
|
if (!string.IsNullOrEmpty(contentPath))
|
||||||
{
|
{
|
||||||
await ViewModel.LoadApplication(contentPath, false, "Mii Applet");
|
ApplicationData applicationData = new()
|
||||||
|
{
|
||||||
|
Name = "miiEdit",
|
||||||
|
Id = 0x0100000000001009,
|
||||||
|
Path = contentPath,
|
||||||
|
};
|
||||||
|
|
||||||
|
await ViewModel.LoadApplication(applicationData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,8 @@ using Ryujinx.Ava.UI.Controls;
|
||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using SixLabors.ImageSharp;
|
using SkiaSharp;
|
||||||
using SixLabors.ImageSharp.Formats.Png;
|
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
|
||||||
using SixLabors.ImageSharp.Processing;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Image = SixLabors.ImageSharp.Image;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Views.User
|
namespace Ryujinx.Ava.UI.Views.User
|
||||||
{
|
{
|
||||||
|
@ -70,15 +66,25 @@ namespace Ryujinx.Ava.UI.Views.User
|
||||||
{
|
{
|
||||||
if (ViewModel.SelectedImage != null)
|
if (ViewModel.SelectedImage != null)
|
||||||
{
|
{
|
||||||
MemoryStream streamJpg = new();
|
using var streamJpg = new MemoryStream();
|
||||||
Image avatarImage = Image.Load(ViewModel.SelectedImage, new PngDecoder());
|
using var bitmap = SKBitmap.Decode(ViewModel.SelectedImage);
|
||||||
|
using var newBitmap = new SKBitmap(bitmap.Width, bitmap.Height);
|
||||||
|
|
||||||
avatarImage.Mutate(x => x.BackgroundColor(new Rgba32(
|
using (var canvas = new SKCanvas(newBitmap))
|
||||||
ViewModel.BackgroundColor.R,
|
{
|
||||||
ViewModel.BackgroundColor.G,
|
canvas.Clear(new SKColor(
|
||||||
ViewModel.BackgroundColor.B,
|
ViewModel.BackgroundColor.R,
|
||||||
ViewModel.BackgroundColor.A)));
|
ViewModel.BackgroundColor.G,
|
||||||
avatarImage.SaveAsJpeg(streamJpg);
|
ViewModel.BackgroundColor.B,
|
||||||
|
ViewModel.BackgroundColor.A));
|
||||||
|
canvas.DrawBitmap(bitmap, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var image = SKImage.FromBitmap(newBitmap))
|
||||||
|
using (var dataJpeg = image.Encode(SKEncodedImageFormat.Jpeg, 100))
|
||||||
|
{
|
||||||
|
dataJpeg.SaveTo(streamJpg);
|
||||||
|
}
|
||||||
|
|
||||||
_profile.Image = streamJpg.ToArray();
|
_profile.Image = streamJpg.ToArray();
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,9 @@ using Ryujinx.Ava.UI.Controls;
|
||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using SixLabors.ImageSharp;
|
using SkiaSharp;
|
||||||
using SixLabors.ImageSharp.Processing;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Image = SixLabors.ImageSharp.Image;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Views.User
|
namespace Ryujinx.Ava.UI.Views.User
|
||||||
{
|
{
|
||||||
|
@ -102,13 +100,19 @@ namespace Ryujinx.Ava.UI.Views.User
|
||||||
|
|
||||||
private static byte[] ProcessProfileImage(byte[] buffer)
|
private static byte[] ProcessProfileImage(byte[] buffer)
|
||||||
{
|
{
|
||||||
using Image image = Image.Load(buffer);
|
using var bitmap = SKBitmap.Decode(buffer);
|
||||||
|
|
||||||
image.Mutate(x => x.Resize(256, 256));
|
var resizedBitmap = bitmap.Resize(new SKImageInfo(256, 256), SKFilterQuality.High);
|
||||||
|
|
||||||
using MemoryStream streamJpg = new();
|
using var streamJpg = new MemoryStream();
|
||||||
|
|
||||||
image.SaveAsJpeg(streamJpg);
|
if (resizedBitmap != null)
|
||||||
|
{
|
||||||
|
using var image = SKImage.FromBitmap(resizedBitmap);
|
||||||
|
using var dataJpeg = image.Encode(SKEncodedImageFormat.Jpeg, 100);
|
||||||
|
|
||||||
|
dataJpeg.SaveTo(streamJpg);
|
||||||
|
}
|
||||||
|
|
||||||
return streamJpg.ToArray();
|
return streamJpg.ToArray();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
using Avalonia.Collections;
|
using Avalonia.Collections;
|
||||||
|
using LibHac.Tools.FsSystem;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
using Ryujinx.UI.App.Common;
|
using Ryujinx.UI.App.Common;
|
||||||
|
using Ryujinx.UI.Common.Configuration;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
@ -34,9 +36,12 @@ namespace Ryujinx.Ava.UI.Windows
|
||||||
public CheatWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName, string titlePath)
|
public CheatWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName, string titlePath)
|
||||||
{
|
{
|
||||||
LoadedCheats = new AvaloniaList<CheatNode>();
|
LoadedCheats = new AvaloniaList<CheatNode>();
|
||||||
|
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||||
|
? IntegrityCheckLevel.ErrorOnInvalid
|
||||||
|
: IntegrityCheckLevel.None;
|
||||||
|
|
||||||
Heading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.CheatWindowHeading, titleName, titleId.ToUpper());
|
Heading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.CheatWindowHeading, titleName, titleId.ToUpper());
|
||||||
BuildId = ApplicationData.GetApplicationBuildId(virtualFileSystem, titlePath);
|
BuildId = ApplicationData.GetBuildId(virtualFileSystem, checkLevel, titlePath);
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
|
|
@ -97,7 +97,7 @@
|
||||||
MaxLines="2"
|
MaxLines="2"
|
||||||
TextWrapping="Wrap"
|
TextWrapping="Wrap"
|
||||||
TextTrimming="CharacterEllipsis"
|
TextTrimming="CharacterEllipsis"
|
||||||
Text="{Binding FileName}" />
|
Text="{Binding Label}" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Margin="10 0"
|
Margin="10 0"
|
||||||
|
|
|
@ -3,13 +3,12 @@ using Avalonia.Interactivity;
|
||||||
using Avalonia.Styling;
|
using Avalonia.Styling;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
|
||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.UI.App.Common;
|
||||||
using Ryujinx.UI.Common.Helper;
|
using Ryujinx.UI.Common.Helper;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Button = Avalonia.Controls.Button;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Windows
|
namespace Ryujinx.Ava.UI.Windows
|
||||||
{
|
{
|
||||||
|
@ -24,22 +23,22 @@ namespace Ryujinx.Ava.UI.Windows
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId)
|
public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
|
||||||
{
|
{
|
||||||
DataContext = ViewModel = new DownloadableContentManagerViewModel(virtualFileSystem, titleId);
|
DataContext = ViewModel = new DownloadableContentManagerViewModel(virtualFileSystem, applicationData);
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task Show(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
public static async Task Show(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
|
||||||
{
|
{
|
||||||
ContentDialog contentDialog = new()
|
ContentDialog contentDialog = new()
|
||||||
{
|
{
|
||||||
PrimaryButtonText = "",
|
PrimaryButtonText = "",
|
||||||
SecondaryButtonText = "",
|
SecondaryButtonText = "",
|
||||||
CloseButtonText = "",
|
CloseButtonText = "",
|
||||||
Content = new DownloadableContentManagerWindow(virtualFileSystem, titleId),
|
Content = new DownloadableContentManagerWindow(virtualFileSystem, applicationData),
|
||||||
Title = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowTitle], titleName, titleId.ToString("X16")),
|
Title = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowTitle], applicationData.Name, applicationData.IdString),
|
||||||
};
|
};
|
||||||
|
|
||||||
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
|
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using SixLabors.ImageSharp;
|
using SkiaSharp;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
@ -36,35 +35,34 @@ namespace Ryujinx.Ava.UI.Windows
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Color GetFilteredColor(Image<Bgra32> image)
|
public static SKColor GetFilteredColor(SKBitmap image)
|
||||||
{
|
{
|
||||||
var color = GetColor(image).ToPixel<Bgra32>();
|
var color = GetColor(image);
|
||||||
|
|
||||||
|
|
||||||
// We don't want colors that are too dark.
|
// We don't want colors that are too dark.
|
||||||
// If the color is too dark, make it brighter by reducing the range
|
// If the color is too dark, make it brighter by reducing the range
|
||||||
// and adding a constant color.
|
// and adding a constant color.
|
||||||
int luminosity = GetColorApproximateLuminosity(color.R, color.G, color.B);
|
int luminosity = GetColorApproximateLuminosity(color.Red, color.Green, color.Blue);
|
||||||
if (luminosity < CutOffLuminosity)
|
if (luminosity < CutOffLuminosity)
|
||||||
{
|
{
|
||||||
color = Color.FromRgb(
|
color = new SKColor(
|
||||||
(byte)Math.Min(CutOffLuminosity + color.R, byte.MaxValue),
|
(byte)Math.Min(CutOffLuminosity + color.Red, byte.MaxValue),
|
||||||
(byte)Math.Min(CutOffLuminosity + color.G, byte.MaxValue),
|
(byte)Math.Min(CutOffLuminosity + color.Green, byte.MaxValue),
|
||||||
(byte)Math.Min(CutOffLuminosity + color.B, byte.MaxValue));
|
(byte)Math.Min(CutOffLuminosity + color.Blue, byte.MaxValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Color GetColor(Image<Bgra32> image)
|
public static SKColor GetColor(SKBitmap image)
|
||||||
{
|
{
|
||||||
var colors = new PaletteColor[TotalColors];
|
var colors = new PaletteColor[TotalColors];
|
||||||
|
|
||||||
var dominantColorBin = new Dictionary<int, int>();
|
var dominantColorBin = new Dictionary<int, int>();
|
||||||
|
|
||||||
var buffer = GetBuffer(image);
|
var buffer = GetBuffer(image);
|
||||||
|
|
||||||
int w = image.Width;
|
int w = image.Width;
|
||||||
|
|
||||||
int w8 = w << 8;
|
int w8 = w << 8;
|
||||||
int h8 = image.Height << 8;
|
int h8 = image.Height << 8;
|
||||||
|
|
||||||
|
@ -84,9 +82,10 @@ namespace Ryujinx.Ava.UI.Windows
|
||||||
{
|
{
|
||||||
int offset = x + yOffset;
|
int offset = x + yOffset;
|
||||||
|
|
||||||
byte cb = buffer[offset].B;
|
SKColor pixel = buffer[offset];
|
||||||
byte cg = buffer[offset].G;
|
byte cr = pixel.Red;
|
||||||
byte cr = buffer[offset].R;
|
byte cg = pixel.Green;
|
||||||
|
byte cb = pixel.Blue;
|
||||||
|
|
||||||
var qck = GetQuantizedColorKey(cr, cg, cb);
|
var qck = GetQuantizedColorKey(cr, cg, cb);
|
||||||
|
|
||||||
|
@ -122,12 +121,22 @@ namespace Ryujinx.Ava.UI.Windows
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Color.FromRgb(bestCandidate.R, bestCandidate.G, bestCandidate.B);
|
return new SKColor(bestCandidate.R, bestCandidate.G, bestCandidate.B);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Bgra32[] GetBuffer(Image<Bgra32> image)
|
public static SKColor[] GetBuffer(SKBitmap image)
|
||||||
{
|
{
|
||||||
return image.DangerousTryGetSinglePixelMemory(out var data) ? data.ToArray() : Array.Empty<Bgra32>();
|
var pixels = new SKColor[image.Width * image.Height];
|
||||||
|
|
||||||
|
for (int y = 0; y < image.Height; y++)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < image.Width; x++)
|
||||||
|
{
|
||||||
|
pixels[x + y * image.Width] = image.GetPixel(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pixels;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int GetColorScore(Dictionary<int, int> dominantColorBin, int maxHitCount, PaletteColor color)
|
private static int GetColorScore(Dictionary<int, int> dominantColorBin, int maxHitCount, PaletteColor color)
|
||||||
|
|
|
@ -5,6 +5,7 @@ using Avalonia.Interactivity;
|
||||||
using Avalonia.Platform;
|
using Avalonia.Platform;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using LibHac.Tools.FsSystem;
|
||||||
using Ryujinx.Ava.Common;
|
using Ryujinx.Ava.Common;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.Input;
|
using Ryujinx.Ava.Input;
|
||||||
|
@ -24,7 +25,7 @@ using Ryujinx.UI.Common;
|
||||||
using Ryujinx.UI.Common.Configuration;
|
using Ryujinx.UI.Common.Configuration;
|
||||||
using Ryujinx.UI.Common.Helper;
|
using Ryujinx.UI.Common.Helper;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -40,6 +41,7 @@ namespace Ryujinx.Ava.UI.Windows
|
||||||
private UserChannelPersistence _userChannelPersistence;
|
private UserChannelPersistence _userChannelPersistence;
|
||||||
private static bool _deferLoad;
|
private static bool _deferLoad;
|
||||||
private static string _launchPath;
|
private static string _launchPath;
|
||||||
|
private static string _launchApplicationId;
|
||||||
private static bool _startFullscreen;
|
private static bool _startFullscreen;
|
||||||
internal readonly AvaHostUIHandler UiHandler;
|
internal readonly AvaHostUIHandler UiHandler;
|
||||||
|
|
||||||
|
@ -168,18 +170,17 @@ namespace Ryujinx.Ava.UI.Windows
|
||||||
{
|
{
|
||||||
ViewModel.SelectedIcon = args.Application.Icon;
|
ViewModel.SelectedIcon = args.Application.Icon;
|
||||||
|
|
||||||
string path = new FileInfo(args.Application.Path).FullName;
|
ViewModel.LoadApplication(args.Application).Wait();
|
||||||
|
|
||||||
ViewModel.LoadApplication(path).Wait();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void DeferLoadApplication(string launchPathArg, bool startFullscreenArg)
|
internal static void DeferLoadApplication(string launchPathArg, string launchApplicationId, bool startFullscreenArg)
|
||||||
{
|
{
|
||||||
_deferLoad = true;
|
_deferLoad = true;
|
||||||
_launchPath = launchPathArg;
|
_launchPath = launchPathArg;
|
||||||
|
_launchApplicationId = launchApplicationId;
|
||||||
_startFullscreen = startFullscreenArg;
|
_startFullscreen = startFullscreenArg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,7 +220,11 @@ namespace Ryujinx.Ava.UI.Windows
|
||||||
LibHacHorizonManager.InitializeBcatServer();
|
LibHacHorizonManager.InitializeBcatServer();
|
||||||
LibHacHorizonManager.InitializeSystemClients();
|
LibHacHorizonManager.InitializeSystemClients();
|
||||||
|
|
||||||
ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem);
|
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||||
|
? IntegrityCheckLevel.ErrorOnInvalid
|
||||||
|
: IntegrityCheckLevel.None;
|
||||||
|
|
||||||
|
ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem, checkLevel);
|
||||||
|
|
||||||
// Save data created before we supported extra data in directory save data will not work properly if
|
// Save data created before we supported extra data in directory save data will not work properly if
|
||||||
// given empty extra data. Luckily some of that extra data can be created using the data from the
|
// given empty extra data. Luckily some of that extra data can be created using the data from the
|
||||||
|
@ -314,7 +319,35 @@ namespace Ryujinx.Ava.UI.Windows
|
||||||
{
|
{
|
||||||
_deferLoad = false;
|
_deferLoad = false;
|
||||||
|
|
||||||
await ViewModel.LoadApplication(_launchPath, _startFullscreen);
|
if (ApplicationLibrary.TryGetApplicationsFromFile(_launchPath, out List<ApplicationData> applications))
|
||||||
|
{
|
||||||
|
ApplicationData applicationData;
|
||||||
|
|
||||||
|
if (_launchApplicationId != null)
|
||||||
|
{
|
||||||
|
applicationData = applications.Find(application => application.IdString == _launchApplicationId);
|
||||||
|
|
||||||
|
if (applicationData != null)
|
||||||
|
{
|
||||||
|
await ViewModel.LoadApplication(applicationData, _startFullscreen);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, $"Couldn't find requested application id '{_launchApplicationId}' in '{_launchPath}'.");
|
||||||
|
await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.ApplicationNotFound));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
applicationData = applications[0];
|
||||||
|
await ViewModel.LoadApplication(applicationData, _startFullscreen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, $"Couldn't find any application in '{_launchPath}'.");
|
||||||
|
await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.ApplicationNotFound));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -5,19 +5,18 @@ using Avalonia.Interactivity;
|
||||||
using Avalonia.Styling;
|
using Avalonia.Styling;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
|
||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.UI.App.Common;
|
||||||
using Ryujinx.UI.Common.Helper;
|
using Ryujinx.UI.Common.Helper;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Button = Avalonia.Controls.Button;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Windows
|
namespace Ryujinx.Ava.UI.Windows
|
||||||
{
|
{
|
||||||
public partial class TitleUpdateWindow : UserControl
|
public partial class TitleUpdateWindow : UserControl
|
||||||
{
|
{
|
||||||
public TitleUpdateViewModel ViewModel;
|
public readonly TitleUpdateViewModel ViewModel;
|
||||||
|
|
||||||
public TitleUpdateWindow()
|
public TitleUpdateWindow()
|
||||||
{
|
{
|
||||||
|
@ -26,22 +25,22 @@ namespace Ryujinx.Ava.UI.Windows
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ulong titleId)
|
public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
|
||||||
{
|
{
|
||||||
DataContext = ViewModel = new TitleUpdateViewModel(virtualFileSystem, titleId);
|
DataContext = ViewModel = new TitleUpdateViewModel(virtualFileSystem, applicationData);
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task Show(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
public static async Task Show(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
|
||||||
{
|
{
|
||||||
ContentDialog contentDialog = new()
|
ContentDialog contentDialog = new()
|
||||||
{
|
{
|
||||||
PrimaryButtonText = "",
|
PrimaryButtonText = "",
|
||||||
SecondaryButtonText = "",
|
SecondaryButtonText = "",
|
||||||
CloseButtonText = "",
|
CloseButtonText = "",
|
||||||
Content = new TitleUpdateWindow(virtualFileSystem, titleId),
|
Content = new TitleUpdateWindow(virtualFileSystem, applicationData),
|
||||||
Title = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.GameUpdateWindowHeading, titleName, titleId.ToString("X16")),
|
Title = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.GameUpdateWindowHeading, applicationData.Name, applicationData.IdString),
|
||||||
};
|
};
|
||||||
|
|
||||||
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
|
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
|
||||||
|
|
Loading…
Reference in a new issue