Compare commits

...

23 Commits

Author SHA1 Message Date
ccd330ba0f Vulkan: Add missing barriers for texture to buffer copy (#7092)
This barrier has always been missing, but it only became apparent when #7012 merged.

I also added some barriers in case the target buffer used here is used by other commands, though right now it isn't.

Fixes a regression where water would turn white on AMD GPUs with the proprietary driver. May fix other issues on this driver.
2024-07-25 16:34:30 -03:00
95d252b7b8 Update kernel GetInfo SVC for firmware 18.0.0 (#7075)
* Implement kernel GetInfo AliasRegionExtraSize

* Implement IsSvcPermitted

* Remove warning supressions that are no longer needed

* Remove useless cast
2024-07-22 12:46:04 -03:00
add681144b Fix checking for the wrong metadata files for applications launched with a different program index (#7055)
* Fix checking for the wrong update metadata file

* Apply the same fix for dlc.json

* Use the base application ids for updates and DLCs in the GUI too

This shouldn't actually change anything, since the program index part of the application id
should always be 0 for all applications currently seen by the GUI.

This was just done for completeness.
2024-07-21 14:42:23 -03:00
c6dc00815a Make sure TryGetApplicationsFromFile() doesn't throw exceptions anymore (#7046)
* Add docstrings for exceptions to methods near TryGetApplicationsFromFile()

* Make sure TryGetApplicationsFromFile() doesn't throw exceptions anymore

* Add missing filePath to ApplicationData when loading applications from ExeFS

* Fix typo

Co-authored-by: riperiperi <rhy3756547@hotmail.com>

---------

Co-authored-by: riperiperi <rhy3756547@hotmail.com>
2024-07-20 16:35:43 -03:00
99f04ac1a6 Fix Skia saving screenshot with transparent background and incorrect origin (#7073)
* Fix Skia saving screenshot with transparent background and incorrect origin

* Remove code that is no longer necessary
2024-07-20 16:27:40 -03:00
ce09450743 Unlink server sessions from multi-wait when service stops processing requests (#7072) 2024-07-20 16:17:40 -03:00
2cb80f37d4 Ava UI: Auto select newly added updates & DLC (#7026)
* Fix DLC not being selected

* FIx conflicts

* Apply suggestions from code review

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

---------

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
2024-07-19 19:00:15 +02:00
827069e784 Add missing Buffer attribute on NGC Check method (#7051) 2024-07-18 15:11:00 -03:00
1a919e99b2 Vulkan: Defer guest barriers, and improve image barrier timings (#7012)
* More guarantees for buffer correct placement, defer guest requested buffers

* Split RP on indirect barrier rn

* Better handling for feedback loops.

* Qualcomm barriers suck too

* Fix condition

* Remove unused field

* Allow render pass barriers on turnip for now
2024-07-17 20:21:32 -03:00
f77bebac80 Include content data foreach-loop in try-catch (#7036) 2024-07-17 19:02:20 -03:00
6fbf279fac Add support for multi game XCIs (second try) (#6515)
* Add default values to ApplicationData directly

* Refactor application loading

It should now be possible to load multi game XCIs.
Included updates won't be detected for now.
Opening a game from the command line currently only opens the first one.

* Only include program NCAs where at least one tuple item is not null

* Get application data by title id and add programIndex check back

* Refactor application loading again and remove duplicate code

* Actually use patch ncas for updates

* Fix number of applications found with multi game xcis

* Don't load bundled updates from multi game xcis

* Change ApplicationData.TitleId type to ulong & Add TitleIdString property

* Use cnmt files and ContentCollection to load programs

* Ava: Add updates and DLCs from gamecarts

* Get the cnmt file from its NCA

* Ava: Identify bundled updates in updater window

* Fix the (hopefully) last few bugs

* Add idOffset parameter to GetNcaByType

* Handle missing file for dlc.json

* Ava: Shorten error message for invalid files

* Gtk: Add additional string for bundled updates in TitleUpdateWindow

* Hopefully fix DLC issues

* Apply formatting

* Finally fix DLC issues

* Adjust property names and fileSize field

* Read the correct update file

* Fix wrong casing for application id strings

* Rename TitleId to ApplicationId

* Address review comments

* Apply suggestions from code review

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Gracefully fail when loading pfs for update and dlc window

* Fix applications with multiple programs

* Fix DLCWindow crash on GTK

* Fix some GUI issues

* Remove IsXci again

* Don't add duplicates to update/dlc windows

* Avoid double lookup

* Preserve DLC enabled state for bundled DLCs

* Fix DLCWindow not opening using GTK

* Fix missing information when loading applications from file

* Address review feedback

Rename ContentCollection to ContentMetaData
Fix casing issues in log messages
Use null as the default value for updatePath

* Fix re-adding bundled DLCs every time

* Fix bundled DLCs disappearing

* Abstract common code to open application pfs

* Remove unused imports

* Fix file exists check when loading DLCs

* Load bundled DLCs only using dlc.json

* Load AoC items correctly

* Add all DLCs from a PFS

* Add argument to launch a specific application id

* Use application-id argument for shortcuts if necessary

* Return the application id from the control NCA if possible

* GetApplicationInformation: Don't overwrite application ids

Move SaveDataOwnerId check to the top, since it seems to be more reliable.

* Get application ids from CNMT again

This commit reverts some parts of 61615b8f0d6f90ae86778958ddc38eaf6dc280ab.
Since the issue wasn't actually related to the application id in CMNTs, we can remove the wrong assumptions.

* Revert erroneous axaml change from adca8900

* Rename title to application

* Wrap nsp/pfs0 case with curly braces

* Check if _applicationData.ControlHolder.ByteSpan is zeros only once

* Catch exceptions while loading applications from nsps

---------

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2024-07-16 18:17:32 -03:00
344f4f52c1 Remove CommandBufferScoped Dependencies (#6958) 2024-07-16 17:01:06 -03:00
eb212aa91b misc: Re-order and manually update DriverID to name. (#7027)
* Re-order and update DriverID -> Name.

* Fix whitespace
2024-07-15 19:27:59 -03:00
a6dbb2ad2b replace ByteMemoryPool usage in Ryujinx.HLE (#6953) 2024-07-15 19:21:53 -03:00
595e514f18 Use SkiaSharp for Avalonia in place of ImageSharp (#6269)
* Rebased

Transformation all at once

Use SkiaSharp instead of ImageSharp

* Apply suggestions from code review

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

* Change back unintentionally changed comment

---------

Co-authored-by: Ac_K <Acoustik666@gmail.com>
Co-authored-by: Emmanuel Hansen <emmausssss@gmail.com>
2024-07-14 08:16:14 +00:00
07435ad844 Use draw clear on Adreno, instead of vkCmdClearAttachments (#7013)
* Use draw clear on Adreno, instead of vkCmdClearAttachments

* Fix GTX TITAN detection
2024-07-10 17:52:45 -03:00
1668ba913f Force dynamic state update after rasterizer discard disable (#7007) 2024-07-09 23:31:01 -03:00
a830eb666b Disallow concurrent fence waits on Adreno (#7001)
* Disallow concurrent fence waits on Adreno

* Ensure locks are released if exceptions are thrown
2024-07-07 19:33:28 -03:00
cfc75d7e78 Disable descriptor set template updates for buffer textures on Adreno (#7002)
* Do not use template updates for buffer textures and buffer images

* No need to do it for images

* Simply buffer texture existence check

* Pipeline is now unused on DescriptorSetUpdater
2024-07-07 19:19:55 -03:00
c525d7d9a9 Force Vulkan swapchain re-creation when window size changes (#7003) 2024-07-07 19:02:11 -03:00
1a0a351a15 Resolve some Vulkan validation errors (#6915)
* Fix some validation errors

* Whitespace correction

* Resolve some runtime validation errors.

* Whitespace

* Properly fix usage realted validation error by setting Extended Usage image creation flag.

* Only if supported

* Remove checking extension for features that are core functionality of Vulkan 1.2
2024-06-26 09:21:44 -03:00
bd3335c143 Make sure the string is long enough before performing basic trim (#6982) 2024-06-26 11:27:23 +02:00
a94445b23e nuget: bump Microsoft.IdentityModel.JsonWebTokens from 7.6.0 to 7.6.2 (#6965)
Bumps [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 7.6.0 to 7.6.2.
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/7.6.2/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/7.6.0...7.6.2)

---
updated-dependencies:
- dependency-name: Microsoft.IdentityModel.JsonWebTokens
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
2024-06-26 10:45:51 +02:00
95 changed files with 2345 additions and 1530 deletions

View File

@ -20,7 +20,7 @@
<PackageVersion Include="LibHac" Version="0.19.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.6.0" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.6.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />

View File

@ -74,13 +74,15 @@ namespace Ryujinx.Graphics.GAL
public int ArrayLength { get; }
public ResourceType Type { 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;
ArrayLength = arrayLength;
Type = type;
Stages = stages;
Write = write;
}
public override int GetHashCode()

View File

@ -78,9 +78,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
ResourceStages stages = vertexAsCompute ? ResourceStages.Compute | ResourceStages.Vertex : VtgStages;
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.BufferImage, imageSetIndex, 0, rrc.ReservedImages);
PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, imageSetIndex, 0, rrc.ReservedImages, true);
}
/// <summary>
@ -91,10 +91,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="setIndex">Resource set index where the resources are used</param>
/// <param name="start">First binding number</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);
AddUsage(stages, type, setIndex, start, count);
AddUsage(stages, type, setIndex, start, count, write);
}
/// <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="binding">Binding number where the resource will be bound</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++)
{
_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,
1,
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);
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)));
}
}

View File

@ -29,7 +29,14 @@ namespace Ryujinx.Graphics.Vulkan
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);
}
}

View File

@ -1,6 +1,7 @@
using Silk.NET.Vulkan;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Ryujinx.Graphics.Vulkan
{
@ -8,22 +9,64 @@ namespace Ryujinx.Graphics.Vulkan
{
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 NativeArray<MemoryBarrier> _memoryBarrierBatch = new(MaxBarriersPerCall);
private readonly NativeArray<BufferMemoryBarrier> _bufferBarrierBatch = new(MaxBarriersPerCall);
private readonly NativeArray<ImageMemoryBarrier> _imageBarrierBatch = new(MaxBarriersPerCall);
private readonly List<BarrierWithStageFlags<MemoryBarrier>> _memoryBarriers = new();
private readonly List<BarrierWithStageFlags<BufferMemoryBarrier>> _bufferBarriers = new();
private readonly List<BarrierWithStageFlags<ImageMemoryBarrier>> _imageBarriers = new();
private readonly List<BarrierWithStageFlags<MemoryBarrier, int>> _memoryBarriers = new();
private readonly List<BarrierWithStageFlags<BufferMemoryBarrier, int>> _bufferBarriers = new();
private readonly List<BarrierWithStageFlags<ImageMemoryBarrier, TextureStorage>> _imageBarriers = new();
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)
{
_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>
{
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 T Barrier;
public readonly T2 Resource;
public BarrierWithStageFlags(StageFlags flags, T barrier)
{
Flags = flags;
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);
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++;
}
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)
{
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)
{
int memoryCount = 0;
@ -86,20 +212,20 @@ namespace Ryujinx.Graphics.Vulkan
bool hasBarrier = false;
StageFlags flags = default;
static void AddBarriers<T>(
static void AddBarriers<T, T2>(
Span<T> target,
ref int queuedBarrierCount,
ref bool hasBarrier,
ref StageFlags flags,
ref int count,
List<BarrierWithStageFlags<T>> list) where T : unmanaged
List<BarrierWithStageFlags<T, T2>> list) where T : unmanaged
{
int firstMatch = -1;
int end = list.Count;
for (int i = 0; i < list.Count; i++)
{
BarrierWithStageFlags<T> barrier = list[i];
BarrierWithStageFlags<T, T2> barrier = list[i];
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,
// 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 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,
// notify the resource that it should add a barrier as soon as a render pass ends to avoid this in future.
// Generally, we want to avoid this from happening in the future, so flag the texture to immediately
// 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)
{
_memoryBarriers.Add(new BarrierWithStageFlags<MemoryBarrier>(
_memoryBarriers.Add(new BarrierWithStageFlags<MemoryBarrier, int>(
barrier.Flags,
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(_bufferBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref bufferCount, _bufferBarriers);
AddBarriers(_imageBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref imageCount, _imageBarriers);
@ -198,14 +379,14 @@ namespace Ryujinx.Graphics.Vulkan
{
PipelineStageFlags srcStageFlags = flags.Source;
if (insideRenderPass)
if (inRenderPass)
{
// Inside a render pass, barrier stages can only be from rasterization.
srcStageFlags &= ~PipelineStageFlags.ComputeShaderBit;
}
_gd.Api.CmdPipelineBarrier(
cb,
cbs.CommandBuffer,
srcStageFlags,
flags.Dest,
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()
{
_memoryBarrierBatch.Dispose();

View File

@ -103,12 +103,19 @@ namespace Ryujinx.Graphics.Vulkan
usage |= BufferUsageFlags.IndirectBufferBit;
}
var externalMemoryBuffer = new ExternalMemoryBufferCreateInfo
{
SType = StructureType.ExternalMemoryBufferCreateInfo,
HandleTypes = ExternalMemoryHandleTypeFlags.HostAllocationBitExt,
};
var bufferCreateInfo = new BufferCreateInfo
{
SType = StructureType.BufferCreateInfo,
Size = (ulong)size,
Usage = usage,
SharingMode = SharingMode.Exclusive,
PNext = &externalMemoryBuffer,
};
gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError();

View File

@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Vulkan
private readonly Device _device;
private readonly Queue _queue;
private readonly object _queueLock;
private readonly bool _concurrentFenceWaitUnsupported;
private readonly CommandPool _pool;
private readonly Thread _owner;
@ -30,11 +31,9 @@ namespace Ryujinx.Graphics.Vulkan
public int SubmissionCount;
public CommandBuffer CommandBuffer;
public FenceHolder Fence;
public SemaphoreHolder Semaphore;
public List<IAuto> Dependants;
public List<MultiFenceHolder> Waitables;
public HashSet<SemaphoreHolder> Dependencies;
public void Initialize(Vk api, Device device, CommandPool pool)
{
@ -50,7 +49,6 @@ namespace Ryujinx.Graphics.Vulkan
Dependants = new List<IAuto>();
Waitables = new List<MultiFenceHolder>();
Dependencies = new HashSet<SemaphoreHolder>();
}
}
@ -61,12 +59,20 @@ namespace Ryujinx.Graphics.Vulkan
private int _queuedCount;
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;
_device = device;
_queue = queue;
_queueLock = queueLock;
_concurrentFenceWaitUnsupported = concurrentFenceWaitUnsupported;
_owner = Thread.CurrentThread;
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)
{
ref var entry = ref _commandBuffers[cbIndex];
@ -345,19 +343,13 @@ namespace Ryujinx.Graphics.Vulkan
waitable.RemoveBufferUses(cbIndex);
}
foreach (var dependency in entry.Dependencies)
{
dependency.Put();
}
entry.Dependants.Clear();
entry.Waitables.Clear();
entry.Dependencies.Clear();
entry.Fence?.Dispose();
if (refreshFence)
{
entry.Fence = new FenceHolder(_api, _device);
entry.Fence = new FenceHolder(_api, _device, _concurrentFenceWaitUnsupported);
}
else
{

View File

@ -26,11 +26,6 @@ namespace Ryujinx.Graphics.Vulkan
_pool.AddWaitable(CommandBufferIndex, waitable);
}
public void AddDependency(CommandBufferScoped dependencyCbs)
{
_pool.AddDependency(CommandBufferIndex, dependencyCbs);
}
public FenceHolder GetFence()
{
return _pool.GetFence(CommandBufferIndex);

View File

@ -73,7 +73,6 @@ namespace Ryujinx.Graphics.Vulkan
private readonly VulkanRenderer _gd;
private readonly Device _device;
private readonly PipelineBase _pipeline;
private ShaderCollection _program;
private readonly BufferRef[] _uniformBufferRefs;
@ -125,11 +124,10 @@ namespace Ryujinx.Graphics.Vulkan
private readonly TextureView _dummyTexture;
private readonly SamplerHolder _dummySampler;
public DescriptorSetUpdater(VulkanRenderer gd, Device device, PipelineBase pipeline)
public DescriptorSetUpdater(VulkanRenderer gd, Device device)
{
_gd = gd;
_device = device;
_pipeline = pipeline;
// 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.
@ -684,7 +682,14 @@ namespace Ryujinx.Graphics.Vulkan
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))
@ -918,31 +923,84 @@ namespace Ryujinx.Graphics.Vulkan
_gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan<uint>.Empty);
}
private unsafe void UpdateBuffers(
CommandBufferScoped cbs,
PipelineBindPoint pbp,
int baseBinding,
ReadOnlySpan<DescriptorBufferInfo> bufferInfo,
DescriptorType type)
private void UpdateAndBindTexturesWithoutTemplate(CommandBufferScoped cbs, ShaderCollection program, PipelineBindPoint pbp)
{
if (bufferInfo.Length == 0)
int setIndex = PipelineBase.TextureSetIndex;
var bindingSegments = program.BindingSegments[setIndex];
if (bindingSegments.Length == 0)
{
return;
}
fixed (DescriptorBufferInfo* pBufferInfo = bufferInfo)
if (_updateDescriptorCacheCbIndex)
{
var writeDescriptorSet = new WriteDescriptorSet
{
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);
_updateDescriptorCacheCbIndex = false;
program.UpdateDescriptorCacheCommandBufferIndex(cbs.CommandBufferIndex);
}
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)]

View File

@ -59,14 +59,14 @@ namespace Ryujinx.Graphics.Vulkan.Effects
var scalingResourceLayout = new ResourceLayoutBuilder()
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
.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()
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 3)
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 4)
.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));

View File

@ -42,7 +42,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
var resourceLayout = new ResourceLayoutBuilder()
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
.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));

View File

@ -81,20 +81,20 @@ namespace Ryujinx.Graphics.Vulkan.Effects
var edgeResourceLayout = new ResourceLayoutBuilder()
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
.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()
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3)
.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()
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
.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));

View File

@ -10,12 +10,15 @@ namespace Ryujinx.Graphics.Vulkan
private readonly Device _device;
private Fence _fence;
private int _referenceCount;
private int _lock;
private readonly bool _concurrentWaitUnsupported;
private bool _disposed;
public unsafe FenceHolder(Vk api, Device device)
public unsafe FenceHolder(Vk api, Device device, bool concurrentWaitUnsupported)
{
_api = api;
_device = device;
_concurrentWaitUnsupported = concurrentWaitUnsupported;
var fenceCreateInfo = new FenceCreateInfo
{
@ -47,6 +50,11 @@ namespace Ryujinx.Graphics.Vulkan
}
while (Interlocked.CompareExchange(ref _referenceCount, lastValue + 1, lastValue) != lastValue);
if (_concurrentWaitUnsupported)
{
AcquireLock();
}
fence = _fence;
return true;
}
@ -57,6 +65,16 @@ namespace Ryujinx.Graphics.Vulkan
return _fence;
}
public void PutLock()
{
Put();
if (_concurrentWaitUnsupported)
{
ReleaseLock();
}
}
public void Put()
{
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()
{
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()
{
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()

View File

@ -286,10 +286,23 @@ namespace Ryujinx.Graphics.Vulkan
_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,
Device device,
CommandBufferScoped cbs)

View File

@ -115,7 +115,7 @@ namespace Ryujinx.Graphics.Vulkan
var strideChangeResourceLayout = new ResourceLayoutBuilder()
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
.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[]
{
@ -125,7 +125,7 @@ namespace Ryujinx.Graphics.Vulkan
var colorCopyResourceLayout = new ResourceLayoutBuilder()
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 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[]
{
@ -155,7 +155,7 @@ namespace Ryujinx.Graphics.Vulkan
var convertD32S8ToD24S8ResourceLayout = new ResourceLayoutBuilder()
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
.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[]
{
@ -165,7 +165,7 @@ namespace Ryujinx.Graphics.Vulkan
var convertIndexBufferResourceLayout = new ResourceLayoutBuilder()
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
.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[]
{
@ -175,7 +175,7 @@ namespace Ryujinx.Graphics.Vulkan
var convertIndirectDataResourceLayout = new ResourceLayoutBuilder()
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
.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();
_programConvertIndirectData = gd.CreateProgramWithMinimalLayout(new[]

View File

@ -196,18 +196,23 @@ namespace Ryujinx.Graphics.Vulkan
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].Put();
for (int i = 0; i < fenceCount; i++)
{
fenceHolders[i].PutLock();
}
}
return signaled;

View File

@ -55,6 +55,7 @@ namespace Ryujinx.Graphics.Vulkan
protected FramebufferParams FramebufferParams;
private Auto<DisposableFramebuffer> _framebuffer;
private RenderPassHolder _rpHolder;
private Auto<DisposableRenderPass> _renderPass;
private RenderPassHolder _nullRenderPass;
private int _writtenAttachmentCount;
@ -85,8 +86,6 @@ namespace Ryujinx.Graphics.Vulkan
private bool _tfActive;
private readonly PipelineColorBlendAttachmentState[] _storedBlend;
private ulong _drawCountSinceBarrier;
public ulong DrawCount { get; private set; }
public bool RenderPassActive { get; private set; }
@ -105,7 +104,7 @@ namespace Ryujinx.Graphics.Vulkan
gd.Api.CreatePipelineCache(device, pipelineCacheCreateInfo, null, out PipelineCache).ThrowOnError();
_descriptorSetUpdater = new DescriptorSetUpdater(gd, device, this);
_descriptorSetUpdater = new DescriptorSetUpdater(gd, device);
_vertexBufferUpdater = new VertexBufferUpdater(gd);
_transformFeedbackBuffers = new BufferState[Constants.MaxTransformFeedbackBuffers];
@ -135,48 +134,7 @@ namespace Ryujinx.Graphics.Vulkan
public unsafe void Barrier()
{
if (_drawCountSinceBarrier != DrawCount)
{
_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);
Gd.Barriers.QueueMemoryBarrier();
}
public void ComputeBarrier()
@ -203,6 +161,7 @@ namespace Ryujinx.Graphics.Vulkan
public void BeginTransformFeedback(PrimitiveTopology topology)
{
Gd.Barriers.EnableTfbBarriers(true);
_tfEnabled = true;
}
@ -249,7 +208,7 @@ namespace Ryujinx.Graphics.Vulkan
CreateRenderPass();
}
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
Gd.Barriers.Flush(Cbs, RenderPassActive, _rpHolder, EndRenderPassDelegate);
BeginRenderPass();
@ -287,7 +246,7 @@ namespace Ryujinx.Graphics.Vulkan
CreateRenderPass();
}
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
Gd.Barriers.Flush(Cbs, RenderPassActive, _rpHolder, EndRenderPassDelegate);
BeginRenderPass();
@ -299,24 +258,7 @@ namespace Ryujinx.Graphics.Vulkan
public unsafe void CommandBufferBarrier()
{
MemoryBarrier memoryBarrier = new()
{
SType = StructureType.MemoryBarrier,
SrcAccessMask = BufferHolder.DefaultAccessFlags,
DstAccessMask = AccessFlags.IndirectCommandReadBit,
};
Gd.Api.CmdPipelineBarrier(
CommandBuffer,
PipelineStageFlags.AllCommandsBit,
PipelineStageFlags.DrawIndirectBit,
0,
1,
memoryBarrier,
0,
null,
0,
null);
Gd.Barriers.QueueCommandBufferBarrier();
}
public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size)
@ -722,6 +664,7 @@ namespace Ryujinx.Graphics.Vulkan
public void EndTransformFeedback()
{
Gd.Barriers.EnableTfbBarriers(false);
PauseTransformFeedbackInternal();
_tfEnabled = false;
}
@ -1020,6 +963,13 @@ namespace Ryujinx.Graphics.Vulkan
{
_newState.RasterizerDiscardEnable = discard;
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)
@ -1401,24 +1351,7 @@ namespace Ryujinx.Graphics.Vulkan
public unsafe void TextureBarrier()
{
MemoryBarrier memoryBarrier = new()
{
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);
Gd.Barriers.QueueTextureBarrier();
}
public void TextureBarrierTiled()
@ -1525,12 +1458,15 @@ namespace Ryujinx.Graphics.Vulkan
// Use the null framebuffer.
_nullRenderPass ??= new RenderPassHolder(Gd, Device, new RenderPassCacheKey(), FramebufferParams);
_rpHolder = _nullRenderPass;
_renderPass = _nullRenderPass.GetRenderPass();
_framebuffer = _nullRenderPass.GetFramebuffer(Gd, Cbs, FramebufferParams);
}
else
{
(_renderPass, _framebuffer) = FramebufferParams.GetPassAndFramebuffer(Gd, Device, Cbs);
(_rpHolder, _framebuffer) = FramebufferParams.GetPassAndFramebuffer(Gd, Device, Cbs);
_renderPass = _rpHolder.GetRenderPass();
}
}
@ -1557,7 +1493,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);
}
@ -1622,7 +1558,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);
@ -1701,6 +1637,8 @@ namespace Ryujinx.Graphics.Vulkan
{
if (RenderPassActive)
{
FramebufferParams.AddStoreOpUsage();
PauseTransformFeedbackInternal();
Gd.Api.CmdEndRenderPass(CommandBuffer);
SignalRenderPassEnd();

View File

@ -9,13 +9,6 @@ namespace Ryujinx.Graphics.Vulkan
{
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)
{
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)
{
@ -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(
0,
0,
PipelineStageFlags.AllGraphicsBit,
PipelineStageFlags.AllGraphicsBit,
SubpassAccessMask,
SubpassAccessMask,
stages,
stages,
access,
access,
0);
}
public unsafe static SubpassDependency2 CreateSubpassDependency2()
public unsafe static SubpassDependency2 CreateSubpassDependency2(VulkanRenderer gd)
{
var (access, stages) = BarrierBatch.GetSubpassAccessSuperset(gd);
return new SubpassDependency2(
StructureType.SubpassDependency2,
null,
0,
0,
PipelineStageFlags.AllGraphicsBit,
PipelineStageFlags.AllGraphicsBit,
SubpassAccessMask,
SubpassAccessMask,
stages,
stages,
access,
access,
0);
}

View File

@ -47,10 +47,11 @@ namespace Ryujinx.Graphics.Vulkan
return;
}
if (componentMask != 0xf)
if (componentMask != 0xf || Gd.IsQualcommProprietary)
{
// We can't use CmdClearAttachments if not writing all components,
// 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);
if (dstTexture == null)
{
@ -87,10 +88,11 @@ namespace Ryujinx.Graphics.Vulkan
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,
// 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();
if (dstTexture == null)
{
@ -255,7 +257,7 @@ namespace Ryujinx.Graphics.Vulkan
PreloadCbs = null;
}
Gd.Barriers.Flush(Cbs.CommandBuffer, false, null);
Gd.Barriers.Flush(Cbs, false, null, null);
CommandBuffer = (Cbs = Gd.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer;
Gd.RegisterFlush();

View File

@ -1,5 +1,7 @@
using Silk.NET.Vulkan;
using System;
using System.Collections.Generic;
using System.Linq;
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 Auto<DisposableRenderPass> _renderPass;
private readonly HashTableSlim<FramebufferCacheKey, Auto<DisposableFramebuffer>> _framebuffers;
private readonly RenderPassCacheKey _key;
private readonly List<ForcedFence> _forcedFences;
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)
{
@ -138,6 +143,8 @@ namespace Ryujinx.Graphics.Vulkan
_textures = textures;
_key = key;
_forcedFences = new List<ForcedFence>();
}
public Auto<DisposableFramebuffer> GetFramebuffer(VulkanRenderer gd, CommandBufferScoped cbs, FramebufferParams fb)
@ -159,6 +166,37 @@ namespace Ryujinx.Graphics.Vulkan
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()
{
// Dispose all framebuffers.

View File

@ -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
{
@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Vulkan
};
_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;
}

View File

@ -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;
}
}
}
}

View File

@ -23,8 +23,13 @@ namespace Ryujinx.Graphics.Vulkan
public bool IsCompute { get; }
public bool HasTessellationControlShader => (Stages & (1u << 3)) != 0;
public bool UpdateTexturesWithoutTemplate { get; }
public uint Stages { get; }
public PipelineStageFlags IncoherentBufferWriteStages { get; }
public PipelineStageFlags IncoherentTextureWriteStages { get; }
public ResourceBindingSegment[][] ClearSegments { get; }
public ResourceBindingSegment[][] BindingSegments { get; }
public DescriptorSetTemplate[] Templates { get; }
@ -127,8 +132,12 @@ namespace Ryujinx.Graphics.Vulkan
Stages = stages;
ClearSegments = BuildClearSegments(sets);
BindingSegments = BuildBindingSegments(resourceLayout.SetUsages);
BindingSegments = BuildBindingSegments(resourceLayout.SetUsages, out bool usesBufferTextures);
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;
_firstBackgroundUse = false;
@ -280,8 +289,10 @@ namespace Ryujinx.Graphics.Vulkan
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][];
for (int setIndex = 0; setIndex < setUsages.Count; setIndex++)
@ -295,6 +306,11 @@ namespace Ryujinx.Graphics.Vulkan
{
ResourceUsage usage = setUsages[setIndex].Usages[index];
if (usage.Type == ResourceType.BufferTexture)
{
usesBufferTextures = true;
}
if (currentUsage.Binding + currentCount != usage.Binding ||
currentUsage.Type != usage.Type ||
currentUsage.Stages != usage.Stages ||
@ -365,6 +381,73 @@ namespace Ryujinx.Graphics.Vulkan
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()
{
await Task.WhenAll(_shaders.Select(shader => shader.CompileTask));

View File

@ -407,7 +407,7 @@ namespace Ryujinx.Graphics.Vulkan
ImageLayout.General,
ImageLayout.General);
var subpassDependency = PipelineConverter.CreateSubpassDependency2();
var subpassDependency = PipelineConverter.CreateSubpassDependency2(gd);
fixed (AttachmentDescription2* pAttachmentDescs = attachmentDescs)
{

View File

@ -38,6 +38,8 @@ namespace Ryujinx.Graphics.Vulkan
public TextureCreateInfo Info => _info;
public bool Disposed { get; private set; }
private readonly Image _image;
private readonly Auto<DisposableImage> _imageAuto;
private readonly Auto<MemoryAllocation> _allocationAuto;
@ -80,7 +82,7 @@ namespace Ryujinx.Graphics.Vulkan
var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities.SupportsShaderStorageImageMultisample);
var flags = ImageCreateFlags.CreateMutableFormatBit;
var flags = ImageCreateFlags.CreateMutableFormatBit | ImageCreateFlags.CreateExtendedUsageBit;
// This flag causes mipmapped texture arrays to break on AMD GCN, so for that copy dependencies are forced for aliasing as cube.
bool isCube = info.Target == Target.Cubemap || info.Target == Target.CubemapArray;
@ -433,6 +435,17 @@ namespace Ryujinx.Graphics.Vulkan
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)
{
PipelineStageFlags srcStageFlags = _lastReadStage | _lastModificationStage;
@ -458,7 +471,7 @@ namespace Ryujinx.Graphics.Vulkan
_info.GetLayers(),
_info.Levels);
_gd.Barriers.QueueBarrier(barrier, srcStageFlags, dstStageFlags);
_gd.Barriers.QueueBarrier(barrier, this, srcStageFlags, dstStageFlags);
_lastReadStage = PipelineStageFlags.None;
_lastReadAccess = AccessFlags.None;
@ -491,7 +504,7 @@ namespace Ryujinx.Graphics.Vulkan
_info.GetLayers(),
_info.Levels);
_gd.Barriers.QueueBarrier(barrier, _lastModificationStage, dstStageFlags);
_gd.Barriers.QueueBarrier(barrier, this, _lastModificationStage, dstStageFlags);
_lastModificationAccess = AccessFlags.None;
}
@ -514,6 +527,8 @@ namespace Ryujinx.Graphics.Vulkan
public void Dispose()
{
Disposed = true;
if (_aliasedStorages != null)
{
foreach (var storage in _aliasedStorages.Values)

View File

@ -100,7 +100,7 @@ namespace Ryujinx.Graphics.Vulkan
unsafe Auto<DisposableImageView> CreateImageView(ComponentMapping cm, ImageSubresourceRange sr, ImageViewType viewType, ImageUsageFlags usageFlags)
{
var usage = new ImageViewUsageCreateInfo
var imageViewUsage = new ImageViewUsageCreateInfo
{
SType = StructureType.ImageViewUsageCreateInfo,
Usage = usageFlags,
@ -114,7 +114,7 @@ namespace Ryujinx.Graphics.Vulkan
Format = format,
Components = cm,
SubresourceRange = sr,
PNext = &usage,
PNext = &imageViewUsage,
};
gd.Api.CreateImageView(device, imageCreateInfo, null, out var imageView).ThrowOnError();
@ -123,7 +123,7 @@ namespace Ryujinx.Graphics.Vulkan
ImageUsageFlags shaderUsage = ImageUsageFlags.SampledBit;
if (info.Format.IsImageCompatible())
if (info.Format.IsImageCompatible() && (_gd.Capabilities.SupportsShaderStorageImageMultisample || !info.Target.IsMultisample()))
{
shaderUsage |= ImageUsageFlags.StorageBit;
}
@ -154,7 +154,7 @@ namespace Ryujinx.Graphics.Vulkan
}
else
{
subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, (uint)info.Depth);
subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, 1, (uint)firstLayer, (uint)info.Depth);
_imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.Type2DArray, usage);
}
@ -667,8 +667,36 @@ namespace Ryujinx.Graphics.Vulkan
if (PrepareOutputBuffer(cbs, hostSize, buffer, out VkBuffer copyToBuffer, out BufferHolder tempCopyHolder))
{
// No barrier necessary, as this is a temporary copy buffer.
offset = 0;
}
else
{
BufferHolder.InsertBufferBarrier(
_gd,
cbs.CommandBuffer,
copyToBuffer,
BufferHolder.DefaultAccessFlags,
AccessFlags.TransferWriteBit,
PipelineStageFlags.AllCommandsBit,
PipelineStageFlags.TransferBit,
offset,
outSize);
}
InsertImageBarrier(
_gd.Api,
cbs.CommandBuffer,
image,
TextureStorage.DefaultAccessMask,
AccessFlags.TransferReadBit,
PipelineStageFlags.AllCommandsBit,
PipelineStageFlags.TransferBit,
Info.Format.ConvertAspectFlags(),
FirstLayer + layer,
FirstLevel + level,
1,
1);
CopyFromOrToBuffer(cbs.CommandBuffer, copyToBuffer, image, hostSize, true, layer, level, 1, 1, singleSlice: true, offset, stride);
@ -677,6 +705,19 @@ namespace Ryujinx.Graphics.Vulkan
CopyDataToOutputBuffer(cbs, tempCopyHolder, autoBuffer, hostSize, range.Offset);
tempCopyHolder.Dispose();
}
else
{
BufferHolder.InsertBufferBarrier(
_gd,
cbs.CommandBuffer,
copyToBuffer,
AccessFlags.TransferWriteBit,
BufferHolder.DefaultAccessFlags,
PipelineStageFlags.TransferBit,
PipelineStageFlags.AllCommandsBit,
offset,
outSize);
}
}
private ReadOnlySpan<byte> GetData(CommandBufferPool cbp, PersistentFlushBuffer flushBuffer)
@ -993,7 +1034,7 @@ namespace Ryujinx.Graphics.Vulkan
throw new NotImplementedException();
}
public (Auto<DisposableRenderPass> renderPass, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
public (RenderPassHolder rpHolder, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
VulkanRenderer gd,
Device device,
CommandBufferScoped cbs,
@ -1006,7 +1047,7 @@ namespace Ryujinx.Graphics.Vulkan
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)

View File

@ -69,27 +69,32 @@ namespace Ryujinx.Graphics.Vulkan
{
DriverId.AmdProprietary => "AMD",
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.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.MesaV3DV => "V3DV",
DriverId.MesaVenus => "Venus",
DriverId.Moltenvk => "MoltenVK",
DriverId.NvidiaProprietary => "NVIDIA",
DriverId.QualcommProprietary => "Qualcomm",
DriverId.MesaPanvk => "PanVK",
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(),
};
}

View File

@ -42,6 +42,8 @@ namespace Ryujinx.Graphics.Vulkan
"VK_EXT_depth_clip_control",
"VK_KHR_portability_subset", // As per spec, we should enable this if present.
"VK_EXT_4444_formats",
"VK_KHR_8bit_storage",
"VK_KHR_maintenance2",
};
private static readonly string[] _requiredExtensions = {
@ -355,6 +357,14 @@ namespace Ryujinx.Graphics.Vulkan
features2.PNext = &supportedFeaturesDepthClipControl;
}
PhysicalDeviceVulkan12Features supportedPhysicalDeviceVulkan12Features = new()
{
SType = StructureType.PhysicalDeviceVulkan12Features,
PNext = features2.PNext,
};
features2.PNext = &supportedPhysicalDeviceVulkan12Features;
api.GetPhysicalDeviceFeatures2(physicalDevice.PhysicalDevice, &features2);
var supportedFeatures = features2.Features;
@ -382,6 +392,7 @@ namespace Ryujinx.Graphics.Vulkan
TessellationShader = supportedFeatures.TessellationShader,
VertexPipelineStoresAndAtomics = supportedFeatures.VertexPipelineStoresAndAtomics,
RobustBufferAccess = useRobustBufferAccess,
SampleRateShading = supportedFeatures.SampleRateShading,
};
void* pExtendedFeatures = null;
@ -451,9 +462,11 @@ namespace Ryujinx.Graphics.Vulkan
{
SType = StructureType.PhysicalDeviceVulkan12Features,
PNext = pExtendedFeatures,
DescriptorIndexing = physicalDevice.IsDeviceExtensionPresent("VK_EXT_descriptor_indexing"),
DrawIndirectCount = physicalDevice.IsDeviceExtensionPresent(KhrDrawIndirectCount.ExtensionName),
UniformBufferStandardLayout = physicalDevice.IsDeviceExtensionPresent("VK_KHR_uniform_buffer_standard_layout"),
DescriptorIndexing = supportedPhysicalDeviceVulkan12Features.DescriptorIndexing,
DrawIndirectCount = supportedPhysicalDeviceVulkan12Features.DrawIndirectCount,
UniformBufferStandardLayout = supportedPhysicalDeviceVulkan12Features.UniformBufferStandardLayout,
UniformAndStorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.UniformAndStorageBuffer8BitAccess,
StorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.StorageBuffer8BitAccess,
};
pExtendedFeatures = &featuresVk12;

View File

@ -87,9 +87,11 @@ namespace Ryujinx.Graphics.Vulkan
internal bool IsAmdGcn { get; private set; }
internal bool IsNvidiaPreTuring { get; private set; }
internal bool IsIntelArc { get; private set; }
internal bool IsQualcommProprietary { get; private set; }
internal bool IsMoltenVk { get; private set; }
internal bool IsTBDR { get; private set; }
internal bool IsSharedMemory { get; private set; }
public string GpuVendor { get; private set; }
public string GpuDriver { get; private set; }
public string GpuRenderer { get; private set; }
@ -344,7 +346,7 @@ namespace Ryujinx.Graphics.Vulkan
{
IsNvidiaPreTuring = gpuNumber < 2000;
}
else if (GpuDriver.Contains("TITAN") && !GpuDriver.Contains("RTX"))
else if (GpuRenderer.Contains("TITAN") && !GpuRenderer.Contains("RTX"))
{
IsNvidiaPreTuring = true;
}
@ -354,6 +356,8 @@ namespace Ryujinx.Graphics.Vulkan
IsIntelArc = GpuRenderer.StartsWith("Intel(R) Arc(TM)");
}
IsQualcommProprietary = hasDriverProperties && driverProperties.DriverID == DriverId.QualcommProprietary;
ulong minResourceAlignment = Math.Max(
Math.Max(
properties.Limits.MinStorageBufferOffsetAlignment,
@ -411,7 +415,7 @@ namespace Ryujinx.Graphics.Vulkan
Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtExternalMemoryHost hostMemoryApi);
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();
@ -688,7 +692,7 @@ namespace Ryujinx.Graphics.Vulkan
GpuVendor,
memoryType: memoryType,
hasFrontFacingBug: IsIntelWindows,
hasVectorIndexingBug: Vendor == Vendor.Qualcomm,
hasVectorIndexingBug: IsQualcommProprietary,
needsFragmentOutputSpecialization: IsMoltenVk,
reduceShaderPrecision: IsMoltenVk,
supportsAstcCompression: features2.Features.TextureCompressionAstcLdr && supportsAstcFormats,
@ -935,6 +939,11 @@ namespace Ryujinx.Graphics.Vulkan
ScreenCaptured?.Invoke(this, bitmap);
}
public bool SupportsRenderPassBarrier(PipelineStageFlags flags)
{
return !(IsMoltenVk || IsQualcommProprietary);
}
public unsafe void Dispose()
{
if (!_initialized)

View File

@ -623,7 +623,8 @@ namespace Ryujinx.Graphics.Vulkan
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)

View File

@ -7,6 +7,7 @@ using Ryujinx.Common.SystemInterop;
using Ryujinx.Modules;
using Ryujinx.SDL2.Common;
using Ryujinx.UI;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper;
@ -322,7 +323,35 @@ namespace Ryujinx
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))

View File

@ -37,7 +37,9 @@ using Ryujinx.UI.Windows;
using Silk.NET.Vulkan;
using SPB.Graphics.Vulkan;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Threading;
@ -60,7 +62,6 @@ namespace Ryujinx.UI
private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution;
private readonly ApplicationLibrary _applicationLibrary;
private readonly GtkHostUIHandler _uiHandler;
private readonly AutoResetEvent _deviceExitStatus;
private readonly ListStore _tableStore;
@ -69,11 +70,12 @@ namespace Ryujinx.UI
private bool _gameLoaded;
private bool _ending;
private string _currentEmulatedGamePath = null;
private ApplicationData _currentApplicationData = null;
private string _lastScannedAmiiboId = "";
private bool _lastScannedAmiiboShowAll = false;
public readonly ApplicationLibrary ApplicationLibrary;
public RendererWidgetBase RendererWidget;
public InputManager InputManager;
@ -180,8 +182,12 @@ namespace Ryujinx.UI
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, CommandLineState.Profile);
_userChannelPersistence = new UserChannelPersistence();
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
? IntegrityCheckLevel.ErrorOnInvalid
: IntegrityCheckLevel.None;
// Instantiate GUI objects.
_applicationLibrary = new ApplicationLibrary(_virtualFileSystem);
ApplicationLibrary = new ApplicationLibrary(_virtualFileSystem, checkLevel);
_uiHandler = new GtkHostUIHandler(this);
_deviceExitStatus = new AutoResetEvent(false);
@ -190,8 +196,8 @@ namespace Ryujinx.UI
FocusInEvent += MainWindow_FocusInEvent;
FocusOutEvent += MainWindow_FocusOutEvent;
_applicationLibrary.ApplicationAdded += Application_Added;
_applicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated;
ApplicationLibrary.ApplicationAdded += Application_Added;
ApplicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated;
_fileMenu.StateChanged += FileMenu_StateChanged;
_actionMenu.StateChanged += ActionMenu_StateChanged;
@ -732,7 +738,7 @@ namespace Ryujinx.UI
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;
})
@ -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();
@ -857,7 +863,7 @@ namespace Ryujinx.UI
case ".xci":
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
return _emulationContext.LoadXci(path);
return _emulationContext.LoadXci(path, applicationId);
case ".nca":
Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
@ -866,7 +872,7 @@ namespace Ryujinx.UI
case ".pfs0":
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
return _emulationContext.LoadNsp(path);
return _emulationContext.LoadNsp(path, applicationId);
default:
Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
try
@ -887,7 +893,7 @@ namespace Ryujinx.UI
return false;
}
public void RunApplication(string path, bool startFullscreen = false)
public void RunApplication(ApplicationData application, bool startFullscreen = false)
{
if (_gameLoaded)
{
@ -909,14 +915,14 @@ namespace Ryujinx.UI
bool isFirmwareTitle = false;
if (path.StartsWith("@SystemContent"))
if (application.Path.StartsWith("@SystemContent"))
{
path = VirtualFileSystem.SwitchPathToSystemPath(path);
application.Path = VirtualFileSystem.SwitchPathToSystemPath(application.Path);
isFirmwareTitle = true;
}
if (!LoadApplication(path, isFirmwareTitle))
if (!LoadApplication(application.Path, application.Id, isFirmwareTitle))
{
_emulationContext.Dispose();
SwitchToGameTable();
@ -926,7 +932,7 @@ namespace Ryujinx.UI
SetupProgressUIHandlers();
_currentEmulatedGamePath = path;
_currentApplicationData = application;
_deviceExitStatus.Reset();
@ -1165,7 +1171,7 @@ namespace Ryujinx.UI
_tableStore.AppendValues(
args.AppData.Favorite,
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.Version,
args.AppData.TimePlayedString,
@ -1253,9 +1259,22 @@ namespace Ryujinx.UI
{
_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)
@ -1313,13 +1332,22 @@ namespace Ryujinx.UI
return;
}
string titleFilePath = _tableStore.GetValue(treeIter, 9).ToString();
string titleName = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[0];
string titleId = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[1].ToLower();
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),
};
BlitStruct<ApplicationControlProperty> controlData = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10);
_ = new GameTableContextMenu(this, _virtualFileSystem, _accountManager, _libHacHorizonManager.RyujinxClient, titleFilePath, titleName, titleId, controlData);
_ = new GameTableContextMenu(this, _virtualFileSystem, _accountManager, _libHacHorizonManager.RyujinxClient, application);
}
private void Load_Application_File(object sender, EventArgs args)
@ -1341,7 +1369,15 @@ namespace Ryujinx.UI
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)
{
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);
RunApplication(contentPath);
ApplicationData applicationData = new()
{
Name = "miiEdit",
Id = 0x0100000000001009ul,
Path = contentPath,
};
RunApplication(applicationData);
}
private void Open_Ryu_Folder(object sender, EventArgs args)
@ -1646,13 +1695,13 @@ namespace Ryujinx.UI
{
_userChannelPersistence.ShouldRestart = false;
RunApplication(_currentEmulatedGamePath);
RunApplication(_currentApplicationData);
}
else
{
// otherwise, clear state.
_userChannelPersistence = new UserChannelPersistence();
_currentEmulatedGamePath = null;
_currentApplicationData = null;
_actionMenu.Sensitive = false;
_firmwareInstallFile.Sensitive = true;
_firmwareInstallDirectory.Sensitive = true;
@ -1714,7 +1763,7 @@ namespace Ryujinx.UI
_emulationContext.Processes.ActiveApplication.ProgramId,
_emulationContext.Processes.ActiveApplication.ApplicationControlProperties
.Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString(),
_currentEmulatedGamePath);
_currentApplicationData.Path);
window.Destroyed += CheatWindow_Destroyed;
window.Show();

View File

@ -16,6 +16,8 @@ using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
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.Common.Configuration;
using Ryujinx.UI.Common.Helper;
@ -23,7 +25,6 @@ using Ryujinx.UI.Windows;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Threading;
@ -36,17 +37,13 @@ namespace Ryujinx.UI.Widgets
private readonly VirtualFileSystem _virtualFileSystem;
private readonly AccountManager _accountManager;
private readonly HorizonClient _horizonClient;
private readonly BlitStruct<ApplicationControlProperty> _controlData;
private readonly string _titleFilePath;
private readonly string _titleName;
private readonly string _titleIdText;
private readonly ulong _titleId;
private readonly ApplicationData _applicationData;
private MessageDialog _dialog;
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;
@ -55,23 +52,22 @@ namespace Ryujinx.UI.Widgets
_virtualFileSystem = virtualFileSystem;
_accountManager = accountManager;
_horizonClient = horizonClient;
_titleFilePath = titleFilePath;
_titleName = titleName;
_titleIdText = titleId;
_controlData = controlData;
_applicationData = applicationData;
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");
return;
_openSaveUserDirMenuItem.Sensitive = _applicationData.ControlHolder.Value.UserAccountSaveDataSize > 0;
_openSaveDeviceDirMenuItem.Sensitive = _applicationData.ControlHolder.Value.DeviceSaveDataSize > 0;
_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;
_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();
string fileExt = System.IO.Path.GetExtension(_applicationData.Path).ToLower();
bool hasNca = fileExt == ".nca" || fileExt == ".nsp" || fileExt == ".pfs0" || fileExt == ".xci";
_extractRomFsMenuItem.Sensitive = hasNca;
@ -137,7 +133,7 @@ namespace Ryujinx.UI.Widgets
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;
}
@ -190,7 +186,7 @@ namespace Ryujinx.UI.Widgets
{
Title = "Ryujinx - NCA Section Extractor",
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,
};
@ -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 patchNca = null;
if ((System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".nsp") ||
(System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".pfs0") ||
(System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".xci"))
if ((System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".nsp") ||
(System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".pfs0") ||
(System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".xci"))
{
IFileSystem pfs;
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;
}
IFileSystem pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(_applicationData.Path, _virtualFileSystem);
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());
}
@ -266,7 +249,11 @@ namespace Ryujinx.UI.Widgets
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)
{
@ -460,44 +447,44 @@ namespace Ryujinx.UI.Widgets
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 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);
}
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);
}
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);
}
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)
{
new DlcWindow(_virtualFileSystem, _titleIdText, _titleName).Show();
new DlcWindow(_virtualFileSystem, _applicationData.IdBaseString, _applicationData).Show();
}
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)
{
string modsBasePath = ModLoader.GetModsBasePath();
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, _titleIdText);
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, _applicationData.IdString);
OpenHelper.OpenFolder(titleModsPath);
}
@ -505,7 +492,7 @@ namespace Ryujinx.UI.Widgets
private void OpenTitleSdModDir_Clicked(object sender, EventArgs args)
{
string sdModsBasePath = ModLoader.GetSdModsBasePath();
string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, _titleIdText);
string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, _applicationData.IdString);
OpenHelper.OpenFolder(titleModsPath);
}
@ -527,7 +514,7 @@ namespace Ryujinx.UI.Widgets
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 backupPath = System.IO.Path.Combine(ptcDir, "1");
@ -544,7 +531,7 @@ namespace Ryujinx.UI.Widgets
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))
{
@ -556,10 +543,10 @@ namespace Ryujinx.UI.Widgets
private void PurgePtcCache_Clicked(object sender, EventArgs args)
{
DirectoryInfo mainDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "0"));
DirectoryInfo backupDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "1"));
DirectoryInfo mainDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "cpu", "0"));
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();
@ -593,9 +580,9 @@ namespace Ryujinx.UI.Widgets
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<FileInfo> newCacheFiles = new();
@ -637,8 +624,11 @@ namespace Ryujinx.UI.Widgets
private void CreateShortcut_Clicked(object sender, EventArgs args)
{
byte[] appIcon = new ApplicationLibrary(_virtualFileSystem).GetApplicationIcon(_titleFilePath, ConfigurationState.Instance.System.Language);
ShortcutHelper.CreateAppShortcut(_titleFilePath, _titleName, _titleIdText, appIcon);
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
? 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);
}
}
}

View File

@ -1,7 +1,9 @@
using Gtk;
using LibHac.Tools.FsSystem;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common.Configuration;
using System;
using System.Collections.Generic;
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"))
{
builder.Autoconnect(this);
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
? IntegrityCheckLevel.ErrorOnInvalid
: IntegrityCheckLevel.None;
_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 titleModsPath = ModLoader.GetApplicationDir(modsBasePath, titleId.ToString("X16"));

View File

@ -2,17 +2,21 @@ using Gtk;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.Loaders.Processes.Extensions;
using Ryujinx.HLE.Utilities;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Widgets;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using GUI = Gtk.Builder.ObjectAttribute;
namespace Ryujinx.UI.Windows
@ -20,7 +24,7 @@ namespace Ryujinx.UI.Windows
public class DlcWindow : Window
{
private readonly VirtualFileSystem _virtualFileSystem;
private readonly string _titleId;
private readonly string _applicationIdBase;
private readonly string _dlcJsonPath;
private readonly List<DownloadableContentContainer> _dlcContainerList;
@ -32,16 +36,16 @@ namespace Ryujinx.UI.Windows
[GUI] TreeSelection _dlcTreeSelection;
#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 applicationIdBase, ApplicationData applicationData) : this(new Builder("Ryujinx.Gtk3.UI.Windows.DlcWindow.glade"), virtualFileSystem, applicationIdBase, applicationData) { }
private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_dlcWindow"))
private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string applicationIdBase, ApplicationData applicationData) : base(builder.GetRawOwnedObject("_dlcWindow"))
{
builder.Autoconnect(this);
_titleId = titleId;
_applicationIdBase = applicationIdBase;
_virtualFileSystem = virtualFileSystem;
_dlcJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleId, "dlc.json");
_baseTitleInfoLabel.Text = $"DLC Available for {titleName} [{titleId.ToUpper()}]";
_dlcJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationIdBase, "dlc.json");
_baseTitleInfoLabel.Text = $"DLC Available for {applicationData.Name} [{applicationIdBase.ToUpper()}]";
try
{
@ -72,7 +76,7 @@ namespace Ryujinx.UI.Windows
};
_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);
foreach (DownloadableContentContainer dlcContainer in _dlcContainerList)
@ -86,18 +90,18 @@ namespace Ryujinx.UI.Windows
bool areAllContentPacksEnabled = dlcContainer.DownloadableContentNcaList.TrueForAll((nca) => nca.Enabled);
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();
pfs.Initialize(containerFile.AsStorage()).ThrowIfFailure();
_virtualFileSystem.ImportTickets(pfs);
if (partitionFileSystem == null)
{
continue;
}
foreach (DownloadableContentNca dlcNca in dlcContainer.DownloadableContentNcaList)
{
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);
if (nca != null)
@ -112,6 +116,9 @@ namespace Ryujinx.UI.Windows
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)
@ -128,6 +135,52 @@ namespace Ryujinx.UI.Windows
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(_applicationIdBase, NumberStyles.HexNumber))
{
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)
{
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)
{
if (!File.Exists(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!");
}
AddDlc(containerPath);
}
}

View File

@ -2,14 +2,17 @@ using Gtk;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Ncm;
using LibHac.Ns;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.Loaders.Processes.Extensions;
using Ryujinx.HLE.Utilities;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Widgets;
using System;
using System.Collections.Generic;
@ -24,7 +27,7 @@ namespace Ryujinx.UI.Windows
{
private readonly MainWindow _parent;
private readonly VirtualFileSystem _virtualFileSystem;
private readonly string _titleId;
private readonly ApplicationData _applicationData;
private readonly string _updateJsonPath;
private TitleUpdateMetadata _titleUpdateWindowData;
@ -38,17 +41,17 @@ namespace Ryujinx.UI.Windows
[GUI] RadioButton _noUpdateRadioButton;
#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;
builder.Autoconnect(this);
_titleId = titleId;
_applicationData = applicationData;
_virtualFileSystem = virtualFileSystem;
_updateJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleId, "updates.json");
_updateJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, applicationData.IdBaseString, "updates.json");
_radioButtonToPathDictionary = new Dictionary<RadioButton, string>();
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.IdBaseString}]";
// Try to get updates from PFS first
AddUpdate(_applicationData.Path, true);
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();
nsp.Initialize(file.AsStorage()).ThrowIfFailure();
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
? 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();
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;
radioLabel = "Bundled: " + radioLabel;
}
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!");
}
}
catch (Exception exception)
{
GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {path}");
}
}
catch (Exception exception)
{
GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {path}");
}
}

View File

@ -14,6 +14,7 @@ using Ryujinx.Common.Utilities;
using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Services.Ssl;
using Ryujinx.HLE.HOS.Services.Time;
using Ryujinx.HLE.Utilities;
using System;
using System.Collections.Generic;
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)
{
// TODO: Check Aoc version.
@ -232,11 +198,7 @@ namespace Ryujinx.HLE.FileSystem
if (!mergedToContainer)
{
using FileStream fileStream = File.OpenRead(containerPath);
using PartitionFileSystem partitionFileSystem = new();
partitionFileSystem.Initialize(fileStream.AsStorage()).ThrowIfFailure();
_virtualFileSystem.ImportTickets(partitionFileSystem);
using var pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(containerPath, _virtualFileSystem);
}
}
}

View 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;
}
}
}

View File

@ -1,10 +0,0 @@
namespace Ryujinx.HLE.HOS.Kernel.Memory
{
enum AddressSpaceType
{
Addr32Bits = 0,
Addr36Bits = 1,
Addr32BitsNoMap = 2,
Addr39Bits = 3,
}
}

View File

@ -58,11 +58,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
public ulong AslrRegionStart { get; private set; }
public ulong AslrRegionEnd { get; private set; }
#pragma warning disable IDE0052 // Remove unread private member
private ulong _heapCapacity;
#pragma warning restore IDE0052
public ulong PhysicalMemoryUsage { get; private set; }
public ulong AliasRegionExtraSize { get; private set; }
private readonly KMemoryBlockManager _blockManager;
@ -98,30 +97,21 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
_reservedAddressSpaceSize = reservedAddressSpaceSize;
}
private static readonly int[] _addrSpaceSizes = { 32, 36, 32, 39 };
public Result InitializeForProcess(
AddressSpaceType addrSpaceType,
bool aslrEnabled,
ProcessCreationFlags flags,
bool fromBack,
MemoryRegion memRegion,
ulong address,
ulong size,
KMemoryBlockSlabManager slabManager)
{
if ((uint)addrSpaceType > (uint)AddressSpaceType.Addr39Bits)
{
throw new ArgumentException($"AddressSpaceType bigger than {(uint)AddressSpaceType.Addr39Bits}: {(uint)addrSpaceType}", nameof(addrSpaceType));
}
_contextId = Context.ContextIdManager.GetId();
ulong addrSpaceBase = 0;
ulong addrSpaceSize = 1UL << _addrSpaceSizes[(int)addrSpaceType];
ulong addrSpaceSize = 1UL << GetAddressSpaceWidth(flags);
Result result = CreateUserAddressSpace(
addrSpaceType,
aslrEnabled,
flags,
fromBack,
addrSpaceBase,
addrSpaceSize,
@ -138,6 +128,22 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
return result;
}
private static int GetAddressSpaceWidth(ProcessCreationFlags flags)
{
switch (flags & ProcessCreationFlags.AddressSpaceMask)
{
case ProcessCreationFlags.AddressSpace32Bit:
case ProcessCreationFlags.AddressSpace32BitWithoutAlias:
return 32;
case ProcessCreationFlags.AddressSpace64BitDeprecated:
return 36;
case ProcessCreationFlags.AddressSpace64Bit:
return 39;
}
throw new ArgumentException($"Invalid process flags {flags}", nameof(flags));
}
private struct Region
{
public ulong Start;
@ -147,8 +153,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
}
private Result CreateUserAddressSpace(
AddressSpaceType addrSpaceType,
bool aslrEnabled,
ProcessCreationFlags flags,
bool fromBack,
ulong addrSpaceStart,
ulong addrSpaceEnd,
@ -168,9 +173,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
ulong stackAndTlsIoStart;
ulong stackAndTlsIoEnd;
switch (addrSpaceType)
AliasRegionExtraSize = 0;
switch (flags & ProcessCreationFlags.AddressSpaceMask)
{
case AddressSpaceType.Addr32Bits:
case ProcessCreationFlags.AddressSpace32Bit:
aliasRegion.Size = 0x40000000;
heapRegion.Size = 0x40000000;
stackRegion.Size = 0;
@ -183,7 +190,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
stackAndTlsIoEnd = 0x40000000;
break;
case AddressSpaceType.Addr36Bits:
case ProcessCreationFlags.AddressSpace64BitDeprecated:
aliasRegion.Size = 0x180000000;
heapRegion.Size = 0x180000000;
stackRegion.Size = 0;
@ -196,7 +203,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
stackAndTlsIoEnd = 0x80000000;
break;
case AddressSpaceType.Addr32BitsNoMap:
case ProcessCreationFlags.AddressSpace32BitWithoutAlias:
aliasRegion.Size = 0;
heapRegion.Size = 0x80000000;
stackRegion.Size = 0;
@ -209,7 +216,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
stackAndTlsIoEnd = 0x40000000;
break;
case AddressSpaceType.Addr39Bits:
case ProcessCreationFlags.AddressSpace64Bit:
if (_reservedAddressSpaceSize < addrSpaceEnd)
{
int addressSpaceWidth = (int)ulong.Log2(_reservedAddressSpaceSize);
@ -218,8 +225,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
heapRegion.Size = 0x180000000;
stackRegion.Size = 1UL << (addressSpaceWidth - 8);
tlsIoRegion.Size = 1UL << (addressSpaceWidth - 3);
CodeRegionStart = BitUtils.AlignDown<ulong>(address, RegionAlignment);
codeRegionSize = BitUtils.AlignUp<ulong>(endAddr, RegionAlignment) - CodeRegionStart;
CodeRegionStart = BitUtils.AlignDown(address, RegionAlignment);
codeRegionSize = BitUtils.AlignUp(endAddr, RegionAlignment) - CodeRegionStart;
stackAndTlsIoStart = 0;
stackAndTlsIoEnd = 0;
AslrRegionStart = 0x8000000;
@ -239,9 +246,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
stackAndTlsIoStart = 0;
stackAndTlsIoEnd = 0;
}
if (flags.HasFlag(ProcessCreationFlags.EnableAliasRegionExtraSize))
{
AliasRegionExtraSize = addrSpaceEnd / 8;
aliasRegion.Size += AliasRegionExtraSize;
}
break;
default:
throw new ArgumentException($"AddressSpaceType bigger than {(uint)AddressSpaceType.Addr39Bits}: {(uint)addrSpaceType}", nameof(addrSpaceType));
throw new ArgumentException($"Invalid process flags {flags}", nameof(flags));
}
CodeRegionEnd = CodeRegionStart + codeRegionSize;
@ -266,6 +280,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
ulong aslrMaxOffset = mapAvailableSize - mapTotalSize;
bool aslrEnabled = flags.HasFlag(ProcessCreationFlags.EnableAslr);
_aslrEnabled = aslrEnabled;
AddrSpaceStart = addrSpaceStart;
@ -725,7 +741,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
{
address = 0;
if (size > HeapRegionEnd - HeapRegionStart)
if (size > HeapRegionEnd - HeapRegionStart || size > _heapCapacity)
{
return KernelResult.OutOfMemory;
}

View File

@ -126,8 +126,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
_contextFactory = contextFactory ?? new ProcessContextFactory();
_customThreadStart = customThreadStart;
AddressSpaceType addrSpaceType = (AddressSpaceType)((int)(creationInfo.Flags & ProcessCreationFlags.AddressSpaceMask) >> (int)ProcessCreationFlags.AddressSpaceShift);
Pid = KernelContext.NewKipId();
if (Pid == 0 || Pid >= KernelConstants.InitialProcessId)
@ -137,8 +135,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
InitializeMemoryManager(creationInfo.Flags);
bool aslrEnabled = creationInfo.Flags.HasFlag(ProcessCreationFlags.EnableAslr);
ulong codeAddress = creationInfo.CodeAddress;
ulong codeSize = (ulong)creationInfo.CodePagesCount * KPageTableBase.PageSize;
@ -148,9 +144,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
: KernelContext.SmallMemoryBlockSlabManager;
Result result = MemoryManager.InitializeForProcess(
addrSpaceType,
aslrEnabled,
!aslrEnabled,
creationInfo.Flags,
!creationInfo.Flags.HasFlag(ProcessCreationFlags.EnableAslr),
memRegion,
codeAddress,
codeSize,
@ -234,8 +229,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
: KernelContext.SmallMemoryBlockSlabManager;
}
AddressSpaceType addrSpaceType = (AddressSpaceType)((int)(creationInfo.Flags & ProcessCreationFlags.AddressSpaceMask) >> (int)ProcessCreationFlags.AddressSpaceShift);
Pid = KernelContext.NewProcessId();
if (Pid == ulong.MaxValue || Pid < KernelConstants.InitialProcessId)
@ -245,16 +238,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
InitializeMemoryManager(creationInfo.Flags);
bool aslrEnabled = creationInfo.Flags.HasFlag(ProcessCreationFlags.EnableAslr);
ulong codeAddress = creationInfo.CodeAddress;
ulong codeSize = codePagesCount * KPageTableBase.PageSize;
Result result = MemoryManager.InitializeForProcess(
addrSpaceType,
aslrEnabled,
!aslrEnabled,
creationInfo.Flags,
!creationInfo.Flags.HasFlag(ProcessCreationFlags.EnableAslr),
memRegion,
codeAddress,
codeSize,
@ -309,8 +299,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
private Result ParseProcessInfo(ProcessCreationInfo creationInfo)
{
// Ensure that the current kernel version is equal or above to the minimum required.
uint requiredKernelVersionMajor = (uint)Capabilities.KernelReleaseVersion >> 19;
uint requiredKernelVersionMinor = ((uint)Capabilities.KernelReleaseVersion >> 15) & 0xf;
uint requiredKernelVersionMajor = Capabilities.KernelReleaseVersion >> 19;
uint requiredKernelVersionMinor = (Capabilities.KernelReleaseVersion >> 15) & 0xf;
if (KernelContext.EnableVersionChecks)
{
@ -519,12 +509,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return result;
}
#pragma warning disable CA1822 // Mark member as static
private void GenerateRandomEntropy()
{
// TODO.
}
#pragma warning restore CA1822
public Result Start(int mainThreadPriority, ulong stackSize)
{
@ -1182,5 +1170,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
// TODO
return false;
}
public bool IsSvcPermitted(int svcId)
{
return Capabilities.IsSvcPermitted(svcId);
}
}
}

View File

@ -8,6 +8,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
class KProcessCapabilities
{
private const int SvcMaskElementBits = 8;
public byte[] SvcAccessMask { get; }
public byte[] IrqAccessMask { get; }
@ -22,7 +24,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public KProcessCapabilities()
{
// length / number of bits of the underlying type
SvcAccessMask = new byte[KernelConstants.SupervisorCallCount / 8];
SvcAccessMask = new byte[KernelConstants.SupervisorCallCount / SvcMaskElementBits];
IrqAccessMask = new byte[0x80];
}
@ -208,7 +210,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return KernelResult.MaximumExceeded;
}
SvcAccessMask[svcId / 8] |= (byte)(1 << (svcId & 7));
SvcAccessMask[svcId / SvcMaskElementBits] |= (byte)(1 << (svcId % SvcMaskElementBits));
}
break;
@ -324,5 +326,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return mask << (int)min;
}
public bool IsSvcPermitted(int svcId)
{
int index = svcId / SvcMaskElementBits;
int mask = 1 << (svcId % SvcMaskElementBits);
return (uint)svcId < KernelConstants.SupervisorCallCount && (SvcAccessMask[index] & mask) != 0;
}
}
}

View File

@ -29,6 +29,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
PoolPartitionMask = 0xf << PoolPartitionShift,
OptimizeMemoryAllocation = 1 << 11,
DisableDeviceAddressSpaceMerge = 1 << 12,
EnableAliasRegionExtraSize = 1 << 13,
All =
Is64Bit |
@ -38,6 +40,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
IsApplication |
DeprecatedUseSecureMemory |
PoolPartitionMask |
OptimizeMemoryAllocation,
OptimizeMemoryAllocation |
DisableDeviceAddressSpaceMerge |
EnableAliasRegionExtraSize,
}
}

View File

@ -21,14 +21,17 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
SystemResourceSizeTotal,
SystemResourceSizeUsed,
ProgramId,
// NOTE: Added in 4.0.0, removed in 5.0.0.
InitialProcessIdRange,
InitialProcessIdRange, // NOTE: Added in 4.0.0, removed in 5.0.0.
UserExceptionContextAddress,
TotalNonSystemMemorySize,
UsedNonSystemMemorySize,
IsApplication,
FreeThreadCount,
ThreadTickCount,
IsSvcPermitted,
IoRegionHint,
AliasRegionExtraSize,
MesosphereCurrentProcess = 65001,
}
}

View File

@ -84,6 +84,17 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return KernelResult.InvalidSize;
}
if (info.Flags.HasFlag(ProcessCreationFlags.EnableAliasRegionExtraSize))
{
if ((info.Flags & ProcessCreationFlags.AddressSpaceMask) != ProcessCreationFlags.AddressSpace64Bit ||
info.SystemResourcePagesCount <= 0)
{
return KernelResult.InvalidState;
}
// TODO: Check that we are in debug mode.
}
if (info.Flags.HasFlag(ProcessCreationFlags.OptimizeMemoryAllocation) &&
!info.Flags.HasFlag(ProcessCreationFlags.IsApplication))
{
@ -139,7 +150,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return handleTable.GenerateHandle(process, out handle);
}
#pragma warning disable CA1822 // Mark member as static
public Result StartProcess(int handle, int priority, int cpuCore, ulong mainThreadStackSize)
{
KProcess process = KernelStatic.GetCurrentProcess().HandleTable.GetObject<KProcess>(handle);
@ -172,17 +182,14 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return Result.Success;
}
#pragma warning restore CA1822
[Svc(0x5f)]
#pragma warning disable CA1822 // Mark member as static
public Result FlushProcessDataCache(int processHandle, ulong address, ulong size)
{
// FIXME: This needs to be implemented as ARMv7 doesn't have any way to do cache maintenance operations on EL0.
// As we don't support (and don't actually need) to flush the cache, this is stubbed.
return Result.Success;
}
#pragma warning restore CA1822
// IPC
@ -256,7 +263,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
}
[Svc(0x22)]
#pragma warning disable CA1822 // Mark member as static
public Result SendSyncRequestWithUserBuffer(
[PointerSized] ulong messagePtr,
[PointerSized] ulong messageSize,
@ -306,7 +312,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return result;
}
#pragma warning restore CA1822
[Svc(0x23)]
public Result SendAsyncRequestWithUserBuffer(
@ -896,7 +901,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
}
[Svc(2)]
#pragma warning disable CA1822 // Mark member as static
public Result SetMemoryPermission([PointerSized] ulong address, [PointerSized] ulong size, KMemoryPermission permission)
{
if (!PageAligned(address))
@ -928,10 +932,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return currentProcess.MemoryManager.SetMemoryPermission(address, size, permission);
}
#pragma warning restore CA1822
[Svc(3)]
#pragma warning disable CA1822 // Mark member as static
public Result SetMemoryAttribute(
[PointerSized] ulong address,
[PointerSized] ulong size,
@ -979,10 +981,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return result;
}
#pragma warning restore CA1822
[Svc(4)]
#pragma warning disable CA1822 // Mark member as static
public Result MapMemory([PointerSized] ulong dst, [PointerSized] ulong src, [PointerSized] ulong size)
{
if (!PageAligned(src | dst))
@ -1018,10 +1018,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return process.MemoryManager.Map(dst, src, size);
}
#pragma warning restore CA1822
[Svc(5)]
#pragma warning disable CA1822 // Mark member as static
public Result UnmapMemory([PointerSized] ulong dst, [PointerSized] ulong src, [PointerSized] ulong size)
{
if (!PageAligned(src | dst))
@ -1057,7 +1055,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return process.MemoryManager.Unmap(dst, src, size);
}
#pragma warning restore CA1822
[Svc(6)]
public Result QueryMemory([PointerSized] ulong infoPtr, [PointerSized] out ulong pageInfo, [PointerSized] ulong address)
@ -1074,7 +1071,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return result;
}
#pragma warning disable CA1822 // Mark member as static
public Result QueryMemory(out MemoryInfo info, out ulong pageInfo, ulong address)
{
KProcess process = KernelStatic.GetCurrentProcess();
@ -1094,10 +1090,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return Result.Success;
}
#pragma warning restore CA1822
[Svc(0x13)]
#pragma warning disable CA1822 // Mark member as static
public Result MapSharedMemory(int handle, [PointerSized] ulong address, [PointerSized] ulong size, KMemoryPermission permission)
{
if (!PageAligned(address))
@ -1143,10 +1137,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
currentProcess,
permission);
}
#pragma warning restore CA1822
[Svc(0x14)]
#pragma warning disable CA1822 // Mark member as static
public Result UnmapSharedMemory(int handle, [PointerSized] ulong address, [PointerSized] ulong size)
{
if (!PageAligned(address))
@ -1186,7 +1178,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
size,
currentProcess);
}
#pragma warning restore CA1822
[Svc(0x15)]
public Result CreateTransferMemory(out int handle, [PointerSized] ulong address, [PointerSized] ulong size, KMemoryPermission permission)
@ -1253,7 +1244,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
}
[Svc(0x51)]
#pragma warning disable CA1822 // Mark member as static
public Result MapTransferMemory(int handle, [PointerSized] ulong address, [PointerSized] ulong size, KMemoryPermission permission)
{
if (!PageAligned(address))
@ -1299,10 +1289,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
currentProcess,
permission);
}
#pragma warning restore CA1822
[Svc(0x52)]
#pragma warning disable CA1822 // Mark member as static
public Result UnmapTransferMemory(int handle, [PointerSized] ulong address, [PointerSized] ulong size)
{
if (!PageAligned(address))
@ -1342,10 +1330,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
size,
currentProcess);
}
#pragma warning restore CA1822
[Svc(0x2c)]
#pragma warning disable CA1822 // Mark member as static
public Result MapPhysicalMemory([PointerSized] ulong address, [PointerSized] ulong size)
{
if (!PageAligned(address))
@ -1380,10 +1366,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return process.MemoryManager.MapPhysicalMemory(address, size);
}
#pragma warning restore CA1822
[Svc(0x2d)]
#pragma warning disable CA1822 // Mark member as static
public Result UnmapPhysicalMemory([PointerSized] ulong address, [PointerSized] ulong size)
{
if (!PageAligned(address))
@ -1418,7 +1402,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return process.MemoryManager.UnmapPhysicalMemory(address, size);
}
#pragma warning restore CA1822
[Svc(0x4b)]
public Result CreateCodeMemory(out int handle, [PointerSized] ulong address, [PointerSized] ulong size)
@ -1462,7 +1445,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
}
[Svc(0x4c)]
#pragma warning disable CA1822 // Mark member as static
public Result ControlCodeMemory(
int handle,
CodeMemoryOperation op,
@ -1540,10 +1522,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return KernelResult.InvalidEnumValue;
}
}
#pragma warning restore CA1822
[Svc(0x73)]
#pragma warning disable CA1822 // Mark member as static
public Result SetProcessMemoryPermission(
int handle,
ulong src,
@ -1584,10 +1564,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return targetProcess.MemoryManager.SetProcessMemoryPermission(src, size, permission);
}
#pragma warning restore CA1822
[Svc(0x74)]
#pragma warning disable CA1822 // Mark member as static
public Result MapProcessMemory(
[PointerSized] ulong dst,
int handle,
@ -1643,10 +1621,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return dstProcess.MemoryManager.MapPages(dst, pageList, MemoryState.ProcessMemory, KMemoryPermission.ReadAndWrite);
}
#pragma warning restore CA1822
[Svc(0x75)]
#pragma warning disable CA1822 // Mark member as static
public Result UnmapProcessMemory(
[PointerSized] ulong dst,
int handle,
@ -1691,10 +1667,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return Result.Success;
}
#pragma warning restore CA1822
[Svc(0x77)]
#pragma warning disable CA1822 // Mark member as static
public Result MapProcessCodeMemory(int handle, ulong dst, ulong src, ulong size)
{
if (!PageAligned(dst) || !PageAligned(src))
@ -1731,10 +1705,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return targetProcess.MemoryManager.MapProcessCodeMemory(dst, src, size);
}
#pragma warning restore CA1822
[Svc(0x78)]
#pragma warning disable CA1822 // Mark member as static
public Result UnmapProcessCodeMemory(int handle, ulong dst, ulong src, ulong size)
{
if (!PageAligned(dst) || !PageAligned(src))
@ -1771,7 +1743,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return targetProcess.MemoryManager.UnmapProcessCodeMemory(dst, src, size);
}
#pragma warning restore CA1822
private static bool PageAligned(ulong address)
{
@ -1781,7 +1752,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
// System
[Svc(0x7b)]
#pragma warning disable CA1822 // Mark member as static
public Result TerminateProcess(int handle)
{
KProcess process = KernelStatic.GetCurrentProcess();
@ -1810,15 +1780,12 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return result;
}
#pragma warning restore CA1822
[Svc(7)]
#pragma warning disable CA1822 // Mark member as static
public void ExitProcess()
{
KernelStatic.GetCurrentProcess().TerminateCurrentProcess();
}
#pragma warning restore CA1822
[Svc(0x11)]
public Result SignalEvent(int handle)
@ -1911,7 +1878,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
}
[Svc(0x26)]
#pragma warning disable CA1822 // Mark member as static
public void Break(ulong reason)
{
KThread currentThread = KernelStatic.GetCurrentThread();
@ -1937,10 +1903,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
Logger.Debug?.Print(LogClass.KernelSvc, "Debugger triggered.");
}
}
#pragma warning restore CA1822
[Svc(0x27)]
#pragma warning disable CA1822 // Mark member as static
public void OutputDebugString([PointerSized] ulong strPtr, [PointerSized] ulong size)
{
KProcess process = KernelStatic.GetCurrentProcess();
@ -1949,7 +1913,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
Logger.Warning?.Print(LogClass.KernelSvc, str);
}
#pragma warning restore CA1822
[Svc(0x29)]
public Result GetInfo(out ulong value, InfoType id, int handle, long subId)
@ -1978,6 +1941,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
case InfoType.UsedNonSystemMemorySize:
case InfoType.IsApplication:
case InfoType.FreeThreadCount:
case InfoType.AliasRegionExtraSize:
{
if (subId != 0)
{
@ -2006,22 +1970,19 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
value = process.MemoryManager.AliasRegionStart;
break;
case InfoType.AliasRegionSize:
value = (process.MemoryManager.AliasRegionEnd -
process.MemoryManager.AliasRegionStart);
value = process.MemoryManager.AliasRegionEnd - process.MemoryManager.AliasRegionStart;
break;
case InfoType.HeapRegionAddress:
value = process.MemoryManager.HeapRegionStart;
break;
case InfoType.HeapRegionSize:
value = (process.MemoryManager.HeapRegionEnd -
process.MemoryManager.HeapRegionStart);
value = process.MemoryManager.HeapRegionEnd - process.MemoryManager.HeapRegionStart;
break;
case InfoType.TotalMemorySize:
value = process.GetMemoryCapacity();
break;
case InfoType.UsedMemorySize:
value = process.GetMemoryUsage();
break;
@ -2029,7 +1990,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
case InfoType.AslrRegionAddress:
value = process.MemoryManager.GetAddrSpaceBaseAddr();
break;
case InfoType.AslrRegionSize:
value = process.MemoryManager.GetAddrSpaceSize();
break;
@ -2038,20 +1998,17 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
value = process.MemoryManager.StackRegionStart;
break;
case InfoType.StackRegionSize:
value = (process.MemoryManager.StackRegionEnd -
process.MemoryManager.StackRegionStart);
value = process.MemoryManager.StackRegionEnd - process.MemoryManager.StackRegionStart;
break;
case InfoType.SystemResourceSizeTotal:
value = process.PersonalMmHeapPagesCount * KPageTableBase.PageSize;
break;
case InfoType.SystemResourceSizeUsed:
if (process.PersonalMmHeapPagesCount != 0)
{
value = process.MemoryManager.GetMmUsedPages() * KPageTableBase.PageSize;
}
break;
case InfoType.ProgramId:
@ -2065,7 +2022,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
case InfoType.TotalNonSystemMemorySize:
value = process.GetMemoryCapacityWithoutPersonalMmHeap();
break;
case InfoType.UsedNonSystemMemorySize:
value = process.GetMemoryUsageWithoutPersonalMmHeap();
break;
@ -2084,10 +2040,12 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{
value = 0;
}
break;
case InfoType.AliasRegionExtraSize:
value = process.MemoryManager.AliasRegionExtraSize;
break;
}
break;
}
@ -2104,7 +2062,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
}
value = KernelStatic.GetCurrentProcess().Debug ? 1UL : 0UL;
break;
}
@ -2136,7 +2093,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
value = (uint)resLimHandle;
}
break;
}
@ -2155,7 +2111,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
}
value = (ulong)KTimeManager.ConvertHostTicksToTicks(_context.Schedulers[currentCore].TotalIdleTimeTicks);
break;
}
@ -2174,7 +2129,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
KProcess currentProcess = KernelStatic.GetCurrentProcess();
value = currentProcess.RandomEntropy[subId];
break;
}
@ -2220,7 +2174,22 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
value = (ulong)KTimeManager.ConvertHostTicksToTicks(totalTimeRunning);
}
break;
}
case InfoType.IsSvcPermitted:
{
if (handle != 0)
{
return KernelResult.InvalidHandle;
}
if (subId != 0x36)
{
return KernelResult.InvalidCombination;
}
value = KernelStatic.GetCurrentProcess().IsSvcPermitted((int)subId) ? 1UL : 0UL;
break;
}
@ -2231,7 +2200,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return KernelResult.InvalidHandle;
}
if ((ulong)subId != 0)
if (subId != 0)
{
return KernelResult.InvalidCombination;
}
@ -2246,8 +2215,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return result;
}
value = (ulong)outHandle;
value = (uint)outHandle;
break;
}
@ -2398,7 +2366,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
}
[Svc(0x30)]
#pragma warning disable CA1822 // Mark member as static
public Result GetResourceLimitLimitValue(out long limitValue, int handle, LimitableResource resource)
{
limitValue = 0;
@ -2419,10 +2386,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return Result.Success;
}
#pragma warning restore CA1822
[Svc(0x31)]
#pragma warning disable CA1822 // Mark member as static
public Result GetResourceLimitCurrentValue(out long limitValue, int handle, LimitableResource resource)
{
limitValue = 0;
@ -2443,10 +2408,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return Result.Success;
}
#pragma warning restore CA1822
[Svc(0x37)]
#pragma warning disable CA1822 // Mark member as static
public Result GetResourceLimitPeakValue(out long peak, int handle, LimitableResource resource)
{
peak = 0;
@ -2467,7 +2430,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return Result.Success;
}
#pragma warning restore CA1822
[Svc(0x7d)]
public Result CreateResourceLimit(out int handle)
@ -2480,7 +2442,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
}
[Svc(0x7e)]
#pragma warning disable CA1822 // Mark member as static
public Result SetResourceLimitLimitValue(int handle, LimitableResource resource, long limitValue)
{
if (resource >= LimitableResource.Count)
@ -2497,7 +2458,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return resourceLimit.SetLimitValue(resource, limitValue);
}
#pragma warning restore CA1822
// Thread
@ -2577,7 +2537,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
}
[Svc(9)]
#pragma warning disable CA1822 // Mark member as static
public Result StartThread(int handle)
{
KProcess process = KernelStatic.GetCurrentProcess();
@ -2604,17 +2563,14 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return KernelResult.InvalidHandle;
}
}
#pragma warning restore CA1822
[Svc(0xa)]
#pragma warning disable CA1822 // Mark member as static
public void ExitThread()
{
KThread currentThread = KernelStatic.GetCurrentThread();
currentThread.Exit();
}
#pragma warning restore CA1822
[Svc(0xb)]
public void SleepThread(long timeout)
@ -2641,7 +2597,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
}
[Svc(0xc)]
#pragma warning disable CA1822 // Mark member as static
public Result GetThreadPriority(out int priority, int handle)
{
KProcess process = KernelStatic.GetCurrentProcess();
@ -2661,10 +2616,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return KernelResult.InvalidHandle;
}
}
#pragma warning restore CA1822
[Svc(0xd)]
#pragma warning disable CA1822 // Mark member as static
public Result SetThreadPriority(int handle, int priority)
{
// TODO: NPDM check.
@ -2682,10 +2635,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return Result.Success;
}
#pragma warning restore CA1822
[Svc(0xe)]
#pragma warning disable CA1822 // Mark member as static
public Result GetThreadCoreMask(out int preferredCore, out ulong affinityMask, int handle)
{
KProcess process = KernelStatic.GetCurrentProcess();
@ -2707,10 +2658,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return KernelResult.InvalidHandle;
}
}
#pragma warning restore CA1822
[Svc(0xf)]
#pragma warning disable CA1822 // Mark member as static
public Result SetThreadCoreMask(int handle, int preferredCore, ulong affinityMask)
{
KProcess currentProcess = KernelStatic.GetCurrentProcess();
@ -2758,18 +2707,14 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return thread.SetCoreAndAffinityMask(preferredCore, affinityMask);
}
#pragma warning restore CA1822
[Svc(0x10)]
#pragma warning disable CA1822 // Mark member as static
public int GetCurrentProcessorNumber()
{
return KernelStatic.GetCurrentThread().CurrentCore;
}
#pragma warning restore CA1822
[Svc(0x25)]
#pragma warning disable CA1822 // Mark member as static
public Result GetThreadId(out ulong threadUid, int handle)
{
KProcess process = KernelStatic.GetCurrentProcess();
@ -2789,10 +2734,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return KernelResult.InvalidHandle;
}
}
#pragma warning restore CA1822
[Svc(0x32)]
#pragma warning disable CA1822 // Mark member as static
public Result SetThreadActivity(int handle, bool pause)
{
KProcess process = KernelStatic.GetCurrentProcess();
@ -2816,10 +2759,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return thread.SetActivity(pause);
}
#pragma warning restore CA1822
[Svc(0x33)]
#pragma warning disable CA1822 // Mark member as static
public Result GetThreadContext3([PointerSized] ulong address, int handle)
{
KProcess currentProcess = KernelStatic.GetCurrentProcess();
@ -2853,7 +2794,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return result;
}
#pragma warning restore CA1822
// Thread synchronization
@ -2986,7 +2926,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
}
[Svc(0x1a)]
#pragma warning disable CA1822 // Mark member as static
public Result ArbitrateLock(int ownerHandle, [PointerSized] ulong mutexAddress, int requesterHandle)
{
if (IsPointingInsideKernel(mutexAddress))
@ -3003,10 +2942,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return currentProcess.AddressArbiter.ArbitrateLock(ownerHandle, mutexAddress, requesterHandle);
}
#pragma warning restore CA1822
[Svc(0x1b)]
#pragma warning disable CA1822 // Mark member as static
public Result ArbitrateUnlock([PointerSized] ulong mutexAddress)
{
if (IsPointingInsideKernel(mutexAddress))
@ -3023,10 +2960,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return currentProcess.AddressArbiter.ArbitrateUnlock(mutexAddress);
}
#pragma warning restore CA1822
[Svc(0x1c)]
#pragma warning disable CA1822 // Mark member as static
public Result WaitProcessWideKeyAtomic(
[PointerSized] ulong mutexAddress,
[PointerSized] ulong condVarAddress,
@ -3056,10 +2991,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
handle,
timeout);
}
#pragma warning restore CA1822
[Svc(0x1d)]
#pragma warning disable CA1822 // Mark member as static
public Result SignalProcessWideKey([PointerSized] ulong address, int count)
{
KProcess currentProcess = KernelStatic.GetCurrentProcess();
@ -3068,10 +3001,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return Result.Success;
}
#pragma warning restore CA1822
[Svc(0x34)]
#pragma warning disable CA1822 // Mark member as static
public Result WaitForAddress([PointerSized] ulong address, ArbitrationType type, int value, long timeout)
{
if (IsPointingInsideKernel(address))
@ -3102,10 +3033,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
_ => KernelResult.InvalidEnumValue,
};
}
#pragma warning restore CA1822
[Svc(0x35)]
#pragma warning disable CA1822 // Mark member as static
public Result SignalToAddress([PointerSized] ulong address, SignalType type, int value, int count)
{
if (IsPointingInsideKernel(address))
@ -3131,17 +3060,14 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
_ => KernelResult.InvalidEnumValue,
};
}
#pragma warning restore CA1822
[Svc(0x36)]
#pragma warning disable CA1822 // Mark member as static
public Result SynchronizePreemptionState()
{
KernelStatic.GetCurrentThread().SynchronizePreemptionState();
return Result.Success;
}
#pragma warning restore CA1822
// Not actual syscalls, used by HLE services and such.

View File

@ -474,9 +474,9 @@ namespace Ryujinx.HLE.HOS.Services
{
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);

View File

@ -85,9 +85,9 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
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);

View File

@ -3,7 +3,6 @@ using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -13,7 +12,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
{
sealed class Parcel : IDisposable
{
private readonly IMemoryOwner<byte> _rawDataOwner;
private readonly MemoryOwner<byte> _rawDataOwner;
private Span<byte> Raw => _rawDataOwner.Memory.Span;
@ -30,7 +29,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
public Parcel(ReadOnlySpan<byte> data)
{
_rawDataOwner = ByteMemoryPool.RentCopy(data);
_rawDataOwner = MemoryOwner<byte>.RentCopy(data);
_payloadPosition = 0;
_objectPosition = 0;
@ -40,7 +39,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
{
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.ObjectsSize = objectsSize;

View File

@ -15,6 +15,12 @@ namespace Ryujinx.HLE.Loaders.Npdm
public ServiceAccessControl ServiceAccessControl { get; private set; }
public KernelAccessControl KernelAccessControl { get; private set; }
/// <exception cref="InvalidNpdmException">The stream doesn't contain valid ACI0 data.</exception>
/// <exception cref="System.ArgumentException">The stream does not support reading, is <see langword="null"/>, or is already closed.</exception>
/// <exception cref="EndOfStreamException">The end of the stream is reached.</exception>
/// <exception cref="System.ObjectDisposedException">The stream is closed.</exception>
/// <exception cref="IOException">An I/O error occurred.</exception>
/// <exception cref="System.NotImplementedException">The FsAccessHeader.ContentOwnerId section is not implemented.</exception>
public Aci0(Stream stream, int offset)
{
stream.Seek(offset, SeekOrigin.Begin);

View File

@ -19,6 +19,11 @@ namespace Ryujinx.HLE.Loaders.Npdm
public ServiceAccessControl ServiceAccessControl { get; private set; }
public KernelAccessControl KernelAccessControl { get; private set; }
/// <exception cref="InvalidNpdmException">The stream doesn't contain valid ACID data.</exception>
/// <exception cref="System.ArgumentException">The stream does not support reading, is <see langword="null"/>, or is already closed.</exception>
/// <exception cref="EndOfStreamException">The end of the stream is reached.</exception>
/// <exception cref="System.ObjectDisposedException">The stream is closed.</exception>
/// <exception cref="IOException">An I/O error occurred.</exception>
public Acid(Stream stream, int offset)
{
stream.Seek(offset, SeekOrigin.Begin);

View File

@ -11,6 +11,10 @@ namespace Ryujinx.HLE.Loaders.Npdm
public int Unknown3 { get; private set; }
public int Unknown4 { get; private set; }
/// <exception cref="System.ArgumentException">The stream does not support reading, is <see langword="null"/>, or is already closed.</exception>
/// <exception cref="EndOfStreamException">The end of the stream is reached.</exception>
/// <exception cref="System.ObjectDisposedException">The stream is closed.</exception>
/// <exception cref="IOException">An I/O error occurred.</exception>
public FsAccessControl(Stream stream, int offset, int size)
{
stream.Seek(offset, SeekOrigin.Begin);

View File

@ -9,6 +9,12 @@ namespace Ryujinx.HLE.Loaders.Npdm
public int Version { get; private set; }
public ulong PermissionsBitmask { get; private set; }
/// <exception cref="InvalidNpdmException">The stream contains invalid data.</exception>
/// <exception cref="NotImplementedException">The ContentOwnerId section is not implemented.</exception>
/// <exception cref="ArgumentException">The stream does not support reading, is <see langword="null"/>, or is already closed.</exception>
/// <exception cref="EndOfStreamException">The end of the stream is reached.</exception>
/// <exception cref="ObjectDisposedException">The stream is closed.</exception>
/// <exception cref="IOException">An I/O error occurred.</exception>
public FsAccessHeader(Stream stream, int offset, int size)
{
stream.Seek(offset, SeekOrigin.Begin);

View File

@ -6,6 +6,10 @@ namespace Ryujinx.HLE.Loaders.Npdm
{
public int[] Capabilities { get; private set; }
/// <exception cref="System.ArgumentException">The stream does not support reading, is <see langword="null"/>, or is already closed.</exception>
/// <exception cref="EndOfStreamException">The end of the stream is reached.</exception>
/// <exception cref="System.ObjectDisposedException">The stream is closed.</exception>
/// <exception cref="IOException">An I/O error occurred.</exception>
public KernelAccessControl(Stream stream, int offset, int size)
{
stream.Seek(offset, SeekOrigin.Begin);

View File

@ -24,6 +24,13 @@ namespace Ryujinx.HLE.Loaders.Npdm
public Aci0 Aci0 { get; private set; }
public Acid Acid { get; private set; }
/// <exception cref="InvalidNpdmException">The stream doesn't contain valid NPDM data.</exception>
/// <exception cref="System.NotImplementedException">The FsAccessHeader.ContentOwnerId section is not implemented.</exception>
/// <exception cref="System.ArgumentException">The stream does not support reading, is <see langword="null"/>, or is already closed.</exception>
/// <exception cref="System.ArgumentException">An error occured while reading bytes from the stream.</exception>
/// <exception cref="EndOfStreamException">The end of the stream is reached.</exception>
/// <exception cref="System.ObjectDisposedException">The stream is closed.</exception>
/// <exception cref="IOException">An I/O error occurred.</exception>
public Npdm(Stream stream)
{
BinaryReader reader = new(stream);

View File

@ -9,6 +9,11 @@ namespace Ryujinx.HLE.Loaders.Npdm
{
public IReadOnlyDictionary<string, bool> Services { get; private set; }
/// <exception cref="System.ArgumentException">The stream does not support reading, is <see langword="null"/>, or is already closed.</exception>
/// <exception cref="System.ArgumentException">An error occured while reading bytes from the stream.</exception>
/// <exception cref="EndOfStreamException">The end of the stream is reached.</exception>
/// <exception cref="System.ObjectDisposedException">The stream is closed.</exception>
/// <exception cref="IOException">An I/O error occurred.</exception>
public ServiceAccessControl(Stream stream, int offset, int size)
{
stream.Seek(offset, SeekOrigin.Begin);

View File

@ -3,7 +3,6 @@ using LibHac.FsSystem;
using LibHac.Loader;
using LibHac.Ncm;
using LibHac.Ns;
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.Loaders.Processes.Extensions;
namespace Ryujinx.HLE.Loaders.Processes

View File

@ -7,16 +7,25 @@ using LibHac.Ncm;
using LibHac.Ns;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using LibHac.Tools.Ncm;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.Utilities;
using System.IO;
using System.Linq;
using ApplicationId = LibHac.Ncm.ApplicationId;
using ContentType = LibHac.Ncm.ContentType;
using Path = System.IO.Path;
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)
{
// Extract RomFs and ExeFs from NCA.
@ -47,7 +56,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
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.
(_, 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;
}
public static ulong GetProgramIdBase(this Nca nca)
{
return nca.Header.TitleId & ~0x1FFFUL;
}
public static int GetProgramIndex(this Nca nca)
{
return (int)(nca.Header.TitleId & 0xF);
@ -96,6 +110,11 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
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)
{
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
@ -108,6 +127,43 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
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, titleIdBase.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)
{
IFileSystem exeFs = null;
@ -172,5 +228,31 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
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;
}
}
}

View File

@ -1,26 +1,58 @@
using LibHac.Common;
using LibHac.Common.Keys;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Ncm;
using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using LibHac.Tools.Ncm;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using ContentType = LibHac.Ncm.ContentType;
namespace Ryujinx.HLE.Loaders.Processes.Extensions
{
public static class PartitionFileSystemExtensions
{
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 TFormat : IPartitionFileSystemFormat
where THeader : unmanaged, IPartitionFileSystemHeader
@ -35,31 +67,22 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
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.
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
if (applicationId == 0)
{
Nca nca = partitionFileSystem.GetNca(device, fileEntry.FullPath);
if (nca.GetProgramIndex() != device.Configuration.UserChannelPersistence.Index)
foreach ((ulong _, ContentMetaData content) in applications)
{
continue;
}
if (nca.IsPatch())
{
patchNca = nca;
}
else if (nca.IsProgram())
{
mainNca = nca;
}
else if (nca.IsControl())
{
controlNca = nca;
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;
}
}
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();
}
@ -79,54 +102,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
return (false, ProcessResult.Failed);
}
// Load Update NCAs.
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;
}
}
}
}
}
(Nca updatePatchNca, Nca updateControlNca) = mainNca.GetUpdateData(device.FileSystem, device.System.FsIntegrityCheckLevel, device.Configuration.UserChannelPersistence.Index, out string _);
if (updatePatchNca != null)
{
@ -138,13 +114,11 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
controlNca = updateControlNca;
}
// Load contained DownloadableContents.
// TODO: If we want to support multi-processes in future, we shouldn't clear AddOnContent data here.
device.Configuration.ContentManager.ClearAocData();
device.Configuration.ContentManager.AddAocData(partitionFileSystem, path, mainNca.Header.TitleId, device.Configuration.FsIntegrityCheckLevel);
// 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.GetProgramIdBase().ToString("x16"), "dlc.json");
if (File.Exists(addOnContentMetadataPath))
{
List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile(addOnContentMetadataPath, _contentSerializerContext.ListDownloadableContentContainer);
@ -153,9 +127,12 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
{
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
{
@ -168,18 +145,18 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
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);
}
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>();
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());
}
}
}

View File

@ -32,7 +32,7 @@ namespace Ryujinx.HLE.Loaders.Processes
_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);
Xci xci = new(_device.Configuration.VirtualFileSystem.KeySet, stream.AsStorage());
@ -44,7 +44,7 @@ namespace Ryujinx.HLE.Loaders.Processes
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)
{
@ -66,13 +66,13 @@ namespace Ryujinx.HLE.Loaders.Processes
return false;
}
public bool LoadNsp(string path)
public bool LoadNsp(string path, ulong applicationId)
{
FileStream file = new(path, FileMode.Open, FileAccess.Read);
PartitionFileSystem partitionFileSystem = new();
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)
{

View File

@ -43,15 +43,14 @@ namespace Ryujinx.HLE.Loaders.Processes
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;
}
ulong currentProgramId = nca.Header.TitleId;
ulong currentMainProgramId = currentProgramId & ~0xFFFul;
ulong currentMainProgramId = nca.GetProgramIdBase();
if (applicationId == 0 && currentMainProgramId != 0)
{
@ -68,7 +67,7 @@ namespace Ryujinx.HLE.Loaders.Processes
break;
}
hasIndex[(int)(currentProgramId & 0xF)] = true;
hasIndex[nca.GetProgramIndex()] = true;
}
if (programCount == 0)

View File

@ -73,9 +73,9 @@ namespace Ryujinx.HLE
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)
@ -83,9 +83,9 @@ namespace Ryujinx.HLE
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)

View 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;
}
}
}

View File

@ -26,7 +26,11 @@ namespace Ryujinx.Horizon.Ngc.Ipc
}
[CmifCommand(1)]
public Result Check(out uint checkMask, ReadOnlySpan<byte> text, uint regionMask, ProfanityFilterOption option)
public Result Check(
out uint checkMask,
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> text,
uint regionMask,
ProfanityFilterOption option)
{
lock (_profanityFilter)
{

View File

@ -21,6 +21,8 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
public long CurrentTime { get; private set; }
public IEnumerable<MultiWaitHolderBase> MultiWaits => _multiWaits;
public MultiWaitImpl()
{
_multiWaits = new List<MultiWaitHolderBase>();

View File

@ -1,4 +1,5 @@
using Ryujinx.Horizon.Sdk.OsTypes.Impl;
using System.Collections.Generic;
namespace Ryujinx.Horizon.Sdk.OsTypes
{
@ -6,6 +7,8 @@ namespace Ryujinx.Horizon.Sdk.OsTypes
{
private readonly MultiWaitImpl _impl;
public IEnumerable<MultiWaitHolderBase> MultiWaits => _impl.MultiWaits;
public MultiWait()
{
_impl = new MultiWaitImpl();

View File

@ -3,6 +3,7 @@ using Ryujinx.Horizon.Sdk.OsTypes;
using Ryujinx.Horizon.Sdk.Sf.Cmif;
using Ryujinx.Horizon.Sdk.Sm;
using System;
using System.Linq;
namespace Ryujinx.Horizon.Sdk.Sf.Hipc
{
@ -116,6 +117,18 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
while (WaitAndProcessRequestsImpl())
{
}
// Unlink pending sessions, dispose expects them to be already unlinked.
ServerSession[] serverSessions = Enumerable.OfType<ServerSession>(_multiWait.MultiWaits).ToArray();
foreach (ServerSession serverSession in serverSessions)
{
if (serverSession.IsLinked)
{
serverSession.UnlinkFromMultiWaitHolder();
}
}
}
public void WaitAndProcessRequests()

View File

@ -9,9 +9,11 @@ using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.Loaders.Processes.Extensions;
using Ryujinx.UI.Common.Helper;
using System;
using System.IO;
using System.Text.Json.Serialization;
namespace Ryujinx.UI.App.Common
{
@ -19,10 +21,10 @@ namespace Ryujinx.UI.App.Common
{
public bool Favorite { get; set; }
public byte[] Icon { get; set; }
public string TitleName { get; set; }
public string TitleId { get; set; }
public string Developer { get; set; }
public string Version { get; set; }
public string Name { get; set; } = "Unknown";
public ulong Id { get; set; }
public string Developer { get; set; } = "Unknown";
public string Version { get; set; } = "0";
public TimeSpan TimePlayed { get; set; }
public DateTime? LastPlayed { get; set; }
public string FileExtension { get; set; }
@ -36,7 +38,13 @@ namespace Ryujinx.UI.App.Common
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;
[JsonIgnore] public string IdBaseString => IdBase.ToString("x16");
public static string GetBuildId(VirtualFileSystem virtualFileSystem, IntegrityCheckLevel checkLevel, string titleFilePath)
{
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
@ -105,7 +113,7 @@ namespace Ryujinx.UI.App.Common
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)
{

File diff suppressed because it is too large Load Diff

View File

@ -104,8 +104,13 @@ namespace Ryujinx.UI.Common
// Find the length to trim the string to guarantee we have space for the trailing ellipsis.
int trimLimit = byteLimit - Encoding.UTF8.GetByteCount(Ellipsis);
// Basic trim to best case scenario of 1 byte characters.
input = input[..trimLimit];
// Make sure the string is long enough to perform the basic trim.
// Amount of bytes != Length of the string
if (input.Length > trimLimit)
{
// Basic trim to best case scenario of 1 byte characters.
input = input[..trimLimit];
}
while (Encoding.UTF8.GetByteCount(input) > trimLimit)
{

View File

@ -14,6 +14,7 @@ namespace Ryujinx.UI.Common.Helper
public static string BaseDirPathArg { get; private set; }
public static string Profile { 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 void ParseArguments(string[] args)
@ -72,6 +73,10 @@ namespace Ryujinx.UI.Common.Helper
OverrideGraphicsBackend = args[++i];
break;
case "-i":
case "--application-id":
LaunchApplicationId = args[++i];
break;
case "--docked-mode":
OverrideDockedMode = true;
break;

View File

@ -15,7 +15,7 @@ namespace Ryujinx.UI.Common.Helper
public static class ShortcutHelper
{
[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");
iconPath += ".ico";
@ -25,13 +25,13 @@ namespace Ryujinx.UI.Common.Helper
image.Mutate(x => x.Resize(128, 128));
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.WriteToFile(Path.Combine(desktopPath, cleanedAppName + ".lnk"));
}
[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");
var desktopFile = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-template.desktop");
@ -41,11 +41,11 @@ namespace Ryujinx.UI.Common.Helper
image.SaveAsPng(iconPath);
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")]
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");
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);
using StreamWriter scriptFile = new(scriptPath);
scriptFile.Write(shortcutScript, basePath, GetArgsString(appFilePath));
scriptFile.Write(shortcutScript, basePath, GetArgsString(appFilePath, applicationId));
// Set execute permission
FileInfo fileInfo = new(scriptPath);
@ -95,7 +95,7 @@ namespace Ryujinx.UI.Common.Helper
{
string iconPath = Path.Combine(AppDataManager.BaseDirPath, "games", applicationId, "app");
CreateShortcutWindows(applicationFilePath, iconData, iconPath, cleanedAppName, desktopPath);
CreateShortcutWindows(applicationFilePath, applicationId, iconData, iconPath, cleanedAppName, desktopPath);
return;
}
@ -105,14 +105,14 @@ namespace Ryujinx.UI.Common.Helper
string iconPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "icons", "Ryujinx");
Directory.CreateDirectory(iconPath);
CreateShortcutLinux(applicationFilePath, iconData, Path.Combine(iconPath, applicationId), desktopPath, cleanedAppName);
CreateShortcutLinux(applicationFilePath, applicationId, iconData, Path.Combine(iconPath, applicationId), desktopPath, cleanedAppName);
return;
}
if (OperatingSystem.IsMacOS())
{
CreateShortcutMacos(applicationFilePath, iconData, desktopPath, cleanedAppName);
CreateShortcutMacos(applicationFilePath, applicationId, iconData, desktopPath, cleanedAppName);
return;
}
@ -120,7 +120,7 @@ namespace Ryujinx.UI.Common.Helper
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
var argsList = new List<string>();
@ -131,6 +131,12 @@ namespace Ryujinx.UI.Common.Helper
argsList.Add($"\"{CommandLineState.BaseDirPathArg}\"");
}
if (appFilePath.ToLower().EndsWith(".xci"))
{
argsList.Add("--application-id");
argsList.Add($"\"{applicationId}\"");
}
argsList.Add($"\"{appFilePath}\"");
return String.Join(" ", argsList);

View File

@ -40,20 +40,17 @@ using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper;
using Silk.NET.Vulkan;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SkiaSharp;
using SPB.Graphics.Vulkan;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
using Image = SixLabors.ImageSharp.Image;
using InputManager = Ryujinx.Input.HLE.InputManager;
using IRenderer = Ryujinx.Graphics.GAL.IRenderer;
using Key = Ryujinx.Input.Key;
@ -135,12 +132,14 @@ namespace Ryujinx.Ava
public int Width { get; private set; }
public int Height { get; private set; }
public string ApplicationPath { get; private set; }
public ulong ApplicationId { get; private set; }
public bool ScreenshotRequested { get; set; }
public AppHost(
RendererHost renderer,
InputManager inputManager,
string applicationPath,
ulong applicationId,
VirtualFileSystem virtualFileSystem,
ContentManager contentManager,
AccountManager accountManager,
@ -164,6 +163,7 @@ namespace Ryujinx.Ava
NpadManager = _inputManager.CreateNpadManager();
TouchScreenManager = _inputManager.CreateTouchScreenManager();
ApplicationPath = applicationPath;
ApplicationId = applicationId;
VirtualFileSystem = virtualFileSystem;
ContentManager = contentManager;
@ -366,25 +366,25 @@ namespace Ryujinx.Ava
return;
}
Image image = e.IsBgra ? Image.LoadPixelData<Bgra32>(e.Data, e.Width, e.Height)
: Image.LoadPixelData<Rgba32>(e.Data, e.Width, e.Height);
var colorType = e.IsBgra ? SKColorType.Bgra8888 : SKColorType.Rgba8888;
using SKBitmap bitmap = new SKBitmap(new SKImageInfo(e.Width, e.Height, colorType, SKAlphaType.Premul));
if (e.FlipX)
{
image.Mutate(x => x.Flip(FlipMode.Horizontal));
}
Marshal.Copy(e.Data, 0, bitmap.GetPixels(), e.Data.Length);
if (e.FlipY)
{
image.Mutate(x => x.Flip(FlipMode.Vertical));
}
using SKBitmap bitmapToSave = new SKBitmap(bitmap.Width, bitmap.Height);
using SKCanvas canvas = new SKCanvas(bitmapToSave);
image.SaveAsPng(path, new PngEncoder
{
ColorType = PngColorType.Rgb,
});
canvas.Clear(SKColors.Black);
image.Dispose();
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, SKPoint.Empty);
SaveBitmapAsPng(bitmapToSave, path);
Logger.Notice.Print(LogClass.Application, $"Screenshot saved to {path}", "Screenshot");
}
@ -396,6 +396,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()
{
if (OperatingSystem.IsWindows())
@ -706,7 +714,7 @@ namespace Ryujinx.Ava
{
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
if (!Device.LoadXci(ApplicationPath))
if (!Device.LoadXci(ApplicationPath, ApplicationId))
{
Device.Dispose();
@ -733,7 +741,7 @@ namespace Ryujinx.Ava
{
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
if (!Device.LoadNsp(ApplicationPath))
if (!Device.LoadNsp(ApplicationPath, ApplicationId))
{
Device.Dispose();

View File

@ -10,6 +10,7 @@
"SettingsTabSystemUseHypervisor": "Use Hypervisor",
"MenuBarFile": "_File",
"MenuBarFileOpenFromFile": "_Load Application From File",
"MenuBarFileOpenFromFileError": "No applications found in selected file.",
"MenuBarFileOpenUnpacked": "Load _Unpacked Game",
"MenuBarFileOpenEmuFolder": "Open Ryujinx Folder",
"MenuBarFileOpenLogsFolder": "Open Logs Folder",
@ -649,6 +650,8 @@
"OpenSetupGuideMessage": "Open the Setup Guide",
"NoUpdate": "No Update",
"TitleUpdateVersionLabel": "Version {0}",
"TitleBundledUpdateVersionLabel": "Bundled: Version {0}",
"TitleBundledDlcLabel": "Bundled:",
"RyujinxInfo": "Ryujinx - Info",
"RyujinxConfirm": "Ryujinx - Confirmation",
"FileDialogAllTypes": "All types",

View File

@ -18,7 +18,8 @@ using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
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 System;
using System.Buffers;
@ -226,7 +227,11 @@ namespace Ryujinx.Ava.Common
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)
{
patchNca = updatePatchNca;

View File

@ -125,7 +125,7 @@ namespace Ryujinx.Ava
if (CommandLineState.LaunchPathArg != null)
{
MainWindow.DeferLoadApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg);
MainWindow.DeferLoadApplication(CommandLineState.LaunchPathArg, CommandLineState.LaunchApplicationId, CommandLineState.StartFullscreenArg);
}
}

View File

@ -54,7 +54,6 @@
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
<PackageReference Include="SPB" />
<PackageReference Include="SharpZipLib" />
<PackageReference Include="SixLabors.ImageSharp" />
</ItemGroup>
<ItemGroup>

View File

@ -1,7 +1,6 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using LibHac.Fs;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common;
@ -15,7 +14,6 @@ using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common.Helper;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using Path = System.IO.Path;
@ -41,7 +39,7 @@ namespace Ryujinx.Ava.UI.Controls
{
viewModel.SelectedApplication.Favorite = !viewModel.SelectedApplication.Favorite;
ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.TitleId, appMetadata =>
ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.IdString, appMetadata =>
{
appMetadata.Favorite = viewModel.SelectedApplication.Favorite;
});
@ -76,19 +74,9 @@ namespace Ryujinx.Ava.UI.Controls
{
if (viewModel?.SelectedApplication != null)
{
if (!ulong.TryParse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
{
Dispatcher.UIThread.InvokeAsync(async () =>
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogRyujinxErrorMessage], LocaleManager.Instance[LocaleKeys.DialogInvalidTitleIdErrorMessage]);
});
var saveDataFilter = SaveDataFilter.Make(viewModel.SelectedApplication.Id, saveDataType, userId, saveDataId: default, index: default);
return;
}
var saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveDataType, userId, saveDataId: default, index: default);
ApplicationHelper.OpenSaveDir(in saveDataFilter, titleIdNumber, viewModel.SelectedApplication.ControlHolder, viewModel.SelectedApplication.TitleName);
ApplicationHelper.OpenSaveDir(in saveDataFilter, viewModel.SelectedApplication.Id, viewModel.SelectedApplication.ControlHolder, viewModel.SelectedApplication.Name);
}
}
@ -98,7 +86,7 @@ namespace Ryujinx.Ava.UI.Controls
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)
{
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(
viewModel.VirtualFileSystem,
viewModel.SelectedApplication.TitleId,
viewModel.SelectedApplication.TitleName,
viewModel.SelectedApplication.IdString,
viewModel.SelectedApplication.Name,
viewModel.SelectedApplication.Path).ShowDialog(viewModel.TopLevel as Window);
}
}
@ -133,7 +121,7 @@ namespace Ryujinx.Ava.UI.Controls
if (viewModel?.SelectedApplication != null)
{
string modsBasePath = ModLoader.GetModsBasePath();
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, viewModel.SelectedApplication.TitleId);
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, viewModel.SelectedApplication.IdString);
OpenHelper.OpenFolder(titleModsPath);
}
@ -146,7 +134,7 @@ namespace Ryujinx.Ava.UI.Controls
if (viewModel?.SelectedApplication != null)
{
string sdModsBasePath = ModLoader.GetSdModsBasePath();
string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, viewModel.SelectedApplication.TitleId);
string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, viewModel.SelectedApplication.IdString);
OpenHelper.OpenFolder(titleModsPath);
}
@ -158,7 +146,7 @@ namespace Ryujinx.Ava.UI.Controls
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(
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.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
if (result == UserResult.Yes)
{
DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "cpu", "0"));
DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "cpu", "1"));
DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "0"));
DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "1"));
List<FileInfo> cacheFiles = new();
@ -218,14 +206,14 @@ namespace Ryujinx.Ava.UI.Controls
{
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
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.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
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<FileInfo> newCacheFiles = new();
@ -273,7 +261,7 @@ namespace Ryujinx.Ava.UI.Controls
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 backupDir = Path.Combine(ptcDir, "1");
@ -294,7 +282,7 @@ namespace Ryujinx.Ava.UI.Controls
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))
{
@ -315,7 +303,7 @@ namespace Ryujinx.Ava.UI.Controls
viewModel.StorageProvider,
NcaSectionType.Code,
viewModel.SelectedApplication.Path,
viewModel.SelectedApplication.TitleName);
viewModel.SelectedApplication.Name);
}
}
@ -329,7 +317,7 @@ namespace Ryujinx.Ava.UI.Controls
viewModel.StorageProvider,
NcaSectionType.Data,
viewModel.SelectedApplication.Path,
viewModel.SelectedApplication.TitleName);
viewModel.SelectedApplication.Name);
}
}
@ -343,7 +331,7 @@ namespace Ryujinx.Ava.UI.Controls
viewModel.StorageProvider,
NcaSectionType.Logo,
viewModel.SelectedApplication.Path,
viewModel.SelectedApplication.TitleName);
viewModel.SelectedApplication.Name);
}
}
@ -354,7 +342,7 @@ namespace Ryujinx.Ava.UI.Controls
if (viewModel?.SelectedApplication != null)
{
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)
{
await viewModel.LoadApplication(viewModel.SelectedApplication.Path);
await viewModel.LoadApplication(viewModel.SelectedApplication);
}
}
}

View File

@ -80,7 +80,7 @@
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding TitleName}"
Text="{Binding Name}"
TextAlignment="Center"
TextWrapping="Wrap" />
</Panel>

View File

@ -85,7 +85,7 @@
<TextBlock
HorizontalAlignment="Stretch"
FontWeight="Bold"
Text="{Binding TitleName}"
Text="{Binding Name}"
TextAlignment="Start"
TextWrapping="Wrap" />
<TextBlock
@ -109,7 +109,7 @@
Spacing="5">
<TextBlock
HorizontalAlignment="Stretch"
Text="{Binding TitleId}"
Text="{Binding Id, StringFormat=X16}"
TextAlignment="Start"
TextWrapping="Wrap" />
<TextBlock

View File

@ -1,3 +1,4 @@
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.ViewModels;
using System.IO;
@ -24,6 +25,9 @@ namespace Ryujinx.Ava.UI.Models
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)
{
TitleId = titleId;

View File

@ -46,14 +46,14 @@ namespace Ryujinx.Ava.UI.Models
TitleId = info.ProgramId;
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;
if (InGameList)
{
Icon = appData.Icon;
Title = appData.TitleName;
Title = appData.Name;
}
else
{

View File

@ -8,7 +8,10 @@ namespace Ryujinx.Ava.UI.Models
public ApplicationControlProperty Control { 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)
{

View File

@ -6,7 +6,6 @@ using DynamicData;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
@ -17,11 +16,13 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.Loaders.Processes.Extensions;
using Ryujinx.HLE.Utilities;
using Ryujinx.UI.App.Common;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Application = Avalonia.Application;
using Path = System.IO.Path;
@ -38,7 +39,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private AvaloniaList<DownloadableContentModel> _selectedDownloadableContents = new();
private string _search;
private readonly ulong _titleId;
private readonly ApplicationData _applicationData;
private readonly IStorageProvider _storageProvider;
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);
}
public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ulong titleId)
public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
{
_virtualFileSystem = virtualFileSystem;
_titleId = titleId;
_applicationData = applicationData;
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
_storageProvider = desktop.MainWindow.StorageProvider;
}
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, applicationData.IdBaseString, "dlc.json");
if (!File.Exists(_downloadableContentJsonPath))
{
_downloadableContentContainerList = new List<DownloadableContentContainer>();
Save();
}
try
{
@ -123,12 +131,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
if (File.Exists(downloadableContentContainer.ContainerPath))
{
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
PartitionFileSystem partitionFileSystem = new();
partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
_virtualFileSystem.ImportTickets(partitionFileSystem);
using IFileSystem partitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(downloadableContentContainer.ContainerPath, _virtualFileSystem);
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.
Save();
Sort();
@ -219,25 +225,23 @@ namespace Ryujinx.Ava.UI.ViewModels
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);
PartitionFileSystem partitionFileSystem = new();
partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
bool containsDownloadableContent = false;
_virtualFileSystem.ImportTickets(partitionFileSystem);
using IFileSystem partitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(path, _virtualFileSystem);
bool success = false;
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
{
using var ncaFile = new UniqueRef<IFile>();
@ -252,26 +256,26 @@ namespace Ryujinx.Ava.UI.ViewModels
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);
DownloadableContents.Add(content);
SelectedDownloadableContents.Add(content);
Dispatcher.UIThread.InvokeAsync(() => SelectedDownloadableContents.Add(content));
OnPropertyChanged(nameof(UpdateCount));
Sort();
containsDownloadableContent = true;
success = true;
}
}
if (!containsDownloadableContent)
if (success)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]);
OnPropertyChanged(nameof(UpdateCount));
Sort();
}
return success;
}
public void Remove(DownloadableContentModel model)

View File

@ -32,7 +32,7 @@ using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper;
using SixLabors.ImageSharp.PixelFormats;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@ -40,7 +40,6 @@ using System.Globalization;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Image = SixLabors.ImageSharp.Image;
using Key = Ryujinx.Input.Key;
using MissingKeyException = LibHac.Common.Keys.MissingKeyException;
using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState;
@ -97,7 +96,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private bool _canUpdate = true;
private Cursor _cursor;
private string _title;
private string _currentEmulatedGamePath;
private ApplicationData _currentApplicationData;
private readonly AutoResetEvent _rendererWaitEvent;
private WindowState _windowState;
private double _windowWidth;
@ -109,7 +108,6 @@ namespace Ryujinx.Ava.UI.ViewModels
public ApplicationData ListSelectedApplication;
public ApplicationData GridSelectedApplication;
private string TitleName { get; set; }
internal AppHost AppHost { get; set; }
public MainWindowViewModel()
@ -955,8 +953,8 @@ namespace Ryujinx.Ava.UI.ViewModels
return SortMode switch
{
#pragma warning disable IDE0055 // Disable formatting
ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName)
: SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName),
ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Name)
: SortExpressionComparer<ApplicationData>.Descending(app => app.Name),
ApplicationSort.Developer => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer)
: SortExpressionComparer<ApplicationData>.Descending(app => app.Developer),
ApplicationSort.LastPlayed => new LastPlayedSortComparer(IsAscending),
@ -1000,7 +998,7 @@ namespace Ryujinx.Ava.UI.ViewModels
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;
@ -1129,7 +1127,7 @@ namespace Ryujinx.Ava.UI.ViewModels
IsLoadingIndeterminate = false;
break;
case LoadState.Loaded:
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, TitleName);
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, _currentApplicationData.Name);
IsLoadingIndeterminate = true;
CacheLoadStatus = "";
break;
@ -1149,7 +1147,7 @@ namespace Ryujinx.Ava.UI.ViewModels
IsLoadingIndeterminate = false;
break;
case ShaderCacheLoadingState.Loaded:
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, TitleName);
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, _currentApplicationData.Name);
IsLoadingIndeterminate = true;
CacheLoadStatus = "";
break;
@ -1164,17 +1162,17 @@ namespace Ryujinx.Ava.UI.ViewModels
private void PrepareLoadScreen()
{
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;
Color progressFgColor = Color.FromRgb(dominantColor.R, dominantColor.G, dominantColor.B);
Color progressFgColor = Color.FromRgb(dominantColor.Red, dominantColor.Green, dominantColor.Blue);
Color progressBgColor = Color.FromRgb(
(byte)(dominantColor.R * ColorMultiple),
(byte)(dominantColor.G * ColorMultiple),
(byte)(dominantColor.B * ColorMultiple));
(byte)(dominantColor.Red * ColorMultiple),
(byte)(dominantColor.Green * ColorMultiple),
(byte)(dominantColor.Blue * ColorMultiple));
ProgressBarForegroundColor = new SolidColorBrush(progressFgColor);
ProgressBarBackgroundColor = new SolidColorBrush(progressBgColor);
@ -1201,13 +1199,13 @@ namespace Ryujinx.Ava.UI.ViewModels
{
UserChannelPersistence.ShouldRestart = false;
await LoadApplication(_currentEmulatedGamePath);
await LoadApplication(_currentApplicationData);
}
else
{
// Otherwise, clear state.
UserChannelPersistence = new UserChannelPersistence();
_currentEmulatedGamePath = null;
_currentApplicationData = null;
}
}
@ -1494,7 +1492,15 @@ namespace Ryujinx.Ava.UI.ViewModels
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)
{
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)
{
@ -1532,7 +1544,7 @@ namespace Ryujinx.Ava.UI.ViewModels
Logger.RestartTime();
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(path, ConfigurationState.Instance.System.Language);
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(application.Path, ConfigurationState.Instance.System.Language, application.Id);
PrepareLoadScreen();
@ -1541,7 +1553,8 @@ namespace Ryujinx.Ava.UI.ViewModels
AppHost = new AppHost(
RendererHostControl,
InputManager,
path,
application.Path,
application.Id,
VirtualFileSystem,
ContentManager,
AccountManager,
@ -1559,17 +1572,17 @@ namespace Ryujinx.Ava.UI.ViewModels
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);
TitleName = AppHost.Device.Processes.ActiveApplication.Name;
application.Name = AppHost.Device.Processes.ActiveApplication.Name;
}
SwitchToRenderer(startFullscreen);
_currentEmulatedGamePath = path;
_currentApplicationData = application;
Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" };
gameThread.Start();

View File

@ -1,4 +1,3 @@
using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform.Storage;
@ -6,7 +5,7 @@ using Avalonia.Threading;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Ncm;
using LibHac.Ns;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
@ -17,12 +16,17 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.Loaders.Processes.Extensions;
using Ryujinx.HLE.Utilities;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Application = Avalonia.Application;
using ContentType = LibHac.Ncm.ContentType;
using Path = System.IO.Path;
using SpanHelpers = LibHac.Common.SpanHelpers;
@ -33,7 +37,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public TitleUpdateMetadata TitleUpdateWindowData;
public readonly string TitleUpdateJsonPath;
private VirtualFileSystem VirtualFileSystem { get; }
private ulong TitleId { get; }
private ApplicationData ApplicationData { get; }
private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
private AvaloniaList<object> _views = new();
@ -73,18 +77,18 @@ namespace Ryujinx.Ava.UI.ViewModels
public IStorageProvider StorageProvider;
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId)
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
{
VirtualFileSystem = virtualFileSystem;
TitleId = titleId;
ApplicationData = applicationData;
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
StorageProvider = desktop.MainWindow.StorageProvider;
}
TitleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
TitleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, ApplicationData.IdBaseString, "updates.json");
try
{
@ -92,7 +96,7 @@ namespace Ryujinx.Ava.UI.ViewModels
}
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.IdBaseString} at {TitleUpdateJsonPath}");
TitleUpdateWindowData = new TitleUpdateMetadata
{
@ -108,6 +112,9 @@ namespace Ryujinx.Ava.UI.ViewModels
private void LoadUpdates()
{
// Try to load updates from PFS first
AddUpdate(ApplicationData.Path, true);
foreach (string path in TitleUpdateWindowData.Paths)
{
AddUpdate(path);
@ -162,38 +169,60 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
private void AddUpdate(string path)
private void AddUpdate(string path, bool ignoreNotFound = false, bool selected = 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();
pfs.Initialize(file.AsStorage()).ThrowIfFailure();
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(VirtualFileSystem, pfs, TitleId.ToString("x16"), 0);
patchNca = content.GetNcaByType(VirtualFileSystem.KeySet, ContentType.Program);
controlNca = content.GetNcaByType(VirtualFileSystem.KeySet, ContentType.Control);
}
if (controlNca != null && patchNca != null)
if (controlNca != null && patchNca != null)
{
ApplicationControlProperty controlData = new();
using UniqueRef<IFile> nacpFile = new();
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();
var update = new TitleUpdateModel(controlData, path);
TitleUpdates.Add(update);
if (selected)
{
ApplicationControlProperty controlData = new();
using UniqueRef<IFile> nacpFile = new();
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();
TitleUpdates.Add(new TitleUpdateModel(controlData, path));
Dispatcher.UIThread.InvokeAsync(() => SelectedUpdate = update);
}
else
}
else
{
if (!ignoreNotFound)
{
Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]));
}
}
catch (Exception ex)
{
Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadFileErrorMessage, ex.Message, path)));
}
}
catch (Exception ex)
{
Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadFileErrorMessage, ex.Message, path)));
}
}
@ -222,7 +251,7 @@ namespace Ryujinx.Ava.UI.ViewModels
foreach (var file in result)
{
AddUpdate(file.Path.LocalPath);
AddUpdate(file.Path.LocalPath, selected: true);
}
SortUpdates();

View File

@ -9,14 +9,14 @@ using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.UI.Models;
using Ryujinx.HLE.FileSystem;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SkiaSharp;
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using Color = Avalonia.Media.Color;
using Image = SkiaSharp.SKImage;
namespace Ryujinx.Ava.UI.ViewModels
{
@ -130,9 +130,12 @@ namespace Ryujinx.Ava.UI.ViewModels
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());
}

View File

@ -11,6 +11,7 @@ using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Utilities;
using Ryujinx.Modules;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper;
@ -134,7 +135,14 @@ namespace Ryujinx.Ava.UI.Views.Main
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);
}
}

View File

@ -6,12 +6,8 @@ using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.HLE.FileSystem;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SkiaSharp;
using System.IO;
using Image = SixLabors.ImageSharp.Image;
namespace Ryujinx.Ava.UI.Views.User
{
@ -70,15 +66,25 @@ namespace Ryujinx.Ava.UI.Views.User
{
if (ViewModel.SelectedImage != null)
{
MemoryStream streamJpg = new();
Image avatarImage = Image.Load(ViewModel.SelectedImage, new PngDecoder());
using var streamJpg = new MemoryStream();
using var bitmap = SKBitmap.Decode(ViewModel.SelectedImage);
using var newBitmap = new SKBitmap(bitmap.Width, bitmap.Height);
avatarImage.Mutate(x => x.BackgroundColor(new Rgba32(
ViewModel.BackgroundColor.R,
ViewModel.BackgroundColor.G,
ViewModel.BackgroundColor.B,
ViewModel.BackgroundColor.A)));
avatarImage.SaveAsJpeg(streamJpg);
using (var canvas = new SKCanvas(newBitmap))
{
canvas.Clear(new SKColor(
ViewModel.BackgroundColor.R,
ViewModel.BackgroundColor.G,
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();

View File

@ -9,11 +9,9 @@ using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.HLE.FileSystem;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
using SkiaSharp;
using System.Collections.Generic;
using System.IO;
using Image = SixLabors.ImageSharp.Image;
namespace Ryujinx.Ava.UI.Views.User
{
@ -102,13 +100,19 @@ namespace Ryujinx.Ava.UI.Views.User
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();
}

View File

@ -1,9 +1,11 @@
using Avalonia.Collections;
using LibHac.Tools.FsSystem;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Models;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common.Configuration;
using System;
using System.Collections.Generic;
using System.Globalization;
@ -34,9 +36,12 @@ namespace Ryujinx.Ava.UI.Windows
public CheatWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName, string titlePath)
{
LoadedCheats = new AvaloniaList<CheatNode>();
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
? IntegrityCheckLevel.ErrorOnInvalid
: IntegrityCheckLevel.None;
Heading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.CheatWindowHeading, titleName, titleId.ToUpper());
BuildId = ApplicationData.GetApplicationBuildId(virtualFileSystem, titlePath);
BuildId = ApplicationData.GetBuildId(virtualFileSystem, checkLevel, titlePath);
InitializeComponent();

View File

@ -97,7 +97,7 @@
MaxLines="2"
TextWrapping="Wrap"
TextTrimming="CharacterEllipsis"
Text="{Binding FileName}" />
Text="{Binding Label}" />
<TextBlock
Grid.Column="1"
Margin="10 0"

View File

@ -3,13 +3,12 @@ using Avalonia.Interactivity;
using Avalonia.Styling;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.HLE.FileSystem;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common.Helper;
using System.Threading.Tasks;
using Button = Avalonia.Controls.Button;
namespace Ryujinx.Ava.UI.Windows
{
@ -24,22 +23,22 @@ namespace Ryujinx.Ava.UI.Windows
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();
}
public static async Task Show(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
public static async Task Show(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
{
ContentDialog contentDialog = new()
{
PrimaryButtonText = "",
SecondaryButtonText = "",
CloseButtonText = "",
Content = new DownloadableContentManagerWindow(virtualFileSystem, titleId),
Title = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowTitle], titleName, titleId.ToString("X16")),
Content = new DownloadableContentManagerWindow(virtualFileSystem, applicationData),
Title = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowTitle], applicationData.Name, applicationData.IdBaseString),
};
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());

View File

@ -1,5 +1,4 @@
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SkiaSharp;
using System;
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.
// If the color is too dark, make it brighter by reducing the range
// 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)
{
color = Color.FromRgb(
(byte)Math.Min(CutOffLuminosity + color.R, byte.MaxValue),
(byte)Math.Min(CutOffLuminosity + color.G, byte.MaxValue),
(byte)Math.Min(CutOffLuminosity + color.B, byte.MaxValue));
color = new SKColor(
(byte)Math.Min(CutOffLuminosity + color.Red, byte.MaxValue),
(byte)Math.Min(CutOffLuminosity + color.Green, byte.MaxValue),
(byte)Math.Min(CutOffLuminosity + color.Blue, byte.MaxValue));
}
return color;
}
public static Color GetColor(Image<Bgra32> image)
public static SKColor GetColor(SKBitmap image)
{
var colors = new PaletteColor[TotalColors];
var dominantColorBin = new Dictionary<int, int>();
var buffer = GetBuffer(image);
int w = image.Width;
int w8 = w << 8;
int h8 = image.Height << 8;
@ -84,9 +82,10 @@ namespace Ryujinx.Ava.UI.Windows
{
int offset = x + yOffset;
byte cb = buffer[offset].B;
byte cg = buffer[offset].G;
byte cr = buffer[offset].R;
SKColor pixel = buffer[offset];
byte cr = pixel.Red;
byte cg = pixel.Green;
byte cb = pixel.Blue;
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)

View File

@ -5,6 +5,7 @@ using Avalonia.Interactivity;
using Avalonia.Platform;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using LibHac.Tools.FsSystem;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input;
@ -24,7 +25,7 @@ using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper;
using System;
using System.IO;
using System.Collections.Generic;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
@ -40,6 +41,7 @@ namespace Ryujinx.Ava.UI.Windows
private UserChannelPersistence _userChannelPersistence;
private static bool _deferLoad;
private static string _launchPath;
private static string _launchApplicationId;
private static bool _startFullscreen;
internal readonly AvaHostUIHandler UiHandler;
@ -168,18 +170,17 @@ namespace Ryujinx.Ava.UI.Windows
{
ViewModel.SelectedIcon = args.Application.Icon;
string path = new FileInfo(args.Application.Path).FullName;
ViewModel.LoadApplication(path).Wait();
ViewModel.LoadApplication(args.Application).Wait();
}
args.Handled = true;
}
internal static void DeferLoadApplication(string launchPathArg, bool startFullscreenArg)
internal static void DeferLoadApplication(string launchPathArg, string launchApplicationId, bool startFullscreenArg)
{
_deferLoad = true;
_launchPath = launchPathArg;
_launchApplicationId = launchApplicationId;
_startFullscreen = startFullscreenArg;
}
@ -219,7 +220,11 @@ namespace Ryujinx.Ava.UI.Windows
LibHacHorizonManager.InitializeBcatServer();
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
// 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;
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

View File

@ -5,19 +5,18 @@ using Avalonia.Interactivity;
using Avalonia.Styling;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.HLE.FileSystem;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common.Helper;
using System.Threading.Tasks;
using Button = Avalonia.Controls.Button;
namespace Ryujinx.Ava.UI.Windows
{
public partial class TitleUpdateWindow : UserControl
{
public TitleUpdateViewModel ViewModel;
public readonly TitleUpdateViewModel ViewModel;
public TitleUpdateWindow()
{
@ -26,22 +25,22 @@ namespace Ryujinx.Ava.UI.Windows
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();
}
public static async Task Show(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
public static async Task Show(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
{
ContentDialog contentDialog = new()
{
PrimaryButtonText = "",
SecondaryButtonText = "",
CloseButtonText = "",
Content = new TitleUpdateWindow(virtualFileSystem, titleId),
Title = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.GameUpdateWindowHeading, titleName, titleId.ToString("X16")),
Content = new TitleUpdateWindow(virtualFileSystem, applicationData),
Title = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.GameUpdateWindowHeading, applicationData.Name, applicationData.IdBaseString),
};
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());