From 9a28ba72b17caa89d1e60e22e1420046097701d0 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Wed, 24 Jan 2024 19:26:59 -0300 Subject: [PATCH 001/126] Use unix timestamps on GetFileTimeStampRaw (#6169) --- src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs b/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs index 43bd27761..0827266a1 100644 --- a/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs +++ b/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs @@ -186,7 +186,12 @@ private static string MakeFullPath(string path, bool isDirectory = true) public void InitializeFsServer(LibHac.Horizon horizon, out HorizonClient fsServerClient) { - LocalFileSystem serverBaseFs = new(AppDataManager.BaseDirPath); + LocalFileSystem serverBaseFs = new(useUnixTimeStamps: true); + Result result = serverBaseFs.Initialize(AppDataManager.BaseDirPath, LocalFileSystem.PathMode.DefaultCaseSensitivity, ensurePathExists: true); + if (result.IsFailure()) + { + throw new HorizonResultException(result, "Error creating LocalFileSystem."); + } fsServerClient = horizon.CreatePrivilegedHorizonClient(); var fsServer = new FileSystemServer(fsServerClient); From 65759524323166894564359a5046f1ec62a82773 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Wed, 24 Jan 2024 22:33:52 +0000 Subject: [PATCH 002/126] Vulkan: Enumerate Query Pool properly (#6167) Turns out that ElementAt for Queue runs the default implementation as it doesn't implement IList, which enumerates elements of the queue up to the given index. This code was creating `count` enumerators and iterating way more queue items than it needed to at higher counts. The solution is just to use one enumerator and break out of the loop when we get the count that we need. 3.5% of backend time was being spent _just_ enumerating at the usual spot in SMO. --- src/Ryujinx.Graphics.Vulkan/Queries/CounterQueue.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.Graphics.Vulkan/Queries/CounterQueue.cs b/src/Ryujinx.Graphics.Vulkan/Queries/CounterQueue.cs index 3984e2826..0d133e50e 100644 --- a/src/Ryujinx.Graphics.Vulkan/Queries/CounterQueue.cs +++ b/src/Ryujinx.Graphics.Vulkan/Queries/CounterQueue.cs @@ -67,9 +67,18 @@ public void ResetFutureCounters(CommandBuffer cmd, int count) lock (_queryPool) { count = Math.Min(count, _queryPool.Count); - for (int i = 0; i < count; i++) + + if (count > 0) { - _queryPool.ElementAt(i).PoolReset(cmd, ResetSequence); + foreach (BufferedQuery query in _queryPool) + { + query.PoolReset(cmd, ResetSequence); + + if (--count == 0) + { + break; + } + } } } } From 2ca70eb9a05e057ebc2122bd749c49b4dea5c523 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Wed, 24 Jan 2024 19:50:43 -0300 Subject: [PATCH 003/126] Implement SQSHL (immediate) CPU instruction (#6155) * Implement SQSHL (immediate) CPU instruction * Fix test --- src/ARMeilleure/Decoders/OpCodeTable.cs | 3 + .../Instructions/InstEmitSimdShift.cs | 105 +++++++++++- src/ARMeilleure/Instructions/InstName.cs | 2 + src/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs | 151 ++++++++++++++++++ 4 files changed, 260 insertions(+), 1 deletion(-) diff --git a/src/ARMeilleure/Decoders/OpCodeTable.cs b/src/ARMeilleure/Decoders/OpCodeTable.cs index 9e13bd9b5..528cef1b6 100644 --- a/src/ARMeilleure/Decoders/OpCodeTable.cs +++ b/src/ARMeilleure/Decoders/OpCodeTable.cs @@ -517,7 +517,10 @@ static OpCodeTable() SetA64("0x00111100>>>xxx100111xxxxxxxxxx", InstName.Sqrshrn_V, InstEmit.Sqrshrn_V, OpCodeSimdShImm.Create); SetA64("0111111100>>>xxx100011xxxxxxxxxx", InstName.Sqrshrun_S, InstEmit.Sqrshrun_S, OpCodeSimdShImm.Create); SetA64("0x10111100>>>xxx100011xxxxxxxxxx", InstName.Sqrshrun_V, InstEmit.Sqrshrun_V, OpCodeSimdShImm.Create); + SetA64("010111110>>>>xxx011101xxxxxxxxxx", InstName.Sqshl_Si, InstEmit.Sqshl_Si, OpCodeSimdShImm.Create); SetA64("0>001110<<1xxxxx010011xxxxxxxxxx", InstName.Sqshl_V, InstEmit.Sqshl_V, OpCodeSimdReg.Create); + SetA64("0000111100>>>xxx011101xxxxxxxxxx", InstName.Sqshl_Vi, InstEmit.Sqshl_Vi, OpCodeSimdShImm.Create); + SetA64("010011110>>>>xxx011101xxxxxxxxxx", InstName.Sqshl_Vi, InstEmit.Sqshl_Vi, OpCodeSimdShImm.Create); SetA64("0101111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_S, InstEmit.Sqshrn_S, OpCodeSimdShImm.Create); SetA64("0x00111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_V, InstEmit.Sqshrn_V, OpCodeSimdShImm.Create); SetA64("0111111100>>>xxx100001xxxxxxxxxx", InstName.Sqshrun_S, InstEmit.Sqshrun_S, OpCodeSimdShImm.Create); diff --git a/src/ARMeilleure/Instructions/InstEmitSimdShift.cs b/src/ARMeilleure/Instructions/InstEmitSimdShift.cs index be0670645..94e912579 100644 --- a/src/ARMeilleure/Instructions/InstEmitSimdShift.cs +++ b/src/ARMeilleure/Instructions/InstEmitSimdShift.cs @@ -116,7 +116,7 @@ public static void Shl_V(ArmEmitterContext context) } else if (shift >= eSize) { - if ((op.RegisterSize == RegisterSize.Simd64)) + if (op.RegisterSize == RegisterSize.Simd64) { Operand res = context.VectorZeroUpper64(GetVec(op.Rd)); @@ -359,6 +359,16 @@ public static void Sqshl_V(ArmEmitterContext context) } } + public static void Sqshl_Si(ArmEmitterContext context) + { + EmitShlImmOp(context, signedDst: true, ShlRegFlags.Signed | ShlRegFlags.Scalar | ShlRegFlags.Saturating); + } + + public static void Sqshl_Vi(ArmEmitterContext context) + { + EmitShlImmOp(context, signedDst: true, ShlRegFlags.Signed | ShlRegFlags.Saturating); + } + public static void Sqshrn_S(ArmEmitterContext context) { if (Optimizations.UseAdvSimd) @@ -1593,6 +1603,99 @@ private enum ShlRegFlags Saturating = 1 << 3, } + private static void EmitShlImmOp(ArmEmitterContext context, bool signedDst, ShlRegFlags flags = ShlRegFlags.None) + { + bool scalar = flags.HasFlag(ShlRegFlags.Scalar); + bool signed = flags.HasFlag(ShlRegFlags.Signed); + bool saturating = flags.HasFlag(ShlRegFlags.Saturating); + + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = !scalar ? op.GetBytesCount() >> op.Size : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size, signed); + + Operand e = !saturating + ? EmitShlImm(context, ne, GetImmShl(op), op.Size) + : EmitShlImmSatQ(context, ne, GetImmShl(op), op.Size, signed, signedDst); + + res = EmitVectorInsert(context, res, e, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static Operand EmitShlImm(ArmEmitterContext context, Operand op, int shiftLsB, int size) + { + int eSize = 8 << size; + + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64); + + Operand res = context.AllocateLocal(OperandType.I64); + + if (shiftLsB >= eSize) + { + Operand shl = context.ShiftLeft(op, Const(shiftLsB)); + context.Copy(res, shl); + } + else + { + Operand zeroL = Const(0L); + context.Copy(res, zeroL); + } + + return res; + } + + private static Operand EmitShlImmSatQ(ArmEmitterContext context, Operand op, int shiftLsB, int size, bool signedSrc, bool signedDst) + { + int eSize = 8 << size; + + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64); + + Operand lblEnd = Label(); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), op); + + if (shiftLsB >= eSize) + { + context.Copy(res, signedSrc + ? EmitSignedSignSatQ(context, op, size) + : EmitUnsignedSignSatQ(context, op, size)); + } + else + { + Operand shl = context.ShiftLeft(op, Const(shiftLsB)); + if (eSize == 64) + { + Operand sarOrShr = signedSrc + ? context.ShiftRightSI(shl, Const(shiftLsB)) + : context.ShiftRightUI(shl, Const(shiftLsB)); + context.Copy(res, shl); + context.BranchIf(lblEnd, sarOrShr, op, Comparison.Equal); + context.Copy(res, signedSrc + ? EmitSignedSignSatQ(context, op, size) + : EmitUnsignedSignSatQ(context, op, size)); + } + else + { + context.Copy(res, signedSrc + ? EmitSignedSrcSatQ(context, shl, size, signedDst) + : EmitUnsignedSrcSatQ(context, shl, size, signedDst)); + } + } + + context.MarkLabel(lblEnd); + + return res; + } + private static void EmitShlRegOp(ArmEmitterContext context, ShlRegFlags flags = ShlRegFlags.None) { bool scalar = flags.HasFlag(ShlRegFlags.Scalar); diff --git a/src/ARMeilleure/Instructions/InstName.cs b/src/ARMeilleure/Instructions/InstName.cs index 32ae38dad..6723a42e4 100644 --- a/src/ARMeilleure/Instructions/InstName.cs +++ b/src/ARMeilleure/Instructions/InstName.cs @@ -384,7 +384,9 @@ enum InstName Sqrshrn_V, Sqrshrun_S, Sqrshrun_V, + Sqshl_Si, Sqshl_V, + Sqshl_Vi, Sqshrn_S, Sqshrn_V, Sqshrun_S, diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs index fbac54c8c..9816bc2cc 100644 --- a/src/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs @@ -311,6 +311,46 @@ private static uint[] _SU_Shll_V_2S2D_4S2D_() }; } + private static uint[] _ShlImm_S_D_() + { + return new[] + { + 0x5F407400u, // SQSHL D0, D0, #0 + }; + } + + private static uint[] _ShlImm_V_8B_16B_() + { + return new[] + { + 0x0F087400u, // SQSHL V0.8B, V0.8B, #0 + }; + } + + private static uint[] _ShlImm_V_4H_8H_() + { + return new[] + { + 0x0F107400u, // SQSHL V0.4H, V0.4H, #0 + }; + } + + private static uint[] _ShlImm_V_2S_4S_() + { + return new[] + { + 0x0F207400u, // SQSHL V0.2S, V0.2S, #0 + }; + } + + private static uint[] _ShlImm_V_2D_() + { + return new[] + { + 0x4F407400u, // SQSHL V0.2D, V0.2D, #0 + }; + } + private static uint[] _ShrImm_Sri_S_D_() { return new[] @@ -813,6 +853,117 @@ public void SU_Shll_V_2S2D_4S2D([ValueSource(nameof(_SU_Shll_V_2S2D_4S2D_))] uin CompareAgainstUnicorn(); } + [Test, Pairwise] + public void ShlImm_S_D([ValueSource(nameof(_ShlImm_S_D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a, + [Values(1u, 64u)] uint shift) + { + uint immHb = (64 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShlImm_V_8B_16B([ValueSource(nameof(_ShlImm_V_8B_16B_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [Values(1u, 8u)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + uint immHb = (8 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShlImm_V_4H_8H([ValueSource(nameof(_ShlImm_V_4H_8H_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_4H_))] ulong z, + [ValueSource(nameof(_4H_))] ulong a, + [Values(1u, 16u)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <4H, 8H> + { + uint immHb = (16 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShlImm_V_2S_4S([ValueSource(nameof(_ShlImm_V_2S_4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_2S_))] ulong z, + [ValueSource(nameof(_2S_))] ulong a, + [Values(1u, 32u)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint immHb = (32 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= (((q | (immHb >> 6)) & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShlImm_V_2D([ValueSource(nameof(_ShlImm_V_2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a, + [Values(1u, 64u)] uint shift) + { + uint immHb = (64 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + [Test, Pairwise] public void ShrImm_Sri_S_D([ValueSource(nameof(_ShrImm_Sri_S_D_))] uint opcodes, [Values(0u)] uint rd, From dd2e851e95e586bf65829da7cdb9e82014db36de Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Thu, 25 Jan 2024 18:25:47 +0000 Subject: [PATCH 004/126] OpenTK (#6143) --- Directory.Packages.props | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index c163cb0bf..33caaf469 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -28,10 +28,10 @@ - - - - + + + + From 795539bc821dd51ff7fb2b1f2c851881b168b03d Mon Sep 17 00:00:00 2001 From: riperiperi Date: Thu, 25 Jan 2024 18:29:53 +0000 Subject: [PATCH 005/126] Vulkan: Use staging buffer for temporary constants (#6168) * Vulkan: Use staging buffer for temporary constants Helper shaders and post processing effects typically need some parameters to tell them what to do, which we pass via constant buffers that are created and destroyed each time. This can vary in cost between different Vulkan drivers. It shows up on profiles on mesa and MoltenVK, so it's worth avoiding. Some games only do it once (BlitColor for present), others multiple times. It's also done for post processing filters and FSR upscaling, which creates two buffers. For mirrors, I added the ability to reserve a range on the staging buffer for use as any type of binding. This PR allows these constant buffers to be instead temporarily allocated on the staging buffer, skipping allocation and buffer management costs entirely. Two temporary allocations do remain: - DrawTexture, because it doesn't have access to the command buffer scope - Index buffer indirect conversion, because one of them is a storage buffer and thus is a little more complicated. There's a small cost in that the uniform buffer takes up more space due to alignment requirements. At worst that's 256 bytes (on a GTX 1070) but more modern GPUs should have a better time. Worth testing across different games and post effects to make sure they still work. * Use temporary buffer for ConvertIndexBufferIndirect * Simplify alignment passing for now * Fix shader params length for CopyIncompatibleFormats * Set data for helpershaders without overlap checks The data is in the staging buffer, so its usage range is guarded using that. --- src/Ryujinx.Graphics.Vulkan/BufferHolder.cs | 8 +- src/Ryujinx.Graphics.Vulkan/BufferManager.cs | 50 +++++++++- .../Effects/FsrScalingFilter.cs | 19 ++-- .../Effects/FxaaPostProcessingEffect.cs | 8 +- .../Effects/SmaaPostProcessingEffect.cs | 9 +- src/Ryujinx.Graphics.Vulkan/HelperShader.cs | 92 ++++++++----------- src/Ryujinx.Graphics.Vulkan/StagingBuffer.cs | 23 ++++- 7 files changed, 126 insertions(+), 83 deletions(-) diff --git a/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs b/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs index bdd5d3856..3673ee5a1 100644 --- a/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs +++ b/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; using VkBuffer = Silk.NET.Vulkan.Buffer; using VkFormat = Silk.NET.Vulkan.Format; @@ -384,7 +385,7 @@ private unsafe bool TryGetMirror(CommandBufferScoped cbs, ref int offset, int si var baseData = new Span((void*)(_map + offset), size); var modData = _pendingData.AsSpan(offset, size); - StagingBufferReserved? newMirror = _gd.BufferManager.StagingBuffer.TryReserveData(cbs, size, (int)_gd.Capabilities.MinResourceAlignment); + StagingBufferReserved? newMirror = _gd.BufferManager.StagingBuffer.TryReserveData(cbs, size); if (newMirror != null) { @@ -838,6 +839,11 @@ public unsafe void SetDataUnchecked(int offset, ReadOnlySpan data) } } + public unsafe void SetDataUnchecked(int offset, ReadOnlySpan data) where T : unmanaged + { + SetDataUnchecked(offset, MemoryMarshal.AsBytes(data)); + } + public void SetDataInline(CommandBufferScoped cbs, Action endRenderPass, int dstOffset, ReadOnlySpan data) { if (!TryPushData(cbs, endRenderPass, dstOffset, data)) diff --git a/src/Ryujinx.Graphics.Vulkan/BufferManager.cs b/src/Ryujinx.Graphics.Vulkan/BufferManager.cs index e9ac98847..33289a0e0 100644 --- a/src/Ryujinx.Graphics.Vulkan/BufferManager.cs +++ b/src/Ryujinx.Graphics.Vulkan/BufferManager.cs @@ -9,6 +9,36 @@ namespace Ryujinx.Graphics.Vulkan { + readonly struct ScopedTemporaryBuffer : IDisposable + { + private readonly BufferManager _bufferManager; + private readonly bool _isReserved; + + public readonly BufferRange Range; + public readonly BufferHolder Holder; + + public BufferHandle Handle => Range.Handle; + public int Offset => Range.Offset; + + public ScopedTemporaryBuffer(BufferManager bufferManager, BufferHolder holder, BufferHandle handle, int offset, int size, bool isReserved) + { + _bufferManager = bufferManager; + + Range = new BufferRange(handle, offset, size); + Holder = holder; + + _isReserved = isReserved; + } + + public void Dispose() + { + if (!_isReserved) + { + _bufferManager.Delete(Range.Handle); + } + } + } + class BufferManager : IDisposable { public const MemoryPropertyFlags DefaultBufferMemoryFlags = @@ -238,6 +268,23 @@ public BufferHandle CreateWithHandle( return Unsafe.As(ref handle64); } + public ScopedTemporaryBuffer ReserveOrCreate(VulkanRenderer gd, CommandBufferScoped cbs, int size) + { + StagingBufferReserved? result = StagingBuffer.TryReserveData(cbs, size); + + if (result.HasValue) + { + return new ScopedTemporaryBuffer(this, result.Value.Buffer, StagingBuffer.Handle, result.Value.Offset, result.Value.Size, true); + } + else + { + // Create a temporary buffer. + BufferHandle handle = CreateWithHandle(gd, size, out BufferHolder holder); + + return new ScopedTemporaryBuffer(this, holder, handle, 0, size, false); + } + } + public unsafe MemoryRequirements GetHostImportedUsageRequirements(VulkanRenderer gd) { var usage = HostImportedBufferUsageFlags; @@ -635,13 +682,14 @@ protected virtual void Dispose(bool disposing) { if (disposing) { + StagingBuffer.Dispose(); + foreach (BufferHolder buffer in _buffers) { buffer.Dispose(); } _buffers.Clear(); - StagingBuffer.Dispose(); } } diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs b/src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs index 23acdcf8f..5c0fc468b 100644 --- a/src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs +++ b/src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs @@ -142,19 +142,18 @@ public void Run( }; int rangeSize = dimensionsBuffer.Length * sizeof(float); - var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize); - _renderer.BufferManager.SetData(bufferHandle, 0, dimensionsBuffer); + using var buffer = _renderer.BufferManager.ReserveOrCreate(_renderer, cbs, rangeSize); + buffer.Holder.SetDataUnchecked(buffer.Offset, dimensionsBuffer); - ReadOnlySpan sharpeningBuffer = stackalloc float[] { 1.5f - (Level * 0.01f * 1.5f) }; - var sharpeningBufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, sizeof(float)); - _renderer.BufferManager.SetData(sharpeningBufferHandle, 0, sharpeningBuffer); + ReadOnlySpan sharpeningBufferData = stackalloc float[] { 1.5f - (Level * 0.01f * 1.5f) }; + using var sharpeningBuffer = _renderer.BufferManager.ReserveOrCreate(_renderer, cbs, sizeof(float)); + sharpeningBuffer.Holder.SetDataUnchecked(sharpeningBuffer.Offset, sharpeningBufferData); int threadGroupWorkRegionDim = 16; int dispatchX = (width + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim; int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim; - var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize); - _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) }); + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) }); _pipeline.SetImage(0, _intermediaryTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)); _pipeline.DispatchCompute(dispatchX, dispatchY, 1); _pipeline.ComputeBarrier(); @@ -162,16 +161,12 @@ public void Run( // Sharpening pass _pipeline.SetProgram(_sharpeningProgram); _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _intermediaryTexture, _sampler); - var sharpeningRange = new BufferRange(sharpeningBufferHandle, 0, sizeof(float)); - _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(4, sharpeningRange) }); + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(4, sharpeningBuffer.Range) }); _pipeline.SetImage(0, destinationTexture); _pipeline.DispatchCompute(dispatchX, dispatchY, 1); _pipeline.ComputeBarrier(); _pipeline.Finish(); - - _renderer.BufferManager.Delete(bufferHandle); - _renderer.BufferManager.Delete(sharpeningBufferHandle); } } } diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs b/src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs index 67e461e51..a7dd8eee8 100644 --- a/src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs +++ b/src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs @@ -66,12 +66,11 @@ public TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int ReadOnlySpan resolutionBuffer = stackalloc float[] { view.Width, view.Height }; int rangeSize = resolutionBuffer.Length * sizeof(float); - var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize); + using var buffer = _renderer.BufferManager.ReserveOrCreate(_renderer, cbs, rangeSize); - _renderer.BufferManager.SetData(bufferHandle, 0, resolutionBuffer); + buffer.Holder.SetDataUnchecked(buffer.Offset, resolutionBuffer); - var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize); - _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) }); + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) }); var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize); var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize); @@ -79,7 +78,6 @@ public TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int _pipeline.SetImage(0, _texture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)); _pipeline.DispatchCompute(dispatchX, dispatchY, 1); - _renderer.BufferManager.Delete(bufferHandle); _pipeline.ComputeBarrier(); _pipeline.Finish(); diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs b/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs index c521f2273..802b73b86 100644 --- a/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs +++ b/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs @@ -215,11 +215,10 @@ public TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int ReadOnlySpan resolutionBuffer = stackalloc float[] { view.Width, view.Height }; int rangeSize = resolutionBuffer.Length * sizeof(float); - var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize); + using var buffer = _renderer.BufferManager.ReserveOrCreate(_renderer, cbs, rangeSize); - _renderer.BufferManager.SetData(bufferHandle, 0, resolutionBuffer); - var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize); - _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) }); + buffer.Holder.SetDataUnchecked(buffer.Offset, resolutionBuffer); + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) }); _pipeline.SetImage(0, _edgeOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)); _pipeline.DispatchCompute(dispatchX, dispatchY, 1); _pipeline.ComputeBarrier(); @@ -245,8 +244,6 @@ public TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int _pipeline.Finish(); - _renderer.BufferManager.Delete(bufferHandle); - return _outputTexture; } diff --git a/src/Ryujinx.Graphics.Vulkan/HelperShader.cs b/src/Ryujinx.Graphics.Vulkan/HelperShader.cs index deaf81625..ce84f7521 100644 --- a/src/Ryujinx.Graphics.Vulkan/HelperShader.cs +++ b/src/Ryujinx.Graphics.Vulkan/HelperShader.cs @@ -430,11 +430,11 @@ public void BlitColor( (region[2], region[3]) = (region[3], region[2]); } - var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize); + using var buffer = gd.BufferManager.ReserveOrCreate(gd, cbs, RegionBufferSize); - gd.BufferManager.SetData(bufferHandle, 0, region); + buffer.Holder.SetDataUnchecked(buffer.Offset, region); - _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, RegionBufferSize)) }); + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, buffer.Range) }); Span viewports = stackalloc Viewport[1]; @@ -490,8 +490,6 @@ public void BlitColor( } _pipeline.Finish(gd, cbs); - - gd.BufferManager.Delete(bufferHandle); } private void BlitDepthStencil( @@ -527,11 +525,11 @@ private void BlitDepthStencil( (region[2], region[3]) = (region[3], region[2]); } - var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize); + using var buffer = gd.BufferManager.ReserveOrCreate(gd, cbs, RegionBufferSize); - gd.BufferManager.SetData(bufferHandle, 0, region); + buffer.Holder.SetDataUnchecked(buffer.Offset, region); - _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, RegionBufferSize)) }); + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, buffer.Range) }); Span viewports = stackalloc Viewport[1]; @@ -582,8 +580,6 @@ private void BlitDepthStencil( } _pipeline.Finish(gd, cbs); - - gd.BufferManager.Delete(bufferHandle); } private static TextureView CreateDepthOrStencilView(TextureView depthStencilTexture, DepthStencilMode depthStencilMode) @@ -681,11 +677,11 @@ public void Clear( _pipeline.SetCommandBuffer(cbs); - var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ClearColorBufferSize); + using var buffer = gd.BufferManager.ReserveOrCreate(gd, cbs, ClearColorBufferSize); - gd.BufferManager.SetData(bufferHandle, 0, clearColor); + buffer.Holder.SetDataUnchecked(buffer.Offset, clearColor); - _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, ClearColorBufferSize)) }); + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, buffer.Range) }); Span viewports = stackalloc Viewport[1]; @@ -721,8 +717,6 @@ public void Clear( _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); _pipeline.Draw(4, 1, 0, 0); _pipeline.Finish(); - - gd.BufferManager.Delete(bufferHandle); } public void Clear( @@ -745,11 +739,11 @@ public void Clear( _pipeline.SetCommandBuffer(cbs); - var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ClearColorBufferSize); + using var buffer = gd.BufferManager.ReserveOrCreate(gd, cbs, ClearColorBufferSize); - gd.BufferManager.SetData(bufferHandle, 0, stackalloc float[] { depthValue }); + buffer.Holder.SetDataUnchecked(buffer.Offset, stackalloc float[] { depthValue }); - _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, ClearColorBufferSize)) }); + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, buffer.Range) }); Span viewports = stackalloc Viewport[1]; @@ -771,8 +765,6 @@ public void Clear( _pipeline.SetStencilTest(CreateStencilTestDescriptor(stencilMask != 0, stencilValue, 0xff, stencilMask)); _pipeline.Draw(4, 1, 0, 0); _pipeline.Finish(); - - gd.BufferManager.Delete(bufferHandle); } public void DrawTexture( @@ -878,13 +870,13 @@ public unsafe void ChangeStride(VulkanRenderer gd, CommandBufferScoped cbs, Buff shaderParams[2] = size; shaderParams[3] = srcOffset; - var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize); + using var buffer = gd.BufferManager.ReserveOrCreate(gd, cbs, ParamsBufferSize); - gd.BufferManager.SetData(bufferHandle, 0, shaderParams); + buffer.Holder.SetDataUnchecked(buffer.Offset, shaderParams); _pipeline.SetCommandBuffer(cbs); - _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(bufferHandle, 0, ParamsBufferSize)) }); + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, buffer.Range) }); Span> sbRanges = new Auto[2]; @@ -896,8 +888,6 @@ public unsafe void ChangeStride(VulkanRenderer gd, CommandBufferScoped cbs, Buff _pipeline.SetProgram(_programStrideChange); _pipeline.DispatchCompute(1 + elems / ConvertElementsPerWorkgroup, 1, 1); - gd.BufferManager.Delete(bufferHandle); - _pipeline.Finish(gd, cbs); } else @@ -1025,7 +1015,7 @@ public void CopyIncompatibleFormats( { const int ParamsBufferSize = 4; - Span shaderParams = stackalloc int[sizeof(int)]; + Span shaderParams = stackalloc int[ParamsBufferSize / sizeof(int)]; int srcBpp = src.Info.BytesPerPixel; int dstBpp = dst.Info.BytesPerPixel; @@ -1034,9 +1024,9 @@ public void CopyIncompatibleFormats( shaderParams[0] = BitOperations.Log2((uint)ratio); - var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize); + using var buffer = gd.BufferManager.ReserveOrCreate(gd, cbs, ParamsBufferSize); - gd.BufferManager.SetData(bufferHandle, 0, shaderParams); + buffer.Holder.SetDataUnchecked(buffer.Offset, shaderParams); TextureView.InsertImageBarrier( gd.Api, @@ -1064,7 +1054,7 @@ public void CopyIncompatibleFormats( var srcFormat = GetFormat(componentSize, srcBpp / componentSize); var dstFormat = GetFormat(componentSize, dstBpp / componentSize); - _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(bufferHandle, 0, ParamsBufferSize)) }); + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, buffer.Range) }); for (int l = 0; l < levels; l++) { @@ -1093,8 +1083,6 @@ public void CopyIncompatibleFormats( } } - gd.BufferManager.Delete(bufferHandle); - _pipeline.Finish(gd, cbs); TextureView.InsertImageBarrier( @@ -1128,9 +1116,9 @@ public void CopyMSToNonMS(VulkanRenderer gd, CommandBufferScoped cbs, TextureVie (shaderParams[0], shaderParams[1]) = GetSampleCountXYLog2(samples); (shaderParams[2], shaderParams[3]) = GetSampleCountXYLog2((int)TextureStorage.ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)samples)); - var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize); + using var buffer = gd.BufferManager.ReserveOrCreate(gd, cbs, ParamsBufferSize); - gd.BufferManager.SetData(bufferHandle, 0, shaderParams); + buffer.Holder.SetDataUnchecked(buffer.Offset, shaderParams); TextureView.InsertImageBarrier( gd.Api, @@ -1147,7 +1135,7 @@ public void CopyMSToNonMS(VulkanRenderer gd, CommandBufferScoped cbs, TextureVie 1); _pipeline.SetCommandBuffer(cbs); - _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(bufferHandle, 0, ParamsBufferSize)) }); + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, buffer.Range) }); if (isDepthOrStencil) { @@ -1226,8 +1214,6 @@ public void CopyMSToNonMS(VulkanRenderer gd, CommandBufferScoped cbs, TextureVie } } - gd.BufferManager.Delete(bufferHandle); - _pipeline.Finish(gd, cbs); TextureView.InsertImageBarrier( @@ -1261,9 +1247,9 @@ public void CopyNonMSToMS(VulkanRenderer gd, CommandBufferScoped cbs, TextureVie (shaderParams[0], shaderParams[1]) = GetSampleCountXYLog2(samples); (shaderParams[2], shaderParams[3]) = GetSampleCountXYLog2((int)TextureStorage.ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)samples)); - var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize); + using var buffer = gd.BufferManager.ReserveOrCreate(gd, cbs, ParamsBufferSize); - gd.BufferManager.SetData(bufferHandle, 0, shaderParams); + buffer.Holder.SetDataUnchecked(buffer.Offset, shaderParams); TextureView.InsertImageBarrier( gd.Api, @@ -1299,7 +1285,7 @@ public void CopyNonMSToMS(VulkanRenderer gd, CommandBufferScoped cbs, TextureVie _pipeline.SetViewports(viewports); _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); - _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(bufferHandle, 0, ParamsBufferSize)) }); + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, buffer.Range) }); if (isDepthOrStencil) { @@ -1364,8 +1350,6 @@ public void CopyNonMSToMS(VulkanRenderer gd, CommandBufferScoped cbs, TextureVie } } - gd.BufferManager.Delete(bufferHandle); - _pipeline.Finish(gd, cbs); TextureView.InsertImageBarrier( @@ -1616,10 +1600,11 @@ public void ConvertIndexBufferIndirect( pattern.OffsetIndex.CopyTo(shaderParams[..pattern.OffsetIndex.Length]); - var patternBufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize, out var patternBuffer); + using var patternScoped = gd.BufferManager.ReserveOrCreate(gd, cbs, ParamsBufferSize); + var patternBuffer = patternScoped.Holder; var patternBufferAuto = patternBuffer.GetBuffer(); - gd.BufferManager.SetData(patternBufferHandle, 0, shaderParams); + patternBuffer.SetDataUnchecked(patternScoped.Offset, shaderParams); _pipeline.SetCommandBuffer(cbs); @@ -1635,7 +1620,8 @@ public void ConvertIndexBufferIndirect( indirectDataSize); _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, drawCountBufferAligned) }); - _pipeline.SetStorageBuffers(1, new[] { srcIndirectBuffer.GetBuffer(), dstIndirectBuffer.GetBuffer(), patternBuffer.GetBuffer() }); + _pipeline.SetStorageBuffers(1, new[] { srcIndirectBuffer.GetBuffer(), dstIndirectBuffer.GetBuffer() }); + _pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(3, patternScoped.Range) }); _pipeline.SetProgram(_programConvertIndirectData); _pipeline.DispatchCompute(1, 1, 1); @@ -1643,12 +1629,12 @@ public void ConvertIndexBufferIndirect( BufferHolder.InsertBufferBarrier( gd, cbs.CommandBuffer, - patternBufferAuto.Get(cbs, ParamsIndirectDispatchOffset, ParamsIndirectDispatchSize).Value, + patternBufferAuto.Get(cbs, patternScoped.Offset + ParamsIndirectDispatchOffset, ParamsIndirectDispatchSize).Value, AccessFlags.ShaderWriteBit, AccessFlags.IndirectCommandReadBit, PipelineStageFlags.ComputeShaderBit, PipelineStageFlags.DrawIndirectBit, - ParamsIndirectDispatchOffset, + patternScoped.Offset + ParamsIndirectDispatchOffset, ParamsIndirectDispatchSize); BufferHolder.InsertBufferBarrier( @@ -1662,11 +1648,11 @@ public void ConvertIndexBufferIndirect( 0, convertedCount * outputIndexSize); - _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(patternBufferHandle, 0, ParamsBufferSize)) }); + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(patternScoped.Handle, patternScoped.Offset, ParamsBufferSize)) }); _pipeline.SetStorageBuffers(1, new[] { srcIndexBuffer.GetBuffer(), dstIndexBuffer.GetBuffer() }); _pipeline.SetProgram(_programConvertIndexBuffer); - _pipeline.DispatchComputeIndirect(patternBufferAuto, ParamsIndirectDispatchOffset); + _pipeline.DispatchComputeIndirect(patternBufferAuto, patternScoped.Offset + ParamsIndirectDispatchOffset); BufferHolder.InsertBufferBarrier( gd, @@ -1679,8 +1665,6 @@ public void ConvertIndexBufferIndirect( 0, convertedCount * outputIndexSize); - gd.BufferManager.Delete(patternBufferHandle); - _pipeline.Finish(gd, cbs); } @@ -1726,13 +1710,13 @@ public unsafe void ConvertD32S8ToD24S8(VulkanRenderer gd, CommandBufferScoped cb shaderParams[0] = pixelCount; shaderParams[1] = dstOffset; - var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize); + using var buffer = gd.BufferManager.ReserveOrCreate(gd, cbs, ParamsBufferSize); - gd.BufferManager.SetData(bufferHandle, 0, shaderParams); + buffer.Holder.SetDataUnchecked(buffer.Offset, shaderParams); _pipeline.SetCommandBuffer(cbs); - _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(bufferHandle, 0, ParamsBufferSize)) }); + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, buffer.Range) }); Span> sbRanges = new Auto[2]; @@ -1744,8 +1728,6 @@ public unsafe void ConvertD32S8ToD24S8(VulkanRenderer gd, CommandBufferScoped cb _pipeline.SetProgram(_programConvertD32S8ToD24S8); _pipeline.DispatchCompute(1 + inSize / ConvertElementsPerWorkgroup, 1, 1); - gd.BufferManager.Delete(bufferHandle); - _pipeline.Finish(gd, cbs); BufferHolder.InsertBufferBarrier( diff --git a/src/Ryujinx.Graphics.Vulkan/StagingBuffer.cs b/src/Ryujinx.Graphics.Vulkan/StagingBuffer.cs index 3a02a28dc..90a47bb67 100644 --- a/src/Ryujinx.Graphics.Vulkan/StagingBuffer.cs +++ b/src/Ryujinx.Graphics.Vulkan/StagingBuffer.cs @@ -1,5 +1,6 @@ using Ryujinx.Common; using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; using System; using System.Collections.Generic; using System.Diagnostics; @@ -29,6 +30,9 @@ class StagingBuffer : IDisposable private readonly VulkanRenderer _gd; private readonly BufferHolder _buffer; + private readonly int _resourceAlignment; + + public readonly BufferHandle Handle; private readonly struct PendingCopy { @@ -48,9 +52,10 @@ public PendingCopy(FenceHolder fence, int size) public StagingBuffer(VulkanRenderer gd, BufferManager bufferManager) { _gd = gd; - _buffer = bufferManager.Create(gd, BufferSize); + Handle = bufferManager.CreateWithHandle(gd, BufferSize, out _buffer); _pendingCopies = new Queue(); _freeSize = BufferSize; + _resourceAlignment = (int)gd.Capabilities.MinResourceAlignment; } public void PushData(CommandBufferPool cbp, CommandBufferScoped? cbs, Action endRenderPass, BufferHolder dst, int dstOffset, ReadOnlySpan data) @@ -197,7 +202,7 @@ private int GetContiguousFreeSize(int alignment) /// Reserve a range on the staging buffer for the current command buffer and upload data to it. /// /// Command buffer to reserve the data on - /// The data to upload + /// The minimum size the reserved data requires /// The required alignment for the buffer offset /// The reserved range of the staging buffer public unsafe StagingBufferReserved? TryReserveData(CommandBufferScoped cbs, int size, int alignment) @@ -223,6 +228,18 @@ private int GetContiguousFreeSize(int alignment) return ReserveDataImpl(cbs, size, alignment); } + /// + /// Reserve a range on the staging buffer for the current command buffer and upload data to it. + /// Uses the most permissive byte alignment. + /// + /// Command buffer to reserve the data on + /// The minimum size the reserved data requires + /// The reserved range of the staging buffer + public unsafe StagingBufferReserved? TryReserveData(CommandBufferScoped cbs, int size) + { + return TryReserveData(cbs, size, _resourceAlignment); + } + private bool WaitFreeCompleted(CommandBufferPool cbp) { if (_pendingCopies.TryPeek(out var pc)) @@ -263,7 +280,7 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - _buffer.Dispose(); + _gd.BufferManager.Delete(Handle); while (_pendingCopies.TryDequeue(out var pc)) { From 1d9b63cc6af882aca137b46f348d9f3ec0e4f853 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Jan 2024 19:35:10 +0100 Subject: [PATCH 006/126] nuget: bump Microsoft.CodeAnalysis.CSharp from 4.7.0 to 4.8.0 (#6118) Bumps [Microsoft.CodeAnalysis.CSharp](https://github.com/dotnet/roslyn) from 4.7.0 to 4.8.0. - [Release notes](https://github.com/dotnet/roslyn/releases) - [Changelog](https://github.com/dotnet/roslyn/blob/main/docs/Breaking%20API%20Changes.md) - [Commits](https://github.com/dotnet/roslyn/commits) --- updated-dependencies: - dependency-name: Microsoft.CodeAnalysis.CSharp dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 33caaf469..15571d6ac 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -20,7 +20,7 @@ - + From 371e6fa24c49584a513ac9f52a9ed730ff72c62d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Jan 2024 19:41:24 +0100 Subject: [PATCH 007/126] nuget: bump Microsoft.IO.RecyclableMemoryStream from 2.3.2 to 3.0.0 (#6120) Bumps [Microsoft.IO.RecyclableMemoryStream](https://github.com/Microsoft/Microsoft.IO.RecyclableMemoryStream) from 2.3.2 to 3.0.0. - [Release notes](https://github.com/Microsoft/Microsoft.IO.RecyclableMemoryStream/releases) - [Changelog](https://github.com/microsoft/Microsoft.IO.RecyclableMemoryStream/blob/master/CHANGES.md) - [Commits](https://github.com/Microsoft/Microsoft.IO.RecyclableMemoryStream/compare/2.3.2...3.0.0) --- updated-dependencies: - dependency-name: Microsoft.IO.RecyclableMemoryStream dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 15571d6ac..678d7575a 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -23,7 +23,7 @@ - + From 43705c2320c2ff7c8f6dca1141f3bf56033966d4 Mon Sep 17 00:00:00 2001 From: TSRBerry <20988865+TSRBerry@users.noreply.github.com> Date: Thu, 25 Jan 2024 20:10:51 +0100 Subject: [PATCH 008/126] ssl: Work around missing remote hostname for authentication (#5988) * ssl: Retrieve remote hostnames if the provided hostname is empty This avoids crashing with an AuthenticationException. * ssl: Remove unused variable from RetrieveHostName --- .../SslService/SslManagedSocketConnection.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/SslManagedSocketConnection.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/SslManagedSocketConnection.cs index 4dd6367ed..8cc761baf 100644 --- a/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/SslManagedSocketConnection.cs +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/SslManagedSocketConnection.cs @@ -3,6 +3,7 @@ using Ryujinx.HLE.HOS.Services.Ssl.Types; using System; using System.IO; +using System.Net; using System.Net.Security; using System.Net.Sockets; using System.Security.Authentication; @@ -83,10 +84,40 @@ private SslProtocols TranslateSslVersion(SslVersion version) } #pragma warning restore SYSLIB0039 + /// + /// Retrieve the hostname of the current remote in case the provided hostname is null or empty. + /// + /// The current hostname + /// Either the resolved or provided hostname + /// + /// This is done to avoid getting an + /// as the remote certificate will be rejected with RemoteCertificateNameMismatch due to an empty hostname. + /// This is not what the switch does! + /// It might just skip remote hostname verification if the hostname wasn't set with before. + /// TODO: Remove this as soon as we know how the switch deals with empty hostnames + /// + private string RetrieveHostName(string hostName) + { + if (!string.IsNullOrEmpty(hostName)) + { + return hostName; + } + + try + { + return Dns.GetHostEntry(Socket.RemoteEndPoint.Address).HostName; + } + catch (SocketException) + { + return hostName; + } + } + public ResultCode Handshake(string hostName) { StartSslOperation(); _stream = new SslStream(new NetworkStream(((ManagedSocket)Socket).Socket, false), false, null, null); + hostName = RetrieveHostName(hostName); _stream.AuthenticateAsClient(hostName, null, TranslateSslVersion(_sslVersion), false); EndSslOperation(); From cd37c75b82f97ad5d3bf6317ffcde62c06a6e920 Mon Sep 17 00:00:00 2001 From: Ac_K Date: Thu, 25 Jan 2024 23:06:53 +0100 Subject: [PATCH 009/126] Horizon: Implement arp:r and arp:w services (#5802) * Horizon: Implement arp:r and arp:w services * Fix formatting * Remove HLE arp services * Revert "Remove HLE arp services" This reverts commit c576fcccadb963db56b96bacabd1c1ac7abfb1ab. * Keep LibHac impl since it's used in bcat * Addresses gdkchan's feedback * ArpApi in PrepoIpcServer and remove LmApi * Fix 2 * Fixes ArpApi init * Fix encoding * Update PrepoService.cs * Fix prepo --- .../Memory/StructArrayHelpers.cs | 11 + .../Memory/StructByteArrayHelpers.cs | 12 + src/Ryujinx.HLE/HOS/Services/Arp/IReader.cs | 8 - src/Ryujinx.HLE/HOS/Services/Arp/IWriter.cs | 8 - .../IParentalControlService.cs | 2 - .../Extensions/FileSystemExtensions.cs | 3 +- .../Extensions/LocalFileSystemExtensions.cs | 2 +- .../Processes/Extensions/NcaExtensions.cs | 2 +- .../Loaders/Processes/ProcessLoader.cs | 5 +- .../Loaders/Processes/ProcessLoaderHelper.cs | 23 +- src/Ryujinx.Horizon/Arp/ArpIpcServer.cs | 61 ++++ src/Ryujinx.Horizon/Arp/ArpMain.cs | 20 ++ src/Ryujinx.Horizon/Arp/Ipc/Reader.cs | 135 ++++++++ src/Ryujinx.Horizon/Arp/Ipc/Registrar.cs | 52 +++ .../Arp/Ipc/UnregistrationNotifier.cs | 25 ++ src/Ryujinx.Horizon/Arp/Ipc/Updater.cs | 74 +++++ src/Ryujinx.Horizon/Arp/Ipc/Writer.cs | 75 +++++ src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs | 21 +- src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs | 7 +- .../Prepo/PrepoServerManager.cs | 18 +- .../Sdk/Arp/ApplicationCertificate.cs | 9 + .../Sdk/Arp/ApplicationKind.cs | 8 + .../Sdk/Arp/ApplicationLaunchProperty.cs | 14 + .../Sdk/Arp/ApplicationProcessProperty.cs | 10 + src/Ryujinx.Horizon/Sdk/Arp/ArpApi.cs | 130 ++++++++ src/Ryujinx.Horizon/Sdk/Arp/ArpResult.cs | 17 + .../Sdk/Arp/Detail/ApplicationInstance.cs | 13 + .../Arp/Detail/ApplicationInstanceManager.cs | 31 ++ src/Ryujinx.Horizon/Sdk/Arp/IReader.cs | 18 + src/Ryujinx.Horizon/Sdk/Arp/IRegistrar.cs | 12 + .../Sdk/Arp/IUnregistrationNotifier.cs | 9 + src/Ryujinx.Horizon/Sdk/Arp/IUpdater.cs | 12 + src/Ryujinx.Horizon/Sdk/Arp/IWriter.cs | 12 + src/Ryujinx.Horizon/Sdk/Ncm/ApplicationId.cs | 2 +- src/Ryujinx.Horizon/Sdk/Ncm/StorageId.cs | 13 + .../Sdk/Ns/ApplicationControlProperty.cs | 309 ++++++++++++++++++ src/Ryujinx.Horizon/Sdk/ServiceUtil.cs | 249 ++++++++++++++ .../Sdk/Sf/Cmif/CmifRequest.cs | 7 + .../Sdk/Sf/Hipc/HipcBufferDescriptor.cs | 7 + src/Ryujinx.Horizon/ServiceTable.cs | 6 + 40 files changed, 1415 insertions(+), 37 deletions(-) delete mode 100644 src/Ryujinx.HLE/HOS/Services/Arp/IReader.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Arp/IWriter.cs create mode 100644 src/Ryujinx.Horizon/Arp/ArpIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Arp/ArpMain.cs create mode 100644 src/Ryujinx.Horizon/Arp/Ipc/Reader.cs create mode 100644 src/Ryujinx.Horizon/Arp/Ipc/Registrar.cs create mode 100644 src/Ryujinx.Horizon/Arp/Ipc/UnregistrationNotifier.cs create mode 100644 src/Ryujinx.Horizon/Arp/Ipc/Updater.cs create mode 100644 src/Ryujinx.Horizon/Arp/Ipc/Writer.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/ApplicationCertificate.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/ApplicationKind.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/ApplicationLaunchProperty.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/ApplicationProcessProperty.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/ArpApi.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/ArpResult.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/Detail/ApplicationInstance.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/Detail/ApplicationInstanceManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/IReader.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/IRegistrar.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/IUnregistrationNotifier.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/IUpdater.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/IWriter.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ncm/StorageId.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ns/ApplicationControlProperty.cs diff --git a/src/Ryujinx.Common/Memory/StructArrayHelpers.cs b/src/Ryujinx.Common/Memory/StructArrayHelpers.cs index 94ce8d2f5..762c73889 100644 --- a/src/Ryujinx.Common/Memory/StructArrayHelpers.cs +++ b/src/Ryujinx.Common/Memory/StructArrayHelpers.cs @@ -744,6 +744,17 @@ public struct Array64 : IArray where T : unmanaged public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); } + public struct Array65 : IArray where T : unmanaged + { + T _e0; + Array64 _other; + public readonly int Length => 65; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + public struct Array73 : IArray where T : unmanaged { T _e0; diff --git a/src/Ryujinx.Common/Memory/StructByteArrayHelpers.cs b/src/Ryujinx.Common/Memory/StructByteArrayHelpers.cs index 3b0666628..79b7d681a 100644 --- a/src/Ryujinx.Common/Memory/StructByteArrayHelpers.cs +++ b/src/Ryujinx.Common/Memory/StructByteArrayHelpers.cs @@ -63,6 +63,18 @@ public struct ByteArray2048 : IArray public Span AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size); } + [StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)] + public struct ByteArray3000 : IArray + { + private const int Size = 3000; + + byte _element; + + public readonly int Length => Size; + public ref byte this[int index] => ref AsSpan()[index]; + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size); + } + [StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)] public struct ByteArray4096 : IArray { diff --git a/src/Ryujinx.HLE/HOS/Services/Arp/IReader.cs b/src/Ryujinx.HLE/HOS/Services/Arp/IReader.cs deleted file mode 100644 index 611310421..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Arp/IReader.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Arp -{ - [Service("arp:r")] - class IReader : IpcService - { - public IReader(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Arp/IWriter.cs b/src/Ryujinx.HLE/HOS/Services/Arp/IWriter.cs deleted file mode 100644 index 22d081b9b..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Arp/IWriter.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Arp -{ - [Service("arp:w")] - class IWriter : IpcService - { - public IWriter(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs b/src/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs index cf8c1f78d..9b026a1c3 100644 --- a/src/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs +++ b/src/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs @@ -159,9 +159,7 @@ public ResultCode ConfirmStereoVisionRestrictionConfigurable(ServiceCtx context) } else { -#pragma warning disable CS0162 // Unreachable code return ResultCode.StereoVisionRestrictionConfigurableDisabled; -#pragma warning restore CS0162 } } diff --git a/src/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs b/src/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs index 28f6fcff4..3904d660e 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs @@ -34,7 +34,7 @@ public static MetaLoader GetNpdm(this IFileSystem fileSystem) return metaLoader; } - public static ProcessResult Load(this IFileSystem exeFs, Switch device, BlitStruct nacpData, MetaLoader metaLoader, bool isHomebrew = false) + public static ProcessResult Load(this IFileSystem exeFs, Switch device, BlitStruct nacpData, MetaLoader metaLoader, byte programIndex, bool isHomebrew = false) { ulong programId = metaLoader.GetProgramId(); @@ -119,6 +119,7 @@ public static ProcessResult Load(this IFileSystem exeFs, Switch device, BlitStru true, programName, metaLoader.GetProgramId(), + programIndex, null, nsoExecutables); diff --git a/src/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs b/src/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs index 798a9261e..39139ecca 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs @@ -26,7 +26,7 @@ public static ProcessResult Load(this LocalFileSystem exeFs, Switch device, stri ProcessLoaderHelper.EnsureSaveData(device, new ApplicationId(programId), nacpData); } - ProcessResult processResult = exeFs.Load(device, nacpData, metaLoader); + ProcessResult processResult = exeFs.Load(device, nacpData, metaLoader, 0); // Load RomFS. if (!string.IsNullOrEmpty(romFsPath)) diff --git a/src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs b/src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs index e369f4b04..e70fcb6fc 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs @@ -61,7 +61,7 @@ public static ProcessResult Load(this Nca nca, Switch device, Nca patchNca, Nca */ - ProcessResult processResult = exeFs.Load(device, nacpData, metaLoader); + ProcessResult processResult = exeFs.Load(device, nacpData, metaLoader, (byte)nca.GetProgramIndex()); // Load RomFS. if (romFs == null) diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs index efeb9f613..e5056c89a 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs @@ -77,7 +77,7 @@ public bool LoadNsp(string path) if (processResult.ProcessId == 0) { // This is not a normal NSP, it's actually a ExeFS as a NSP - processResult = partitionFileSystem.Load(_device, new BlitStruct(1), partitionFileSystem.GetNpdm(), true); + processResult = partitionFileSystem.Load(_device, new BlitStruct(1), partitionFileSystem.GetNpdm(), 0, true); } if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult)) @@ -198,7 +198,7 @@ public bool LoadNxo(string path) } else { - programName = System.IO.Path.GetFileNameWithoutExtension(path); + programName = Path.GetFileNameWithoutExtension(path); executable = new NsoExecutable(new LocalStorage(path, FileAccess.Read), programName); } @@ -215,6 +215,7 @@ public bool LoadNxo(string path) allowCodeMemoryForJit: true, programName, programId, + 0, null, executable); diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs index a6a1d87e0..fe2aaac6d 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs @@ -19,6 +19,7 @@ using Ryujinx.HLE.Loaders.Executables; using Ryujinx.HLE.Loaders.Processes.Extensions; using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Arp; using System; using System.Linq; using System.Runtime.InteropServices; @@ -229,6 +230,7 @@ public static ProcessResult LoadNsos( bool allowCodeMemoryForJit, string name, ulong programId, + byte programIndex, byte[] arguments = null, params IExecutable[] executables) { @@ -421,7 +423,7 @@ public static ProcessResult LoadNsos( // Once everything is loaded, we can load cheats. device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(programId, tamperInfo, device.TamperMachine); - return new ProcessResult( + ProcessResult processResult = new( metaLoader, applicationControlProperties, diskCacheEnabled, @@ -431,6 +433,25 @@ public static ProcessResult LoadNsos( meta.MainThreadPriority, meta.MainThreadStackSize, device.System.State.DesiredTitleLanguage); + + // Register everything in arp service. + device.System.ServiceTable.ArpWriter.AcquireRegistrar(out IRegistrar registrar); + registrar.SetApplicationControlProperty(MemoryMarshal.Cast(applicationControlProperties.ByteSpan)[0]); + // TODO: Handle Version and StorageId when it will be needed. + registrar.SetApplicationLaunchProperty(new ApplicationLaunchProperty() + { + ApplicationId = new Horizon.Sdk.Ncm.ApplicationId(programId), + Version = 0x00, + Storage = Horizon.Sdk.Ncm.StorageId.BuiltInUser, + PatchStorage = Horizon.Sdk.Ncm.StorageId.None, + ApplicationKind = ApplicationKind.Application, + }); + + device.System.ServiceTable.ArpReader.GetApplicationInstanceId(out ulong applicationInstanceId, process.Pid); + device.System.ServiceTable.ArpWriter.AcquireApplicationProcessPropertyUpdater(out IUpdater updater, applicationInstanceId); + updater.SetApplicationProcessProperty(process.Pid, new ApplicationProcessProperty() { ProgramIndex = programIndex }); + + return processResult; } public static Result LoadIntoMemory(KProcess process, IExecutable image, ulong baseAddress) diff --git a/src/Ryujinx.Horizon/Arp/ArpIpcServer.cs b/src/Ryujinx.Horizon/Arp/ArpIpcServer.cs new file mode 100644 index 000000000..6080b4750 --- /dev/null +++ b/src/Ryujinx.Horizon/Arp/ArpIpcServer.cs @@ -0,0 +1,61 @@ +using Ryujinx.Horizon.Arp.Ipc; +using Ryujinx.Horizon.Sdk.Arp; +using Ryujinx.Horizon.Sdk.Arp.Detail; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Arp +{ + class ArpIpcServer + { + private const int ArpRMaxSessionsCount = 16; + private const int ArpWMaxSessionsCount = 8; + private const int MaxSessionsCount = ArpRMaxSessionsCount + ArpWMaxSessionsCount; + + private const int PointerBufferSize = 0x1000; + private const int MaxDomains = 24; + private const int MaxDomainObjects = 32; + private const int MaxPortsCount = 2; + + private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + private ApplicationInstanceManager _applicationInstanceManager; + + public IReader Reader { get; private set; } + public IWriter Writer { get; private set; } + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _managerOptions, MaxSessionsCount); + + _applicationInstanceManager = new ApplicationInstanceManager(); + + Reader reader = new(_applicationInstanceManager); + Reader = reader; + + Writer writer = new(_applicationInstanceManager); + Writer = writer; + + _serverManager.RegisterObjectForServer(reader, ServiceName.Encode("arp:r"), ArpRMaxSessionsCount); + _serverManager.RegisterObjectForServer(writer, ServiceName.Encode("arp:w"), ArpWMaxSessionsCount); + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _applicationInstanceManager.Dispose(); + _serverManager.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Arp/ArpMain.cs b/src/Ryujinx.Horizon/Arp/ArpMain.cs new file mode 100644 index 000000000..a28baa71a --- /dev/null +++ b/src/Ryujinx.Horizon/Arp/ArpMain.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Horizon.Arp +{ + class ArpMain : IService + { + public static void Main(ServiceTable serviceTable) + { + ArpIpcServer arpIpcServer = new(); + + arpIpcServer.Initialize(); + + serviceTable.ArpReader = arpIpcServer.Reader; + serviceTable.ArpWriter = arpIpcServer.Writer; + + serviceTable.SignalServiceReady(); + + arpIpcServer.ServiceRequests(); + arpIpcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Arp/Ipc/Reader.cs b/src/Ryujinx.Horizon/Arp/Ipc/Reader.cs new file mode 100644 index 000000000..de99c2ade --- /dev/null +++ b/src/Ryujinx.Horizon/Arp/Ipc/Reader.cs @@ -0,0 +1,135 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Arp; +using Ryujinx.Horizon.Sdk.Arp.Detail; +using Ryujinx.Horizon.Sdk.Ns; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Arp.Ipc +{ + partial class Reader : IReader, IServiceObject + { + private readonly ApplicationInstanceManager _applicationInstanceManager; + + public Reader(ApplicationInstanceManager applicationInstanceManager) + { + _applicationInstanceManager = applicationInstanceManager; + } + + [CmifCommand(0)] + public Result GetApplicationLaunchProperty(out ApplicationLaunchProperty applicationLaunchProperty, ulong applicationInstanceId) + { + if (_applicationInstanceManager.Entries[applicationInstanceId] == null || !_applicationInstanceManager.Entries[applicationInstanceId].LaunchProperty.HasValue) + { + applicationLaunchProperty = default; + + return ArpResult.InvalidInstanceId; + } + + applicationLaunchProperty = _applicationInstanceManager.Entries[applicationInstanceId].LaunchProperty.Value; + + return Result.Success; + } + + [CmifCommand(1)] + public Result GetApplicationControlProperty([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias, 0x4000)] out ApplicationControlProperty applicationControlProperty, ulong applicationInstanceId) + { + if (_applicationInstanceManager.Entries[applicationInstanceId] == null || !_applicationInstanceManager.Entries[applicationInstanceId].ControlProperty.HasValue) + { + applicationControlProperty = default; + + return ArpResult.InvalidInstanceId; + } + + applicationControlProperty = _applicationInstanceManager.Entries[applicationInstanceId].ControlProperty.Value; + + return Result.Success; + } + + [CmifCommand(2)] + public Result GetApplicationProcessProperty(out ApplicationProcessProperty applicationProcessProperty, ulong applicationInstanceId) + { + if (_applicationInstanceManager.Entries[applicationInstanceId] == null || !_applicationInstanceManager.Entries[applicationInstanceId].ProcessProperty.HasValue) + { + applicationProcessProperty = default; + + return ArpResult.InvalidInstanceId; + } + + applicationProcessProperty = _applicationInstanceManager.Entries[applicationInstanceId].ProcessProperty.Value; + + return Result.Success; + } + + [CmifCommand(3)] + public Result GetApplicationInstanceId(out ulong applicationInstanceId, ulong pid) + { + applicationInstanceId = 0; + + if (pid == 0) + { + return ArpResult.InvalidPid; + } + + for (int i = 0; i < _applicationInstanceManager.Entries.Length; i++) + { + if (_applicationInstanceManager.Entries[i] != null && _applicationInstanceManager.Entries[i].Pid == pid) + { + applicationInstanceId = (ulong)i; + + return Result.Success; + } + } + + return ArpResult.InvalidPid; + } + + [CmifCommand(4)] + public Result GetApplicationInstanceUnregistrationNotifier(out IUnregistrationNotifier unregistrationNotifier) + { + unregistrationNotifier = new UnregistrationNotifier(_applicationInstanceManager); + + return Result.Success; + } + + [CmifCommand(5)] + public Result ListApplicationInstanceId(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span applicationInstanceIdList) + { + count = 0; + + if (_applicationInstanceManager.Entries[0] != null) + { + applicationInstanceIdList[count++] = 0; + } + + if (_applicationInstanceManager.Entries[1] != null) + { + applicationInstanceIdList[count++] = 1; + } + + return Result.Success; + } + + [CmifCommand(6)] + public Result GetMicroApplicationInstanceId(out ulong microApplicationInstanceId, [ClientProcessId] ulong pid) + { + return GetApplicationInstanceId(out microApplicationInstanceId, pid); + } + + [CmifCommand(7)] + public Result GetApplicationCertificate([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.FixedSize, 0x528)] out ApplicationCertificate applicationCertificate, ulong applicationInstanceId) + { + if (_applicationInstanceManager.Entries[applicationInstanceId] == null) + { + applicationCertificate = default; + + return ArpResult.InvalidInstanceId; + } + + applicationCertificate = _applicationInstanceManager.Entries[applicationInstanceId].Certificate.Value; + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Arp/Ipc/Registrar.cs b/src/Ryujinx.Horizon/Arp/Ipc/Registrar.cs new file mode 100644 index 000000000..841ab7601 --- /dev/null +++ b/src/Ryujinx.Horizon/Arp/Ipc/Registrar.cs @@ -0,0 +1,52 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Arp; +using Ryujinx.Horizon.Sdk.Arp.Detail; +using Ryujinx.Horizon.Sdk.Ns; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Arp.Ipc +{ + partial class Registrar : IRegistrar, IServiceObject + { + private readonly ApplicationInstance _applicationInstance; + + public Registrar(ApplicationInstance applicationInstance) + { + _applicationInstance = applicationInstance; + } + + [CmifCommand(0)] + public Result Issue(out ulong applicationInstanceId) + { + throw new NotImplementedException(); + } + + [CmifCommand(1)] + public Result SetApplicationLaunchProperty(ApplicationLaunchProperty applicationLaunchProperty) + { + if (_applicationInstance.LaunchProperty != null) + { + return ArpResult.DataAlreadyBound; + } + + _applicationInstance.LaunchProperty = applicationLaunchProperty; + + return Result.Success; + } + + [CmifCommand(2)] + public Result SetApplicationControlProperty([Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias | HipcBufferFlags.FixedSize, 0x4000)] in ApplicationControlProperty applicationControlProperty) + { + if (_applicationInstance.ControlProperty != null) + { + return ArpResult.DataAlreadyBound; + } + + _applicationInstance.ControlProperty = applicationControlProperty; + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Arp/Ipc/UnregistrationNotifier.cs b/src/Ryujinx.Horizon/Arp/Ipc/UnregistrationNotifier.cs new file mode 100644 index 000000000..49f2b1cce --- /dev/null +++ b/src/Ryujinx.Horizon/Arp/Ipc/UnregistrationNotifier.cs @@ -0,0 +1,25 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Arp; +using Ryujinx.Horizon.Sdk.Arp.Detail; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Arp.Ipc +{ + partial class UnregistrationNotifier : IUnregistrationNotifier, IServiceObject + { + private readonly ApplicationInstanceManager _applicationInstanceManager; + + public UnregistrationNotifier(ApplicationInstanceManager applicationInstanceManager) + { + _applicationInstanceManager = applicationInstanceManager; + } + + [CmifCommand(0)] + public Result GetReadableHandle([CopyHandle] out int readableHandle) + { + readableHandle = _applicationInstanceManager.EventHandle; + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Arp/Ipc/Updater.cs b/src/Ryujinx.Horizon/Arp/Ipc/Updater.cs new file mode 100644 index 000000000..f7531d712 --- /dev/null +++ b/src/Ryujinx.Horizon/Arp/Ipc/Updater.cs @@ -0,0 +1,74 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Arp; +using Ryujinx.Horizon.Sdk.Arp.Detail; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Arp.Ipc +{ + partial class Updater : IUpdater, IServiceObject + { + private readonly ApplicationInstanceManager _applicationInstanceManager; + private readonly ulong _applicationInstanceId; + private readonly bool _forCertificate; + + public Updater(ApplicationInstanceManager applicationInstanceManager, ulong applicationInstanceId, bool forCertificate) + { + _applicationInstanceManager = applicationInstanceManager; + _applicationInstanceId = applicationInstanceId; + _forCertificate = forCertificate; + } + + [CmifCommand(0)] + public Result Issue() + { + throw new NotImplementedException(); + } + + [CmifCommand(1)] + public Result SetApplicationProcessProperty(ulong pid, ApplicationProcessProperty applicationProcessProperty) + { + if (_forCertificate) + { + return ArpResult.DataAlreadyBound; + } + + if (pid == 0) + { + return ArpResult.InvalidPid; + } + + _applicationInstanceManager.Entries[_applicationInstanceId].Pid = pid; + _applicationInstanceManager.Entries[_applicationInstanceId].ProcessProperty = applicationProcessProperty; + + return Result.Success; + } + + [CmifCommand(2)] + public Result DeleteApplicationProcessProperty() + { + if (_forCertificate) + { + return ArpResult.DataAlreadyBound; + } + + _applicationInstanceManager.Entries[_applicationInstanceId].ProcessProperty = new ApplicationProcessProperty(); + + return Result.Success; + } + + [CmifCommand(3)] + public Result SetApplicationCertificate([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ApplicationCertificate applicationCertificate) + { + if (!_forCertificate) + { + return ArpResult.DataAlreadyBound; + } + + _applicationInstanceManager.Entries[_applicationInstanceId].Certificate = applicationCertificate; + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Arp/Ipc/Writer.cs b/src/Ryujinx.Horizon/Arp/Ipc/Writer.cs new file mode 100644 index 000000000..29c98b779 --- /dev/null +++ b/src/Ryujinx.Horizon/Arp/Ipc/Writer.cs @@ -0,0 +1,75 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Arp; +using Ryujinx.Horizon.Sdk.Arp.Detail; +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Arp.Ipc +{ + partial class Writer : IWriter, IServiceObject + { + private readonly ApplicationInstanceManager _applicationInstanceManager; + + public Writer(ApplicationInstanceManager applicationInstanceManager) + { + _applicationInstanceManager = applicationInstanceManager; + } + + [CmifCommand(0)] + public Result AcquireRegistrar(out IRegistrar registrar) + { + if (_applicationInstanceManager.Entries[0] != null) + { + if (_applicationInstanceManager.Entries[1] != null) + { + registrar = null; + + return ArpResult.NoFreeInstance; + } + else + { + _applicationInstanceManager.Entries[1] = new ApplicationInstance(); + + registrar = new Registrar(_applicationInstanceManager.Entries[1]); + } + } + else + { + _applicationInstanceManager.Entries[0] = new ApplicationInstance(); + + registrar = new Registrar(_applicationInstanceManager.Entries[0]); + } + + return Result.Success; + } + + [CmifCommand(1)] + public Result UnregisterApplicationInstance(ulong applicationInstanceId) + { + if (_applicationInstanceManager.Entries[applicationInstanceId] != null) + { + _applicationInstanceManager.Entries[applicationInstanceId] = null; + } + + Os.SignalSystemEvent(ref _applicationInstanceManager.SystemEvent); + + return Result.Success; + } + + [CmifCommand(2)] + public Result AcquireApplicationProcessPropertyUpdater(out IUpdater updater, ulong applicationInstanceId) + { + updater = new Updater(_applicationInstanceManager, applicationInstanceId, false); + + return Result.Success; + } + + [CmifCommand(3)] + public Result AcquireApplicationCertificateUpdater(out IUpdater updater, ulong applicationInstanceId) + { + updater = new Updater(_applicationInstanceManager, applicationInstanceId, true); + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs b/src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs index c165f46fa..4ed7dd48e 100644 --- a/src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs +++ b/src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs @@ -5,6 +5,7 @@ using Ryujinx.Horizon.Common; using Ryujinx.Horizon.Prepo.Types; using Ryujinx.Horizon.Sdk.Account; +using Ryujinx.Horizon.Sdk.Arp; using Ryujinx.Horizon.Sdk.Prepo; using Ryujinx.Horizon.Sdk.Sf; using Ryujinx.Horizon.Sdk.Sf.Hipc; @@ -22,14 +23,16 @@ enum PlayReportKind System, } + private readonly ArpApi _arp; private readonly PrepoServicePermissionLevel _permissionLevel; private ulong _systemSessionId; private bool _immediateTransmissionEnabled; private bool _userAgreementCheckEnabled = true; - public PrepoService(PrepoServicePermissionLevel permissionLevel) + public PrepoService(ArpApi arp, PrepoServicePermissionLevel permissionLevel) { + _arp = arp; _permissionLevel = permissionLevel; } @@ -165,7 +168,7 @@ public Result SetUserAgreementCheckEnabled(bool enabled) return PrepoResult.PermissionDenied; } - private static Result ProcessPlayReport(PlayReportKind playReportKind, ReadOnlySpan gameRoomBuffer, ReadOnlySpan reportBuffer, ulong pid, Uid userId, bool withUserId = false, ApplicationId applicationId = default) + private Result ProcessPlayReport(PlayReportKind playReportKind, ReadOnlySpan gameRoomBuffer, ReadOnlySpan reportBuffer, ulong pid, Uid userId, bool withUserId = false, ApplicationId applicationId = default) { if (withUserId) { @@ -199,8 +202,8 @@ private static Result ProcessPlayReport(PlayReportKind playReportKind, ReadOnlyS builder.AppendLine("PlayReport log:"); builder.AppendLine($" Kind: {playReportKind}"); - // NOTE: The service calls arp:r using the pid to get the application id, if it fails PrepoResult.InvalidPid is returned. - // Reports are stored internally and an event is signaled to transmit them. + // NOTE: Reports are stored internally and an event is signaled to transmit them. + if (pid != 0) { builder.AppendLine($" Pid: {pid}"); @@ -210,6 +213,16 @@ private static Result ProcessPlayReport(PlayReportKind playReportKind, ReadOnlyS builder.AppendLine($" ApplicationId: {applicationId}"); } + Result result = _arp.GetApplicationInstanceId(out ulong applicationInstanceId, pid); + if (result.IsFailure) + { + return PrepoResult.InvalidPid; + } + + _arp.GetApplicationLaunchProperty(out ApplicationLaunchProperty applicationLaunchProperty, applicationInstanceId).AbortOnFailure(); + + builder.AppendLine($" ApplicationVersion: {applicationLaunchProperty.Version}"); + if (!userId.IsNull) { builder.AppendLine($" UserId: {userId}"); diff --git a/src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs b/src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs index fd3f86ff9..1902cde23 100644 --- a/src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs +++ b/src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs @@ -1,4 +1,5 @@ using Ryujinx.Horizon.Prepo.Types; +using Ryujinx.Horizon.Sdk.Arp; using Ryujinx.Horizon.Sdk.Sf.Hipc; using Ryujinx.Horizon.Sdk.Sm; @@ -17,16 +18,19 @@ class PrepoIpcServer private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); private SmApi _sm; + private ArpApi _arp; private PrepoServerManager _serverManager; public void Initialize() { HeapAllocator allocator = new(); + _arp = new ArpApi(allocator); + _sm = new SmApi(); _sm.Initialize().AbortOnFailure(); - _serverManager = new PrepoServerManager(allocator, _sm, MaxPortsCount, _managerOptions, TotalMaxSessionsCount); + _serverManager = new PrepoServerManager(allocator, _sm, _arp, MaxPortsCount, _managerOptions, TotalMaxSessionsCount); #pragma warning disable IDE0055 // Disable formatting _serverManager.RegisterServer((int)PrepoPortIndex.Admin, ServiceName.Encode("prepo:a"), MaxSessionsCount); // 1.0.0-5.1.0 @@ -45,6 +49,7 @@ public void ServiceRequests() public void Shutdown() { + _arp.Dispose(); _serverManager.Dispose(); } } diff --git a/src/Ryujinx.Horizon/Prepo/PrepoServerManager.cs b/src/Ryujinx.Horizon/Prepo/PrepoServerManager.cs index 0c1c782f6..8cac44c8f 100644 --- a/src/Ryujinx.Horizon/Prepo/PrepoServerManager.cs +++ b/src/Ryujinx.Horizon/Prepo/PrepoServerManager.cs @@ -1,6 +1,7 @@ using Ryujinx.Horizon.Common; using Ryujinx.Horizon.Prepo.Ipc; using Ryujinx.Horizon.Prepo.Types; +using Ryujinx.Horizon.Sdk.Arp; using Ryujinx.Horizon.Sdk.Sf.Hipc; using Ryujinx.Horizon.Sdk.Sm; using System; @@ -9,8 +10,11 @@ namespace Ryujinx.Horizon.Prepo { class PrepoServerManager : ServerManager { - public PrepoServerManager(HeapAllocator allocator, SmApi sm, int maxPorts, ManagerOptions options, int maxSessions) : base(allocator, sm, maxPorts, options, maxSessions) + private readonly ArpApi _arp; + + public PrepoServerManager(HeapAllocator allocator, SmApi sm, ArpApi arp, int maxPorts, ManagerOptions options, int maxSessions) : base(allocator, sm, maxPorts, options, maxSessions) { + _arp = arp; } protected override Result OnNeedsToAccept(int portIndex, Server server) @@ -18,12 +22,12 @@ protected override Result OnNeedsToAccept(int portIndex, Server server) return (PrepoPortIndex)portIndex switch { #pragma warning disable IDE0055 // Disable formatting - PrepoPortIndex.Admin => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.Admin)), - PrepoPortIndex.Admin2 => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.Admin)), - PrepoPortIndex.Manager => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.Manager)), - PrepoPortIndex.User => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.User)), - PrepoPortIndex.System => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.System)), - PrepoPortIndex.Debug => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.Debug)), + PrepoPortIndex.Admin => AcceptImpl(server, new PrepoService(_arp, PrepoServicePermissionLevel.Admin)), + PrepoPortIndex.Admin2 => AcceptImpl(server, new PrepoService(_arp, PrepoServicePermissionLevel.Admin)), + PrepoPortIndex.Manager => AcceptImpl(server, new PrepoService(_arp, PrepoServicePermissionLevel.Manager)), + PrepoPortIndex.User => AcceptImpl(server, new PrepoService(_arp, PrepoServicePermissionLevel.User)), + PrepoPortIndex.System => AcceptImpl(server, new PrepoService(_arp, PrepoServicePermissionLevel.System)), + PrepoPortIndex.Debug => AcceptImpl(server, new PrepoService(_arp, PrepoServicePermissionLevel.Debug)), _ => throw new ArgumentOutOfRangeException(nameof(portIndex)), #pragma warning restore IDE0055 }; diff --git a/src/Ryujinx.Horizon/Sdk/Arp/ApplicationCertificate.cs b/src/Ryujinx.Horizon/Sdk/Arp/ApplicationCertificate.cs new file mode 100644 index 000000000..d60d337d3 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/ApplicationCertificate.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + [StructLayout(LayoutKind.Sequential, Size = 0x528)] + public struct ApplicationCertificate + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/ApplicationKind.cs b/src/Ryujinx.Horizon/Sdk/Arp/ApplicationKind.cs new file mode 100644 index 000000000..586e6a98a --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/ApplicationKind.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Horizon.Sdk.Arp +{ + public enum ApplicationKind : byte + { + Application, + MicroApplication, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/ApplicationLaunchProperty.cs b/src/Ryujinx.Horizon/Sdk/Arp/ApplicationLaunchProperty.cs new file mode 100644 index 000000000..00b818321 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/ApplicationLaunchProperty.cs @@ -0,0 +1,14 @@ +using Ryujinx.Horizon.Sdk.Ncm; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + public struct ApplicationLaunchProperty + { + public ApplicationId ApplicationId; + public uint Version; + public StorageId Storage; + public StorageId PatchStorage; + public ApplicationKind ApplicationKind; + public byte Padding; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/ApplicationProcessProperty.cs b/src/Ryujinx.Horizon/Sdk/Arp/ApplicationProcessProperty.cs new file mode 100644 index 000000000..13d222a12 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/ApplicationProcessProperty.cs @@ -0,0 +1,10 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + public struct ApplicationProcessProperty + { + public byte ProgramIndex; + public Array15 Unknown; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/ArpApi.cs b/src/Ryujinx.Horizon/Sdk/Arp/ArpApi.cs new file mode 100644 index 000000000..b0acc0062 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/ArpApi.cs @@ -0,0 +1,130 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Ns; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + class ArpApi : IDisposable + { + private const string ArpRName = "arp:r"; + + private readonly HeapAllocator _allocator; + private int _sessionHandle; + + public ArpApi(HeapAllocator allocator) + { + _allocator = allocator; + } + + private void InitializeArpRService() + { + if (_sessionHandle == 0) + { + using var smApi = new SmApi(); + + smApi.Initialize(); + smApi.GetServiceHandle(out _sessionHandle, ServiceName.Encode(ArpRName)).AbortOnFailure(); + } + } + + public Result GetApplicationInstanceId(out ulong applicationInstanceId, ulong applicationPid) + { + Span data = stackalloc byte[8]; + SpanWriter writer = new(data); + + writer.Write(applicationPid); + + InitializeArpRService(); + + Result result = ServiceUtil.SendRequest(out CmifResponse response, _sessionHandle, 3, sendPid: false, data); + if (result.IsFailure) + { + applicationInstanceId = 0; + + return result; + } + + SpanReader reader = new(response.Data); + + applicationInstanceId = reader.Read(); + + return Result.Success; + } + + public Result GetApplicationLaunchProperty(out ApplicationLaunchProperty applicationLaunchProperty, ulong applicationInstanceId) + { + applicationLaunchProperty = default; + + Span data = stackalloc byte[8]; + SpanWriter writer = new(data); + + writer.Write(applicationInstanceId); + + InitializeArpRService(); + + Result result = ServiceUtil.SendRequest(out CmifResponse response, _sessionHandle, 0, sendPid: false, data); + if (result.IsFailure) + { + return result; + } + + SpanReader reader = new(response.Data); + + applicationLaunchProperty = reader.Read(); + + return Result.Success; + } + + public Result GetApplicationControlProperty(out ApplicationControlProperty applicationControlProperty, ulong applicationInstanceId) + { + applicationControlProperty = default; + + Span data = stackalloc byte[8]; + SpanWriter writer = new(data); + + writer.Write(applicationInstanceId); + + ulong bufferSize = (ulong)Unsafe.SizeOf(); + ulong bufferAddress = _allocator.Allocate(bufferSize); + + InitializeArpRService(); + + Result result = ServiceUtil.SendRequest( + out CmifResponse response, + _sessionHandle, + 1, + sendPid: false, + data, + stackalloc[] { HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.FixedSize }, + stackalloc[] { new PointerAndSize(bufferAddress, bufferSize) }); + + if (result.IsFailure) + { + return result; + } + + applicationControlProperty = HorizonStatic.AddressSpace.Read(bufferAddress); + + _allocator.Free(bufferAddress, bufferSize); + + return Result.Success; + } + + public void Dispose() + { + if (_sessionHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_sessionHandle); + + _sessionHandle = 0; + } + + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/ArpResult.cs b/src/Ryujinx.Horizon/Sdk/Arp/ArpResult.cs new file mode 100644 index 000000000..5de07871d --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/ArpResult.cs @@ -0,0 +1,17 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + static class ArpResult + { + private const int ModuleId = 157; + + public static Result InvalidArgument => new(ModuleId, 30); + public static Result InvalidPid => new(ModuleId, 31); + public static Result InvalidPointer => new(ModuleId, 32); + public static Result DataAlreadyBound => new(ModuleId, 42); + public static Result AllocationFailed => new(ModuleId, 63); + public static Result NoFreeInstance => new(ModuleId, 101); + public static Result InvalidInstanceId => new(ModuleId, 102); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/Detail/ApplicationInstance.cs b/src/Ryujinx.Horizon/Sdk/Arp/Detail/ApplicationInstance.cs new file mode 100644 index 000000000..5eb0ab184 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/Detail/ApplicationInstance.cs @@ -0,0 +1,13 @@ +using Ryujinx.Horizon.Sdk.Ns; + +namespace Ryujinx.Horizon.Sdk.Arp.Detail +{ + class ApplicationInstance + { + public ulong Pid { get; set; } + public ApplicationLaunchProperty? LaunchProperty { get; set; } + public ApplicationProcessProperty? ProcessProperty { get; set; } + public ApplicationControlProperty? ControlProperty { get; set; } + public ApplicationCertificate? Certificate { get; set; } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/Detail/ApplicationInstanceManager.cs b/src/Ryujinx.Horizon/Sdk/Arp/Detail/ApplicationInstanceManager.cs new file mode 100644 index 000000000..18c993ce4 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/Detail/ApplicationInstanceManager.cs @@ -0,0 +1,31 @@ +using Ryujinx.Horizon.Sdk.OsTypes; +using System; +using System.Threading; + +namespace Ryujinx.Horizon.Sdk.Arp.Detail +{ + class ApplicationInstanceManager : IDisposable + { + private int _disposalState; + + public SystemEventType SystemEvent; + public int EventHandle; + + public readonly ApplicationInstance[] Entries = new ApplicationInstance[2]; + + public ApplicationInstanceManager() + { + Os.CreateSystemEvent(out SystemEvent, EventClearMode.ManualClear, true).AbortOnFailure(); + + EventHandle = Os.GetReadableHandleOfSystemEvent(ref SystemEvent); + } + + public void Dispose() + { + if (EventHandle != 0 && Interlocked.Exchange(ref _disposalState, 1) == 0) + { + Os.DestroySystemEvent(ref SystemEvent); + } + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/IReader.cs b/src/Ryujinx.Horizon/Sdk/Arp/IReader.cs new file mode 100644 index 000000000..ef78f7fd6 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/IReader.cs @@ -0,0 +1,18 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Ns; +using System; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + public interface IReader + { + public Result GetApplicationLaunchProperty(out ApplicationLaunchProperty applicationLaunchProperty, ulong applicationInstanceId); + public Result GetApplicationControlProperty(out ApplicationControlProperty applicationControlProperty, ulong applicationInstanceId); + public Result GetApplicationProcessProperty(out ApplicationProcessProperty applicationControlProperty, ulong applicationInstanceId); + public Result GetApplicationInstanceId(out ulong applicationInstanceId, ulong pid); + public Result GetApplicationInstanceUnregistrationNotifier(out IUnregistrationNotifier unregistrationNotifier); + public Result ListApplicationInstanceId(out int count, Span applicationInstanceIdList); + public Result GetMicroApplicationInstanceId(out ulong MicroApplicationInstanceId, ulong pid); + public Result GetApplicationCertificate(out ApplicationCertificate applicationCertificate, ulong applicationInstanceId); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/IRegistrar.cs b/src/Ryujinx.Horizon/Sdk/Arp/IRegistrar.cs new file mode 100644 index 000000000..467f3dbd3 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/IRegistrar.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Ns; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + public interface IRegistrar + { + public Result Issue(out ulong applicationInstanceId); + public Result SetApplicationLaunchProperty(ApplicationLaunchProperty applicationLaunchProperty); + public Result SetApplicationControlProperty(in ApplicationControlProperty applicationControlProperty); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/IUnregistrationNotifier.cs b/src/Ryujinx.Horizon/Sdk/Arp/IUnregistrationNotifier.cs new file mode 100644 index 000000000..24b9807d8 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/IUnregistrationNotifier.cs @@ -0,0 +1,9 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + public interface IUnregistrationNotifier + { + public Result GetReadableHandle(out int readableHandle); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/IUpdater.cs b/src/Ryujinx.Horizon/Sdk/Arp/IUpdater.cs new file mode 100644 index 000000000..f9beeb690 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/IUpdater.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + public interface IUpdater + { + public Result Issue(); + public Result SetApplicationProcessProperty(ulong pid, ApplicationProcessProperty applicationProcessProperty); + public Result DeleteApplicationProcessProperty(); + public Result SetApplicationCertificate(ApplicationCertificate applicationCertificate); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/IWriter.cs b/src/Ryujinx.Horizon/Sdk/Arp/IWriter.cs new file mode 100644 index 000000000..b3e000e1e --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/IWriter.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + public interface IWriter + { + public Result AcquireRegistrar(out IRegistrar registrar); + public Result UnregisterApplicationInstance(ulong applicationInstanceId); + public Result AcquireApplicationProcessPropertyUpdater(out IUpdater updater, ulong applicationInstanceId); + public Result AcquireApplicationCertificateUpdater(out IUpdater updater, ulong applicationInstanceId); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ncm/ApplicationId.cs b/src/Ryujinx.Horizon/Sdk/Ncm/ApplicationId.cs index 4c5e76e6f..24b7d9cab 100644 --- a/src/Ryujinx.Horizon/Sdk/Ncm/ApplicationId.cs +++ b/src/Ryujinx.Horizon/Sdk/Ncm/ApplicationId.cs @@ -1,6 +1,6 @@ namespace Ryujinx.Horizon.Sdk.Ncm { - readonly struct ApplicationId + public readonly struct ApplicationId { public readonly ulong Id; diff --git a/src/Ryujinx.Horizon/Sdk/Ncm/StorageId.cs b/src/Ryujinx.Horizon/Sdk/Ncm/StorageId.cs new file mode 100644 index 000000000..e2fb32505 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ncm/StorageId.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Horizon.Sdk.Ncm +{ + public enum StorageId : byte + { + None, + Host, + GameCard, + BuiltInSystem, + BuiltInUser, + SdCard, + Any, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ns/ApplicationControlProperty.cs b/src/Ryujinx.Horizon/Sdk/Ns/ApplicationControlProperty.cs new file mode 100644 index 000000000..12c19168d --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ns/ApplicationControlProperty.cs @@ -0,0 +1,309 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Horizon.Sdk.Arp.Detail; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Horizon.Sdk.Ns +{ + public struct ApplicationControlProperty + { + public Array16 Title; + public Array37 Isbn; + public StartupUserAccountValue StartupUserAccount; + public UserAccountSwitchLockValue UserAccountSwitchLock; + public AddOnContentRegistrationTypeValue AddOnContentRegistrationType; + public AttributeFlagValue AttributeFlag; + public uint SupportedLanguageFlag; + public ParentalControlFlagValue ParentalControlFlag; + public ScreenshotValue Screenshot; + public VideoCaptureValue VideoCapture; + public DataLossConfirmationValue DataLossConfirmation; + public PlayLogPolicyValue PlayLogPolicy; + public ulong PresenceGroupId; + public Array32 RatingAge; + public Array16 DisplayVersion; + public ulong AddOnContentBaseId; + public ulong SaveDataOwnerId; + public long UserAccountSaveDataSize; + public long UserAccountSaveDataJournalSize; + public long DeviceSaveDataSize; + public long DeviceSaveDataJournalSize; + public long BcatDeliveryCacheStorageSize; + public Array8 ApplicationErrorCodeCategory; + public Array8 LocalCommunicationId; + public LogoTypeValue LogoType; + public LogoHandlingValue LogoHandling; + public RuntimeAddOnContentInstallValue RuntimeAddOnContentInstall; + public RuntimeParameterDeliveryValue RuntimeParameterDelivery; + public Array2 Reserved30F4; + public CrashReportValue CrashReport; + public HdcpValue Hdcp; + public ulong SeedForPseudoDeviceId; + public Array65 BcatPassphrase; + public StartupUserAccountOptionFlagValue StartupUserAccountOption; + public Array6 ReservedForUserAccountSaveDataOperation; + public long UserAccountSaveDataSizeMax; + public long UserAccountSaveDataJournalSizeMax; + public long DeviceSaveDataSizeMax; + public long DeviceSaveDataJournalSizeMax; + public long TemporaryStorageSize; + public long CacheStorageSize; + public long CacheStorageJournalSize; + public long CacheStorageDataAndJournalSizeMax; + public ushort CacheStorageIndexMax; + public byte Reserved318A; + public byte RuntimeUpgrade; + public uint SupportingLimitedLicenses; + public Array16 PlayLogQueryableApplicationId; + public PlayLogQueryCapabilityValue PlayLogQueryCapability; + public RepairFlagValue RepairFlag; + public byte ProgramIndex; + public RequiredNetworkServiceLicenseOnLaunchValue RequiredNetworkServiceLicenseOnLaunchFlag; + public Array4 Reserved3214; + public ApplicationNeighborDetectionClientConfiguration NeighborDetectionClientConfiguration; + public ApplicationJitConfiguration JitConfiguration; + public RequiredAddOnContentsSetBinaryDescriptor RequiredAddOnContentsSetBinaryDescriptors; + public PlayReportPermissionValue PlayReportPermission; + public CrashScreenshotForProdValue CrashScreenshotForProd; + public CrashScreenshotForDevValue CrashScreenshotForDev; + public byte ContentsAvailabilityTransitionPolicy; + public Array4 Reserved3404; + public AccessibleLaunchRequiredVersionValue AccessibleLaunchRequiredVersion; + public ByteArray3000 Reserved3448; + + public readonly string IsbnString => Encoding.UTF8.GetString(Isbn.AsSpan()).TrimEnd('\0'); + public readonly string DisplayVersionString => Encoding.UTF8.GetString(DisplayVersion.AsSpan()).TrimEnd('\0'); + public readonly string ApplicationErrorCodeCategoryString => Encoding.UTF8.GetString(ApplicationErrorCodeCategory.AsSpan()).TrimEnd('\0'); + public readonly string BcatPassphraseString => Encoding.UTF8.GetString(BcatPassphrase.AsSpan()).TrimEnd('\0'); + + public struct ApplicationTitle + { + public ByteArray512 Name; + public Array256 Publisher; + + public readonly string NameString => Encoding.UTF8.GetString(Name.AsSpan()).TrimEnd('\0'); + public readonly string PublisherString => Encoding.UTF8.GetString(Publisher.AsSpan()).TrimEnd('\0'); + } + + public struct ApplicationNeighborDetectionClientConfiguration + { + public ApplicationNeighborDetectionGroupConfiguration SendGroupConfiguration; + public Array16 ReceivableGroupConfigurations; + } + + public struct ApplicationNeighborDetectionGroupConfiguration + { + public ulong GroupId; + public Array16 Key; + } + + public struct ApplicationJitConfiguration + { + public JitConfigurationFlag Flags; + public long MemorySize; + } + + public struct RequiredAddOnContentsSetBinaryDescriptor + { + public Array32 Descriptors; + } + + public struct AccessibleLaunchRequiredVersionValue + { + public Array8 ApplicationId; + } + + public enum Language + { + AmericanEnglish = 0, + BritishEnglish = 1, + Japanese = 2, + French = 3, + German = 4, + LatinAmericanSpanish = 5, + Spanish = 6, + Italian = 7, + Dutch = 8, + CanadianFrench = 9, + Portuguese = 10, + Russian = 11, + Korean = 12, + TraditionalChinese = 13, + SimplifiedChinese = 14, + BrazilianPortuguese = 15, + } + + public enum Organization + { + CERO = 0, + GRACGCRB = 1, + GSRMR = 2, + ESRB = 3, + ClassInd = 4, + USK = 5, + PEGI = 6, + PEGIPortugal = 7, + PEGIBBFC = 8, + Russian = 9, + ACB = 10, + OFLC = 11, + IARCGeneric = 12, + } + + public enum StartupUserAccountValue : byte + { + None = 0, + Required = 1, + RequiredWithNetworkServiceAccountAvailable = 2, + } + + public enum UserAccountSwitchLockValue : byte + { + Disable = 0, + Enable = 1, + } + + public enum AddOnContentRegistrationTypeValue : byte + { + AllOnLaunch = 0, + OnDemand = 1, + } + + [Flags] + public enum AttributeFlagValue + { + None = 0, + Demo = 1 << 0, + RetailInteractiveDisplay = 1 << 1, + } + + public enum ParentalControlFlagValue + { + None = 0, + FreeCommunication = 1, + } + + public enum ScreenshotValue : byte + { + Allow = 0, + Deny = 1, + } + + public enum VideoCaptureValue : byte + { + Disable = 0, + Manual = 1, + Enable = 2, + } + + public enum DataLossConfirmationValue : byte + { + None = 0, + Required = 1, + } + + public enum PlayLogPolicyValue : byte + { + Open = 0, + LogOnly = 1, + None = 2, + Closed = 3, + All = Open, + } + + public enum LogoTypeValue : byte + { + LicensedByNintendo = 0, + DistributedByNintendo = 1, + Nintendo = 2, + } + + public enum LogoHandlingValue : byte + { + Auto = 0, + Manual = 1, + } + + public enum RuntimeAddOnContentInstallValue : byte + { + Deny = 0, + AllowAppend = 1, + AllowAppendButDontDownloadWhenUsingNetwork = 2, + } + + public enum RuntimeParameterDeliveryValue : byte + { + Always = 0, + AlwaysIfUserStateMatched = 1, + OnRestart = 2, + } + + public enum CrashReportValue : byte + { + Deny = 0, + Allow = 1, + } + + public enum HdcpValue : byte + { + None = 0, + Required = 1, + } + + [Flags] + public enum StartupUserAccountOptionFlagValue : byte + { + None = 0, + IsOptional = 1 << 0, + } + + public enum PlayLogQueryCapabilityValue : byte + { + None = 0, + WhiteList = 1, + All = 2, + } + + [Flags] + public enum RepairFlagValue : byte + { + None = 0, + SuppressGameCardAccess = 1 << 0, + } + + [Flags] + public enum RequiredNetworkServiceLicenseOnLaunchValue : byte + { + None = 0, + Common = 1 << 0, + } + + [Flags] + public enum JitConfigurationFlag : ulong + { + None = 0, + Enabled = 1 << 0, + } + + [Flags] + public enum PlayReportPermissionValue : byte + { + None = 0, + TargetMarketing = 1 << 0, + } + + public enum CrashScreenshotForProdValue : byte + { + Deny = 0, + Allow = 1, + } + + public enum CrashScreenshotForDevValue : byte + { + Deny = 0, + Allow = 1, + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/ServiceUtil.cs b/src/Ryujinx.Horizon/Sdk/ServiceUtil.cs index ccd6c93a6..5527c1e35 100644 --- a/src/Ryujinx.Horizon/Sdk/ServiceUtil.cs +++ b/src/Ryujinx.Horizon/Sdk/ServiceUtil.cs @@ -35,5 +35,254 @@ public static Result SendRequest(out CmifResponse response, int sessionHandle, u return CmifMessage.ParseResponse(out response, HorizonStatic.AddressSpace.GetWritableRegion(tlsAddress, tlsSize).Memory.Span, false, 0); } + + public static Result SendRequest( + out CmifResponse response, + int sessionHandle, + uint requestId, + bool sendPid, + scoped ReadOnlySpan data, + ReadOnlySpan bufferFlags, + ReadOnlySpan buffers) + { + ulong tlsAddress = HorizonStatic.ThreadContext.TlsAddress; + int tlsSize = Api.TlsMessageBufferSize; + + using (var tlsRegion = HorizonStatic.AddressSpace.GetWritableRegion(tlsAddress, tlsSize)) + { + CmifRequestFormat format = new() + { + DataSize = data.Length, + RequestId = requestId, + SendPid = sendPid, + }; + + for (int index = 0; index < bufferFlags.Length; index++) + { + FormatProcessBuffer(ref format, bufferFlags[index]); + } + + CmifRequest request = CmifMessage.CreateRequest(tlsRegion.Memory.Span, format); + + for (int index = 0; index < buffers.Length; index++) + { + RequestProcessBuffer(ref request, buffers[index], bufferFlags[index]); + } + + data.CopyTo(request.Data); + } + + Result result = HorizonStatic.Syscall.SendSyncRequest(sessionHandle); + + if (result.IsFailure) + { + response = default; + + return result; + } + + return CmifMessage.ParseResponse(out response, HorizonStatic.AddressSpace.GetWritableRegion(tlsAddress, tlsSize).Memory.Span, false, 0); + } + + private static void FormatProcessBuffer(ref CmifRequestFormat format, HipcBufferFlags flags) + { + if (flags == 0) + { + return; + } + + bool isIn = flags.HasFlag(HipcBufferFlags.In); + bool isOut = flags.HasFlag(HipcBufferFlags.Out); + + if (flags.HasFlag(HipcBufferFlags.AutoSelect)) + { + if (isIn) + { + format.InAutoBuffersCount++; + } + + if (isOut) + { + format.OutAutoBuffersCount++; + } + } + else if (flags.HasFlag(HipcBufferFlags.Pointer)) + { + if (isIn) + { + format.InPointersCount++; + } + + if (isOut) + { + if (flags.HasFlag(HipcBufferFlags.FixedSize)) + { + format.OutFixedPointersCount++; + } + else + { + format.OutPointersCount++; + } + } + } + else if (flags.HasFlag(HipcBufferFlags.MapAlias)) + { + if (isIn && isOut) + { + format.InOutBuffersCount++; + } + else if (isIn) + { + format.InBuffersCount++; + } + else + { + format.OutBuffersCount++; + } + } + } + + private static void RequestProcessBuffer(ref CmifRequest request, PointerAndSize buffer, HipcBufferFlags flags) + { + if (flags == 0) + { + return; + } + + bool isIn = flags.HasFlag(HipcBufferFlags.In); + bool isOut = flags.HasFlag(HipcBufferFlags.Out); + + if (flags.HasFlag(HipcBufferFlags.AutoSelect)) + { + HipcBufferMode mode = HipcBufferMode.Normal; + + if (flags.HasFlag(HipcBufferFlags.MapTransferAllowsNonSecure)) + { + mode = HipcBufferMode.NonSecure; + } + + if (flags.HasFlag(HipcBufferFlags.MapTransferAllowsNonDevice)) + { + mode = HipcBufferMode.NonDevice; + } + + if (isIn) + { + RequestInAutoBuffer(ref request, buffer.Address, buffer.Size, mode); + } + + if (isOut) + { + RequestOutAutoBuffer(ref request, buffer.Address, buffer.Size, mode); + } + } + else if (flags.HasFlag(HipcBufferFlags.Pointer)) + { + if (isIn) + { + RequestInPointer(ref request, buffer.Address, buffer.Size); + } + + if (isOut) + { + if (flags.HasFlag(HipcBufferFlags.FixedSize)) + { + RequestOutFixedPointer(ref request, buffer.Address, buffer.Size); + } + else + { + RequestOutPointer(ref request, buffer.Address, buffer.Size); + } + } + } + else if (flags.HasFlag(HipcBufferFlags.MapAlias)) + { + HipcBufferMode mode = HipcBufferMode.Normal; + + if (flags.HasFlag(HipcBufferFlags.MapTransferAllowsNonSecure)) + { + mode = HipcBufferMode.NonSecure; + } + + if (flags.HasFlag(HipcBufferFlags.MapTransferAllowsNonDevice)) + { + mode = HipcBufferMode.NonDevice; + } + + if (isIn && isOut) + { + RequestInOutBuffer(ref request, buffer.Address, buffer.Size, mode); + } + else if (isIn) + { + RequestInBuffer(ref request, buffer.Address, buffer.Size, mode); + } + else + { + RequestOutBuffer(ref request, buffer.Address, buffer.Size, mode); + } + } + } + + private static void RequestInAutoBuffer(ref CmifRequest request, ulong bufferAddress, ulong bufferSize, HipcBufferMode mode) + { + if (request.ServerPointerSize != 0 && bufferSize <= (ulong)request.ServerPointerSize) + { + RequestInPointer(ref request, bufferAddress, bufferSize); + RequestInBuffer(ref request, 0UL, 0UL, mode); + } + else + { + RequestInPointer(ref request, 0UL, 0UL); + RequestInBuffer(ref request, bufferAddress, bufferSize, mode); + } + } + + private static void RequestOutAutoBuffer(ref CmifRequest request, ulong bufferAddress, ulong bufferSize, HipcBufferMode mode) + { + if (request.ServerPointerSize != 0 && bufferSize <= (ulong)request.ServerPointerSize) + { + RequestOutPointer(ref request, bufferAddress, bufferSize); + RequestOutBuffer(ref request, 0UL, 0UL, mode); + } + else + { + RequestOutPointer(ref request, 0UL, 0UL); + RequestOutBuffer(ref request, bufferAddress, bufferSize, mode); + } + } + + private static void RequestInBuffer(ref CmifRequest request, ulong bufferAddress, ulong bufferSize, HipcBufferMode mode) + { + request.Hipc.SendBuffers[request.SendBufferIndex++] = new HipcBufferDescriptor(bufferAddress, bufferSize, mode); + } + + private static void RequestOutBuffer(ref CmifRequest request, ulong bufferAddress, ulong bufferSize, HipcBufferMode mode) + { + request.Hipc.ReceiveBuffers[request.RecvBufferIndex++] = new HipcBufferDescriptor(bufferAddress, bufferSize, mode); + } + + private static void RequestInOutBuffer(ref CmifRequest request, ulong bufferAddress, ulong bufferSize, HipcBufferMode mode) + { + request.Hipc.ExchangeBuffers[request.ExchBufferIndex++] = new HipcBufferDescriptor(bufferAddress, bufferSize, mode); + } + + private static void RequestInPointer(ref CmifRequest request, ulong bufferAddress, ulong bufferSize) + { + request.Hipc.SendStatics[request.SendStaticIndex++] = new HipcStaticDescriptor(bufferAddress, (ushort)bufferSize, request.CurrentInPointerId++); + request.ServerPointerSize -= (int)bufferSize; + } + + private static void RequestOutFixedPointer(ref CmifRequest request, ulong bufferAddress, ulong bufferSize) + { + request.Hipc.ReceiveList[request.RecvListIndex++] = new HipcReceiveListEntry(bufferAddress, (ushort)bufferSize); + request.ServerPointerSize -= (int)bufferSize; + } + + private static void RequestOutPointer(ref CmifRequest request, ulong bufferAddress, ulong bufferSize) + { + RequestOutFixedPointer(ref request, bufferAddress, bufferSize); + request.OutPointerSizes[request.OutPointerSizeIndex++] = (ushort)bufferSize; + } } } diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs index d409be5b3..62c15baa6 100644 --- a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs @@ -10,5 +10,12 @@ ref struct CmifRequest public Span OutPointerSizes; public Span Objects; public int ServerPointerSize; + public int CurrentInPointerId; + public int SendBufferIndex; + public int RecvBufferIndex; + public int ExchBufferIndex; + public int SendStaticIndex; + public int RecvListIndex; + public int OutPointerSizeIndex; } } diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs index 03ef6d3fc..4e9628947 100644 --- a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs @@ -11,5 +11,12 @@ readonly struct HipcBufferDescriptor public ulong Address => _addressLow | (((ulong)_word2 << 4) & 0xf00000000UL) | (((ulong)_word2 << 34) & 0x7000000000UL); public ulong Size => _sizeLow | ((ulong)_word2 << 8) & 0xf00000000UL; public HipcBufferMode Mode => (HipcBufferMode)(_word2 & 3); + + public HipcBufferDescriptor(ulong address, ulong size, HipcBufferMode mode) + { + _sizeLow = (uint)size; + _addressLow = (uint)address; + _word2 = (uint)mode | ((uint)(address >> 34) & 0x1c) | ((uint)(size >> 32) << 24) | ((uint)(address >> 4) & 0xf0000000); + } } } diff --git a/src/Ryujinx.Horizon/ServiceTable.cs b/src/Ryujinx.Horizon/ServiceTable.cs index c79328a96..ee62ee84d 100644 --- a/src/Ryujinx.Horizon/ServiceTable.cs +++ b/src/Ryujinx.Horizon/ServiceTable.cs @@ -1,3 +1,4 @@ +using Ryujinx.Horizon.Arp; using Ryujinx.Horizon.Bcat; using Ryujinx.Horizon.Hshl; using Ryujinx.Horizon.Ins; @@ -8,6 +9,7 @@ using Ryujinx.Horizon.Ovln; using Ryujinx.Horizon.Prepo; using Ryujinx.Horizon.Psc; +using Ryujinx.Horizon.Sdk.Arp; using Ryujinx.Horizon.Srepo; using Ryujinx.Horizon.Usb; using Ryujinx.Horizon.Wlan; @@ -23,6 +25,9 @@ public class ServiceTable private readonly ManualResetEvent _servicesReadyEvent = new(false); + public IReader ArpReader { get; internal set; } + public IWriter ArpWriter { get; internal set; } + public IEnumerable GetServices(HorizonOptions options) { List entries = new(); @@ -32,6 +37,7 @@ void RegisterService() where T : IService entries.Add(new ServiceEntry(T.Main, this, options)); } + RegisterService(); RegisterService(); RegisterService(); RegisterService(); From 5d3eea40be467e35d78e30b1760509d1cc8dfcf7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 00:17:02 +0100 Subject: [PATCH 010/126] nuget: bump DynamicData from 7.14.2 to 8.3.27 (#6028) Bumps [DynamicData](https://github.com/reactiveui/DynamicData) from 7.14.2 to 8.3.27. - [Release notes](https://github.com/reactiveui/DynamicData/releases) - [Changelog](https://github.com/reactivemarbles/DynamicData/blob/main/ReleaseNotes.md) - [Commits](https://github.com/reactiveui/DynamicData/compare/7.14.2...8.3.27) --- updated-dependencies: - dependency-name: DynamicData dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ac_K --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 678d7575a..eb64c94fc 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -13,7 +13,7 @@ - + From f33fea32877a9069bfa23bba12ee8b6ea3f6a631 Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Thu, 25 Jan 2024 23:20:50 +0000 Subject: [PATCH 011/126] Remove Custom Theming (#6175) --- src/Ryujinx.Ava/App.axaml.cs | 21 ----- src/Ryujinx.Ava/Assets/Locales/en_US.json | 10 +-- .../UI/ViewModels/SettingsViewModel.cs | 20 ----- .../UI/Views/Settings/SettingsUIView.axaml | 76 +++++-------------- .../UI/Views/Settings/SettingsUIView.axaml.cs | 24 ------ 5 files changed, 20 insertions(+), 131 deletions(-) diff --git a/src/Ryujinx.Ava/App.axaml.cs b/src/Ryujinx.Ava/App.axaml.cs index c1a3ab3e2..54e61c67c 100644 --- a/src/Ryujinx.Ava/App.axaml.cs +++ b/src/Ryujinx.Ava/App.axaml.cs @@ -12,7 +12,6 @@ using Ryujinx.Ui.Common.Helper; using System; using System.Diagnostics; -using System.IO; namespace Ryujinx.Ava { @@ -90,8 +89,6 @@ private void ApplyConfiguredTheme() try { string baseStyle = ConfigurationState.Instance.Ui.BaseStyle; - string themePath = ConfigurationState.Instance.Ui.CustomThemePath; - bool enableCustomTheme = ConfigurationState.Instance.Ui.EnableCustomTheme; if (string.IsNullOrWhiteSpace(baseStyle)) { @@ -106,24 +103,6 @@ private void ApplyConfiguredTheme() "Dark" => ThemeVariant.Dark, _ => ThemeVariant.Default, }; - - if (enableCustomTheme) - { - if (!string.IsNullOrWhiteSpace(themePath)) - { - try - { - var themeContent = File.ReadAllText(themePath); - var customStyle = AvaloniaRuntimeXamlLoader.Parse(themeContent); - - Styles.Add(customStyle); - } - catch (Exception ex) - { - Logger.Error?.Print(LogClass.Application, $"Failed to Apply Custom Theme. Error: {ex.Message}"); - } - } - } } catch (Exception) { diff --git a/src/Ryujinx.Ava/Assets/Locales/en_US.json b/src/Ryujinx.Ava/Assets/Locales/en_US.json index 72b5e8e3c..dcb7421fc 100644 --- a/src/Ryujinx.Ava/Assets/Locales/en_US.json +++ b/src/Ryujinx.Ava/Assets/Locales/en_US.json @@ -294,13 +294,9 @@ "GameListContextMenuRunApplication": "Run Application", "GameListContextMenuToggleFavorite": "Toggle Favorite", "GameListContextMenuToggleFavoriteToolTip": "Toggle Favorite status of Game", - "SettingsTabGeneralTheme": "Theme", - "SettingsTabGeneralThemeCustomTheme": "Custom Theme Path", - "SettingsTabGeneralThemeBaseStyle": "Base Style", - "SettingsTabGeneralThemeBaseStyleDark": "Dark", - "SettingsTabGeneralThemeBaseStyleLight": "Light", - "SettingsTabGeneralThemeEnableCustomTheme": "Enable Custom Theme", - "ButtonBrowse": "Browse", + "SettingsTabGeneralTheme": "Theme:", + "SettingsTabGeneralThemeDark": "Dark", + "SettingsTabGeneralThemeLight": "Light", "ControllerSettingsConfigureGeneral": "Configure", "ControllerSettingsRumble": "Rumble", "ControllerSettingsRumbleStrongMultiplier": "Strong Rumble Multiplier", diff --git a/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs index 604e34067..9e462a900 100644 --- a/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs @@ -48,7 +48,6 @@ public class SettingsViewModel : BaseModel private readonly List _gpuIds = new(); private KeyboardHotkeys _keyboardHotkeys; private int _graphicsBackendIndex; - private string _customThemePath; private int _scalingFilter; private int _scalingFilterLevel; @@ -160,7 +159,6 @@ public bool DirectoryChanged public bool IsOpenAlEnabled { get; set; } public bool IsSoundIoEnabled { get; set; } public bool IsSDL2Enabled { get; set; } - public bool EnableCustomTheme { get; set; } public bool IsCustomResolutionScaleActive => _resolutionScale == 4; public bool IsScalingFilterActive => _scalingFilter == (int)Ryujinx.Common.Configuration.ScalingFilter.Fsr; @@ -170,20 +168,6 @@ public bool DirectoryChanged public string TimeZone { get; set; } public string ShaderDumpPath { get; set; } - public string CustomThemePath - { - get - { - return _customThemePath; - } - set - { - _customThemePath = value; - - OnPropertyChanged(); - } - } - public int Language { get; set; } public int Region { get; set; } public int FsGlobalAccessLogMode { get; set; } @@ -426,8 +410,6 @@ public void LoadCurrentConfiguration() GameDirectories.Clear(); GameDirectories.AddRange(config.Ui.GameDirs.Value); - EnableCustomTheme = config.Ui.EnableCustomTheme; - CustomThemePath = config.Ui.CustomThemePath; BaseStyleIndex = config.Ui.BaseStyle == "Light" ? 0 : 1; // Input @@ -515,8 +497,6 @@ public void SaveSettings() config.Ui.GameDirs.Value = gameDirs; } - config.Ui.EnableCustomTheme.Value = EnableCustomTheme; - config.Ui.CustomThemePath.Value = CustomThemePath; config.Ui.BaseStyle.Value = BaseStyleIndex == 0 ? "Light" : "Dark"; // Input diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsUIView.axaml b/src/Ryujinx.Ava/UI/Views/Settings/SettingsUIView.axaml index b7471d385..6504637e6 100644 --- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsUIView.axaml +++ b/src/Ryujinx.Ava/UI/Views/Settings/SettingsUIView.axaml @@ -36,7 +36,7 @@ - + @@ -54,6 +54,22 @@ + + + + + + + + + + + @@ -106,64 +122,6 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml.cs b/src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml.cs new file mode 100644 index 000000000..5de09ba0b --- /dev/null +++ b/src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml.cs @@ -0,0 +1,139 @@ +using Avalonia.Controls; +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.Ui.Common.Helper; +using System.Threading.Tasks; +using Button = Avalonia.Controls.Button; + +namespace Ryujinx.Ava.UI.Windows +{ + public partial class ModManagerWindow : UserControl + { + public ModManagerViewModel ViewModel; + + public ModManagerWindow() + { + DataContext = this; + + InitializeComponent(); + } + + public ModManagerWindow(ulong titleId) + { + DataContext = ViewModel = new ModManagerViewModel(titleId); + + InitializeComponent(); + } + + public static async Task Show(ulong titleId, string titleName) + { + ContentDialog contentDialog = new() + { + PrimaryButtonText = "", + SecondaryButtonText = "", + CloseButtonText = "", + Content = new ModManagerWindow(titleId), + Title = string.Format(LocaleManager.Instance[LocaleKeys.ModWindowHeading], titleName, titleId.ToString("X16")), + }; + + Style bottomBorder = new(x => x.OfType().Name("DialogSpace").Child().OfType()); + bottomBorder.Setters.Add(new Setter(IsVisibleProperty, false)); + + contentDialog.Styles.Add(bottomBorder); + + await contentDialog.ShowAsync(); + } + + private void SaveAndClose(object sender, RoutedEventArgs e) + { + ViewModel.Save(); + ((ContentDialog)Parent).Hide(); + } + + private void Close(object sender, RoutedEventArgs e) + { + ((ContentDialog)Parent).Hide(); + } + + private async void DeleteMod(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + if (button.DataContext is ModModel model) + { + var result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogWarning], + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogModManagerDeletionWarningMessage, model.Name), + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + + if (result == UserResult.Yes) + { + ViewModel.Delete(model); + } + } + } + } + + private async void DeleteAll(object sender, RoutedEventArgs e) + { + var result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogWarning], + LocaleManager.Instance[LocaleKeys.DialogModManagerDeletionAllWarningMessage], + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + + if (result == UserResult.Yes) + { + ViewModel.DeleteAll(); + } + } + + private void OpenLocation(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + if (button.DataContext is ModModel model) + { + OpenHelper.OpenFolder(model.Path); + } + } + } + + private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + foreach (var content in e.AddedItems) + { + if (content is ModModel model) + { + var index = ViewModel.Mods.IndexOf(model); + + if (index != -1) + { + ViewModel.Mods[index].Enabled = true; + } + } + } + + foreach (var content in e.RemovedItems) + { + if (content is ModModel model) + { + var index = ViewModel.Mods.IndexOf(model); + + if (index != -1) + { + ViewModel.Mods[index].Enabled = false; + } + } + } + } + } +} diff --git a/src/Ryujinx.Common/Configuration/Mod.cs b/src/Ryujinx.Common/Configuration/Mod.cs new file mode 100644 index 000000000..052c7c8d1 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Mod.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Common.Configuration +{ + public class Mod + { + public string Name { get; set; } + public string Path { get; set; } + public bool Enabled { get; set; } + } +} diff --git a/src/Ryujinx.Common/Configuration/ModMetadata.cs b/src/Ryujinx.Common/Configuration/ModMetadata.cs new file mode 100644 index 000000000..174320d0a --- /dev/null +++ b/src/Ryujinx.Common/Configuration/ModMetadata.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Ryujinx.Common.Configuration +{ + public struct ModMetadata + { + public List Mods { get; set; } + + public ModMetadata() + { + Mods = new List(); + } + } +} diff --git a/src/Ryujinx.Common/Configuration/ModMetadataJsonSerializerContext.cs b/src/Ryujinx.Common/Configuration/ModMetadataJsonSerializerContext.cs new file mode 100644 index 000000000..8c1e242ad --- /dev/null +++ b/src/Ryujinx.Common/Configuration/ModMetadataJsonSerializerContext.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration +{ + [JsonSourceGenerationOptions(WriteIndented = true)] + [JsonSerializable(typeof(ModMetadata))] + public partial class ModMetadataJsonSerializerContext : JsonSerializerContext + { + } +} diff --git a/src/Ryujinx.HLE/HOS/ModLoader.cs b/src/Ryujinx.HLE/HOS/ModLoader.cs index 834bc0595..669c775b7 100644 --- a/src/Ryujinx.HLE/HOS/ModLoader.cs +++ b/src/Ryujinx.HLE/HOS/ModLoader.cs @@ -7,6 +7,7 @@ using LibHac.Tools.FsSystem.RomFs; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.Loaders.Executables; using Ryujinx.HLE.Loaders.Mods; @@ -37,15 +38,19 @@ public class ModLoader private const string AmsNroPatchDir = "nro_patches"; private const string AmsKipPatchDir = "kip_patches"; + private static readonly ModMetadataJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + public readonly struct Mod where T : FileSystemInfo { public readonly string Name; public readonly T Path; + public readonly bool Enabled; - public Mod(string name, T path) + public Mod(string name, T path, bool enabled) { Name = name; Path = path; + Enabled = enabled; } } @@ -67,7 +72,7 @@ public Cheat(string name, FileInfo path, IEnumerable instructions) } } - // Title dependent mods + // Application dependent mods public class ModCache { public List> RomfsContainers { get; } @@ -88,7 +93,7 @@ public ModCache() } } - // Title independent mods + // Application independent mods private class PatchCache { public List> NsoPatches { get; } @@ -107,7 +112,7 @@ public PatchCache() } } - private readonly Dictionary _appMods; // key is TitleId + private readonly Dictionary _appMods; // key is ApplicationId private PatchCache _patches; private static readonly EnumerationOptions _dirEnumOptions; @@ -153,26 +158,52 @@ private static string EnsureBaseDirStructure(string modsBasePath) return modsDir.FullName; } - private static DirectoryInfo FindTitleDir(DirectoryInfo contentsDir, string titleId) - => contentsDir.EnumerateDirectories(titleId, _dirEnumOptions).FirstOrDefault(); + private static DirectoryInfo FindApplicationDir(DirectoryInfo contentsDir, string applicationId) + => contentsDir.EnumerateDirectories(applicationId, _dirEnumOptions).FirstOrDefault(); - private static void AddModsFromDirectory(ModCache mods, DirectoryInfo dir, string titleId) + private static void AddModsFromDirectory(ModCache mods, DirectoryInfo dir, ModMetadata modMetadata) { System.Text.StringBuilder types = new(); foreach (var modDir in dir.EnumerateDirectories()) { types.Clear(); - Mod mod = new("", null); + Mod mod = new("", null, true); if (StrEquals(RomfsDir, modDir.Name)) { - mods.RomfsDirs.Add(mod = new Mod(dir.Name, modDir)); + bool enabled; + + try + { + var modData = modMetadata.Mods.Find(x => modDir.FullName.Contains(x.Path)); + enabled = modData.Enabled; + } + catch + { + // Mod is not in the list yet. New mods should be enabled by default. + enabled = true; + } + + mods.RomfsDirs.Add(mod = new Mod(dir.Name, modDir, enabled)); types.Append('R'); } else if (StrEquals(ExefsDir, modDir.Name)) { - mods.ExefsDirs.Add(mod = new Mod(dir.Name, modDir)); + bool enabled; + + try + { + var modData = modMetadata.Mods.Find(x => modDir.FullName.Contains(x.Path)); + enabled = modData.Enabled; + } + catch + { + // Mod is not in the list yet. New mods should be enabled by default. + enabled = true; + } + + mods.ExefsDirs.Add(mod = new Mod(dir.Name, modDir, enabled)); types.Append('E'); } else if (StrEquals(CheatDir, modDir.Name)) @@ -181,7 +212,7 @@ private static void AddModsFromDirectory(ModCache mods, DirectoryInfo dir, strin } else { - AddModsFromDirectory(mods, modDir, titleId); + AddModsFromDirectory(mods, modDir, modMetadata); } if (types.Length > 0) @@ -191,18 +222,18 @@ private static void AddModsFromDirectory(ModCache mods, DirectoryInfo dir, strin } } - public static string GetTitleDir(string modsBasePath, string titleId) + public static string GetApplicationDir(string modsBasePath, string applicationId) { var contentsDir = new DirectoryInfo(Path.Combine(modsBasePath, AmsContentsDir)); - var titleModsPath = FindTitleDir(contentsDir, titleId); + var applicationModsPath = FindApplicationDir(contentsDir, applicationId); - if (titleModsPath == null) + if (applicationModsPath == null) { - Logger.Info?.Print(LogClass.ModLoader, $"Creating mods directory for Title {titleId.ToUpper()}"); - titleModsPath = contentsDir.CreateSubdirectory(titleId); + Logger.Info?.Print(LogClass.ModLoader, $"Creating mods directory for Application {applicationId.ToUpper()}"); + applicationModsPath = contentsDir.CreateSubdirectory(applicationId); } - return titleModsPath.FullName; + return applicationModsPath.FullName; } // Static Query Methods @@ -238,47 +269,68 @@ private static void QueryPatchDirs(PatchCache cache, DirectoryInfo patchDir) foreach (var modDir in patchDir.EnumerateDirectories()) { - patches.Add(new Mod(modDir.Name, modDir)); + patches.Add(new Mod(modDir.Name, modDir, true)); Logger.Info?.Print(LogClass.ModLoader, $"Found {type} patch '{modDir.Name}'"); } } - private static void QueryTitleDir(ModCache mods, DirectoryInfo titleDir) + private static void QueryApplicationDir(ModCache mods, DirectoryInfo applicationDir, ulong applicationId) { - if (!titleDir.Exists) + if (!applicationDir.Exists) { return; } - var fsFile = new FileInfo(Path.Combine(titleDir.FullName, RomfsContainer)); + string modJsonPath = Path.Combine(AppDataManager.GamesDirPath, applicationId.ToString("x16"), "mods.json"); + ModMetadata modMetadata = new(); + + if (File.Exists(modJsonPath)) + { + try + { + modMetadata = JsonHelper.DeserializeFromFile(modJsonPath, _serializerContext.ModMetadata); + } + catch + { + Logger.Warning?.Print(LogClass.ModLoader, $"Failed to deserialize mod data for {applicationId:X16} at {modJsonPath}"); + } + } + + var fsFile = new FileInfo(Path.Combine(applicationDir.FullName, RomfsContainer)); if (fsFile.Exists) { - mods.RomfsContainers.Add(new Mod($"<{titleDir.Name} RomFs>", fsFile)); + var modData = modMetadata.Mods.Find(x => fsFile.FullName.Contains(x.Path)); + var enabled = modData == null || modData.Enabled; + + mods.RomfsContainers.Add(new Mod($"<{applicationDir.Name} RomFs>", fsFile, enabled)); } - fsFile = new FileInfo(Path.Combine(titleDir.FullName, ExefsContainer)); + fsFile = new FileInfo(Path.Combine(applicationDir.FullName, ExefsContainer)); if (fsFile.Exists) { - mods.ExefsContainers.Add(new Mod($"<{titleDir.Name} ExeFs>", fsFile)); + var modData = modMetadata.Mods.Find(x => fsFile.FullName.Contains(x.Path)); + var enabled = modData == null || modData.Enabled; + + mods.ExefsContainers.Add(new Mod($"<{applicationDir.Name} ExeFs>", fsFile, enabled)); } - AddModsFromDirectory(mods, titleDir, titleDir.Name); + AddModsFromDirectory(mods, applicationDir, modMetadata); } - public static void QueryContentsDir(ModCache mods, DirectoryInfo contentsDir, ulong titleId) + public static void QueryContentsDir(ModCache mods, DirectoryInfo contentsDir, ulong applicationId) { if (!contentsDir.Exists) { return; } - Logger.Info?.Print(LogClass.ModLoader, $"Searching mods for {((titleId & 0x1000) != 0 ? "DLC" : "Title")} {titleId:X16}"); + Logger.Info?.Print(LogClass.ModLoader, $"Searching mods for {((applicationId & 0x1000) != 0 ? "DLC" : "Application")} {applicationId:X16}"); - var titleDir = FindTitleDir(contentsDir, $"{titleId:x16}"); + var applicationDir = FindApplicationDir(contentsDir, $"{applicationId:x16}"); - if (titleDir != null) + if (applicationDir != null) { - QueryTitleDir(mods, titleDir); + QueryApplicationDir(mods, applicationDir, applicationId); } } @@ -387,9 +439,9 @@ static bool TryQuery(DirectoryInfo searchDir, PatchCache patches, Dictionary titles, params string[] searchDirPaths) + public void CollectMods(IEnumerable applications, params string[] searchDirPaths) { Clear(); - foreach (ulong titleId in titles) + foreach (ulong applicationId in applications) { - _appMods[titleId] = new ModCache(); + _appMods[applicationId] = new ModCache(); } CollectMods(_appMods, _patches, searchDirPaths); } - internal IStorage ApplyRomFsMods(ulong titleId, IStorage baseStorage) + internal IStorage ApplyRomFsMods(ulong applicationId, IStorage baseStorage) { - if (!_appMods.TryGetValue(titleId, out ModCache mods) || mods.RomfsDirs.Count + mods.RomfsContainers.Count == 0) + if (!_appMods.TryGetValue(applicationId, out ModCache mods) || mods.RomfsDirs.Count + mods.RomfsContainers.Count == 0) { return baseStorage; } @@ -448,11 +500,16 @@ internal IStorage ApplyRomFsMods(ulong titleId, IStorage baseStorage) var builder = new RomFsBuilder(); int count = 0; - Logger.Info?.Print(LogClass.ModLoader, $"Applying RomFS mods for Title {titleId:X16}"); + Logger.Info?.Print(LogClass.ModLoader, $"Applying RomFS mods for Application {applicationId:X16}"); // Prioritize loose files first foreach (var mod in mods.RomfsDirs) { + if (!mod.Enabled) + { + continue; + } + using (IFileSystem fs = new LocalFileSystem(mod.Path.FullName)) { AddFiles(fs, mod.Name, fileSet, builder); @@ -463,7 +520,12 @@ internal IStorage ApplyRomFsMods(ulong titleId, IStorage baseStorage) // Then files inside images foreach (var mod in mods.RomfsContainers) { - Logger.Info?.Print(LogClass.ModLoader, $"Found 'romfs.bin' for Title {titleId:X16}"); + if (!mod.Enabled) + { + continue; + } + + Logger.Info?.Print(LogClass.ModLoader, $"Found 'romfs.bin' for Application {applicationId:X16}"); using (IFileSystem fs = new RomFsFileSystem(mod.Path.OpenRead().AsStorage())) { AddFiles(fs, mod.Name, fileSet, builder); @@ -519,9 +581,9 @@ private static void AddFiles(IFileSystem fs, string modName, ISet fileSe } } - internal bool ReplaceExefsPartition(ulong titleId, ref IFileSystem exefs) + internal bool ReplaceExefsPartition(ulong applicationId, ref IFileSystem exefs) { - if (!_appMods.TryGetValue(titleId, out ModCache mods) || mods.ExefsContainers.Count == 0) + if (!_appMods.TryGetValue(applicationId, out ModCache mods) || mods.ExefsContainers.Count == 0) { return false; } @@ -549,7 +611,7 @@ public struct ModLoadResult public bool Modified => (Stubs.Data | Replaces.Data) != 0; } - internal ModLoadResult ApplyExefsMods(ulong titleId, NsoExecutable[] nsos) + internal ModLoadResult ApplyExefsMods(ulong applicationId, NsoExecutable[] nsos) { ModLoadResult modLoadResult = new() { @@ -557,7 +619,7 @@ internal ModLoadResult ApplyExefsMods(ulong titleId, NsoExecutable[] nsos) Replaces = new BitVector32(), }; - if (!_appMods.TryGetValue(titleId, out ModCache mods) || mods.ExefsDirs.Count == 0) + if (!_appMods.TryGetValue(applicationId, out ModCache mods) || mods.ExefsDirs.Count == 0) { return modLoadResult; } @@ -571,6 +633,11 @@ internal ModLoadResult ApplyExefsMods(ulong titleId, NsoExecutable[] nsos) foreach (var mod in exeMods) { + if (!mod.Enabled) + { + continue; + } + for (int i = 0; i < ProcessConst.ExeFsPrefixes.Length; ++i) { var nsoName = ProcessConst.ExeFsPrefixes[i]; @@ -637,11 +704,11 @@ internal void ApplyNroPatches(NroExecutable nro) ApplyProgramPatches(nroPatches, 0, nro); } - internal bool ApplyNsoPatches(ulong titleId, params IExecutable[] programs) + internal bool ApplyNsoPatches(ulong applicationId, params IExecutable[] programs) { IEnumerable> nsoMods = _patches.NsoPatches; - if (_appMods.TryGetValue(titleId, out ModCache mods)) + if (_appMods.TryGetValue(applicationId, out ModCache mods)) { nsoMods = nsoMods.Concat(mods.ExefsDirs); } @@ -651,7 +718,7 @@ internal bool ApplyNsoPatches(ulong titleId, params IExecutable[] programs) return ApplyProgramPatches(nsoMods, 0x100, programs); } - internal void LoadCheats(ulong titleId, ProcessTamperInfo tamperInfo, TamperMachine tamperMachine) + internal void LoadCheats(ulong applicationId, ProcessTamperInfo tamperInfo, TamperMachine tamperMachine) { if (tamperInfo?.BuildIds == null || tamperInfo.CodeAddresses == null) { @@ -660,9 +727,9 @@ internal void LoadCheats(ulong titleId, ProcessTamperInfo tamperInfo, TamperMach return; } - Logger.Info?.Print(LogClass.ModLoader, $"Build ids found for title {titleId:X16}:\n {String.Join("\n ", tamperInfo.BuildIds)}"); + Logger.Info?.Print(LogClass.ModLoader, $"Build ids found for application {applicationId:X16}:\n {String.Join("\n ", tamperInfo.BuildIds)}"); - if (!_appMods.TryGetValue(titleId, out ModCache mods) || mods.Cheats.Count == 0) + if (!_appMods.TryGetValue(applicationId, out ModCache mods) || mods.Cheats.Count == 0) { return; } @@ -687,12 +754,12 @@ internal void LoadCheats(ulong titleId, ProcessTamperInfo tamperInfo, TamperMach tamperMachine.InstallAtmosphereCheat(cheat.Name, cheatId, cheat.Instructions, tamperInfo, exeAddress); } - EnableCheats(titleId, tamperMachine); + EnableCheats(applicationId, tamperMachine); } - internal static void EnableCheats(ulong titleId, TamperMachine tamperMachine) + internal static void EnableCheats(ulong applicationId, TamperMachine tamperMachine) { - var contentDirectory = FindTitleDir(new DirectoryInfo(Path.Combine(GetModsBasePath(), AmsContentsDir)), $"{titleId:x16}"); + var contentDirectory = FindApplicationDir(new DirectoryInfo(Path.Combine(GetModsBasePath(), AmsContentsDir)), $"{applicationId:x16}"); string enabledCheatsPath = Path.Combine(contentDirectory.FullName, CheatDir, "enabled.txt"); if (File.Exists(enabledCheatsPath)) @@ -724,6 +791,11 @@ private static bool ApplyProgramPatches(IEnumerable> mods, in // Collect patches foreach (var mod in mods) { + if (!mod.Enabled) + { + continue; + } + var patchDir = mod.Path; foreach (var patchFile in patchDir.EnumerateFiles()) { diff --git a/src/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs b/src/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs index 39139ecca..6c2415e46 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs @@ -16,10 +16,7 @@ public static ProcessResult Load(this LocalFileSystem exeFs, Switch device, stri var nacpData = new BlitStruct(1); ulong programId = metaLoader.GetProgramId(); - device.Configuration.VirtualFileSystem.ModLoader.CollectMods( - new[] { programId }, - ModLoader.GetModsBasePath(), - ModLoader.GetSdModsBasePath()); + device.Configuration.VirtualFileSystem.ModLoader.CollectMods(new[] { programId }); if (programId != 0) { diff --git a/src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs b/src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs index eb048b00d..6bf43842c 100644 --- a/src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs +++ b/src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs @@ -497,7 +497,7 @@ private void ManageCheats_Clicked(object sender, EventArgs args) private void OpenTitleModDir_Clicked(object sender, EventArgs args) { string modsBasePath = ModLoader.GetModsBasePath(); - string titleModsPath = ModLoader.GetTitleDir(modsBasePath, _titleIdText); + string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, _titleIdText); OpenHelper.OpenFolder(titleModsPath); } @@ -505,7 +505,7 @@ private void OpenTitleModDir_Clicked(object sender, EventArgs args) private void OpenTitleSdModDir_Clicked(object sender, EventArgs args) { string sdModsBasePath = ModLoader.GetSdModsBasePath(); - string titleModsPath = ModLoader.GetTitleDir(sdModsBasePath, _titleIdText); + string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, _titleIdText); OpenHelper.OpenFolder(titleModsPath); } diff --git a/src/Ryujinx/Ui/Windows/CheatWindow.cs b/src/Ryujinx/Ui/Windows/CheatWindow.cs index 1eca732b2..afccec53c 100644 --- a/src/Ryujinx/Ui/Windows/CheatWindow.cs +++ b/src/Ryujinx/Ui/Windows/CheatWindow.cs @@ -31,7 +31,7 @@ private CheatWindow(Builder builder, VirtualFileSystem virtualFileSystem, ulong _buildIdTextView.Buffer.Text = $"BuildId: {ApplicationData.GetApplicationBuildId(virtualFileSystem, titlePath)}"; string modsBasePath = ModLoader.GetModsBasePath(); - string titleModsPath = ModLoader.GetTitleDir(modsBasePath, titleId.ToString("X16")); + string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, titleId.ToString("X16")); _enabledCheatsPath = System.IO.Path.Combine(titleModsPath, "cheats", "enabled.txt"); From cea204d48e3853e51e28ace959a1226b4468373e Mon Sep 17 00:00:00 2001 From: Ac_K Date: Fri, 26 Jan 2024 02:43:15 +0100 Subject: [PATCH 015/126] Fs: Log when Commit fails due to PathAlreadyInUse (#6178) * Fs: Log when Commit fails due to PathAlreadyInUse This fixes and superseed #5418, nothing more. (See original PR for description) Co-Authored-By: James R T * Update IFileSystem.cs --------- Co-authored-by: James R T --- .../HOS/Services/Fs/FileSystemProxy/IFileSystem.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFileSystem.cs b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFileSystem.cs index 66020d57b..e19d17912 100644 --- a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFileSystem.cs +++ b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFileSystem.cs @@ -2,6 +2,7 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; +using Ryujinx.Common.Logging; using Path = LibHac.FsSrv.Sf.Path; namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy @@ -149,7 +150,13 @@ public ResultCode OpenDirectory(ServiceCtx context) // Commit() public ResultCode Commit(ServiceCtx context) { - return (ResultCode)_fileSystem.Get.Commit().Value; + ResultCode resultCode = (ResultCode)_fileSystem.Get.Commit().Value; + if (resultCode == ResultCode.PathAlreadyInUse) + { + Logger.Warning?.Print(LogClass.ServiceFs, "The file system is already in use by another process."); + } + + return resultCode; } [CommandCmif(11)] From a620cbcc9050d7d3b0932d97db87628dd4e97b0a Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Fri, 26 Jan 2024 14:25:48 +0000 Subject: [PATCH 016/126] Ava UI: Mod Manager Fixes (#6179) * Fix string format + Crash * Better Logging * Properly Delete Mods Rename * Catch when trying to access bad directory --- src/Ryujinx.Ava/Assets/Locales/en_US.json | 2 + .../UI/ViewModels/ModManagerViewModel.cs | 70 +++++++++++++++++-- src/Ryujinx.HLE/HOS/ModLoader.cs | 2 +- 3 files changed, 69 insertions(+), 5 deletions(-) diff --git a/src/Ryujinx.Ava/Assets/Locales/en_US.json b/src/Ryujinx.Ava/Assets/Locales/en_US.json index 79692a08c..8b38490f7 100644 --- a/src/Ryujinx.Ava/Assets/Locales/en_US.json +++ b/src/Ryujinx.Ava/Assets/Locales/en_US.json @@ -383,6 +383,8 @@ "DialogControllerSettingsModifiedConfirmSubMessage": "Do you want to save?", "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", "DialogModAlreadyExistsMessage": "Mod already exists", + "DialogModInvalidMessage": "The specified directory does not contain a mod!", + "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", "DialogDlcNoDlcErrorMessage": "The specified file does not contain a DLC for the selected title!", "DialogPerformanceCheckLoggingEnabledMessage": "You have trace logging enabled, which is designed to be used by developers only.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "For optimal performance, it's recommended to disable trace logging. Would you like to disable trace logging now?", diff --git a/src/Ryujinx.Ava/UI/ViewModels/ModManagerViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/ModManagerViewModel.cs index 7340873ae..2c29660bf 100644 --- a/src/Ryujinx.Ava/UI/ViewModels/ModManagerViewModel.cs +++ b/src/Ryujinx.Ava/UI/ViewModels/ModManagerViewModel.cs @@ -8,8 +8,10 @@ using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Models; using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; using Ryujinx.Common.Utilities; using Ryujinx.HLE.HOS; +using System; using System.IO; using System.Linq; @@ -181,7 +183,30 @@ public void Save() public void Delete(ModModel model) { - Directory.Delete(model.Path, true); + var modsDir = ModLoader.GetApplicationDir(ModLoader.GetSdModsBasePath(), _applicationId.ToString("x16")); + var parentDir = String.Empty; + + foreach (var dir in Directory.GetDirectories(modsDir, "*", SearchOption.TopDirectoryOnly)) + { + if (Directory.GetDirectories(dir, "*", SearchOption.AllDirectories).Contains(model.Path)) + { + parentDir = dir; + } + } + + if (parentDir == String.Empty) + { + Dispatcher.UIThread.Post(async () => + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue( + LocaleKeys.DialogModDeleteNoParentMessage, + parentDir)); + }); + return; + } + + Logger.Info?.Print(LogClass.Application, $"Deleting mod at \"{model.Path}\""); + Directory.Delete(parentDir, true); Mods.Remove(model); OnPropertyChanged(nameof(ModCount)); @@ -190,9 +215,43 @@ public void Delete(ModModel model) private void AddMod(DirectoryInfo directory) { - var directories = Directory.GetDirectories(directory.ToString(), "*", SearchOption.AllDirectories); + string[] directories; + + try + { + directories = Directory.GetDirectories(directory.ToString(), "*", SearchOption.AllDirectories); + } + catch (Exception exception) + { + Dispatcher.UIThread.Post(async () => + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue( + LocaleKeys.DialogLoadFileErrorMessage, + exception.ToString(), + directory)); + }); + return; + } + var destinationDir = ModLoader.GetApplicationDir(ModLoader.GetSdModsBasePath(), _applicationId.ToString("x16")); + // TODO: More robust checking for valid mod folders + var isDirectoryValid = true; + + if (directories.Length == 0) + { + isDirectoryValid = false; + } + + if (!isDirectoryValid) + { + Dispatcher.UIThread.Post(async () => + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogModInvalidMessage]); + }); + return; + } + foreach (var dir in directories) { string dirToCreate = dir.Replace(directory.Parent.ToString(), destinationDir); @@ -202,7 +261,10 @@ private void AddMod(DirectoryInfo directory) { Dispatcher.UIThread.Post(async () => { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadFileErrorMessage, LocaleKeys.DialogModAlreadyExistsMessage, dirToCreate)); + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue( + LocaleKeys.DialogLoadFileErrorMessage, + LocaleManager.Instance[LocaleKeys.DialogModAlreadyExistsMessage], + dirToCreate)); }); return; @@ -239,7 +301,7 @@ public void DeleteAll() { foreach (var mod in Mods) { - Directory.Delete(mod.Path, true); + Delete(mod); } Mods.Clear(); diff --git a/src/Ryujinx.HLE/HOS/ModLoader.cs b/src/Ryujinx.HLE/HOS/ModLoader.cs index 669c775b7..322c8afdc 100644 --- a/src/Ryujinx.HLE/HOS/ModLoader.cs +++ b/src/Ryujinx.HLE/HOS/ModLoader.cs @@ -324,7 +324,7 @@ public static void QueryContentsDir(ModCache mods, DirectoryInfo contentsDir, ul return; } - Logger.Info?.Print(LogClass.ModLoader, $"Searching mods for {((applicationId & 0x1000) != 0 ? "DLC" : "Application")} {applicationId:X16}"); + Logger.Info?.Print(LogClass.ModLoader, $"Searching mods for {((applicationId & 0x1000) != 0 ? "DLC" : "Application")} {applicationId:X16} in \"{contentsDir.FullName}\""); var applicationDir = FindApplicationDir(contentsDir, $"{applicationId:x16}"); From b8d992e5a770931382fd39108601b0abe75149cc Mon Sep 17 00:00:00 2001 From: gdkchan Date: Fri, 26 Jan 2024 13:58:57 -0300 Subject: [PATCH 017/126] Allow skipping draws with broken pipeline variants on Vulkan (#5807) * Allow skipping draws with broken pipeline variants on Vulkan * Move IsLinked check to CreatePipeline * Restore throw on error behaviour for background compile * Can't remove SetAlphaTest pragmas yet * Double new line --- src/Ryujinx.Graphics.Vulkan/PipelineBase.cs | 122 ++++++++++++------ src/Ryujinx.Graphics.Vulkan/PipelineFull.cs | 5 +- src/Ryujinx.Graphics.Vulkan/PipelineState.cs | 28 ++-- .../ShaderCollection.cs | 4 +- .../VulkanException.cs | 8 +- 5 files changed, 110 insertions(+), 57 deletions(-) diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs index af3a27e55..61215b672 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -34,7 +34,8 @@ class PipelineBase : IDisposable protected PipelineDynamicState DynamicState; private PipelineState _newState; - private bool _stateDirty; + private bool _graphicsStateDirty; + private bool _computeStateDirty; private PrimitiveTopology _topology; private ulong _currentPipelineHandle; @@ -353,7 +354,7 @@ public void DispatchCompute(int groupsX, int groupsY, int groupsZ) } EndRenderPass(); - RecreatePipelineIfNeeded(PipelineBindPoint.Compute); + RecreateComputePipelineIfNeeded(); Gd.Api.CmdDispatch(CommandBuffer, (uint)groupsX, (uint)groupsY, (uint)groupsZ); } @@ -366,19 +367,23 @@ public void DispatchComputeIndirect(Auto indirectBuffer, int i } EndRenderPass(); - RecreatePipelineIfNeeded(PipelineBindPoint.Compute); + RecreateComputePipelineIfNeeded(); Gd.Api.CmdDispatchIndirect(CommandBuffer, indirectBuffer.Get(Cbs, indirectBufferOffset, 12).Value, (ulong)indirectBufferOffset); } public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance) { - if (!_program.IsLinked || vertexCount == 0) + if (vertexCount == 0) + { + return; + } + + if (!RecreateGraphicsPipelineIfNeeded()) { return; } - RecreatePipelineIfNeeded(PipelineBindPoint.Graphics); BeginRenderPass(); DrawCount++; @@ -437,13 +442,18 @@ PrimitiveTopology.TriangleFan or public void DrawIndexed(int indexCount, int instanceCount, int firstIndex, int firstVertex, int firstInstance) { - if (!_program.IsLinked || indexCount == 0) + if (indexCount == 0) { return; } UpdateIndexBufferPattern(); - RecreatePipelineIfNeeded(PipelineBindPoint.Graphics); + + if (!RecreateGraphicsPipelineIfNeeded()) + { + return; + } + BeginRenderPass(); DrawCount++; @@ -476,17 +486,17 @@ public void DrawIndexed(int indexCount, int instanceCount, int firstIndex, int f public void DrawIndexedIndirect(BufferRange indirectBuffer) { - if (!_program.IsLinked) - { - return; - } - var buffer = Gd.BufferManager .GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false) .Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value; UpdateIndexBufferPattern(); - RecreatePipelineIfNeeded(PipelineBindPoint.Graphics); + + if (!RecreateGraphicsPipelineIfNeeded()) + { + return; + } + BeginRenderPass(); DrawCount++; @@ -522,11 +532,6 @@ public void DrawIndexedIndirect(BufferRange indirectBuffer) public void DrawIndexedIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride) { - if (!_program.IsLinked) - { - return; - } - var countBuffer = Gd.BufferManager .GetBuffer(CommandBuffer, parameterBuffer.Handle, parameterBuffer.Offset, parameterBuffer.Size, false) .Get(Cbs, parameterBuffer.Offset, parameterBuffer.Size).Value; @@ -536,7 +541,12 @@ public void DrawIndexedIndirectCount(BufferRange indirectBuffer, BufferRange par .Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value; UpdateIndexBufferPattern(); - RecreatePipelineIfNeeded(PipelineBindPoint.Graphics); + + if (!RecreateGraphicsPipelineIfNeeded()) + { + return; + } + BeginRenderPass(); DrawCount++; @@ -614,18 +624,17 @@ public void DrawIndexedIndirectCount(BufferRange indirectBuffer, BufferRange par public void DrawIndirect(BufferRange indirectBuffer) { - if (!_program.IsLinked) - { - return; - } - // TODO: Support quads and other unsupported topologies. var buffer = Gd.BufferManager .GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false) .Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size, false).Value; - RecreatePipelineIfNeeded(PipelineBindPoint.Graphics); + if (!RecreateGraphicsPipelineIfNeeded()) + { + return; + } + BeginRenderPass(); ResumeTransformFeedbackInternal(); DrawCount++; @@ -641,11 +650,6 @@ public void DrawIndirectCount(BufferRange indirectBuffer, BufferRange parameterB throw new NotSupportedException(); } - if (!_program.IsLinked) - { - return; - } - var buffer = Gd.BufferManager .GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false) .Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size, false).Value; @@ -656,7 +660,11 @@ public void DrawIndirectCount(BufferRange indirectBuffer, BufferRange parameterB // TODO: Support quads and other unsupported topologies. - RecreatePipelineIfNeeded(PipelineBindPoint.Graphics); + if (!RecreateGraphicsPipelineIfNeeded()) + { + return; + } + BeginRenderPass(); ResumeTransformFeedbackInternal(); DrawCount++; @@ -1576,10 +1584,23 @@ protected unsafe void CreateRenderPass() protected void SignalStateChange() { - _stateDirty = true; + _graphicsStateDirty = true; + _computeStateDirty = true; } - private void RecreatePipelineIfNeeded(PipelineBindPoint pbp) + private void RecreateComputePipelineIfNeeded() + { + if (_computeStateDirty || Pbp != PipelineBindPoint.Compute) + { + CreatePipeline(PipelineBindPoint.Compute); + _computeStateDirty = false; + Pbp = PipelineBindPoint.Compute; + } + + _descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Compute); + } + + private bool RecreateGraphicsPipelineIfNeeded() { if (AutoFlush.ShouldFlushDraw(DrawCount)) { @@ -1620,17 +1641,23 @@ private void RecreatePipelineIfNeeded(PipelineBindPoint pbp) _vertexBufferUpdater.Commit(Cbs); } - if (_stateDirty || Pbp != pbp) + if (_graphicsStateDirty || Pbp != PipelineBindPoint.Graphics) { - CreatePipeline(pbp); - _stateDirty = false; - Pbp = pbp; + if (!CreatePipeline(PipelineBindPoint.Graphics)) + { + return false; + } + + _graphicsStateDirty = false; + Pbp = PipelineBindPoint.Graphics; } - _descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, pbp); + _descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Graphics); + + return true; } - private void CreatePipeline(PipelineBindPoint pbp) + private bool CreatePipeline(PipelineBindPoint pbp) { // We can only create a pipeline if the have the shader stages set. if (_newState.Stages != null) @@ -1640,10 +1667,25 @@ private void CreatePipeline(PipelineBindPoint pbp) CreateRenderPass(); } + if (!_program.IsLinked) + { + // Background compile failed, we likely can't create the pipeline because the shader is broken + // or the driver failed to compile it. + + return false; + } + var pipeline = pbp == PipelineBindPoint.Compute ? _newState.CreateComputePipeline(Gd, Device, _program, PipelineCache) : _newState.CreateGraphicsPipeline(Gd, Device, _program, PipelineCache, _renderPass.Get(Cbs).Value); + if (pipeline == null) + { + // Host failed to create the pipeline, likely due to driver bugs. + + return false; + } + ulong pipelineHandle = pipeline.GetUnsafe().Value.Handle; if (_currentPipelineHandle != pipelineHandle) @@ -1655,6 +1697,8 @@ private void CreatePipeline(PipelineBindPoint pbp) Gd.Api.CmdBindPipeline(CommandBuffer, pbp, Pipeline.Get(Cbs).Value); } } + + return true; } private unsafe void BeginRenderPass() diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs b/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs index 24ca715fe..a3e6818f3 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs @@ -246,7 +246,10 @@ public void Restore() SignalCommandBufferChange(); - DynamicState.ReplayIfDirty(Gd.Api, CommandBuffer); + if (Pipeline != null && Pbp == PipelineBindPoint.Graphics) + { + DynamicState.ReplayIfDirty(Gd.Api, CommandBuffer); + } } public void FlushCommandsImpl() diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs index 11f532510..25fd7168f 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs @@ -312,7 +312,6 @@ public bool DepthMode } public NativeArray Stages; - public NativeArray StageRequiredSubgroupSizes; public PipelineLayout PipelineLayout; public SpecData SpecializationData; @@ -321,16 +320,6 @@ public bool DepthMode public void Initialize() { Stages = new NativeArray(Constants.MaxShaderStages); - StageRequiredSubgroupSizes = new NativeArray(Constants.MaxShaderStages); - - for (int index = 0; index < Constants.MaxShaderStages; index++) - { - StageRequiredSubgroupSizes[index] = new PipelineShaderStageRequiredSubgroupSizeCreateInfoEXT - { - SType = StructureType.PipelineShaderStageRequiredSubgroupSizeCreateInfoExt, - RequiredSubgroupSize = RequiredSubgroupSize, - }; - } AdvancedBlendSrcPreMultiplied = true; AdvancedBlendDstPreMultiplied = true; @@ -397,7 +386,8 @@ public unsafe Auto CreateGraphicsPipeline( Device device, ShaderCollection program, PipelineCache cache, - RenderPass renderPass) + RenderPass renderPass, + bool throwOnError = false) { if (program.TryGetGraphicsPipeline(ref Internal, out var pipeline)) { @@ -630,7 +620,18 @@ public unsafe Auto CreateGraphicsPipeline( BasePipelineIndex = -1, }; - gd.Api.CreateGraphicsPipelines(device, cache, 1, &pipelineCreateInfo, null, &pipelineHandle).ThrowOnError(); + Result result = gd.Api.CreateGraphicsPipelines(device, cache, 1, &pipelineCreateInfo, null, &pipelineHandle); + + if (throwOnError) + { + result.ThrowOnError(); + } + else if (result.IsError()) + { + program.AddGraphicsPipeline(ref Internal, null); + + return null; + } // Restore previous blend enable values if we changed it. while (blendEnables != 0) @@ -708,7 +709,6 @@ private int GetVertexBufferIndex(uint binding) public readonly void Dispose() { Stages.Dispose(); - StageRequiredSubgroupSizes.Dispose(); } } } diff --git a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs index 0d6da0391..7c25c6d14 100644 --- a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs +++ b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs @@ -374,7 +374,7 @@ public void CreateBackgroundGraphicsPipeline() pipeline.StagesCount = (uint)_shaders.Length; pipeline.PipelineLayout = PipelineLayout; - pipeline.CreateGraphicsPipeline(_gd, _device, this, (_gd.Pipeline as PipelineBase).PipelineCache, renderPass.Value); + pipeline.CreateGraphicsPipeline(_gd, _device, this, (_gd.Pipeline as PipelineBase).PipelineCache, renderPass.Value, throwOnError: true); pipeline.Dispose(); } @@ -511,7 +511,7 @@ protected virtual void Dispose(bool disposing) { foreach (Auto pipeline in _graphicsPipelineCache.Values) { - pipeline.Dispose(); + pipeline?.Dispose(); } } diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanException.cs b/src/Ryujinx.Graphics.Vulkan/VulkanException.cs index 0d4036802..e203a3a21 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanException.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanException.cs @@ -6,10 +6,16 @@ namespace Ryujinx.Graphics.Vulkan { static class ResultExtensions { + public static bool IsError(this Result result) + { + // Only negative result codes are errors. + return result < Result.Success; + } + public static void ThrowOnError(this Result result) { // Only negative result codes are errors. - if ((int)result < (int)Result.Success) + if (result.IsError()) { throw new VulkanException(result); } From 0335c522548803310d825dbdb2d5ea101ef74823 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 27 Jan 2024 02:02:12 +0100 Subject: [PATCH 018/126] nuget: bump NetCoreServer from 7.0.0 to 8.0.7 (#6119) Bumps [NetCoreServer](https://github.com/chronoxor/NetCoreServer) from 7.0.0 to 8.0.7. - [Release notes](https://github.com/chronoxor/NetCoreServer/releases) - [Commits](https://github.com/chronoxor/NetCoreServer/commits) --- updated-dependencies: - dependency-name: NetCoreServer dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index b79eb8e01..f2c080d86 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -25,7 +25,7 @@ - + From f6475cca175cc1132167aaa805a0afa5aea8954e Mon Sep 17 00:00:00 2001 From: TSRBerry <20988865+TSRBerry@users.noreply.github.com> Date: Sat, 27 Jan 2024 20:50:28 +0100 Subject: [PATCH 019/126] infra: Reformat README.md & add new generic Mako workflow (#5791) * Adjust workflow paths to exclude all markdown files * editorconfig: Add default charset and adjust indention for a few file types * Reformat README.md and add a link to our documentation * Add generic Mako workflow and remove old Mako steps * editorconfig: Move charset change to a different PR * Update compatibility stats Co-authored-by: Ac_K --------- Co-authored-by: Ac_K --- .editorconfig | 4 +- .github/workflows/checks.yml | 2 +- .github/workflows/mako.yml | 45 ++++++++++++++++ .github/workflows/pr_triage.yml | 19 ------- .github/workflows/release.yml | 2 +- README.md | 91 +++++++++++++++++++++------------ 6 files changed, 107 insertions(+), 56 deletions(-) create mode 100644 .github/workflows/mako.yml diff --git a/.editorconfig b/.editorconfig index ae4c97662..1eaf77ae6 100644 --- a/.editorconfig +++ b/.editorconfig @@ -17,8 +17,8 @@ tab_width = 4 end_of_line = lf insert_final_newline = true -# JSON files -[*.json] +# Markdown, JSON, YAML, props and csproj files +[*.{md,json,yml,props,csproj}] # Indentation and spacing indent_size = 2 diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 5311a67f6..2bef2d8e0 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -8,7 +8,7 @@ on: - '!.github/**' - '!*.yml' - '!*.config' - - '!README.md' + - '!*.md' - '.github/workflows/*.yml' permissions: diff --git a/.github/workflows/mako.yml b/.github/workflows/mako.yml new file mode 100644 index 000000000..7317cc9d3 --- /dev/null +++ b/.github/workflows/mako.yml @@ -0,0 +1,45 @@ +name: Mako +on: + discussion: + types: [created, edited, answered, unanswered, category_changed] + discussion_comment: + types: [created, edited] + gollum: + issue_comment: + types: [created, edited] + issues: + types: [opened, edited, reopened, pinned, milestoned, demilestoned, assigned, unassigned, labeled, unlabeled] + pull_request_review: + types: [submitted, dismissed] + pull_request_review_comment: + types: [created, edited] + pull_request_target: + types: [opened, edited, reopened, synchronize, ready_for_review, assigned, unassigned] + +jobs: + tasks: + name: Run Ryujinx tasks + permissions: + actions: read + contents: read + discussions: write + issues: write + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + if: github.event_name == 'pull_request_target' + with: + # Ensure we pin the source origin as pull_request_target run under forks. + fetch-depth: 0 + repository: Ryujinx/Ryujinx + ref: master + + - name: Run Mako command + uses: Ryujinx/Ryujinx-Mako@master + with: + command: exec-ryujinx-tasks + args: --event-name "${{ github.event_name }}" --event-path "${{ github.event_path }}" -w "${{ github.workspace }}" "${{ github.repository }}" "${{ github.run_id }}" + app_id: ${{ secrets.MAKO_APP_ID }} + private_key: ${{ secrets.MAKO_PRIVATE_KEY }} + installation_id: ${{ secrets.MAKO_INSTALLATION_ID }} diff --git a/.github/workflows/pr_triage.yml b/.github/workflows/pr_triage.yml index 93aa89626..d8d66b70f 100644 --- a/.github/workflows/pr_triage.yml +++ b/.github/workflows/pr_triage.yml @@ -21,27 +21,8 @@ jobs: repository: Ryujinx/Ryujinx ref: master - - name: Checkout Ryujinx-Mako - uses: actions/checkout@v4 - with: - repository: Ryujinx/Ryujinx-Mako - ref: master - path: '.ryujinx-mako' - - - name: Setup Ryujinx-Mako - uses: ./.ryujinx-mako/.github/actions/setup-mako - - name: Update labels based on changes uses: actions/labeler@v5 with: sync-labels: true dot: true - - - name: Assign reviewers - run: | - poetry -n -C .ryujinx-mako run ryujinx-mako update-reviewers ${{ github.repository }} ${{ github.event.pull_request.number }} .github/reviewers.yml - shell: bash - env: - MAKO_APP_ID: ${{ secrets.MAKO_APP_ID }} - MAKO_PRIVATE_KEY: ${{ secrets.MAKO_PRIVATE_KEY }} - MAKO_INSTALLATION_ID: ${{ secrets.MAKO_INSTALLATION_ID }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7a4b13d7d..1fb0acdc7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,7 @@ on: - '*.yml' - '*.json' - '*.config' - - 'README.md' + - '*.md' concurrency: release diff --git a/README.md b/README.md index b2f95cc1f..f2f3cb001 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,21 @@ -


- Ryujinx + Ryujinx
Ryujinx
(REE-YOU-JINX)
-

- Ryujinx is an open-source Nintendo Switch emulator, created by gdkchan, written in C#. - This emulator aims at providing excellent accuracy and performance, a user-friendly interface and consistent builds. - It was written from scratch and development on the project began in September 2017. Ryujinx is available on Github under the MIT license.
- + Ryujinx is an open-source Nintendo Switch emulator, created by gdkchan, written in C#. + This emulator aims at providing excellent accuracy and performance, a user-friendly interface and consistent builds. + It was written from scratch and development on the project began in September 2017. + Ryujinx is available on Github under the MIT license. +

+

-
+## Compatibility -
+As of October 2023, Ryujinx has been tested on approximately 4,200 titles; +over 4,150 boot past menus and into gameplay, with roughly 3,500 of those being considered playable. -## Compatibility +You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues). -As of April 2023, Ryujinx has been tested on approximately 4,050 titles; over 4,000 boot past menus and into gameplay, with roughly 3,400 of those being considered playable. -You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues). Anyone is free to submit a new game test or update an existing game test entry; simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue. Use the search function to see if a game has been tested already! +Anyone is free to submit a new game test or update an existing game test entry; +simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue. +Use the search function to see if a game has been tested already! ## Usage -To run this emulator, your PC must be equipped with at least 8GiB of RAM; failing to meet this requirement may result in a poor gameplay experience or unexpected crashes. +To run this emulator, your PC must be equipped with at least 8GiB of RAM; +failing to meet this requirement may result in a poor gameplay experience or unexpected crashes. See our [Setup & Configuration Guide](https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide) on how to set up the emulator. -For our Local Wireless and LAN builds, see our [Multiplayer: Local Play/Local Wireless Guide +For our Local Wireless (LDN) builds, see our [Multiplayer: Local Play/Local Wireless Guide ](https://github.com/Ryujinx/Ryujinx/wiki/Multiplayer-(LDN-Local-Wireless)-Guide). Avalonia UI comes with translations for various languages. See [Crowdin](https://crwd.in/ryujinx) for more information. ## Latest build -These builds are compiled automatically for each commit on the master branch. While we strive to ensure optimal stability and performance prior to pushing an update, our automated builds **may be unstable or completely broken.** +These builds are compiled automatically for each commit on the master branch. +While we strive to ensure optimal stability and performance prior to pushing an update, our automated builds **may be unstable or completely broken**. If you want to see details on updates to the emulator, you can visit our [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog). The latest automatic build for Windows, macOS, and Linux can be found on the [Official Website](https://ryujinx.org/download). +## Documentation + +If you are planning to contribute or just want to learn more about this project please read through our [documentation](docs/README.md). ## Building If you wish to build the emulator yourself, follow these steps: ### Step 1 -Install the X64 version of [.NET 8.0 (or higher) SDK](https://dotnet.microsoft.com/download/dotnet/8.0). + +Install the [.NET 8.0 (or higher) SDK](https://dotnet.microsoft.com/download/dotnet/8.0). +Make sure your SDK version is higher or equal to the required version specified in [global.json](global.json). ### Step 2 + Either use `git clone https://github.com/Ryujinx/Ryujinx` on the command line to clone the repository or use Code --> Download zip button to get the files. ### Step 3 -To build Ryujinx, open a command prompt inside the project directory. You can quickly access it on Windows by holding shift in File Explorer, then right clicking and selecting `Open command window here`. Then type the following command: -`dotnet build -c Release -o build` +To build Ryujinx, open a command prompt inside the project directory. +You can quickly access it on Windows by holding shift in File Explorer, then right clicking and selecting `Open command window here`. +Then type the following command: `dotnet build -c Release -o build` the built files will be found in the newly created build directory. -Ryujinx system files are stored in the `Ryujinx` folder. This folder is located in the user folder, which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI. - +Ryujinx system files are stored in the `Ryujinx` folder. +This folder is located in the user folder, which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI. ## Features - - **Audio** +- **Audio** - Audio output is entirely supported, audio input (microphone) isn't supported. We use C# wrappers for [OpenAL](https://openal-soft.org/), and [SDL2](https://www.libsdl.org/) & [libsoundio](http://libsound.io/) as fallbacks. + Audio output is entirely supported, audio input (microphone) isn't supported. + We use C# wrappers for [OpenAL](https://openal-soft.org/), and [SDL2](https://www.libsdl.org/) & [libsoundio](http://libsound.io/) as fallbacks. - **CPU** - The CPU emulator, ARMeilleure, emulates an ARMv8 CPU and currently has support for most 64-bit ARMv8 and some of the ARMv7 (and older) instructions, including partial 32-bit support. It translates the ARM code to a custom IR, performs a few optimizations, and turns that into x86 code. - There are three memory manager options available depending on the user's preference, leveraging both software-based (slower) and host-mapped modes (much faster). The fastest option (host, unchecked) is set by default. - Ryujinx also features an optional Profiled Persistent Translation Cache, which essentially caches translated functions so that they do not need to be translated every time the game loads. The net result is a significant reduction in load times (the amount of time between launching a game and arriving at the title screen) for nearly every game. NOTE: this feature is enabled by default in the Options menu > System tab. You must launch the game at least twice to the title screen or beyond before performance improvements are unlocked on the third launch! These improvements are permanent and do not require any extra launches going forward. + The CPU emulator, ARMeilleure, emulates an ARMv8 CPU and currently has support for most 64-bit ARMv8 and some of the ARMv7 (and older) instructions, including partial 32-bit support. + It translates the ARM code to a custom IR, performs a few optimizations, and turns that into x86 code. + There are three memory manager options available depending on the user's preference, leveraging both software-based (slower) and host-mapped modes (much faster). + The fastest option (host, unchecked) is set by default. + Ryujinx also features an optional Profiled Persistent Translation Cache, which essentially caches translated functions so that they do not need to be translated every time the game loads. + The net result is a significant reduction in load times (the amount of time between launching a game and arriving at the title screen) for nearly every game. + NOTE: This feature is enabled by default in the Options menu > System tab. + You must launch the game at least twice to the title screen or beyond before performance improvements are unlocked on the third launch! + These improvements are permanent and do not require any extra launches going forward. - **GPU** - The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively. There are currently six graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Anti-Aliasing, Scaling Filters (including FSR), Anisotropic Filtering and Aspect Ratio Adjustment. These enhancements can be adjusted or toggled as desired in the GUI. + The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively. + There are currently six graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Anti-Aliasing, Scaling Filters (including FSR), Anisotropic Filtering and Aspect Ratio Adjustment. + These enhancements can be adjusted or toggled as desired in the GUI. - **Input** - We currently have support for keyboard, mouse, touch input, JoyCon input support, and nearly all controllers. Motion controls are natively supported in most cases; for dual-JoyCon motion support, DS4Windows or BetterJoy are currently required. - In all scenarios, you can set up everything inside the input configuration menu. + We currently have support for keyboard, mouse, touch input, JoyCon input support, and nearly all controllers. + Motion controls are natively supported in most cases; for dual-JoyCon motion support, DS4Windows or BetterJoy are currently required. + In all scenarios, you can set up everything inside the input configuration menu. - **DLC & Modifications** - Ryujinx is able to manage add-on content/downloadable content through the GUI. Mods (romfs, exefs, and runtime mods such as cheats) are also supported; the GUI contains a shortcut to open the respective mods folder for a particular game. + Ryujinx is able to manage add-on content/downloadable content through the GUI. + Mods (romfs, exefs, and runtime mods such as cheats) are also supported; + the GUI contains a shortcut to open the respective mods folder for a particular game. - **Configuration** - The emulator has settings for enabling or disabling some logging, remapping controllers, and more. You can configure all of them through the graphical interface or manually through the config file, `Config.json`, found in the user folder which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI. - + The emulator has settings for enabling or disabling some logging, remapping controllers, and more. + You can configure all of them through the graphical interface or manually through the config file, `Config.json`, found in the user folder which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI. ## Contact -If you have contributions, suggestions, need emulator support or just want to get in touch with the team, join our [Discord server](https://discord.com/invite/Ryujinx). You may also review our [FAQ](https://github.com/Ryujinx/Ryujinx/wiki/Frequently-Asked-Questions). +If you have contributions, suggestions, need emulator support or just want to get in touch with the team, join our [Discord server](https://discord.com/invite/Ryujinx). +You may also review our [FAQ](https://github.com/Ryujinx/Ryujinx/wiki/Frequently-Asked-Questions). ## Donations @@ -134,9 +158,10 @@ All funds received through Patreon are considered a donation to support the proj ## License -This software is licensed under the terms of the
MIT license.
+This software is licensed under the terms of the [MIT license](LICENSE.txt). This project makes use of code authored by the libvpx project, licensed under BSD and the ffmpeg project, licensed under LGPLv3. See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](distribution/legal/THIRDPARTY.md) for more details. + ## Credits - [LibHac](https://github.com/Thealexbarney/LibHac) is used for our file-system. From 30bdc4544e5270ba0676e3a288b947b685aa1cab Mon Sep 17 00:00:00 2001 From: gdkchan Date: Mon, 29 Jan 2024 12:28:32 -0300 Subject: [PATCH 020/126] Fix NRE when calling GetSockName before Bind (#6206) --- src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs index 870a6b36c..1e8a90051 100644 --- a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs @@ -121,7 +121,14 @@ private void WriteSockAddr(ServiceCtx context, ulong bufferPosition, ISocket soc { IPEndPoint endPoint = isRemote ? socket.RemoteEndPoint : socket.LocalEndPoint; - context.Memory.Write(bufferPosition, BsdSockAddr.FromIPEndPoint(endPoint)); + if (endPoint != null) + { + context.Memory.Write(bufferPosition, BsdSockAddr.FromIPEndPoint(endPoint)); + } + else + { + context.Memory.Write(bufferPosition, new BsdSockAddr()); + } } [CommandCmif(0)] From 7795b662a9b6054343195db9fc221a920509c1be Mon Sep 17 00:00:00 2001 From: Ac_K Date: Mon, 29 Jan 2024 16:32:34 +0100 Subject: [PATCH 021/126] Mod: Do LayeredFs loading Parallel to improve speed (#6180) * Mod: Do LayeredFs loading Parallel to improve speed This fixes and superseed #5672 due to inactivity, nothing more. (See original PR for description) Testing are welcome. Close #5661 * Addresses gdkchan's feedback * commit to test mako change * Revert "commit to test mako change" This reverts commit 8b0caa8a21db298db3dfcbe5b7e9029c4f066c46. --- src/Ryujinx.HLE/HOS/ModLoader.cs | 13 ++-- .../Services/Fs/FileSystemProxy/LazyFile.cs | 65 +++++++++++++++++++ 2 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/LazyFile.cs diff --git a/src/Ryujinx.HLE/HOS/ModLoader.cs b/src/Ryujinx.HLE/HOS/ModLoader.cs index 322c8afdc..8ae529655 100644 --- a/src/Ryujinx.HLE/HOS/ModLoader.cs +++ b/src/Ryujinx.HLE/HOS/ModLoader.cs @@ -18,6 +18,7 @@ using System.Globalization; using System.IO; using System.Linq; +using LazyFile = Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy.LazyFile; using Path = System.IO.Path; namespace Ryujinx.HLE.HOS @@ -512,7 +513,7 @@ internal IStorage ApplyRomFsMods(ulong applicationId, IStorage baseStorage) using (IFileSystem fs = new LocalFileSystem(mod.Path.FullName)) { - AddFiles(fs, mod.Name, fileSet, builder); + AddFiles(fs, mod.Name, mod.Path.FullName, fileSet, builder); } count++; } @@ -528,7 +529,7 @@ internal IStorage ApplyRomFsMods(ulong applicationId, IStorage baseStorage) Logger.Info?.Print(LogClass.ModLoader, $"Found 'romfs.bin' for Application {applicationId:X16}"); using (IFileSystem fs = new RomFsFileSystem(mod.Path.OpenRead().AsStorage())) { - AddFiles(fs, mod.Name, fileSet, builder); + AddFiles(fs, mod.Name, mod.Path.FullName, fileSet, builder); } count++; } @@ -561,18 +562,18 @@ internal IStorage ApplyRomFsMods(ulong applicationId, IStorage baseStorage) return newStorage; } - private static void AddFiles(IFileSystem fs, string modName, ISet fileSet, RomFsBuilder builder) + private static void AddFiles(IFileSystem fs, string modName, string rootPath, ISet fileSet, RomFsBuilder builder) { foreach (var entry in fs.EnumerateEntries() + .AsParallel() .Where(f => f.Type == DirectoryEntryType.File) .OrderBy(f => f.FullPath, StringComparer.Ordinal)) { - using var file = new UniqueRef(); + var file = new LazyFile(entry.FullPath, rootPath, fs); - fs.OpenFile(ref file.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); if (fileSet.Add(entry.FullPath)) { - builder.AddFile(entry.FullPath, file.Release()); + builder.AddFile(entry.FullPath, file); } else { diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/LazyFile.cs b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/LazyFile.cs new file mode 100644 index 000000000..a179e8e38 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/LazyFile.cs @@ -0,0 +1,65 @@ +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using System; +using System.IO; + +namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy +{ + class LazyFile : LibHac.Fs.Fsa.IFile + { + private readonly LibHac.Fs.Fsa.IFileSystem _fs; + private readonly string _filePath; + private readonly UniqueRef _fileReference = new(); + private readonly FileInfo _fileInfo; + + public LazyFile(string filePath, string prefix, LibHac.Fs.Fsa.IFileSystem fs) + { + _fs = fs; + _filePath = filePath; + _fileInfo = new FileInfo(prefix + "/" + filePath); + } + + private void PrepareFile() + { + if (_fileReference.Get == null) + { + _fs.OpenFile(ref _fileReference.Ref, _filePath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + } + } + + protected override Result DoRead(out long bytesRead, long offset, Span destination, in ReadOption option) + { + PrepareFile(); + + return _fileReference.Get!.Read(out bytesRead, offset, destination); + } + + protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + { + throw new NotSupportedException(); + } + + protected override Result DoFlush() + { + throw new NotSupportedException(); + } + + protected override Result DoSetSize(long size) + { + throw new NotSupportedException(); + } + + protected override Result DoGetSize(out long size) + { + size = _fileInfo.Length; + + return Result.Success; + } + + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) + { + throw new NotSupportedException(); + } + } +} From 70fcba39de34fc211d09da12783898161724b0dc Mon Sep 17 00:00:00 2001 From: TSRBerry <20988865+TSRBerry@users.noreply.github.com> Date: Mon, 29 Jan 2024 19:58:18 +0100 Subject: [PATCH 022/126] Make config filename changable for releases & Log to Ryujinx directory if application directory is not writable (#4707) * Remove GetBaseApplicationDirectory() & Move logs directory to user base path We should assume the application directory might be write-protected. * Use Ryujinx.sh in Ryujinx.desktop This desktop file isn't really used right now, so this changes effectively nothing. * Use properties in ReleaseInformation.cs and add ConfigName property * Configure config filename in Github workflows * Add a separate config step for macOS Because they use BSD sed instead of GNU sed * Keep log directory at the old location for dev environments * Add FileSystemUtils since Directory.Move() doesn't work across filesystems Steal CopyDirectory code from https://learn.microsoft.com/en-us/dotnet/standard/io/how-to-copy-directories * Fix "Open Logs folder" button pointing to the wrong directory * Add execute permissions to Ryujinx.sh * Fix missing newlines * AppDataManager: Use FileSystemUtils.MoveDirectory() * Make dotnet format happy * Add a fallback for the logging directory --- .github/workflows/build.yml | 17 +++++- .github/workflows/release.yml | 2 + distribution/linux/Ryujinx.desktop | 2 +- distribution/linux/Ryujinx.sh | 2 +- src/Ryujinx.Ava/Modules/Updater/Updater.cs | 4 +- src/Ryujinx.Ava/Program.cs | 6 +- .../UI/ViewModels/MainWindowViewModel.cs | 9 ++- .../Configuration/AppDataManager.cs | 38 ++---------- .../Logging/Targets/FileLogTarget.cs | 60 +++++++++++++++---- src/Ryujinx.Common/ReleaseInformation.cs | 55 +++++------------ .../Utilities/FileSystemUtils.cs | 48 +++++++++++++++ src/Ryujinx.Headless.SDL2/Program.cs | 27 +++++++-- src/Ryujinx.SDL2.Common/SDL2Driver.cs | 5 +- .../App/ApplicationLibrary.cs | 3 +- .../Configuration/LoggerModule.cs | 19 +++++- .../Helper/FileAssociationHelper.cs | 4 +- src/Ryujinx/Modules/Updater/UpdateDialog.cs | 3 +- src/Ryujinx/Modules/Updater/Updater.cs | 4 +- src/Ryujinx/Program.cs | 6 +- src/Ryujinx/Ui/Helper/ThemeHelper.cs | 3 +- src/Ryujinx/Ui/MainWindow.cs | 7 ++- .../Ui/Widgets/GameTableContextMenu.cs | 2 +- 22 files changed, 209 insertions(+), 117 deletions(-) mode change 100644 => 100755 distribution/linux/Ryujinx.sh create mode 100644 src/Ryujinx.Common/Utilities/FileSystemUtils.cs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cf4fdf051..6124ae513 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,7 +40,7 @@ jobs: - uses: actions/setup-dotnet@v4 with: global-json-file: global.json - + - name: Overwrite csc problem matcher run: echo "::add-matcher::.github/csc.json" @@ -49,6 +49,16 @@ jobs: run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT shell: bash + - name: Change config filename + run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs + shell: bash + if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest' + + - name: Change config filename for macOS + run: sed -r -i '' 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs + shell: bash + if: github.event_name == 'pull_request' && matrix.os == 'macOS-latest' + - name: Build run: dotnet build -c "${{ matrix.configuration }}" -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER @@ -135,6 +145,11 @@ jobs: id: git_short_hash run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT + - name: Change config filename + run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs + shell: bash + if: github.event_name == 'pull_request' + - name: Publish macOS Ryujinx.Ava run: | ./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1fb0acdc7..d6bcd3fa4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -85,6 +85,7 @@ jobs: sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs + sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs shell: bash - name: Create output dir @@ -186,6 +187,7 @@ jobs: sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs + sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs shell: bash - name: Publish macOS Ryujinx.Ava diff --git a/distribution/linux/Ryujinx.desktop b/distribution/linux/Ryujinx.desktop index a4550d104..44f05bf3f 100644 --- a/distribution/linux/Ryujinx.desktop +++ b/distribution/linux/Ryujinx.desktop @@ -4,7 +4,7 @@ Name=Ryujinx Type=Application Icon=Ryujinx Exec=Ryujinx.sh %f -Comment=Plays Nintendo Switch applications +Comment=A Nintendo Switch Emulator GenericName=Nintendo Switch Emulator Terminal=false Categories=Game;Emulator; diff --git a/distribution/linux/Ryujinx.sh b/distribution/linux/Ryujinx.sh old mode 100644 new mode 100755 index f356cad01..a80cdcaec --- a/distribution/linux/Ryujinx.sh +++ b/distribution/linux/Ryujinx.sh @@ -17,4 +17,4 @@ if command -v gamemoderun > /dev/null 2>&1; then COMMAND="$COMMAND gamemoderun" fi -$COMMAND "$SCRIPT_DIR/$RYUJINX_BIN" "$@" \ No newline at end of file +$COMMAND "$SCRIPT_DIR/$RYUJINX_BIN" "$@" diff --git a/src/Ryujinx.Ava/Modules/Updater/Updater.cs b/src/Ryujinx.Ava/Modules/Updater/Updater.cs index af7608d34..ad33b1013 100644 --- a/src/Ryujinx.Ava/Modules/Updater/Updater.cs +++ b/src/Ryujinx.Ava/Modules/Updater/Updater.cs @@ -665,7 +665,7 @@ public static bool CanUpdate(bool showWarnings) return false; } - if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid()) + if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid) { if (showWarnings) { @@ -683,7 +683,7 @@ public static bool CanUpdate(bool showWarnings) #else if (showWarnings) { - if (ReleaseInformation.IsFlatHubBuild()) + if (ReleaseInformation.IsFlatHubBuild) { Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateWarningDialog( diff --git a/src/Ryujinx.Ava/Program.cs b/src/Ryujinx.Ava/Program.cs index cc062a256..d85749eff 100644 --- a/src/Ryujinx.Ava/Program.cs +++ b/src/Ryujinx.Ava/Program.cs @@ -35,7 +35,7 @@ internal partial class Program public static void Main(string[] args) { - Version = ReleaseInformation.GetVersion(); + Version = ReleaseInformation.Version; if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134)) { @@ -125,8 +125,8 @@ private static void Initialize(string[] args) public static void ReloadConfig() { - string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"); - string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, "Config.json"); + string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName); + string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName); // Now load the configuration as the other subsystems are now registered if (File.Exists(localConfigurationPath)) diff --git a/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs index 7146dfd7c..dff5b59bd 100644 --- a/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs @@ -357,7 +357,7 @@ public ApplicationData SelectedApplication public bool OpenBcatSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0; - public bool CreateShortcutEnabled => !ReleaseInformation.IsFlatHubBuild(); + public bool CreateShortcutEnabled => !ReleaseInformation.IsFlatHubBuild; public string LoadHeading { @@ -1350,7 +1350,12 @@ public void OpenRyujinxFolder() public void OpenLogsFolder() { - string logPath = Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "Logs"); + string logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs"); + + if (ReleaseInformation.IsValid) + { + logPath = Path.Combine(AppDataManager.BaseDirPath, "Logs"); + } new DirectoryInfo(logPath).Create(); diff --git a/src/Ryujinx.Common/Configuration/AppDataManager.cs b/src/Ryujinx.Common/Configuration/AppDataManager.cs index 8a226d9ab..35aea3c2f 100644 --- a/src/Ryujinx.Common/Configuration/AppDataManager.cs +++ b/src/Ryujinx.Common/Configuration/AppDataManager.cs @@ -1,4 +1,5 @@ using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; using System; using System.IO; @@ -6,8 +7,8 @@ namespace Ryujinx.Common.Configuration { public static class AppDataManager { - public const string DefaultBaseDir = "Ryujinx"; - public const string DefaultPortableDir = "portable"; + private const string DefaultBaseDir = "Ryujinx"; + private const string DefaultPortableDir = "portable"; // The following 3 are always part of Base Directory private const string GamesDir = "games"; @@ -109,8 +110,7 @@ public static void Initialize(string baseDirPath) string oldConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir); if (Path.Exists(oldConfigPath) && !IsPathSymlink(oldConfigPath) && !Path.Exists(BaseDirPath)) { - CopyDirectory(oldConfigPath, BaseDirPath); - Directory.Delete(oldConfigPath, true); + FileSystemUtils.MoveDirectory(oldConfigPath, BaseDirPath); Directory.CreateSymbolicLink(oldConfigPath, BaseDirPath); } } @@ -127,41 +127,13 @@ private static void SetupBasePaths() } // Check if existing old baseDirPath is a symlink, to prevent possible errors. - // Should be removed, when the existance of the old directory isn't checked anymore. + // Should be removed, when the existence of the old directory isn't checked anymore. private static bool IsPathSymlink(string path) { FileAttributes attributes = File.GetAttributes(path); return (attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint; } - private static void CopyDirectory(string sourceDir, string destinationDir) - { - var dir = new DirectoryInfo(sourceDir); - - if (!dir.Exists) - { - throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}"); - } - - DirectoryInfo[] subDirs = dir.GetDirectories(); - Directory.CreateDirectory(destinationDir); - - foreach (FileInfo file in dir.GetFiles()) - { - if (file.Name == ".DS_Store") - { - continue; - } - - file.CopyTo(Path.Combine(destinationDir, file.Name)); - } - - foreach (DirectoryInfo subDir in subDirs) - { - CopyDirectory(subDir.FullName, Path.Combine(destinationDir, subDir.Name)); - } - } - public static string GetModsPath() => CustomModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultModsDir)).FullName; public static string GetSdModsPath() => CustomSdModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultSdcardDir, "atmosphere")).FullName; } diff --git a/src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs b/src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs index 8aa2a26b9..c40c3abec 100644 --- a/src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs +++ b/src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs @@ -13,31 +13,71 @@ public class FileLogTarget : ILogTarget string ILogTarget.Name { get => _name; } - public FileLogTarget(string path, string name) - : this(path, name, FileShare.Read, FileMode.Append) - { } + public FileLogTarget(string name, FileStream fileStream) + { + _name = name; + _logWriter = new StreamWriter(fileStream); + _formatter = new DefaultLogFormatter(); + } - public FileLogTarget(string path, string name, FileShare fileShare, FileMode fileMode) + public static FileStream PrepareLogFile(string path) { // Ensure directory is present DirectoryInfo logDir = new(Path.Combine(path, "Logs")); - logDir.Create(); + try + { + logDir.Create(); + } + catch (IOException exception) + { + Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}': {exception}"); + + return null; + } // Clean up old logs, should only keep 3 FileInfo[] files = logDir.GetFiles("*.log").OrderBy((info => info.CreationTime)).ToArray(); for (int i = 0; i < files.Length - 2; i++) { - files[i].Delete(); + try + { + files[i].Delete(); + } + catch (UnauthorizedAccessException exception) + { + Logger.Warning?.Print(LogClass.Application, $"Old log file could not be deleted '{files[i].FullName}': {exception}"); + + return null; + } + catch (IOException exception) + { + Logger.Warning?.Print(LogClass.Application, $"Old log file could not be deleted '{files[i].FullName}': {exception}"); + + return null; + } } - string version = ReleaseInformation.GetVersion(); + string version = ReleaseInformation.Version; // Get path for the current time path = Path.Combine(logDir.FullName, $"Ryujinx_{version}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log"); - _name = name; - _logWriter = new StreamWriter(File.Open(path, fileMode, FileAccess.Write, fileShare)); - _formatter = new DefaultLogFormatter(); + try + { + return File.Open(path, FileMode.Append, FileAccess.Write, FileShare.Read); + } + catch (UnauthorizedAccessException exception) + { + Logger.Warning?.Print(LogClass.Application, $"Log file could not be created '{path}': {exception}"); + + return null; + } + catch (IOException exception) + { + Logger.Warning?.Print(LogClass.Application, $"Log file could not be created '{path}': {exception}"); + + return null; + } } public void Log(object sender, LogEventArgs args) diff --git a/src/Ryujinx.Common/ReleaseInformation.cs b/src/Ryujinx.Common/ReleaseInformation.cs index ab65a98f3..774ae012a 100644 --- a/src/Ryujinx.Common/ReleaseInformation.cs +++ b/src/Ryujinx.Common/ReleaseInformation.cs @@ -1,5 +1,3 @@ -using Ryujinx.Common.Configuration; -using System; using System.Reflection; namespace Ryujinx.Common @@ -9,50 +7,25 @@ public static class ReleaseInformation { private const string FlatHubChannelOwner = "flathub"; - public const string BuildVersion = "%%RYUJINX_BUILD_VERSION%%"; - public const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%"; - public const string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%"; + private const string BuildVersion = "%%RYUJINX_BUILD_VERSION%%"; + private const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%"; + private const string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%"; + private const string ConfigFileName = "%%RYUJINX_CONFIG_FILE_NAME%%"; + public const string ReleaseChannelOwner = "%%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER%%"; public const string ReleaseChannelRepo = "%%RYUJINX_TARGET_RELEASE_CHANNEL_REPO%%"; - public static bool IsValid() - { - return !BuildGitHash.StartsWith("%%") && - !ReleaseChannelName.StartsWith("%%") && - !ReleaseChannelOwner.StartsWith("%%") && - !ReleaseChannelRepo.StartsWith("%%"); - } - - public static bool IsFlatHubBuild() - { - return IsValid() && ReleaseChannelOwner.Equals(FlatHubChannelOwner); - } - - public static string GetVersion() - { - if (IsValid()) - { - return BuildVersion; - } + public static string ConfigName => !ConfigFileName.StartsWith("%%") ? ConfigFileName : "Config.json"; - return Assembly.GetEntryAssembly().GetCustomAttribute().InformationalVersion; - } + public static bool IsValid => + !BuildGitHash.StartsWith("%%") && + !ReleaseChannelName.StartsWith("%%") && + !ReleaseChannelOwner.StartsWith("%%") && + !ReleaseChannelRepo.StartsWith("%%") && + !ConfigFileName.StartsWith("%%"); -#if FORCE_EXTERNAL_BASE_DIR - public static string GetBaseApplicationDirectory() - { - return AppDataManager.BaseDirPath; - } -#else - public static string GetBaseApplicationDirectory() - { - if (IsFlatHubBuild() || OperatingSystem.IsMacOS()) - { - return AppDataManager.BaseDirPath; - } + public static bool IsFlatHubBuild => IsValid && ReleaseChannelOwner.Equals(FlatHubChannelOwner); - return AppDomain.CurrentDomain.BaseDirectory; - } -#endif + public static string Version => IsValid ? BuildVersion : Assembly.GetEntryAssembly()!.GetCustomAttribute()?.InformationalVersion; } } diff --git a/src/Ryujinx.Common/Utilities/FileSystemUtils.cs b/src/Ryujinx.Common/Utilities/FileSystemUtils.cs new file mode 100644 index 000000000..e76c2b60b --- /dev/null +++ b/src/Ryujinx.Common/Utilities/FileSystemUtils.cs @@ -0,0 +1,48 @@ +using System.IO; + +namespace Ryujinx.Common.Utilities +{ + public static class FileSystemUtils + { + public static void CopyDirectory(string sourceDir, string destinationDir, bool recursive) + { + // Get information about the source directory + var dir = new DirectoryInfo(sourceDir); + + // Check if the source directory exists + if (!dir.Exists) + { + throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}"); + } + + // Cache directories before we start copying + DirectoryInfo[] dirs = dir.GetDirectories(); + + // Create the destination directory + Directory.CreateDirectory(destinationDir); + + // Get the files in the source directory and copy to the destination directory + foreach (FileInfo file in dir.GetFiles()) + { + string targetFilePath = Path.Combine(destinationDir, file.Name); + file.CopyTo(targetFilePath); + } + + // If recursive and copying subdirectories, recursively call this method + if (recursive) + { + foreach (DirectoryInfo subDir in dirs) + { + string newDestinationDir = Path.Combine(destinationDir, subDir.Name); + CopyDirectory(subDir.FullName, newDestinationDir, true); + } + } + } + + public static void MoveDirectory(string sourceDir, string destinationDir) + { + CopyDirectory(sourceDir, destinationDir, true); + Directory.Delete(sourceDir, true); + } + } +} diff --git a/src/Ryujinx.Headless.SDL2/Program.cs b/src/Ryujinx.Headless.SDL2/Program.cs index e545079b9..6eaa1b860 100644 --- a/src/Ryujinx.Headless.SDL2/Program.cs +++ b/src/Ryujinx.Headless.SDL2/Program.cs @@ -61,7 +61,7 @@ class Program static void Main(string[] args) { - Version = ReleaseInformation.GetVersion(); + Version = ReleaseInformation.Version; // Make process DPI aware for proper window sizing on high-res screens. ForceDpiAware.Windows(); @@ -427,11 +427,26 @@ static void LoadPlayerConfiguration(string inputProfileName, string inputId, Pla if (!option.DisableFileLog) { - Logger.AddTarget(new AsyncLogTargetWrapper( - new FileLogTarget(ReleaseInformation.GetBaseApplicationDirectory(), "file"), - 1000, - AsyncLogTargetOverflowAction.Block - )); + FileStream logFile = FileLogTarget.PrepareLogFile(AppDomain.CurrentDomain.BaseDirectory); + + if (logFile == null) + { + logFile = FileLogTarget.PrepareLogFile(AppDataManager.BaseDirPath); + + if (logFile == null) + { + Logger.Error?.Print(LogClass.Application, "No writable log directory available. Make sure either the application directory or the Ryujinx directory is writable."); + } + } + + if (logFile != null) + { + Logger.AddTarget(new AsyncLogTargetWrapper( + new FileLogTarget("file", logFile), + 1000, + AsyncLogTargetOverflowAction.Block + )); + } } // Setup graphics configuration diff --git a/src/Ryujinx.SDL2.Common/SDL2Driver.cs b/src/Ryujinx.SDL2.Common/SDL2Driver.cs index db1a85e3a..552deafdd 100644 --- a/src/Ryujinx.SDL2.Common/SDL2Driver.cs +++ b/src/Ryujinx.SDL2.Common/SDL2Driver.cs @@ -1,4 +1,3 @@ -using Ryujinx.Common; using Ryujinx.Common.Logging; using System; using System.Collections.Concurrent; @@ -13,8 +12,6 @@ public class SDL2Driver : IDisposable { private static SDL2Driver _instance; - public static bool IsInitialized => _instance != null; - public static SDL2Driver Instance { get @@ -96,7 +93,7 @@ public void Initialize() SDL_EventState(SDL_EventType.SDL_CONTROLLERSENSORUPDATE, SDL_DISABLE); - string gamepadDbPath = Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "SDL_GameControllerDB.txt"); + string gamepadDbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SDL_GameControllerDB.txt"); if (File.Exists(gamepadDbPath)) { diff --git a/src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs b/src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs index 950eb55b4..3b35ff270 100644 --- a/src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs +++ b/src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs @@ -8,6 +8,7 @@ using LibHac.Tools.Fs; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using Ryujinx.Common.Utilities; @@ -105,7 +106,7 @@ public void LoadApplications(List appDirs, Language desiredTitleLanguage if (!Directory.Exists(appDir)) { - Logger.Warning?.Print(LogClass.Application, $"The \"game_dirs\" section in \"Config.json\" contains an invalid directory: \"{appDir}\""); + Logger.Warning?.Print(LogClass.Application, $"The \"game_dirs\" section in \"{ReleaseInformation.ConfigName}\" contains an invalid directory: \"{appDir}\""); continue; } diff --git a/src/Ryujinx.Ui.Common/Configuration/LoggerModule.cs b/src/Ryujinx.Ui.Common/Configuration/LoggerModule.cs index 54ad20dd7..6cd63272e 100644 --- a/src/Ryujinx.Ui.Common/Configuration/LoggerModule.cs +++ b/src/Ryujinx.Ui.Common/Configuration/LoggerModule.cs @@ -1,7 +1,9 @@ using Ryujinx.Common; +using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using Ryujinx.Common.Logging.Targets; using System; +using System.IO; namespace Ryujinx.Ui.Common.Configuration { @@ -80,8 +82,23 @@ private static void ReloadFileLogger(object sender, ReactiveEventArgs e) { if (e.NewValue) { + FileStream logFile = FileLogTarget.PrepareLogFile(AppDomain.CurrentDomain.BaseDirectory); + + if (logFile == null) + { + logFile = FileLogTarget.PrepareLogFile(AppDataManager.BaseDirPath); + + if (logFile == null) + { + Logger.Error?.Print(LogClass.Application, "No writable log directory available. Make sure either the application directory or the Ryujinx directory is writable."); + Logger.RemoveTarget("file"); + + return; + } + } + Logger.AddTarget(new AsyncLogTargetWrapper( - new FileLogTarget(ReleaseInformation.GetBaseApplicationDirectory(), "file"), + new FileLogTarget("file", logFile), 1000, AsyncLogTargetOverflowAction.Block )); diff --git a/src/Ryujinx.Ui.Common/Helper/FileAssociationHelper.cs b/src/Ryujinx.Ui.Common/Helper/FileAssociationHelper.cs index 570fb91e2..daa59d251 100644 --- a/src/Ryujinx.Ui.Common/Helper/FileAssociationHelper.cs +++ b/src/Ryujinx.Ui.Common/Helper/FileAssociationHelper.cs @@ -22,7 +22,7 @@ public static partial class FileAssociationHelper [LibraryImport("shell32.dll", SetLastError = true)] public static partial void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2); - public static bool IsTypeAssociationSupported => (OperatingSystem.IsLinux() || OperatingSystem.IsWindows()) && !ReleaseInformation.IsFlatHubBuild(); + public static bool IsTypeAssociationSupported => (OperatingSystem.IsLinux() || OperatingSystem.IsWindows()) && !ReleaseInformation.IsFlatHubBuild; [SupportedOSPlatform("linux")] private static bool AreMimeTypesRegisteredLinux() => File.Exists(Path.Combine(_mimeDbPath, "packages", "Ryujinx.xml")); @@ -34,7 +34,7 @@ private static bool InstallLinuxMimeTypes(bool uninstall = false) if ((uninstall && AreMimeTypesRegisteredLinux()) || (!uninstall && !AreMimeTypesRegisteredLinux())) { - string mimeTypesFile = Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "mime", "Ryujinx.xml"); + string mimeTypesFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "mime", "Ryujinx.xml"); string additionalArgs = !uninstall ? "--novendor" : ""; using Process mimeProcess = new(); diff --git a/src/Ryujinx/Modules/Updater/UpdateDialog.cs b/src/Ryujinx/Modules/Updater/UpdateDialog.cs index 695634374..0057761bb 100644 --- a/src/Ryujinx/Modules/Updater/UpdateDialog.cs +++ b/src/Ryujinx/Modules/Updater/UpdateDialog.cs @@ -1,6 +1,7 @@ using Gdk; using Gtk; using Ryujinx.Common; +using Ryujinx.Common.Configuration; using Ryujinx.Ui; using Ryujinx.Ui.Common.Configuration; using Ryujinx.Ui.Common.Helper; @@ -52,7 +53,7 @@ private void YesButton_Clicked(object sender, EventArgs args) ProcessStartInfo processStart = new(ryuName) { UseShellExecute = true, - WorkingDirectory = ReleaseInformation.GetBaseApplicationDirectory(), + WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory }; foreach (string argument in CommandLineState.Arguments) diff --git a/src/Ryujinx/Modules/Updater/Updater.cs b/src/Ryujinx/Modules/Updater/Updater.cs index f8ce4c0b9..2fed43622 100644 --- a/src/Ryujinx/Modules/Updater/Updater.cs +++ b/src/Ryujinx/Modules/Updater/Updater.cs @@ -532,7 +532,7 @@ public static bool CanUpdate(bool showWarnings) return false; } - if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid()) + if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid) { if (showWarnings) { @@ -546,7 +546,7 @@ public static bool CanUpdate(bool showWarnings) #else if (showWarnings) { - if (ReleaseInformation.IsFlatHubBuild()) + if (ReleaseInformation.IsFlatHubBuild) { GtkDialog.CreateWarningDialog("Updater Disabled!", "Please update Ryujinx via FlatHub."); } diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs index 597d00f30..3b5cd770b 100644 --- a/src/Ryujinx/Program.cs +++ b/src/Ryujinx/Program.cs @@ -71,7 +71,7 @@ static Program() static void Main(string[] args) { - Version = ReleaseInformation.GetVersion(); + Version = ReleaseInformation.Version; if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134)) { @@ -167,8 +167,8 @@ static void SetEnvironmentVariableNoCaching(string key, string value) Quality = 100, }); - string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"); - string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, "Config.json"); + string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName); + string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName); // Now load the configuration as the other subsystems are now registered ConfigurationPath = File.Exists(localConfigurationPath) diff --git a/src/Ryujinx/Ui/Helper/ThemeHelper.cs b/src/Ryujinx/Ui/Helper/ThemeHelper.cs index 67962cb6c..5cd9ad520 100644 --- a/src/Ryujinx/Ui/Helper/ThemeHelper.cs +++ b/src/Ryujinx/Ui/Helper/ThemeHelper.cs @@ -1,4 +1,5 @@ using Gtk; +using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.Ui.Common.Configuration; using System.IO; @@ -24,7 +25,7 @@ public static void ApplyTheme() } else { - Logger.Warning?.Print(LogClass.Application, $"The \"custom_theme_path\" section in \"Config.json\" contains an invalid path: \"{ConfigurationState.Instance.Ui.CustomThemePath}\"."); + Logger.Warning?.Print(LogClass.Application, $"The \"custom_theme_path\" section in \"{ReleaseInformation.ConfigName}\" contains an invalid path: \"{ConfigurationState.Instance.Ui.CustomThemePath}\"."); ConfigurationState.Instance.Ui.CustomThemePath.Value = ""; ConfigurationState.Instance.Ui.EnableCustomTheme.Value = false; diff --git a/src/Ryujinx/Ui/MainWindow.cs b/src/Ryujinx/Ui/MainWindow.cs index 6cce034b6..3cd2b0eb6 100644 --- a/src/Ryujinx/Ui/MainWindow.cs +++ b/src/Ryujinx/Ui/MainWindow.cs @@ -1376,7 +1376,12 @@ private void Open_Ryu_Folder(object sender, EventArgs args) private void OpenLogsFolder_Pressed(object sender, EventArgs args) { - string logPath = System.IO.Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "Logs"); + string logPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs"); + + if (ReleaseInformation.IsValid) + { + logPath = System.IO.Path.Combine(AppDataManager.BaseDirPath, "Logs"); + } new DirectoryInfo(logPath).Create(); diff --git a/src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs b/src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs index 6bf43842c..eb9f52d73 100644 --- a/src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs +++ b/src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs @@ -78,7 +78,7 @@ public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSyst _extractExeFsMenuItem.Sensitive = hasNca; _extractLogoMenuItem.Sensitive = hasNca; - _createShortcutMenuItem.Sensitive = !ReleaseInformation.IsFlatHubBuild(); + _createShortcutMenuItem.Sensitive = !ReleaseInformation.IsFlatHubBuild; PopupAtPointer(null); } From 20a392ad552ce5cdbff1cb74f1d26d2f797cca31 Mon Sep 17 00:00:00 2001 From: TSRBerry <20988865+TSRBerry@users.noreply.github.com> Date: Mon, 29 Jan 2024 20:10:29 +0100 Subject: [PATCH 023/126] Remove events that trigger from a forked repository (#6213) [skip ci] --- .github/workflows/mako.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/mako.yml b/.github/workflows/mako.yml index 7317cc9d3..19165fb04 100644 --- a/.github/workflows/mako.yml +++ b/.github/workflows/mako.yml @@ -9,10 +9,6 @@ on: types: [created, edited] issues: types: [opened, edited, reopened, pinned, milestoned, demilestoned, assigned, unassigned, labeled, unlabeled] - pull_request_review: - types: [submitted, dismissed] - pull_request_review_comment: - types: [created, edited] pull_request_target: types: [opened, edited, reopened, synchronize, ready_for_review, assigned, unassigned] From 4117c13377b51b83ff87b1d00393be1a5ab5bfff Mon Sep 17 00:00:00 2001 From: gdkchan Date: Mon, 29 Jan 2024 18:45:40 -0300 Subject: [PATCH 024/126] Migrate friends service to new IPC (#6174) * Migrate friends service to new IPC * Add a note that the pointer buffer size and domain counts are wrong * Wrong length * Format whitespace * PR feedback * Fill in structs from PR feedback * Missed that one * Somehow forgot to save that one * Fill in enums from PR review * Language enum, NotificationTime * Format whitespace * Fix the warning --- src/Ryujinx.HLE/HOS/Horizon.cs | 2 +- .../Services/Account/Acc/AccountManager.cs | 13 +- .../HOS/Services/Friend/IServiceCreator.cs | 55 - .../HOS/Services/Friend/ResultCode.cs | 14 - .../FriendService/Types/Friend.cs | 29 - .../FriendService/Types/FriendFilter.cs | 24 - .../FriendService/Types/UserPresence.cs | 34 - .../IDaemonSuspendSessionService.cs | 14 - .../Friend/ServiceCreator/IFriendService.cs | 374 ------ .../ServiceCreator/INotificationService.cs | 178 --- .../NotificationEventHandler.cs | 74 -- .../Types/NotificationInfo.cs | 13 - .../Friends/FriendsIpcServer.cs | 49 + src/Ryujinx.Horizon/Friends/FriendsMain.cs | 17 + .../Friends/FriendsPortIndex.cs | 11 + .../Friends/FriendsServerManager.cs | 36 + src/Ryujinx.Horizon/HorizonOptions.cs | 5 +- .../Sdk/Account/IEmulatorAccountManager.cs | 8 + .../Sdk/Account/NetworkServiceAccountId.cs | 20 + src/Ryujinx.Horizon/Sdk/Account/Nickname.cs | 29 + src/Ryujinx.Horizon/Sdk/Account/Uid.cs | 16 +- .../Sdk/Friends/ApplicationInfo.cs | 12 + .../Sdk/Friends/Detail/BlockedUserImpl.cs | 8 + .../Sdk/Friends/Detail/FriendCandidateImpl.cs | 8 + .../Friends/Detail/FriendDetailedInfoImpl.cs | 9 + .../Sdk/Friends/Detail/FriendImpl.cs | 19 + .../Detail/FriendInvitationForViewerImpl.cs | 8 + .../Detail/FriendInvitationGroupImpl.cs | 9 + .../Sdk/Friends/Detail/FriendRequestImpl.cs | 8 + .../Sdk/Friends/Detail/FriendSettingImpl.cs | 9 + .../Detail/Ipc/DaemonSuspendSessionService.cs | 7 + .../Sdk/Friends/Detail/Ipc/FriendService.cs | 1015 +++++++++++++++++ .../Ipc/FriendsServicePermissionLevel.cs} | 9 +- .../Ipc/IDaemonSuspendSessionService.cs | 9 + .../Sdk/Friends/Detail/Ipc/IFriendService.cs | 97 ++ .../Detail/Ipc/INotificationService.cs | 12 + .../Sdk/Friends/Detail/Ipc/IServiceCreator.cs | 13 + .../Detail/Ipc/NotificationEventHandler.cs | 58 + .../Detail/Ipc}/NotificationEventType.cs | 2 +- .../Friends/Detail/Ipc/NotificationService.cs | 172 +++ .../Detail/Ipc}/PresenceStatusFilter.cs | 2 +- .../Sdk/Friends/Detail/Ipc/ServiceCreator.cs | 51 + .../Friends/Detail/Ipc/SizedFriendFilter.cs | 25 + .../Detail/Ipc/SizedNotificationInfo.cs | 13 + .../Detail/NintendoNetworkIdFriendImpl.cs | 8 + .../Sdk/Friends/Detail/PlayHistoryImpl.cs | 8 + .../Sdk/Friends/Detail}/PresenceStatus.cs | 2 +- .../Sdk/Friends/Detail/ProfileExtraImpl.cs | 9 + .../Sdk/Friends/Detail/ProfileImpl.cs | 8 + .../Friends/Detail/SnsAccountFriendImpl.cs | 8 + .../Sdk/Friends/Detail/UserPresenceImpl.cs | 29 + .../Friends/Detail/UserPresenceViewImpl.cs | 9 + .../Sdk/Friends/Detail/UserSettingImpl.cs | 9 + .../Sdk/Friends/ExternalApplicationCatalog.cs | 9 + .../Friends/ExternalApplicationCatalogId.cs | 9 + .../FacedFriendRequestRegistrationKey.cs | 9 + src/Ryujinx.Horizon/Sdk/Friends/FriendCode.cs | 9 + .../FriendInvitationGameModeDescription.cs | 9 + .../Sdk/Friends/FriendInvitationGroupId.cs | 9 + .../Sdk/Friends/FriendInvitationId.cs | 8 + .../Sdk/Friends/FriendResult.cs | 13 + .../Sdk/Friends/InAppScreenName.cs | 26 + .../Sdk/Friends/MiiImageUrlParam.cs | 9 + src/Ryujinx.Horizon/Sdk/Friends/MiiName.cs | 9 + .../Sdk/Friends/NintendoNetworkIdUserInfo.cs | 9 + .../Friends}/PlayHistoryRegistrationKey.cs | 8 +- .../Sdk/Friends/PlayHistoryStatistics.cs | 9 + .../Sdk/Friends/Relationship.cs | 9 + src/Ryujinx.Horizon/Sdk/Friends/RequestId.cs | 9 + .../Sdk/Friends/SnsAccountLinkage.cs | 9 + .../Sdk/Friends/SnsAccountProfile.cs | 9 + src/Ryujinx.Horizon/Sdk/Friends/Url.cs | 30 + src/Ryujinx.Horizon/Sdk/Friends/WebPageUrl.cs | 9 + .../Sdk/Settings/BatteryLot.cs | 9 + .../Settings/Factory/AccelerometerOffset.cs | 12 + .../Settings/Factory/AccelerometerScale.cs | 12 + .../Factory/AmiiboEcdsaCertificate.cs | 9 + .../Factory/AmiiboEcqvBlsCertificate.cs | 9 + .../Sdk/Settings/Factory/AmiiboEcqvBlsKey.cs | 9 + .../Factory/AmiiboEcqvBlsRootCertificate.cs | 9 + .../Settings/Factory/AmiiboEcqvCertificate.cs | 9 + .../Sdk/Settings/Factory/AmiiboKey.cs | 9 + .../Factory/AnalogStickFactoryCalibration.cs | 9 + .../Factory/AnalogStickModelParameter.cs | 9 + .../Sdk/Settings/Factory/BdAddress.cs | 9 + .../Sdk/Settings/Factory/ConfigurationId1.cs | 9 + .../ConsoleSixAxisSensorHorizontalOffset.cs | 12 + .../Sdk/Settings/Factory/CountryCode.cs | 8 + .../Factory/EccB233DeviceCertificate.cs | 9 + .../Sdk/Settings/Factory/EccB233DeviceKey.cs | 9 + .../Settings/Factory/GameCardCertificate.cs | 9 + .../Sdk/Settings/Factory/GameCardKey.cs | 9 + .../Sdk/Settings/Factory/GyroscopeOffset.cs | 12 + .../Sdk/Settings/Factory/GyroscopeScale.cs | 12 + .../Sdk/Settings/Factory/MacAddress.cs | 9 + .../Factory/Rsa2048DeviceCertificate.cs | 9 + .../Sdk/Settings/Factory/Rsa2048DeviceKey.cs | 9 + .../Sdk/Settings/Factory/SerialNumber.cs | 9 + .../Sdk/Settings/Factory/SpeakerParameter.cs | 32 + .../Sdk/Settings/Factory/SslCertificate.cs | 9 + .../Sdk/Settings/Factory/SslKey.cs | 9 + src/Ryujinx.Horizon/Sdk/Settings/Language.cs | 24 + .../Sdk/Settings/LanguageCode.cs | 63 + .../Sdk/Settings/SettingsItemKey.cs | 9 + .../Sdk/Settings/SettingsName.cs | 9 + .../System/AccountNotificationSettings.cs | 15 + .../System/AccountOnlineStorageSettings.cs | 6 + .../Sdk/Settings/System/AccountSettings.cs | 9 + .../Sdk/Settings/System/AllowedSslHost.cs | 9 + .../System/AnalogStickUserCalibration.cs | 9 + .../Sdk/Settings/System/AppletLaunchFlag.cs | 9 + .../Sdk/Settings/System/AudioVolume.cs | 9 + .../Sdk/Settings/System/BacklightSettings.cs | 22 + .../Settings/System/BacklightSettingsEx.cs | 9 + .../Sdk/Settings/System/BlePairingSettings.cs | 6 + .../System/BluetoothDevicesSettings.cs | 29 + .../System/ButtonConfigRegisteredSettings.cs | 9 + .../Settings/System/ButtonConfigSettings.cs | 9 + .../ConsoleSixAxisSensorAccelerationBias.cs | 9 + .../ConsoleSixAxisSensorAccelerationGain.cs | 9 + ...ConsoleSixAxisSensorAngularAcceleration.cs | 9 + ...ConsoleSixAxisSensorAngularVelocityBias.cs | 9 + ...ConsoleSixAxisSensorAngularVelocityGain.cs | 9 + ...oleSixAxisSensorAngularVelocityTimeBias.cs | 9 + .../Settings/System/DataDeletionSettings.cs | 18 + .../Sdk/Settings/System/DeviceNickName.cs | 25 + .../Sdk/Settings/System/Edid.cs | 9 + .../Sdk/Settings/System/EulaVersion.cs | 6 + .../Sdk/Settings/System/FatalDirtyFlag.cs | 9 + .../Sdk/Settings/System/FirmwareVersion.cs | 9 + .../Settings/System/FirmwareVersionDigest.cs | 9 + .../Sdk/Settings/System/HomeMenuScheme.cs | 14 + .../Sdk/Settings/System/HostFsMountPoint.cs | 9 + .../Settings/System/InitialLaunchSettings.cs | 14 + .../Sdk/Settings/System/NetworkSettings.cs | 6 + .../Settings/System/NotificationSettings.cs | 38 + .../System/NxControllerLegacySettings.cs | 9 + .../Settings/System/NxControllerSettings.cs | 9 + .../Settings/System/PtmFuelGaugeParameter.cs | 20 + .../System/RebootlessSystemUpdateVersion.cs | 9 + .../Sdk/Settings/System/SerialNumber.cs | 9 + .../System/ServiceDiscoveryControlSettings.cs | 10 + .../Sdk/Settings/System/SleepSettings.cs | 40 + .../Sdk/Settings/System/TelemetryDirtyFlag.cs | 9 + .../Sdk/Settings/System/ThemeId.cs | 9 + .../Sdk/Settings/System/ThemeSettings.cs | 9 + .../Sdk/Settings/System/TvSettings.cs | 59 + src/Ryujinx.Horizon/ServiceTable.cs | 2 + 148 files changed, 3026 insertions(+), 832 deletions(-) delete mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ResultCode.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/Friend.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/FriendFilter.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/UserPresence.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IDaemonSuspendSessionService.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/NotificationEventHandler.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationInfo.cs create mode 100644 src/Ryujinx.Horizon/Friends/FriendsIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Friends/FriendsMain.cs create mode 100644 src/Ryujinx.Horizon/Friends/FriendsPortIndex.cs create mode 100644 src/Ryujinx.Horizon/Friends/FriendsServerManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Account/IEmulatorAccountManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Account/NetworkServiceAccountId.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Account/Nickname.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/ApplicationInfo.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/BlockedUserImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendCandidateImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendDetailedInfoImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationForViewerImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationGroupImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendRequestImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendSettingImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/DaemonSuspendSessionService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendService.cs rename src/{Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/FriendServicePermissionLevel.cs => Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendsServicePermissionLevel.cs} (64%) create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IDaemonSuspendSessionService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IFriendService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/INotificationService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IServiceCreator.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationEventHandler.cs rename src/{Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types => Ryujinx.Horizon/Sdk/Friends/Detail/Ipc}/NotificationEventType.cs (64%) create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationService.cs rename src/{Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types => Ryujinx.Horizon/Sdk/Friends/Detail/Ipc}/PresenceStatusFilter.cs (64%) create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/ServiceCreator.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedFriendFilter.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedNotificationInfo.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/NintendoNetworkIdFriendImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/PlayHistoryImpl.cs rename src/{Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types => Ryujinx.Horizon/Sdk/Friends/Detail}/PresenceStatus.cs (58%) create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileExtraImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/SnsAccountFriendImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceViewImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/UserSettingImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalog.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalogId.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/FacedFriendRequestRegistrationKey.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/FriendCode.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGameModeDescription.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGroupId.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationId.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/FriendResult.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/InAppScreenName.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/MiiImageUrlParam.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/MiiName.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/NintendoNetworkIdUserInfo.cs rename src/{Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types => Ryujinx.Horizon/Sdk/Friends}/PlayHistoryRegistrationKey.cs (59%) create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/PlayHistoryStatistics.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Relationship.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/RequestId.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/SnsAccountLinkage.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/SnsAccountProfile.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Url.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/WebPageUrl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/BatteryLot.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerOffset.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerScale.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcdsaCertificate.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsCertificate.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsKey.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsRootCertificate.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvCertificate.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboKey.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickFactoryCalibration.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickModelParameter.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/BdAddress.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/ConfigurationId1.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/ConsoleSixAxisSensorHorizontalOffset.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/CountryCode.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceCertificate.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceKey.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardCertificate.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardKey.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeOffset.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeScale.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/MacAddress.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceCertificate.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceKey.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/SerialNumber.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/SpeakerParameter.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/SslCertificate.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/SslKey.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Language.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/LanguageCode.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/SettingsItemKey.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/SettingsName.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/AccountNotificationSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/AccountOnlineStorageSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/AccountSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/AllowedSslHost.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/AnalogStickUserCalibration.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/AppletLaunchFlag.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/AudioVolume.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettingsEx.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/BlePairingSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/BluetoothDevicesSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigRegisteredSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationBias.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationGain.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularAcceleration.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityBias.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityGain.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityTimeBias.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/DataDeletionSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/DeviceNickName.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/Edid.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/EulaVersion.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/FatalDirtyFlag.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersion.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersionDigest.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/HomeMenuScheme.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/HostFsMountPoint.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/InitialLaunchSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/NetworkSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/NotificationSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerLegacySettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/PtmFuelGaugeParameter.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/RebootlessSystemUpdateVersion.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/SerialNumber.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ServiceDiscoveryControlSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/SleepSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/TelemetryDirtyFlag.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ThemeId.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ThemeSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/TvSettings.cs diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs index 1a402240f..cd1719580 100644 --- a/src/Ryujinx.HLE/HOS/Horizon.cs +++ b/src/Ryujinx.HLE/HOS/Horizon.cs @@ -330,7 +330,7 @@ private void StartNewServices() HorizonFsClient fsClient = new(this); ServiceTable = new ServiceTable(); - var services = ServiceTable.GetServices(new HorizonOptions(Device.Configuration.IgnoreMissingServices, LibHacHorizonManager.BcatClient, fsClient)); + var services = ServiceTable.GetServices(new HorizonOptions(Device.Configuration.IgnoreMissingServices, LibHacHorizonManager.BcatClient, fsClient, AccountManager)); foreach (var service in services) { diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs index 924ac3fb9..c724660ea 100644 --- a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs @@ -4,6 +4,7 @@ using LibHac.Fs.Shim; using Ryujinx.Common; using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Sdk.Account; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -11,7 +12,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc { - public class AccountManager + public class AccountManager : IEmulatorAccountManager { public static readonly UserId DefaultUserId = new("00000000000000010000000000000000"); @@ -106,6 +107,11 @@ public void CloseUser(UserId userId) _accountSaveDataManager.Save(_profiles); } + public void OpenUserOnlinePlay(Uid userId) + { + OpenUserOnlinePlay(new UserId((long)userId.Low, (long)userId.High)); + } + public void OpenUserOnlinePlay(UserId userId) { if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile)) @@ -127,6 +133,11 @@ public void OpenUserOnlinePlay(UserId userId) _accountSaveDataManager.Save(_profiles); } + public void CloseUserOnlinePlay(Uid userId) + { + CloseUserOnlinePlay(new UserId((long)userId.Low, (long)userId.High)); + } + public void CloseUserOnlinePlay(UserId userId) { if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile)) diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs b/src/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs deleted file mode 100644 index 3f15f3fc6..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Ryujinx.Common; -using Ryujinx.HLE.HOS.Services.Account.Acc; -using Ryujinx.HLE.HOS.Services.Friend.ServiceCreator; - -namespace Ryujinx.HLE.HOS.Services.Friend -{ - [Service("friend:a", FriendServicePermissionLevel.Administrator)] - [Service("friend:m", FriendServicePermissionLevel.Manager)] - [Service("friend:s", FriendServicePermissionLevel.System)] - [Service("friend:u", FriendServicePermissionLevel.User)] - [Service("friend:v", FriendServicePermissionLevel.Viewer)] - class IServiceCreator : IpcService - { - private readonly FriendServicePermissionLevel _permissionLevel; - - public IServiceCreator(ServiceCtx context, FriendServicePermissionLevel permissionLevel) - { - _permissionLevel = permissionLevel; - } - - [CommandCmif(0)] - // CreateFriendService() -> object - public ResultCode CreateFriendService(ServiceCtx context) - { - MakeObject(context, new IFriendService(_permissionLevel)); - - return ResultCode.Success; - } - - [CommandCmif(1)] // 2.0.0+ - // CreateNotificationService(nn::account::Uid userId) -> object - public ResultCode CreateNotificationService(ServiceCtx context) - { - UserId userId = context.RequestData.ReadStruct(); - - if (userId.IsNull) - { - return ResultCode.InvalidArgument; - } - - MakeObject(context, new INotificationService(context, userId, _permissionLevel)); - - return ResultCode.Success; - } - - [CommandCmif(2)] // 4.0.0+ - // CreateDaemonSuspendSessionService() -> object - public ResultCode CreateDaemonSuspendSessionService(ServiceCtx context) - { - MakeObject(context, new IDaemonSuspendSessionService(_permissionLevel)); - - return ResultCode.Success; - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ResultCode.cs deleted file mode 100644 index 9f612059c..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Friend/ResultCode.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Friend -{ - enum ResultCode - { - ModuleId = 121, - ErrorCodeShift = 9, - - Success = 0, - - InvalidArgument = (2 << ErrorCodeShift) | ModuleId, - InternetRequestDenied = (6 << ErrorCodeShift) | ModuleId, - NotificationQueueEmpty = (15 << ErrorCodeShift) | ModuleId, - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/Friend.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/Friend.cs deleted file mode 100644 index 28745c3f2..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/Friend.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Ryujinx.HLE.HOS.Services.Account.Acc; -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService -{ - [StructLayout(LayoutKind.Sequential, Pack = 0x8, Size = 0x200, CharSet = CharSet.Ansi)] - struct Friend - { - public UserId UserId; - public long NetworkUserId; - - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x21)] - public string Nickname; - - public UserPresence presence; - - [MarshalAs(UnmanagedType.I1)] - public bool IsFavourite; - - [MarshalAs(UnmanagedType.I1)] - public bool IsNew; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x6)] - readonly char[] Unknown; - - [MarshalAs(UnmanagedType.I1)] - public bool IsValid; - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/FriendFilter.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/FriendFilter.cs deleted file mode 100644 index 5f13f3136..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/FriendFilter.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService -{ - [StructLayout(LayoutKind.Sequential)] - struct FriendFilter - { - public PresenceStatusFilter PresenceStatus; - - [MarshalAs(UnmanagedType.I1)] - public bool IsFavoriteOnly; - - [MarshalAs(UnmanagedType.I1)] - public bool IsSameAppPresenceOnly; - - [MarshalAs(UnmanagedType.I1)] - public bool IsSameAppPlayedOnly; - - [MarshalAs(UnmanagedType.I1)] - public bool IsArbitraryAppPlayedOnly; - - public long PresenceGroupId; - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/UserPresence.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/UserPresence.cs deleted file mode 100644 index 80d142059..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/UserPresence.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Ryujinx.Common.Memory; -using Ryujinx.HLE.HOS.Services.Account.Acc; -using System; -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService -{ - [StructLayout(LayoutKind.Sequential, Pack = 0x8)] - struct UserPresence - { - public UserId UserId; - public long LastTimeOnlineTimestamp; - public PresenceStatus Status; - - [MarshalAs(UnmanagedType.I1)] - public bool SamePresenceGroupApplication; - - public Array3 Unknown; - private AppKeyValueStorageHolder _appKeyValueStorage; - - public Span AppKeyValueStorage => MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref _appKeyValueStorage, AppKeyValueStorageHolder.Size)); - - [StructLayout(LayoutKind.Sequential, Pack = 0x1, Size = Size)] - private struct AppKeyValueStorageHolder - { - public const int Size = 0xC0; - } - - public readonly override string ToString() - { - return $"UserPresence {{ UserId: {UserId}, LastTimeOnlineTimestamp: {LastTimeOnlineTimestamp}, Status: {Status} }}"; - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IDaemonSuspendSessionService.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IDaemonSuspendSessionService.cs deleted file mode 100644 index 3b1601abb..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IDaemonSuspendSessionService.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator -{ - class IDaemonSuspendSessionService : IpcService - { -#pragma warning disable IDE0052 // Remove unread private member - private readonly FriendServicePermissionLevel _permissionLevel; -#pragma warning restore IDE0052 - - public IDaemonSuspendSessionService(FriendServicePermissionLevel permissionLevel) - { - _permissionLevel = permissionLevel; - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs deleted file mode 100644 index 54d23e88c..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs +++ /dev/null @@ -1,374 +0,0 @@ -using LibHac.Ns; -using Ryujinx.Common; -using Ryujinx.Common.Logging; -using Ryujinx.Common.Memory; -using Ryujinx.Common.Utilities; -using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel.Threading; -using Ryujinx.HLE.HOS.Services.Account.Acc; -using Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService; -using Ryujinx.Horizon.Common; -using System; -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator -{ - class IFriendService : IpcService - { -#pragma warning disable IDE0052 // Remove unread private member - private readonly FriendServicePermissionLevel _permissionLevel; -#pragma warning restore IDE0052 - private KEvent _completionEvent; - - public IFriendService(FriendServicePermissionLevel permissionLevel) - { - _permissionLevel = permissionLevel; - } - - [CommandCmif(0)] - // GetCompletionEvent() -> handle - public ResultCode GetCompletionEvent(ServiceCtx context) - { - _completionEvent ??= new KEvent(context.Device.System.KernelContext); - - if (context.Process.HandleTable.GenerateHandle(_completionEvent.ReadableEvent, out int completionEventHandle) != Result.Success) - { - throw new InvalidOperationException("Out of handles!"); - } - - _completionEvent.WritableEvent.Signal(); - - context.Response.HandleDesc = IpcHandleDesc.MakeCopy(completionEventHandle); - - return ResultCode.Success; - } - - [CommandCmif(1)] - // nn::friends::Cancel() - public ResultCode Cancel(ServiceCtx context) - { - // TODO: Original service sets an internal field to 1 here. Determine usage. - Logger.Stub?.PrintStub(LogClass.ServiceFriend); - - return ResultCode.Success; - } - - [CommandCmif(10100)] - // nn::friends::GetFriendListIds(int offset, nn::account::Uid userId, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid) - // -> int outCount, array - public ResultCode GetFriendListIds(ServiceCtx context) - { - int offset = context.RequestData.ReadInt32(); - - // Padding - context.RequestData.ReadInt32(); - - UserId userId = context.RequestData.ReadStruct(); - FriendFilter filter = context.RequestData.ReadStruct(); - - // Pid placeholder - context.RequestData.ReadInt64(); - - if (userId.IsNull) - { - return ResultCode.InvalidArgument; - } - - // There are no friends online, so we return 0 because the nn::account::NetworkServiceAccountId array is empty. - context.ResponseData.Write(0); - - Logger.Stub?.PrintStub(LogClass.ServiceFriend, new - { - UserId = userId.ToString(), - offset, - filter.PresenceStatus, - filter.IsFavoriteOnly, - filter.IsSameAppPresenceOnly, - filter.IsSameAppPlayedOnly, - filter.IsArbitraryAppPlayedOnly, - filter.PresenceGroupId, - }); - - return ResultCode.Success; - } - - [CommandCmif(10101)] - // nn::friends::GetFriendList(int offset, nn::account::Uid userId, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid) - // -> int outCount, array - public ResultCode GetFriendList(ServiceCtx context) - { - int offset = context.RequestData.ReadInt32(); - - // Padding - context.RequestData.ReadInt32(); - - UserId userId = context.RequestData.ReadStruct(); - FriendFilter filter = context.RequestData.ReadStruct(); - - // Pid placeholder - context.RequestData.ReadInt64(); - - if (userId.IsNull) - { - return ResultCode.InvalidArgument; - } - - // There are no friends online, so we return 0 because the nn::account::NetworkServiceAccountId array is empty. - context.ResponseData.Write(0); - - Logger.Stub?.PrintStub(LogClass.ServiceFriend, new - { - UserId = userId.ToString(), - offset, - filter.PresenceStatus, - filter.IsFavoriteOnly, - filter.IsSameAppPresenceOnly, - filter.IsSameAppPlayedOnly, - filter.IsArbitraryAppPlayedOnly, - filter.PresenceGroupId, - }); - - return ResultCode.Success; - } - - [CommandCmif(10120)] // 10.0.0+ - // nn::friends::IsFriendListCacheAvailable(nn::account::Uid userId) -> bool - public ResultCode IsFriendListCacheAvailable(ServiceCtx context) - { - UserId userId = context.RequestData.ReadStruct(); - - if (userId.IsNull) - { - return ResultCode.InvalidArgument; - } - - // TODO: Service mount the friends:/ system savedata and try to load friend.cache file, returns true if exists, false otherwise. - // NOTE: If no cache is available, guest then calls nn::friends::EnsureFriendListAvailable, we can avoid that by faking the cache check. - context.ResponseData.Write(true); - - // TODO: Since we don't support friend features, it's fine to stub it for now. - Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() }); - - return ResultCode.Success; - } - - [CommandCmif(10121)] // 10.0.0+ - // nn::friends::EnsureFriendListAvailable(nn::account::Uid userId) - public ResultCode EnsureFriendListAvailable(ServiceCtx context) - { - UserId userId = context.RequestData.ReadStruct(); - - if (userId.IsNull) - { - return ResultCode.InvalidArgument; - } - - // TODO: Service mount the friends:/ system savedata and create a friend.cache file for the given user id. - // Since we don't support friend features, it's fine to stub it for now. - Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() }); - - return ResultCode.Success; - } - - [CommandCmif(10400)] - // nn::friends::GetBlockedUserListIds(int offset, nn::account::Uid userId) -> (u32, buffer) - public ResultCode GetBlockedUserListIds(ServiceCtx context) - { - int offset = context.RequestData.ReadInt32(); - - // Padding - context.RequestData.ReadInt32(); - - UserId userId = context.RequestData.ReadStruct(); - - // There are no friends blocked, so we return 0 because the nn::account::NetworkServiceAccountId array is empty. - context.ResponseData.Write(0); - - Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { offset, UserId = userId.ToString() }); - - return ResultCode.Success; - } - - [CommandCmif(10420)] - // nn::friends::CheckBlockedUserListAvailability(nn::account::Uid userId) -> bool - public ResultCode CheckBlockedUserListAvailability(ServiceCtx context) - { - UserId userId = context.RequestData.ReadStruct(); - - // Yes, it is available. - context.ResponseData.Write(true); - - Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() }); - - return ResultCode.Success; - } - - [CommandCmif(10600)] - // nn::friends::DeclareOpenOnlinePlaySession(nn::account::Uid userId) - public ResultCode DeclareOpenOnlinePlaySession(ServiceCtx context) - { - UserId userId = context.RequestData.ReadStruct(); - - if (userId.IsNull) - { - return ResultCode.InvalidArgument; - } - - context.Device.System.AccountManager.OpenUserOnlinePlay(userId); - - Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() }); - - return ResultCode.Success; - } - - [CommandCmif(10601)] - // nn::friends::DeclareCloseOnlinePlaySession(nn::account::Uid userId) - public ResultCode DeclareCloseOnlinePlaySession(ServiceCtx context) - { - UserId userId = context.RequestData.ReadStruct(); - - if (userId.IsNull) - { - return ResultCode.InvalidArgument; - } - - context.Device.System.AccountManager.CloseUserOnlinePlay(userId); - - Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() }); - - return ResultCode.Success; - } - - [CommandCmif(10610)] - // nn::friends::UpdateUserPresence(nn::account::Uid, u64, pid, buffer) - public ResultCode UpdateUserPresence(ServiceCtx context) - { - UserId uuid = context.RequestData.ReadStruct(); - - // Pid placeholder - context.RequestData.ReadInt64(); - - ulong position = context.Request.PtrBuff[0].Position; - ulong size = context.Request.PtrBuff[0].Size; - - ReadOnlySpan userPresenceInputArray = MemoryMarshal.Cast(context.Memory.GetSpan(position, (int)size)); - - if (uuid.IsNull) - { - return ResultCode.InvalidArgument; - } - - Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = uuid.ToString(), userPresenceInputArray = userPresenceInputArray.ToArray() }); - - return ResultCode.Success; - } - - [CommandCmif(10700)] - // nn::friends::GetPlayHistoryRegistrationKey(b8 unknown, nn::account::Uid) -> buffer - public ResultCode GetPlayHistoryRegistrationKey(ServiceCtx context) - { - bool unknownBool = context.RequestData.ReadBoolean(); - UserId userId = context.RequestData.ReadStruct(); - - context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(0x40UL); - - ulong bufferPosition = context.Request.RecvListBuff[0].Position; - - if (userId.IsNull) - { - return ResultCode.InvalidArgument; - } - - // NOTE: Calls nn::friends::detail::service::core::PlayHistoryManager::GetInstance and stores the instance. - - byte[] randomBytes = new byte[8]; - - Random.Shared.NextBytes(randomBytes); - - // NOTE: Calls nn::friends::detail::service::core::UuidManager::GetInstance and stores the instance. - // Then call nn::friends::detail::service::core::AccountStorageManager::GetInstance and store the instance. - // Then it checks if an Uuid is already stored for the UserId, if not it generates a random Uuid. - // And store it in the savedata 8000000000000080 in the friends:/uid.bin file. - - Array16 randomGuid = new(); - - Guid.NewGuid().ToByteArray().AsSpan().CopyTo(randomGuid.AsSpan()); - - PlayHistoryRegistrationKey playHistoryRegistrationKey = new() - { - Type = 0x101, - KeyIndex = (byte)(randomBytes[0] & 7), - UserIdBool = 0, // TODO: Find it. - UnknownBool = (byte)(unknownBool ? 1 : 0), // TODO: Find it. - Reserved = new Array11(), - Uuid = randomGuid, - }; - - ReadOnlySpan playHistoryRegistrationKeyBuffer = SpanHelpers.AsByteSpan(ref playHistoryRegistrationKey); - - /* - - NOTE: The service uses the KeyIndex to get a random key from a keys buffer (since the key index is stored in the returned buffer). - We currently don't support play history and online services so we can use a blank key for now. - Code for reference: - - byte[] hmacKey = new byte[0x20]; - - HMACSHA256 hmacSha256 = new HMACSHA256(hmacKey); - byte[] hmacHash = hmacSha256.ComputeHash(playHistoryRegistrationKeyBuffer); - - */ - - context.Memory.Write(bufferPosition, playHistoryRegistrationKeyBuffer); - context.Memory.Write(bufferPosition + 0x20, new byte[0x20]); // HmacHash - - return ResultCode.Success; - } - - [CommandCmif(10702)] - // nn::friends::AddPlayHistory(nn::account::Uid, u64, pid, buffer, buffer, buffer) - public ResultCode AddPlayHistory(ServiceCtx context) - { - UserId userId = context.RequestData.ReadStruct(); - - // Pid placeholder - context.RequestData.ReadInt64(); -#pragma warning disable IDE0059 // Remove unnecessary value assignment - ulong pid = context.Request.HandleDesc.PId; - - ulong playHistoryRegistrationKeyPosition = context.Request.PtrBuff[0].Position; - ulong playHistoryRegistrationKeySize = context.Request.PtrBuff[0].Size; - - ulong inAppScreenName1Position = context.Request.PtrBuff[1].Position; -#pragma warning restore IDE0059 - ulong inAppScreenName1Size = context.Request.PtrBuff[1].Size; - -#pragma warning disable IDE0059 // Remove unnecessary value assignment - ulong inAppScreenName2Position = context.Request.PtrBuff[2].Position; -#pragma warning restore IDE0059 - ulong inAppScreenName2Size = context.Request.PtrBuff[2].Size; - - if (userId.IsNull || inAppScreenName1Size > 0x48 || inAppScreenName2Size > 0x48) - { - return ResultCode.InvalidArgument; - } - - // TODO: Call nn::arp::GetApplicationControlProperty here when implemented. -#pragma warning disable IDE0059 // Remove unnecessary value assignment - ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties; -#pragma warning restore IDE0059 - - /* - - NOTE: The service calls nn::friends::detail::service::core::PlayHistoryManager to store informations using the registration key computed in GetPlayHistoryRegistrationKey. - Then calls nn::friends::detail::service::core::FriendListManager to update informations on the friend list. - We currently don't support play history and online services so it's fine to do nothing. - - */ - - Logger.Stub?.PrintStub(LogClass.ServiceFriend); - - return ResultCode.Success; - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs deleted file mode 100644 index 8fc7a4609..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs +++ /dev/null @@ -1,178 +0,0 @@ -using Ryujinx.Common; -using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel.Threading; -using Ryujinx.HLE.HOS.Services.Account.Acc; -using Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService; -using Ryujinx.Horizon.Common; -using System; -using System.Collections.Generic; - -namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator -{ - class INotificationService : DisposableIpcService - { - private readonly UserId _userId; - private readonly FriendServicePermissionLevel _permissionLevel; - - private readonly object _lock = new(); - - private readonly KEvent _notificationEvent; - private int _notificationEventHandle = 0; - - private readonly LinkedList _notifications; - - private bool _hasNewFriendRequest; - private bool _hasFriendListUpdate; - - public INotificationService(ServiceCtx context, UserId userId, FriendServicePermissionLevel permissionLevel) - { - _userId = userId; - _permissionLevel = permissionLevel; - _notifications = new LinkedList(); - _notificationEvent = new KEvent(context.Device.System.KernelContext); - - _hasNewFriendRequest = false; - _hasFriendListUpdate = false; - - NotificationEventHandler.Instance.RegisterNotificationService(this); - } - - [CommandCmif(0)] //2.0.0+ - // nn::friends::detail::ipc::INotificationService::GetEvent() -> handle - public ResultCode GetEvent(ServiceCtx context) - { - if (_notificationEventHandle == 0) - { - if (context.Process.HandleTable.GenerateHandle(_notificationEvent.ReadableEvent, out _notificationEventHandle) != Result.Success) - { - throw new InvalidOperationException("Out of handles!"); - } - } - - context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_notificationEventHandle); - - return ResultCode.Success; - } - - [CommandCmif(1)] //2.0.0+ - // nn::friends::detail::ipc::INotificationService::Clear() - public ResultCode Clear(ServiceCtx context) - { - lock (_lock) - { - _hasNewFriendRequest = false; - _hasFriendListUpdate = false; - - _notifications.Clear(); - } - - return ResultCode.Success; - } - - [CommandCmif(2)] // 2.0.0+ - // nn::friends::detail::ipc::INotificationService::Pop() -> nn::friends::detail::ipc::SizedNotificationInfo - public ResultCode Pop(ServiceCtx context) - { - lock (_lock) - { - if (_notifications.Count >= 1) - { - NotificationInfo notificationInfo = _notifications.First.Value; - _notifications.RemoveFirst(); - - if (notificationInfo.Type == NotificationEventType.FriendListUpdate) - { - _hasFriendListUpdate = false; - } - else if (notificationInfo.Type == NotificationEventType.NewFriendRequest) - { - _hasNewFriendRequest = false; - } - - context.ResponseData.WriteStruct(notificationInfo); - - return ResultCode.Success; - } - } - - return ResultCode.NotificationQueueEmpty; - } - - public void SignalFriendListUpdate(UserId targetId) - { - lock (_lock) - { - if (_userId == targetId) - { - if (!_hasFriendListUpdate) - { - NotificationInfo friendListNotification = new(); - - if (_notifications.Count != 0) - { - friendListNotification = _notifications.First.Value; - _notifications.RemoveFirst(); - } - - friendListNotification.Type = NotificationEventType.FriendListUpdate; - _hasFriendListUpdate = true; - - if (_hasNewFriendRequest) - { - NotificationInfo newFriendRequestNotification = new(); - - if (_notifications.Count != 0) - { - newFriendRequestNotification = _notifications.First.Value; - _notifications.RemoveFirst(); - } - - newFriendRequestNotification.Type = NotificationEventType.NewFriendRequest; - _notifications.AddFirst(newFriendRequestNotification); - } - - // We defer this to make sure we are on top of the queue. - _notifications.AddFirst(friendListNotification); - } - - _notificationEvent.ReadableEvent.Signal(); - } - } - } - - public void SignalNewFriendRequest(UserId targetId) - { - lock (_lock) - { - if ((_permissionLevel & FriendServicePermissionLevel.ViewerMask) != 0 && _userId == targetId) - { - if (!_hasNewFriendRequest) - { - if (_notifications.Count == 100) - { - SignalFriendListUpdate(targetId); - } - - NotificationInfo newFriendRequestNotification = new() - { - Type = NotificationEventType.NewFriendRequest, - }; - - _notifications.AddLast(newFriendRequestNotification); - _hasNewFriendRequest = true; - } - - _notificationEvent.ReadableEvent.Signal(); - } - } - } - - protected override void Dispose(bool isDisposing) - { - if (isDisposing) - { - NotificationEventHandler.Instance.UnregisterNotificationService(this); - } - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/NotificationEventHandler.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/NotificationEventHandler.cs deleted file mode 100644 index 88627fd78..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/NotificationEventHandler.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Ryujinx.HLE.HOS.Services.Account.Acc; - -namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService -{ - public sealed class NotificationEventHandler - { - private static NotificationEventHandler _instance; - private static readonly object _instanceLock = new(); - - private readonly INotificationService[] _registry; - - public static NotificationEventHandler Instance - { - get - { - lock (_instanceLock) - { - _instance ??= new NotificationEventHandler(); - - return _instance; - } - } - } - - NotificationEventHandler() - { - _registry = new INotificationService[0x20]; - } - - internal void RegisterNotificationService(INotificationService service) - { - // NOTE: in case there isn't space anymore in the registry array, Nintendo doesn't return any errors. - for (int i = 0; i < _registry.Length; i++) - { - if (_registry[i] == null) - { - _registry[i] = service; - break; - } - } - } - - internal void UnregisterNotificationService(INotificationService service) - { - // NOTE: in case there isn't the entry in the registry array, Nintendo doesn't return any errors. - for (int i = 0; i < _registry.Length; i++) - { - if (_registry[i] == service) - { - _registry[i] = null; - break; - } - } - } - - // TODO: Use this when we will have enough things to go online. - public void SignalFriendListUpdate(UserId targetId) - { - for (int i = 0; i < _registry.Length; i++) - { - _registry[i]?.SignalFriendListUpdate(targetId); - } - } - - // TODO: Use this when we will have enough things to go online. - public void SignalNewFriendRequest(UserId targetId) - { - for (int i = 0; i < _registry.Length; i++) - { - _registry[i]?.SignalNewFriendRequest(targetId); - } - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationInfo.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationInfo.cs deleted file mode 100644 index aa58433d8..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationInfo.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Ryujinx.Common.Memory; -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService -{ - [StructLayout(LayoutKind.Sequential, Size = 0x10)] - struct NotificationInfo - { - public NotificationEventType Type; - private Array4 _padding; - public long NetworkUserIdPlaceholder; - } -} diff --git a/src/Ryujinx.Horizon/Friends/FriendsIpcServer.cs b/src/Ryujinx.Horizon/Friends/FriendsIpcServer.cs new file mode 100644 index 000000000..523c617a8 --- /dev/null +++ b/src/Ryujinx.Horizon/Friends/FriendsIpcServer.cs @@ -0,0 +1,49 @@ +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Friends +{ + class FriendsIpcServer + { + private const int MaxSessionsCount = 8; + private const int TotalMaxSessionsCount = MaxSessionsCount * 5; + + private const int PointerBufferSize = 0xA00; + private const int MaxDomains = 64; + private const int MaxDomainObjects = 16; + private const int MaxPortsCount = 5; + + private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private FriendsServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new FriendsServerManager(allocator, _sm, MaxPortsCount, _managerOptions, TotalMaxSessionsCount); + +#pragma warning disable IDE0055 // Disable formatting + _serverManager.RegisterServer((int)FriendsPortIndex.Admin, ServiceName.Encode("friend:a"), MaxSessionsCount); + _serverManager.RegisterServer((int)FriendsPortIndex.User, ServiceName.Encode("friend:u"), MaxSessionsCount); + _serverManager.RegisterServer((int)FriendsPortIndex.Viewer, ServiceName.Encode("friend:v"), MaxSessionsCount); + _serverManager.RegisterServer((int)FriendsPortIndex.Manager, ServiceName.Encode("friend:m"), MaxSessionsCount); + _serverManager.RegisterServer((int)FriendsPortIndex.System, ServiceName.Encode("friend:s"), MaxSessionsCount); +#pragma warning restore IDE0055 + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Friends/FriendsMain.cs b/src/Ryujinx.Horizon/Friends/FriendsMain.cs new file mode 100644 index 000000000..0f119cf01 --- /dev/null +++ b/src/Ryujinx.Horizon/Friends/FriendsMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Friends +{ + class FriendsMain : IService + { + public static void Main(ServiceTable serviceTable) + { + FriendsIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Friends/FriendsPortIndex.cs b/src/Ryujinx.Horizon/Friends/FriendsPortIndex.cs new file mode 100644 index 000000000..f567db302 --- /dev/null +++ b/src/Ryujinx.Horizon/Friends/FriendsPortIndex.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Horizon.Friends +{ + enum FriendsPortIndex + { + Admin, + User, + Viewer, + Manager, + System, + } +} diff --git a/src/Ryujinx.Horizon/Friends/FriendsServerManager.cs b/src/Ryujinx.Horizon/Friends/FriendsServerManager.cs new file mode 100644 index 000000000..5026206b7 --- /dev/null +++ b/src/Ryujinx.Horizon/Friends/FriendsServerManager.cs @@ -0,0 +1,36 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Account; +using Ryujinx.Horizon.Sdk.Friends.Detail.Ipc; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; +using System; + +namespace Ryujinx.Horizon.Friends +{ + class FriendsServerManager : ServerManager + { + private readonly IEmulatorAccountManager _accountManager; + private readonly NotificationEventHandler _notificationEventHandler; + + public FriendsServerManager(HeapAllocator allocator, SmApi sm, int maxPorts, ManagerOptions options, int maxSessions) : base(allocator, sm, maxPorts, options, maxSessions) + { + _accountManager = HorizonStatic.Options.AccountManager; + _notificationEventHandler = new(); + } + + protected override Result OnNeedsToAccept(int portIndex, Server server) + { + return (FriendsPortIndex)portIndex switch + { +#pragma warning disable IDE0055 // Disable formatting + FriendsPortIndex.Admin => AcceptImpl(server, new ServiceCreator(_accountManager, _notificationEventHandler, FriendsServicePermissionLevel.Admin)), + FriendsPortIndex.User => AcceptImpl(server, new ServiceCreator(_accountManager, _notificationEventHandler, FriendsServicePermissionLevel.User)), + FriendsPortIndex.Viewer => AcceptImpl(server, new ServiceCreator(_accountManager, _notificationEventHandler, FriendsServicePermissionLevel.Viewer)), + FriendsPortIndex.Manager => AcceptImpl(server, new ServiceCreator(_accountManager, _notificationEventHandler, FriendsServicePermissionLevel.Manager)), + FriendsPortIndex.System => AcceptImpl(server, new ServiceCreator(_accountManager, _notificationEventHandler, FriendsServicePermissionLevel.System)), + _ => throw new ArgumentOutOfRangeException(nameof(portIndex)), +#pragma warning restore IDE0055 + }; + } + } +} diff --git a/src/Ryujinx.Horizon/HorizonOptions.cs b/src/Ryujinx.Horizon/HorizonOptions.cs index e3c862da4..794620569 100644 --- a/src/Ryujinx.Horizon/HorizonOptions.cs +++ b/src/Ryujinx.Horizon/HorizonOptions.cs @@ -1,4 +1,5 @@ using LibHac; +using Ryujinx.Horizon.Sdk.Account; using Ryujinx.Horizon.Sdk.Fs; namespace Ryujinx.Horizon @@ -10,13 +11,15 @@ public readonly struct HorizonOptions public HorizonClient BcatClient { get; } public IFsClient FsClient { get; } + public IEmulatorAccountManager AccountManager { get; } - public HorizonOptions(bool ignoreMissingServices, HorizonClient bcatClient, IFsClient fsClient) + public HorizonOptions(bool ignoreMissingServices, HorizonClient bcatClient, IFsClient fsClient, IEmulatorAccountManager accountManager) { IgnoreMissingServices = ignoreMissingServices; ThrowOnInvalidCommandIds = true; BcatClient = bcatClient; FsClient = fsClient; + AccountManager = accountManager; } } } diff --git a/src/Ryujinx.Horizon/Sdk/Account/IEmulatorAccountManager.cs b/src/Ryujinx.Horizon/Sdk/Account/IEmulatorAccountManager.cs new file mode 100644 index 000000000..af02cc8eb --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Account/IEmulatorAccountManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Horizon.Sdk.Account +{ + public interface IEmulatorAccountManager + { + void OpenUserOnlinePlay(Uid userId); + void CloseUserOnlinePlay(Uid userId); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Account/NetworkServiceAccountId.cs b/src/Ryujinx.Horizon/Sdk/Account/NetworkServiceAccountId.cs new file mode 100644 index 000000000..2512975e3 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Account/NetworkServiceAccountId.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Account +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x8)] + readonly record struct NetworkServiceAccountId + { + public readonly ulong Id; + + public NetworkServiceAccountId(ulong id) + { + Id = id; + } + + public override readonly string ToString() + { + return Id.ToString("x16"); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Account/Nickname.cs b/src/Ryujinx.Horizon/Sdk/Account/Nickname.cs new file mode 100644 index 000000000..1f351ee3a --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Account/Nickname.cs @@ -0,0 +1,29 @@ +using Ryujinx.Common.Memory; +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Horizon.Sdk.Account +{ + [StructLayout(LayoutKind.Sequential, Size = 0x21, Pack = 0x1)] + readonly struct Nickname + { + public readonly Array33 Name; + + public Nickname(in Array33 name) + { + Name = name; + } + + public override string ToString() + { + int length = ((ReadOnlySpan)Name.AsSpan()).IndexOf((byte)0); + if (length < 0) + { + length = 33; + } + + return Encoding.UTF8.GetString(Name.AsSpan()[..length]); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Account/Uid.cs b/src/Ryujinx.Horizon/Sdk/Account/Uid.cs index a76e6d256..ada2c02ba 100644 --- a/src/Ryujinx.Horizon/Sdk/Account/Uid.cs +++ b/src/Ryujinx.Horizon/Sdk/Account/Uid.cs @@ -6,16 +6,16 @@ namespace Ryujinx.Horizon.Sdk.Account { [StructLayout(LayoutKind.Sequential)] - readonly record struct Uid + public readonly record struct Uid { - public readonly long High; - public readonly long Low; + public readonly ulong High; + public readonly ulong Low; public bool IsNull => (Low | High) == 0; public static Uid Null => new(0, 0); - public Uid(long low, long high) + public Uid(ulong low, ulong high) { Low = low; High = high; @@ -23,8 +23,8 @@ public Uid(long low, long high) public Uid(byte[] bytes) { - High = BitConverter.ToInt64(bytes, 0); - Low = BitConverter.ToInt64(bytes, 8); + High = BitConverter.ToUInt64(bytes, 0); + Low = BitConverter.ToUInt64(bytes, 8); } public Uid(string hex) @@ -34,8 +34,8 @@ public Uid(string hex) throw new ArgumentException("Invalid Hex value!", nameof(hex)); } - Low = Convert.ToInt64(hex[16..], 16); - High = Convert.ToInt64(hex[..16], 16); + Low = Convert.ToUInt64(hex[16..], 16); + High = Convert.ToUInt64(hex[..16], 16); } public void Write(BinaryWriter binaryWriter) diff --git a/src/Ryujinx.Horizon/Sdk/Friends/ApplicationInfo.cs b/src/Ryujinx.Horizon/Sdk/Friends/ApplicationInfo.cs new file mode 100644 index 000000000..23bad3d13 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/ApplicationInfo.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Sdk.Ncm; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)] + struct ApplicationInfo + { + public ApplicationId ApplicationId; + public ulong PresenceGroupId; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/BlockedUserImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/BlockedUserImpl.cs new file mode 100644 index 000000000..d5f8a0313 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/BlockedUserImpl.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + struct BlockedUserImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendCandidateImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendCandidateImpl.cs new file mode 100644 index 000000000..21e99c754 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendCandidateImpl.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + struct FriendCandidateImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendDetailedInfoImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendDetailedInfoImpl.cs new file mode 100644 index 000000000..1b46dccd5 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendDetailedInfoImpl.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x800)] + struct FriendDetailedInfoImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendImpl.cs new file mode 100644 index 000000000..d22ca4b90 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendImpl.cs @@ -0,0 +1,19 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Horizon.Sdk.Account; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x200, Pack = 0x8)] + struct FriendImpl + { + public Uid UserId; + public NetworkServiceAccountId NetworkUserId; + public Nickname Nickname; + public UserPresenceImpl Presence; + public bool IsFavourite; + public bool IsNew; + public Array6 Unknown; + public bool IsValid; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationForViewerImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationForViewerImpl.cs new file mode 100644 index 000000000..416ba3655 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationForViewerImpl.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + struct FriendInvitationForViewerImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationGroupImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationGroupImpl.cs new file mode 100644 index 000000000..ef9238347 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationGroupImpl.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x1400)] + struct FriendInvitationGroupImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendRequestImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendRequestImpl.cs new file mode 100644 index 000000000..ba5671692 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendRequestImpl.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + struct FriendRequestImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendSettingImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendSettingImpl.cs new file mode 100644 index 000000000..f711d31fd --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendSettingImpl.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x40)] + struct FriendSettingImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/DaemonSuspendSessionService.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/DaemonSuspendSessionService.cs new file mode 100644 index 000000000..aaf88ed03 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/DaemonSuspendSessionService.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + partial class DaemonSuspendSessionService : IDaemonSuspendSessionService + { + // NOTE: This service has no commands. + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendService.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendService.cs new file mode 100644 index 000000000..1b4c8c309 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendService.cs @@ -0,0 +1,1015 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Account; +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Settings; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + partial class FriendService : IFriendService, IDisposable + { + private readonly IEmulatorAccountManager _accountManager; + private SystemEventType _completionEvent; + + public FriendService(IEmulatorAccountManager accountManager, FriendsServicePermissionLevel permissionLevel) + { + _accountManager = accountManager; + + Os.CreateSystemEvent(out _completionEvent, EventClearMode.ManualClear, interProcess: true).AbortOnFailure(); + Os.SignalSystemEvent(ref _completionEvent); // TODO: Figure out where we are supposed to signal this. + } + + [CmifCommand(0)] + public Result GetCompletionEvent([CopyHandle] out int completionEventHandle) + { + completionEventHandle = Os.GetReadableHandleOfSystemEvent(ref _completionEvent); + + return Result.Success; + } + + [CmifCommand(1)] + public Result Cancel() + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend); + + return Result.Success; + } + + [CmifCommand(10100)] + public Result GetFriendListIds( + out int count, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer)] Span friendIds, + Uid userId, + int offset, + SizedFriendFilter filter, + ulong pidPlaceholder, + [ClientProcessId] ulong pid) + { + count = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, offset, filter, pidPlaceholder, pid }); + + if (userId.IsNull) + { + return FriendResult.InvalidArgument; + } + + return Result.Success; + } + + [CmifCommand(10101)] + public Result GetFriendList( + out int count, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span friendList, + Uid userId, + int offset, + SizedFriendFilter filter, + ulong pidPlaceholder, + [ClientProcessId] ulong pid) + { + count = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, offset, filter, pidPlaceholder, pid }); + + if (userId.IsNull) + { + return FriendResult.InvalidArgument; + } + + return Result.Success; + } + + [CmifCommand(10102)] + public Result UpdateFriendInfo( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span info, + Uid userId, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan friendIds, + ulong pidPlaceholder, + [ClientProcessId] ulong pid) + { + string friendIdList = string.Join(", ", friendIds.ToArray()); + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendIdList, pidPlaceholder, pid }); + + return Result.Success; + } + + [CmifCommand(10110)] + public Result GetFriendProfileImage( + out int size, + Uid userId, + NetworkServiceAccountId friendId, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span profileImage) + { + size = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId }); + + return Result.Success; + } + + [CmifCommand(10120)] + public Result CheckFriendListAvailability(out bool listAvailable, Uid userId) + { + listAvailable = true; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(10121)] + public Result EnsureFriendListAvailable(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(10200)] + public Result SendFriendRequestForApplication( + Uid userId, + NetworkServiceAccountId friendId, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg2, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg3, + ulong pidPlaceholder, + [ClientProcessId] ulong pid) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, arg2, arg3, pidPlaceholder, pid }); + + return Result.Success; + } + + [CmifCommand(10211)] + public Result AddFacedFriendRequestForApplication( + Uid userId, + FacedFriendRequestRegistrationKey key, + Nickname nickname, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan arg3, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg4, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg5, + ulong pidPlaceholder, + [ClientProcessId] ulong pid) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, key, nickname, arg4, arg5, pidPlaceholder, pid }); + + return Result.Success; + } + + [CmifCommand(10400)] + public Result GetBlockedUserListIds( + out int count, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer)] Span blockedIds, + Uid userId, + int offset) + { + count = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, offset }); + + return Result.Success; + } + + [CmifCommand(10420)] + public Result CheckBlockedUserListAvailability(out bool listAvailable, Uid userId) + { + listAvailable = true; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(10421)] + public Result EnsureBlockedUserListAvailable(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(10500)] + public Result GetProfileList( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span profileList, + Uid userId, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan friendIds) + { + string friendIdList = string.Join(", ", friendIds.ToArray()); + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendIdList }); + + return Result.Success; + } + + [CmifCommand(10600)] + public Result DeclareOpenOnlinePlaySession(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + if (userId.IsNull) + { + return FriendResult.InvalidArgument; + } + + _accountManager.OpenUserOnlinePlay(userId); + + return Result.Success; + } + + [CmifCommand(10601)] + public Result DeclareCloseOnlinePlaySession(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + if (userId.IsNull) + { + return FriendResult.InvalidArgument; + } + + _accountManager.CloseUserOnlinePlay(userId); + + return Result.Success; + } + + [CmifCommand(10610)] + public Result UpdateUserPresence( + Uid userId, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0xE0)] in UserPresenceImpl userPresence, + ulong pidPlaceholder, + [ClientProcessId] ulong pid) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, userPresence, pidPlaceholder, pid }); + + return Result.Success; + } + + [CmifCommand(10700)] + public Result GetPlayHistoryRegistrationKey( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x40)] out PlayHistoryRegistrationKey registrationKey, + Uid userId, + bool arg2) + { + if (userId.IsNull) + { + registrationKey = default; + + return FriendResult.InvalidArgument; + } + + // NOTE: Calls nn::friends::detail::service::core::PlayHistoryManager::GetInstance and stores the instance. + + // NOTE: Calls nn::friends::detail::service::core::UuidManager::GetInstance and stores the instance. + // Then calls nn::friends::detail::service::core::AccountStorageManager::GetInstance and stores the instance. + // Then it checks if an Uuid is already stored for the UserId, if not it generates a random Uuid, + // and stores it in the savedata 8000000000000080 in the friends:/uid.bin file. + + /* + + NOTE: The service uses the KeyIndex to get a random key from a keys buffer (since the key index is stored in the returned buffer). + We currently don't support play history and online services so we can use a blank key for now. + Code for reference: + + byte[] hmacKey = new byte[0x20]; + + HMACSHA256 hmacSha256 = new HMACSHA256(hmacKey); + byte[] hmacHash = hmacSha256.ComputeHash(playHistoryRegistrationKeyBuffer); + + */ + + Uid randomGuid = new(); + + Guid.NewGuid().TryWriteBytes(MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref randomGuid, 1))); + + registrationKey = new() + { + Type = 0x101, + KeyIndex = (byte)(Random.Shared.Next() & 7), + UserIdBool = 0, // TODO: Find it. + UnknownBool = (byte)(arg2 ? 1 : 0), // TODO: Find it. + Reserved = new(), + Uuid = randomGuid, + HmacHash = new(), + }; + + return Result.Success; + } + + [CmifCommand(10701)] + public Result GetPlayHistoryRegistrationKeyWithNetworkServiceAccountId( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x40)] out PlayHistoryRegistrationKey registrationKey, + NetworkServiceAccountId friendId, + bool arg2) + { + registrationKey = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { friendId, arg2 }); + + return Result.Success; + } + + [CmifCommand(10702)] + public Result AddPlayHistory( + Uid userId, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x40)] in PlayHistoryRegistrationKey registrationKey, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg2, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg3, + ulong pidPlaceholder, + [ClientProcessId] ulong pid) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, registrationKey, arg2, arg3, pidPlaceholder, pid }); + + return Result.Success; + } + + [CmifCommand(11000)] + public Result GetProfileImageUrl(out Url imageUrl, Url url, int arg2) + { + imageUrl = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { url, arg2 }); + + return Result.Success; + } + + [CmifCommand(20100)] + public Result GetFriendCount(out int count, Uid userId, SizedFriendFilter filter, ulong pidPlaceholder, [ClientProcessId] ulong pid) + { + count = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, filter, pidPlaceholder, pid }); + + return Result.Success; + } + + [CmifCommand(20101)] + public Result GetNewlyFriendCount(out int count, Uid userId) + { + count = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(20102)] + public Result GetFriendDetailedInfo( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x800)] out FriendDetailedInfoImpl detailedInfo, + Uid userId, + NetworkServiceAccountId friendId) + { + detailedInfo = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId }); + + return Result.Success; + } + + [CmifCommand(20103)] + public Result SyncFriendList(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(20104)] + public Result RequestSyncFriendList(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(20110)] + public Result LoadFriendSetting( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x40)] out FriendSettingImpl friendSetting, + Uid userId, + NetworkServiceAccountId friendId) + { + friendSetting = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId }); + + return Result.Success; + } + + [CmifCommand(20200)] + public Result GetReceivedFriendRequestCount(out int count, out int count2, Uid userId) + { + count = 0; + count2 = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(20201)] + public Result GetFriendRequestList( + out int count, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span requestList, + Uid userId, + int arg3, + int arg4) + { + count = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, arg3, arg4 }); + + return Result.Success; + } + + [CmifCommand(20300)] + public Result GetFriendCandidateList( + out int count, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span candidateList, + Uid userId, + int arg3) + { + count = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, arg3 }); + + return Result.Success; + } + + [CmifCommand(20301)] + public Result GetNintendoNetworkIdInfo( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x38)] out NintendoNetworkIdUserInfo networkIdInfo, + out int arg1, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span friendInfo, + Uid userId, + int arg4) + { + networkIdInfo = default; + arg1 = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, arg4 }); + + return Result.Success; + } + + [CmifCommand(20302)] + public Result GetSnsAccountLinkage(out SnsAccountLinkage accountLinkage, Uid userId) + { + accountLinkage = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(20303)] + public Result GetSnsAccountProfile( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x380)] out SnsAccountProfile accountProfile, + Uid userId, + NetworkServiceAccountId friendId, + int arg3) + { + accountProfile = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, arg3 }); + + return Result.Success; + } + + [CmifCommand(20304)] + public Result GetSnsAccountFriendList( + out int count, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span friendList, + Uid userId, + int arg3) + { + count = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, arg3 }); + + return Result.Success; + } + + [CmifCommand(20400)] + public Result GetBlockedUserList( + out int count, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span blockedUsers, + Uid userId, + int arg3) + { + count = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, arg3 }); + + return Result.Success; + } + + [CmifCommand(20401)] + public Result SyncBlockedUserList(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(20500)] + public Result GetProfileExtraList( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span extraList, + Uid userId, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan friendIds) + { + string friendIdList = string.Join(", ", friendIds.ToArray()); + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendIdList }); + + return Result.Success; + } + + [CmifCommand(20501)] + public Result GetRelationship(out Relationship relationship, Uid userId, NetworkServiceAccountId friendId) + { + relationship = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId }); + + return Result.Success; + } + + [CmifCommand(20600)] + public Result GetUserPresenceView([Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0xE0)] out UserPresenceViewImpl userPresenceView, Uid userId) + { + userPresenceView = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(20700)] + public Result GetPlayHistoryList(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span playHistoryList, Uid userId, int arg3) + { + count = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, arg3 }); + + return Result.Success; + } + + [CmifCommand(20701)] + public Result GetPlayHistoryStatistics(out PlayHistoryStatistics statistics, Uid userId) + { + statistics = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(20800)] + public Result LoadUserSetting([Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x800)] out UserSettingImpl userSetting, Uid userId) + { + userSetting = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(20801)] + public Result SyncUserSetting(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(20900)] + public Result RequestListSummaryOverlayNotification() + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend); + + return Result.Success; + } + + [CmifCommand(21000)] + public Result GetExternalApplicationCatalog( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x4B8)] out ExternalApplicationCatalog catalog, + ExternalApplicationCatalogId catalogId, + LanguageCode language) + { + catalog = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { catalogId, language }); + + return Result.Success; + } + + [CmifCommand(22000)] + public Result GetReceivedFriendInvitationList( + out int count, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span invitationList, + Uid userId) + { + count = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(22001)] + public Result GetReceivedFriendInvitationDetailedInfo( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias, 0x1400)] out FriendInvitationGroupImpl invicationGroup, + Uid userId, + FriendInvitationGroupId groupId) + { + invicationGroup = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, groupId }); + + return Result.Success; + } + + [CmifCommand(22010)] + public Result GetReceivedFriendInvitationCountCache(out int count, Uid userId) + { + count = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(30100)] + public Result DropFriendNewlyFlags(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(30101)] + public Result DeleteFriend(Uid userId, NetworkServiceAccountId friendId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId }); + + return Result.Success; + } + + [CmifCommand(30110)] + public Result DropFriendNewlyFlag(Uid userId, NetworkServiceAccountId friendId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId }); + + return Result.Success; + } + + [CmifCommand(30120)] + public Result ChangeFriendFavoriteFlag(Uid userId, NetworkServiceAccountId friendId, bool favoriteFlag) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, favoriteFlag }); + + return Result.Success; + } + + [CmifCommand(30121)] + public Result ChangeFriendOnlineNotificationFlag(Uid userId, NetworkServiceAccountId friendId, bool onlineNotificationFlag) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, onlineNotificationFlag }); + + return Result.Success; + } + + [CmifCommand(30200)] + public Result SendFriendRequest(Uid userId, NetworkServiceAccountId friendId, int arg2) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, arg2 }); + + return Result.Success; + } + + [CmifCommand(30201)] + public Result SendFriendRequestWithApplicationInfo( + Uid userId, + NetworkServiceAccountId friendId, + int arg2, + ApplicationInfo applicationInfo, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg4, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg5) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, arg2, applicationInfo, arg4, arg5 }); + + return Result.Success; + } + + [CmifCommand(30202)] + public Result CancelFriendRequest(Uid userId, RequestId requestId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, requestId }); + + return Result.Success; + } + + [CmifCommand(30203)] + public Result AcceptFriendRequest(Uid userId, RequestId requestId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, requestId }); + + return Result.Success; + } + + [CmifCommand(30204)] + public Result RejectFriendRequest(Uid userId, RequestId requestId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, requestId }); + + return Result.Success; + } + + [CmifCommand(30205)] + public Result ReadFriendRequest(Uid userId, RequestId requestId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, requestId }); + + return Result.Success; + } + + [CmifCommand(30210)] + public Result GetFacedFriendRequestRegistrationKey(out FacedFriendRequestRegistrationKey registrationKey, Uid userId) + { + registrationKey = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(30211)] + public Result AddFacedFriendRequest( + Uid userId, + FacedFriendRequestRegistrationKey registrationKey, + Nickname nickname, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan arg3) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, registrationKey, nickname }); + + return Result.Success; + } + + [CmifCommand(30212)] + public Result CancelFacedFriendRequest(Uid userId, NetworkServiceAccountId friendId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId }); + + return Result.Success; + } + + [CmifCommand(30213)] + public Result GetFacedFriendRequestProfileImage( + out int size, + Uid userId, + NetworkServiceAccountId friendId, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span profileImage) + { + size = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId }); + + return Result.Success; + } + + [CmifCommand(30214)] + public Result GetFacedFriendRequestProfileImageFromPath( + out int size, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan path, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span profileImage) + { + size = 0; + + string pathString = Encoding.UTF8.GetString(path); + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { pathString }); + + return Result.Success; + } + + [CmifCommand(30215)] + public Result SendFriendRequestWithExternalApplicationCatalogId( + Uid userId, + NetworkServiceAccountId friendId, + int arg2, + ExternalApplicationCatalogId catalogId, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg4, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg5) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, arg2, catalogId, arg4, arg5 }); + + return Result.Success; + } + + [CmifCommand(30216)] + public Result ResendFacedFriendRequest(Uid userId, NetworkServiceAccountId friendId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId }); + + return Result.Success; + } + + [CmifCommand(30217)] + public Result SendFriendRequestWithNintendoNetworkIdInfo( + Uid userId, + NetworkServiceAccountId friendId, + int arg2, + MiiName arg3, + MiiImageUrlParam arg4, + MiiName arg5, + MiiImageUrlParam arg6) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, arg2, arg3, arg4, arg5, arg6 }); + + return Result.Success; + } + + [CmifCommand(30300)] + public Result GetSnsAccountLinkPageUrl([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias, 0x1000)] out WebPageUrl url, Uid userId, int arg2) + { + url = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, arg2 }); + + return Result.Success; + } + + [CmifCommand(30301)] + public Result UnlinkSnsAccount(Uid userId, int arg1) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, arg1 }); + + return Result.Success; + } + + [CmifCommand(30400)] + public Result BlockUser(Uid userId, NetworkServiceAccountId friendId, int arg2) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, arg2 }); + + return Result.Success; + } + + [CmifCommand(30401)] + public Result BlockUserWithApplicationInfo( + Uid userId, + NetworkServiceAccountId friendId, + int arg2, + ApplicationInfo applicationInfo, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg4) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, arg2, applicationInfo, arg4 }); + + return Result.Success; + } + + [CmifCommand(30402)] + public Result UnblockUser(Uid userId, NetworkServiceAccountId friendId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId }); + + return Result.Success; + } + + [CmifCommand(30500)] + public Result GetProfileExtraFromFriendCode( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x400)] out ProfileExtraImpl profileExtra, + Uid userId, + FriendCode friendCode) + { + profileExtra = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendCode }); + + return Result.Success; + } + + [CmifCommand(30700)] + public Result DeletePlayHistory(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(30810)] + public Result ChangePresencePermission(Uid userId, int permission) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, permission }); + + return Result.Success; + } + + [CmifCommand(30811)] + public Result ChangeFriendRequestReception(Uid userId, bool reception) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, reception }); + + return Result.Success; + } + + [CmifCommand(30812)] + public Result ChangePlayLogPermission(Uid userId, int permission) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, permission }); + + return Result.Success; + } + + [CmifCommand(30820)] + public Result IssueFriendCode(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(30830)] + public Result ClearPlayLog(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(30900)] + public Result SendFriendInvitation( + Uid userId, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan friendIds, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias, 0xC00)] in FriendInvitationGameModeDescription description, + ApplicationInfo applicationInfo, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan arg4, + bool arg5) + { + string friendIdList = string.Join(", ", friendIds.ToArray()); + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendIdList, description, applicationInfo, arg5 }); + + return Result.Success; + } + + [CmifCommand(30910)] + public Result ReadFriendInvitation(Uid userId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan invitationIds) + { + string invitationIdList = string.Join(", ", invitationIds.ToArray()); + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, invitationIdList }); + + return Result.Success; + } + + [CmifCommand(30911)] + public Result ReadAllFriendInvitations(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(40100)] + public Result DeleteFriendListCache(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(40400)] + public Result DeleteBlockedUserListCache(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(49900)] + public Result DeleteNetworkServiceAccountCache(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Os.DestroySystemEvent(ref _completionEvent); + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/FriendServicePermissionLevel.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendsServicePermissionLevel.cs similarity index 64% rename from src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/FriendServicePermissionLevel.cs rename to src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendsServicePermissionLevel.cs index 7902d9c53..f4bbe100f 100644 --- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/FriendServicePermissionLevel.cs +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendsServicePermissionLevel.cs @@ -1,16 +1,13 @@ -using System; - -namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc { - [Flags] - enum FriendServicePermissionLevel + enum FriendsServicePermissionLevel { UserMask = 1, ViewerMask = 2, ManagerMask = 4, SystemMask = 8, - Administrator = -1, + Admin = -1, User = UserMask, Viewer = UserMask | ViewerMask, Manager = UserMask | ViewerMask | ManagerMask, diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IDaemonSuspendSessionService.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IDaemonSuspendSessionService.cs new file mode 100644 index 000000000..2bb0434e8 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IDaemonSuspendSessionService.cs @@ -0,0 +1,9 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + interface IDaemonSuspendSessionService : IServiceObject + { + // NOTE: This service has no commands. + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IFriendService.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IFriendService.cs new file mode 100644 index 000000000..c19d0b788 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IFriendService.cs @@ -0,0 +1,97 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Account; +using Ryujinx.Horizon.Sdk.Settings; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + interface IFriendService : IServiceObject + { + Result GetCompletionEvent(out int completionEventHandle); + Result Cancel(); + Result GetFriendListIds(out int count, Span friendIds, Uid userId, int offset, SizedFriendFilter filter, ulong pidPlaceholder, ulong pid); + Result GetFriendList(out int count, Span friendList, Uid userId, int offset, SizedFriendFilter filter, ulong pidPlaceholder, ulong pid); + Result UpdateFriendInfo(Span info, Uid userId, ReadOnlySpan friendIds, ulong pidPlaceholder, ulong pid); + Result GetFriendProfileImage(out int size, Uid userId, NetworkServiceAccountId friendId, Span profileImage); + Result CheckFriendListAvailability(out bool listAvailable, Uid userId); + Result EnsureFriendListAvailable(Uid userId); + Result SendFriendRequestForApplication(Uid userId, NetworkServiceAccountId friendId, in InAppScreenName arg2, in InAppScreenName arg3, ulong pidPlaceholder, ulong pid); + Result AddFacedFriendRequestForApplication(Uid userId, FacedFriendRequestRegistrationKey key, Nickname nickname, ReadOnlySpan arg3, in InAppScreenName arg4, in InAppScreenName arg5, ulong pidPlaceholder, ulong pid); + Result GetBlockedUserListIds(out int count, Span blockedIds, Uid userId, int offset); + Result CheckBlockedUserListAvailability(out bool listAvailable, Uid userId); + Result EnsureBlockedUserListAvailable(Uid userId); + Result GetProfileList(Span profileList, Uid userId, ReadOnlySpan friendIds); + Result DeclareOpenOnlinePlaySession(Uid userId); + Result DeclareCloseOnlinePlaySession(Uid userId); + Result UpdateUserPresence(Uid userId, in UserPresenceImpl userPresence, ulong pidPlaceholder, ulong pid); + Result GetPlayHistoryRegistrationKey(out PlayHistoryRegistrationKey registrationKey, Uid userId, bool arg2); + Result GetPlayHistoryRegistrationKeyWithNetworkServiceAccountId(out PlayHistoryRegistrationKey registrationKey, NetworkServiceAccountId friendId, bool arg2); + Result AddPlayHistory(Uid userId, in PlayHistoryRegistrationKey registrationKey, in InAppScreenName arg2, in InAppScreenName arg3, ulong pidPlaceholder, ulong pid); + Result GetProfileImageUrl(out Url imageUrl, Url url, int arg2); + Result GetFriendCount(out int count, Uid userId, SizedFriendFilter filter, ulong pidPlaceholder, ulong pid); + Result GetNewlyFriendCount(out int count, Uid userId); + Result GetFriendDetailedInfo(out FriendDetailedInfoImpl detailedInfo, Uid userId, NetworkServiceAccountId friendId); + Result SyncFriendList(Uid userId); + Result RequestSyncFriendList(Uid userId); + Result LoadFriendSetting(out FriendSettingImpl friendSetting, Uid userId, NetworkServiceAccountId friendId); + Result GetReceivedFriendRequestCount(out int count, out int count2, Uid userId); + Result GetFriendRequestList(out int count, Span requestList, Uid userId, int arg3, int arg4); + Result GetFriendCandidateList(out int count, Span candidateList, Uid userId, int arg3); + Result GetNintendoNetworkIdInfo(out NintendoNetworkIdUserInfo networkIdInfo, out int arg1, Span friendInfo, Uid userId, int arg4); + Result GetSnsAccountLinkage(out SnsAccountLinkage accountLinkage, Uid userId); + Result GetSnsAccountProfile(out SnsAccountProfile accountProfile, Uid userId, NetworkServiceAccountId friendId, int arg3); + Result GetSnsAccountFriendList(out int count, Span friendList, Uid userId, int arg3); + Result GetBlockedUserList(out int count, Span blockedUsers, Uid userId, int arg3); + Result SyncBlockedUserList(Uid userId); + Result GetProfileExtraList(Span extraList, Uid userId, ReadOnlySpan friendIds); + Result GetRelationship(out Relationship relationship, Uid userId, NetworkServiceAccountId friendId); + Result GetUserPresenceView(out UserPresenceViewImpl userPresenceView, Uid userId); + Result GetPlayHistoryList(out int count, Span playHistoryList, Uid userId, int arg3); + Result GetPlayHistoryStatistics(out PlayHistoryStatistics statistics, Uid userId); + Result LoadUserSetting(out UserSettingImpl userSetting, Uid userId); + Result SyncUserSetting(Uid userId); + Result RequestListSummaryOverlayNotification(); + Result GetExternalApplicationCatalog(out ExternalApplicationCatalog catalog, ExternalApplicationCatalogId catalogId, LanguageCode language); + Result GetReceivedFriendInvitationList(out int count, Span invitationList, Uid userId); + Result GetReceivedFriendInvitationDetailedInfo(out FriendInvitationGroupImpl invicationGroup, Uid userId, FriendInvitationGroupId groupId); + Result GetReceivedFriendInvitationCountCache(out int count, Uid userId); + Result DropFriendNewlyFlags(Uid userId); + Result DeleteFriend(Uid userId, NetworkServiceAccountId friendId); + Result DropFriendNewlyFlag(Uid userId, NetworkServiceAccountId friendId); + Result ChangeFriendFavoriteFlag(Uid userId, NetworkServiceAccountId friendId, bool favoriteFlag); + Result ChangeFriendOnlineNotificationFlag(Uid userId, NetworkServiceAccountId friendId, bool onlineNotificationFlag); + Result SendFriendRequest(Uid userId, NetworkServiceAccountId friendId, int arg2); + Result SendFriendRequestWithApplicationInfo(Uid userId, NetworkServiceAccountId friendId, int arg2, ApplicationInfo applicationInfo, in InAppScreenName arg4, in InAppScreenName arg5); + Result CancelFriendRequest(Uid userId, RequestId requestId); + Result AcceptFriendRequest(Uid userId, RequestId requestId); + Result RejectFriendRequest(Uid userId, RequestId requestId); + Result ReadFriendRequest(Uid userId, RequestId requestId); + Result GetFacedFriendRequestRegistrationKey(out FacedFriendRequestRegistrationKey registrationKey, Uid userId); + Result AddFacedFriendRequest(Uid userId, FacedFriendRequestRegistrationKey registrationKey, Nickname nickname, ReadOnlySpan arg3); + Result CancelFacedFriendRequest(Uid userId, NetworkServiceAccountId friendId); + Result GetFacedFriendRequestProfileImage(out int size, Uid userId, NetworkServiceAccountId friendId, Span profileImage); + Result GetFacedFriendRequestProfileImageFromPath(out int size, ReadOnlySpan path, Span profileImage); + Result SendFriendRequestWithExternalApplicationCatalogId(Uid userId, NetworkServiceAccountId friendId, int arg2, ExternalApplicationCatalogId catalogId, in InAppScreenName arg4, in InAppScreenName arg5); + Result ResendFacedFriendRequest(Uid userId, NetworkServiceAccountId friendId); + Result SendFriendRequestWithNintendoNetworkIdInfo(Uid userId, NetworkServiceAccountId friendId, int arg2, MiiName arg3, MiiImageUrlParam arg4, MiiName arg5, MiiImageUrlParam arg6); + Result GetSnsAccountLinkPageUrl(out WebPageUrl url, Uid userId, int arg2); + Result UnlinkSnsAccount(Uid userId, int arg1); + Result BlockUser(Uid userId, NetworkServiceAccountId friendId, int arg2); + Result BlockUserWithApplicationInfo(Uid userId, NetworkServiceAccountId friendId, int arg2, ApplicationInfo applicationInfo, in InAppScreenName arg4); + Result UnblockUser(Uid userId, NetworkServiceAccountId friendId); + Result GetProfileExtraFromFriendCode(out ProfileExtraImpl profileExtra, Uid userId, FriendCode friendCode); + Result DeletePlayHistory(Uid userId); + Result ChangePresencePermission(Uid userId, int permission); + Result ChangeFriendRequestReception(Uid userId, bool reception); + Result ChangePlayLogPermission(Uid userId, int permission); + Result IssueFriendCode(Uid userId); + Result ClearPlayLog(Uid userId); + Result SendFriendInvitation(Uid userId, ReadOnlySpan friendIds, in FriendInvitationGameModeDescription description, ApplicationInfo applicationInfo, ReadOnlySpan arg4, bool arg5); + Result ReadFriendInvitation(Uid userId, ReadOnlySpan invitationIds); + Result ReadAllFriendInvitations(Uid userId); + Result DeleteFriendListCache(Uid userId); + Result DeleteBlockedUserListCache(Uid userId); + Result DeleteNetworkServiceAccountCache(Uid userId); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/INotificationService.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/INotificationService.cs new file mode 100644 index 000000000..a3a28e8ce --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/INotificationService.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + interface INotificationService : IServiceObject + { + Result GetEvent(out int eventHandle); + Result Clear(); + Result Pop(out SizedNotificationInfo sizedNotificationInfo); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IServiceCreator.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IServiceCreator.cs new file mode 100644 index 000000000..58e2569bb --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IServiceCreator.cs @@ -0,0 +1,13 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Account; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + interface IServiceCreator : IServiceObject + { + Result CreateFriendService(out IFriendService friendService); + Result CreateNotificationService(out INotificationService notificationService, Uid userId); + Result CreateDaemonSuspendSessionService(out IDaemonSuspendSessionService daemonSuspendSessionService); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationEventHandler.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationEventHandler.cs new file mode 100644 index 000000000..61c692a6e --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationEventHandler.cs @@ -0,0 +1,58 @@ +using Ryujinx.Horizon.Sdk.Account; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + sealed class NotificationEventHandler + { + private readonly NotificationService[] _registry; + + public NotificationEventHandler() + { + _registry = new NotificationService[0x20]; + } + + public void RegisterNotificationService(NotificationService service) + { + // NOTE: When there's no enough space in the registry array, Nintendo doesn't return any errors. + for (int i = 0; i < _registry.Length; i++) + { + if (_registry[i] == null) + { + _registry[i] = service; + break; + } + } + } + + public void UnregisterNotificationService(NotificationService service) + { + // NOTE: When there's no enough space in the registry array, Nintendo doesn't return any errors. + for (int i = 0; i < _registry.Length; i++) + { + if (_registry[i] == service) + { + _registry[i] = null; + break; + } + } + } + + // TODO: Use this when we have enough things to go online. + public void SignalFriendListUpdate(Uid targetId) + { + for (int i = 0; i < _registry.Length; i++) + { + _registry[i]?.SignalFriendListUpdate(targetId); + } + } + + // TODO: Use this when we have enough things to go online. + public void SignalNewFriendRequest(Uid targetId) + { + for (int i = 0; i < _registry.Length; i++) + { + _registry[i]?.SignalNewFriendRequest(targetId); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationEventType.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationEventType.cs similarity index 64% rename from src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationEventType.cs rename to src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationEventType.cs index 363e03eaf..e46fc9b7a 100644 --- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationEventType.cs +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationEventType.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc { enum NotificationEventType : uint { diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationService.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationService.cs new file mode 100644 index 000000000..534bf63ed --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationService.cs @@ -0,0 +1,172 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Account; +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + partial class NotificationService : INotificationService, IDisposable + { + private readonly NotificationEventHandler _notificationEventHandler; + private readonly Uid _userId; + private readonly FriendsServicePermissionLevel _permissionLevel; + + private readonly object _lock = new(); + + private SystemEventType _notificationEvent; + + private readonly LinkedList _notifications; + + private bool _hasNewFriendRequest; + private bool _hasFriendListUpdate; + + public NotificationService(NotificationEventHandler notificationEventHandler, Uid userId, FriendsServicePermissionLevel permissionLevel) + { + _notificationEventHandler = notificationEventHandler; + _userId = userId; + _permissionLevel = permissionLevel; + _notifications = new LinkedList(); + Os.CreateSystemEvent(out _notificationEvent, EventClearMode.AutoClear, interProcess: true).AbortOnFailure(); + + _hasNewFriendRequest = false; + _hasFriendListUpdate = false; + + notificationEventHandler.RegisterNotificationService(this); + } + + [CmifCommand(0)] + public Result GetEvent([CopyHandle] out int eventHandle) + { + eventHandle = Os.GetReadableHandleOfSystemEvent(ref _notificationEvent); + + return Result.Success; + } + + [CmifCommand(1)] + public Result Clear() + { + lock (_lock) + { + _hasNewFriendRequest = false; + _hasFriendListUpdate = false; + + _notifications.Clear(); + } + + return Result.Success; + } + + [CmifCommand(2)] + public Result Pop(out SizedNotificationInfo sizedNotificationInfo) + { + lock (_lock) + { + if (_notifications.Count >= 1) + { + sizedNotificationInfo = _notifications.First.Value; + _notifications.RemoveFirst(); + + if (sizedNotificationInfo.Type == NotificationEventType.FriendListUpdate) + { + _hasFriendListUpdate = false; + } + else if (sizedNotificationInfo.Type == NotificationEventType.NewFriendRequest) + { + _hasNewFriendRequest = false; + } + + return Result.Success; + } + } + + sizedNotificationInfo = default; + + return FriendResult.NotificationQueueEmpty; + } + + public void SignalFriendListUpdate(Uid targetId) + { + lock (_lock) + { + if (_userId == targetId) + { + if (!_hasFriendListUpdate) + { + SizedNotificationInfo friendListNotification = new(); + + if (_notifications.Count != 0) + { + friendListNotification = _notifications.First.Value; + _notifications.RemoveFirst(); + } + + friendListNotification.Type = NotificationEventType.FriendListUpdate; + _hasFriendListUpdate = true; + + if (_hasNewFriendRequest) + { + SizedNotificationInfo newFriendRequestNotification = new(); + + if (_notifications.Count != 0) + { + newFriendRequestNotification = _notifications.First.Value; + _notifications.RemoveFirst(); + } + + newFriendRequestNotification.Type = NotificationEventType.NewFriendRequest; + _notifications.AddFirst(newFriendRequestNotification); + } + + // We defer this to make sure we are on top of the queue. + _notifications.AddFirst(friendListNotification); + } + + Os.SignalSystemEvent(ref _notificationEvent); + } + } + } + + public void SignalNewFriendRequest(Uid targetId) + { + lock (_lock) + { + if (_permissionLevel.HasFlag(FriendsServicePermissionLevel.ViewerMask) && _userId == targetId) + { + if (!_hasNewFriendRequest) + { + if (_notifications.Count == 100) + { + SignalFriendListUpdate(targetId); + } + + SizedNotificationInfo newFriendRequestNotification = new() + { + Type = NotificationEventType.NewFriendRequest, + }; + + _notifications.AddLast(newFriendRequestNotification); + _hasNewFriendRequest = true; + } + + Os.SignalSystemEvent(ref _notificationEvent); + } + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _notificationEventHandler.UnregisterNotificationService(this); + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatusFilter.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/PresenceStatusFilter.cs similarity index 64% rename from src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatusFilter.cs rename to src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/PresenceStatusFilter.cs index c9a54250f..3ea105872 100644 --- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatusFilter.cs +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/PresenceStatusFilter.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc { enum PresenceStatusFilter : uint { diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/ServiceCreator.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/ServiceCreator.cs new file mode 100644 index 000000000..1be804dfd --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/ServiceCreator.cs @@ -0,0 +1,51 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Account; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + partial class ServiceCreator : IServiceCreator + { + private readonly IEmulatorAccountManager _accountManager; + private readonly NotificationEventHandler _notificationEventHandler; + private readonly FriendsServicePermissionLevel _permissionLevel; + + public ServiceCreator(IEmulatorAccountManager accountManager, NotificationEventHandler notificationEventHandler, FriendsServicePermissionLevel permissionLevel) + { + _accountManager = accountManager; + _notificationEventHandler = notificationEventHandler; + _permissionLevel = permissionLevel; + } + + [CmifCommand(0)] + public Result CreateFriendService(out IFriendService friendService) + { + friendService = new FriendService(_accountManager, _permissionLevel); + + return Result.Success; + } + + [CmifCommand(1)] // 2.0.0+ + public Result CreateNotificationService(out INotificationService notificationService, Uid userId) + { + if (userId.IsNull) + { + notificationService = null; + + return FriendResult.InvalidArgument; + } + + notificationService = new NotificationService(_notificationEventHandler, userId, _permissionLevel); + + return Result.Success; + } + + [CmifCommand(2)] // 4.0.0+ + public Result CreateDaemonSuspendSessionService(out IDaemonSuspendSessionService daemonSuspendSessionService) + { + daemonSuspendSessionService = new DaemonSuspendSessionService(); + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedFriendFilter.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedFriendFilter.cs new file mode 100644 index 000000000..d93a2ae29 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedFriendFilter.cs @@ -0,0 +1,25 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)] + struct SizedFriendFilter + { + public PresenceStatusFilter PresenceStatus; + public bool IsFavoriteOnly; + public bool IsSameAppPresenceOnly; + public bool IsSameAppPlayedOnly; + public bool IsArbitraryAppPlayedOnly; + public ulong PresenceGroupId; + + public readonly override string ToString() + { + return $"{{ PresenceStatus: {PresenceStatus}, " + + $"IsFavoriteOnly: {IsFavoriteOnly}, " + + $"IsSameAppPresenceOnly: {IsSameAppPresenceOnly}, " + + $"IsSameAppPlayedOnly: {IsSameAppPlayedOnly}, " + + $"IsArbitraryAppPlayedOnly: {IsArbitraryAppPlayedOnly}, " + + $"PresenceGroupId: {PresenceGroupId} }}"; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedNotificationInfo.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedNotificationInfo.cs new file mode 100644 index 000000000..0da26a1ae --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedNotificationInfo.cs @@ -0,0 +1,13 @@ +using Ryujinx.Horizon.Sdk.Account; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)] + struct SizedNotificationInfo + { + public NotificationEventType Type; + public uint Padding; + public NetworkServiceAccountId NetworkUserIdPlaceholder; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/NintendoNetworkIdFriendImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/NintendoNetworkIdFriendImpl.cs new file mode 100644 index 000000000..66d61e4c1 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/NintendoNetworkIdFriendImpl.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + struct NintendoNetworkIdFriendImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/PlayHistoryImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/PlayHistoryImpl.cs new file mode 100644 index 000000000..9f90f0c8f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/PlayHistoryImpl.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + struct PlayHistoryImpl + { + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatus.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/PresenceStatus.cs similarity index 58% rename from src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatus.cs rename to src/Ryujinx.Horizon/Sdk/Friends/Detail/PresenceStatus.cs index 7930aff0b..5ddbe14ea 100644 --- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatus.cs +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/PresenceStatus.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService +namespace Ryujinx.Horizon.Sdk.Friends.Detail { enum PresenceStatus : uint { diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileExtraImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileExtraImpl.cs new file mode 100644 index 000000000..1548d725f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileExtraImpl.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x400)] + struct ProfileExtraImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileImpl.cs new file mode 100644 index 000000000..f779d93cf --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileImpl.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + struct ProfileImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/SnsAccountFriendImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/SnsAccountFriendImpl.cs new file mode 100644 index 000000000..dc6adf03a --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/SnsAccountFriendImpl.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + struct SnsAccountFriendImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceImpl.cs new file mode 100644 index 000000000..cf4520cf4 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceImpl.cs @@ -0,0 +1,29 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Horizon.Sdk.Account; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0xE0)] + struct UserPresenceImpl + { + public Uid UserId; + public long LastTimeOnlineTimestamp; + public PresenceStatus Status; + public bool SamePresenceGroupApplication; + public Array3 Unknown; + public AppKeyValueStorageHolder AppKeyValueStorage; + + [InlineArray(0xC0)] + public struct AppKeyValueStorageHolder + { + public byte Value; + } + + public readonly override string ToString() + { + return $"{{ UserId: {UserId}, LastTimeOnlineTimestamp: {LastTimeOnlineTimestamp}, Status: {Status} }}"; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceViewImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceViewImpl.cs new file mode 100644 index 000000000..04c092600 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceViewImpl.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0xE0)] + struct UserPresenceViewImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserSettingImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserSettingImpl.cs new file mode 100644 index 000000000..9d057fb1e --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserSettingImpl.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x800)] + struct UserSettingImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalog.cs b/src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalog.cs new file mode 100644 index 000000000..0d9c157d3 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalog.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x4B8)] + struct ExternalApplicationCatalog + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalogId.cs b/src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalogId.cs new file mode 100644 index 000000000..7ed36cd9d --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalogId.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)] + struct ExternalApplicationCatalogId + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/FacedFriendRequestRegistrationKey.cs b/src/Ryujinx.Horizon/Sdk/Friends/FacedFriendRequestRegistrationKey.cs new file mode 100644 index 000000000..6b5812f64 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/FacedFriendRequestRegistrationKey.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x40, Pack = 0x1)] + struct FacedFriendRequestRegistrationKey + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/FriendCode.cs b/src/Ryujinx.Horizon/Sdk/Friends/FriendCode.cs new file mode 100644 index 000000000..d78497a18 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/FriendCode.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 0x1)] + struct FriendCode + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGameModeDescription.cs b/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGameModeDescription.cs new file mode 100644 index 000000000..29b4a0974 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGameModeDescription.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0xC00)] + struct FriendInvitationGameModeDescription + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGroupId.cs b/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGroupId.cs new file mode 100644 index 000000000..ef53882b3 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGroupId.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x8)] + struct FriendInvitationGroupId + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationId.cs b/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationId.cs new file mode 100644 index 000000000..7be19d574 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationId.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + struct FriendInvitationId + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/FriendResult.cs b/src/Ryujinx.Horizon/Sdk/Friends/FriendResult.cs new file mode 100644 index 000000000..5965d508d --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/FriendResult.cs @@ -0,0 +1,13 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + static class FriendResult + { + private const int ModuleId = 121; + + public static Result InvalidArgument => new(ModuleId, 2); + public static Result InternetRequestDenied => new(ModuleId, 6); + public static Result NotificationQueueEmpty => new(ModuleId, 15); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/InAppScreenName.cs b/src/Ryujinx.Horizon/Sdk/Friends/InAppScreenName.cs new file mode 100644 index 000000000..22574a5cc --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/InAppScreenName.cs @@ -0,0 +1,26 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Horizon.Sdk.Settings; +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x48)] + struct InAppScreenName + { + public Array64 Name; + public LanguageCode LanguageCode; + + public override readonly string ToString() + { + int length = Name.AsSpan().IndexOf((byte)0); + if (length < 0) + { + length = 64; + } + + return Encoding.UTF8.GetString(Name.AsSpan()[..length]); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/MiiImageUrlParam.cs b/src/Ryujinx.Horizon/Sdk/Friends/MiiImageUrlParam.cs new file mode 100644 index 000000000..8790bb931 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/MiiImageUrlParam.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x1)] + struct MiiImageUrlParam + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/MiiName.cs b/src/Ryujinx.Horizon/Sdk/Friends/MiiName.cs new file mode 100644 index 000000000..e73c0d833 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/MiiName.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 0x1)] + struct MiiName + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/NintendoNetworkIdUserInfo.cs b/src/Ryujinx.Horizon/Sdk/Friends/NintendoNetworkIdUserInfo.cs new file mode 100644 index 000000000..a2a9e046f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/NintendoNetworkIdUserInfo.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x38)] + struct NintendoNetworkIdUserInfo + { + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/PlayHistoryRegistrationKey.cs b/src/Ryujinx.Horizon/Sdk/Friends/PlayHistoryRegistrationKey.cs similarity index 59% rename from src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/PlayHistoryRegistrationKey.cs rename to src/Ryujinx.Horizon/Sdk/Friends/PlayHistoryRegistrationKey.cs index 9687c5478..bb672a795 100644 --- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/PlayHistoryRegistrationKey.cs +++ b/src/Ryujinx.Horizon/Sdk/Friends/PlayHistoryRegistrationKey.cs @@ -1,9 +1,10 @@ using Ryujinx.Common.Memory; +using Ryujinx.Horizon.Sdk.Account; using System.Runtime.InteropServices; -namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator +namespace Ryujinx.Horizon.Sdk.Friends { - [StructLayout(LayoutKind.Sequential, Size = 0x20)] + [StructLayout(LayoutKind.Sequential, Size = 0x40)] struct PlayHistoryRegistrationKey { public ushort Type; @@ -11,6 +12,7 @@ struct PlayHistoryRegistrationKey public byte UserIdBool; public byte UnknownBool; public Array11 Reserved; - public Array16 Uuid; + public Uid Uuid; + public Array32 HmacHash; } } diff --git a/src/Ryujinx.Horizon/Sdk/Friends/PlayHistoryStatistics.cs b/src/Ryujinx.Horizon/Sdk/Friends/PlayHistoryStatistics.cs new file mode 100644 index 000000000..ea3e3d997 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/PlayHistoryStatistics.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)] + struct PlayHistoryStatistics + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Relationship.cs b/src/Ryujinx.Horizon/Sdk/Friends/Relationship.cs new file mode 100644 index 000000000..efba09a8f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Relationship.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x1)] + struct Relationship + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/RequestId.cs b/src/Ryujinx.Horizon/Sdk/Friends/RequestId.cs new file mode 100644 index 000000000..3236a1d7d --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/RequestId.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x8)] + struct RequestId + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/SnsAccountLinkage.cs b/src/Ryujinx.Horizon/Sdk/Friends/SnsAccountLinkage.cs new file mode 100644 index 000000000..b4660d9e7 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/SnsAccountLinkage.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x1)] + struct SnsAccountLinkage + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/SnsAccountProfile.cs b/src/Ryujinx.Horizon/Sdk/Friends/SnsAccountProfile.cs new file mode 100644 index 000000000..d872b3dac --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/SnsAccountProfile.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x380)] + struct SnsAccountProfile + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Url.cs b/src/Ryujinx.Horizon/Sdk/Friends/Url.cs new file mode 100644 index 000000000..833ee1230 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Url.cs @@ -0,0 +1,30 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0xA0, Pack = 0x1)] + struct Url + { + public UrlStorage Path; + + [InlineArray(0xA0)] + public struct UrlStorage + { + public byte Value; + } + + public override readonly string ToString() + { + int length = ((ReadOnlySpan)Path).IndexOf((byte)0); + if (length < 0) + { + length = 33; + } + + return Encoding.UTF8.GetString(((ReadOnlySpan)Path)[..length]); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/WebPageUrl.cs b/src/Ryujinx.Horizon/Sdk/Friends/WebPageUrl.cs new file mode 100644 index 000000000..85488af61 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/WebPageUrl.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x1000)] + struct WebPageUrl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/BatteryLot.cs b/src/Ryujinx.Horizon/Sdk/Settings/BatteryLot.cs new file mode 100644 index 000000000..71185fcd0 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/BatteryLot.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings +{ + [StructLayout(LayoutKind.Sequential, Size = 0x18, Pack = 0x1)] + struct BatteryLot + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerOffset.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerOffset.cs new file mode 100644 index 000000000..292a368f1 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerOffset.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x6, Pack = 0x2)] + struct AccelerometerOffset + { + public ushort X; + public ushort Y; + public ushort Z; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerScale.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerScale.cs new file mode 100644 index 000000000..ef9d17ef9 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerScale.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x6, Pack = 0x2)] + struct AccelerometerScale + { + public ushort X; + public ushort Y; + public ushort Z; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcdsaCertificate.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcdsaCertificate.cs new file mode 100644 index 000000000..7cbab2f09 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcdsaCertificate.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x74, Pack = 0x4)] + struct AmiiboEcdsaCertificate + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsCertificate.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsCertificate.cs new file mode 100644 index 000000000..8d16b51b1 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsCertificate.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x24, Pack = 0x4)] + struct AmiiboEcqvBlsCertificate + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsKey.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsKey.cs new file mode 100644 index 000000000..da6ca53bc --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsKey.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x48, Pack = 0x4)] + struct AmiiboEcqvBlsKey + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsRootCertificate.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsRootCertificate.cs new file mode 100644 index 000000000..e69e38a1a --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsRootCertificate.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x94, Pack = 0x4)] + struct AmiiboEcqvBlsRootCertificate + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvCertificate.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvCertificate.cs new file mode 100644 index 000000000..43742fbb7 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvCertificate.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x18, Pack = 0x4)] + struct AmiiboEcqvCertificate + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboKey.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboKey.cs new file mode 100644 index 000000000..43ffccb00 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboKey.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x58, Pack = 0x4)] + struct AmiiboKey + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickFactoryCalibration.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickFactoryCalibration.cs new file mode 100644 index 000000000..3fe6f3223 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickFactoryCalibration.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x9, Pack = 0x1)] + struct AnalogStickFactoryCalibration + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickModelParameter.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickModelParameter.cs new file mode 100644 index 000000000..a442032c7 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickModelParameter.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x12, Pack = 0x1)] + struct AnalogStickModelParameter + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/BdAddress.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/BdAddress.cs new file mode 100644 index 000000000..519d72e8f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/BdAddress.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x6, Pack = 0x1)] + struct BdAddress + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/ConfigurationId1.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/ConfigurationId1.cs new file mode 100644 index 000000000..40565805f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/ConfigurationId1.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x1E, Pack = 0x1)] + struct ConfigurationId1 + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/ConsoleSixAxisSensorHorizontalOffset.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/ConsoleSixAxisSensorHorizontalOffset.cs new file mode 100644 index 000000000..c5503edc5 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/ConsoleSixAxisSensorHorizontalOffset.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x6, Pack = 0x2)] + struct ConsoleSixAxisSensorHorizontalOffset + { + public ushort X; + public ushort Y; + public ushort Z; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/CountryCode.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/CountryCode.cs new file mode 100644 index 000000000..daf2ba3b8 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/CountryCode.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + struct CountryCode + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceCertificate.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceCertificate.cs new file mode 100644 index 000000000..727408ed5 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceCertificate.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x180)] + struct EccB233DeviceCertificate + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceKey.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceKey.cs new file mode 100644 index 000000000..a0481f4dc --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceKey.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x58, Pack = 0x4)] + struct EccB233DeviceKey + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardCertificate.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardCertificate.cs new file mode 100644 index 000000000..ce3908afe --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardCertificate.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x400)] + struct GameCardCertificate + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardKey.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardKey.cs new file mode 100644 index 000000000..81144ac48 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardKey.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x138)] + struct GameCardKey + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeOffset.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeOffset.cs new file mode 100644 index 000000000..801d117cb --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeOffset.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x6, Pack = 0x2)] + struct GyroscopeOffset + { + public ushort X; + public ushort Y; + public ushort Z; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeScale.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeScale.cs new file mode 100644 index 000000000..7812281f8 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeScale.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x6, Pack = 0x2)] + struct GyroscopeScale + { + public ushort X; + public ushort Y; + public ushort Z; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/MacAddress.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/MacAddress.cs new file mode 100644 index 000000000..65e222ee5 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/MacAddress.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x6, Pack = 0x1)] + struct MacAddress + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceCertificate.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceCertificate.cs new file mode 100644 index 000000000..57217059f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceCertificate.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x240)] + struct Rsa2048DeviceCertificate + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceKey.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceKey.cs new file mode 100644 index 000000000..d2fd51cf7 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceKey.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x248)] + struct Rsa2048DeviceKey + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/SerialNumber.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/SerialNumber.cs new file mode 100644 index 000000000..af664cdc5 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/SerialNumber.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x18, Pack = 0x1)] + struct SerialNumber + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/SpeakerParameter.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/SpeakerParameter.cs new file mode 100644 index 000000000..f147f66ff --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/SpeakerParameter.cs @@ -0,0 +1,32 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x5A, Pack = 0x2)] + struct SpeakerParameter + { + public ushort Version; + public Array34 Reserved; + public ushort SpeakerHpf2A1; + public ushort SpeakerHpf2A2; + public ushort SpeakerHpf2H0; + public ushort SpeakerEqInputVolume; + public ushort SpeakerEqOutputVolume; + public ushort SpeakerEqCtrl1; + public ushort SpeakerEqCtrl2; + public ushort SpeakerDrcAgcCtrl2; + public ushort SpeakerDrcAgcCtrl3; + public ushort SpeakerDrcAgcCtrl1; + public ushort SpeakerAnalogVolume; + public ushort HeadphoneAnalogVolume; + public ushort SpeakerDigitalVolumeMin; + public ushort SpeakerDigitalVolumeMax; + public ushort HeadphoneDigitalVolumeMin; + public ushort HeadphoneDigitalVolumeMax; + public ushort MicFixedGain; + public ushort MicVariableVolumeMin; + public ushort MicVariableVolumeMax; + public Array16 Reserved2; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/SslCertificate.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/SslCertificate.cs new file mode 100644 index 000000000..5d8252164 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/SslCertificate.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x804)] + struct SslCertificate + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/SslKey.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/SslKey.cs new file mode 100644 index 000000000..7d4b41369 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/SslKey.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x138)] + struct SslKey + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Language.cs b/src/Ryujinx.Horizon/Sdk/Settings/Language.cs new file mode 100644 index 000000000..4ffc66fec --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Language.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Horizon.Sdk.Settings +{ + enum Language : uint + { + Japanese, + AmericanEnglish, + French, + German, + Italian, + Spanish, + Chinese, + Korean, + Dutch, + Portuguese, + Russian, + Taiwanese, + BritishEnglish, + CanadianFrench, + LatinAmericanSpanish, + SimplifiedChinese, + TraditionalChinese, + BrazilianPortuguese, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/LanguageCode.cs b/src/Ryujinx.Horizon/Sdk/Settings/LanguageCode.cs new file mode 100644 index 000000000..dc9712692 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/LanguageCode.cs @@ -0,0 +1,63 @@ +using Ryujinx.Common.Memory; +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Horizon.Sdk.Settings +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x1)] + struct LanguageCode + { + private static readonly string[] _languageCodes = new string[] + { + "ja", + "en-US", + "fr", + "de", + "it", + "es", + "zh-CN", + "ko", + "nl", + "pt", + "ru", + "zh-TW", + "en-GB", + "fr-CA", + "es-419", + "zh-Hans", + "zh-Hant", + "pt-BR" + }; + + public Array8 Value; + + public bool IsValid() + { + int length = Value.AsSpan().IndexOf((byte)0); + if (length < 0) + { + return false; + } + + string str = Encoding.ASCII.GetString(Value.AsSpan()[..length]); + + return _languageCodes.AsSpan().Contains(str); + } + + public LanguageCode(Language language) + { + if ((uint)language >= _languageCodes.Length) + { + throw new ArgumentOutOfRangeException(nameof(language)); + } + + Value = new LanguageCode(_languageCodes[(int)language]).Value; + } + + public LanguageCode(string strCode) + { + Encoding.ASCII.GetBytes(strCode, Value.AsSpan()); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/SettingsItemKey.cs b/src/Ryujinx.Horizon/Sdk/Settings/SettingsItemKey.cs new file mode 100644 index 000000000..661184103 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/SettingsItemKey.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings +{ + [StructLayout(LayoutKind.Sequential, Size = 0x48)] + struct SettingsItemKey + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/SettingsName.cs b/src/Ryujinx.Horizon/Sdk/Settings/SettingsName.cs new file mode 100644 index 000000000..6864b8cd6 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/SettingsName.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings +{ + [StructLayout(LayoutKind.Sequential, Size = 0x48)] + struct SettingsName + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/AccountNotificationSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/AccountNotificationSettings.cs new file mode 100644 index 000000000..a2cbad6a6 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/AccountNotificationSettings.cs @@ -0,0 +1,15 @@ +using Ryujinx.Horizon.Sdk.Account; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + struct AccountNotificationSettings + { +#pragma warning disable CS0649 // Field is never assigned to + public Uid UserId; + public uint Flags; + public byte FriendPresenceOverlayPermission; + public byte FriendInvitationOverlayPermission; + public ushort Reserved; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/AccountOnlineStorageSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/AccountOnlineStorageSettings.cs new file mode 100644 index 000000000..3ed77e52b --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/AccountOnlineStorageSettings.cs @@ -0,0 +1,6 @@ +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + struct AccountOnlineStorageSettings + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/AccountSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/AccountSettings.cs new file mode 100644 index 000000000..bd27ea0bf --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/AccountSettings.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x4, Pack = 0x4)] + struct AccountSettings + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/AllowedSslHost.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/AllowedSslHost.cs new file mode 100644 index 000000000..cb90daf18 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/AllowedSslHost.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x100)] + struct AllowedSslHost + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/AnalogStickUserCalibration.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/AnalogStickUserCalibration.cs new file mode 100644 index 000000000..36023da9c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/AnalogStickUserCalibration.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x4)] + struct AnalogStickUserCalibration + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/AppletLaunchFlag.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/AppletLaunchFlag.cs new file mode 100644 index 000000000..00d6f4d06 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/AppletLaunchFlag.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [Flags] + enum AppletLaunchFlag : uint + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/AudioVolume.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/AudioVolume.cs new file mode 100644 index 000000000..d246bc2b9 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/AudioVolume.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x4)] + struct AudioVolume + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettings.cs new file mode 100644 index 000000000..00de6869c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettings.cs @@ -0,0 +1,22 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x28, Pack = 0x4)] + struct BacklightSettings + { + // TODO: Determine field names. + public uint Unknown0x00; + public float Unknown0x04; + // 1st group + public float Unknown0x08; + public float Unknown0x0C; + public float Unknown0x10; + // 2nd group + public float Unknown0x14; + public float Unknown0x18; + public float Unknown0x1C; + public float Unknown0x20; + public float Unknown0x24; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettingsEx.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettingsEx.cs new file mode 100644 index 000000000..347afdfe3 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettingsEx.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x2C, Pack = 0x4)] + struct BacklightSettingsEx + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/BlePairingSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/BlePairingSettings.cs new file mode 100644 index 000000000..d9b01f9ff --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/BlePairingSettings.cs @@ -0,0 +1,6 @@ +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + struct BlePairingSettings + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/BluetoothDevicesSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/BluetoothDevicesSettings.cs new file mode 100644 index 000000000..ec5c97c5a --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/BluetoothDevicesSettings.cs @@ -0,0 +1,29 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + struct BluetoothDevicesSettings + { +#pragma warning disable CS0649 // Field is never assigned to + public Array6 BdAddr; + public Array32 DeviceName; + public Array3 ClassOfDevice; + public Array16 LinkKey; + public bool LinkKeyPresent; + public ushort Version; + public uint TrustedServices; + public ushort Vid; + public ushort Pid; + public byte SubClass; + public byte AttributeMask; + public ushort DescriptorLength; + public Array128 Descriptor; + public byte KeyType; + public byte DeviceType; + public ushort BrrSize; + public Array9 Brr; + public Array256 Reserved; + public Array43 Reserved2; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigRegisteredSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigRegisteredSettings.cs new file mode 100644 index 000000000..8bd4924e9 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigRegisteredSettings.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x5C8)] + struct ButtonConfigRegisteredSettings + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigSettings.cs new file mode 100644 index 000000000..2f06e32e1 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigSettings.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x5A8)] + struct ButtonConfigSettings + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationBias.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationBias.cs new file mode 100644 index 000000000..c70d4ff28 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationBias.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0xC, Pack = 0x4)] + struct ConsoleSixAxisSensorAccelerationBias + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationGain.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationGain.cs new file mode 100644 index 000000000..0803beb87 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationGain.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x24, Pack = 0x4)] + struct ConsoleSixAxisSensorAccelerationGain + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularAcceleration.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularAcceleration.cs new file mode 100644 index 000000000..831e44bd5 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularAcceleration.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x24, Pack = 0x4)] + struct ConsoleSixAxisSensorAngularAcceleration + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityBias.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityBias.cs new file mode 100644 index 000000000..83d1faa8d --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityBias.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0xC, Pack = 0x4)] + struct ConsoleSixAxisSensorAngularVelocityBias + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityGain.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityGain.cs new file mode 100644 index 000000000..68e0c614a --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityGain.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x24, Pack = 0x4)] + struct ConsoleSixAxisSensorAngularVelocityGain + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityTimeBias.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityTimeBias.cs new file mode 100644 index 000000000..47f3d951c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityTimeBias.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0xC, Pack = 0x4)] + struct ConsoleSixAxisSensorAngularVelocityTimeBias + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/DataDeletionSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/DataDeletionSettings.cs new file mode 100644 index 000000000..a10a265d1 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/DataDeletionSettings.cs @@ -0,0 +1,18 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [Flags] + enum DataDeletionFlag : uint + { + AutomaticDeletionFlag = 1 << 0, + } + + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x4)] + struct DataDeletionSettings + { + public DataDeletionFlag Flags; + public uint UseCount; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/DeviceNickName.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/DeviceNickName.cs new file mode 100644 index 000000000..99c9f9817 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/DeviceNickName.cs @@ -0,0 +1,25 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x80)] + struct DeviceNickName + { + public Array128 Value; + + public DeviceNickName(string value) + { + int bytesWritten = Encoding.ASCII.GetBytes(value, Value.AsSpan()); + if (bytesWritten < 128) + { + Value[bytesWritten] = 0; + } + else + { + Value[127] = 0; + } + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/Edid.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/Edid.cs new file mode 100644 index 000000000..3ff566854 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/Edid.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x200)] + struct Edid + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/EulaVersion.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/EulaVersion.cs new file mode 100644 index 000000000..65905b1ba --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/EulaVersion.cs @@ -0,0 +1,6 @@ +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + struct EulaVersion + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/FatalDirtyFlag.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/FatalDirtyFlag.cs new file mode 100644 index 000000000..6be941151 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/FatalDirtyFlag.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)] + struct FatalDirtyFlag + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersion.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersion.cs new file mode 100644 index 000000000..39825e010 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersion.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x100)] + struct FirmwareVersion + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersionDigest.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersionDigest.cs new file mode 100644 index 000000000..0027d7ef1 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersionDigest.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x40, Pack = 0x1)] + struct FirmwareVersionDigest + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/HomeMenuScheme.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/HomeMenuScheme.cs new file mode 100644 index 000000000..cc7b317be --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/HomeMenuScheme.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x14, Pack = 0x1)] + struct HomeMenuScheme + { + public uint Main; + public uint Back; + public uint Sub; + public uint Bezel; + public uint Extra; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/HostFsMountPoint.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/HostFsMountPoint.cs new file mode 100644 index 000000000..1a66abac9 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/HostFsMountPoint.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x100)] + struct HostFsMountPoint + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/InitialLaunchSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/InitialLaunchSettings.cs new file mode 100644 index 000000000..b3989de75 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/InitialLaunchSettings.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 0x8)] + struct InitialLaunchSettings + { + public uint Flags; + public uint Reserved; + public ulong TimeStamp1; + public ulong TimeStamp2; + public ulong TimeStamp3; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/NetworkSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/NetworkSettings.cs new file mode 100644 index 000000000..a0101b626 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/NetworkSettings.cs @@ -0,0 +1,6 @@ +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + struct NetworkSettings + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/NotificationSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/NotificationSettings.cs new file mode 100644 index 000000000..2ce56c4df --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/NotificationSettings.cs @@ -0,0 +1,38 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [Flags] + enum NotificationFlag : uint + { + RingtoneFlag = 1 << 0, + DownloadCompletionFlag = 1 << 1, + EnablesNews = 1 << 8, + IncomingLampFlag = 1 << 9, + } + + enum NotificationVolume : uint + { + Mute, + Low, + High, + } + + struct NotificationTime + { +#pragma warning disable CS0649 // Field is never assigned to + public uint Hour; + public uint Minute; +#pragma warning restore CS0649 + } + + [StructLayout(LayoutKind.Sequential, Size = 0x18, Pack = 0x4)] + struct NotificationSettings + { + public NotificationFlag Flag; + public NotificationVolume Volume; + public NotificationTime HeadTime; + public NotificationTime TailTime; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerLegacySettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerLegacySettings.cs new file mode 100644 index 000000000..845715df2 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerLegacySettings.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x29)] + struct NxControllerLegacySettings + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerSettings.cs new file mode 100644 index 000000000..c8f81cecb --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerSettings.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x42C)] + struct NxControllerSettings + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/PtmFuelGaugeParameter.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/PtmFuelGaugeParameter.cs new file mode 100644 index 000000000..b843bcd64 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/PtmFuelGaugeParameter.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x18, Pack = 0x4)] + struct PtmFuelGaugeParameter + { + public ushort Rcomp0; + public ushort TempCo; + public ushort FullCap; + public ushort FullCapNom; + public ushort IavgEmpty; + public ushort QrTable00; + public ushort QrTable10; + public ushort QrTable20; + public ushort QrTable30; + public ushort Reserved; + public uint Cycles; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/RebootlessSystemUpdateVersion.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/RebootlessSystemUpdateVersion.cs new file mode 100644 index 000000000..b4e9b8b28 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/RebootlessSystemUpdateVersion.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x40, Pack = 0x4)] + struct RebootlessSystemUpdateVersion + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/SerialNumber.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/SerialNumber.cs new file mode 100644 index 000000000..22ddb85ce --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/SerialNumber.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x18, Pack = 0x1)] + struct SerialNumber + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ServiceDiscoveryControlSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ServiceDiscoveryControlSettings.cs new file mode 100644 index 000000000..7c7b625a2 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ServiceDiscoveryControlSettings.cs @@ -0,0 +1,10 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [Flags] + enum ServiceDiscoveryControlSettings : uint + { + IsChangeEnvironmentIdentifierDisabled = 1 << 0, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/SleepSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/SleepSettings.cs new file mode 100644 index 000000000..7493c677c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/SleepSettings.cs @@ -0,0 +1,40 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [Flags] + enum SleepFlag : uint + { + SleepsWhilePlayingMedia = 1 << 0, + WakesAtPowerStateChange = 1 << 1, + } + + enum HandheldSleepPlan : uint + { + At1Min, + At3Min, + At5Min, + At10Min, + At30Min, + Never, + } + + enum ConsoleSleepPlan : uint + { + At1Hour, + At2Hour, + At3Hour, + At6Hour, + At12Hour, + Never, + } + + [StructLayout(LayoutKind.Sequential, Size = 0xC, Pack = 0x4)] + struct SleepSettings + { + public SleepFlag Flags; + public HandheldSleepPlan HandheldSleepPlan; + public ConsoleSleepPlan ConsoleSleepPlan; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/TelemetryDirtyFlag.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/TelemetryDirtyFlag.cs new file mode 100644 index 000000000..46ec2d767 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/TelemetryDirtyFlag.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)] + struct TelemetryDirtyFlag + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ThemeId.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ThemeId.cs new file mode 100644 index 000000000..886ec8721 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ThemeId.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x80, Pack = 0x8)] + struct ThemeId + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ThemeSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ThemeSettings.cs new file mode 100644 index 000000000..ac36bcd80 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ThemeSettings.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x8)] + struct ThemeSettings + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/TvSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/TvSettings.cs new file mode 100644 index 000000000..5ee0b85d9 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/TvSettings.cs @@ -0,0 +1,59 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [Flags] + enum TvFlag : uint + { + Allows4k = 1 << 0, + Allows3d = 1 << 1, + AllowsCec = 1 << 2, + PreventsScreenBurnIn = 1 << 3, + } + + enum TvResolution : uint + { + Auto, + At1080p, + At720p, + At480p, + } + + enum HdmiContentType : uint + { + None, + Graphics, + Cinema, + Photo, + Game, + } + + enum RgbRange : uint + { + Auto, + Full, + Limited, + } + + enum CmuMode : uint + { + None, + ColorInvert, + HighContrast, + GrayScale, + } + + [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 0x4)] + struct TvSettings + { + public TvFlag Flags; + public TvResolution TvResolution; + public HdmiContentType HdmiContentType; + public RgbRange RgbRange; + public CmuMode CmuMode; + public float TvUnderscan; + public float TvGamma; + public float ContrastRatio; + } +} diff --git a/src/Ryujinx.Horizon/ServiceTable.cs b/src/Ryujinx.Horizon/ServiceTable.cs index ee62ee84d..f3fe51940 100644 --- a/src/Ryujinx.Horizon/ServiceTable.cs +++ b/src/Ryujinx.Horizon/ServiceTable.cs @@ -1,5 +1,6 @@ using Ryujinx.Horizon.Arp; using Ryujinx.Horizon.Bcat; +using Ryujinx.Horizon.Friends; using Ryujinx.Horizon.Hshl; using Ryujinx.Horizon.Ins; using Ryujinx.Horizon.Lbl; @@ -39,6 +40,7 @@ void RegisterService() where T : IService RegisterService(); RegisterService(); + RegisterService(); RegisterService(); RegisterService(); RegisterService(); From 4e81ab4229c0c979dbc52f8b6b39328f2428c265 Mon Sep 17 00:00:00 2001 From: TSRBerry <20988865+TSRBerry@users.noreply.github.com> Date: Mon, 29 Jan 2024 22:57:20 +0100 Subject: [PATCH 025/126] Avalonia: Fix dialog issues caused by 1.1.1105 (#6211) * Set _contentDialogOverlayWindow to null * Make CheckLaunchState async --- .../UI/Helpers/ContentDialogHelper.cs | 1 + .../UI/Windows/MainWindow.axaml.cs | 24 ++++++------------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs b/src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs index a57deb1a0..0863cbaa4 100644 --- a/src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs +++ b/src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs @@ -388,6 +388,7 @@ async Task ShowDialog() { _contentDialogOverlayWindow.Content = null; _contentDialogOverlayWindow.Close(); + _contentDialogOverlayWindow = null; } return result; diff --git a/src/Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs index 1aecbd041..ae26b6512 100644 --- a/src/Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs +++ b/src/Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs @@ -263,7 +263,7 @@ private static async Task ShowVmMaxMapCountDialog() } } - private void CheckLaunchState() + private async Task CheckLaunchState() { if (OperatingSystem.IsLinux() && LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount) { @@ -271,23 +271,11 @@ private void CheckLaunchState() if (LinuxHelper.PkExecPath is not null) { - Dispatcher.UIThread.Post(async () => - { - if (OperatingSystem.IsLinux()) - { - await ShowVmMaxMapCountDialog(); - } - }); + await Dispatcher.UIThread.InvokeAsync(ShowVmMaxMapCountDialog); } else { - Dispatcher.UIThread.Post(async () => - { - if (OperatingSystem.IsLinux()) - { - await ShowVmMaxMapCountWarning(); - } - }); + await Dispatcher.UIThread.InvokeAsync(ShowVmMaxMapCountWarning); } } @@ -304,12 +292,12 @@ private void CheckLaunchState() { ShowKeyErrorOnLoad = false; - Dispatcher.UIThread.Post(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys)); + await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys)); } if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false)) { - Updater.BeginParse(this, false).ContinueWith(task => + await Updater.BeginParse(this, false).ContinueWith(task => { Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}"); }, TaskContinuationOptions.OnlyOnFaulted); @@ -404,7 +392,9 @@ protected override void OnOpened(EventArgs e) LoadApplications(); +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed CheckLaunchState(); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed } private void SetMainContent(Control content = null) From a8fbcdae9f505577685a4afed464a58b568e8cd9 Mon Sep 17 00:00:00 2001 From: merry Date: Mon, 29 Jan 2024 22:08:24 +0000 Subject: [PATCH 026/126] UI: Clarify Create Application Shortcut tooltip text (#6217) --- src/Ryujinx.Ava/Assets/Locales/en_US.json | 1 + src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml | 2 +- src/Ryujinx/Ui/Widgets/GameTableContextMenu.Designer.cs | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.Ava/Assets/Locales/en_US.json b/src/Ryujinx.Ava/Assets/Locales/en_US.json index 8b38490f7..6ac9ae948 100644 --- a/src/Ryujinx.Ava/Assets/Locales/en_US.json +++ b/src/Ryujinx.Ava/Assets/Locales/en_US.json @@ -72,6 +72,7 @@ "GameListContextMenuExtractDataLogoToolTip": "Extract the Logo section from Application's current config (including updates)", "GameListContextMenuCreateShortcut": "Create Application Shortcut", "GameListContextMenuCreateShortcutToolTip": "Create a Desktop Shortcut that launches the selected Application", + "GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application", "StatusBarGamesLoaded": "{0}/{1} Games Loaded", "StatusBarSystemVersion": "System Version: {0}", "LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected", diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml b/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml index 7f786cf38..5bdeb8ad3 100644 --- a/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml +++ b/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml @@ -16,7 +16,7 @@ Click="CreateApplicationShortcut_Click" Header="{locale:Locale GameListContextMenuCreateShortcut}" IsEnabled="{Binding CreateShortcutEnabled}" - ToolTip.Tip="{locale:Locale GameListContextMenuCreateShortcutToolTip}" /> + ToolTip.Tip="{OnPlatform Default={locale:Locale GameListContextMenuCreateShortcutToolTip}, macOS={locale:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" /> Date: Mon, 29 Jan 2024 22:14:19 +0000 Subject: [PATCH 027/126] Ava UI: Mod Manager Fixes (Again) (#6187) * Fix typo + Fix deleting from old dir * Avoid double enumeration * Break when parentDir is found * Fix deleting non subdirectory mods * Typo --- src/Ryujinx.Ava/UI/Models/ModModel.cs | 4 +- .../UI/ViewModels/ModManagerViewModel.cs | 54 ++++++++++++------- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/Ryujinx.Ava/UI/Models/ModModel.cs b/src/Ryujinx.Ava/UI/Models/ModModel.cs index f68e15939..ee28ca5f5 100644 --- a/src/Ryujinx.Ava/UI/Models/ModModel.cs +++ b/src/Ryujinx.Ava/UI/Models/ModModel.cs @@ -17,14 +17,16 @@ public bool Enabled } } + public bool InSd { get; } public string Path { get; } public string Name { get; } - public ModModel(string path, string name, bool enabled) + public ModModel(string path, string name, bool enabled, bool inSd) { Path = path; Name = name; Enabled = enabled; + InSd = inSd; } } } diff --git a/src/Ryujinx.Ava/UI/ViewModels/ModManagerViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/ModManagerViewModel.cs index 2c29660bf..8321bf894 100644 --- a/src/Ryujinx.Ava/UI/ViewModels/ModManagerViewModel.cs +++ b/src/Ryujinx.Ava/UI/ViewModels/ModManagerViewModel.cs @@ -102,13 +102,14 @@ private void LoadMods(ulong applicationId) foreach (var path in modsBasePaths) { + var inSd = path == ModLoader.GetSdModsBasePath(); var modCache = new ModLoader.ModCache(); ModLoader.QueryContentsDir(modCache, new DirectoryInfo(Path.Combine(path, "contents")), applicationId); foreach (var mod in modCache.RomfsDirs) { - var modModel = new ModModel(mod.Path.Parent.FullName, mod.Name, mod.Enabled); + var modModel = new ModModel(mod.Path.Parent.FullName, mod.Name, mod.Enabled, inSd); if (Mods.All(x => x.Path != mod.Path.Parent.FullName)) { Mods.Add(modModel); @@ -117,12 +118,12 @@ private void LoadMods(ulong applicationId) foreach (var mod in modCache.RomfsContainers) { - Mods.Add(new ModModel(mod.Path.FullName, mod.Name, mod.Enabled)); + Mods.Add(new ModModel(mod.Path.FullName, mod.Name, mod.Enabled, inSd)); } foreach (var mod in modCache.ExefsDirs) { - var modModel = new ModModel(mod.Path.Parent.FullName, mod.Name, mod.Enabled); + var modModel = new ModModel(mod.Path.Parent.FullName, mod.Name, mod.Enabled, inSd); if (Mods.All(x => x.Path != mod.Path.Parent.FullName)) { Mods.Add(modModel); @@ -131,7 +132,7 @@ private void LoadMods(ulong applicationId) foreach (var mod in modCache.ExefsContainers) { - Mods.Add(new ModModel(mod.Path.FullName, mod.Name, mod.Enabled)); + Mods.Add(new ModModel(mod.Path.FullName, mod.Name, mod.Enabled, inSd)); } } @@ -183,30 +184,43 @@ public void Save() public void Delete(ModModel model) { - var modsDir = ModLoader.GetApplicationDir(ModLoader.GetSdModsBasePath(), _applicationId.ToString("x16")); - var parentDir = String.Empty; + var isSubdir = true; + var pathToDelete = model.Path; + var basePath = model.InSd ? ModLoader.GetSdModsBasePath() : ModLoader.GetModsBasePath(); + var modsDir = ModLoader.GetApplicationDir(basePath, _applicationId.ToString("x16")); - foreach (var dir in Directory.GetDirectories(modsDir, "*", SearchOption.TopDirectoryOnly)) + if (new DirectoryInfo(model.Path).Parent?.FullName == modsDir) { - if (Directory.GetDirectories(dir, "*", SearchOption.AllDirectories).Contains(model.Path)) - { - parentDir = dir; - } + isSubdir = false; } - if (parentDir == String.Empty) + if (isSubdir) { - Dispatcher.UIThread.Post(async () => + var parentDir = String.Empty; + + foreach (var dir in Directory.GetDirectories(modsDir, "*", SearchOption.TopDirectoryOnly)) { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue( - LocaleKeys.DialogModDeleteNoParentMessage, - parentDir)); - }); - return; + if (Directory.GetDirectories(dir, "*", SearchOption.AllDirectories).Contains(model.Path)) + { + parentDir = dir; + break; + } + } + + if (parentDir == String.Empty) + { + Dispatcher.UIThread.Post(async () => + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue( + LocaleKeys.DialogModDeleteNoParentMessage, + model.Path)); + }); + return; + } } - Logger.Info?.Print(LogClass.Application, $"Deleting mod at \"{model.Path}\""); - Directory.Delete(parentDir, true); + Logger.Info?.Print(LogClass.Application, $"Deleting mod at \"{pathToDelete}\""); + Directory.Delete(pathToDelete, true); Mods.Remove(model); OnPropertyChanged(nameof(ModCount)); From 2adf0318300a0f444c8051aceba1e4759fbedc6f Mon Sep 17 00:00:00 2001 From: Ac_K Date: Mon, 29 Jan 2024 23:22:42 +0100 Subject: [PATCH 028/126] deps: Update Avalonia.Svg (#6218) Updates `Avalonia.Svg` from 11.0.0.10 to 11.0.0.13 Updates `Avalonia.Svg.Skia` from 11.0.0.10 to 11.0.0.13 And reflect update changes as dependabot can't do it. Superseed #6209 --- Directory.Packages.props | 4 ++-- src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs | 3 ++- src/Ryujinx.Ava/UI/ViewModels/ControllerInputViewModel.cs | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index f2c080d86..2f272459c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -8,8 +8,8 @@ - - + + diff --git a/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs b/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs index 34de5223b..f7d751a66 100644 --- a/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs +++ b/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs @@ -9,6 +9,7 @@ using Ryujinx.Common; using Ryujinx.HLE.HOS.Applets; using Ryujinx.HLE.HOS.Services.Hid; +using System; using System.Linq; using System.Threading.Tasks; @@ -103,7 +104,7 @@ private static SvgImage GetResource(string path) if (!string.IsNullOrWhiteSpace(path)) { - SvgSource source = new(); + SvgSource source = new(default(Uri)); source.Load(EmbeddedResources.GetStream(path)); diff --git a/src/Ryujinx.Ava/UI/ViewModels/ControllerInputViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/ControllerInputViewModel.cs index c0c625321..042803f36 100644 --- a/src/Ryujinx.Ava/UI/ViewModels/ControllerInputViewModel.cs +++ b/src/Ryujinx.Ava/UI/ViewModels/ControllerInputViewModel.cs @@ -180,7 +180,7 @@ public SvgImage Image if (!string.IsNullOrWhiteSpace(_controllerImage)) { - SvgSource source = new(); + SvgSource source = new(default(Uri)); source.Load(EmbeddedResources.GetStream(_controllerImage)); From 8bf102d2cd744f56e2a4839fa0391acda3e201b8 Mon Sep 17 00:00:00 2001 From: Ac_K Date: Tue, 30 Jan 2024 00:51:05 +0100 Subject: [PATCH 029/126] Cpu: Implement Vpadal and Vrintr instructions (#6185) * Cpu: Implement Vpadal and Vrintr instructions This PR superseed last instructions left in #2242. Since I'm not a CPU guy I've just ported the code and nothing more. Please be precise during review if there are some changes to be done. It should fixes #1781 Co-Authored-By: Piyachet Kanda * Addresses gdkchan's feedback * Addresses gdkchan's feedback 2 * Apply suggestions from code review Co-authored-by: gdkchan * another fix * Update InstEmitSimdHelper32.cs * Correct fix * Addresses gdkchan's feedback * Update CpuTestSimdCvt32.cs --------- Co-authored-by: Piyachet Kanda Co-authored-by: gdkchan --- src/ARMeilleure/Decoders/OpCodeTable.cs | 2 + .../Instructions/InstEmitSimdArithmetic32.cs | 7 ++++ .../Instructions/InstEmitSimdCvt32.cs | 16 ++++++++ .../Instructions/InstEmitSimdHelper32.cs | 29 ++++++++++++++ src/ARMeilleure/Instructions/InstName.cs | 2 + src/Ryujinx.Tests/Cpu/CpuTestSimdCvt32.cs | 39 +++++++++++++++++++ src/Ryujinx.Tests/Cpu/CpuTestSimdReg32.cs | 38 ++++++++++++++++++ 7 files changed, 133 insertions(+) diff --git a/src/ARMeilleure/Decoders/OpCodeTable.cs b/src/ARMeilleure/Decoders/OpCodeTable.cs index 528cef1b6..edc004125 100644 --- a/src/ARMeilleure/Decoders/OpCodeTable.cs +++ b/src/ARMeilleure/Decoders/OpCodeTable.cs @@ -875,6 +875,7 @@ static OpCodeTable() SetVfp("<<<<11100x10xxxxxxxx101xx1x0xxxx", InstName.Vnmul, InstEmit32.Vnmul_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); SetVfp("111111101x1110xxxxxx101x01x0xxxx", InstName.Vrint, InstEmit32.Vrint_RM, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); SetVfp("<<<<11101x110110xxxx101x11x0xxxx", InstName.Vrint, InstEmit32.Vrint_Z, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11101x110110xxxx101x01x0xxxx", InstName.Vrintr, InstEmit32.Vrintr_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); SetVfp("<<<<11101x110111xxxx101x01x0xxxx", InstName.Vrintx, InstEmit32.Vrintx_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); SetVfp("<<<<11101x110001xxxx101x11x0xxxx", InstName.Vsqrt, InstEmit32.Vsqrt_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); SetVfp("111111100xxxxxxxxxxx101xx0x0xxxx", InstName.Vsel, InstEmit32.Vsel, OpCode32SimdSel.Create, OpCode32SimdSel.CreateT32); @@ -995,6 +996,7 @@ static OpCodeTable() SetAsimd("1111001x1x000xxxxxxx< context.Add(context.Add(op1, op2), op3), op.Opc != 1); + } + public static void Vpaddl(ArmEmitterContext context) { OpCode32Simd op = (OpCode32Simd)context.CurrOp; diff --git a/src/ARMeilleure/Instructions/InstEmitSimdCvt32.cs b/src/ARMeilleure/Instructions/InstEmitSimdCvt32.cs index 630e114c4..8eef6b14d 100644 --- a/src/ARMeilleure/Instructions/InstEmitSimdCvt32.cs +++ b/src/ARMeilleure/Instructions/InstEmitSimdCvt32.cs @@ -578,6 +578,22 @@ public static void Vrintp_V(ArmEmitterContext context) } } + // VRINTR (floating-point). + public static void Vrintr_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FrintiS); + } + else + { + EmitScalarUnaryOpF32(context, (op1) => + { + return EmitRoundByRMode(context, op1); + }); + } + } + // VRINTZ (floating-point). public static void Vrint_Z(ArmEmitterContext context) { diff --git a/src/ARMeilleure/Instructions/InstEmitSimdHelper32.cs b/src/ARMeilleure/Instructions/InstEmitSimdHelper32.cs index c1c59b87b..2f021a1a1 100644 --- a/src/ARMeilleure/Instructions/InstEmitSimdHelper32.cs +++ b/src/ARMeilleure/Instructions/InstEmitSimdHelper32.cs @@ -673,6 +673,35 @@ public static void EmitVectorPairwiseLongOpI32(ArmEmitterContext context, Func2I context.Copy(GetVecA32(op.Qd), res); } + public static void EmitVectorPairwiseTernaryLongOpI32(ArmEmitterContext context, Func3I emit, bool signed) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + int elems = op.GetBytesCount() >> op.Size; + int pairs = elems >> 1; + + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index * 2; + Operand m1 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex, op.Size, signed); + Operand m2 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex + 1, op.Size, signed); + + if (op.Size == 2) + { + m1 = signed ? context.SignExtend32(OperandType.I64, m1) : context.ZeroExtend32(OperandType.I64, m1); + m2 = signed ? context.SignExtend32(OperandType.I64, m2) : context.ZeroExtend32(OperandType.I64, m2); + } + + Operand d1 = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size + 1, signed); + + res = EmitVectorInsert(context, res, emit(m1, m2, d1), op.Id + index, op.Size + 1); + } + + context.Copy(GetVecA32(op.Qd), res); + } + // Narrow public static void EmitVectorUnaryNarrowOp32(ArmEmitterContext context, Func1I emit, bool signed = false) diff --git a/src/ARMeilleure/Instructions/InstName.cs b/src/ARMeilleure/Instructions/InstName.cs index 6723a42e4..457abbf49 100644 --- a/src/ARMeilleure/Instructions/InstName.cs +++ b/src/ARMeilleure/Instructions/InstName.cs @@ -637,6 +637,7 @@ enum InstName Vorn, Vorr, Vpadd, + Vpadal, Vpaddl, Vpmax, Vpmin, @@ -656,6 +657,7 @@ enum InstName Vrintm, Vrintn, Vrintp, + Vrintr, Vrintx, Vrshr, Vrshrn, diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdCvt32.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdCvt32.cs index 5b24432bb..ba201a480 100644 --- a/src/Ryujinx.Tests/Cpu/CpuTestSimdCvt32.cs +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdCvt32.cs @@ -511,6 +511,45 @@ public void Vcvt_V_Fixed_I32_F32([Values(0u, 1u, 2u, 3u)] uint vd, CompareAgainstUnicorn(); } + + [Test, Pairwise, Description("VRINTR.F , ")] + [Platform(Exclude = "Linux,MacOsX")] // Instruction isn't testable due to Unicorn. + public void Vrintr([Values(0u, 1u)] uint rd, + [Values(0u, 1u)] uint rm, + [Values(2u, 3u)] uint size, + [ValueSource(nameof(_1D_F_))] ulong s0, + [ValueSource(nameof(_1D_F_))] ulong s1, + [ValueSource(nameof(_1D_F_))] ulong s2, + [Values(RMode.Rn, RMode.Rm, RMode.Rp)] RMode rMode) + { + uint opcode = 0xEEB60A40; + + V128 v0, v1, v2; + + if (size == 2) + { + opcode |= ((rm & 0x1e) >> 1) | ((rm & 0x1) << 5); + opcode |= ((rd & 0x1e) << 11) | ((rd & 0x1) << 22); + v0 = MakeVectorE0E1((uint)BitConverter.SingleToInt32Bits(s0), (uint)BitConverter.SingleToInt32Bits(s0)); + v1 = MakeVectorE0E1((uint)BitConverter.SingleToInt32Bits(s1), (uint)BitConverter.SingleToInt32Bits(s0)); + v2 = MakeVectorE0E1((uint)BitConverter.SingleToInt32Bits(s2), (uint)BitConverter.SingleToInt32Bits(s1)); + } + else + { + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + v0 = MakeVectorE0E1((uint)BitConverter.DoubleToInt64Bits(s0), (uint)BitConverter.DoubleToInt64Bits(s0)); + v1 = MakeVectorE0E1((uint)BitConverter.DoubleToInt64Bits(s1), (uint)BitConverter.DoubleToInt64Bits(s0)); + v2 = MakeVectorE0E1((uint)BitConverter.DoubleToInt64Bits(s2), (uint)BitConverter.DoubleToInt64Bits(s1)); + } + + opcode |= ((size & 3) << 8); + + int fpscr = (int)rMode << (int)Fpcr.RMode; + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2, fpscr: fpscr); + + CompareAgainstUnicorn(); + } #endif } } diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdReg32.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdReg32.cs index 9d9606bba..38e08bf89 100644 --- a/src/Ryujinx.Tests/Cpu/CpuTestSimdReg32.cs +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdReg32.cs @@ -908,6 +908,44 @@ public void Vqdmulh_I([Range(0u, 5u)] uint rd, CompareAgainstUnicorn(); } + + [Test, Pairwise] + public void Vp_Add_Long_Accumulate([Values(0u, 2u, 4u, 8u)] uint rd, + [Values(0u, 2u, 4u, 8u)] uint rm, + [Values(0u, 1u, 2u)] uint size, + [Random(RndCnt)] ulong z, + [Random(RndCnt)] ulong a, + [Random(RndCnt)] ulong b, + [Values] bool q, + [Values] bool unsigned) + { + uint opcode = 0xF3B00600; // VPADAL.S8 D0, Q0 + + if (q) + { + opcode |= 1 << 6; + rm <<= 1; + rd <<= 1; + } + + if (unsigned) + { + opcode |= 1 << 7; + } + + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + + opcode |= size << 18; + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, z); + V128 v2 = MakeVectorE0E1(b, z); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } #endif } } From ccbbaddbcb6a0eb0cee0d2e2482546611237bee3 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Mon, 29 Jan 2024 21:19:39 -0300 Subject: [PATCH 030/126] Fix exception when trying to read output pointer buffer size (#6221) * Fix exception when trying to read output pointer buffer size * Better name --- src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs | 3 +++ src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs | 1 + src/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs index 887c82eb8..73321a891 100644 --- a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs @@ -181,6 +181,7 @@ private static HipcMessageData CreateMessageData(HipcMetadata meta, Span d } Span dataWords = Span.Empty; + Span dataWordsPadded = Span.Empty; if (meta.DataWordsCount != 0) { @@ -189,6 +190,7 @@ private static HipcMessageData CreateMessageData(HipcMetadata meta, Span d int padding = (dataOffsetAligned - dataOffset) / sizeof(uint); dataWords = MemoryMarshal.Cast(data)[padding..meta.DataWordsCount]; + dataWordsPadded = MemoryMarshal.Cast(data)[..meta.DataWordsCount]; data = data[(meta.DataWordsCount * sizeof(uint))..]; } @@ -209,6 +211,7 @@ private static HipcMessageData CreateMessageData(HipcMetadata meta, Span d ReceiveBuffers = receiveBuffers, ExchangeBuffers = exchangeBuffers, DataWords = dataWords, + DataWordsPadded = dataWordsPadded, ReceiveList = receiveList, CopyHandles = copyHandles, MoveHandles = moveHandles, diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs index 548f12e8b..0d45d756f 100644 --- a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs @@ -9,6 +9,7 @@ ref struct HipcMessageData public Span ReceiveBuffers; public Span ExchangeBuffers; public Span DataWords; + public Span DataWordsPadded; public Span ReceiveList; public Span CopyHandles; public Span MoveHandles; diff --git a/src/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs b/src/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs index bb9b37e28..f7694a74d 100644 --- a/src/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs +++ b/src/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs @@ -206,7 +206,7 @@ public Result ProcessBuffers(ref ServiceDispatchContext context, bool[] isBuffer } else { - var data = MemoryMarshal.Cast(context.Request.Data.DataWords); + var data = MemoryMarshal.Cast(context.Request.Data.DataWordsPadded); var recvPointerSizes = MemoryMarshal.Cast(data[runtimeMetadata.UnfixedOutPointerSizeOffset..]); size = recvPointerSizes[unfixedRecvPointerIndex++]; From 4505a7f162b2e7920d64ddfe2a70c4da6d8f9118 Mon Sep 17 00:00:00 2001 From: TSRBerry <20988865+TSRBerry@users.noreply.github.com> Date: Tue, 30 Jan 2024 17:52:45 +0100 Subject: [PATCH 031/126] Fix opening the wrong log directory (#6220) --- src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs | 4 ++-- src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs | 2 +- src/Ryujinx.Headless.SDL2/Program.cs | 4 ++-- src/Ryujinx.Ui.Common/Configuration/LoggerModule.cs | 11 +++++++++-- src/Ryujinx/Ui/MainWindow.cs | 4 ++-- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs index dff5b59bd..2caee16cd 100644 --- a/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs @@ -1352,9 +1352,9 @@ public void OpenLogsFolder() { string logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs"); - if (ReleaseInformation.IsValid) + if (LoggerModule.LogDirectoryPath != null) { - logPath = Path.Combine(AppDataManager.BaseDirPath, "Logs"); + logPath = LoggerModule.LogDirectoryPath; } new DirectoryInfo(logPath).Create(); diff --git a/src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs b/src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs index c40c3abec..a4e8f7147 100644 --- a/src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs +++ b/src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs @@ -23,7 +23,7 @@ public FileLogTarget(string name, FileStream fileStream) public static FileStream PrepareLogFile(string path) { // Ensure directory is present - DirectoryInfo logDir = new(Path.Combine(path, "Logs")); + DirectoryInfo logDir = new(path); try { logDir.Create(); diff --git a/src/Ryujinx.Headless.SDL2/Program.cs b/src/Ryujinx.Headless.SDL2/Program.cs index 6eaa1b860..c23002757 100644 --- a/src/Ryujinx.Headless.SDL2/Program.cs +++ b/src/Ryujinx.Headless.SDL2/Program.cs @@ -427,11 +427,11 @@ static void LoadPlayerConfiguration(string inputProfileName, string inputId, Pla if (!option.DisableFileLog) { - FileStream logFile = FileLogTarget.PrepareLogFile(AppDomain.CurrentDomain.BaseDirectory); + FileStream logFile = FileLogTarget.PrepareLogFile(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs")); if (logFile == null) { - logFile = FileLogTarget.PrepareLogFile(AppDataManager.BaseDirPath); + logFile = FileLogTarget.PrepareLogFile(Path.Combine(AppDataManager.BaseDirPath, "Logs")); if (logFile == null) { diff --git a/src/Ryujinx.Ui.Common/Configuration/LoggerModule.cs b/src/Ryujinx.Ui.Common/Configuration/LoggerModule.cs index 6cd63272e..f22ee83ae 100644 --- a/src/Ryujinx.Ui.Common/Configuration/LoggerModule.cs +++ b/src/Ryujinx.Ui.Common/Configuration/LoggerModule.cs @@ -9,6 +9,8 @@ namespace Ryujinx.Ui.Common.Configuration { public static class LoggerModule { + public static string LogDirectoryPath { get; private set; } + public static void Initialize() { ConfigurationState.Instance.Logger.EnableDebug.Event += ReloadEnableDebug; @@ -82,21 +84,26 @@ private static void ReloadFileLogger(object sender, ReactiveEventArgs e) { if (e.NewValue) { - FileStream logFile = FileLogTarget.PrepareLogFile(AppDomain.CurrentDomain.BaseDirectory); + string logDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs"); + FileStream logFile = FileLogTarget.PrepareLogFile(logDir); if (logFile == null) { - logFile = FileLogTarget.PrepareLogFile(AppDataManager.BaseDirPath); + logDir = Path.Combine(AppDataManager.BaseDirPath, "Logs"); + logFile = FileLogTarget.PrepareLogFile(logDir); if (logFile == null) { Logger.Error?.Print(LogClass.Application, "No writable log directory available. Make sure either the application directory or the Ryujinx directory is writable."); + LogDirectoryPath = null; Logger.RemoveTarget("file"); return; } } + LogDirectoryPath = logDir; + Logger.AddTarget(new AsyncLogTargetWrapper( new FileLogTarget("file", logFile), 1000, diff --git a/src/Ryujinx/Ui/MainWindow.cs b/src/Ryujinx/Ui/MainWindow.cs index 3cd2b0eb6..1ecbb9ea0 100644 --- a/src/Ryujinx/Ui/MainWindow.cs +++ b/src/Ryujinx/Ui/MainWindow.cs @@ -1378,9 +1378,9 @@ private void OpenLogsFolder_Pressed(object sender, EventArgs args) { string logPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs"); - if (ReleaseInformation.IsValid) + if (LoggerModule.LogDirectoryPath != null) { - logPath = System.IO.Path.Combine(AppDataManager.BaseDirPath, "Logs"); + logPath = LoggerModule.LogDirectoryPath; } new DirectoryInfo(logPath).Create(); From d1b30fbe08d79ad81167358779d77cf4e7167386 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Jan 2024 23:46:48 +0100 Subject: [PATCH 032/126] nuget: bump Microsoft.IdentityModel.JsonWebTokens from 7.2.0 to 7.3.0 (#6227) Bumps [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 7.2.0 to 7.3.0. - [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/dev/CHANGELOG.md) - [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/7.2.0...7.3.0) --- updated-dependencies: - dependency-name: Microsoft.IdentityModel.JsonWebTokens dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 2f272459c..b1cebda96 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -21,7 +21,7 @@ - + From c94f0fbb8307873f68df982c100d3fb01aa6ccf5 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Wed, 31 Jan 2024 22:49:50 +0000 Subject: [PATCH 033/126] Vulkan: Add Render Pass / Framebuffer Cache (#6182) * Vulkan: Add Render Pass / Framebuffer Cache Cache is owned by each texture view. - Window's way of getting framebuffer cache for swapchain images is really messy - it creates a TextureView out of just a vk image view, with invalid info and no storage. * Clear up limited use of alternate TextureView constructor * Formatting and messages * More formatting and messages I apologize for `_colorsCanonical[index]?.Storage?.InsertReadToWriteBarrier`, the compiler made me do it * Self review, change GetFramebuffer to GetPassAndFramebuffer * Avoid allocations on Remove for HashTableSlim * Member can be readonly * Generate texture create info for swapchain images * Improve hashcode * Remove format, samples, size and isDepthStencil when possible Tested in a number of games, seems fine. * Removed load op barriers These can be introduced later. * Reintroduce UpdateModifications Technically meant to be replaced by load op stuff. --- .../Effects/SmaaPostProcessingEffect.cs | 2 +- src/Ryujinx.Graphics.Vulkan/FormatTable.cs | 14 ++ .../FramebufferParams.cs | 107 ++++++++--- src/Ryujinx.Graphics.Vulkan/HashTableSlim.cs | 87 ++++++--- src/Ryujinx.Graphics.Vulkan/HelperShader.cs | 92 +++------ src/Ryujinx.Graphics.Vulkan/PipelineBase.cs | 98 ++-------- src/Ryujinx.Graphics.Vulkan/PipelineFull.cs | 5 +- .../PipelineHelperShader.cs | 13 +- .../RenderPassCacheKey.cs | 43 +++++ .../RenderPassHolder.cs | 180 ++++++++++++++++++ src/Ryujinx.Graphics.Vulkan/TextureView.cs | 67 ++++++- src/Ryujinx.Graphics.Vulkan/Window.cs | 35 ++-- 12 files changed, 512 insertions(+), 231 deletions(-) create mode 100644 src/Ryujinx.Graphics.Vulkan/RenderPassCacheKey.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs b/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs index 802b73b86..be392fe0e 100644 --- a/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs +++ b/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs @@ -257,7 +257,7 @@ private void Clear(TextureView texture) scissors[0] = new Rectangle(0, 0, texture.Width, texture.Height); - _pipeline.SetRenderTarget(texture.GetImageViewForAttachment(), (uint)texture.Width, (uint)texture.Height, false, texture.VkFormat); + _pipeline.SetRenderTarget(texture, (uint)texture.Width, (uint)texture.Height); _pipeline.SetRenderTargetColorMasks(colorMasks); _pipeline.SetScissors(scissors); _pipeline.ClearRenderTargetColor(0, 0, 1, new ColorF(0f, 0f, 0f, 1f)); diff --git a/src/Ryujinx.Graphics.Vulkan/FormatTable.cs b/src/Ryujinx.Graphics.Vulkan/FormatTable.cs index 5f767df16..a12e3efd0 100644 --- a/src/Ryujinx.Graphics.Vulkan/FormatTable.cs +++ b/src/Ryujinx.Graphics.Vulkan/FormatTable.cs @@ -1,5 +1,6 @@ using Ryujinx.Graphics.GAL; using System; +using System.Collections.Generic; using VkFormat = Silk.NET.Vulkan.Format; namespace Ryujinx.Graphics.Vulkan @@ -7,10 +8,12 @@ namespace Ryujinx.Graphics.Vulkan static class FormatTable { private static readonly VkFormat[] _table; + private static readonly Dictionary _reverseMap; static FormatTable() { _table = new VkFormat[Enum.GetNames(typeof(Format)).Length]; + _reverseMap = new Dictionary(); #pragma warning disable IDE0055 // Disable formatting Add(Format.R8Unorm, VkFormat.R8Unorm); @@ -164,6 +167,7 @@ static FormatTable() private static void Add(Format format, VkFormat vkFormat) { _table[(int)format] = vkFormat; + _reverseMap[vkFormat] = format; } public static VkFormat GetFormat(Format format) @@ -171,6 +175,16 @@ public static VkFormat GetFormat(Format format) return _table[(int)format]; } + public static Format GetFormat(VkFormat format) + { + if (!_reverseMap.TryGetValue(format, out Format result)) + { + return Format.B8G8R8A8Unorm; + } + + return result; + } + public static Format ConvertRgba8SrgbToUnorm(Format format) { return format switch diff --git a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs index 458a16464..af22f2656 100644 --- a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs +++ b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs @@ -12,6 +12,8 @@ class FramebufferParams private readonly Auto[] _attachments; private readonly TextureView[] _colors; private readonly TextureView _depthStencil; + private readonly TextureView[] _colorsCanonical; + private readonly TextureView _baseAttachment; private readonly uint _validColorAttachments; public uint Width { get; } @@ -28,25 +30,31 @@ class FramebufferParams public bool HasDepthStencil { get; } public int ColorAttachmentsCount => AttachmentsCount - (HasDepthStencil ? 1 : 0); - public FramebufferParams( - Device device, - Auto view, - uint width, - uint height, - uint samples, - bool isDepthStencil, - VkFormat format) + public FramebufferParams(Device device, TextureView view, uint width, uint height) { + bool isDepthStencil = view.Info.Format.IsDepthOrStencil(); + _device = device; - _attachments = new[] { view }; + _attachments = new[] { view.GetImageViewForAttachment() }; _validColorAttachments = isDepthStencil ? 0u : 1u; + _baseAttachment = view; + + if (isDepthStencil) + { + _depthStencil = view; + } + else + { + _colors = new TextureView[] { view }; + _colorsCanonical = _colors; + } Width = width; Height = height; Layers = 1; - AttachmentSamples = new[] { samples }; - AttachmentFormats = new[] { format }; + AttachmentSamples = new[] { (uint)view.Info.Samples }; + AttachmentFormats = new[] { view.VkFormat }; AttachmentIndices = isDepthStencil ? Array.Empty() : new[] { 0 }; AttachmentsCount = 1; @@ -64,6 +72,7 @@ public FramebufferParams(Device device, ITexture[] colors, ITexture depthStencil _attachments = new Auto[count]; _colors = new TextureView[colorsCount]; + _colorsCanonical = colors.Select(color => color is TextureView view && view.Valid ? view : null).ToArray(); AttachmentSamples = new uint[count]; AttachmentFormats = new VkFormat[count]; @@ -86,6 +95,7 @@ public FramebufferParams(Device device, ITexture[] colors, ITexture depthStencil _attachments[index] = texture.GetImageViewForAttachment(); _colors[index] = texture; _validColorAttachments |= 1u << bindIndex; + _baseAttachment = texture; AttachmentSamples[index] = (uint)texture.Info.Samples; AttachmentFormats[index] = texture.VkFormat; @@ -115,6 +125,7 @@ public FramebufferParams(Device device, ITexture[] colors, ITexture depthStencil { _attachments[count - 1] = dsTexture.GetImageViewForAttachment(); _depthStencil = dsTexture; + _baseAttachment ??= dsTexture; AttachmentSamples[count - 1] = (uint)dsTexture.Info.Samples; AttachmentFormats[count - 1] = dsTexture.VkFormat; @@ -251,19 +262,11 @@ public void UpdateModifications() public void InsertClearBarrier(CommandBufferScoped cbs, int index) { - if (_colors != null) - { - int realIndex = Array.IndexOf(AttachmentIndices, index); - - if (realIndex != -1) - { - _colors[realIndex].Storage?.InsertReadToWriteBarrier( - cbs, - AccessFlags.ColorAttachmentWriteBit, - PipelineStageFlags.ColorAttachmentOutputBit, - insideRenderPass: true); - } - } + _colorsCanonical?[index]?.Storage?.InsertReadToWriteBarrier( + cbs, + AccessFlags.ColorAttachmentWriteBit, + PipelineStageFlags.ColorAttachmentOutputBit, + insideRenderPass: true); } public void InsertClearBarrierDS(CommandBufferScoped cbs) @@ -274,5 +277,61 @@ public void InsertClearBarrierDS(CommandBufferScoped cbs) PipelineStageFlags.LateFragmentTestsBit, insideRenderPass: true); } + + public TextureView[] GetAttachmentViews() + { + var result = new TextureView[_attachments.Length]; + + _colors?.CopyTo(result, 0); + + if (_depthStencil != null) + { + result[^1] = _depthStencil; + } + + return result; + } + + public RenderPassCacheKey GetRenderPassCacheKey() + { + return new RenderPassCacheKey(_depthStencil, _colorsCanonical); + } + + public void InsertLoadOpBarriers(CommandBufferScoped cbs) + { + if (_colors != null) + { + foreach (var color in _colors) + { + // If Clear or DontCare were used, this would need to be write bit. + color.Storage?.InsertWriteToReadBarrier(cbs, AccessFlags.ColorAttachmentReadBit, PipelineStageFlags.ColorAttachmentOutputBit); + color.Storage?.SetModification(AccessFlags.ColorAttachmentWriteBit, PipelineStageFlags.ColorAttachmentOutputBit); + } + } + + if (_depthStencil != null) + { + _depthStencil.Storage?.InsertWriteToReadBarrier(cbs, AccessFlags.DepthStencilAttachmentReadBit, PipelineStageFlags.EarlyFragmentTestsBit); + _depthStencil.Storage?.SetModification(AccessFlags.DepthStencilAttachmentWriteBit, PipelineStageFlags.LateFragmentTestsBit); + } + } + + public (Auto renderPass, Auto framebuffer) GetPassAndFramebuffer( + VulkanRenderer gd, + Device device, + CommandBufferScoped cbs) + { + return _baseAttachment.GetPassAndFramebuffer(gd, device, cbs, this); + } + + public TextureView GetColorView(int index) + { + return _colorsCanonical[index]; + } + + public TextureView GetDepthStencilView() + { + return _depthStencil; + } } } diff --git a/src/Ryujinx.Graphics.Vulkan/HashTableSlim.cs b/src/Ryujinx.Graphics.Vulkan/HashTableSlim.cs index ff4eb7890..3796e3c52 100644 --- a/src/Ryujinx.Graphics.Vulkan/HashTableSlim.cs +++ b/src/Ryujinx.Graphics.Vulkan/HashTableSlim.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; namespace Ryujinx.Graphics.Vulkan { @@ -20,20 +21,29 @@ private struct Entry public TValue Value; } - private readonly Entry[][] _hashTable = new Entry[TotalBuckets][]; + private struct Bucket + { + public int Length; + public Entry[] Entries; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Span AsSpan() + { + return Entries == null ? Span.Empty : Entries.AsSpan(0, Length); + } + } + + private readonly Bucket[] _hashTable = new Bucket[TotalBuckets]; public IEnumerable Keys { get { - foreach (Entry[] bucket in _hashTable) + foreach (Bucket bucket in _hashTable) { - if (bucket != null) + for (int i = 0; i < bucket.Length; i++) { - foreach (Entry entry in bucket) - { - yield return entry.Key; - } + yield return bucket.Entries[i].Key; } } } @@ -43,14 +53,11 @@ public IEnumerable Values { get { - foreach (Entry[] bucket in _hashTable) + foreach (Bucket bucket in _hashTable) { - if (bucket != null) + for (int i = 0; i < bucket.Length; i++) { - foreach (Entry entry in bucket) - { - yield return entry.Value; - } + yield return bucket.Entries[i].Value; } } } @@ -68,40 +75,64 @@ public void Add(ref TKey key, TValue value) int hashCode = key.GetHashCode(); int bucketIndex = hashCode & TotalBucketsMask; - var bucket = _hashTable[bucketIndex]; - if (bucket != null) + ref var bucket = ref _hashTable[bucketIndex]; + if (bucket.Entries != null) { int index = bucket.Length; - Array.Resize(ref _hashTable[bucketIndex], index + 1); + if (index >= bucket.Entries.Length) + { + Array.Resize(ref bucket.Entries, index + 1); + } - _hashTable[bucketIndex][index] = entry; + bucket.Entries[index] = entry; } else { - _hashTable[bucketIndex] = new[] + bucket.Entries = new[] { entry, }; } + + bucket.Length++; } - public bool TryGetValue(ref TKey key, out TValue value) + public bool Remove(ref TKey key) { int hashCode = key.GetHashCode(); - var bucket = _hashTable[hashCode & TotalBucketsMask]; - if (bucket != null) + ref var bucket = ref _hashTable[hashCode & TotalBucketsMask]; + var entries = bucket.AsSpan(); + for (int i = 0; i < entries.Length; i++) { - for (int i = 0; i < bucket.Length; i++) + ref var entry = ref entries[i]; + + if (entry.Hash == hashCode && entry.Key.Equals(ref key)) { - ref var entry = ref bucket[i]; + entries[(i + 1)..].CopyTo(entries[i..]); + bucket.Length--; - if (entry.Hash == hashCode && entry.Key.Equals(ref key)) - { - value = entry.Value; - return true; - } + return true; + } + } + + return false; + } + + public bool TryGetValue(ref TKey key, out TValue value) + { + int hashCode = key.GetHashCode(); + + var entries = _hashTable[hashCode & TotalBucketsMask].AsSpan(); + for (int i = 0; i < entries.Length; i++) + { + ref var entry = ref entries[i]; + + if (entry.Hash == hashCode && entry.Key.Equals(ref key)) + { + value = entry.Value; + return true; } } diff --git a/src/Ryujinx.Graphics.Vulkan/HelperShader.cs b/src/Ryujinx.Graphics.Vulkan/HelperShader.cs index ce84f7521..c0ded5b3b 100644 --- a/src/Ryujinx.Graphics.Vulkan/HelperShader.cs +++ b/src/Ryujinx.Graphics.Vulkan/HelperShader.cs @@ -256,17 +256,8 @@ public void Blit( using var cbs = gd.CommandBufferPool.Rent(); - var dstFormat = dst.VkFormat; - var dstSamples = dst.Info.Samples; - for (int l = 0; l < levels; l++) { - int srcWidth = Math.Max(1, src.Width >> l); - int srcHeight = Math.Max(1, src.Height >> l); - - int dstWidth = Math.Max(1, dst.Width >> l); - int dstHeight = Math.Max(1, dst.Height >> l); - var mipSrcRegion = new Extents2D( srcRegion.X1 >> l, srcRegion.Y1 >> l, @@ -290,11 +281,7 @@ public void Blit( gd, cbs, srcView, - dst.GetImageViewForAttachment(), - dstWidth, - dstHeight, - dstSamples, - dstFormat, + dstView, mipSrcRegion, mipDstRegion); } @@ -304,12 +291,7 @@ public void Blit( gd, cbs, srcView, - dst.GetImageViewForAttachment(), - dstWidth, - dstHeight, - dstSamples, - dstFormat, - false, + dstView, mipSrcRegion, mipDstRegion, linearFilter, @@ -367,12 +349,7 @@ public void CopyColor( gd, cbs, srcView, - dstView.GetImageViewForAttachment(), - dstView.Width, - dstView.Height, - dstView.Info.Samples, - dstView.VkFormat, - dstView.Info.Format.IsDepthOrStencil(), + dstView, extents, extents, false); @@ -394,12 +371,7 @@ public void BlitColor( VulkanRenderer gd, CommandBufferScoped cbs, TextureView src, - Auto dst, - int dstWidth, - int dstHeight, - int dstSamples, - VkFormat dstFormat, - bool dstIsDepthOrStencil, + TextureView dst, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter, @@ -453,6 +425,8 @@ public void BlitColor( 0f, 1f); + bool dstIsDepthOrStencil = dst.Info.Format.IsDepthOrStencil(); + if (dstIsDepthOrStencil) { _pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit); @@ -471,7 +445,10 @@ public void BlitColor( _pipeline.SetProgram(_programColorBlit); } - _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, (uint)dstSamples, dstIsDepthOrStencil, dstFormat); + int dstWidth = dst.Width; + int dstHeight = dst.Height; + + _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight); _pipeline.SetRenderTargetColorMasks(new uint[] { 0xf }); _pipeline.SetScissors(stackalloc Rectangle[] { new Rectangle(0, 0, dstWidth, dstHeight) }); @@ -496,11 +473,7 @@ private void BlitDepthStencil( VulkanRenderer gd, CommandBufferScoped cbs, TextureView src, - Auto dst, - int dstWidth, - int dstHeight, - int dstSamples, - VkFormat dstFormat, + TextureView dst, Extents2D srcRegion, Extents2D dstRegion) { @@ -548,7 +521,10 @@ private void BlitDepthStencil( 0f, 1f); - _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, (uint)dstSamples, true, dstFormat); + int dstWidth = dst.Width; + int dstHeight = dst.Height; + + _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight); _pipeline.SetScissors(stackalloc Rectangle[] { new Rectangle(0, 0, dstWidth, dstHeight) }); _pipeline.SetViewports(viewports); _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); @@ -660,12 +636,11 @@ private static StencilTestDescriptor CreateStencilTestDescriptor( public void Clear( VulkanRenderer gd, - Auto dst, + TextureView dst, ReadOnlySpan clearColor, uint componentMask, int dstWidth, int dstHeight, - VkFormat dstFormat, ComponentType type, Rectangle scissor) { @@ -710,7 +685,7 @@ public void Clear( } _pipeline.SetProgram(program); - _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false, dstFormat); + _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight); _pipeline.SetRenderTargetColorMasks(new[] { componentMask }); _pipeline.SetViewports(viewports); _pipeline.SetScissors(stackalloc Rectangle[] { scissor }); @@ -721,7 +696,7 @@ public void Clear( public void Clear( VulkanRenderer gd, - Auto dst, + TextureView dst, float depthValue, bool depthMask, int stencilValue, @@ -757,7 +732,7 @@ public void Clear( 1f); _pipeline.SetProgram(_programDepthStencilClear); - _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, true, dstFormat); + _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight); _pipeline.SetViewports(viewports); _pipeline.SetScissors(stackalloc Rectangle[] { scissor }); _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); @@ -1163,12 +1138,7 @@ public void CopyMSToNonMS(VulkanRenderer gd, CommandBufferScoped cbs, TextureVie var srcView = Create2DLayerView(src, srcLayer + z, 0); var dstView = Create2DLayerView(dst, dstLayer + z, 0); - _pipeline.SetRenderTarget( - dstView.GetImageViewForAttachment(), - (uint)dst.Width, - (uint)dst.Height, - true, - dst.VkFormat); + _pipeline.SetRenderTarget(dstView, (uint)dst.Width, (uint)dst.Height); CopyMSDraw(srcView, aspectFlags, fromMS: true); @@ -1294,13 +1264,7 @@ public void CopyNonMSToMS(VulkanRenderer gd, CommandBufferScoped cbs, TextureVie var srcView = Create2DLayerView(src, srcLayer + z, 0); var dstView = Create2DLayerView(dst, dstLayer + z, 0); - _pipeline.SetRenderTarget( - dstView.GetImageViewForAttachment(), - (uint)dst.Width, - (uint)dst.Height, - (uint)samples, - true, - dst.VkFormat); + _pipeline.SetRenderTarget(dstView, (uint)dst.Width, (uint)dst.Height); CopyMSDraw(srcView, aspectFlags, fromMS: false); @@ -1328,13 +1292,7 @@ public void CopyNonMSToMS(VulkanRenderer gd, CommandBufferScoped cbs, TextureVie var dstView = Create2DLayerView(dst, dstLayer + z, 0); _pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Fragment, 0, srcView, null); - _pipeline.SetRenderTarget( - dstView.GetView(format).GetImageViewForAttachment(), - (uint)dst.Width, - (uint)dst.Height, - (uint)samples, - false, - vkFormat); + _pipeline.SetRenderTarget(dstView.GetView(format), (uint)dst.Width, (uint)dst.Height); _pipeline.Draw(4, 1, 0, 0); @@ -1471,9 +1429,9 @@ private static TextureView Create2DLayerView(TextureView from, int layer, int le }; var info = new TextureCreateInfo( - from.Info.Width, - from.Info.Height, - from.Info.Depth, + Math.Max(1, from.Info.Width >> level), + Math.Max(1, from.Info.Height >> level), + 1, 1, from.Info.Samples, from.Info.BlockWidth, diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs index 61215b672..3aef1317a 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -55,6 +55,7 @@ class PipelineBase : IDisposable protected FramebufferParams FramebufferParams; private Auto _framebuffer; private Auto _renderPass; + private RenderPassHolder _nullRenderPass; private int _writtenAttachmentCount; private bool _framebufferUsingColorWriteMask; @@ -1488,98 +1489,22 @@ protected void UpdatePipelineAttachmentFormats() protected unsafe void CreateRenderPass() { - const int MaxAttachments = Constants.MaxRenderTargets + 1; - - AttachmentDescription[] attachmentDescs = null; - - var subpass = new SubpassDescription - { - PipelineBindPoint = PipelineBindPoint.Graphics, - }; - - AttachmentReference* attachmentReferences = stackalloc AttachmentReference[MaxAttachments]; - var hasFramebuffer = FramebufferParams != null; - if (hasFramebuffer && FramebufferParams.AttachmentsCount != 0) - { - attachmentDescs = new AttachmentDescription[FramebufferParams.AttachmentsCount]; - - for (int i = 0; i < FramebufferParams.AttachmentsCount; i++) - { - attachmentDescs[i] = new AttachmentDescription( - 0, - FramebufferParams.AttachmentFormats[i], - TextureStorage.ConvertToSampleCountFlags(Gd.Capabilities.SupportedSampleCounts, FramebufferParams.AttachmentSamples[i]), - AttachmentLoadOp.Load, - AttachmentStoreOp.Store, - AttachmentLoadOp.Load, - AttachmentStoreOp.Store, - ImageLayout.General, - ImageLayout.General); - } - - int colorAttachmentsCount = FramebufferParams.ColorAttachmentsCount; - - if (colorAttachmentsCount > MaxAttachments - 1) - { - colorAttachmentsCount = MaxAttachments - 1; - } - - if (colorAttachmentsCount != 0) - { - int maxAttachmentIndex = FramebufferParams.MaxColorAttachmentIndex; - subpass.ColorAttachmentCount = (uint)maxAttachmentIndex + 1; - subpass.PColorAttachments = &attachmentReferences[0]; - - // Fill with VK_ATTACHMENT_UNUSED to cover any gaps. - for (int i = 0; i <= maxAttachmentIndex; i++) - { - subpass.PColorAttachments[i] = new AttachmentReference(Vk.AttachmentUnused, ImageLayout.Undefined); - } - - for (int i = 0; i < colorAttachmentsCount; i++) - { - int bindIndex = FramebufferParams.AttachmentIndices[i]; - - subpass.PColorAttachments[bindIndex] = new AttachmentReference((uint)i, ImageLayout.General); - } - } + EndRenderPass(); - if (FramebufferParams.HasDepthStencil) - { - uint dsIndex = (uint)FramebufferParams.AttachmentsCount - 1; + if (!hasFramebuffer || FramebufferParams.AttachmentsCount == 0) + { + // Use the null framebuffer. + _nullRenderPass ??= new RenderPassHolder(Gd, Device, new RenderPassCacheKey(), FramebufferParams); - subpass.PDepthStencilAttachment = &attachmentReferences[MaxAttachments - 1]; - *subpass.PDepthStencilAttachment = new AttachmentReference(dsIndex, ImageLayout.General); - } + _renderPass = _nullRenderPass.GetRenderPass(); + _framebuffer = _nullRenderPass.GetFramebuffer(Gd, Cbs, FramebufferParams); } - - var subpassDependency = PipelineConverter.CreateSubpassDependency(); - - fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs) + else { - var renderPassCreateInfo = new RenderPassCreateInfo - { - SType = StructureType.RenderPassCreateInfo, - PAttachments = pAttachmentDescs, - AttachmentCount = attachmentDescs != null ? (uint)attachmentDescs.Length : 0, - PSubpasses = &subpass, - SubpassCount = 1, - PDependencies = &subpassDependency, - DependencyCount = 1, - }; - - Gd.Api.CreateRenderPass(Device, renderPassCreateInfo, null, out var renderPass).ThrowOnError(); - - _renderPass?.Dispose(); - _renderPass = new Auto(new DisposableRenderPass(Gd.Api, Device, renderPass)); + (_renderPass, _framebuffer) = FramebufferParams.GetPassAndFramebuffer(Gd, Device, Cbs); } - - EndRenderPass(); - - _framebuffer?.Dispose(); - _framebuffer = hasFramebuffer ? FramebufferParams.Create(Gd.Api, Cbs, _renderPass) : null; } protected void SignalStateChange() @@ -1770,8 +1695,7 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - _renderPass?.Dispose(); - _framebuffer?.Dispose(); + _nullRenderPass?.Dispose(); _newState.Dispose(); _descriptorSetUpdater.Dispose(); _vertexBufferUpdater.Dispose(); diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs b/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs index a3e6818f3..6c4419cd2 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs @@ -51,7 +51,7 @@ public void ClearRenderTargetColor(int index, int layer, int layerCount, uint co { // We can't use CmdClearAttachments if not writing all components, // because on Vulkan, the pipeline state does not affect clears. - var dstTexture = FramebufferParams.GetAttachment(index); + var dstTexture = FramebufferParams.GetColorView(index); if (dstTexture == null) { return; @@ -71,7 +71,6 @@ public void ClearRenderTargetColor(int index, int layer, int layerCount, uint co componentMask, (int)FramebufferParams.Width, (int)FramebufferParams.Height, - FramebufferParams.AttachmentFormats[index], FramebufferParams.GetAttachmentComponentType(index), ClearScissor); } @@ -92,7 +91,7 @@ public void ClearRenderTargetDepthStencil(int layer, int layerCount, float depth { // 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. - var dstTexture = FramebufferParams.GetDepthStencilAttachment(); + var dstTexture = FramebufferParams.GetDepthStencilView(); if (dstTexture == null) { return; diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs b/src/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs index 0a871a5c8..dfbf19013 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs @@ -9,21 +9,16 @@ public PipelineHelperShader(VulkanRenderer gd, Device device) : base(gd, device) { } - public void SetRenderTarget(Auto view, uint width, uint height, bool isDepthStencil, VkFormat format) + public void SetRenderTarget(TextureView view, uint width, uint height) { - SetRenderTarget(view, width, height, 1u, isDepthStencil, format); - } - - public void SetRenderTarget(Auto view, uint width, uint height, uint samples, bool isDepthStencil, VkFormat format) - { - CreateFramebuffer(view, width, height, samples, isDepthStencil, format); + CreateFramebuffer(view, width, height); CreateRenderPass(); SignalStateChange(); } - private void CreateFramebuffer(Auto view, uint width, uint height, uint samples, bool isDepthStencil, VkFormat format) + private void CreateFramebuffer(TextureView view, uint width, uint height) { - FramebufferParams = new FramebufferParams(Device, view, width, height, samples, isDepthStencil, format); + FramebufferParams = new FramebufferParams(Device, view, width, height); UpdatePipelineAttachmentFormats(); } diff --git a/src/Ryujinx.Graphics.Vulkan/RenderPassCacheKey.cs b/src/Ryujinx.Graphics.Vulkan/RenderPassCacheKey.cs new file mode 100644 index 000000000..7c57b8feb --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/RenderPassCacheKey.cs @@ -0,0 +1,43 @@ +using System; +using System.Linq; + +namespace Ryujinx.Graphics.Vulkan +{ + internal readonly struct RenderPassCacheKey : IRefEquatable + { + private readonly TextureView _depthStencil; + private readonly TextureView[] _colors; + + public RenderPassCacheKey(TextureView depthStencil, TextureView[] colors) + { + _depthStencil = depthStencil; + _colors = colors; + } + + public override int GetHashCode() + { + HashCode hc = new(); + + hc.Add(_depthStencil); + + if (_colors != null) + { + foreach (var color in _colors) + { + hc.Add(color); + } + } + + return hc.ToHashCode(); + } + + public bool Equals(ref RenderPassCacheKey other) + { + bool colorsNull = _colors == null; + bool otherNull = other._colors == null; + return other._depthStencil == _depthStencil && + colorsNull == otherNull && + (colorsNull || other._colors.SequenceEqual(_colors)); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs b/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs new file mode 100644 index 000000000..3d883b2d5 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs @@ -0,0 +1,180 @@ +using Silk.NET.Vulkan; +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + internal class RenderPassHolder + { + private readonly struct FramebufferCacheKey : IRefEquatable + { + private readonly uint _width; + private readonly uint _height; + private readonly uint _layers; + + public FramebufferCacheKey(uint width, uint height, uint layers) + { + _width = width; + _height = height; + _layers = layers; + } + + public override int GetHashCode() + { + return HashCode.Combine(_width, _height, _layers); + } + + public bool Equals(ref FramebufferCacheKey other) + { + return other._width == _width && other._height == _height && other._layers == _layers; + } + } + + private readonly TextureView[] _textures; + private readonly Auto _renderPass; + private readonly HashTableSlim> _framebuffers; + private readonly RenderPassCacheKey _key; + + public unsafe RenderPassHolder(VulkanRenderer gd, Device device, RenderPassCacheKey key, FramebufferParams fb) + { + // Create render pass using framebuffer params. + + const int MaxAttachments = Constants.MaxRenderTargets + 1; + + AttachmentDescription[] attachmentDescs = null; + + var subpass = new SubpassDescription + { + PipelineBindPoint = PipelineBindPoint.Graphics, + }; + + AttachmentReference* attachmentReferences = stackalloc AttachmentReference[MaxAttachments]; + + var hasFramebuffer = fb != null; + + if (hasFramebuffer && fb.AttachmentsCount != 0) + { + attachmentDescs = new AttachmentDescription[fb.AttachmentsCount]; + + for (int i = 0; i < fb.AttachmentsCount; i++) + { + attachmentDescs[i] = new AttachmentDescription( + 0, + fb.AttachmentFormats[i], + TextureStorage.ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, fb.AttachmentSamples[i]), + AttachmentLoadOp.Load, + AttachmentStoreOp.Store, + AttachmentLoadOp.Load, + AttachmentStoreOp.Store, + ImageLayout.General, + ImageLayout.General); + } + + int colorAttachmentsCount = fb.ColorAttachmentsCount; + + if (colorAttachmentsCount > MaxAttachments - 1) + { + colorAttachmentsCount = MaxAttachments - 1; + } + + if (colorAttachmentsCount != 0) + { + int maxAttachmentIndex = fb.MaxColorAttachmentIndex; + subpass.ColorAttachmentCount = (uint)maxAttachmentIndex + 1; + subpass.PColorAttachments = &attachmentReferences[0]; + + // Fill with VK_ATTACHMENT_UNUSED to cover any gaps. + for (int i = 0; i <= maxAttachmentIndex; i++) + { + subpass.PColorAttachments[i] = new AttachmentReference(Vk.AttachmentUnused, ImageLayout.Undefined); + } + + for (int i = 0; i < colorAttachmentsCount; i++) + { + int bindIndex = fb.AttachmentIndices[i]; + + subpass.PColorAttachments[bindIndex] = new AttachmentReference((uint)i, ImageLayout.General); + } + } + + if (fb.HasDepthStencil) + { + uint dsIndex = (uint)fb.AttachmentsCount - 1; + + subpass.PDepthStencilAttachment = &attachmentReferences[MaxAttachments - 1]; + *subpass.PDepthStencilAttachment = new AttachmentReference(dsIndex, ImageLayout.General); + } + } + + var subpassDependency = PipelineConverter.CreateSubpassDependency(); + + fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs) + { + var renderPassCreateInfo = new RenderPassCreateInfo + { + SType = StructureType.RenderPassCreateInfo, + PAttachments = pAttachmentDescs, + AttachmentCount = attachmentDescs != null ? (uint)attachmentDescs.Length : 0, + PSubpasses = &subpass, + SubpassCount = 1, + PDependencies = &subpassDependency, + DependencyCount = 1, + }; + + gd.Api.CreateRenderPass(device, renderPassCreateInfo, null, out var renderPass).ThrowOnError(); + + _renderPass?.Dispose(); + _renderPass = new Auto(new DisposableRenderPass(gd.Api, device, renderPass)); + } + + _framebuffers = new HashTableSlim>(); + + // Register this render pass with all render target views. + + var textures = fb.GetAttachmentViews(); + + foreach (var texture in textures) + { + texture.AddRenderPass(key, this); + } + + _textures = textures; + _key = key; + } + + public Auto GetFramebuffer(VulkanRenderer gd, CommandBufferScoped cbs, FramebufferParams fb) + { + var key = new FramebufferCacheKey(fb.Width, fb.Height, fb.Layers); + + if (!_framebuffers.TryGetValue(ref key, out Auto result)) + { + result = fb.Create(gd.Api, cbs, _renderPass); + + _framebuffers.Add(ref key, result); + } + + return result; + } + + public Auto GetRenderPass() + { + return _renderPass; + } + + public void Dispose() + { + // Dispose all framebuffers + + foreach (var fb in _framebuffers.Values) + { + fb.Dispose(); + } + + // Notify all texture views that this render pass has been disposed. + + foreach (var texture in _textures) + { + texture.RemoveRenderPass(_key); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/TextureView.cs b/src/Ryujinx.Graphics.Vulkan/TextureView.cs index f5b80f948..393db2611 100644 --- a/src/Ryujinx.Graphics.Vulkan/TextureView.cs +++ b/src/Ryujinx.Graphics.Vulkan/TextureView.cs @@ -3,6 +3,7 @@ using Silk.NET.Vulkan; using System; using System.Collections.Generic; +using System.Linq; using Format = Ryujinx.Graphics.GAL.Format; using VkBuffer = Silk.NET.Vulkan.Buffer; using VkFormat = Silk.NET.Vulkan.Format; @@ -23,6 +24,8 @@ class TextureView : ITexture, IDisposable private readonly TextureCreateInfo _info; + private HashTableSlim _renderPasses; + public TextureCreateInfo Info => _info; public TextureStorage Storage { get; } @@ -158,6 +161,26 @@ unsafe Auto CreateImageView(ComponentMapping cm, ImageSubre Valid = true; } + /// + /// Create a texture view for an existing swapchain image view. + /// Does not set storage, so only appropriate for swapchain use. + /// + /// Do not use this for normal textures, and make sure uses do not try to read storage. + public TextureView(VulkanRenderer gd, Device device, DisposableImageView view, TextureCreateInfo info, VkFormat format) + { + _gd = gd; + _device = device; + + _imageView = new Auto(view); + _imageViewDraw = _imageView; + _imageViewIdentity = _imageView; + _info = info; + + VkFormat = format; + + Valid = true; + } + public Auto GetImage() { return Storage.GetImage(); @@ -939,6 +962,34 @@ public void SetStorage(BufferRange buffer) throw new NotImplementedException(); } + public (Auto renderPass, Auto framebuffer) GetPassAndFramebuffer( + VulkanRenderer gd, + Device device, + CommandBufferScoped cbs, + FramebufferParams fb) + { + var key = fb.GetRenderPassCacheKey(); + + if (_renderPasses == null || !_renderPasses.TryGetValue(ref key, out RenderPassHolder rpHolder)) + { + rpHolder = new RenderPassHolder(gd, device, key, fb); + } + + return (rpHolder.GetRenderPass(), rpHolder.GetFramebuffer(gd, cbs, fb)); + } + + public void AddRenderPass(RenderPassCacheKey key, RenderPassHolder renderPass) + { + _renderPasses ??= new HashTableSlim(); + + _renderPasses.Add(ref key, renderPass); + } + + public void RemoveRenderPass(RenderPassCacheKey key) + { + _renderPasses.Remove(ref key); + } + protected virtual void Dispose(bool disposing) { if (disposing) @@ -948,15 +999,29 @@ protected virtual void Dispose(bool disposing) if (_gd.Textures.Remove(this)) { _imageView.Dispose(); - _imageViewIdentity.Dispose(); _imageView2dArray?.Dispose(); + if (_imageViewIdentity != _imageView) + { + _imageViewIdentity.Dispose(); + } + if (_imageViewDraw != _imageViewIdentity) { _imageViewDraw.Dispose(); } Storage.DecrementViewsCount(); + + if (_renderPasses != null) + { + var renderPasses = _renderPasses.Values.ToArray(); + + foreach (var pass in renderPasses) + { + pass.Dispose(); + } + } } } } diff --git a/src/Ryujinx.Graphics.Vulkan/Window.cs b/src/Ryujinx.Graphics.Vulkan/Window.cs index 2c5764a99..5ddb6eeda 100644 --- a/src/Ryujinx.Graphics.Vulkan/Window.cs +++ b/src/Ryujinx.Graphics.Vulkan/Window.cs @@ -20,7 +20,7 @@ class Window : WindowBase, IDisposable private SwapchainKHR _swapchain; private Image[] _swapchainImages; - private Auto[] _swapchainImageViews; + private TextureView[] _swapchainImageViews; private Semaphore[] _imageAvailableSemaphores; private Semaphore[] _renderFinishedSemaphores; @@ -143,6 +143,23 @@ private unsafe void CreateSwapchain() Clipped = true, }; + var textureCreateInfo = new TextureCreateInfo( + _width, + _height, + 1, + 1, + 1, + 1, + 1, + 1, + FormatTable.GetFormat(surfaceFormat.Format), + DepthStencilMode.Depth, + Target.Texture2D, + SwizzleComponent.Red, + SwizzleComponent.Green, + SwizzleComponent.Blue, + SwizzleComponent.Alpha); + _gd.SwapchainApi.CreateSwapchain(_device, swapchainCreateInfo, null, out _swapchain).ThrowOnError(); _gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, null); @@ -154,11 +171,11 @@ private unsafe void CreateSwapchain() _gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, pSwapchainImages); } - _swapchainImageViews = new Auto[imageCount]; + _swapchainImageViews = new TextureView[imageCount]; for (int i = 0; i < _swapchainImageViews.Length; i++) { - _swapchainImageViews[i] = CreateSwapchainImageView(_swapchainImages[i], surfaceFormat.Format); + _swapchainImageViews[i] = CreateSwapchainImageView(_swapchainImages[i], surfaceFormat.Format, textureCreateInfo); } var semaphoreCreateInfo = new SemaphoreCreateInfo @@ -181,7 +198,7 @@ private unsafe void CreateSwapchain() } } - private unsafe Auto CreateSwapchainImageView(Image swapchainImage, VkFormat format) + private unsafe TextureView CreateSwapchainImageView(Image swapchainImage, VkFormat format, TextureCreateInfo info) { var componentMapping = new ComponentMapping( ComponentSwizzle.R, @@ -204,7 +221,8 @@ private unsafe Auto CreateSwapchainImageView(Image swapchai }; _gd.Api.CreateImageView(_device, imageCreateInfo, null, out var imageView).ThrowOnError(); - return new Auto(new DisposableImageView(_gd.Api, _device, imageView)); + + return new TextureView(_gd, _device, new DisposableImageView(_gd.Api, _device, imageView), info, format); } private static SurfaceFormatKHR ChooseSwapSurfaceFormat(SurfaceFormatKHR[] availableFormats, bool colorSpacePassthroughEnabled) @@ -406,7 +424,7 @@ public unsafe override void Present(ITexture texture, ImageCrop crop, Action swa _scalingFilter.Run( view, cbs, - _swapchainImageViews[nextImage], + _swapchainImageViews[nextImage].GetImageViewForAttachment(), _format, _width, _height, @@ -421,11 +439,6 @@ public unsafe override void Present(ITexture texture, ImageCrop crop, Action swa cbs, view, _swapchainImageViews[nextImage], - _width, - _height, - 1, - _format, - false, new Extents2D(srcX0, srcY0, srcX1, srcY1), new Extents2D(dstX0, dstY1, dstX1, dstY0), _isLinear, From d704bcd93b90c288e6e200378373403525b59220 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Fri, 2 Feb 2024 20:56:51 -0300 Subject: [PATCH 034/126] Ensure SM service won't listen to closed sessions (#6246) * Ensure SM service won't listen to closed sessions * PR feedback --- src/Ryujinx.HLE/HOS/Services/ServerBase.cs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Ryujinx.HLE/HOS/Services/ServerBase.cs b/src/Ryujinx.HLE/HOS/Services/ServerBase.cs index e892d6ab6..b484a768c 100644 --- a/src/Ryujinx.HLE/HOS/Services/ServerBase.cs +++ b/src/Ryujinx.HLE/HOS/Services/ServerBase.cs @@ -287,6 +287,10 @@ private void ServerLoop() _wakeEvent.WritableEvent.Clear(); } } + else if (rc == KernelResult.PortRemoteClosed && signaledIndex >= 0) + { + DestroySession(handles[signaledIndex]); + } _selfProcess.CpuMemory.Write(messagePtr + 0x0, 0); _selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10); @@ -299,6 +303,16 @@ private void ServerLoop() Dispose(); } + private void DestroySession(int serverSessionHandle) + { + _context.Syscall.CloseHandle(serverSessionHandle); + + if (RemoveSessionObj(serverSessionHandle, out var session)) + { + (session as IDisposable)?.Dispose(); + } + } + private bool Process(int serverSessionHandle, ulong recvListAddr) { IpcMessage request = ReadRequest(); @@ -360,7 +374,7 @@ private bool Process(int serverSessionHandle, ulong recvListAddr) response.RawData = _responseDataStream.ToArray(); } else if (request.Type == IpcMessageType.CmifControl || - request.Type == IpcMessageType.CmifControlWithContext) + request.Type == IpcMessageType.CmifControlWithContext) { #pragma warning disable IDE0059 // Remove unnecessary value assignment uint magic = (uint)_requestDataReader.ReadUInt64(); @@ -412,11 +426,7 @@ private bool Process(int serverSessionHandle, ulong recvListAddr) } else if (request.Type == IpcMessageType.CmifCloseSession || request.Type == IpcMessageType.TipcCloseSession) { - _context.Syscall.CloseHandle(serverSessionHandle); - if (RemoveSessionObj(serverSessionHandle, out var session)) - { - (session as IDisposable)?.Dispose(); - } + DestroySession(serverSessionHandle); shouldReply = false; } // If the type is past 0xF, we are using TIPC From e5066449a5bd0d77a4a5539e931776e97460ff95 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sat, 3 Feb 2024 15:40:09 -0300 Subject: [PATCH 035/126] Limit remote closed session removal to SM service (#6248) --- src/Ryujinx.HLE/HOS/Services/ServerBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ryujinx.HLE/HOS/Services/ServerBase.cs b/src/Ryujinx.HLE/HOS/Services/ServerBase.cs index b484a768c..5e18d7981 100644 --- a/src/Ryujinx.HLE/HOS/Services/ServerBase.cs +++ b/src/Ryujinx.HLE/HOS/Services/ServerBase.cs @@ -287,7 +287,7 @@ private void ServerLoop() _wakeEvent.WritableEvent.Clear(); } } - else if (rc == KernelResult.PortRemoteClosed && signaledIndex >= 0) + else if (rc == KernelResult.PortRemoteClosed && signaledIndex >= 0 && SmObjectFactory != null) { DestroySession(handles[signaledIndex]); } From 24c8b0edc06c986f1fca80f01244f83b5bb4346c Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sun, 4 Feb 2024 07:10:45 -0300 Subject: [PATCH 036/126] Remove component operand for texture gather with depth compare (#6247) --- src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs index 55f7d5778..e9349542d 100644 --- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs @@ -592,7 +592,10 @@ void AddTextureOffset(int coordsCount, int stride, int size) flags |= TextureFlags.Offset; } - sourcesList.Add(Const((int)tld4sOp.TexComp)); + if (!tld4sOp.Dc) + { + sourcesList.Add(Const((int)tld4sOp.TexComp)); + } } else { From bbed3b99265ad03f8649ff2cc1300a135c0ba3f1 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sun, 4 Feb 2024 16:58:17 -0300 Subject: [PATCH 037/126] Fix depth compare value for TLD4S shader instruction with offset (#6253) * Fix depth compare value for TLD4S shader instruction with offset * Shader cache version bump --- .../Shader/DiskCache/DiskCacheHostStorage.cs | 2 +- .../Instructions/InstEmitTexture.cs | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index 125ab8993..c5763b025 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs @@ -22,7 +22,7 @@ class DiskCacheHostStorage private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMinor = 2; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; - private const uint CodeGenVersion = 5958; + private const uint CodeGenVersion = 6253; private const string SharedTocFileName = "shared.toc"; private const string SharedDataFileName = "shared.data"; diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs index e9349542d..06daa26a0 100644 --- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs @@ -578,12 +578,7 @@ void AddTextureOffset(int coordsCount, int stride, int size) type = SamplerType.Texture2D; flags = TextureFlags.Gather; - if (tld4sOp.Dc) - { - sourcesList.Add(Rb()); - - type |= SamplerType.Shadow; - } + int depthCompareIndex = sourcesList.Count; if (tld4sOp.Aoffi) { @@ -592,7 +587,13 @@ void AddTextureOffset(int coordsCount, int stride, int size) flags |= TextureFlags.Offset; } - if (!tld4sOp.Dc) + if (tld4sOp.Dc) + { + sourcesList.Insert(depthCompareIndex, Rb()); + + type |= SamplerType.Shadow; + } + else { sourcesList.Add(Const((int)tld4sOp.TexComp)); } From 8927e0669f0735b5fbea5c6b82a9c917cc8b6e0c Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sun, 4 Feb 2024 18:12:12 -0300 Subject: [PATCH 038/126] Revert change to skip flush when range size is 0 (#6254) --- src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs index 0fb4f1be7..fb2097444 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs @@ -1630,12 +1630,6 @@ public void FlushAction(TextureGroupHandle handle, ulong address, ulong size) return; } - // If size is zero, we have nothing to flush. - if (size == 0) - { - return; - } - // There is a small gap here where the action is removed but _actionRegistered is still 1. // In this case it will skip registering the action, but here we are already handling it, // so there shouldn't be any issue as it's the same handler for all actions. From 25123232bd7186d8009332f67124a5991f2a978d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 15:39:45 +0100 Subject: [PATCH 039/126] nuget: bump SPB from 0.0.4-build28 to 0.0.4-build32 (#6235) Bumps [SPB](https://github.com/marysaka/SPB) from 0.0.4-build28 to 0.0.4-build32. - [Release notes](https://github.com/marysaka/SPB/releases) - [Commits](https://github.com/marysaka/SPB/commits) --- updated-dependencies: - dependency-name: SPB dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index b1cebda96..dfb38a6e2 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -45,7 +45,7 @@ - + From a37e2d6e44c7f384f26062dea5599749c7c60623 Mon Sep 17 00:00:00 2001 From: sharmander Date: Tue, 6 Feb 2024 09:05:32 -0800 Subject: [PATCH 040/126] Resolve an issue where changes to the main window's positioning could cause the application to crash if a modal was dismissed beforehand. (#6223) --- src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs b/src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs index 0863cbaa4..b9d919f95 100644 --- a/src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs +++ b/src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs @@ -336,6 +336,11 @@ public static async Task ShowAsync(ContentDialog contentDia void OverlayOnPositionChanged(object sender, PixelPointEventArgs e) { + if (_contentDialogOverlayWindow is null) + { + return; + } + _contentDialogOverlayWindow.Position = parent.PointToScreen(new Point()); } From d56bab1e24d3461a037005b67d64e9cd4fc2a0df Mon Sep 17 00:00:00 2001 From: riperiperi Date: Tue, 6 Feb 2024 22:11:20 +0000 Subject: [PATCH 041/126] AccountService: Cache token data (#6260) * AccountService: Cache token data This method appears to indicate that the token returned should be cached. I've made it so that it generates a token that lasts until its expiration time, and reuses it on subsequent calls. * Private naming convention --- .../Account/Acc/AccountService/ManagerServer.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ManagerServer.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ManagerServer.cs index ec7fa5c4f..75bad0e3f 100644 --- a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ManagerServer.cs +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ManagerServer.cs @@ -22,6 +22,9 @@ class ManagerServer private readonly UserId _userId; #pragma warning restore IDE0052 + private byte[] _cachedTokenData; + private DateTime _cachedTokenExpiry; + public ManagerServer(UserId userId) { _userId = userId; @@ -144,7 +147,13 @@ public ResultCode LoadIdTokenCache(ServiceCtx context) } */ - byte[] tokenData = Encoding.ASCII.GetBytes(GenerateIdToken()); + if (_cachedTokenData == null || DateTime.UtcNow > _cachedTokenExpiry) + { + _cachedTokenExpiry = DateTime.UtcNow + TimeSpan.FromHours(3); + _cachedTokenData = Encoding.ASCII.GetBytes(GenerateIdToken()); + } + + byte[] tokenData = _cachedTokenData; context.Memory.Write(bufferPosition, tokenData); context.ResponseData.Write(tokenData.Length); From 6c90d50c8e5b89b337e5d97ea100a0206c73c0db Mon Sep 17 00:00:00 2001 From: SamusAranX Date: Tue, 6 Feb 2024 23:29:50 +0100 Subject: [PATCH 042/126] Redact usernames from logs (#6255) * Redact usernames from logs * Changed internal vars to private and applied naming rules * Use Directory.GetParent() instead of DirectoryInfo * Update src/Ryujinx.Common/Logging/Logger.cs --------- Co-authored-by: Ac_K --- src/Ryujinx.Common/Logging/Logger.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx.Common/Logging/Logger.cs b/src/Ryujinx.Common/Logging/Logger.cs index f03a7fd8f..db46739ac 100644 --- a/src/Ryujinx.Common/Logging/Logger.cs +++ b/src/Ryujinx.Common/Logging/Logger.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Runtime.CompilerServices; using System.Threading; @@ -22,6 +23,9 @@ public static class Logger public readonly struct Log { + private static readonly string _homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + private static readonly string _homeDirRedacted = Path.Combine(Directory.GetParent(_homeDir).FullName, "[redacted]"); + internal readonly LogLevel Level; internal Log(LogLevel level) @@ -100,7 +104,12 @@ public void PrintRawMsg(string message) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string FormatMessage(LogClass logClass, string caller, string message) => $"{logClass} {caller}: {message}"; + private static string FormatMessage(LogClass logClass, string caller, string message) + { + message = message.Replace(_homeDir, _homeDirRedacted); + + return $"{logClass} {caller}: {message}"; + } } public static Log? Debug { get; private set; } From 628d092fc64d8b89a5e9f05cc15136bac16122ab Mon Sep 17 00:00:00 2001 From: Mary Guillemard Date: Wed, 7 Feb 2024 22:43:44 +0100 Subject: [PATCH 043/126] chore: Update Ryujinx.SDL2-CS to 2.30.0 (#6261) Also add linux-arm64 support. Signed-off-by: Mary Guillemard --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index dfb38a6e2..e45ffccae 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -36,7 +36,7 @@ - + From 8bb7a3fc977db964b33645166e733b4b29259cb9 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Thu, 8 Feb 2024 14:27:12 -0300 Subject: [PATCH 044/126] Clamp vertex buffer size to mapped size if too high (#6272) * Clamp vertex buffer size to mapped size if too high * Update comment --- .../Engine/Threed/StateUpdater.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs index 2b65b4560..6b4ea89f3 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs @@ -26,6 +26,9 @@ class StateUpdater public const int PrimitiveRestartStateIndex = 12; public const int RenderTargetStateIndex = 27; + // Vertex buffers larger than this size will be clamped to the mapped size. + private const ulong VertexBufferSizeToMappedSizeThreshold = 256 * 1024 * 1024; // 256 MB + private readonly GpuContext _context; private readonly GpuChannel _channel; private readonly DeviceStateWithShadow _state; @@ -1144,6 +1147,14 @@ private void UpdateVertexBufferState() size = Math.Min(size, maxVertexBufferSize); } + else if (size > VertexBufferSizeToMappedSizeThreshold) + { + // Make sure we have a sane vertex buffer size, since in some cases applications + // might set the "end address" of the vertex buffer to the end of the GPU address space, + // which would result in a several GBs large buffer. + + size = _channel.MemoryManager.GetMappedSize(address, size); + } } else { From 459efd0db7ac64a408860a1a94d9244753fb808e Mon Sep 17 00:00:00 2001 From: sunshineinabox Date: Thu, 8 Feb 2024 10:34:48 -0800 Subject: [PATCH 045/126] Replace Flex Panels in favor of Wrap Panels for Avalonia (#6267) * Switch from using Flex panel to a Wrap panel for Grid view. This allows keyboard navigation. * Stop using Flex panel in favor of Avalonia Wrap Panel. --- Directory.Packages.props | 3 +-- src/Ryujinx.Ava/Ryujinx.Ava.csproj | 1 - .../UI/Controls/ApplicationGridView.axaml | 8 +++----- .../UI/Views/User/UserSelectorView.axaml | 12 +++++------- src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml | 13 +++++-------- 5 files changed, 14 insertions(+), 23 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index e45ffccae..bea91391c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -17,7 +17,6 @@ - @@ -51,4 +50,4 @@ - \ No newline at end of file + diff --git a/src/Ryujinx.Ava/Ryujinx.Ava.csproj b/src/Ryujinx.Ava/Ryujinx.Ava.csproj index b6d37a2f1..7f5224cfb 100644 --- a/src/Ryujinx.Ava/Ryujinx.Ava.csproj +++ b/src/Ryujinx.Ava/Ryujinx.Ava.csproj @@ -43,7 +43,6 @@ - diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml b/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml index bbdb4c4a7..2dc95662a 100644 --- a/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml +++ b/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml @@ -4,7 +4,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox" xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" @@ -33,11 +32,10 @@ SelectionChanged="GameList_SelectionChanged"> - + VerticalAlignment="Top" + Orientation="Horizontal" /> diff --git a/src/Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml b/src/Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml index 818a21d69..3a9de3039 100644 --- a/src/Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml +++ b/src/Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml @@ -4,7 +4,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models" @@ -40,11 +39,10 @@ ItemsSource="{Binding Profiles}"> - + @@ -161,4 +159,4 @@ Content="{locale:Locale UserProfilesClose}" />
- \ No newline at end of file + diff --git a/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml b/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml index a0fd2a1ac..ace1094e4 100644 --- a/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml +++ b/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml @@ -3,7 +3,6 @@ xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" @@ -49,13 +48,11 @@ Grid.Column="0" Height="80" Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" /> - + HorizontalAlignment="Right" + VerticalAlignment="Center" + Orientation="Vertical"> - + Date: Thu, 8 Feb 2024 19:38:51 +0100 Subject: [PATCH 046/126] nuget: bump Microsoft.NET.Test.Sdk from 17.8.0 to 17.9.0 (#6265) Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.8.0 to 17.9.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.8.0...v17.9.0) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index bea91391c..80fadfe41 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -21,7 +21,7 @@ - + From dfc0819e7273e6887135546d32e2fed6e6d26073 Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Thu, 8 Feb 2024 18:45:18 +0000 Subject: [PATCH 047/126] Bump Ava (#6271) --- Directory.Packages.props | 10 +++++----- src/Ryujinx.Ava/Ryujinx.Ava.csproj | 3 --- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 80fadfe41..330c6196f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,11 +3,11 @@ true - - - - - + + + + + diff --git a/src/Ryujinx.Ava/Ryujinx.Ava.csproj b/src/Ryujinx.Ava/Ryujinx.Ava.csproj index 7f5224cfb..5d9767c8f 100644 --- a/src/Ryujinx.Ava/Ryujinx.Ava.csproj +++ b/src/Ryujinx.Ava/Ryujinx.Ava.csproj @@ -56,9 +56,6 @@ - - - From 609de33b0b55a405dff9e4f1e2ca3748d6b39828 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Thu, 8 Feb 2024 15:52:38 -0300 Subject: [PATCH 048/126] Implement BGR10A2 render target format (#6273) --- src/Ryujinx.Graphics.GAL/Format.cs | 4 ++++ src/Ryujinx.Graphics.Gpu/Engine/Types/ColorFormat.cs | 2 ++ src/Ryujinx.Graphics.OpenGL/FormatTable.cs | 1 + src/Ryujinx.Graphics.Vulkan/FormatTable.cs | 1 + 4 files changed, 8 insertions(+) diff --git a/src/Ryujinx.Graphics.GAL/Format.cs b/src/Ryujinx.Graphics.GAL/Format.cs index 99c89dcec..035543560 100644 --- a/src/Ryujinx.Graphics.GAL/Format.cs +++ b/src/Ryujinx.Graphics.GAL/Format.cs @@ -147,6 +147,7 @@ public enum Format A1B5G5R5Unorm, B8G8R8A8Unorm, B8G8R8A8Srgb, + B10G10R10A2Unorm, } public static class FormatExtensions @@ -260,6 +261,7 @@ public static int GetScalarSize(this Format format) case Format.R10G10B10A2Sint: case Format.R10G10B10A2Uscaled: case Format.R10G10B10A2Sscaled: + case Format.B10G10R10A2Unorm: return 4; case Format.S8Uint: @@ -451,6 +453,7 @@ public static bool IsRtColorCompatible(this Format format) case Format.R32G32Uint: case Format.B8G8R8A8Unorm: case Format.B8G8R8A8Srgb: + case Format.B10G10R10A2Unorm: case Format.R10G10B10A2Unorm: case Format.R10G10B10A2Uint: case Format.R8G8B8A8Unorm: @@ -611,6 +614,7 @@ public static bool IsBgr(this Format format) case Format.B5G5R5A1Unorm: case Format.B8G8R8A8Unorm: case Format.B8G8R8A8Srgb: + case Format.B10G10R10A2Unorm: return true; } diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Types/ColorFormat.cs b/src/Ryujinx.Graphics.Gpu/Engine/Types/ColorFormat.cs index c798384f0..273438a67 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Types/ColorFormat.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Types/ColorFormat.cs @@ -37,6 +37,7 @@ enum ColorFormat R16G16Sint = 0xdc, R16G16Uint = 0xdd, R16G16Float = 0xde, + B10G10R10A2Unorm = 0xdf, R11G11B10Float = 0xe0, R32Sint = 0xe3, R32Uint = 0xe4, @@ -104,6 +105,7 @@ public static FormatInfo Convert(this ColorFormat format) ColorFormat.R16G16Sint => new FormatInfo(Format.R16G16Sint, 1, 1, 4, 2), ColorFormat.R16G16Uint => new FormatInfo(Format.R16G16Uint, 1, 1, 4, 2), ColorFormat.R16G16Float => new FormatInfo(Format.R16G16Float, 1, 1, 4, 2), + ColorFormat.B10G10R10A2Unorm => new FormatInfo(Format.B10G10R10A2Unorm, 1, 1, 4, 4), ColorFormat.R11G11B10Float => new FormatInfo(Format.R11G11B10Float, 1, 1, 4, 3), ColorFormat.R32Sint => new FormatInfo(Format.R32Sint, 1, 1, 4, 1), ColorFormat.R32Uint => new FormatInfo(Format.R32Uint, 1, 1, 4, 1), diff --git a/src/Ryujinx.Graphics.OpenGL/FormatTable.cs b/src/Ryujinx.Graphics.OpenGL/FormatTable.cs index 3dac33b94..c7e3e4e28 100644 --- a/src/Ryujinx.Graphics.OpenGL/FormatTable.cs +++ b/src/Ryujinx.Graphics.OpenGL/FormatTable.cs @@ -161,6 +161,7 @@ static FormatTable() Add(Format.A1B5G5R5Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort5551)); Add(Format.B8G8R8A8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Rgba, PixelType.UnsignedByte)); Add(Format.B8G8R8A8Srgb, new FormatInfo(4, false, false, All.Srgb8Alpha8, PixelFormat.Rgba, PixelType.UnsignedByte)); + Add(Format.B10G10R10A2Unorm, new FormatInfo(4, false, false, All.Rgb10A2, PixelFormat.Rgba, PixelType.UnsignedInt2101010Reversed)); Add(Format.R8Unorm, SizedInternalFormat.R8); Add(Format.R8Uint, SizedInternalFormat.R8ui); diff --git a/src/Ryujinx.Graphics.Vulkan/FormatTable.cs b/src/Ryujinx.Graphics.Vulkan/FormatTable.cs index a12e3efd0..596a665fc 100644 --- a/src/Ryujinx.Graphics.Vulkan/FormatTable.cs +++ b/src/Ryujinx.Graphics.Vulkan/FormatTable.cs @@ -161,6 +161,7 @@ static FormatTable() Add(Format.A1B5G5R5Unorm, VkFormat.R5G5B5A1UnormPack16); Add(Format.B8G8R8A8Unorm, VkFormat.B8G8R8A8Unorm); Add(Format.B8G8R8A8Srgb, VkFormat.B8G8R8A8Srgb); + Add(Format.B10G10R10A2Unorm, VkFormat.A2R10G10B10UnormPack32); #pragma warning restore IDE0055 } From a0b3d82ee0d808b894de3a2e61b78f48aec5ffb0 Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Thu, 8 Feb 2024 19:01:03 +0000 Subject: [PATCH 049/126] Remove Vic Reference to Host1x (#6277) --- src/Ryujinx.Graphics.Vic/Ryujinx.Graphics.Vic.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Ryujinx.Graphics.Vic/Ryujinx.Graphics.Vic.csproj b/src/Ryujinx.Graphics.Vic/Ryujinx.Graphics.Vic.csproj index b3f39f2ef..a6c4fb2bb 100644 --- a/src/Ryujinx.Graphics.Vic/Ryujinx.Graphics.Vic.csproj +++ b/src/Ryujinx.Graphics.Vic/Ryujinx.Graphics.Vic.csproj @@ -8,7 +8,6 @@ - From ea07328aea4b6d70f5d5aa2c3c3874a748854ba1 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Thu, 8 Feb 2024 16:17:47 -0300 Subject: [PATCH 050/126] LightningJit: Reduce stack usage for Arm32 code (#6245) * Write/read guest state to context for sync points, stop reserving stack for them * Fix UsedGprsMask not being updated when allocating with preferencing * POP should be also considered a return --- src/Ryujinx.Cpu/LightningJit/Arm32/Block.cs | 5 ++ src/Ryujinx.Cpu/LightningJit/Arm32/Decoder.cs | 16 ++++- .../LightningJit/Arm32/MultiBlock.cs | 3 + .../LightningJit/Arm32/RegisterAllocator.cs | 1 + .../Arm32/Target/Arm64/Compiler.cs | 27 ++++++-- .../Arm32/Target/Arm64/InstEmitFlow.cs | 4 +- .../Arm32/Target/Arm64/InstEmitSystem.cs | 66 +++++++++++-------- 7 files changed, 86 insertions(+), 36 deletions(-) diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Block.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Block.cs index 4729f6940..c4568995c 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm32/Block.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Block.cs @@ -10,6 +10,7 @@ class Block public readonly List Instructions; public readonly bool EndsWithBranch; public readonly bool HasHostCall; + public readonly bool HasHostCallSkipContext; public readonly bool IsTruncated; public readonly bool IsLoopEnd; public readonly bool IsThumb; @@ -20,6 +21,7 @@ public Block( List instructions, bool endsWithBranch, bool hasHostCall, + bool hasHostCallSkipContext, bool isTruncated, bool isLoopEnd, bool isThumb) @@ -31,6 +33,7 @@ public Block( Instructions = instructions; EndsWithBranch = endsWithBranch; HasHostCall = hasHostCall; + HasHostCallSkipContext = hasHostCallSkipContext; IsTruncated = isTruncated; IsLoopEnd = isLoopEnd; IsThumb = isThumb; @@ -57,6 +60,7 @@ public Block( Instructions.GetRange(0, splitIndex), false, HasHostCall, + HasHostCallSkipContext, false, false, IsThumb); @@ -67,6 +71,7 @@ public Block( Instructions.GetRange(splitIndex, splitCount), EndsWithBranch, HasHostCall, + HasHostCallSkipContext, IsTruncated, IsLoopEnd, IsThumb); diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Decoder.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Decoder.cs index e0a18e666..8a2b389ad 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm32/Decoder.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Decoder.cs @@ -208,6 +208,7 @@ private static Block Decode(CpuPreset cpuPreset, IMemoryManager memoryManager, u InstMeta meta; InstFlags extraFlags = InstFlags.None; bool hasHostCall = false; + bool hasHostCallSkipContext = false; bool isTruncated = false; do @@ -246,9 +247,17 @@ private static Block Decode(CpuPreset cpuPreset, IMemoryManager memoryManager, u meta = InstTableA32.GetMeta(encoding, cpuPreset.Version, cpuPreset.Features); } - if (meta.Name.IsSystemOrCall() && !hasHostCall) + if (meta.Name.IsSystemOrCall()) { - hasHostCall = meta.Name.IsCall() || InstEmitSystem.NeedsCall(meta.Name); + if (!hasHostCall) + { + hasHostCall = InstEmitSystem.NeedsCall(meta.Name); + } + + if (!hasHostCallSkipContext) + { + hasHostCallSkipContext = meta.Name.IsCall() || InstEmitSystem.NeedsCallSkipContext(meta.Name); + } } insts.Add(new(encoding, meta.Name, meta.EmitFunc, meta.Flags | extraFlags)); @@ -259,8 +268,8 @@ private static Block Decode(CpuPreset cpuPreset, IMemoryManager memoryManager, u if (!isTruncated && IsBackwardsBranch(meta.Name, encoding)) { - hasHostCall = true; isLoopEnd = true; + hasHostCallSkipContext = true; } return new( @@ -269,6 +278,7 @@ private static Block Decode(CpuPreset cpuPreset, IMemoryManager memoryManager, u insts, !isTruncated, hasHostCall, + hasHostCallSkipContext, isTruncated, isLoopEnd, isThumb); diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/MultiBlock.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/MultiBlock.cs index a213c222c..ca25057fe 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm32/MultiBlock.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/MultiBlock.cs @@ -6,6 +6,7 @@ class MultiBlock { public readonly List Blocks; public readonly bool HasHostCall; + public readonly bool HasHostCallSkipContext; public readonly bool IsTruncated; public MultiBlock(List blocks) @@ -15,12 +16,14 @@ public MultiBlock(List blocks) Block block = blocks[0]; HasHostCall = block.HasHostCall; + HasHostCallSkipContext = block.HasHostCallSkipContext; for (int index = 1; index < blocks.Count; index++) { block = blocks[index]; HasHostCall |= block.HasHostCall; + HasHostCallSkipContext |= block.HasHostCallSkipContext; } block = blocks[^1]; diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/RegisterAllocator.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/RegisterAllocator.cs index 6c7057229..4a3f03b8a 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm32/RegisterAllocator.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/RegisterAllocator.cs @@ -106,6 +106,7 @@ private int AllocateTempRegisterWithPreferencing() if ((regMask & AbiConstants.ReservedRegsMask) == 0) { _gprMask |= regMask; + UsedGprsMask |= regMask; return firstCalleeSaved; } diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/Compiler.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/Compiler.cs index 1e8a89157..a668b5777 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/Compiler.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/Compiler.cs @@ -305,12 +305,23 @@ public static CompiledFunction Compile(CpuPreset cpuPreset, IMemoryManager memor ForceConditionalEnd(cgContext, ref lastCondition, lastConditionIp); } + int reservedStackSize = 0; + + if (multiBlock.HasHostCall) + { + reservedStackSize = CalculateStackSizeForCallSpill(regAlloc.UsedGprsMask, regAlloc.UsedFpSimdMask, UsablePStateMask); + } + else if (multiBlock.HasHostCallSkipContext) + { + reservedStackSize = 2 * sizeof(ulong); // Context and page table pointers. + } + RegisterSaveRestore rsr = new( regAlloc.UsedGprsMask & AbiConstants.GprCalleeSavedRegsMask, regAlloc.UsedFpSimdMask & AbiConstants.FpSimdCalleeSavedRegsMask, OperandType.FP64, - multiBlock.HasHostCall, - multiBlock.HasHostCall ? CalculateStackSizeForCallSpill(regAlloc.UsedGprsMask, regAlloc.UsedFpSimdMask, UsablePStateMask) : 0); + multiBlock.HasHostCall || multiBlock.HasHostCallSkipContext, + reservedStackSize); TailMerger tailMerger = new(); @@ -596,7 +607,8 @@ private static void RewriteIndirectBranchInstructionWithTarget(in Context contex name == InstName.Ldm || name == InstName.Ldmda || name == InstName.Ldmdb || - name == InstName.Ldmib) + name == InstName.Ldmib || + name == InstName.Pop) { // Arm32 does not have a return instruction, instead returns are implemented // either using BX LR (for leaf functions), or POP { ... PC }. @@ -711,7 +723,14 @@ private static void RewriteHostCall(in Context context, InstName name, BranchTyp switch (type) { case BranchType.SyncPoint: - InstEmitSystem.WriteSyncPoint(context.Writer, context.RegisterAllocator, context.TailMerger, context.GetReservedStackOffset()); + InstEmitSystem.WriteSyncPoint( + context.Writer, + ref asm, + context.RegisterAllocator, + context.TailMerger, + context.GetReservedStackOffset(), + context.StoreToContext, + context.LoadFromContext); break; case BranchType.SoftwareInterrupt: context.StoreToContext(); diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs index 81e44ba00..3b1ff5a2a 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs @@ -199,12 +199,12 @@ public unsafe static void WriteCallWithGuestAddress( } } - private static void WriteSpillSkipContext(ref Assembler asm, RegisterAllocator regAlloc, int spillOffset) + public static void WriteSpillSkipContext(ref Assembler asm, RegisterAllocator regAlloc, int spillOffset) { WriteSpillOrFillSkipContext(ref asm, regAlloc, spillOffset, spill: true); } - private static void WriteFillSkipContext(ref Assembler asm, RegisterAllocator regAlloc, int spillOffset) + public static void WriteFillSkipContext(ref Assembler asm, RegisterAllocator regAlloc, int spillOffset) { WriteSpillOrFillSkipContext(ref asm, regAlloc, spillOffset, spill: false); } diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitSystem.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitSystem.cs index be0976fd3..07f9f86a8 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitSystem.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitSystem.cs @@ -354,11 +354,18 @@ public static bool NeedsCall(InstName name) // All instructions that might do a host call should be included here. // That is required to reserve space on the stack for caller saved registers. + return name == InstName.Mrrc; + } + + public static bool NeedsCallSkipContext(InstName name) + { + // All instructions that might do a host call should be included here. + // That is required to reserve space on the stack for caller saved registers. + switch (name) { case InstName.Mcr: case InstName.Mrc: - case InstName.Mrrc: case InstName.Svc: case InstName.Udf: return true; @@ -372,7 +379,7 @@ public static void WriteBkpt(CodeWriter writer, RegisterAllocator regAlloc, Tail Assembler asm = new(writer); WriteCall(ref asm, regAlloc, GetBkptHandlerPtr(), skipContext: true, spillBaseOffset, null, pc, imm); - WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, skipContext: true, spillBaseOffset); + WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, spillBaseOffset); } public static void WriteSvc(CodeWriter writer, RegisterAllocator regAlloc, TailMerger tailMerger, int spillBaseOffset, uint pc, uint svcId) @@ -380,7 +387,7 @@ public static void WriteSvc(CodeWriter writer, RegisterAllocator regAlloc, TailM Assembler asm = new(writer); WriteCall(ref asm, regAlloc, GetSvcHandlerPtr(), skipContext: true, spillBaseOffset, null, pc, svcId); - WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, skipContext: true, spillBaseOffset); + WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, spillBaseOffset); } public static void WriteUdf(CodeWriter writer, RegisterAllocator regAlloc, TailMerger tailMerger, int spillBaseOffset, uint pc, uint imm) @@ -388,7 +395,7 @@ public static void WriteUdf(CodeWriter writer, RegisterAllocator regAlloc, TailM Assembler asm = new(writer); WriteCall(ref asm, regAlloc, GetUdfHandlerPtr(), skipContext: true, spillBaseOffset, null, pc, imm); - WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, skipContext: true, spillBaseOffset); + WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, spillBaseOffset); } public static void WriteReadCntpct(CodeWriter writer, RegisterAllocator regAlloc, int spillBaseOffset, int rt, int rt2) @@ -422,14 +429,14 @@ public static void WriteReadCntpct(CodeWriter writer, RegisterAllocator regAlloc WriteFill(ref asm, regAlloc, resultMask, skipContext: false, spillBaseOffset, tempRegister); } - public static void WriteSyncPoint(CodeWriter writer, RegisterAllocator regAlloc, TailMerger tailMerger, int spillBaseOffset) - { - Assembler asm = new(writer); - - WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, skipContext: false, spillBaseOffset); - } - - private static void WriteSyncPoint(CodeWriter writer, ref Assembler asm, RegisterAllocator regAlloc, TailMerger tailMerger, bool skipContext, int spillBaseOffset) + public static void WriteSyncPoint( + CodeWriter writer, + ref Assembler asm, + RegisterAllocator regAlloc, + TailMerger tailMerger, + int spillBaseOffset, + Action storeToContext = null, + Action loadFromContext = null) { int tempRegister = regAlloc.AllocateTempGprRegister(); @@ -440,7 +447,8 @@ private static void WriteSyncPoint(CodeWriter writer, ref Assembler asm, Registe int branchIndex = writer.InstructionPointer; asm.Cbnz(rt, 0); - WriteSpill(ref asm, regAlloc, 1u << tempRegister, skipContext, spillBaseOffset, tempRegister); + storeToContext?.Invoke(); + WriteSpill(ref asm, regAlloc, 1u << tempRegister, skipContext: true, spillBaseOffset, tempRegister); Operand rn = Register(tempRegister == 0 ? 1 : 0); @@ -449,7 +457,8 @@ private static void WriteSyncPoint(CodeWriter writer, ref Assembler asm, Registe tailMerger.AddConditionalZeroReturn(writer, asm, Register(0, OperandType.I32)); - WriteFill(ref asm, regAlloc, 1u << tempRegister, skipContext, spillBaseOffset, tempRegister); + WriteFill(ref asm, regAlloc, 1u << tempRegister, skipContext: true, spillBaseOffset, tempRegister); + loadFromContext?.Invoke(); asm.LdrRiUn(rt, Register(regAlloc.FixedContextRegister), NativeContextOffsets.CounterOffset); @@ -514,18 +523,31 @@ private static void WriteCall( private static void WriteSpill(ref Assembler asm, RegisterAllocator regAlloc, uint exceptMask, bool skipContext, int spillOffset, int tempRegister) { - WriteSpillOrFill(ref asm, regAlloc, skipContext, exceptMask, spillOffset, tempRegister, spill: true); + if (skipContext) + { + InstEmitFlow.WriteSpillSkipContext(ref asm, regAlloc, spillOffset); + } + else + { + WriteSpillOrFill(ref asm, regAlloc, exceptMask, spillOffset, tempRegister, spill: true); + } } private static void WriteFill(ref Assembler asm, RegisterAllocator regAlloc, uint exceptMask, bool skipContext, int spillOffset, int tempRegister) { - WriteSpillOrFill(ref asm, regAlloc, skipContext, exceptMask, spillOffset, tempRegister, spill: false); + if (skipContext) + { + InstEmitFlow.WriteFillSkipContext(ref asm, regAlloc, spillOffset); + } + else + { + WriteSpillOrFill(ref asm, regAlloc, exceptMask, spillOffset, tempRegister, spill: false); + } } private static void WriteSpillOrFill( ref Assembler asm, RegisterAllocator regAlloc, - bool skipContext, uint exceptMask, int spillOffset, int tempRegister, @@ -533,11 +555,6 @@ private static void WriteSpillOrFill( { uint gprMask = regAlloc.UsedGprsMask & ~(AbiConstants.GprCalleeSavedRegsMask | exceptMask); - if (skipContext) - { - gprMask &= ~Compiler.UsableGprsMask; - } - if (!spill) { // We must reload the status register before reloading the GPRs, @@ -600,11 +617,6 @@ private static void WriteSpillOrFill( uint fpSimdMask = regAlloc.UsedFpSimdMask; - if (skipContext) - { - fpSimdMask &= ~Compiler.UsableFpSimdMask; - } - while (fpSimdMask != 0) { int reg = BitOperations.TrailingZeroCount(fpSimdMask); From d29da11d5fcb3db5fdde9e79faaf3f598e50f4d1 Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Thu, 8 Feb 2024 19:36:59 +0000 Subject: [PATCH 051/126] Remove SDC (#6275) --- Directory.Packages.props | 1 - .../Helper/ShortcutHelper.cs | 23 ++++++++----------- .../Ryujinx.Ui.Common.csproj | 1 - 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 330c6196f..36db32d11 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -45,7 +45,6 @@ - diff --git a/src/Ryujinx.Ui.Common/Helper/ShortcutHelper.cs b/src/Ryujinx.Ui.Common/Helper/ShortcutHelper.cs index 60b928985..3d27d3ffb 100644 --- a/src/Ryujinx.Ui.Common/Helper/ShortcutHelper.cs +++ b/src/Ryujinx.Ui.Common/Helper/ShortcutHelper.cs @@ -2,15 +2,13 @@ using Ryujinx.Common.Configuration; using ShellLink; using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using System; using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Drawing2D; -using System.Drawing.Imaging; using System.IO; using System.Runtime.Versioning; -using Image = System.Drawing.Image; namespace Ryujinx.Ui.Common.Helper { @@ -23,12 +21,9 @@ private static void CreateShortcutWindows(string applicationFilePath, byte[] ico iconPath += ".ico"; MemoryStream iconDataStream = new(iconData); - using Image image = Image.FromStream(iconDataStream); - using Bitmap bitmap = new(128, 128); - using System.Drawing.Graphics graphic = System.Drawing.Graphics.FromImage(bitmap); - graphic.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphic.DrawImage(image, 0, 0, 128, 128); - SaveBitmapAsIcon(bitmap, iconPath); + var image = Image.Load(iconDataStream); + image.Mutate(x => x.Resize(128, 128)); + SaveBitmapAsIcon(image, iconPath); var shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath), iconPath, 0); shortcut.StringData.NameString = cleanedAppName; @@ -42,7 +37,7 @@ private static void CreateShortcutLinux(string applicationFilePath, byte[] iconD var desktopFile = EmbeddedResources.ReadAllText("Ryujinx.Ui.Common/shortcut-template.desktop"); iconPath += ".png"; - var image = SixLabors.ImageSharp.Image.Load(iconData); + var image = Image.Load(iconData); image.SaveAsPng(iconPath); using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop")); @@ -83,7 +78,7 @@ private static void CreateShortcutMacos(string appFilePath, byte[] iconData, str } const string IconName = "icon.png"; - var image = SixLabors.ImageSharp.Image.Load(iconData); + var image = Image.Load(iconData); image.SaveAsPng(Path.Combine(resourceFolderPath, IconName)); // plist file @@ -147,7 +142,7 @@ private static string GetArgsString(string appFilePath) /// The source bitmap image that will be saved as an .ico file /// The location that the new .ico file will be saved too (Make sure to include '.ico' in the path). [SupportedOSPlatform("windows")] - private static void SaveBitmapAsIcon(Bitmap source, string filePath) + private static void SaveBitmapAsIcon(Image source, string filePath) { // Code Modified From https://stackoverflow.com/a/11448060/368354 by Benlitz byte[] header = { 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 32, 0, 0, 0, 0, 0, 22, 0, 0, 0 }; @@ -155,7 +150,7 @@ private static void SaveBitmapAsIcon(Bitmap source, string filePath) fs.Write(header); // Writing actual data - source.Save(fs, ImageFormat.Png); + source.Save(fs, PngFormat.Instance); // Getting data length (file length minus header) long dataLength = fs.Length - header.Length; // Write it in the correct place diff --git a/src/Ryujinx.Ui.Common/Ryujinx.Ui.Common.csproj b/src/Ryujinx.Ui.Common/Ryujinx.Ui.Common.csproj index 1a8c216aa..24f26a3f5 100644 --- a/src/Ryujinx.Ui.Common/Ryujinx.Ui.Common.csproj +++ b/src/Ryujinx.Ui.Common/Ryujinx.Ui.Common.csproj @@ -57,7 +57,6 @@ - From a082e14ede0b850f2cba4bb7700b8fc16e39c10e Mon Sep 17 00:00:00 2001 From: Mary Guillemard Date: Thu, 8 Feb 2024 22:14:30 +0100 Subject: [PATCH 052/126] Revert "Bump Ava (#6271)" This reverts commit dfc0819e7273e6887135546d32e2fed6e6d26073. X popup position broke entirely (my fault oops), waiting for next release with a fix (see https://github.com/AvaloniaUI/Avalonia/pull/14551) --- Directory.Packages.props | 11 ++++++----- src/Ryujinx.Ava/Ryujinx.Ava.csproj | 3 +++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 36db32d11..80fadfe41 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,11 +3,11 @@ true - - - - - + + + + + @@ -45,6 +45,7 @@ + diff --git a/src/Ryujinx.Ava/Ryujinx.Ava.csproj b/src/Ryujinx.Ava/Ryujinx.Ava.csproj index 5d9767c8f..7f5224cfb 100644 --- a/src/Ryujinx.Ava/Ryujinx.Ava.csproj +++ b/src/Ryujinx.Ava/Ryujinx.Ava.csproj @@ -56,6 +56,9 @@ + + + From 0c73eba3dbf80a1ed1e765b713c6ee5c2966a759 Mon Sep 17 00:00:00 2001 From: Mary Guillemard Date: Sat, 10 Feb 2024 11:23:44 +0100 Subject: [PATCH 053/126] misc: Update to Ryujinx.Graphics.Nvdec.Dependencies 5.0.3-build14 (#6279) Signed-off-by: Mary Guillemard --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 80fadfe41..3fa3bfe2c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -32,7 +32,7 @@ - + From 4a6724622ee32397642fa9221e494c8fb6fbf126 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sat, 10 Feb 2024 15:38:58 -0300 Subject: [PATCH 054/126] Force CPU copy for non-identity DMA remap (#6293) --- .../Engine/Dma/DmaClass.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs b/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs index 6dae829ff..de395d574 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs @@ -277,6 +277,14 @@ private void DmaCopy(int argument) ReadOnlySpan srcSpan = memoryManager.GetSpan(srcGpuVa + (ulong)srcBaseOffset, srcSize, true); + // If remapping is disabled, we always copy the components directly, in order. + // If it's enabled, but the mapping is just XYZW, we also copy them in order. + bool isIdentityRemap = !remap || + (_state.State.SetRemapComponentsDstX == SetRemapComponentsDst.SrcX && + (dstComponents < 2 || _state.State.SetRemapComponentsDstY == SetRemapComponentsDst.SrcY) && + (dstComponents < 3 || _state.State.SetRemapComponentsDstZ == SetRemapComponentsDst.SrcZ) && + (dstComponents < 4 || _state.State.SetRemapComponentsDstW == SetRemapComponentsDst.SrcW)); + bool completeSource = IsTextureCopyComplete(src, srcLinear, srcBpp, srcStride, xCount, yCount); bool completeDest = IsTextureCopyComplete(dst, dstLinear, dstBpp, dstStride, xCount, yCount); @@ -284,7 +292,7 @@ private void DmaCopy(int argument) // but only if we are doing a complete copy, // and not for block linear to linear copies, since those are typically accessed from the CPU. - if (completeSource && completeDest && !(dstLinear && !srcLinear)) + if (completeSource && completeDest && !(dstLinear && !srcLinear) && isIdentityRemap) { var target = memoryManager.Physical.TextureCache.FindTexture( memoryManager, @@ -353,14 +361,6 @@ private void DmaCopy(int argument) TextureParams srcParams = new(srcRegionX, srcRegionY, srcBaseOffset, srcBpp, srcLinear, srcCalculator); TextureParams dstParams = new(dstRegionX, dstRegionY, dstBaseOffset, dstBpp, dstLinear, dstCalculator); - // If remapping is enabled, we always copy the components directly, in order. - // If it's enabled, but the mapping is just XYZW, we also copy them in order. - bool isIdentityRemap = !remap || - (_state.State.SetRemapComponentsDstX == SetRemapComponentsDst.SrcX && - (dstComponents < 2 || _state.State.SetRemapComponentsDstY == SetRemapComponentsDst.SrcY) && - (dstComponents < 3 || _state.State.SetRemapComponentsDstZ == SetRemapComponentsDst.SrcZ) && - (dstComponents < 4 || _state.State.SetRemapComponentsDstW == SetRemapComponentsDst.SrcW)); - if (isIdentityRemap) { // The order of the components doesn't change, so we can just copy directly From b82e789d4f023f1797c9352bb0e7155595d6f1a2 Mon Sep 17 00:00:00 2001 From: jcm Date: Sat, 10 Feb 2024 12:41:02 -0600 Subject: [PATCH 055/126] Load custom SDL mappings from application data folder (#6295) Co-authored-by: jcm --- src/Ryujinx.SDL2.Common/SDL2Driver.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx.SDL2.Common/SDL2Driver.cs b/src/Ryujinx.SDL2.Common/SDL2Driver.cs index 552deafdd..ed6d94190 100644 --- a/src/Ryujinx.SDL2.Common/SDL2Driver.cs +++ b/src/Ryujinx.SDL2.Common/SDL2Driver.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using System; using System.Collections.Concurrent; @@ -93,7 +94,7 @@ public void Initialize() SDL_EventState(SDL_EventType.SDL_CONTROLLERSENSORUPDATE, SDL_DISABLE); - string gamepadDbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SDL_GameControllerDB.txt"); + string gamepadDbPath = Path.Combine(AppDataManager.BaseDirPath, "SDL_GameControllerDB.txt"); if (File.Exists(gamepadDbPath)) { From bd6937ae5ca8bbc7c5e9b7792933d9971cf1768b Mon Sep 17 00:00:00 2001 From: Mary Guillemard Date: Sat, 10 Feb 2024 20:13:10 +0100 Subject: [PATCH 056/126] Make IOpenGLContext.HasContext context dependent (#6290) This makes IOpenGLContext.HasContext not static and be implementable. By doing this, we can support more than WGL and WGL. This also allows the SDL2 headless version to run under Wayland. Signed-off-by: Mary --- src/Ryujinx.Ava/UI/Renderer/SPBOpenGLContext.cs | 2 ++ .../BackgroundContextWorker.cs | 2 ++ src/Ryujinx.Graphics.OpenGL/IOpenGLContext.cs | 17 +---------------- src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs | 2 +- .../OpenGL/OpenGLWindow.cs | 2 ++ src/Ryujinx/Ui/SPBOpenGLContext.cs | 2 ++ 6 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/Ryujinx.Ava/UI/Renderer/SPBOpenGLContext.cs b/src/Ryujinx.Ava/UI/Renderer/SPBOpenGLContext.cs index 5ff756f24..63bf6cf7c 100644 --- a/src/Ryujinx.Ava/UI/Renderer/SPBOpenGLContext.cs +++ b/src/Ryujinx.Ava/UI/Renderer/SPBOpenGLContext.cs @@ -29,6 +29,8 @@ public void MakeCurrent() _context.MakeCurrent(_window); } + public bool HasContext() => _context.IsCurrent; + public static SPBOpenGLContext CreateBackgroundContext(OpenGLContextBase sharedContext) { OpenGLContextBase context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, 3, 3, OpenGLContextFlags.Compat, true, sharedContext); diff --git a/src/Ryujinx.Graphics.OpenGL/BackgroundContextWorker.cs b/src/Ryujinx.Graphics.OpenGL/BackgroundContextWorker.cs index ae647e388..f22e0df57 100644 --- a/src/Ryujinx.Graphics.OpenGL/BackgroundContextWorker.cs +++ b/src/Ryujinx.Graphics.OpenGL/BackgroundContextWorker.cs @@ -30,6 +30,8 @@ public BackgroundContextWorker(IOpenGLContext backgroundContext) _thread.Start(); } + public bool HasContext() => _backgroundContext.HasContext(); + private void Run() { InBackground = true; diff --git a/src/Ryujinx.Graphics.OpenGL/IOpenGLContext.cs b/src/Ryujinx.Graphics.OpenGL/IOpenGLContext.cs index a11b9cb29..525418d74 100644 --- a/src/Ryujinx.Graphics.OpenGL/IOpenGLContext.cs +++ b/src/Ryujinx.Graphics.OpenGL/IOpenGLContext.cs @@ -7,21 +7,6 @@ public interface IOpenGLContext : IDisposable { void MakeCurrent(); - // TODO: Support more APIs per platform. - static bool HasContext() - { - if (OperatingSystem.IsWindows()) - { - return WGLHelper.GetCurrentContext() != IntPtr.Zero; - } - else if (OperatingSystem.IsLinux()) - { - return GLXHelper.GetCurrentContext() != IntPtr.Zero; - } - else - { - return false; - } - } + bool HasContext(); } } diff --git a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs index 3d774aad4..eabcb3c10 100644 --- a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs +++ b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs @@ -248,7 +248,7 @@ public void BackgroundContextAction(Action action, bool alwaysBackground = false { // alwaysBackground is ignored, since we cannot switch from the current context. - if (IOpenGLContext.HasContext()) + if (_window.BackgroundContext.HasContext()) { action(); // We have a context already - use that (assuming it is the main one). } diff --git a/src/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs b/src/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs index 3fb93a0ec..7ea6e1481 100644 --- a/src/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs +++ b/src/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs @@ -96,6 +96,8 @@ public void MakeCurrent() } } + public bool HasContext() => SDL_GL_GetCurrentContext() != IntPtr.Zero; + public void Dispose() { SDL_GL_DeleteContext(_context); diff --git a/src/Ryujinx/Ui/SPBOpenGLContext.cs b/src/Ryujinx/Ui/SPBOpenGLContext.cs index 6f2db697a..0c5e57360 100644 --- a/src/Ryujinx/Ui/SPBOpenGLContext.cs +++ b/src/Ryujinx/Ui/SPBOpenGLContext.cs @@ -29,6 +29,8 @@ public void MakeCurrent() _context.MakeCurrent(_window); } + public bool HasContext() => _context.IsCurrent; + public static SPBOpenGLContext CreateBackgroundContext(OpenGLContextBase sharedContext) { OpenGLContextBase context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, 3, 3, OpenGLContextFlags.Compat, true, sharedContext); From e59dba42efee4b7226581fa019227c2bb44fbc6c Mon Sep 17 00:00:00 2001 From: Mary Guillemard Date: Sat, 10 Feb 2024 20:27:17 +0100 Subject: [PATCH 057/126] Set PointSize in shader on OpenGL (#6292) Previously we were only doing it for Vulkan, but it turns out that not setting it when PROGRAM_POINT_SIZE is set is considered UB on OpenGL Core. Signed-off-by: Mary --- src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs b/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs index f1dffb351..e1157eea4 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs @@ -80,9 +80,10 @@ private void EmitStart() return; } - if (TranslatorContext.Definitions.Stage == ShaderStage.Vertex && TranslatorContext.Options.TargetApi == TargetApi.Vulkan) + // Vulkan requires the point size to be always written on the shader if the primitive topology is points. + // OpenGL requires the point size to be always written on the shader if PROGRAM_POINT_SIZE is set. + if (TranslatorContext.Definitions.Stage == ShaderStage.Vertex) { - // Vulkan requires the point size to be always written on the shader if the primitive topology is points. this.Store(StorageKind.Output, IoVariable.PointSize, null, ConstF(TranslatorContext.Definitions.PointSize)); } From 7dc3a62c1435a1439d12273a266f685353c4cf07 Mon Sep 17 00:00:00 2001 From: Mary Guillemard Date: Sat, 10 Feb 2024 22:05:46 +0100 Subject: [PATCH 058/126] ci: Enable Linux ARM64 on build and release (#6291) * ci: Enable Linux ARM64 on build and release Signed-off-by: Mary * Address gdkchan comment Signed-off-by: Mary --------- Signed-off-by: Mary --- .github/workflows/build.yml | 56 ++++++++----------- .github/workflows/release.yml | 41 ++++++-------- src/Ryujinx.Ava/Ryujinx.Ava.csproj | 2 +- .../Ryujinx.Headless.SDL2.csproj | 2 +- src/Ryujinx/Ryujinx.csproj | 2 +- 5 files changed, 43 insertions(+), 60 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6124ae513..598f23c5e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,28 +10,17 @@ env: jobs: build: - name: ${{ matrix.OS_NAME }} (${{ matrix.configuration }}) - runs-on: ${{ matrix.os }} + name: ${{ matrix.platform.name }} (${{ matrix.configuration }}) + runs-on: ${{ matrix.platform.os }} timeout-minutes: 45 strategy: matrix: - os: [ubuntu-latest, macOS-latest, windows-latest] configuration: [Debug, Release] - include: - - os: ubuntu-latest - OS_NAME: Linux x64 - DOTNET_RUNTIME_IDENTIFIER: linux-x64 - RELEASE_ZIP_OS_NAME: linux_x64 - - - os: macOS-latest - OS_NAME: macOS x64 - DOTNET_RUNTIME_IDENTIFIER: osx-x64 - RELEASE_ZIP_OS_NAME: osx_x64 - - - os: windows-latest - OS_NAME: Windows x64 - DOTNET_RUNTIME_IDENTIFIER: win-x64 - RELEASE_ZIP_OS_NAME: win_x64 + platform: + - { name: win-x64, os: windows-latest, zip_os_name: win_x64 } + - { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 } + - { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 } + - { name: osx-x64, os: macOS-latest, zip_os_name: osx_x64 } fail-fast: false steps: @@ -52,12 +41,12 @@ jobs: - name: Change config filename run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs shell: bash - if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest' + if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' - name: Change config filename for macOS run: sed -r -i '' 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs shell: bash - if: github.event_name == 'pull_request' && matrix.os == 'macOS-latest' + if: github.event_name == 'pull_request' && matrix.platform.os == 'macOS-latest' - name: Build run: dotnet build -c "${{ matrix.configuration }}" -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER @@ -68,46 +57,47 @@ jobs: commands: dotnet test --no-build -c "${{ matrix.configuration }}" timeout-minutes: 10 retry-codes: 139 + if: matrix.platform.name != 'linux-arm64' - name: Publish Ryujinx - run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained true - if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest' + run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained true + if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' - name: Publish Ryujinx.Headless.SDL2 - run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true - if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest' + run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true + if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' - name: Publish Ryujinx.Ava - run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Ava --self-contained true - if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest' + run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_ava -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Ava --self-contained true + if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' - name: Set executable bit run: | chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh chmod +x ./publish_sdl2_headless/Ryujinx.Headless.SDL2 ./publish_sdl2_headless/Ryujinx.sh chmod +x ./publish_ava/Ryujinx.Ava ./publish_ava/Ryujinx.sh - if: github.event_name == 'pull_request' && matrix.os == 'ubuntu-latest' + if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest' - name: Upload Ryujinx artifact uses: actions/upload-artifact@v4 with: - name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }} + name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }} path: publish - if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest' + if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' - name: Upload Ryujinx.Headless.SDL2 artifact uses: actions/upload-artifact@v4 with: - name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }} + name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }} path: publish_sdl2_headless - if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest' + if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' - name: Upload Ryujinx.Ava artifact uses: actions/upload-artifact@v4 with: - name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }} + name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }} path: publish_ava - if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest' + if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' build_macos: name: macOS Universal (${{ matrix.configuration }}) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d6bcd3fa4..ac598684f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,22 +45,15 @@ jobs: }) release: - name: Release ${{ matrix.OS_NAME }} - runs-on: ${{ matrix.os }} + name: Release for ${{ matrix.platform.name }} + runs-on: ${{ matrix.platform.os }} timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }} strategy: matrix: - os: [ ubuntu-latest, windows-latest ] - include: - - os: ubuntu-latest - OS_NAME: Linux x64 - DOTNET_RUNTIME_IDENTIFIER: linux-x64 - RELEASE_ZIP_OS_NAME: linux_x64 - - - os: windows-latest - OS_NAME: Windows x64 - DOTNET_RUNTIME_IDENTIFIER: win-x64 - RELEASE_ZIP_OS_NAME: win_x64 + platform: + - { name: win-x64, os: windows-latest, zip_os_name: win_x64 } + - { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 } + - { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 } steps: - uses: actions/checkout@v4 @@ -93,42 +86,42 @@ jobs: - name: Publish run: | - dotnet publish -c Release -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_gtk/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained true - dotnet publish -c Release -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained true - dotnet publish -c Release -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Ava --self-contained true + dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_gtk/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained true + dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained true + dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Ava --self-contained true - name: Packing Windows builds - if: matrix.os == 'windows-latest' + if: matrix.platform.os == 'windows-latest' run: | pushd publish_gtk - 7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish + 7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish popd pushd publish_sdl2_headless - 7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish + 7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish popd pushd publish_ava - 7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish + 7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish popd shell: bash - name: Packing Linux builds - if: matrix.os == 'ubuntu-latest' + if: matrix.platform.os == 'ubuntu-latest' run: | pushd publish_gtk chmod +x publish/Ryujinx.sh publish/Ryujinx - tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish + tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish popd pushd publish_sdl2_headless chmod +x publish/Ryujinx.sh publish/Ryujinx.Headless.SDL2 - tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish + tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish popd pushd publish_ava chmod +x publish/Ryujinx.sh publish/Ryujinx.Ava - tar -czvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish + tar -czvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish popd shell: bash diff --git a/src/Ryujinx.Ava/Ryujinx.Ava.csproj b/src/Ryujinx.Ava/Ryujinx.Ava.csproj index 7f5224cfb..f0db71e8a 100644 --- a/src/Ryujinx.Ava/Ryujinx.Ava.csproj +++ b/src/Ryujinx.Ava/Ryujinx.Ava.csproj @@ -92,7 +92,7 @@ - + Always diff --git a/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj b/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj index 7b13df736..290886f85 100644 --- a/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj +++ b/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj @@ -48,7 +48,7 @@ - + Always diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj index 9890b761b..99e9ad14a 100644 --- a/src/Ryujinx/Ryujinx.csproj +++ b/src/Ryujinx/Ryujinx.csproj @@ -63,7 +63,7 @@ - + Always From 8dd1eb333c316c06e4a0f54485f4e7b6567fb60d Mon Sep 17 00:00:00 2001 From: Mary Guillemard Date: Sat, 10 Feb 2024 22:49:32 +0100 Subject: [PATCH 059/126] Add missing RID exclusions for linux-arm64 (#6298) * Add missing RID exclusions for linux-arm64 Signed-off-by: Mary Guillemard * Remove libsoundio.so from linux-arm64 deployment This is a x86_64 library. Signed-off-by: Mary Guillemard --------- Signed-off-by: Mary Guillemard --- .../Ryujinx.Audio.Backends.SoundIo.csproj | 6 +++--- src/Ryujinx.Ava/Ryujinx.Ava.csproj | 6 +++--- src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj | 2 +- src/Ryujinx.Ui.Common/Ryujinx.Ui.Common.csproj | 2 +- src/Ryujinx/Ryujinx.csproj | 8 ++++---- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj b/src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj index 1d92d9d2e..5c9423463 100644 --- a/src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj +++ b/src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj @@ -11,15 +11,15 @@ - + PreserveNewest libsoundio.dll - + PreserveNewest libsoundio.dylib - + PreserveNewest libsoundio.so diff --git a/src/Ryujinx.Ava/Ryujinx.Ava.csproj b/src/Ryujinx.Ava/Ryujinx.Ava.csproj index f0db71e8a..5665178a7 100644 --- a/src/Ryujinx.Ava/Ryujinx.Ava.csproj +++ b/src/Ryujinx.Ava/Ryujinx.Ava.csproj @@ -47,9 +47,9 @@ - + - + @@ -78,7 +78,7 @@ - + Always alsoft.ini diff --git a/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj b/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj index 290886f85..cc5a36518 100644 --- a/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj +++ b/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj @@ -34,7 +34,7 @@ - + diff --git a/src/Ryujinx.Ui.Common/Ryujinx.Ui.Common.csproj b/src/Ryujinx.Ui.Common/Ryujinx.Ui.Common.csproj index 24f26a3f5..387e998b0 100644 --- a/src/Ryujinx.Ui.Common/Ryujinx.Ui.Common.csproj +++ b/src/Ryujinx.Ui.Common/Ryujinx.Ui.Common.csproj @@ -45,7 +45,7 @@ - + diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj index 99e9ad14a..830e0e6c1 100644 --- a/src/Ryujinx/Ryujinx.csproj +++ b/src/Ryujinx/Ryujinx.csproj @@ -21,11 +21,11 @@ - + - - + + @@ -49,7 +49,7 @@ - + Always alsoft.ini From 6a8ac389e5038b34fdbbd6641fdf786dace64088 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sat, 10 Feb 2024 20:41:17 -0300 Subject: [PATCH 060/126] Fix mip offset/size for full 3D texture upload on Vulkan (#6294) --- src/Ryujinx.Graphics.Vulkan/TextureView.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx.Graphics.Vulkan/TextureView.cs b/src/Ryujinx.Graphics.Vulkan/TextureView.cs index 393db2611..ef511565c 100644 --- a/src/Ryujinx.Graphics.Vulkan/TextureView.cs +++ b/src/Ryujinx.Graphics.Vulkan/TextureView.cs @@ -839,7 +839,9 @@ public void CopyFromOrToBuffer( for (int level = 0; level < levels; level++) { - int mipSize = GetBufferDataLength(Info.GetMipSize2D(dstLevel + level) * dstLayers); + int mipSize = GetBufferDataLength(is3D && !singleSlice + ? Info.GetMipSize(dstLevel + level) + : Info.GetMipSize2D(dstLevel + level) * dstLayers); int endOffset = offset + mipSize; From 4ae9921063028362958c768bd7a1da4ff8dc941b Mon Sep 17 00:00:00 2001 From: sunshineinabox Date: Sat, 10 Feb 2024 15:45:14 -0800 Subject: [PATCH 061/126] Update Avalonia About Window like requested in PR #6267 (#6278) * Update About Window like requested in PR #6267 * Feedback * Apply suggestions from code review Co-authored-by: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> * Fix indents --------- Co-authored-by: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> --- src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml | 52 +++++++++++--------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml b/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml index ace1094e4..a06571408 100644 --- a/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml +++ b/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml @@ -44,31 +44,37 @@ - - - + + - - + Orientation="Vertical"> + + + + Date: Sat, 10 Feb 2024 23:52:11 +0000 Subject: [PATCH 062/126] Remove ReflectionBinding in Mod Manager (#6280) --- src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml b/src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml index d9f586408..0ed05ce3f 100644 --- a/src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml +++ b/src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml @@ -40,14 +40,14 @@ Name="EnableAllButton" MinWidth="90" Margin="5" - Command="{ReflectionBinding EnableAll}"> + Command="{Binding EnableAll}"> From 95c4912d58a535de4f5c03a2e380bdd39a543c12 Mon Sep 17 00:00:00 2001 From: lasers Date: Sat, 10 Feb 2024 17:57:23 -0600 Subject: [PATCH 063/126] Linux: Reorder available executables in Ryujinx.sh (#6171) * Avoid Ryujinx.Headless.SDL2 as a last resort in Ryujinx.desktop when you have more than one executable installed. --- distribution/linux/Ryujinx.sh | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/distribution/linux/Ryujinx.sh b/distribution/linux/Ryujinx.sh index a80cdcaec..6cce4d213 100755 --- a/distribution/linux/Ryujinx.sh +++ b/distribution/linux/Ryujinx.sh @@ -1,14 +1,21 @@ #!/bin/sh SCRIPT_DIR=$(dirname "$(realpath "$0")") -RYUJINX_BIN="Ryujinx" + +if [ -f "$SCRIPT_DIR/Ryujinx.Headless.SDL2" ]; then + RYUJINX_BIN="Ryujinx.Headless.SDL2" +fi if [ -f "$SCRIPT_DIR/Ryujinx.Ava" ]; then RYUJINX_BIN="Ryujinx.Ava" fi -if [ -f "$SCRIPT_DIR/Ryujinx.Headless.SDL2" ]; then - RYUJINX_BIN="Ryujinx.Headless.SDL2" +if [ -f "$SCRIPT_DIR/Ryujinx" ]; then + RYUJINX_BIN="Ryujinx" +fi + +if [ -z "$RYUJINX_BIN" ]; then + exit 1 fi COMMAND="env DOTNET_EnableAlternateStackCheck=1" @@ -17,4 +24,4 @@ if command -v gamemoderun > /dev/null 2>&1; then COMMAND="$COMMAND gamemoderun" fi -$COMMAND "$SCRIPT_DIR/$RYUJINX_BIN" "$@" +exec $COMMAND "$SCRIPT_DIR/$RYUJINX_BIN" "$@" From 84d6e8d121a1b329d26cc0e462aadd1108d99a04 Mon Sep 17 00:00:00 2001 From: jcm Date: Sat, 10 Feb 2024 19:17:19 -0600 Subject: [PATCH 064/126] Standardize logging locations across desktop platforms (#6238) * Standardize logging locations across desktop platforms * Return null instead of empty literal on exceptions * Remove LogDirectoryPath from LoggerModule * Catch exception when creating DirectoryInfo in FileLogTarget * Remove redundant log path vars, handle exception better, add null check * Address styling issues * Remove extra newline, quote file path in log, move directory check to OpenHelper * Add GetOrCreateLogsDir to get/create log directory during runtime * misc format changes * Update src/Ryujinx.Common/Configuration/AppDataManager.cs --------- Co-authored-by: jcm Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com> Co-authored-by: Ac_K --- .../UI/ViewModels/MainWindowViewModel.cs | 11 +- .../Configuration/AppDataManager.cs | 129 ++++++++++++++++-- .../Logging/Targets/FileLogTarget.cs | 13 +- src/Ryujinx.Headless.SDL2/Program.cs | 16 +-- .../Configuration/LoggerModule.cs | 24 ++-- src/Ryujinx/Ui/MainWindow.cs | 11 +- 6 files changed, 155 insertions(+), 49 deletions(-) diff --git a/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs index 2caee16cd..243d870a4 100644 --- a/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs @@ -1350,16 +1350,11 @@ public void OpenRyujinxFolder() public void OpenLogsFolder() { - string logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs"); - - if (LoggerModule.LogDirectoryPath != null) + string logPath = AppDataManager.GetOrCreateLogsDir(); + if (!string.IsNullOrEmpty(logPath)) { - logPath = LoggerModule.LogDirectoryPath; + OpenHelper.OpenFolder(logPath); } - - new DirectoryInfo(logPath).Create(); - - OpenHelper.OpenFolder(logPath); } public void ToggleDockMode() diff --git a/src/Ryujinx.Common/Configuration/AppDataManager.cs b/src/Ryujinx.Common/Configuration/AppDataManager.cs index 35aea3c2f..f3df0fc9d 100644 --- a/src/Ryujinx.Common/Configuration/AppDataManager.cs +++ b/src/Ryujinx.Common/Configuration/AppDataManager.cs @@ -30,6 +30,8 @@ public enum LaunchMode public static string KeysDirPath { get; private set; } public static string KeysDirPathUser { get; } + public static string LogsDirPath { get; private set; } + public const string DefaultNandDir = "bis"; public const string DefaultSdcardDir = "sdcard"; private const string DefaultModsDir = "mods"; @@ -46,15 +48,7 @@ static AppDataManager() public static void Initialize(string baseDirPath) { - string appDataPath; - if (OperatingSystem.IsMacOS()) - { - appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support"); - } - else - { - appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - } + string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); if (appDataPath.Length == 0) { @@ -118,9 +112,126 @@ public static void Initialize(string baseDirPath) SetupBasePaths(); } + public static string GetOrCreateLogsDir() + { + if (Directory.Exists(LogsDirPath)) + { + return LogsDirPath; + } + + Logger.Notice.Print(LogClass.Application, "Logging directory not found; attempting to create new logging directory."); + LogsDirPath = SetUpLogsDir(); + + return LogsDirPath; + } + + private static string SetUpLogsDir() + { + string logDir = ""; + + if (Mode == LaunchMode.Portable) + { + logDir = Path.Combine(BaseDirPath, "Logs"); + try + { + Directory.CreateDirectory(logDir); + } + catch + { + Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'"); + + return null; + } + } + else + { + if (OperatingSystem.IsMacOS()) + { + // NOTE: Should evaluate to "~/Library/Logs/Ryujinx/". + logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Logs", DefaultBaseDir); + try + { + Directory.CreateDirectory(logDir); + } + catch + { + Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'"); + logDir = ""; + } + + if (string.IsNullOrEmpty(logDir)) + { + // NOTE: Should evaluate to "~/Library/Application Support/Ryujinx/Logs". + logDir = Path.Combine(BaseDirPath, "Logs"); + + try + { + Directory.CreateDirectory(logDir); + } + catch + { + Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'"); + + return null; + } + } + } + else if (OperatingSystem.IsWindows()) + { + // NOTE: Should evaluate to a "Logs" directory in whatever directory Ryujinx was launched from. + logDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs"); + try + { + Directory.CreateDirectory(logDir); + } + catch + { + Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'"); + logDir = ""; + } + + if (string.IsNullOrEmpty(logDir)) + { + // NOTE: Should evaluate to "C:\Users\user\AppData\Roaming\Ryujinx\Logs". + logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir, "Logs"); + + try + { + Directory.CreateDirectory(logDir); + } + catch + { + Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'"); + + return null; + } + } + } + else if (OperatingSystem.IsLinux()) + { + // NOTE: Should evaluate to "~/.config/Ryujinx/Logs". + logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir, "Logs"); + + try + { + Directory.CreateDirectory(logDir); + } + catch + { + Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'"); + + return null; + } + } + } + + return logDir; + } + private static void SetupBasePaths() { Directory.CreateDirectory(BaseDirPath); + LogsDirPath = SetUpLogsDir(); Directory.CreateDirectory(GamesDirPath = Path.Combine(BaseDirPath, GamesDir)); Directory.CreateDirectory(ProfilesDirPath = Path.Combine(BaseDirPath, ProfilesDir)); Directory.CreateDirectory(KeysDirPath = Path.Combine(BaseDirPath, KeysDir)); diff --git a/src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs b/src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs index a4e8f7147..8d4ede96c 100644 --- a/src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs +++ b/src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs @@ -23,7 +23,18 @@ public FileLogTarget(string name, FileStream fileStream) public static FileStream PrepareLogFile(string path) { // Ensure directory is present - DirectoryInfo logDir = new(path); + DirectoryInfo logDir; + try + { + logDir = new DirectoryInfo(path); + } + catch (ArgumentException exception) + { + Logger.Warning?.Print(LogClass.Application, $"Logging directory path ('{path}') was invalid: {exception}"); + + return null; + } + try { logDir.Create(); diff --git a/src/Ryujinx.Headless.SDL2/Program.cs b/src/Ryujinx.Headless.SDL2/Program.cs index c23002757..85aff6712 100644 --- a/src/Ryujinx.Headless.SDL2/Program.cs +++ b/src/Ryujinx.Headless.SDL2/Program.cs @@ -427,16 +427,12 @@ static void LoadPlayerConfiguration(string inputProfileName, string inputId, Pla if (!option.DisableFileLog) { - FileStream logFile = FileLogTarget.PrepareLogFile(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs")); + string logDir = AppDataManager.LogsDirPath; + FileStream logFile = null; - if (logFile == null) + if (!string.IsNullOrEmpty(logDir)) { - logFile = FileLogTarget.PrepareLogFile(Path.Combine(AppDataManager.BaseDirPath, "Logs")); - - if (logFile == null) - { - Logger.Error?.Print(LogClass.Application, "No writable log directory available. Make sure either the application directory or the Ryujinx directory is writable."); - } + logFile = FileLogTarget.PrepareLogFile(logDir); } if (logFile != null) @@ -447,6 +443,10 @@ static void LoadPlayerConfiguration(string inputProfileName, string inputId, Pla AsyncLogTargetOverflowAction.Block )); } + else + { + Logger.Error?.Print(LogClass.Application, "No writable log directory available. Make sure either the Logs directory, Application Data, or the Ryujinx directory is writable."); + } } // Setup graphics configuration diff --git a/src/Ryujinx.Ui.Common/Configuration/LoggerModule.cs b/src/Ryujinx.Ui.Common/Configuration/LoggerModule.cs index f22ee83ae..2edcd07f0 100644 --- a/src/Ryujinx.Ui.Common/Configuration/LoggerModule.cs +++ b/src/Ryujinx.Ui.Common/Configuration/LoggerModule.cs @@ -9,8 +9,6 @@ namespace Ryujinx.Ui.Common.Configuration { public static class LoggerModule { - public static string LogDirectoryPath { get; private set; } - public static void Initialize() { ConfigurationState.Instance.Logger.EnableDebug.Event += ReloadEnableDebug; @@ -84,26 +82,22 @@ private static void ReloadFileLogger(object sender, ReactiveEventArgs e) { if (e.NewValue) { - string logDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs"); - FileStream logFile = FileLogTarget.PrepareLogFile(logDir); + string logDir = AppDataManager.LogsDirPath; + FileStream logFile = null; - if (logFile == null) + if (!string.IsNullOrEmpty(logDir)) { - logDir = Path.Combine(AppDataManager.BaseDirPath, "Logs"); logFile = FileLogTarget.PrepareLogFile(logDir); + } - if (logFile == null) - { - Logger.Error?.Print(LogClass.Application, "No writable log directory available. Make sure either the application directory or the Ryujinx directory is writable."); - LogDirectoryPath = null; - Logger.RemoveTarget("file"); + if (logFile == null) + { + Logger.Error?.Print(LogClass.Application, "No writable log directory available. Make sure either the Logs directory, Application Data, or the Ryujinx directory is writable."); + Logger.RemoveTarget("file"); - return; - } + return; } - LogDirectoryPath = logDir; - Logger.AddTarget(new AsyncLogTargetWrapper( new FileLogTarget("file", logFile), 1000, diff --git a/src/Ryujinx/Ui/MainWindow.cs b/src/Ryujinx/Ui/MainWindow.cs index 1ecbb9ea0..8bfe09cf1 100644 --- a/src/Ryujinx/Ui/MainWindow.cs +++ b/src/Ryujinx/Ui/MainWindow.cs @@ -1376,16 +1376,11 @@ private void Open_Ryu_Folder(object sender, EventArgs args) private void OpenLogsFolder_Pressed(object sender, EventArgs args) { - string logPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs"); - - if (LoggerModule.LogDirectoryPath != null) + string logPath = AppDataManager.GetOrCreateLogsDir(); + if (!string.IsNullOrEmpty(logPath)) { - logPath = LoggerModule.LogDirectoryPath; + OpenHelper.OpenFolder(logPath); } - - new DirectoryInfo(logPath).Create(); - - OpenHelper.OpenFolder(logPath); } private void Exit_Pressed(object sender, EventArgs args) From f06d22d6f01e657ebbc0c8ef082739cd468e47b5 Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Sun, 11 Feb 2024 02:09:18 +0000 Subject: [PATCH 065/126] Infra: Capitalisation Consistency (#6296) * Rename Ryujinx.UI.Common * Rename Ryujinx.UI.LocaleGenerator * Update in Files AboutWindow * Configuration State * Rename projects * Ryujinx/UI * Fix build * Main remaining inconsistencies * HLE.UI Namespace * HLE.UI Files * Namespace * Ryujinx.UI.Common.Configuration.UI * Ryujinx.UI.Common,Configuration.UI Files * More instances --- .github/labeler.yml | 2 +- Ryujinx.sln | 4 +- src/Ryujinx.Ava/App.axaml.cs | 16 +- src/Ryujinx.Ava/AppHost.cs | 14 +- src/Ryujinx.Ava/Common/ApplicationHelper.cs | 4 +- src/Ryujinx.Ava/Common/KeyboardHotkeyState.cs | 2 +- .../Common/Locale/LocaleManager.cs | 6 +- src/Ryujinx.Ava/Modules/Updater/Updater.cs | 4 +- src/Ryujinx.Ava/Program.cs | 8 +- src/Ryujinx.Ava/Ryujinx.Ava.csproj | 6 +- ...vaHostUiHandler.cs => AvaHostUIHandler.cs} | 14 +- .../Applet/AvaloniaDynamicTextInputHandler.cs | 2 +- ...aHostUiTheme.cs => AvaloniaHostUITheme.cs} | 6 +- .../UI/Applet/ControllerAppletDialog.axaml.cs | 4 +- .../UI/Applet/ErrorAppletWindow.axaml | 2 +- .../UI/Applet/SwkbdAppletDialog.axaml | 2 +- .../UI/Applet/SwkbdAppletDialog.axaml.cs | 2 +- .../Controls/ApplicationContextMenu.axaml.cs | 4 +- .../UI/Controls/ApplicationGridView.axaml.cs | 2 +- .../UI/Controls/ApplicationListView.axaml.cs | 2 +- .../UI/Controls/UpdateWaitWindow.axaml | 4 +- .../UI/Helpers/ApplicationOpenedEventArgs.cs | 2 +- .../UI/Helpers/ContentDialogHelper.cs | 2 +- .../UI/Helpers/LocalizedNeverConverter.cs | 2 +- src/Ryujinx.Ava/UI/Helpers/LoggerAdapter.cs | 4 +- src/Ryujinx.Ava/UI/Helpers/UserErrorDialog.cs | 4 +- .../Models/Generic/LastPlayedSortComparer.cs | 2 +- .../Models/Generic/TimePlayedSortComparer.cs | 2 +- src/Ryujinx.Ava/UI/Models/SaveModel.cs | 4 +- src/Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs | 4 +- .../UI/Renderer/EmbeddedWindowOpenGL.cs | 4 +- .../UI/Renderer/RendererHost.axaml.cs | 2 +- .../UI/ViewModels/AboutWindowViewModel.cs | 20 +- .../UI/ViewModels/AmiiboWindowViewModel.cs | 4 +- .../UI/ViewModels/ControllerInputViewModel.cs | 10 +- .../UI/ViewModels/MainWindowViewModel.cs | 58 ++--- .../UI/ViewModels/SettingsViewModel.cs | 12 +- .../UI/ViewModels/TitleUpdateViewModel.cs | 2 +- .../UI/Views/Main/MainMenuBarView.axaml.cs | 8 +- .../UI/Views/Main/MainStatusBarView.axaml.cs | 2 +- .../Views/Settings/SettingsHotkeysView.axaml | 2 +- src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml | 2 +- .../UI/Windows/AboutWindow.axaml.cs | 2 +- .../UI/Windows/AmiiboWindow.axaml.cs | 2 +- .../UI/Windows/CheatWindow.axaml.cs | 2 +- .../DownloadableContentManagerWindow.axaml.cs | 2 +- .../UI/Windows/MainWindow.axaml.cs | 46 ++-- .../UI/Windows/ModManagerWindow.axaml.cs | 2 +- src/Ryujinx.Ava/UI/Windows/StyleableWindow.cs | 4 +- .../UI/Windows/TitleUpdateWindow.axaml.cs | 2 +- .../Configuration/Hid/KeyboardHotkeys.cs | 2 +- src/Ryujinx.Common/Logging/LogClass.cs | 2 +- src/Ryujinx.HLE/HLEConfiguration.cs | 8 +- .../Applets/Controller/ControllerApplet.cs | 4 +- ...letUiArgs.cs => ControllerAppletUIArgs.cs} | 2 +- .../HOS/Applets/Error/ErrorApplet.cs | 8 +- src/Ryujinx.HLE/HOS/Applets/IApplet.cs | 2 +- .../SoftwareKeyboardApplet.cs | 16 +- .../SoftwareKeyboardRenderer.cs | 12 +- .../SoftwareKeyboardRendererBase.cs | 8 +- ...ardUiArgs.cs => SoftwareKeyboardUIArgs.cs} | 2 +- ...dUiState.cs => SoftwareKeyboardUIState.cs} | 4 +- .../ApplicationProxy/IApplicationFunctions.cs | 4 +- .../RootService/IApplicationDisplayService.cs | 2 +- src/Ryujinx.HLE/Switch.cs | 6 +- .../{Ui => UI}/DynamicTextChangedHandler.cs | 2 +- .../{Ui => UI}/IDynamicTextInputHandler.cs | 2 +- .../IHostUIHandler.cs} | 10 +- .../IHostUiTheme.cs => UI/IHostUITheme.cs} | 4 +- .../{Ui => UI}/Input/NpadButtonHandler.cs | 2 +- .../{Ui => UI}/Input/NpadReader.cs | 2 +- .../{Ui => UI}/KeyPressedHandler.cs | 2 +- .../{Ui => UI}/KeyReleasedHandler.cs | 2 +- .../{Ui => UI}/RenderingSurfaceInfo.cs | 2 +- src/Ryujinx.HLE/{Ui => UI}/ThemeColor.cs | 2 +- .../HeadlessDynamicTextInputHandler.cs | 2 +- .../HeadlessHostUiTheme.cs | 4 +- src/Ryujinx.Headless.SDL2/WindowBase.cs | 12 +- .../App/ApplicationAddedEventArgs.cs | 2 +- .../App/ApplicationCountUpdatedEventArgs.cs | 2 +- .../App/ApplicationData.cs | 4 +- .../App/ApplicationJsonSerializerContext.cs | 2 +- .../App/ApplicationLibrary.cs | 28 +- .../App/ApplicationMetadata.cs | 2 +- .../Configuration/AudioBackend.cs | 2 +- .../Configuration/ConfigurationFileFormat.cs | 6 +- .../ConfigurationFileFormatSettings.cs | 2 +- .../ConfigurationJsonSerializerContext.cs | 2 +- .../Configuration/ConfigurationState.cs | 240 +++++++++--------- .../Configuration/FileTypes.cs | 2 +- .../Configuration/LoggerModule.cs | 2 +- .../Configuration/System/Language.cs | 2 +- .../Configuration/System/Region.cs | 2 +- .../Configuration/UI}/ColumnSort.cs | 2 +- .../Configuration/UI}/GuiColumns.cs | 2 +- .../Configuration/UI}/ShownFileTypes.cs | 2 +- .../Configuration/UI}/WindowStartup.cs | 2 +- .../DiscordIntegrationModule.cs | 4 +- .../Extensions/FileTypeExtensions.cs | 4 +- .../Helper/CommandLineState.cs | 2 +- .../Helper/ConsoleHelper.cs | 2 +- .../Helper/FileAssociationHelper.cs | 2 +- .../Helper/LinuxHelper.cs | 2 +- .../Helper/ObjectiveC.cs | 2 +- .../Helper/OpenHelper.cs | 2 +- .../Helper/SetupValidator.cs | 2 +- .../Helper/ShortcutHelper.cs | 8 +- .../Helper/TitleHelper.cs | 2 +- .../Helper/ValueFormatUtils.cs | 2 +- .../Models/Amiibo/AmiiboApi.cs | 2 +- .../Models/Amiibo/AmiiboApiGamesSwitch.cs | 2 +- .../Models/Amiibo/AmiiboApiUsage.cs | 2 +- .../Models/Amiibo/AmiiboJson.cs | 2 +- .../Amiibo/AmiiboJsonSerializerContext.cs | 2 +- .../Github/GithubReleaseAssetJsonResponse.cs | 2 +- .../Github/GithubReleasesJsonResponse.cs | 2 +- .../GithubReleasesJsonSerializerContext.cs | 2 +- .../Resources/Controller_JoyConLeft.svg | 0 .../Resources/Controller_JoyConPair.svg | 0 .../Resources/Controller_JoyConRight.svg | 0 .../Resources/Controller_ProCon.svg | 0 .../Resources/Icon_NCA.png | Bin .../Resources/Icon_NRO.png | Bin .../Resources/Icon_NSO.png | Bin .../Resources/Icon_NSP.png | Bin .../Resources/Icon_XCI.png | Bin .../Resources/Logo_Amiibo.png | Bin .../Resources/Logo_Discord_Dark.png | Bin .../Resources/Logo_Discord_Light.png | Bin .../Resources/Logo_GitHub_Dark.png | Bin .../Resources/Logo_GitHub_Light.png | Bin .../Resources/Logo_Patreon_Dark.png | Bin .../Resources/Logo_Patreon_Light.png | Bin .../Resources/Logo_Ryujinx.png | Bin .../Resources/Logo_Twitter_Dark.png | Bin .../Resources/Logo_Twitter_Light.png | Bin .../Ryujinx.UI.Common.csproj} | 0 .../SystemInfo/LinuxSystemInfo.cs | 2 +- .../SystemInfo/MacOSSystemInfo.cs | 2 +- .../SystemInfo/SystemInfo.cs | 4 +- .../SystemInfo/WindowsSystemInfo.cs | 2 +- .../UserError.cs | 2 +- .../LocaleGenerator.cs | 2 +- .../Ryujinx.UI.LocaleGenerator.csproj} | 0 src/Ryujinx/Modules/Updater/UpdateDialog.cs | 8 +- src/Ryujinx/Modules/Updater/Updater.cs | 6 +- src/Ryujinx/Program.cs | 12 +- src/Ryujinx/Ryujinx.csproj | 30 +-- .../{Ui => UI}/Applet/ErrorAppletDialog.cs | 6 +- .../Applet/GtkDynamicTextInputHandler.cs | 6 +- .../Applet/GtkHostUIHandler.cs} | 18 +- .../Applet/GtkHostUITheme.cs} | 8 +- .../{Ui => UI}/Applet/SwkbdAppletDialog.cs | 2 +- src/Ryujinx/{Ui => UI}/Helper/MetalHelper.cs | 2 +- src/Ryujinx/{Ui => UI}/Helper/SortHelper.cs | 4 +- src/Ryujinx/{Ui => UI}/Helper/ThemeHelper.cs | 16 +- src/Ryujinx/{Ui => UI}/MainWindow.cs | 160 ++++++------ src/Ryujinx/{Ui => UI}/MainWindow.glade | 4 +- src/Ryujinx/{Ui => UI}/OpenGLRenderer.cs | 6 +- .../{Ui => UI}/OpenToolkitBindingsContext.cs | 2 +- src/Ryujinx/{Ui => UI}/RendererWidgetBase.cs | 18 +- src/Ryujinx/{Ui => UI}/SPBOpenGLContext.cs | 2 +- .../{Ui => UI}/StatusUpdatedEventArgs.cs | 2 +- src/Ryujinx/{Ui => UI}/VulkanRenderer.cs | 4 +- .../Widgets/GameTableContextMenu.Designer.cs | 2 +- .../Widgets/GameTableContextMenu.cs | 14 +- src/Ryujinx/{Ui => UI}/Widgets/GtkDialog.cs | 6 +- .../{Ui => UI}/Widgets/GtkInputDialog.cs | 2 +- .../{Ui => UI}/Widgets/ProfileDialog.cs | 8 +- .../{Ui => UI}/Widgets/ProfileDialog.glade | 0 .../{Ui => UI}/Widgets/RawInputToTextEntry.cs | 2 +- .../{Ui => UI}/Widgets/UserErrorDialog.cs | 6 +- .../Windows/AboutWindow.Designer.cs | 14 +- src/Ryujinx/{Ui => UI}/Windows/AboutWindow.cs | 6 +- .../Windows/AmiiboWindow.Designer.cs | 2 +- .../{Ui => UI}/Windows/AmiiboWindow.cs | 12 +- .../{Ui => UI}/Windows/AvatarWindow.cs | 6 +- src/Ryujinx/{Ui => UI}/Windows/CheatWindow.cs | 6 +- .../{Ui => UI}/Windows/CheatWindow.glade | 0 .../{Ui => UI}/Windows/ControllerWindow.cs | 18 +- .../{Ui => UI}/Windows/ControllerWindow.glade | 0 src/Ryujinx/{Ui => UI}/Windows/DlcWindow.cs | 6 +- .../{Ui => UI}/Windows/DlcWindow.glade | 0 .../{Ui => UI}/Windows/SettingsWindow.cs | 26 +- .../{Ui => UI}/Windows/SettingsWindow.glade | 0 .../{Ui => UI}/Windows/TitleUpdateWindow.cs | 8 +- .../Windows/TitleUpdateWindow.glade | 0 .../UserProfilesManagerWindow.Designer.cs | 2 +- .../Windows/UserProfilesManagerWindow.cs | 8 +- 189 files changed, 648 insertions(+), 648 deletions(-) rename src/Ryujinx.Ava/UI/Applet/{AvaHostUiHandler.cs => AvaHostUIHandler.cs} (94%) rename src/Ryujinx.Ava/UI/Applet/{AvaloniaHostUiTheme.cs => AvaloniaHostUITheme.cs} (92%) rename src/Ryujinx.HLE/HOS/Applets/Controller/{ControllerAppletUiArgs.cs => ControllerAppletUIArgs.cs} (88%) rename src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/{SoftwareKeyboardUiArgs.cs => SoftwareKeyboardUIArgs.cs} (90%) rename src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/{SoftwareKeyboardUiState.cs => SoftwareKeyboardUIState.cs} (89%) rename src/Ryujinx.HLE/{Ui => UI}/DynamicTextChangedHandler.cs (82%) rename src/Ryujinx.HLE/{Ui => UI}/IDynamicTextInputHandler.cs (94%) rename src/Ryujinx.HLE/{Ui/IHostUiHandler.cs => UI/IHostUIHandler.cs} (89%) rename src/Ryujinx.HLE/{Ui/IHostUiTheme.cs => UI/IHostUITheme.cs} (83%) rename src/Ryujinx.HLE/{Ui => UI}/Input/NpadButtonHandler.cs (81%) rename src/Ryujinx.HLE/{Ui => UI}/Input/NpadReader.cs (99%) rename src/Ryujinx.HLE/{Ui => UI}/KeyPressedHandler.cs (79%) rename src/Ryujinx.HLE/{Ui => UI}/KeyReleasedHandler.cs (79%) rename src/Ryujinx.HLE/{Ui => UI}/RenderingSurfaceInfo.cs (98%) rename src/Ryujinx.HLE/{Ui => UI}/ThemeColor.cs (93%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/App/ApplicationAddedEventArgs.cs (81%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/App/ApplicationCountUpdatedEventArgs.cs (85%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/App/ApplicationData.cs (98%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/App/ApplicationJsonSerializerContext.cs (88%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/App/ApplicationLibrary.cs (97%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/App/ApplicationMetadata.cs (98%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Configuration/AudioBackend.cs (85%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Configuration/ConfigurationFileFormat.cs (99%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Configuration/ConfigurationFileFormatSettings.cs (85%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Configuration/ConfigurationJsonSerializerContext.cs (85%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Configuration/ConfigurationState.cs (90%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Configuration/FileTypes.cs (81%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Configuration/LoggerModule.cs (98%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Configuration/System/Language.cs (91%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Configuration/System/Region.cs (85%) rename src/{Ryujinx.Ui.Common/Configuration/Ui => Ryujinx.UI.Common/Configuration/UI}/ColumnSort.cs (75%) rename src/{Ryujinx.Ui.Common/Configuration/Ui => Ryujinx.UI.Common/Configuration/UI}/GuiColumns.cs (91%) rename src/{Ryujinx.Ui.Common/Configuration/Ui => Ryujinx.UI.Common/Configuration/UI}/ShownFileTypes.cs (86%) rename src/{Ryujinx.Ui.Common/Configuration/Ui => Ryujinx.UI.Common/Configuration/UI}/WindowStartup.cs (86%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/DiscordIntegrationModule.cs (97%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Extensions/FileTypeExtensions.cs (91%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Helper/CommandLineState.cs (98%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Helper/ConsoleHelper.cs (97%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Helper/FileAssociationHelper.cs (99%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Helper/LinuxHelper.cs (98%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Helper/ObjectiveC.cs (99%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Helper/OpenHelper.cs (99%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Helper/SetupValidator.cs (99%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Helper/ShortcutHelper.cs (97%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Helper/TitleHelper.cs (96%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Helper/ValueFormatUtils.cs (99%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Models/Amiibo/AmiiboApi.cs (97%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Models/Amiibo/AmiiboApiGamesSwitch.cs (90%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Models/Amiibo/AmiiboApiUsage.cs (85%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Models/Amiibo/AmiiboJson.cs (88%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Models/Amiibo/AmiiboJsonSerializerContext.cs (80%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Models/Github/GithubReleaseAssetJsonResponse.cs (82%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Models/Github/GithubReleasesJsonResponse.cs (83%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Models/Github/GithubReleasesJsonSerializerContext.cs (85%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Resources/Controller_JoyConLeft.svg (100%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Resources/Controller_JoyConPair.svg (100%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Resources/Controller_JoyConRight.svg (100%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Resources/Controller_ProCon.svg (100%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Resources/Icon_NCA.png (100%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Resources/Icon_NRO.png (100%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Resources/Icon_NSO.png (100%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Resources/Icon_NSP.png (100%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Resources/Icon_XCI.png (100%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Resources/Logo_Amiibo.png (100%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Resources/Logo_Discord_Dark.png (100%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Resources/Logo_Discord_Light.png (100%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Resources/Logo_GitHub_Dark.png (100%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Resources/Logo_GitHub_Light.png (100%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Resources/Logo_Patreon_Dark.png (100%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Resources/Logo_Patreon_Light.png (100%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Resources/Logo_Ryujinx.png (100%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Resources/Logo_Twitter_Dark.png (100%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/Resources/Logo_Twitter_Light.png (100%) rename src/{Ryujinx.Ui.Common/Ryujinx.Ui.Common.csproj => Ryujinx.UI.Common/Ryujinx.UI.Common.csproj} (100%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/SystemInfo/LinuxSystemInfo.cs (98%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/SystemInfo/MacOSSystemInfo.cs (99%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/SystemInfo/SystemInfo.cs (97%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/SystemInfo/WindowsSystemInfo.cs (98%) rename src/{Ryujinx.Ui.Common => Ryujinx.UI.Common}/UserError.cs (96%) rename src/{Ryujinx.Ui.LocaleGenerator => Ryujinx.UI.LocaleGenerator}/LocaleGenerator.cs (97%) rename src/{Ryujinx.Ui.LocaleGenerator/Ryujinx.Ui.LocaleGenerator.csproj => Ryujinx.UI.LocaleGenerator/Ryujinx.UI.LocaleGenerator.csproj} (100%) rename src/Ryujinx/{Ui => UI}/Applet/ErrorAppletDialog.cs (85%) rename src/Ryujinx/{Ui => UI}/Applet/GtkDynamicTextInputHandler.cs (97%) rename src/Ryujinx/{Ui/Applet/GtkHostUiHandler.cs => UI/Applet/GtkHostUIHandler.cs} (93%) rename src/Ryujinx/{Ui/Applet/GtkHostUiTheme.cs => UI/Applet/GtkHostUITheme.cs} (96%) rename src/Ryujinx/{Ui => UI}/Applet/SwkbdAppletDialog.cs (99%) rename src/Ryujinx/{Ui => UI}/Helper/MetalHelper.cs (99%) rename src/Ryujinx/{Ui => UI}/Helper/SortHelper.cs (94%) rename src/Ryujinx/{Ui => UI}/Helper/ThemeHelper.cs (66%) rename src/Ryujinx/{Ui => UI}/MainWindow.cs (93%) rename src/Ryujinx/{Ui => UI}/MainWindow.glade (99%) rename src/Ryujinx/{Ui => UI}/OpenGLRenderer.cs (96%) rename src/Ryujinx/{Ui => UI}/OpenToolkitBindingsContext.cs (95%) rename src/Ryujinx/{Ui => UI}/RendererWidgetBase.cs (99%) rename src/Ryujinx/{Ui => UI}/SPBOpenGLContext.cs (98%) rename src/Ryujinx/{Ui => UI}/StatusUpdatedEventArgs.cs (97%) rename src/Ryujinx/{Ui => UI}/VulkanRenderer.cs (98%) rename src/Ryujinx/{Ui => UI}/Widgets/GameTableContextMenu.Designer.cs (99%) rename src/Ryujinx/{Ui => UI}/Widgets/GameTableContextMenu.cs (98%) rename src/Ryujinx/{Ui => UI}/Widgets/GtkDialog.cs (96%) rename src/Ryujinx/{Ui => UI}/Widgets/GtkInputDialog.cs (97%) rename src/Ryujinx/{Ui => UI}/Widgets/ProfileDialog.cs (89%) rename src/Ryujinx/{Ui => UI}/Widgets/ProfileDialog.glade (100%) rename src/Ryujinx/{Ui => UI}/Widgets/RawInputToTextEntry.cs (95%) rename src/Ryujinx/{Ui => UI}/Widgets/UserErrorDialog.cs (97%) rename src/Ryujinx/{Ui => UI}/Windows/AboutWindow.Designer.cs (97%) rename src/Ryujinx/{Ui => UI}/Windows/AboutWindow.cs (95%) rename src/Ryujinx/{Ui => UI}/Windows/AmiiboWindow.Designer.cs (99%) rename src/Ryujinx/{Ui => UI}/Windows/AmiiboWindow.cs (98%) rename src/Ryujinx/{Ui => UI}/Windows/AvatarWindow.cs (98%) rename src/Ryujinx/{Ui => UI}/Windows/CheatWindow.cs (97%) rename src/Ryujinx/{Ui => UI}/Windows/CheatWindow.glade (100%) rename src/Ryujinx/{Ui => UI}/Windows/ControllerWindow.cs (99%) rename src/Ryujinx/{Ui => UI}/Windows/ControllerWindow.glade (100%) rename src/Ryujinx/{Ui => UI}/Windows/DlcWindow.cs (98%) rename src/Ryujinx/{Ui => UI}/Windows/DlcWindow.glade (100%) rename src/Ryujinx/{Ui => UI}/Windows/SettingsWindow.cs (98%) rename src/Ryujinx/{Ui => UI}/Windows/SettingsWindow.glade (100%) rename src/Ryujinx/{Ui => UI}/Windows/TitleUpdateWindow.cs (98%) rename src/Ryujinx/{Ui => UI}/Windows/TitleUpdateWindow.glade (100%) rename src/Ryujinx/{Ui => UI}/Windows/UserProfilesManagerWindow.Designer.cs (99%) rename src/Ryujinx/{Ui => UI}/Windows/UserProfilesManagerWindow.cs (98%) diff --git a/.github/labeler.yml b/.github/labeler.yml index 027448437..b967cc776 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -20,7 +20,7 @@ gpu: gui: - changed-files: - - any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.Ui.Common/**', 'src/Ryujinx.Ui.LocaleGenerator/**', 'src/Ryujinx.Ava/**'] + - any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.UI.Common/**', 'src/Ryujinx.UI.LocaleGenerator/**', 'src/Ryujinx.Ava/**'] horizon: - changed-files: diff --git a/Ryujinx.sln b/Ryujinx.sln index bb196cabc..47a5c714c 100644 --- a/Ryujinx.sln +++ b/Ryujinx.sln @@ -71,7 +71,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.FFmp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ava", "src\Ryujinx.Ava\Ryujinx.Ava.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ui.Common", "src\Ryujinx.Ui.Common\Ryujinx.Ui.Common.csproj", "{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.UI.Common", "src\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj", "{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Generators", "src\Ryujinx.Horizon.Generators\Ryujinx.Horizon.Generators.csproj", "{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}" EndProject @@ -79,7 +79,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Vulkan", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spv.Generator", "src\Spv.Generator\Spv.Generator.csproj", "{2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ui.LocaleGenerator", "src\Ryujinx.Ui.LocaleGenerator\Ryujinx.Ui.LocaleGenerator.csproj", "{77D01AD9-2C98-478E-AE1D-8F7100738FB4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.UI.LocaleGenerator", "src\Ryujinx.UI.LocaleGenerator\Ryujinx.UI.LocaleGenerator.csproj", "{77D01AD9-2C98-478E-AE1D-8F7100738FB4}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Common", "src\Ryujinx.Horizon.Common\Ryujinx.Horizon.Common.csproj", "{77F96ECE-4952-42DB-A528-DED25572A573}" EndProject diff --git a/src/Ryujinx.Ava/App.axaml.cs b/src/Ryujinx.Ava/App.axaml.cs index 54e61c67c..387a6dc14 100644 --- a/src/Ryujinx.Ava/App.axaml.cs +++ b/src/Ryujinx.Ava/App.axaml.cs @@ -8,8 +8,8 @@ using Ryujinx.Ava.UI.Windows; using Ryujinx.Common; using Ryujinx.Common.Logging; -using Ryujinx.Ui.Common.Configuration; -using Ryujinx.Ui.Common.Helper; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; using System; using System.Diagnostics; @@ -42,9 +42,9 @@ public override void OnFrameworkInitializationCompleted() { ApplyConfiguredTheme(); - ConfigurationState.Instance.Ui.BaseStyle.Event += ThemeChanged_Event; - ConfigurationState.Instance.Ui.CustomThemePath.Event += ThemeChanged_Event; - ConfigurationState.Instance.Ui.EnableCustomTheme.Event += CustomThemeChanged_Event; + ConfigurationState.Instance.UI.BaseStyle.Event += ThemeChanged_Event; + ConfigurationState.Instance.UI.CustomThemePath.Event += ThemeChanged_Event; + ConfigurationState.Instance.UI.EnableCustomTheme.Event += CustomThemeChanged_Event; } } @@ -88,13 +88,13 @@ private void ApplyConfiguredTheme() { try { - string baseStyle = ConfigurationState.Instance.Ui.BaseStyle; + string baseStyle = ConfigurationState.Instance.UI.BaseStyle; if (string.IsNullOrWhiteSpace(baseStyle)) { - ConfigurationState.Instance.Ui.BaseStyle.Value = "Dark"; + ConfigurationState.Instance.UI.BaseStyle.Value = "Dark"; - baseStyle = ConfigurationState.Instance.Ui.BaseStyle; + baseStyle = ConfigurationState.Instance.UI.BaseStyle; } RequestedThemeVariant = baseStyle switch diff --git a/src/Ryujinx.Ava/AppHost.cs b/src/Ryujinx.Ava/AppHost.cs index 696a4046f..04cec9579 100644 --- a/src/Ryujinx.Ava/AppHost.cs +++ b/src/Ryujinx.Ava/AppHost.cs @@ -34,10 +34,10 @@ using Ryujinx.HLE.HOS.SystemState; using Ryujinx.Input; using Ryujinx.Input.HLE; -using Ryujinx.Ui.App.Common; -using Ryujinx.Ui.Common; -using Ryujinx.Ui.Common.Configuration; -using Ryujinx.Ui.Common.Helper; +using Ryujinx.UI.App.Common; +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; @@ -1070,7 +1070,7 @@ private bool UpdateFrame() case KeyboardHotkeyState.Screenshot: ScreenshotRequested = true; break; - case KeyboardHotkeyState.ShowUi: + case KeyboardHotkeyState.ShowUI: _viewModel.ShowMenuAndStatusBar = !_viewModel.ShowMenuAndStatusBar; break; case KeyboardHotkeyState.Pause: @@ -1160,9 +1160,9 @@ private KeyboardHotkeyState GetHotkeyState() { state = KeyboardHotkeyState.Screenshot; } - else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUi)) + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI)) { - state = KeyboardHotkeyState.ShowUi; + state = KeyboardHotkeyState.ShowUI; } else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause)) { diff --git a/src/Ryujinx.Ava/Common/ApplicationHelper.cs b/src/Ryujinx.Ava/Common/ApplicationHelper.cs index 91ca8f4d5..622a6a024 100644 --- a/src/Ryujinx.Ava/Common/ApplicationHelper.cs +++ b/src/Ryujinx.Ava/Common/ApplicationHelper.cs @@ -18,8 +18,8 @@ using Ryujinx.Common.Logging; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.Services.Account.Acc; -using Ryujinx.Ui.App.Common; -using Ryujinx.Ui.Common.Helper; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Common.Helper; using System; using System.Buffers; using System.IO; diff --git a/src/Ryujinx.Ava/Common/KeyboardHotkeyState.cs b/src/Ryujinx.Ava/Common/KeyboardHotkeyState.cs index b24016404..6e4920988 100644 --- a/src/Ryujinx.Ava/Common/KeyboardHotkeyState.cs +++ b/src/Ryujinx.Ava/Common/KeyboardHotkeyState.cs @@ -5,7 +5,7 @@ public enum KeyboardHotkeyState None, ToggleVSync, Screenshot, - ShowUi, + ShowUI, Pause, ToggleMute, ResScaleUp, diff --git a/src/Ryujinx.Ava/Common/Locale/LocaleManager.cs b/src/Ryujinx.Ava/Common/Locale/LocaleManager.cs index 0e613838d..b2f3e7ab9 100644 --- a/src/Ryujinx.Ava/Common/Locale/LocaleManager.cs +++ b/src/Ryujinx.Ava/Common/Locale/LocaleManager.cs @@ -1,7 +1,7 @@ using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Common; using Ryujinx.Common.Utilities; -using Ryujinx.Ui.Common.Configuration; +using Ryujinx.UI.Common.Configuration; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -38,9 +38,9 @@ public void Load() // If the view is loaded with the UI Previewer detached, then override it with the saved one or default. if (Program.PreviewerDetached) { - if (!string.IsNullOrEmpty(ConfigurationState.Instance.Ui.LanguageCode.Value)) + if (!string.IsNullOrEmpty(ConfigurationState.Instance.UI.LanguageCode.Value)) { - localeLanguageCode = ConfigurationState.Instance.Ui.LanguageCode.Value; + localeLanguageCode = ConfigurationState.Instance.UI.LanguageCode.Value; } else { diff --git a/src/Ryujinx.Ava/Modules/Updater/Updater.cs b/src/Ryujinx.Ava/Modules/Updater/Updater.cs index ad33b1013..bd211fa5b 100644 --- a/src/Ryujinx.Ava/Modules/Updater/Updater.cs +++ b/src/Ryujinx.Ava/Modules/Updater/Updater.cs @@ -10,8 +10,8 @@ using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.Common.Utilities; -using Ryujinx.Ui.Common.Helper; -using Ryujinx.Ui.Common.Models.Github; +using Ryujinx.UI.Common.Helper; +using Ryujinx.UI.Common.Models.Github; using System; using System.Collections.Generic; using System.Diagnostics; diff --git a/src/Ryujinx.Ava/Program.cs b/src/Ryujinx.Ava/Program.cs index d85749eff..7751bb520 100644 --- a/src/Ryujinx.Ava/Program.cs +++ b/src/Ryujinx.Ava/Program.cs @@ -9,10 +9,10 @@ using Ryujinx.Common.SystemInterop; using Ryujinx.Modules; using Ryujinx.SDL2.Common; -using Ryujinx.Ui.Common; -using Ryujinx.Ui.Common.Configuration; -using Ryujinx.Ui.Common.Helper; -using Ryujinx.Ui.Common.SystemInfo; +using Ryujinx.UI.Common; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; +using Ryujinx.UI.Common.SystemInfo; using System; using System.IO; using System.Runtime.InteropServices; diff --git a/src/Ryujinx.Ava/Ryujinx.Ava.csproj b/src/Ryujinx.Ava/Ryujinx.Ava.csproj index 5665178a7..91c2744f0 100644 --- a/src/Ryujinx.Ava/Ryujinx.Ava.csproj +++ b/src/Ryujinx.Ava/Ryujinx.Ava.csproj @@ -73,8 +73,8 @@ - - + + @@ -103,7 +103,7 @@ - + Designer diff --git a/src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs b/src/Ryujinx.Ava/UI/Applet/AvaHostUIHandler.cs similarity index 94% rename from src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs rename to src/Ryujinx.Ava/UI/Applet/AvaHostUIHandler.cs index e11939104..4bcc35a7a 100644 --- a/src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs +++ b/src/Ryujinx.Ava/UI/Applet/AvaHostUIHandler.cs @@ -8,26 +8,26 @@ using Ryujinx.HLE; using Ryujinx.HLE.HOS.Applets; using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; -using Ryujinx.HLE.Ui; +using Ryujinx.HLE.UI; using System; using System.Threading; namespace Ryujinx.Ava.UI.Applet { - internal class AvaHostUiHandler : IHostUiHandler + internal class AvaHostUIHandler : IHostUIHandler { private readonly MainWindow _parent; - public IHostUiTheme HostUiTheme { get; } + public IHostUITheme HostUITheme { get; } - public AvaHostUiHandler(MainWindow parent) + public AvaHostUIHandler(MainWindow parent) { _parent = parent; - HostUiTheme = new AvaloniaHostUiTheme(parent); + HostUITheme = new AvaloniaHostUITheme(parent); } - public bool DisplayMessageDialog(ControllerAppletUiArgs args) + public bool DisplayMessageDialog(ControllerAppletUIArgs args) { ManualResetEvent dialogCloseEvent = new(false); @@ -110,7 +110,7 @@ public bool DisplayMessageDialog(string title, string message) return okPressed; } - public bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText) + public bool DisplayInputDialog(SoftwareKeyboardUIArgs args, out string userText) { ManualResetEvent dialogCloseEvent = new(false); diff --git a/src/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs b/src/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs index 2411659f7..531d00611 100644 --- a/src/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs +++ b/src/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs @@ -5,7 +5,7 @@ using Ryujinx.Ava.Input; using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Windows; -using Ryujinx.HLE.Ui; +using Ryujinx.HLE.UI; using System; using System.Threading; using HidKey = Ryujinx.Common.Configuration.Hid.Key; diff --git a/src/Ryujinx.Ava/UI/Applet/AvaloniaHostUiTheme.cs b/src/Ryujinx.Ava/UI/Applet/AvaloniaHostUITheme.cs similarity index 92% rename from src/Ryujinx.Ava/UI/Applet/AvaloniaHostUiTheme.cs rename to src/Ryujinx.Ava/UI/Applet/AvaloniaHostUITheme.cs index 4ee177d72..016fb4842 100644 --- a/src/Ryujinx.Ava/UI/Applet/AvaloniaHostUiTheme.cs +++ b/src/Ryujinx.Ava/UI/Applet/AvaloniaHostUITheme.cs @@ -1,13 +1,13 @@ using Avalonia.Media; using Ryujinx.Ava.UI.Windows; -using Ryujinx.HLE.Ui; +using Ryujinx.HLE.UI; using System; namespace Ryujinx.Ava.UI.Applet { - class AvaloniaHostUiTheme : IHostUiTheme + class AvaloniaHostUITheme : IHostUITheme { - public AvaloniaHostUiTheme(MainWindow parent) + public AvaloniaHostUITheme(MainWindow parent) { FontFamily = OperatingSystem.IsWindows() && OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000) ? "Segoe UI Variable" : parent.FontFamily.Name; DefaultBackgroundColor = BrushToThemeColor(parent.Background); diff --git a/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs b/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs index f7d751a66..279af07c3 100644 --- a/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs +++ b/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs @@ -36,7 +36,7 @@ internal partial class ControllerAppletDialog : UserControl private readonly MainWindow _mainWindow; - public ControllerAppletDialog(MainWindow mainWindow, ControllerAppletUiArgs args) + public ControllerAppletDialog(MainWindow mainWindow, ControllerAppletUIArgs args) { if (args.PlayerCountMin == args.PlayerCountMax) { @@ -69,7 +69,7 @@ public ControllerAppletDialog(MainWindow mainWindow) InitializeComponent(); } - public static async Task ShowControllerAppletDialog(MainWindow window, ControllerAppletUiArgs args) + public static async Task ShowControllerAppletDialog(MainWindow window, ControllerAppletUIArgs args) { ContentDialog contentDialog = new(); UserResult result = UserResult.Cancel; diff --git a/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml b/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml index 6186b7d93..51f370519 100644 --- a/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml +++ b/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml @@ -34,7 +34,7 @@ Height="80" MinWidth="50" Margin="5,10,20,10" - Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" /> + Source="resm:Ryujinx.UI.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.UI.Common" /> + Source="resm:Ryujinx.UI.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.UI.Common" /> ShowInputDialog(string title, SoftwareKeyboardUiArgs args) + public static async Task<(UserResult Result, string Input)> ShowInputDialog(string title, SoftwareKeyboardUIArgs args) { ContentDialog contentDialog = new(); diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs b/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs index 01d977091..1fb9d3b31 100644 --- a/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs +++ b/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs @@ -11,8 +11,8 @@ using Ryujinx.Ava.UI.Windows; using Ryujinx.Common.Configuration; using Ryujinx.HLE.HOS; -using Ryujinx.Ui.App.Common; -using Ryujinx.Ui.Common.Helper; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Common.Helper; using System; using System.Collections.Generic; using System.Globalization; diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml.cs b/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml.cs index 821d6fd9f..ee15bc8d5 100644 --- a/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml.cs +++ b/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml.cs @@ -3,7 +3,7 @@ using Avalonia.Interactivity; using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.Ui.App.Common; +using Ryujinx.UI.App.Common; using System; namespace Ryujinx.Ava.UI.Controls diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml.cs b/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml.cs index dd60503af..8681158ff 100644 --- a/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml.cs +++ b/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml.cs @@ -3,7 +3,7 @@ using Avalonia.Interactivity; using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.Ui.App.Common; +using Ryujinx.UI.App.Common; using System; namespace Ryujinx.Ava.UI.Controls diff --git a/src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml b/src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml index c5041230d..09fa04045 100644 --- a/src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml +++ b/src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml @@ -26,7 +26,7 @@ Height="70" MinWidth="50" Margin="5,10,20,10" - Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" /> + Source="resm:Ryujinx.UI.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.UI.Common" /> - \ No newline at end of file + diff --git a/src/Ryujinx.Ava/UI/Helpers/ApplicationOpenedEventArgs.cs b/src/Ryujinx.Ava/UI/Helpers/ApplicationOpenedEventArgs.cs index cd63a99b0..bc5622b54 100644 --- a/src/Ryujinx.Ava/UI/Helpers/ApplicationOpenedEventArgs.cs +++ b/src/Ryujinx.Ava/UI/Helpers/ApplicationOpenedEventArgs.cs @@ -1,5 +1,5 @@ using Avalonia.Interactivity; -using Ryujinx.Ui.App.Common; +using Ryujinx.UI.App.Common; namespace Ryujinx.Ava.UI.Helpers { diff --git a/src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs b/src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs index b9d919f95..15b7ddd14 100644 --- a/src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs +++ b/src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs @@ -383,7 +383,7 @@ async Task ShowDialog() { result = ContentDialogResult.None; - Logger.Warning?.Print(LogClass.Ui, "Content dialog overlay failed to populate. Default value has been returned."); + Logger.Warning?.Print(LogClass.UI, "Content dialog overlay failed to populate. Default value has been returned."); } return result; diff --git a/src/Ryujinx.Ava/UI/Helpers/LocalizedNeverConverter.cs b/src/Ryujinx.Ava/UI/Helpers/LocalizedNeverConverter.cs index b61a924f4..26fe36c4c 100644 --- a/src/Ryujinx.Ava/UI/Helpers/LocalizedNeverConverter.cs +++ b/src/Ryujinx.Ava/UI/Helpers/LocalizedNeverConverter.cs @@ -1,7 +1,7 @@ using Avalonia.Data.Converters; using Avalonia.Markup.Xaml; using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ui.Common.Helper; +using Ryujinx.UI.Common.Helper; using System; using System.Globalization; diff --git a/src/Ryujinx.Ava/UI/Helpers/LoggerAdapter.cs b/src/Ryujinx.Ava/UI/Helpers/LoggerAdapter.cs index 0b1789880..fc7145410 100644 --- a/src/Ryujinx.Ava/UI/Helpers/LoggerAdapter.cs +++ b/src/Ryujinx.Ava/UI/Helpers/LoggerAdapter.cs @@ -39,12 +39,12 @@ public bool IsEnabled(AvaLogLevel level, string area) public void Log(AvaLogLevel level, string area, object source, string messageTemplate) { - GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(level, area, messageTemplate, source, null)); + GetLog(level)?.PrintMsg(RyuLogClass.UI, Format(level, area, messageTemplate, source, null)); } public void Log(AvaLogLevel level, string area, object source, string messageTemplate, params object[] propertyValues) { - GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(level, area, messageTemplate, source, propertyValues)); + GetLog(level)?.PrintMsg(RyuLogClass.UI, Format(level, area, messageTemplate, source, propertyValues)); } private static string Format(AvaLogLevel level, string area, string template, object source, object[] v) diff --git a/src/Ryujinx.Ava/UI/Helpers/UserErrorDialog.cs b/src/Ryujinx.Ava/UI/Helpers/UserErrorDialog.cs index fc82bd6b1..9a44b862b 100644 --- a/src/Ryujinx.Ava/UI/Helpers/UserErrorDialog.cs +++ b/src/Ryujinx.Ava/UI/Helpers/UserErrorDialog.cs @@ -1,6 +1,6 @@ using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ui.Common; -using Ryujinx.Ui.Common.Helper; +using Ryujinx.UI.Common; +using Ryujinx.UI.Common.Helper; using System.Threading.Tasks; namespace Ryujinx.Ava.UI.Helpers diff --git a/src/Ryujinx.Ava/UI/Models/Generic/LastPlayedSortComparer.cs b/src/Ryujinx.Ava/UI/Models/Generic/LastPlayedSortComparer.cs index 8340d39df..224f78f45 100644 --- a/src/Ryujinx.Ava/UI/Models/Generic/LastPlayedSortComparer.cs +++ b/src/Ryujinx.Ava/UI/Models/Generic/LastPlayedSortComparer.cs @@ -1,4 +1,4 @@ -using Ryujinx.Ui.App.Common; +using Ryujinx.UI.App.Common; using System; using System.Collections.Generic; diff --git a/src/Ryujinx.Ava/UI/Models/Generic/TimePlayedSortComparer.cs b/src/Ryujinx.Ava/UI/Models/Generic/TimePlayedSortComparer.cs index d53ff566f..f0fb035d1 100644 --- a/src/Ryujinx.Ava/UI/Models/Generic/TimePlayedSortComparer.cs +++ b/src/Ryujinx.Ava/UI/Models/Generic/TimePlayedSortComparer.cs @@ -1,4 +1,4 @@ -using Ryujinx.Ui.App.Common; +using Ryujinx.UI.App.Common; using System; using System.Collections.Generic; diff --git a/src/Ryujinx.Ava/UI/Models/SaveModel.cs b/src/Ryujinx.Ava/UI/Models/SaveModel.cs index 7b476932b..d6dea2f69 100644 --- a/src/Ryujinx.Ava/UI/Models/SaveModel.cs +++ b/src/Ryujinx.Ava/UI/Models/SaveModel.cs @@ -3,8 +3,8 @@ using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.Windows; using Ryujinx.HLE.FileSystem; -using Ryujinx.Ui.App.Common; -using Ryujinx.Ui.Common.Helper; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Common.Helper; using System.IO; using System.Linq; using System.Threading.Tasks; diff --git a/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs b/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs index fa55c8d3f..3bf19b43e 100644 --- a/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs +++ b/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs @@ -3,8 +3,8 @@ using Avalonia.Input; using Avalonia.Platform; using Ryujinx.Common.Configuration; -using Ryujinx.Ui.Common.Configuration; -using Ryujinx.Ui.Common.Helper; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; using SPB.Graphics; using SPB.Platform; using SPB.Platform.GLX; diff --git a/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindowOpenGL.cs b/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindowOpenGL.cs index 769a1c91a..3842301de 100644 --- a/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindowOpenGL.cs +++ b/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindowOpenGL.cs @@ -3,7 +3,7 @@ using Ryujinx.Common.Logging; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.OpenGL; -using Ryujinx.Ui.Common.Configuration; +using Ryujinx.UI.Common.Configuration; using SPB.Graphics; using SPB.Graphics.Exceptions; using SPB.Graphics.OpenGL; @@ -75,7 +75,7 @@ public void MakeCurrent(bool unbind = false, bool shouldThrow = true) throw; } - Logger.Warning?.Print(LogClass.Ui, $"Failed to {(!unbind ? "bind" : "unbind")} OpenGL context: {e}"); + Logger.Warning?.Print(LogClass.UI, $"Failed to {(!unbind ? "bind" : "unbind")} OpenGL context: {e}"); } } diff --git a/src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs b/src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs index 12c18e4a7..d055d9ea4 100644 --- a/src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs +++ b/src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs @@ -1,7 +1,7 @@ using Avalonia; using Avalonia.Controls; using Ryujinx.Common.Configuration; -using Ryujinx.Ui.Common.Configuration; +using Ryujinx.UI.Common.Configuration; using System; namespace Ryujinx.Ava.UI.Renderer diff --git a/src/Ryujinx.Ava/UI/ViewModels/AboutWindowViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/AboutWindowViewModel.cs index 70ede4c12..6020f40e0 100644 --- a/src/Ryujinx.Ava/UI/ViewModels/AboutWindowViewModel.cs +++ b/src/Ryujinx.Ava/UI/ViewModels/AboutWindowViewModel.cs @@ -3,7 +3,7 @@ using Avalonia.Threading; using Ryujinx.Ava.Common.Locale; using Ryujinx.Common.Utilities; -using Ryujinx.Ui.Common.Configuration; +using Ryujinx.UI.Common.Configuration; using System; using System.Net.Http; using System.Net.NetworkInformation; @@ -87,19 +87,19 @@ public AboutWindowViewModel() { Version = Program.Version; - if (ConfigurationState.Instance.Ui.BaseStyle.Value == "Light") + if (ConfigurationState.Instance.UI.BaseStyle.Value == "Light") { - GithubLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_GitHub_Light.png?assembly=Ryujinx.Ui.Common"))); - DiscordLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Discord_Light.png?assembly=Ryujinx.Ui.Common"))); - PatreonLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Patreon_Light.png?assembly=Ryujinx.Ui.Common"))); - TwitterLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Twitter_Light.png?assembly=Ryujinx.Ui.Common"))); + GithubLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_GitHub_Light.png?assembly=Ryujinx.UI.Common"))); + DiscordLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Discord_Light.png?assembly=Ryujinx.UI.Common"))); + PatreonLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Patreon_Light.png?assembly=Ryujinx.UI.Common"))); + TwitterLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Twitter_Light.png?assembly=Ryujinx.UI.Common"))); } else { - GithubLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_GitHub_Dark.png?assembly=Ryujinx.Ui.Common"))); - DiscordLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Discord_Dark.png?assembly=Ryujinx.Ui.Common"))); - PatreonLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Patreon_Dark.png?assembly=Ryujinx.Ui.Common"))); - TwitterLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Twitter_Dark.png?assembly=Ryujinx.Ui.Common"))); + GithubLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_GitHub_Dark.png?assembly=Ryujinx.UI.Common"))); + DiscordLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Discord_Dark.png?assembly=Ryujinx.UI.Common"))); + PatreonLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Patreon_Dark.png?assembly=Ryujinx.UI.Common"))); + TwitterLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Twitter_Dark.png?assembly=Ryujinx.UI.Common"))); } Dispatcher.UIThread.InvokeAsync(DownloadPatronsJson); diff --git a/src/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs index 0e0d858a4..8f09568a6 100644 --- a/src/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs +++ b/src/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs @@ -9,7 +9,7 @@ using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using Ryujinx.Common.Utilities; -using Ryujinx.Ui.Common.Models.Amiibo; +using Ryujinx.UI.Common.Models.Amiibo; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -65,7 +65,7 @@ public AmiiboWindowViewModel(StyleableWindow owner, string lastScannedAmiiboId, _amiiboSeries = new ObservableCollection(); _amiibos = new AvaloniaList(); - _amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.Ui.Common/Resources/Logo_Amiibo.png"); + _amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.UI.Common/Resources/Logo_Amiibo.png"); _ = LoadContentAsync(); } diff --git a/src/Ryujinx.Ava/UI/ViewModels/ControllerInputViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/ControllerInputViewModel.cs index 042803f36..71ad2c127 100644 --- a/src/Ryujinx.Ava/UI/ViewModels/ControllerInputViewModel.cs +++ b/src/Ryujinx.Ava/UI/ViewModels/ControllerInputViewModel.cs @@ -19,7 +19,7 @@ using Ryujinx.Common.Logging; using Ryujinx.Common.Utilities; using Ryujinx.Input; -using Ryujinx.Ui.Common.Configuration; +using Ryujinx.UI.Common.Configuration; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -35,10 +35,10 @@ namespace Ryujinx.Ava.UI.ViewModels public class ControllerInputViewModel : BaseModel, IDisposable { private const string Disabled = "disabled"; - private const string ProControllerResource = "Ryujinx.Ui.Common/Resources/Controller_ProCon.svg"; - private const string JoyConPairResource = "Ryujinx.Ui.Common/Resources/Controller_JoyConPair.svg"; - private const string JoyConLeftResource = "Ryujinx.Ui.Common/Resources/Controller_JoyConLeft.svg"; - private const string JoyConRightResource = "Ryujinx.Ui.Common/Resources/Controller_JoyConRight.svg"; + private const string ProControllerResource = "Ryujinx.UI.Common/Resources/Controller_ProCon.svg"; + private const string JoyConPairResource = "Ryujinx.UI.Common/Resources/Controller_JoyConPair.svg"; + private const string JoyConLeftResource = "Ryujinx.UI.Common/Resources/Controller_JoyConLeft.svg"; + private const string JoyConRightResource = "Ryujinx.UI.Common/Resources/Controller_JoyConRight.svg"; private const string KeyboardString = "keyboard"; private const string ControllerString = "controller"; private readonly MainWindow _mainWindow; diff --git a/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs index 243d870a4..17bd69b14 100644 --- a/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs @@ -25,13 +25,13 @@ using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS.Services.Account.Acc; -using Ryujinx.HLE.Ui; +using Ryujinx.HLE.UI; using Ryujinx.Input.HLE; using Ryujinx.Modules; -using Ryujinx.Ui.App.Common; -using Ryujinx.Ui.Common; -using Ryujinx.Ui.Common.Configuration; -using Ryujinx.Ui.Common.Helper; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Common; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; using SixLabors.ImageSharp.PixelFormats; using System; using System.Collections.Generic; @@ -138,7 +138,7 @@ public void Initialize( InputManager inputManager, UserChannelPersistence userChannelPersistence, LibHacHorizonManager libHacHorizonManager, - IHostUiHandler uiHandler, + IHostUIHandler uiHandler, Action showLoading, Action switchToGameControl, Action setMainContent, @@ -685,10 +685,10 @@ internal void Sort(ApplicationSort sort) public bool StartGamesInFullscreen { - get => ConfigurationState.Instance.Ui.StartFullscreen; + get => ConfigurationState.Instance.UI.StartFullscreen; set { - ConfigurationState.Instance.Ui.StartFullscreen.Value = value; + ConfigurationState.Instance.UI.StartFullscreen.Value = value; ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); @@ -698,10 +698,10 @@ public bool StartGamesInFullscreen public bool ShowConsole { - get => ConfigurationState.Instance.Ui.ShowConsole; + get => ConfigurationState.Instance.UI.ShowConsole; set { - ConfigurationState.Instance.Ui.ShowConsole.Value = value; + ConfigurationState.Instance.UI.ShowConsole.Value = value; ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); @@ -743,10 +743,10 @@ public ObservableCollection Applications public Glyph Glyph { - get => (Glyph)ConfigurationState.Instance.Ui.GameListViewMode.Value; + get => (Glyph)ConfigurationState.Instance.UI.GameListViewMode.Value; set { - ConfigurationState.Instance.Ui.GameListViewMode.Value = (int)value; + ConfigurationState.Instance.UI.GameListViewMode.Value = (int)value; OnPropertyChanged(); OnPropertyChanged(nameof(IsGrid)); @@ -758,9 +758,9 @@ public Glyph Glyph public bool ShowNames { - get => ConfigurationState.Instance.Ui.ShowNames && ConfigurationState.Instance.Ui.GridSize > 1; set + get => ConfigurationState.Instance.UI.ShowNames && ConfigurationState.Instance.UI.GridSize > 1; set { - ConfigurationState.Instance.Ui.ShowNames.Value = value; + ConfigurationState.Instance.UI.ShowNames.Value = value; OnPropertyChanged(); OnPropertyChanged(nameof(GridSizeScale)); @@ -772,10 +772,10 @@ public bool ShowNames internal ApplicationSort SortMode { - get => (ApplicationSort)ConfigurationState.Instance.Ui.ApplicationSort.Value; + get => (ApplicationSort)ConfigurationState.Instance.UI.ApplicationSort.Value; private set { - ConfigurationState.Instance.Ui.ApplicationSort.Value = (int)value; + ConfigurationState.Instance.UI.ApplicationSort.Value = (int)value; OnPropertyChanged(); OnPropertyChanged(nameof(SortName)); @@ -788,7 +788,7 @@ public int ListItemSelectorSize { get { - return ConfigurationState.Instance.Ui.GridSize.Value switch + return ConfigurationState.Instance.UI.GridSize.Value switch { 1 => 78, 2 => 100, @@ -803,7 +803,7 @@ public int GridItemSelectorSize { get { - return ConfigurationState.Instance.Ui.GridSize.Value switch + return ConfigurationState.Instance.UI.GridSize.Value switch { 1 => 120, 2 => ShowNames ? 210 : 150, @@ -816,10 +816,10 @@ public int GridItemSelectorSize public int GridSizeScale { - get => ConfigurationState.Instance.Ui.GridSize; + get => ConfigurationState.Instance.UI.GridSize; set { - ConfigurationState.Instance.Ui.GridSize.Value = value; + ConfigurationState.Instance.UI.GridSize.Value = value; if (value < 2) { @@ -860,10 +860,10 @@ public string SortName public bool IsAscending { - get => ConfigurationState.Instance.Ui.IsAscendingOrder; + get => ConfigurationState.Instance.UI.IsAscendingOrder; private set { - ConfigurationState.Instance.Ui.IsAscendingOrder.Value = value; + ConfigurationState.Instance.UI.IsAscendingOrder.Value = value; OnPropertyChanged(); OnPropertyChanged(nameof(SortMode)); @@ -919,7 +919,7 @@ public KeyGesture PauseKey public RendererHost RendererHostControl { get; private set; } public bool IsClosing { get; set; } public LibHacHorizonManager LibHacHorizonManager { get; internal set; } - public IHostUiHandler UiHandler { get; internal set; } + public IHostUIHandler UiHandler { get; internal set; } public bool IsSortedByFavorite => SortMode == ApplicationSort.Favorite; public bool IsSortedByTitle => SortMode == ApplicationSort.Title; public bool IsSortedByDeveloper => SortMode == ApplicationSort.Developer; @@ -928,10 +928,10 @@ public KeyGesture PauseKey public bool IsSortedByType => SortMode == ApplicationSort.FileType; public bool IsSortedBySize => SortMode == ApplicationSort.FileSize; public bool IsSortedByPath => SortMode == ApplicationSort.Path; - public bool IsGridSmall => ConfigurationState.Instance.Ui.GridSize == 1; - public bool IsGridMedium => ConfigurationState.Instance.Ui.GridSize == 2; - public bool IsGridLarge => ConfigurationState.Instance.Ui.GridSize == 3; - public bool IsGridHuge => ConfigurationState.Instance.Ui.GridSize == 4; + public bool IsGridSmall => ConfigurationState.Instance.UI.GridSize == 1; + public bool IsGridMedium => ConfigurationState.Instance.UI.GridSize == 2; + public bool IsGridLarge => ConfigurationState.Instance.UI.GridSize == 3; + public bool IsGridHuge => ConfigurationState.Instance.UI.GridSize == 4; #endregion @@ -1245,7 +1245,7 @@ public void SetUiProgressHandlers(Switch emulationContext) public void LoadConfigurableHotKeys() { - if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUi, out var showUiKey)) + if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI, out var showUiKey)) { ShowUiKey = new KeyGesture(showUiKey); } @@ -1385,7 +1385,7 @@ public static void ChangeLanguage(object languageCode) if (Program.PreviewerDetached) { - ConfigurationState.Instance.Ui.LanguageCode.Value = (string)languageCode; + ConfigurationState.Instance.UI.LanguageCode.Value = (string)languageCode; ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); } } diff --git a/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs index 9e462a900..bcaa08600 100644 --- a/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs @@ -16,8 +16,8 @@ using Ryujinx.Graphics.Vulkan; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.Services.Time.TimeZone; -using Ryujinx.Ui.Common.Configuration; -using Ryujinx.Ui.Common.Configuration.System; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Configuration.System; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -408,9 +408,9 @@ public void LoadCurrentConfiguration() HideCursor = (int)config.HideCursor.Value; GameDirectories.Clear(); - GameDirectories.AddRange(config.Ui.GameDirs.Value); + GameDirectories.AddRange(config.UI.GameDirs.Value); - BaseStyleIndex = config.Ui.BaseStyle == "Light" ? 0 : 1; + BaseStyleIndex = config.UI.BaseStyle == "Light" ? 0 : 1; // Input EnableDockedMode = config.System.EnableDockedMode; @@ -494,10 +494,10 @@ public void SaveSettings() if (_directoryChanged) { List gameDirs = new(GameDirectories); - config.Ui.GameDirs.Value = gameDirs; + config.UI.GameDirs.Value = gameDirs; } - config.Ui.BaseStyle.Value = BaseStyleIndex == 0 ? "Light" : "Dark"; + config.UI.BaseStyle.Value = BaseStyleIndex == 0 ? "Light" : "Dark"; // Input config.System.EnableDockedMode.Value = EnableDockedMode; diff --git a/src/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs index 8a287ecac..5989ce09a 100644 --- a/src/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs +++ b/src/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs @@ -17,7 +17,7 @@ using Ryujinx.Common.Logging; using Ryujinx.Common.Utilities; using Ryujinx.HLE.FileSystem; -using Ryujinx.Ui.App.Common; +using Ryujinx.UI.App.Common; using System; using System.Collections.Generic; using System.IO; diff --git a/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs b/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs index 9c0e683a0..8dff50868 100644 --- a/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs +++ b/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs @@ -10,9 +10,9 @@ using Ryujinx.Common; using Ryujinx.Common.Utilities; using Ryujinx.Modules; -using Ryujinx.Ui.Common; -using Ryujinx.Ui.Common.Configuration; -using Ryujinx.Ui.Common.Helper; +using Ryujinx.UI.Common; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; using System; using System.Collections.Generic; using System.IO; @@ -43,7 +43,7 @@ private CheckBox[] GenerateToggleFileTypeItems() checkBoxes.Add(new CheckBox { Content = $".{fileName}", - IsChecked = ((FileTypes)item).GetConfigValue(ConfigurationState.Instance.Ui.ShownFileTypes), + IsChecked = ((FileTypes)item).GetConfigValue(ConfigurationState.Instance.UI.ShownFileTypes), Command = MiniCommand.Create(() => Window.ToggleFileType(fileName)), }); } diff --git a/src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml.cs b/src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml.cs index 822045d44..239a7cbfc 100644 --- a/src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml.cs +++ b/src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml.cs @@ -5,7 +5,7 @@ using Ryujinx.Ava.UI.Windows; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; -using Ryujinx.Ui.Common.Configuration; +using Ryujinx.UI.Common.Configuration; using System; namespace Ryujinx.Ava.UI.Views.Main diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsHotkeysView.axaml b/src/Ryujinx.Ava/UI/Views/Settings/SettingsHotkeysView.axaml index a53c1dfe4..b4eae01ef 100644 --- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsHotkeysView.axaml +++ b/src/Ryujinx.Ava/UI/Views/Settings/SettingsHotkeysView.axaml @@ -45,7 +45,7 @@ diff --git a/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml b/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml index a06571408..69fa82517 100644 --- a/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml +++ b/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml @@ -51,7 +51,7 @@ Spacing="10"> ConfigurationState.Instance.Ui.ShownFileTypes.NSP.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NSP, - "PFS0" => ConfigurationState.Instance.Ui.ShownFileTypes.PFS0.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.PFS0, - "XCI" => ConfigurationState.Instance.Ui.ShownFileTypes.XCI.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.XCI, - "NCA" => ConfigurationState.Instance.Ui.ShownFileTypes.NCA.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NCA, - "NRO" => ConfigurationState.Instance.Ui.ShownFileTypes.NRO.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NRO, - "NSO" => ConfigurationState.Instance.Ui.ShownFileTypes.NSO.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NSO, + "NSP" => ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value = !ConfigurationState.Instance.UI.ShownFileTypes.NSP, + "PFS0" => ConfigurationState.Instance.UI.ShownFileTypes.PFS0.Value = !ConfigurationState.Instance.UI.ShownFileTypes.PFS0, + "XCI" => ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value = !ConfigurationState.Instance.UI.ShownFileTypes.XCI, + "NCA" => ConfigurationState.Instance.UI.ShownFileTypes.NCA.Value = !ConfigurationState.Instance.UI.ShownFileTypes.NCA, + "NRO" => ConfigurationState.Instance.UI.ShownFileTypes.NRO.Value = !ConfigurationState.Instance.UI.ShownFileTypes.NRO, + "NSO" => ConfigurationState.Instance.UI.ShownFileTypes.NSO.Value = !ConfigurationState.Instance.UI.ShownFileTypes.NSO, _ => throw new ArgumentOutOfRangeException(fileType), #pragma warning restore IDE0055 }; @@ -537,7 +537,7 @@ private void ReloadGameList() Thread applicationLibraryThread = new(() => { - ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs, ConfigurationState.Instance.System.Language); + ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language); _isLoading = false; }) diff --git a/src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml.cs b/src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml.cs index 5de09ba0b..d9ae0d4f3 100644 --- a/src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml.cs +++ b/src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml.cs @@ -6,7 +6,7 @@ using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.Ui.Common.Helper; +using Ryujinx.UI.Common.Helper; using System.Threading.Tasks; using Button = Avalonia.Controls.Button; diff --git a/src/Ryujinx.Ava/UI/Windows/StyleableWindow.cs b/src/Ryujinx.Ava/UI/Windows/StyleableWindow.cs index 3cd12bc8c..a12d2b3e8 100644 --- a/src/Ryujinx.Ava/UI/Windows/StyleableWindow.cs +++ b/src/Ryujinx.Ava/UI/Windows/StyleableWindow.cs @@ -4,7 +4,7 @@ using Avalonia.Media.Imaging; using Avalonia.Platform; using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ui.Common.Configuration; +using Ryujinx.UI.Common.Configuration; using System.IO; using System.Reflection; @@ -19,7 +19,7 @@ public StyleableWindow() WindowStartupLocation = WindowStartupLocation.CenterOwner; TransparencyLevelHint = new[] { WindowTransparencyLevel.None }; - using Stream stream = Assembly.GetAssembly(typeof(ConfigurationState)).GetManifestResourceStream("Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png"); + using Stream stream = Assembly.GetAssembly(typeof(ConfigurationState)).GetManifestResourceStream("Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); Icon = new WindowIcon(stream); stream.Position = 0; diff --git a/src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs b/src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs index 7ece63355..f3ac69600 100644 --- a/src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs +++ b/src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs @@ -7,7 +7,7 @@ using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.ViewModels; using Ryujinx.HLE.FileSystem; -using Ryujinx.Ui.Common.Helper; +using Ryujinx.UI.Common.Helper; using System.Threading.Tasks; using Button = Avalonia.Controls.Button; diff --git a/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs b/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs index b4f2f9468..e9c163cf2 100644 --- a/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs +++ b/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs @@ -6,7 +6,7 @@ public class KeyboardHotkeys { public Key ToggleVsync { get; set; } public Key Screenshot { get; set; } - public Key ShowUi { get; set; } + public Key ShowUI { get; set; } public Key Pause { get; set; } public Key ToggleMute { get; set; } public Key ResScaleUp { get; set; } diff --git a/src/Ryujinx.Common/Logging/LogClass.cs b/src/Ryujinx.Common/Logging/LogClass.cs index f277dd06d..1b404a06a 100644 --- a/src/Ryujinx.Common/Logging/LogClass.cs +++ b/src/Ryujinx.Common/Logging/LogClass.cs @@ -70,7 +70,7 @@ public enum LogClass ServiceVi, SurfaceFlinger, TamperMachine, - Ui, + UI, Vic, } } diff --git a/src/Ryujinx.HLE/HLEConfiguration.cs b/src/Ryujinx.HLE/HLEConfiguration.cs index f589bfdda..955fee4b5 100644 --- a/src/Ryujinx.HLE/HLEConfiguration.cs +++ b/src/Ryujinx.HLE/HLEConfiguration.cs @@ -7,7 +7,7 @@ using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.SystemState; -using Ryujinx.HLE.Ui; +using Ryujinx.HLE.UI; using System; namespace Ryujinx.HLE @@ -63,7 +63,7 @@ public class HLEConfiguration /// The handler for various UI related operations needed outside of HLE. /// /// This cannot be changed after instantiation. - internal readonly IHostUiHandler HostUiHandler; + internal readonly IHostUIHandler HostUIHandler; /// /// Control the memory configuration used by the emulation context. @@ -177,7 +177,7 @@ public HLEConfiguration(VirtualFileSystem virtualFileSystem, IRenderer gpuRenderer, IHardwareDeviceDriver audioDeviceDriver, MemoryConfiguration memoryConfiguration, - IHostUiHandler hostUiHandler, + IHostUIHandler hostUIHandler, SystemLanguage systemLanguage, RegionCode region, bool enableVsync, @@ -204,7 +204,7 @@ public HLEConfiguration(VirtualFileSystem virtualFileSystem, GpuRenderer = gpuRenderer; AudioDeviceDriver = audioDeviceDriver; MemoryConfiguration = memoryConfiguration; - HostUiHandler = hostUiHandler; + HostUIHandler = hostUIHandler; SystemLanguage = systemLanguage; Region = region; EnableVsync = enableVsync; diff --git a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs index 867202178..5ec9d4b08 100644 --- a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs +++ b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs @@ -86,7 +86,7 @@ public ResultCode Start(AppletSession normalSession, AppletSession interactiveSe PlayerIndex primaryIndex; while (!_system.Device.Hid.Npads.Validate(playerMin, playerMax, (ControllerType)privateArg.NpadStyleSet, out configuredCount, out primaryIndex)) { - ControllerAppletUiArgs uiArgs = new() + ControllerAppletUIArgs uiArgs = new() { PlayerCountMin = playerMin, PlayerCountMax = playerMax, @@ -95,7 +95,7 @@ public ResultCode Start(AppletSession normalSession, AppletSession interactiveSe IsDocked = _system.State.DockedMode, }; - if (!_system.Device.UiHandler.DisplayMessageDialog(uiArgs)) + if (!_system.Device.UIHandler.DisplayMessageDialog(uiArgs)) { break; } diff --git a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUiArgs.cs b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUIArgs.cs similarity index 88% rename from src/Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUiArgs.cs rename to src/Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUIArgs.cs index bf440515b..10cba58ba 100644 --- a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUiArgs.cs +++ b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUIArgs.cs @@ -3,7 +3,7 @@ namespace Ryujinx.HLE.HOS.Applets { - public struct ControllerAppletUiArgs + public struct ControllerAppletUIArgs { public int PlayerCountMin; public int PlayerCountMax; diff --git a/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs index 5c474f229..7ee9b9e90 100644 --- a/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs +++ b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs @@ -166,13 +166,13 @@ private void ParseErrorCommonArg() string[] buttons = GetButtonsText(module, description, "DlgBtn"); - bool showDetails = _horizon.Device.UiHandler.DisplayErrorAppletDialog($"Error Code: {module}-{description:0000}", "\n" + message, buttons); + bool showDetails = _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Code: {module}-{description:0000}", "\n" + message, buttons); if (showDetails) { message = GetMessageText(module, description, "FlvMsg"); buttons = GetButtonsText(module, description, "FlvBtn"); - _horizon.Device.UiHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons); + _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons); } } @@ -200,12 +200,12 @@ private void ParseApplicationErrorArg() buttons.Add("OK"); - bool showDetails = _horizon.Device.UiHandler.DisplayErrorAppletDialog($"Error Number: {applicationErrorArg.ErrorNumber}", "\n" + messageText, buttons.ToArray()); + bool showDetails = _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Number: {applicationErrorArg.ErrorNumber}", "\n" + messageText, buttons.ToArray()); if (showDetails) { buttons.RemoveAt(0); - _horizon.Device.UiHandler.DisplayErrorAppletDialog($"Error Number: {applicationErrorArg.ErrorNumber} (Details)", "\n" + detailsText, buttons.ToArray()); + _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Number: {applicationErrorArg.ErrorNumber} (Details)", "\n" + detailsText, buttons.ToArray()); } } diff --git a/src/Ryujinx.HLE/HOS/Applets/IApplet.cs b/src/Ryujinx.HLE/HOS/Applets/IApplet.cs index 5ccf3994f..985887c47 100644 --- a/src/Ryujinx.HLE/HOS/Applets/IApplet.cs +++ b/src/Ryujinx.HLE/HOS/Applets/IApplet.cs @@ -1,5 +1,5 @@ using Ryujinx.HLE.HOS.Services.Am.AppletAE; -using Ryujinx.HLE.Ui; +using Ryujinx.HLE.UI; using Ryujinx.Memory; using System; using System.Runtime.InteropServices; diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs index 432bf6a8a..0462a5b00 100644 --- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs @@ -4,8 +4,8 @@ using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard; using Ryujinx.HLE.HOS.Services.Am.AppletAE; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad; -using Ryujinx.HLE.Ui; -using Ryujinx.HLE.Ui.Input; +using Ryujinx.HLE.UI; +using Ryujinx.HLE.UI.Input; using Ryujinx.Memory; using System; using System.Diagnostics; @@ -92,14 +92,14 @@ public ResultCode Start(AppletSession normalSession, AppletSession interactiveSe _keyboardBackgroundInitialize = MemoryMarshal.Read(keyboardConfig); _backgroundState = InlineKeyboardState.Uninitialized; - if (_device.UiHandler == null) + if (_device.UIHandler == null) { Logger.Error?.Print(LogClass.ServiceAm, "GUI Handler is not set, software keyboard applet will not work properly"); } else { // Create a text handler that converts keyboard strokes to strings. - _dynamicTextInputHandler = _device.UiHandler.CreateDynamicTextInputHandler(); + _dynamicTextInputHandler = _device.UIHandler.CreateDynamicTextInputHandler(); _dynamicTextInputHandler.TextChangedEvent += HandleTextChangedEvent; _dynamicTextInputHandler.KeyPressedEvent += HandleKeyPressedEvent; @@ -107,7 +107,7 @@ public ResultCode Start(AppletSession normalSession, AppletSession interactiveSe _npads.NpadButtonDownEvent += HandleNpadButtonDownEvent; _npads.NpadButtonUpEvent += HandleNpadButtonUpEvent; - _keyboardRenderer = new SoftwareKeyboardRenderer(_device.UiHandler.HostUiTheme); + _keyboardRenderer = new SoftwareKeyboardRenderer(_device.UIHandler.HostUITheme); } return ResultCode.Success; @@ -199,7 +199,7 @@ private void ExecuteForegroundKeyboard() _keyboardForegroundConfig.StringLengthMax = 100; } - if (_device.UiHandler == null) + if (_device.UIHandler == null) { Logger.Warning?.Print(LogClass.Application, "GUI Handler is not set. Falling back to default"); @@ -209,7 +209,7 @@ private void ExecuteForegroundKeyboard() else { // Call the configured GUI handler to get user's input. - var args = new SoftwareKeyboardUiArgs + var args = new SoftwareKeyboardUIArgs { KeyboardMode = _keyboardForegroundConfig.Mode, HeaderText = StripUnicodeControlCodes(_keyboardForegroundConfig.HeaderText), @@ -222,7 +222,7 @@ private void ExecuteForegroundKeyboard() InitialText = initialText, }; - _lastResult = _device.UiHandler.DisplayInputDialog(args, out _textValue) ? KeyboardResult.Accept : KeyboardResult.Cancel; + _lastResult = _device.UIHandler.DisplayInputDialog(args, out _textValue) ? KeyboardResult.Accept : KeyboardResult.Cancel; _textValue ??= initialText ?? DefaultInputText; } diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs index f76cce295..3f7516e6a 100644 --- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs @@ -1,4 +1,4 @@ -using Ryujinx.HLE.Ui; +using Ryujinx.HLE.UI; using Ryujinx.Memory; using System; using System.Threading; @@ -15,13 +15,13 @@ internal class SoftwareKeyboardRenderer : IDisposable private readonly object _stateLock = new(); - private readonly SoftwareKeyboardUiState _state = new(); + private readonly SoftwareKeyboardUIState _state = new(); private readonly SoftwareKeyboardRendererBase _renderer; private readonly TimedAction _textBoxBlinkTimedAction = new(); private readonly TimedAction _renderAction = new(); - public SoftwareKeyboardRenderer(IHostUiTheme uiTheme) + public SoftwareKeyboardRenderer(IHostUITheme uiTheme) { _renderer = new SoftwareKeyboardRendererBase(uiTheme); @@ -29,7 +29,7 @@ public SoftwareKeyboardRenderer(IHostUiTheme uiTheme) StartRenderer(_renderAction, _renderer, _state, _stateLock); } - private static void StartTextBoxBlinker(TimedAction timedAction, SoftwareKeyboardUiState state, object stateLock) + private static void StartTextBoxBlinker(TimedAction timedAction, SoftwareKeyboardUIState state, object stateLock) { timedAction.Reset(() => { @@ -45,9 +45,9 @@ private static void StartTextBoxBlinker(TimedAction timedAction, SoftwareKeyboar }, TextBoxBlinkSleepMilliseconds); } - private static void StartRenderer(TimedAction timedAction, SoftwareKeyboardRendererBase renderer, SoftwareKeyboardUiState state, object stateLock) + private static void StartRenderer(TimedAction timedAction, SoftwareKeyboardRendererBase renderer, SoftwareKeyboardUIState state, object stateLock) { - SoftwareKeyboardUiState internalState = new(); + SoftwareKeyboardUIState internalState = new(); bool canCreateSurface = false; bool needsUpdate = true; diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs index 3971a33be..75c648ff1 100644 --- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs @@ -1,4 +1,4 @@ -using Ryujinx.HLE.Ui; +using Ryujinx.HLE.UI; using Ryujinx.Memory; using SixLabors.Fonts; using SixLabors.ImageSharp; @@ -63,7 +63,7 @@ internal class SoftwareKeyboardRendererBase private Point _logoPosition; private float _messagePositionY; - public SoftwareKeyboardRendererBase(IHostUiTheme uiTheme) + public SoftwareKeyboardRendererBase(IHostUITheme uiTheme) { int ryujinxLogoSize = 32; @@ -205,7 +205,7 @@ private void DrawImmutableElements() }); } - public void DrawMutableElements(SoftwareKeyboardUiState state) + public void DrawMutableElements(SoftwareKeyboardUIState state) { if (_surface == null) { @@ -322,7 +322,7 @@ private static RectangleF MeasureString(ReadOnlySpan text, Font font) return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height); } - private void DrawTextBox(IImageProcessingContext context, SoftwareKeyboardUiState state) + private void DrawTextBox(IImageProcessingContext context, SoftwareKeyboardUIState state) { var inputTextRectangle = MeasureString(state.InputText, _inputTextFont); diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiArgs.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUIArgs.cs similarity index 90% rename from src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiArgs.cs rename to src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUIArgs.cs index 52fa7ed85..854f04a3b 100644 --- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiArgs.cs +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUIArgs.cs @@ -2,7 +2,7 @@ namespace Ryujinx.HLE.HOS.Applets { - public struct SoftwareKeyboardUiArgs + public struct SoftwareKeyboardUIArgs { public KeyboardMode KeyboardMode; public string HeaderText; diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiState.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUIState.cs similarity index 89% rename from src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiState.cs rename to src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUIState.cs index 608d51f32..6199ff666 100644 --- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiState.cs +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUIState.cs @@ -1,11 +1,11 @@ -using Ryujinx.HLE.Ui; +using Ryujinx.HLE.UI; namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard { /// /// TODO /// - internal class SoftwareKeyboardUiState + internal class SoftwareKeyboardUIState { public string InputText = ""; public int CursorBegin = 0; diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs index 271d00605..9a7fdcc16 100644 --- a/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs @@ -97,7 +97,7 @@ public ResultCode CreateApplicationAndRequestToStart(ServiceCtx context) if (titleId == 0) { - context.Device.UiHandler.ExecuteProgram(context.Device, ProgramSpecifyKind.RestartProgram, titleId); + context.Device.UIHandler.ExecuteProgram(context.Device, ProgramSpecifyKind.RestartProgram, titleId); } else { @@ -524,7 +524,7 @@ public ResultCode ExecuteProgram(ServiceCtx context) Logger.Stub?.PrintStub(LogClass.ServiceAm, new { kind, value }); - context.Device.UiHandler.ExecuteProgram(context.Device, kind, value); + context.Device.UIHandler.ExecuteProgram(context.Device, kind, value); return ResultCode.Success; } diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs index 143e21661..b6988f08d 100644 --- a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs +++ b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs @@ -7,7 +7,7 @@ using Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService; using Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService.Types; using Ryujinx.HLE.HOS.Services.Vi.Types; -using Ryujinx.HLE.Ui; +using Ryujinx.HLE.UI; using Ryujinx.Horizon.Common; using System; using System.Collections.Generic; diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs index ae063a47d..498714dcd 100644 --- a/src/Ryujinx.HLE/Switch.cs +++ b/src/Ryujinx.HLE/Switch.cs @@ -7,7 +7,7 @@ using Ryujinx.HLE.HOS.Services.Apm; using Ryujinx.HLE.HOS.Services.Hid; using Ryujinx.HLE.Loaders.Processes; -using Ryujinx.HLE.Ui; +using Ryujinx.HLE.UI; using Ryujinx.Memory; using System; @@ -25,7 +25,7 @@ public class Switch : IDisposable public PerformanceStatistics Statistics { get; } public Hid Hid { get; } public TamperMachine TamperMachine { get; } - public IHostUiHandler UiHandler { get; } + public IHostUIHandler UIHandler { get; } public bool EnableDeviceVsync { get; set; } = true; @@ -39,7 +39,7 @@ public Switch(HLEConfiguration configuration) Configuration = configuration; FileSystem = Configuration.VirtualFileSystem; - UiHandler = Configuration.HostUiHandler; + UIHandler = Configuration.HostUIHandler; MemoryAllocationFlags memoryAllocationFlags = configuration.MemoryManagerMode == MemoryManagerMode.SoftwarePageTable ? MemoryAllocationFlags.Reserve diff --git a/src/Ryujinx.HLE/Ui/DynamicTextChangedHandler.cs b/src/Ryujinx.HLE/UI/DynamicTextChangedHandler.cs similarity index 82% rename from src/Ryujinx.HLE/Ui/DynamicTextChangedHandler.cs rename to src/Ryujinx.HLE/UI/DynamicTextChangedHandler.cs index cb9ca0dec..c0945259b 100644 --- a/src/Ryujinx.HLE/Ui/DynamicTextChangedHandler.cs +++ b/src/Ryujinx.HLE/UI/DynamicTextChangedHandler.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.HLE.Ui +namespace Ryujinx.HLE.UI { public delegate void DynamicTextChangedHandler(string text, int cursorBegin, int cursorEnd, bool overwriteMode); } diff --git a/src/Ryujinx.HLE/Ui/IDynamicTextInputHandler.cs b/src/Ryujinx.HLE/UI/IDynamicTextInputHandler.cs similarity index 94% rename from src/Ryujinx.HLE/Ui/IDynamicTextInputHandler.cs rename to src/Ryujinx.HLE/UI/IDynamicTextInputHandler.cs index e530d2c4e..1ff451d10 100644 --- a/src/Ryujinx.HLE/Ui/IDynamicTextInputHandler.cs +++ b/src/Ryujinx.HLE/UI/IDynamicTextInputHandler.cs @@ -1,6 +1,6 @@ using System; -namespace Ryujinx.HLE.Ui +namespace Ryujinx.HLE.UI { public interface IDynamicTextInputHandler : IDisposable { diff --git a/src/Ryujinx.HLE/Ui/IHostUiHandler.cs b/src/Ryujinx.HLE/UI/IHostUIHandler.cs similarity index 89% rename from src/Ryujinx.HLE/Ui/IHostUiHandler.cs rename to src/Ryujinx.HLE/UI/IHostUIHandler.cs index 68f78f22d..3b3a430ee 100644 --- a/src/Ryujinx.HLE/Ui/IHostUiHandler.cs +++ b/src/Ryujinx.HLE/UI/IHostUIHandler.cs @@ -1,16 +1,16 @@ using Ryujinx.HLE.HOS.Applets; using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; -namespace Ryujinx.HLE.Ui +namespace Ryujinx.HLE.UI { - public interface IHostUiHandler + public interface IHostUIHandler { /// /// Displays an Input Dialog box to the user and blocks until text is entered. /// /// Text that the user entered. Set to `null` on internal errors /// True when OK is pressed, False otherwise. Also returns True on internal errors - bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText); + bool DisplayInputDialog(SoftwareKeyboardUIArgs args, out string userText); /// /// Displays a Message Dialog box to the user and blocks until it is closed. @@ -22,7 +22,7 @@ public interface IHostUiHandler /// Displays a Message Dialog box specific to Controller Applet and blocks until it is closed. /// /// True when OK is pressed, False otherwise. - bool DisplayMessageDialog(ControllerAppletUiArgs args); + bool DisplayMessageDialog(ControllerAppletUIArgs args); /// /// Tell the UI that we need to transisition to another program. @@ -46,6 +46,6 @@ public interface IHostUiHandler /// /// Gets fonts and colors used by the host. /// - IHostUiTheme HostUiTheme { get; } + IHostUITheme HostUITheme { get; } } } diff --git a/src/Ryujinx.HLE/Ui/IHostUiTheme.cs b/src/Ryujinx.HLE/UI/IHostUITheme.cs similarity index 83% rename from src/Ryujinx.HLE/Ui/IHostUiTheme.cs rename to src/Ryujinx.HLE/UI/IHostUITheme.cs index 11d82361a..3b0544004 100644 --- a/src/Ryujinx.HLE/Ui/IHostUiTheme.cs +++ b/src/Ryujinx.HLE/UI/IHostUITheme.cs @@ -1,6 +1,6 @@ -namespace Ryujinx.HLE.Ui +namespace Ryujinx.HLE.UI { - public interface IHostUiTheme + public interface IHostUITheme { string FontFamily { get; } diff --git a/src/Ryujinx.HLE/Ui/Input/NpadButtonHandler.cs b/src/Ryujinx.HLE/UI/Input/NpadButtonHandler.cs similarity index 81% rename from src/Ryujinx.HLE/Ui/Input/NpadButtonHandler.cs rename to src/Ryujinx.HLE/UI/Input/NpadButtonHandler.cs index 2d1c1c491..73c306614 100644 --- a/src/Ryujinx.HLE/Ui/Input/NpadButtonHandler.cs +++ b/src/Ryujinx.HLE/UI/Input/NpadButtonHandler.cs @@ -1,6 +1,6 @@ using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad; -namespace Ryujinx.HLE.Ui.Input +namespace Ryujinx.HLE.UI.Input { delegate void NpadButtonHandler(int npadIndex, NpadButton button); } diff --git a/src/Ryujinx.HLE/Ui/Input/NpadReader.cs b/src/Ryujinx.HLE/UI/Input/NpadReader.cs similarity index 99% rename from src/Ryujinx.HLE/Ui/Input/NpadReader.cs rename to src/Ryujinx.HLE/UI/Input/NpadReader.cs index 8fc95dc94..8276d6160 100644 --- a/src/Ryujinx.HLE/Ui/Input/NpadReader.cs +++ b/src/Ryujinx.HLE/UI/Input/NpadReader.cs @@ -1,7 +1,7 @@ using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad; -namespace Ryujinx.HLE.Ui.Input +namespace Ryujinx.HLE.UI.Input { /// /// Class that converts Hid entries for the Npad into pressed / released events. diff --git a/src/Ryujinx.HLE/Ui/KeyPressedHandler.cs b/src/Ryujinx.HLE/UI/KeyPressedHandler.cs similarity index 79% rename from src/Ryujinx.HLE/Ui/KeyPressedHandler.cs rename to src/Ryujinx.HLE/UI/KeyPressedHandler.cs index 31e754377..6feb11bd8 100644 --- a/src/Ryujinx.HLE/Ui/KeyPressedHandler.cs +++ b/src/Ryujinx.HLE/UI/KeyPressedHandler.cs @@ -1,6 +1,6 @@ using Ryujinx.Common.Configuration.Hid; -namespace Ryujinx.HLE.Ui +namespace Ryujinx.HLE.UI { public delegate bool KeyPressedHandler(Key key); } diff --git a/src/Ryujinx.HLE/Ui/KeyReleasedHandler.cs b/src/Ryujinx.HLE/UI/KeyReleasedHandler.cs similarity index 79% rename from src/Ryujinx.HLE/Ui/KeyReleasedHandler.cs rename to src/Ryujinx.HLE/UI/KeyReleasedHandler.cs index d5b6d2019..3de89d0c7 100644 --- a/src/Ryujinx.HLE/Ui/KeyReleasedHandler.cs +++ b/src/Ryujinx.HLE/UI/KeyReleasedHandler.cs @@ -1,6 +1,6 @@ using Ryujinx.Common.Configuration.Hid; -namespace Ryujinx.HLE.Ui +namespace Ryujinx.HLE.UI { public delegate bool KeyReleasedHandler(Key key); } diff --git a/src/Ryujinx.HLE/Ui/RenderingSurfaceInfo.cs b/src/Ryujinx.HLE/UI/RenderingSurfaceInfo.cs similarity index 98% rename from src/Ryujinx.HLE/Ui/RenderingSurfaceInfo.cs rename to src/Ryujinx.HLE/UI/RenderingSurfaceInfo.cs index 0b3d0a909..af0a0d44e 100644 --- a/src/Ryujinx.HLE/Ui/RenderingSurfaceInfo.cs +++ b/src/Ryujinx.HLE/UI/RenderingSurfaceInfo.cs @@ -1,7 +1,7 @@ using Ryujinx.HLE.HOS.Services.SurfaceFlinger; using System; -namespace Ryujinx.HLE.Ui +namespace Ryujinx.HLE.UI { /// /// Information about the indirect layer that is being drawn to. diff --git a/src/Ryujinx.HLE/Ui/ThemeColor.cs b/src/Ryujinx.HLE/UI/ThemeColor.cs similarity index 93% rename from src/Ryujinx.HLE/Ui/ThemeColor.cs rename to src/Ryujinx.HLE/UI/ThemeColor.cs index 23657ed2b..c5cfb1474 100644 --- a/src/Ryujinx.HLE/Ui/ThemeColor.cs +++ b/src/Ryujinx.HLE/UI/ThemeColor.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.HLE.Ui +namespace Ryujinx.HLE.UI { public readonly struct ThemeColor { diff --git a/src/Ryujinx.Headless.SDL2/HeadlessDynamicTextInputHandler.cs b/src/Ryujinx.Headless.SDL2/HeadlessDynamicTextInputHandler.cs index aae01a0ce..503874ff1 100644 --- a/src/Ryujinx.Headless.SDL2/HeadlessDynamicTextInputHandler.cs +++ b/src/Ryujinx.Headless.SDL2/HeadlessDynamicTextInputHandler.cs @@ -1,4 +1,4 @@ -using Ryujinx.HLE.Ui; +using Ryujinx.HLE.UI; using System.Threading; using System.Threading.Tasks; diff --git a/src/Ryujinx.Headless.SDL2/HeadlessHostUiTheme.cs b/src/Ryujinx.Headless.SDL2/HeadlessHostUiTheme.cs index a2df6f3ee..78cd43ae5 100644 --- a/src/Ryujinx.Headless.SDL2/HeadlessHostUiTheme.cs +++ b/src/Ryujinx.Headless.SDL2/HeadlessHostUiTheme.cs @@ -1,8 +1,8 @@ -using Ryujinx.HLE.Ui; +using Ryujinx.HLE.UI; namespace Ryujinx.Headless.SDL2 { - internal class HeadlessHostUiTheme : IHostUiTheme + internal class HeadlessHostUiTheme : IHostUITheme { public string FontFamily => "sans-serif"; diff --git a/src/Ryujinx.Headless.SDL2/WindowBase.cs b/src/Ryujinx.Headless.SDL2/WindowBase.cs index b1f43dc22..8768913f5 100644 --- a/src/Ryujinx.Headless.SDL2/WindowBase.cs +++ b/src/Ryujinx.Headless.SDL2/WindowBase.cs @@ -7,7 +7,7 @@ using Ryujinx.Graphics.OpenGL; using Ryujinx.HLE.HOS.Applets; using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; -using Ryujinx.HLE.Ui; +using Ryujinx.HLE.UI; using Ryujinx.Input; using Ryujinx.Input.HLE; using Ryujinx.SDL2.Common; @@ -25,7 +25,7 @@ namespace Ryujinx.Headless.SDL2 { - abstract partial class WindowBase : IHostUiHandler, IDisposable + abstract partial class WindowBase : IHostUIHandler, IDisposable { protected const int DefaultWidth = 1280; protected const int DefaultHeight = 720; @@ -53,7 +53,7 @@ public static void QueueMainThreadAction(Action action) protected IntPtr WindowHandle { get; set; } - public IHostUiTheme HostUiTheme { get; } + public IHostUITheme HostUITheme { get; } public int Width { get; private set; } public int Height { get; private set; } public int DisplayId { get; set; } @@ -106,7 +106,7 @@ public WindowBase( _gpuDoneEvent = new ManualResetEvent(false); _aspectRatio = aspectRatio; _enableMouse = enableMouse; - HostUiTheme = new HeadlessHostUiTheme(); + HostUITheme = new HeadlessHostUiTheme(); SDL2Driver.Instance.Initialize(); } @@ -465,7 +465,7 @@ public void Execute() Exit(); } - public bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText) + public bool DisplayInputDialog(SoftwareKeyboardUIArgs args, out string userText) { // SDL2 doesn't support input dialogs userText = "Ryujinx"; @@ -480,7 +480,7 @@ public bool DisplayMessageDialog(string title, string message) return true; } - public bool DisplayMessageDialog(ControllerAppletUiArgs args) + public bool DisplayMessageDialog(ControllerAppletUIArgs args) { string playerCount = args.PlayerCountMin == args.PlayerCountMax ? $"exactly {args.PlayerCountMin}" : $"{args.PlayerCountMin}-{args.PlayerCountMax}"; diff --git a/src/Ryujinx.Ui.Common/App/ApplicationAddedEventArgs.cs b/src/Ryujinx.UI.Common/App/ApplicationAddedEventArgs.cs similarity index 81% rename from src/Ryujinx.Ui.Common/App/ApplicationAddedEventArgs.cs rename to src/Ryujinx.UI.Common/App/ApplicationAddedEventArgs.cs index 01e20276e..58e066b9d 100644 --- a/src/Ryujinx.Ui.Common/App/ApplicationAddedEventArgs.cs +++ b/src/Ryujinx.UI.Common/App/ApplicationAddedEventArgs.cs @@ -1,6 +1,6 @@ using System; -namespace Ryujinx.Ui.App.Common +namespace Ryujinx.UI.App.Common { public class ApplicationAddedEventArgs : EventArgs { diff --git a/src/Ryujinx.Ui.Common/App/ApplicationCountUpdatedEventArgs.cs b/src/Ryujinx.UI.Common/App/ApplicationCountUpdatedEventArgs.cs similarity index 85% rename from src/Ryujinx.Ui.Common/App/ApplicationCountUpdatedEventArgs.cs rename to src/Ryujinx.UI.Common/App/ApplicationCountUpdatedEventArgs.cs index ca54ddf7a..5ed7baf19 100644 --- a/src/Ryujinx.Ui.Common/App/ApplicationCountUpdatedEventArgs.cs +++ b/src/Ryujinx.UI.Common/App/ApplicationCountUpdatedEventArgs.cs @@ -1,6 +1,6 @@ using System; -namespace Ryujinx.Ui.App.Common +namespace Ryujinx.UI.App.Common { public class ApplicationCountUpdatedEventArgs : EventArgs { diff --git a/src/Ryujinx.Ui.Common/App/ApplicationData.cs b/src/Ryujinx.UI.Common/App/ApplicationData.cs similarity index 98% rename from src/Ryujinx.Ui.Common/App/ApplicationData.cs rename to src/Ryujinx.UI.Common/App/ApplicationData.cs index bd844805b..8cc7238e9 100644 --- a/src/Ryujinx.Ui.Common/App/ApplicationData.cs +++ b/src/Ryujinx.UI.Common/App/ApplicationData.cs @@ -9,11 +9,11 @@ using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common.Logging; using Ryujinx.HLE.FileSystem; -using Ryujinx.Ui.Common.Helper; +using Ryujinx.UI.Common.Helper; using System; using System.IO; -namespace Ryujinx.Ui.App.Common +namespace Ryujinx.UI.App.Common { public class ApplicationData { diff --git a/src/Ryujinx.Ui.Common/App/ApplicationJsonSerializerContext.cs b/src/Ryujinx.UI.Common/App/ApplicationJsonSerializerContext.cs similarity index 88% rename from src/Ryujinx.Ui.Common/App/ApplicationJsonSerializerContext.cs rename to src/Ryujinx.UI.Common/App/ApplicationJsonSerializerContext.cs index 9a7b3eddf..ada7cc346 100644 --- a/src/Ryujinx.Ui.Common/App/ApplicationJsonSerializerContext.cs +++ b/src/Ryujinx.UI.Common/App/ApplicationJsonSerializerContext.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Ryujinx.Ui.App.Common +namespace Ryujinx.UI.App.Common { [JsonSourceGenerationOptions(WriteIndented = true)] [JsonSerializable(typeof(ApplicationMetadata))] diff --git a/src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs similarity index 97% rename from src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs rename to src/Ryujinx.UI.Common/App/ApplicationLibrary.cs index 3b35ff270..65cf7a9e6 100644 --- a/src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs +++ b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs @@ -15,8 +15,8 @@ using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.Loaders.Npdm; -using Ryujinx.Ui.Common.Configuration; -using Ryujinx.Ui.Common.Configuration.System; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Configuration.System; using System; using System.Collections.Generic; using System.Globalization; @@ -29,7 +29,7 @@ using Path = System.IO.Path; using TimeSpan = System.TimeSpan; -namespace Ryujinx.Ui.App.Common +namespace Ryujinx.UI.App.Common { public class ApplicationLibrary { @@ -53,11 +53,11 @@ public ApplicationLibrary(VirtualFileSystem virtualFileSystem) { _virtualFileSystem = virtualFileSystem; - _nspIcon = GetResourceBytes("Ryujinx.Ui.Common.Resources.Icon_NSP.png"); - _xciIcon = GetResourceBytes("Ryujinx.Ui.Common.Resources.Icon_XCI.png"); - _ncaIcon = GetResourceBytes("Ryujinx.Ui.Common.Resources.Icon_NCA.png"); - _nroIcon = GetResourceBytes("Ryujinx.Ui.Common.Resources.Icon_NRO.png"); - _nsoIcon = GetResourceBytes("Ryujinx.Ui.Common.Resources.Icon_NSO.png"); + _nspIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_NSP.png"); + _xciIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_XCI.png"); + _ncaIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_NCA.png"); + _nroIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_NRO.png"); + _nsoIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_NSO.png"); } private static byte[] GetResourceBytes(string resourceName) @@ -116,12 +116,12 @@ public void LoadApplications(List appDirs, Language desiredTitleLanguage IEnumerable files = Directory.EnumerateFiles(appDir, "*", SearchOption.AllDirectories).Where(file => { return - (Path.GetExtension(file).ToLower() is ".nsp" && ConfigurationState.Instance.Ui.ShownFileTypes.NSP.Value) || - (Path.GetExtension(file).ToLower() is ".pfs0" && ConfigurationState.Instance.Ui.ShownFileTypes.PFS0.Value) || - (Path.GetExtension(file).ToLower() is ".xci" && ConfigurationState.Instance.Ui.ShownFileTypes.XCI.Value) || - (Path.GetExtension(file).ToLower() is ".nca" && ConfigurationState.Instance.Ui.ShownFileTypes.NCA.Value) || - (Path.GetExtension(file).ToLower() is ".nro" && ConfigurationState.Instance.Ui.ShownFileTypes.NRO.Value) || - (Path.GetExtension(file).ToLower() is ".nso" && ConfigurationState.Instance.Ui.ShownFileTypes.NSO.Value); + (Path.GetExtension(file).ToLower() is ".nsp" && ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value) || + (Path.GetExtension(file).ToLower() is ".pfs0" && ConfigurationState.Instance.UI.ShownFileTypes.PFS0.Value) || + (Path.GetExtension(file).ToLower() is ".xci" && ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value) || + (Path.GetExtension(file).ToLower() is ".nca" && ConfigurationState.Instance.UI.ShownFileTypes.NCA.Value) || + (Path.GetExtension(file).ToLower() is ".nro" && ConfigurationState.Instance.UI.ShownFileTypes.NRO.Value) || + (Path.GetExtension(file).ToLower() is ".nso" && ConfigurationState.Instance.UI.ShownFileTypes.NSO.Value); }); foreach (string app in files) diff --git a/src/Ryujinx.Ui.Common/App/ApplicationMetadata.cs b/src/Ryujinx.UI.Common/App/ApplicationMetadata.cs similarity index 98% rename from src/Ryujinx.Ui.Common/App/ApplicationMetadata.cs rename to src/Ryujinx.UI.Common/App/ApplicationMetadata.cs index 43647feef..81193c5b3 100644 --- a/src/Ryujinx.Ui.Common/App/ApplicationMetadata.cs +++ b/src/Ryujinx.UI.Common/App/ApplicationMetadata.cs @@ -1,7 +1,7 @@ using System; using System.Text.Json.Serialization; -namespace Ryujinx.Ui.App.Common +namespace Ryujinx.UI.App.Common { public class ApplicationMetadata { diff --git a/src/Ryujinx.Ui.Common/Configuration/AudioBackend.cs b/src/Ryujinx.UI.Common/Configuration/AudioBackend.cs similarity index 85% rename from src/Ryujinx.Ui.Common/Configuration/AudioBackend.cs rename to src/Ryujinx.UI.Common/Configuration/AudioBackend.cs index dc0a5ac61..a952e7ac0 100644 --- a/src/Ryujinx.Ui.Common/Configuration/AudioBackend.cs +++ b/src/Ryujinx.UI.Common/Configuration/AudioBackend.cs @@ -1,7 +1,7 @@ using Ryujinx.Common.Utilities; using System.Text.Json.Serialization; -namespace Ryujinx.Ui.Common.Configuration +namespace Ryujinx.UI.Common.Configuration { [JsonConverter(typeof(TypedStringEnumConverter))] public enum AudioBackend diff --git a/src/Ryujinx.Ui.Common/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs similarity index 99% rename from src/Ryujinx.Ui.Common/Configuration/ConfigurationFileFormat.cs rename to src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs index 8a4db1fe7..0ee51d830 100644 --- a/src/Ryujinx.Ui.Common/Configuration/ConfigurationFileFormat.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs @@ -3,12 +3,12 @@ using Ryujinx.Common.Configuration.Multiplayer; using Ryujinx.Common.Logging; using Ryujinx.Common.Utilities; -using Ryujinx.Ui.Common.Configuration.System; -using Ryujinx.Ui.Common.Configuration.Ui; +using Ryujinx.UI.Common.Configuration.System; +using Ryujinx.UI.Common.Configuration.UI; using System.Collections.Generic; using System.Text.Json.Nodes; -namespace Ryujinx.Ui.Common.Configuration +namespace Ryujinx.UI.Common.Configuration { public class ConfigurationFileFormat { diff --git a/src/Ryujinx.Ui.Common/Configuration/ConfigurationFileFormatSettings.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormatSettings.cs similarity index 85% rename from src/Ryujinx.Ui.Common/Configuration/ConfigurationFileFormatSettings.cs rename to src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormatSettings.cs index 9a1841fc5..9861ebf1f 100644 --- a/src/Ryujinx.Ui.Common/Configuration/ConfigurationFileFormatSettings.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormatSettings.cs @@ -1,6 +1,6 @@ using Ryujinx.Common.Utilities; -namespace Ryujinx.Ui.Common.Configuration +namespace Ryujinx.UI.Common.Configuration { internal static class ConfigurationFileFormatSettings { diff --git a/src/Ryujinx.Ui.Common/Configuration/ConfigurationJsonSerializerContext.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationJsonSerializerContext.cs similarity index 85% rename from src/Ryujinx.Ui.Common/Configuration/ConfigurationJsonSerializerContext.cs rename to src/Ryujinx.UI.Common/Configuration/ConfigurationJsonSerializerContext.cs index 03989edec..3c3e3f20d 100644 --- a/src/Ryujinx.Ui.Common/Configuration/ConfigurationJsonSerializerContext.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationJsonSerializerContext.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Ryujinx.Ui.Common.Configuration +namespace Ryujinx.UI.Common.Configuration { [JsonSourceGenerationOptions(WriteIndented = true)] [JsonSerializable(typeof(ConfigurationFileFormat))] diff --git a/src/Ryujinx.Ui.Common/Configuration/ConfigurationState.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs similarity index 90% rename from src/Ryujinx.Ui.Common/Configuration/ConfigurationState.cs rename to src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs index b017d384c..1d6934ce3 100644 --- a/src/Ryujinx.Ui.Common/Configuration/ConfigurationState.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs @@ -6,21 +6,21 @@ using Ryujinx.Common.Configuration.Multiplayer; using Ryujinx.Common.Logging; using Ryujinx.Graphics.Vulkan; -using Ryujinx.Ui.Common.Configuration.System; -using Ryujinx.Ui.Common.Configuration.Ui; -using Ryujinx.Ui.Common.Helper; +using Ryujinx.UI.Common.Configuration.System; +using Ryujinx.UI.Common.Configuration.UI; +using Ryujinx.UI.Common.Helper; using System; using System.Collections.Generic; using System.Text.Json.Nodes; -namespace Ryujinx.Ui.Common.Configuration +namespace Ryujinx.UI.Common.Configuration { public class ConfigurationState { /// /// UI configuration section /// - public class UiSection + public class UISection { public class Columns { @@ -186,7 +186,7 @@ public WindowStartupSettings() /// public ReactiveObject IsAscendingOrder { get; private set; } - public UiSection() + public UISection() { GuiColumns = new Columns(); ColumnSort = new ColumnSortSettings(); @@ -582,9 +582,9 @@ public MultiplayerSection() public static ConfigurationState Instance { get; private set; } /// - /// The Ui section + /// The UI section /// - public UiSection Ui { get; private set; } + public UISection UI { get; private set; } /// /// The Logger section @@ -633,7 +633,7 @@ public MultiplayerSection() private ConfigurationState() { - Ui = new UiSection(); + UI = new UISection(); Logger = new LoggerSection(); System = new SystemSection(); Graphics = new GraphicsSection(); @@ -696,51 +696,51 @@ public ConfigurationFileFormat ToFileFormat() UseHypervisor = System.UseHypervisor, GuiColumns = new GuiColumns { - FavColumn = Ui.GuiColumns.FavColumn, - IconColumn = Ui.GuiColumns.IconColumn, - AppColumn = Ui.GuiColumns.AppColumn, - DevColumn = Ui.GuiColumns.DevColumn, - VersionColumn = Ui.GuiColumns.VersionColumn, - TimePlayedColumn = Ui.GuiColumns.TimePlayedColumn, - LastPlayedColumn = Ui.GuiColumns.LastPlayedColumn, - FileExtColumn = Ui.GuiColumns.FileExtColumn, - FileSizeColumn = Ui.GuiColumns.FileSizeColumn, - PathColumn = Ui.GuiColumns.PathColumn, + FavColumn = UI.GuiColumns.FavColumn, + IconColumn = UI.GuiColumns.IconColumn, + AppColumn = UI.GuiColumns.AppColumn, + DevColumn = UI.GuiColumns.DevColumn, + VersionColumn = UI.GuiColumns.VersionColumn, + TimePlayedColumn = UI.GuiColumns.TimePlayedColumn, + LastPlayedColumn = UI.GuiColumns.LastPlayedColumn, + FileExtColumn = UI.GuiColumns.FileExtColumn, + FileSizeColumn = UI.GuiColumns.FileSizeColumn, + PathColumn = UI.GuiColumns.PathColumn, }, ColumnSort = new ColumnSort { - SortColumnId = Ui.ColumnSort.SortColumnId, - SortAscending = Ui.ColumnSort.SortAscending, + SortColumnId = UI.ColumnSort.SortColumnId, + SortAscending = UI.ColumnSort.SortAscending, }, - GameDirs = Ui.GameDirs, + GameDirs = UI.GameDirs, ShownFileTypes = new ShownFileTypes { - NSP = Ui.ShownFileTypes.NSP, - PFS0 = Ui.ShownFileTypes.PFS0, - XCI = Ui.ShownFileTypes.XCI, - NCA = Ui.ShownFileTypes.NCA, - NRO = Ui.ShownFileTypes.NRO, - NSO = Ui.ShownFileTypes.NSO, + NSP = UI.ShownFileTypes.NSP, + PFS0 = UI.ShownFileTypes.PFS0, + XCI = UI.ShownFileTypes.XCI, + NCA = UI.ShownFileTypes.NCA, + NRO = UI.ShownFileTypes.NRO, + NSO = UI.ShownFileTypes.NSO, }, WindowStartup = new WindowStartup { - WindowSizeWidth = Ui.WindowStartup.WindowSizeWidth, - WindowSizeHeight = Ui.WindowStartup.WindowSizeHeight, - WindowPositionX = Ui.WindowStartup.WindowPositionX, - WindowPositionY = Ui.WindowStartup.WindowPositionY, - WindowMaximized = Ui.WindowStartup.WindowMaximized, + WindowSizeWidth = UI.WindowStartup.WindowSizeWidth, + WindowSizeHeight = UI.WindowStartup.WindowSizeHeight, + WindowPositionX = UI.WindowStartup.WindowPositionX, + WindowPositionY = UI.WindowStartup.WindowPositionY, + WindowMaximized = UI.WindowStartup.WindowMaximized, }, - LanguageCode = Ui.LanguageCode, - EnableCustomTheme = Ui.EnableCustomTheme, - CustomThemePath = Ui.CustomThemePath, - BaseStyle = Ui.BaseStyle, - GameListViewMode = Ui.GameListViewMode, - ShowNames = Ui.ShowNames, - GridSize = Ui.GridSize, - ApplicationSort = Ui.ApplicationSort, - IsAscendingOrder = Ui.IsAscendingOrder, - StartFullscreen = Ui.StartFullscreen, - ShowConsole = Ui.ShowConsole, + LanguageCode = UI.LanguageCode, + EnableCustomTheme = UI.EnableCustomTheme, + CustomThemePath = UI.CustomThemePath, + BaseStyle = UI.BaseStyle, + GameListViewMode = UI.GameListViewMode, + ShowNames = UI.ShowNames, + GridSize = UI.GridSize, + ApplicationSort = UI.ApplicationSort, + IsAscendingOrder = UI.IsAscendingOrder, + StartFullscreen = UI.StartFullscreen, + ShowConsole = UI.ShowConsole, EnableKeyboard = Hid.EnableKeyboard, EnableMouse = Hid.EnableMouse, Hotkeys = Hid.Hotkeys, @@ -806,41 +806,41 @@ public void LoadDefault() System.UseHypervisor.Value = true; Multiplayer.LanInterfaceId.Value = "0"; Multiplayer.Mode.Value = MultiplayerMode.Disabled; - Ui.GuiColumns.FavColumn.Value = true; - Ui.GuiColumns.IconColumn.Value = true; - Ui.GuiColumns.AppColumn.Value = true; - Ui.GuiColumns.DevColumn.Value = true; - Ui.GuiColumns.VersionColumn.Value = true; - Ui.GuiColumns.TimePlayedColumn.Value = true; - Ui.GuiColumns.LastPlayedColumn.Value = true; - Ui.GuiColumns.FileExtColumn.Value = true; - Ui.GuiColumns.FileSizeColumn.Value = true; - Ui.GuiColumns.PathColumn.Value = true; - Ui.ColumnSort.SortColumnId.Value = 0; - Ui.ColumnSort.SortAscending.Value = false; - Ui.GameDirs.Value = new List(); - Ui.ShownFileTypes.NSP.Value = true; - Ui.ShownFileTypes.PFS0.Value = true; - Ui.ShownFileTypes.XCI.Value = true; - Ui.ShownFileTypes.NCA.Value = true; - Ui.ShownFileTypes.NRO.Value = true; - Ui.ShownFileTypes.NSO.Value = true; - Ui.EnableCustomTheme.Value = true; - Ui.LanguageCode.Value = "en_US"; - Ui.CustomThemePath.Value = ""; - Ui.BaseStyle.Value = "Dark"; - Ui.GameListViewMode.Value = 0; - Ui.ShowNames.Value = true; - Ui.GridSize.Value = 2; - Ui.ApplicationSort.Value = 0; - Ui.IsAscendingOrder.Value = true; - Ui.StartFullscreen.Value = false; - Ui.ShowConsole.Value = true; - Ui.WindowStartup.WindowSizeWidth.Value = 1280; - Ui.WindowStartup.WindowSizeHeight.Value = 760; - Ui.WindowStartup.WindowPositionX.Value = 0; - Ui.WindowStartup.WindowPositionY.Value = 0; - Ui.WindowStartup.WindowMaximized.Value = false; + UI.GuiColumns.FavColumn.Value = true; + UI.GuiColumns.IconColumn.Value = true; + UI.GuiColumns.AppColumn.Value = true; + UI.GuiColumns.DevColumn.Value = true; + UI.GuiColumns.VersionColumn.Value = true; + UI.GuiColumns.TimePlayedColumn.Value = true; + UI.GuiColumns.LastPlayedColumn.Value = true; + UI.GuiColumns.FileExtColumn.Value = true; + UI.GuiColumns.FileSizeColumn.Value = true; + UI.GuiColumns.PathColumn.Value = true; + UI.ColumnSort.SortColumnId.Value = 0; + UI.ColumnSort.SortAscending.Value = false; + UI.GameDirs.Value = new List(); + UI.ShownFileTypes.NSP.Value = true; + UI.ShownFileTypes.PFS0.Value = true; + UI.ShownFileTypes.XCI.Value = true; + UI.ShownFileTypes.NCA.Value = true; + UI.ShownFileTypes.NRO.Value = true; + UI.ShownFileTypes.NSO.Value = true; + UI.EnableCustomTheme.Value = true; + UI.LanguageCode.Value = "en_US"; + UI.CustomThemePath.Value = ""; + UI.BaseStyle.Value = "Dark"; + UI.GameListViewMode.Value = 0; + UI.ShowNames.Value = true; + UI.GridSize.Value = 2; + UI.ApplicationSort.Value = 0; + UI.IsAscendingOrder.Value = true; + UI.StartFullscreen.Value = false; + UI.ShowConsole.Value = true; + UI.WindowStartup.WindowSizeWidth.Value = 1280; + UI.WindowStartup.WindowSizeHeight.Value = 760; + UI.WindowStartup.WindowPositionX.Value = 0; + UI.WindowStartup.WindowPositionY.Value = 0; + UI.WindowStartup.WindowMaximized.Value = false; Hid.EnableKeyboard.Value = false; Hid.EnableMouse.Value = false; Hid.Hotkeys.Value = new KeyboardHotkeys @@ -848,7 +848,7 @@ public void LoadDefault() ToggleVsync = Key.F1, ToggleMute = Key.F2, Screenshot = Key.F8, - ShowUi = Key.F4, + ShowUI = Key.F4, Pause = Key.F5, ResScaleUp = Key.Unbound, ResScaleDown = Key.Unbound, @@ -1185,7 +1185,7 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu { ToggleVsync = Key.F1, Screenshot = Key.F8, - ShowUi = Key.F4, + ShowUI = Key.F4, }; configurationFileUpdated = true; @@ -1228,7 +1228,7 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu { ToggleVsync = configurationFileFormat.Hotkeys.ToggleVsync, Screenshot = configurationFileFormat.Hotkeys.Screenshot, - ShowUi = configurationFileFormat.Hotkeys.ShowUi, + ShowUI = configurationFileFormat.Hotkeys.ShowUI, Pause = Key.F5, }; @@ -1243,7 +1243,7 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu { ToggleVsync = configurationFileFormat.Hotkeys.ToggleVsync, Screenshot = configurationFileFormat.Hotkeys.Screenshot, - ShowUi = configurationFileFormat.Hotkeys.ShowUi, + ShowUI = configurationFileFormat.Hotkeys.ShowUI, Pause = configurationFileFormat.Hotkeys.Pause, ToggleMute = Key.F2, }; @@ -1317,7 +1317,7 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu { ToggleVsync = configurationFileFormat.Hotkeys.ToggleVsync, Screenshot = configurationFileFormat.Hotkeys.Screenshot, - ShowUi = configurationFileFormat.Hotkeys.ShowUi, + ShowUI = configurationFileFormat.Hotkeys.ShowUI, Pause = configurationFileFormat.Hotkeys.Pause, ToggleMute = configurationFileFormat.Hotkeys.ToggleMute, ResScaleUp = Key.Unbound, @@ -1344,7 +1344,7 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu { ToggleVsync = configurationFileFormat.Hotkeys.ToggleVsync, Screenshot = configurationFileFormat.Hotkeys.Screenshot, - ShowUi = configurationFileFormat.Hotkeys.ShowUi, + ShowUI = configurationFileFormat.Hotkeys.ShowUI, Pause = configurationFileFormat.Hotkeys.Pause, ToggleMute = configurationFileFormat.Hotkeys.ToggleMute, ResScaleUp = configurationFileFormat.Hotkeys.ResScaleUp, @@ -1476,41 +1476,41 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu System.ExpandRam.Value = configurationFileFormat.ExpandRam; System.IgnoreMissingServices.Value = configurationFileFormat.IgnoreMissingServices; System.UseHypervisor.Value = configurationFileFormat.UseHypervisor; - Ui.GuiColumns.FavColumn.Value = configurationFileFormat.GuiColumns.FavColumn; - Ui.GuiColumns.IconColumn.Value = configurationFileFormat.GuiColumns.IconColumn; - Ui.GuiColumns.AppColumn.Value = configurationFileFormat.GuiColumns.AppColumn; - Ui.GuiColumns.DevColumn.Value = configurationFileFormat.GuiColumns.DevColumn; - Ui.GuiColumns.VersionColumn.Value = configurationFileFormat.GuiColumns.VersionColumn; - Ui.GuiColumns.TimePlayedColumn.Value = configurationFileFormat.GuiColumns.TimePlayedColumn; - Ui.GuiColumns.LastPlayedColumn.Value = configurationFileFormat.GuiColumns.LastPlayedColumn; - Ui.GuiColumns.FileExtColumn.Value = configurationFileFormat.GuiColumns.FileExtColumn; - Ui.GuiColumns.FileSizeColumn.Value = configurationFileFormat.GuiColumns.FileSizeColumn; - Ui.GuiColumns.PathColumn.Value = configurationFileFormat.GuiColumns.PathColumn; - Ui.ColumnSort.SortColumnId.Value = configurationFileFormat.ColumnSort.SortColumnId; - Ui.ColumnSort.SortAscending.Value = configurationFileFormat.ColumnSort.SortAscending; - Ui.GameDirs.Value = configurationFileFormat.GameDirs; - Ui.ShownFileTypes.NSP.Value = configurationFileFormat.ShownFileTypes.NSP; - Ui.ShownFileTypes.PFS0.Value = configurationFileFormat.ShownFileTypes.PFS0; - Ui.ShownFileTypes.XCI.Value = configurationFileFormat.ShownFileTypes.XCI; - Ui.ShownFileTypes.NCA.Value = configurationFileFormat.ShownFileTypes.NCA; - Ui.ShownFileTypes.NRO.Value = configurationFileFormat.ShownFileTypes.NRO; - Ui.ShownFileTypes.NSO.Value = configurationFileFormat.ShownFileTypes.NSO; - Ui.EnableCustomTheme.Value = configurationFileFormat.EnableCustomTheme; - Ui.LanguageCode.Value = configurationFileFormat.LanguageCode; - Ui.CustomThemePath.Value = configurationFileFormat.CustomThemePath; - Ui.BaseStyle.Value = configurationFileFormat.BaseStyle; - Ui.GameListViewMode.Value = configurationFileFormat.GameListViewMode; - Ui.ShowNames.Value = configurationFileFormat.ShowNames; - Ui.IsAscendingOrder.Value = configurationFileFormat.IsAscendingOrder; - Ui.GridSize.Value = configurationFileFormat.GridSize; - Ui.ApplicationSort.Value = configurationFileFormat.ApplicationSort; - Ui.StartFullscreen.Value = configurationFileFormat.StartFullscreen; - Ui.ShowConsole.Value = configurationFileFormat.ShowConsole; - Ui.WindowStartup.WindowSizeWidth.Value = configurationFileFormat.WindowStartup.WindowSizeWidth; - Ui.WindowStartup.WindowSizeHeight.Value = configurationFileFormat.WindowStartup.WindowSizeHeight; - Ui.WindowStartup.WindowPositionX.Value = configurationFileFormat.WindowStartup.WindowPositionX; - Ui.WindowStartup.WindowPositionY.Value = configurationFileFormat.WindowStartup.WindowPositionY; - Ui.WindowStartup.WindowMaximized.Value = configurationFileFormat.WindowStartup.WindowMaximized; + UI.GuiColumns.FavColumn.Value = configurationFileFormat.GuiColumns.FavColumn; + UI.GuiColumns.IconColumn.Value = configurationFileFormat.GuiColumns.IconColumn; + UI.GuiColumns.AppColumn.Value = configurationFileFormat.GuiColumns.AppColumn; + UI.GuiColumns.DevColumn.Value = configurationFileFormat.GuiColumns.DevColumn; + UI.GuiColumns.VersionColumn.Value = configurationFileFormat.GuiColumns.VersionColumn; + UI.GuiColumns.TimePlayedColumn.Value = configurationFileFormat.GuiColumns.TimePlayedColumn; + UI.GuiColumns.LastPlayedColumn.Value = configurationFileFormat.GuiColumns.LastPlayedColumn; + UI.GuiColumns.FileExtColumn.Value = configurationFileFormat.GuiColumns.FileExtColumn; + UI.GuiColumns.FileSizeColumn.Value = configurationFileFormat.GuiColumns.FileSizeColumn; + UI.GuiColumns.PathColumn.Value = configurationFileFormat.GuiColumns.PathColumn; + UI.ColumnSort.SortColumnId.Value = configurationFileFormat.ColumnSort.SortColumnId; + UI.ColumnSort.SortAscending.Value = configurationFileFormat.ColumnSort.SortAscending; + UI.GameDirs.Value = configurationFileFormat.GameDirs; + UI.ShownFileTypes.NSP.Value = configurationFileFormat.ShownFileTypes.NSP; + UI.ShownFileTypes.PFS0.Value = configurationFileFormat.ShownFileTypes.PFS0; + UI.ShownFileTypes.XCI.Value = configurationFileFormat.ShownFileTypes.XCI; + UI.ShownFileTypes.NCA.Value = configurationFileFormat.ShownFileTypes.NCA; + UI.ShownFileTypes.NRO.Value = configurationFileFormat.ShownFileTypes.NRO; + UI.ShownFileTypes.NSO.Value = configurationFileFormat.ShownFileTypes.NSO; + UI.EnableCustomTheme.Value = configurationFileFormat.EnableCustomTheme; + UI.LanguageCode.Value = configurationFileFormat.LanguageCode; + UI.CustomThemePath.Value = configurationFileFormat.CustomThemePath; + UI.BaseStyle.Value = configurationFileFormat.BaseStyle; + UI.GameListViewMode.Value = configurationFileFormat.GameListViewMode; + UI.ShowNames.Value = configurationFileFormat.ShowNames; + UI.IsAscendingOrder.Value = configurationFileFormat.IsAscendingOrder; + UI.GridSize.Value = configurationFileFormat.GridSize; + UI.ApplicationSort.Value = configurationFileFormat.ApplicationSort; + UI.StartFullscreen.Value = configurationFileFormat.StartFullscreen; + UI.ShowConsole.Value = configurationFileFormat.ShowConsole; + UI.WindowStartup.WindowSizeWidth.Value = configurationFileFormat.WindowStartup.WindowSizeWidth; + UI.WindowStartup.WindowSizeHeight.Value = configurationFileFormat.WindowStartup.WindowSizeHeight; + UI.WindowStartup.WindowPositionX.Value = configurationFileFormat.WindowStartup.WindowPositionX; + UI.WindowStartup.WindowPositionY.Value = configurationFileFormat.WindowStartup.WindowPositionY; + UI.WindowStartup.WindowMaximized.Value = configurationFileFormat.WindowStartup.WindowMaximized; Hid.EnableKeyboard.Value = configurationFileFormat.EnableKeyboard; Hid.EnableMouse.Value = configurationFileFormat.EnableMouse; Hid.Hotkeys.Value = configurationFileFormat.Hotkeys; diff --git a/src/Ryujinx.Ui.Common/Configuration/FileTypes.cs b/src/Ryujinx.UI.Common/Configuration/FileTypes.cs similarity index 81% rename from src/Ryujinx.Ui.Common/Configuration/FileTypes.cs rename to src/Ryujinx.UI.Common/Configuration/FileTypes.cs index 9d2f63864..1974207b6 100644 --- a/src/Ryujinx.Ui.Common/Configuration/FileTypes.cs +++ b/src/Ryujinx.UI.Common/Configuration/FileTypes.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.Ui.Common +namespace Ryujinx.UI.Common { public enum FileTypes { diff --git a/src/Ryujinx.Ui.Common/Configuration/LoggerModule.cs b/src/Ryujinx.UI.Common/Configuration/LoggerModule.cs similarity index 98% rename from src/Ryujinx.Ui.Common/Configuration/LoggerModule.cs rename to src/Ryujinx.UI.Common/Configuration/LoggerModule.cs index 2edcd07f0..9cb283593 100644 --- a/src/Ryujinx.Ui.Common/Configuration/LoggerModule.cs +++ b/src/Ryujinx.UI.Common/Configuration/LoggerModule.cs @@ -5,7 +5,7 @@ using System; using System.IO; -namespace Ryujinx.Ui.Common.Configuration +namespace Ryujinx.UI.Common.Configuration { public static class LoggerModule { diff --git a/src/Ryujinx.Ui.Common/Configuration/System/Language.cs b/src/Ryujinx.UI.Common/Configuration/System/Language.cs similarity index 91% rename from src/Ryujinx.Ui.Common/Configuration/System/Language.cs rename to src/Ryujinx.UI.Common/Configuration/System/Language.cs index 72416bfe7..d1d395b00 100644 --- a/src/Ryujinx.Ui.Common/Configuration/System/Language.cs +++ b/src/Ryujinx.UI.Common/Configuration/System/Language.cs @@ -1,7 +1,7 @@ using Ryujinx.Common.Utilities; using System.Text.Json.Serialization; -namespace Ryujinx.Ui.Common.Configuration.System +namespace Ryujinx.UI.Common.Configuration.System { [JsonConverter(typeof(TypedStringEnumConverter))] public enum Language diff --git a/src/Ryujinx.Ui.Common/Configuration/System/Region.cs b/src/Ryujinx.UI.Common/Configuration/System/Region.cs similarity index 85% rename from src/Ryujinx.Ui.Common/Configuration/System/Region.cs rename to src/Ryujinx.UI.Common/Configuration/System/Region.cs index 2478b40f9..6087c70e5 100644 --- a/src/Ryujinx.Ui.Common/Configuration/System/Region.cs +++ b/src/Ryujinx.UI.Common/Configuration/System/Region.cs @@ -1,7 +1,7 @@ using Ryujinx.Common.Utilities; using System.Text.Json.Serialization; -namespace Ryujinx.Ui.Common.Configuration.System +namespace Ryujinx.UI.Common.Configuration.System { [JsonConverter(typeof(TypedStringEnumConverter))] public enum Region diff --git a/src/Ryujinx.Ui.Common/Configuration/Ui/ColumnSort.cs b/src/Ryujinx.UI.Common/Configuration/UI/ColumnSort.cs similarity index 75% rename from src/Ryujinx.Ui.Common/Configuration/Ui/ColumnSort.cs rename to src/Ryujinx.UI.Common/Configuration/UI/ColumnSort.cs index 88cf7cdac..44e98c407 100644 --- a/src/Ryujinx.Ui.Common/Configuration/Ui/ColumnSort.cs +++ b/src/Ryujinx.UI.Common/Configuration/UI/ColumnSort.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.Ui.Common.Configuration.Ui +namespace Ryujinx.UI.Common.Configuration.UI { public struct ColumnSort { diff --git a/src/Ryujinx.Ui.Common/Configuration/Ui/GuiColumns.cs b/src/Ryujinx.UI.Common/Configuration/UI/GuiColumns.cs similarity index 91% rename from src/Ryujinx.Ui.Common/Configuration/Ui/GuiColumns.cs rename to src/Ryujinx.UI.Common/Configuration/UI/GuiColumns.cs index 7e944015b..c778ef1f1 100644 --- a/src/Ryujinx.Ui.Common/Configuration/Ui/GuiColumns.cs +++ b/src/Ryujinx.UI.Common/Configuration/UI/GuiColumns.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.Ui.Common.Configuration.Ui +namespace Ryujinx.UI.Common.Configuration.UI { public struct GuiColumns { diff --git a/src/Ryujinx.Ui.Common/Configuration/Ui/ShownFileTypes.cs b/src/Ryujinx.UI.Common/Configuration/UI/ShownFileTypes.cs similarity index 86% rename from src/Ryujinx.Ui.Common/Configuration/Ui/ShownFileTypes.cs rename to src/Ryujinx.UI.Common/Configuration/UI/ShownFileTypes.cs index 1b14fd467..6c72a6930 100644 --- a/src/Ryujinx.Ui.Common/Configuration/Ui/ShownFileTypes.cs +++ b/src/Ryujinx.UI.Common/Configuration/UI/ShownFileTypes.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.Ui.Common.Configuration.Ui +namespace Ryujinx.UI.Common.Configuration.UI { public struct ShownFileTypes { diff --git a/src/Ryujinx.Ui.Common/Configuration/Ui/WindowStartup.cs b/src/Ryujinx.UI.Common/Configuration/UI/WindowStartup.cs similarity index 86% rename from src/Ryujinx.Ui.Common/Configuration/Ui/WindowStartup.cs rename to src/Ryujinx.UI.Common/Configuration/UI/WindowStartup.cs index ce0dde6aa..0df459134 100644 --- a/src/Ryujinx.Ui.Common/Configuration/Ui/WindowStartup.cs +++ b/src/Ryujinx.UI.Common/Configuration/UI/WindowStartup.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.Ui.Common.Configuration.Ui +namespace Ryujinx.UI.Common.Configuration.UI { public struct WindowStartup { diff --git a/src/Ryujinx.Ui.Common/DiscordIntegrationModule.cs b/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs similarity index 97% rename from src/Ryujinx.Ui.Common/DiscordIntegrationModule.cs rename to src/Ryujinx.UI.Common/DiscordIntegrationModule.cs index edc634aa5..0b9439eaa 100644 --- a/src/Ryujinx.Ui.Common/DiscordIntegrationModule.cs +++ b/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs @@ -1,8 +1,8 @@ using DiscordRPC; using Ryujinx.Common; -using Ryujinx.Ui.Common.Configuration; +using Ryujinx.UI.Common.Configuration; -namespace Ryujinx.Ui.Common +namespace Ryujinx.UI.Common { public static class DiscordIntegrationModule { diff --git a/src/Ryujinx.Ui.Common/Extensions/FileTypeExtensions.cs b/src/Ryujinx.UI.Common/Extensions/FileTypeExtensions.cs similarity index 91% rename from src/Ryujinx.Ui.Common/Extensions/FileTypeExtensions.cs rename to src/Ryujinx.UI.Common/Extensions/FileTypeExtensions.cs index c827f750e..7e71ba7a4 100644 --- a/src/Ryujinx.Ui.Common/Extensions/FileTypeExtensions.cs +++ b/src/Ryujinx.UI.Common/Extensions/FileTypeExtensions.cs @@ -1,7 +1,7 @@ using System; -using static Ryujinx.Ui.Common.Configuration.ConfigurationState.UiSection; +using static Ryujinx.UI.Common.Configuration.ConfigurationState.UISection; -namespace Ryujinx.Ui.Common +namespace Ryujinx.UI.Common { public static class FileTypesExtensions { diff --git a/src/Ryujinx.Ui.Common/Helper/CommandLineState.cs b/src/Ryujinx.UI.Common/Helper/CommandLineState.cs similarity index 98% rename from src/Ryujinx.Ui.Common/Helper/CommandLineState.cs rename to src/Ryujinx.UI.Common/Helper/CommandLineState.cs index 714cf2f0a..c3c5bd37e 100644 --- a/src/Ryujinx.Ui.Common/Helper/CommandLineState.cs +++ b/src/Ryujinx.UI.Common/Helper/CommandLineState.cs @@ -1,7 +1,7 @@ using Ryujinx.Common.Logging; using System.Collections.Generic; -namespace Ryujinx.Ui.Common.Helper +namespace Ryujinx.UI.Common.Helper { public static class CommandLineState { diff --git a/src/Ryujinx.Ui.Common/Helper/ConsoleHelper.cs b/src/Ryujinx.UI.Common/Helper/ConsoleHelper.cs similarity index 97% rename from src/Ryujinx.Ui.Common/Helper/ConsoleHelper.cs rename to src/Ryujinx.UI.Common/Helper/ConsoleHelper.cs index 65155641f..208ff5c9d 100644 --- a/src/Ryujinx.Ui.Common/Helper/ConsoleHelper.cs +++ b/src/Ryujinx.UI.Common/Helper/ConsoleHelper.cs @@ -3,7 +3,7 @@ using System.Runtime.InteropServices; using System.Runtime.Versioning; -namespace Ryujinx.Ui.Common.Helper +namespace Ryujinx.UI.Common.Helper { public static partial class ConsoleHelper { diff --git a/src/Ryujinx.Ui.Common/Helper/FileAssociationHelper.cs b/src/Ryujinx.UI.Common/Helper/FileAssociationHelper.cs similarity index 99% rename from src/Ryujinx.Ui.Common/Helper/FileAssociationHelper.cs rename to src/Ryujinx.UI.Common/Helper/FileAssociationHelper.cs index daa59d251..7ed020319 100644 --- a/src/Ryujinx.Ui.Common/Helper/FileAssociationHelper.cs +++ b/src/Ryujinx.UI.Common/Helper/FileAssociationHelper.cs @@ -7,7 +7,7 @@ using System.Runtime.InteropServices; using System.Runtime.Versioning; -namespace Ryujinx.Ui.Common.Helper +namespace Ryujinx.UI.Common.Helper { public static partial class FileAssociationHelper { diff --git a/src/Ryujinx.Ui.Common/Helper/LinuxHelper.cs b/src/Ryujinx.UI.Common/Helper/LinuxHelper.cs similarity index 98% rename from src/Ryujinx.Ui.Common/Helper/LinuxHelper.cs rename to src/Ryujinx.UI.Common/Helper/LinuxHelper.cs index bf647719a..b57793791 100644 --- a/src/Ryujinx.Ui.Common/Helper/LinuxHelper.cs +++ b/src/Ryujinx.UI.Common/Helper/LinuxHelper.cs @@ -3,7 +3,7 @@ using System.IO; using System.Runtime.Versioning; -namespace Ryujinx.Ui.Common.Helper +namespace Ryujinx.UI.Common.Helper { [SupportedOSPlatform("linux")] public static class LinuxHelper diff --git a/src/Ryujinx.Ui.Common/Helper/ObjectiveC.cs b/src/Ryujinx.UI.Common/Helper/ObjectiveC.cs similarity index 99% rename from src/Ryujinx.Ui.Common/Helper/ObjectiveC.cs rename to src/Ryujinx.UI.Common/Helper/ObjectiveC.cs index af8723e2f..6aba377a3 100644 --- a/src/Ryujinx.Ui.Common/Helper/ObjectiveC.cs +++ b/src/Ryujinx.UI.Common/Helper/ObjectiveC.cs @@ -2,7 +2,7 @@ using System.Runtime.InteropServices; using System.Runtime.Versioning; -namespace Ryujinx.Ui.Common.Helper +namespace Ryujinx.UI.Common.Helper { [SupportedOSPlatform("macos")] public static partial class ObjectiveC diff --git a/src/Ryujinx.Ui.Common/Helper/OpenHelper.cs b/src/Ryujinx.UI.Common/Helper/OpenHelper.cs similarity index 99% rename from src/Ryujinx.Ui.Common/Helper/OpenHelper.cs rename to src/Ryujinx.UI.Common/Helper/OpenHelper.cs index 04ebbf3b0..af6170afe 100644 --- a/src/Ryujinx.Ui.Common/Helper/OpenHelper.cs +++ b/src/Ryujinx.UI.Common/Helper/OpenHelper.cs @@ -4,7 +4,7 @@ using System.IO; using System.Runtime.InteropServices; -namespace Ryujinx.Ui.Common.Helper +namespace Ryujinx.UI.Common.Helper { public static partial class OpenHelper { diff --git a/src/Ryujinx.Ui.Common/Helper/SetupValidator.cs b/src/Ryujinx.UI.Common/Helper/SetupValidator.cs similarity index 99% rename from src/Ryujinx.Ui.Common/Helper/SetupValidator.cs rename to src/Ryujinx.UI.Common/Helper/SetupValidator.cs index 65c38d7b8..a954be26f 100644 --- a/src/Ryujinx.Ui.Common/Helper/SetupValidator.cs +++ b/src/Ryujinx.UI.Common/Helper/SetupValidator.cs @@ -3,7 +3,7 @@ using System; using System.IO; -namespace Ryujinx.Ui.Common.Helper +namespace Ryujinx.UI.Common.Helper { /// /// Ensure installation validity diff --git a/src/Ryujinx.Ui.Common/Helper/ShortcutHelper.cs b/src/Ryujinx.UI.Common/Helper/ShortcutHelper.cs similarity index 97% rename from src/Ryujinx.Ui.Common/Helper/ShortcutHelper.cs rename to src/Ryujinx.UI.Common/Helper/ShortcutHelper.cs index 3d27d3ffb..c2085b28c 100644 --- a/src/Ryujinx.Ui.Common/Helper/ShortcutHelper.cs +++ b/src/Ryujinx.UI.Common/Helper/ShortcutHelper.cs @@ -10,7 +10,7 @@ using System.IO; using System.Runtime.Versioning; -namespace Ryujinx.Ui.Common.Helper +namespace Ryujinx.UI.Common.Helper { public static class ShortcutHelper { @@ -34,7 +34,7 @@ private static void CreateShortcutWindows(string applicationFilePath, byte[] ico private static void CreateShortcutLinux(string applicationFilePath, 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"); + var desktopFile = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-template.desktop"); iconPath += ".png"; var image = Image.Load(iconData); @@ -48,8 +48,8 @@ private static void CreateShortcutLinux(string applicationFilePath, byte[] iconD private static void CreateShortcutMacos(string appFilePath, byte[] iconData, string desktopPath, string cleanedAppName) { string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx"); - var plistFile = EmbeddedResources.ReadAllText("Ryujinx.Ui.Common/shortcut-template.plist"); - var shortcutScript = EmbeddedResources.ReadAllText("Ryujinx.Ui.Common/shortcut-launch-script.sh"); + var plistFile = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-template.plist"); + var shortcutScript = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-launch-script.sh"); // Macos .App folder string contentFolderPath = Path.Combine("/Applications", cleanedAppName + ".app", "Contents"); string scriptFolderPath = Path.Combine(contentFolderPath, "MacOS"); diff --git a/src/Ryujinx.Ui.Common/Helper/TitleHelper.cs b/src/Ryujinx.UI.Common/Helper/TitleHelper.cs similarity index 96% rename from src/Ryujinx.Ui.Common/Helper/TitleHelper.cs rename to src/Ryujinx.UI.Common/Helper/TitleHelper.cs index 089b52154..8b47ac38b 100644 --- a/src/Ryujinx.Ui.Common/Helper/TitleHelper.cs +++ b/src/Ryujinx.UI.Common/Helper/TitleHelper.cs @@ -1,7 +1,7 @@ using Ryujinx.HLE.Loaders.Processes; using System; -namespace Ryujinx.Ui.Common.Helper +namespace Ryujinx.UI.Common.Helper { public static class TitleHelper { diff --git a/src/Ryujinx.Ui.Common/Helper/ValueFormatUtils.cs b/src/Ryujinx.UI.Common/Helper/ValueFormatUtils.cs similarity index 99% rename from src/Ryujinx.Ui.Common/Helper/ValueFormatUtils.cs rename to src/Ryujinx.UI.Common/Helper/ValueFormatUtils.cs index b1597a7cc..8ea3e721f 100644 --- a/src/Ryujinx.Ui.Common/Helper/ValueFormatUtils.cs +++ b/src/Ryujinx.UI.Common/Helper/ValueFormatUtils.cs @@ -2,7 +2,7 @@ using System.Globalization; using System.Linq; -namespace Ryujinx.Ui.Common.Helper +namespace Ryujinx.UI.Common.Helper { public static class ValueFormatUtils { diff --git a/src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApi.cs b/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApi.cs similarity index 97% rename from src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApi.cs rename to src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApi.cs index ff0b80c46..7989f0f1a 100644 --- a/src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApi.cs +++ b/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApi.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text.Json.Serialization; -namespace Ryujinx.Ui.Common.Models.Amiibo +namespace Ryujinx.UI.Common.Models.Amiibo { public struct AmiiboApi : IEquatable { diff --git a/src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApiGamesSwitch.cs b/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApiGamesSwitch.cs similarity index 90% rename from src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApiGamesSwitch.cs rename to src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApiGamesSwitch.cs index 3c62b7cc4..40e635bf0 100644 --- a/src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApiGamesSwitch.cs +++ b/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApiGamesSwitch.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Text.Json.Serialization; -namespace Ryujinx.Ui.Common.Models.Amiibo +namespace Ryujinx.UI.Common.Models.Amiibo { public class AmiiboApiGamesSwitch { diff --git a/src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApiUsage.cs b/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApiUsage.cs similarity index 85% rename from src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApiUsage.cs rename to src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApiUsage.cs index 3c774fd56..4f8d292b1 100644 --- a/src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApiUsage.cs +++ b/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApiUsage.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Ryujinx.Ui.Common.Models.Amiibo +namespace Ryujinx.UI.Common.Models.Amiibo { public class AmiiboApiUsage { diff --git a/src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboJson.cs b/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboJson.cs similarity index 88% rename from src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboJson.cs rename to src/Ryujinx.UI.Common/Models/Amiibo/AmiiboJson.cs index c9d91c50a..15083f505 100644 --- a/src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboJson.cs +++ b/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboJson.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text.Json.Serialization; -namespace Ryujinx.Ui.Common.Models.Amiibo +namespace Ryujinx.UI.Common.Models.Amiibo { public struct AmiiboJson { diff --git a/src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboJsonSerializerContext.cs b/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboJsonSerializerContext.cs similarity index 80% rename from src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboJsonSerializerContext.cs rename to src/Ryujinx.UI.Common/Models/Amiibo/AmiiboJsonSerializerContext.cs index 4906c6524..bc3f1303c 100644 --- a/src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboJsonSerializerContext.cs +++ b/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboJsonSerializerContext.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Ryujinx.Ui.Common.Models.Amiibo +namespace Ryujinx.UI.Common.Models.Amiibo { [JsonSerializable(typeof(AmiiboJson))] public partial class AmiiboJsonSerializerContext : JsonSerializerContext diff --git a/src/Ryujinx.Ui.Common/Models/Github/GithubReleaseAssetJsonResponse.cs b/src/Ryujinx.UI.Common/Models/Github/GithubReleaseAssetJsonResponse.cs similarity index 82% rename from src/Ryujinx.Ui.Common/Models/Github/GithubReleaseAssetJsonResponse.cs rename to src/Ryujinx.UI.Common/Models/Github/GithubReleaseAssetJsonResponse.cs index 67d238d24..8f528dc0b 100644 --- a/src/Ryujinx.Ui.Common/Models/Github/GithubReleaseAssetJsonResponse.cs +++ b/src/Ryujinx.UI.Common/Models/Github/GithubReleaseAssetJsonResponse.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.Ui.Common.Models.Github +namespace Ryujinx.UI.Common.Models.Github { public class GithubReleaseAssetJsonResponse { diff --git a/src/Ryujinx.Ui.Common/Models/Github/GithubReleasesJsonResponse.cs b/src/Ryujinx.UI.Common/Models/Github/GithubReleasesJsonResponse.cs similarity index 83% rename from src/Ryujinx.Ui.Common/Models/Github/GithubReleasesJsonResponse.cs rename to src/Ryujinx.UI.Common/Models/Github/GithubReleasesJsonResponse.cs index 0f83e32cc..0250e1094 100644 --- a/src/Ryujinx.Ui.Common/Models/Github/GithubReleasesJsonResponse.cs +++ b/src/Ryujinx.UI.Common/Models/Github/GithubReleasesJsonResponse.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace Ryujinx.Ui.Common.Models.Github +namespace Ryujinx.UI.Common.Models.Github { public class GithubReleasesJsonResponse { diff --git a/src/Ryujinx.Ui.Common/Models/Github/GithubReleasesJsonSerializerContext.cs b/src/Ryujinx.UI.Common/Models/Github/GithubReleasesJsonSerializerContext.cs similarity index 85% rename from src/Ryujinx.Ui.Common/Models/Github/GithubReleasesJsonSerializerContext.cs rename to src/Ryujinx.UI.Common/Models/Github/GithubReleasesJsonSerializerContext.cs index 8a19277b3..71864257c 100644 --- a/src/Ryujinx.Ui.Common/Models/Github/GithubReleasesJsonSerializerContext.cs +++ b/src/Ryujinx.UI.Common/Models/Github/GithubReleasesJsonSerializerContext.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Ryujinx.Ui.Common.Models.Github +namespace Ryujinx.UI.Common.Models.Github { [JsonSerializable(typeof(GithubReleasesJsonResponse), GenerationMode = JsonSourceGenerationMode.Metadata)] public partial class GithubReleasesJsonSerializerContext : JsonSerializerContext diff --git a/src/Ryujinx.Ui.Common/Resources/Controller_JoyConLeft.svg b/src/Ryujinx.UI.Common/Resources/Controller_JoyConLeft.svg similarity index 100% rename from src/Ryujinx.Ui.Common/Resources/Controller_JoyConLeft.svg rename to src/Ryujinx.UI.Common/Resources/Controller_JoyConLeft.svg diff --git a/src/Ryujinx.Ui.Common/Resources/Controller_JoyConPair.svg b/src/Ryujinx.UI.Common/Resources/Controller_JoyConPair.svg similarity index 100% rename from src/Ryujinx.Ui.Common/Resources/Controller_JoyConPair.svg rename to src/Ryujinx.UI.Common/Resources/Controller_JoyConPair.svg diff --git a/src/Ryujinx.Ui.Common/Resources/Controller_JoyConRight.svg b/src/Ryujinx.UI.Common/Resources/Controller_JoyConRight.svg similarity index 100% rename from src/Ryujinx.Ui.Common/Resources/Controller_JoyConRight.svg rename to src/Ryujinx.UI.Common/Resources/Controller_JoyConRight.svg diff --git a/src/Ryujinx.Ui.Common/Resources/Controller_ProCon.svg b/src/Ryujinx.UI.Common/Resources/Controller_ProCon.svg similarity index 100% rename from src/Ryujinx.Ui.Common/Resources/Controller_ProCon.svg rename to src/Ryujinx.UI.Common/Resources/Controller_ProCon.svg diff --git a/src/Ryujinx.Ui.Common/Resources/Icon_NCA.png b/src/Ryujinx.UI.Common/Resources/Icon_NCA.png similarity index 100% rename from src/Ryujinx.Ui.Common/Resources/Icon_NCA.png rename to src/Ryujinx.UI.Common/Resources/Icon_NCA.png diff --git a/src/Ryujinx.Ui.Common/Resources/Icon_NRO.png b/src/Ryujinx.UI.Common/Resources/Icon_NRO.png similarity index 100% rename from src/Ryujinx.Ui.Common/Resources/Icon_NRO.png rename to src/Ryujinx.UI.Common/Resources/Icon_NRO.png diff --git a/src/Ryujinx.Ui.Common/Resources/Icon_NSO.png b/src/Ryujinx.UI.Common/Resources/Icon_NSO.png similarity index 100% rename from src/Ryujinx.Ui.Common/Resources/Icon_NSO.png rename to src/Ryujinx.UI.Common/Resources/Icon_NSO.png diff --git a/src/Ryujinx.Ui.Common/Resources/Icon_NSP.png b/src/Ryujinx.UI.Common/Resources/Icon_NSP.png similarity index 100% rename from src/Ryujinx.Ui.Common/Resources/Icon_NSP.png rename to src/Ryujinx.UI.Common/Resources/Icon_NSP.png diff --git a/src/Ryujinx.Ui.Common/Resources/Icon_XCI.png b/src/Ryujinx.UI.Common/Resources/Icon_XCI.png similarity index 100% rename from src/Ryujinx.Ui.Common/Resources/Icon_XCI.png rename to src/Ryujinx.UI.Common/Resources/Icon_XCI.png diff --git a/src/Ryujinx.Ui.Common/Resources/Logo_Amiibo.png b/src/Ryujinx.UI.Common/Resources/Logo_Amiibo.png similarity index 100% rename from src/Ryujinx.Ui.Common/Resources/Logo_Amiibo.png rename to src/Ryujinx.UI.Common/Resources/Logo_Amiibo.png diff --git a/src/Ryujinx.Ui.Common/Resources/Logo_Discord_Dark.png b/src/Ryujinx.UI.Common/Resources/Logo_Discord_Dark.png similarity index 100% rename from src/Ryujinx.Ui.Common/Resources/Logo_Discord_Dark.png rename to src/Ryujinx.UI.Common/Resources/Logo_Discord_Dark.png diff --git a/src/Ryujinx.Ui.Common/Resources/Logo_Discord_Light.png b/src/Ryujinx.UI.Common/Resources/Logo_Discord_Light.png similarity index 100% rename from src/Ryujinx.Ui.Common/Resources/Logo_Discord_Light.png rename to src/Ryujinx.UI.Common/Resources/Logo_Discord_Light.png diff --git a/src/Ryujinx.Ui.Common/Resources/Logo_GitHub_Dark.png b/src/Ryujinx.UI.Common/Resources/Logo_GitHub_Dark.png similarity index 100% rename from src/Ryujinx.Ui.Common/Resources/Logo_GitHub_Dark.png rename to src/Ryujinx.UI.Common/Resources/Logo_GitHub_Dark.png diff --git a/src/Ryujinx.Ui.Common/Resources/Logo_GitHub_Light.png b/src/Ryujinx.UI.Common/Resources/Logo_GitHub_Light.png similarity index 100% rename from src/Ryujinx.Ui.Common/Resources/Logo_GitHub_Light.png rename to src/Ryujinx.UI.Common/Resources/Logo_GitHub_Light.png diff --git a/src/Ryujinx.Ui.Common/Resources/Logo_Patreon_Dark.png b/src/Ryujinx.UI.Common/Resources/Logo_Patreon_Dark.png similarity index 100% rename from src/Ryujinx.Ui.Common/Resources/Logo_Patreon_Dark.png rename to src/Ryujinx.UI.Common/Resources/Logo_Patreon_Dark.png diff --git a/src/Ryujinx.Ui.Common/Resources/Logo_Patreon_Light.png b/src/Ryujinx.UI.Common/Resources/Logo_Patreon_Light.png similarity index 100% rename from src/Ryujinx.Ui.Common/Resources/Logo_Patreon_Light.png rename to src/Ryujinx.UI.Common/Resources/Logo_Patreon_Light.png diff --git a/src/Ryujinx.Ui.Common/Resources/Logo_Ryujinx.png b/src/Ryujinx.UI.Common/Resources/Logo_Ryujinx.png similarity index 100% rename from src/Ryujinx.Ui.Common/Resources/Logo_Ryujinx.png rename to src/Ryujinx.UI.Common/Resources/Logo_Ryujinx.png diff --git a/src/Ryujinx.Ui.Common/Resources/Logo_Twitter_Dark.png b/src/Ryujinx.UI.Common/Resources/Logo_Twitter_Dark.png similarity index 100% rename from src/Ryujinx.Ui.Common/Resources/Logo_Twitter_Dark.png rename to src/Ryujinx.UI.Common/Resources/Logo_Twitter_Dark.png diff --git a/src/Ryujinx.Ui.Common/Resources/Logo_Twitter_Light.png b/src/Ryujinx.UI.Common/Resources/Logo_Twitter_Light.png similarity index 100% rename from src/Ryujinx.Ui.Common/Resources/Logo_Twitter_Light.png rename to src/Ryujinx.UI.Common/Resources/Logo_Twitter_Light.png diff --git a/src/Ryujinx.Ui.Common/Ryujinx.Ui.Common.csproj b/src/Ryujinx.UI.Common/Ryujinx.UI.Common.csproj similarity index 100% rename from src/Ryujinx.Ui.Common/Ryujinx.Ui.Common.csproj rename to src/Ryujinx.UI.Common/Ryujinx.UI.Common.csproj diff --git a/src/Ryujinx.Ui.Common/SystemInfo/LinuxSystemInfo.cs b/src/Ryujinx.UI.Common/SystemInfo/LinuxSystemInfo.cs similarity index 98% rename from src/Ryujinx.Ui.Common/SystemInfo/LinuxSystemInfo.cs rename to src/Ryujinx.UI.Common/SystemInfo/LinuxSystemInfo.cs index 5f1ab5416..c7fe05a09 100644 --- a/src/Ryujinx.Ui.Common/SystemInfo/LinuxSystemInfo.cs +++ b/src/Ryujinx.UI.Common/SystemInfo/LinuxSystemInfo.cs @@ -5,7 +5,7 @@ using System.IO; using System.Runtime.Versioning; -namespace Ryujinx.Ui.Common.SystemInfo +namespace Ryujinx.UI.Common.SystemInfo { [SupportedOSPlatform("linux")] class LinuxSystemInfo : SystemInfo diff --git a/src/Ryujinx.Ui.Common/SystemInfo/MacOSSystemInfo.cs b/src/Ryujinx.UI.Common/SystemInfo/MacOSSystemInfo.cs similarity index 99% rename from src/Ryujinx.Ui.Common/SystemInfo/MacOSSystemInfo.cs rename to src/Ryujinx.UI.Common/SystemInfo/MacOSSystemInfo.cs index 3508ae3a4..36deaf35f 100644 --- a/src/Ryujinx.Ui.Common/SystemInfo/MacOSSystemInfo.cs +++ b/src/Ryujinx.UI.Common/SystemInfo/MacOSSystemInfo.cs @@ -5,7 +5,7 @@ using System.Runtime.Versioning; using System.Text; -namespace Ryujinx.Ui.Common.SystemInfo +namespace Ryujinx.UI.Common.SystemInfo { [SupportedOSPlatform("macos")] partial class MacOSSystemInfo : SystemInfo diff --git a/src/Ryujinx.Ui.Common/SystemInfo/SystemInfo.cs b/src/Ryujinx.UI.Common/SystemInfo/SystemInfo.cs similarity index 97% rename from src/Ryujinx.Ui.Common/SystemInfo/SystemInfo.cs rename to src/Ryujinx.UI.Common/SystemInfo/SystemInfo.cs index e78db8af7..38728b9cf 100644 --- a/src/Ryujinx.Ui.Common/SystemInfo/SystemInfo.cs +++ b/src/Ryujinx.UI.Common/SystemInfo/SystemInfo.cs @@ -1,11 +1,11 @@ using Ryujinx.Common.Logging; -using Ryujinx.Ui.Common.Helper; +using Ryujinx.UI.Common.Helper; using System; using System.Runtime.InteropServices; using System.Runtime.Intrinsics.X86; using System.Text; -namespace Ryujinx.Ui.Common.SystemInfo +namespace Ryujinx.UI.Common.SystemInfo { public class SystemInfo { diff --git a/src/Ryujinx.Ui.Common/SystemInfo/WindowsSystemInfo.cs b/src/Ryujinx.UI.Common/SystemInfo/WindowsSystemInfo.cs similarity index 98% rename from src/Ryujinx.Ui.Common/SystemInfo/WindowsSystemInfo.cs rename to src/Ryujinx.UI.Common/SystemInfo/WindowsSystemInfo.cs index 9bb0fbf74..bf49c2a66 100644 --- a/src/Ryujinx.Ui.Common/SystemInfo/WindowsSystemInfo.cs +++ b/src/Ryujinx.UI.Common/SystemInfo/WindowsSystemInfo.cs @@ -4,7 +4,7 @@ using System.Runtime.InteropServices; using System.Runtime.Versioning; -namespace Ryujinx.Ui.Common.SystemInfo +namespace Ryujinx.UI.Common.SystemInfo { [SupportedOSPlatform("windows")] partial class WindowsSystemInfo : SystemInfo diff --git a/src/Ryujinx.Ui.Common/UserError.cs b/src/Ryujinx.UI.Common/UserError.cs similarity index 96% rename from src/Ryujinx.Ui.Common/UserError.cs rename to src/Ryujinx.UI.Common/UserError.cs index 832aae9d6..706971efa 100644 --- a/src/Ryujinx.Ui.Common/UserError.cs +++ b/src/Ryujinx.UI.Common/UserError.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.Ui.Common +namespace Ryujinx.UI.Common { /// /// Represent a common error that could be reported to the user by the emulator. diff --git a/src/Ryujinx.Ui.LocaleGenerator/LocaleGenerator.cs b/src/Ryujinx.UI.LocaleGenerator/LocaleGenerator.cs similarity index 97% rename from src/Ryujinx.Ui.LocaleGenerator/LocaleGenerator.cs rename to src/Ryujinx.UI.LocaleGenerator/LocaleGenerator.cs index 27573a8fb..bb4918d55 100644 --- a/src/Ryujinx.Ui.LocaleGenerator/LocaleGenerator.cs +++ b/src/Ryujinx.UI.LocaleGenerator/LocaleGenerator.cs @@ -2,7 +2,7 @@ using System.Linq; using System.Text; -namespace Ryujinx.Ui.LocaleGenerator +namespace Ryujinx.UI.LocaleGenerator { [Generator] public class LocaleGenerator : IIncrementalGenerator diff --git a/src/Ryujinx.Ui.LocaleGenerator/Ryujinx.Ui.LocaleGenerator.csproj b/src/Ryujinx.UI.LocaleGenerator/Ryujinx.UI.LocaleGenerator.csproj similarity index 100% rename from src/Ryujinx.Ui.LocaleGenerator/Ryujinx.Ui.LocaleGenerator.csproj rename to src/Ryujinx.UI.LocaleGenerator/Ryujinx.UI.LocaleGenerator.csproj diff --git a/src/Ryujinx/Modules/Updater/UpdateDialog.cs b/src/Ryujinx/Modules/Updater/UpdateDialog.cs index 0057761bb..ec24cdc89 100644 --- a/src/Ryujinx/Modules/Updater/UpdateDialog.cs +++ b/src/Ryujinx/Modules/Updater/UpdateDialog.cs @@ -2,9 +2,9 @@ using Gtk; using Ryujinx.Common; using Ryujinx.Common.Configuration; -using Ryujinx.Ui; -using Ryujinx.Ui.Common.Configuration; -using Ryujinx.Ui.Common.Helper; +using Ryujinx.UI; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; using System; using System.Diagnostics; using System.Reflection; @@ -34,7 +34,7 @@ private UpdateDialog(Builder builder, MainWindow mainWindow, Version newVersion, _mainWindow = mainWindow; _buildUrl = buildUrl; - Icon = new Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png"); + Icon = new Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); MainText.Text = "Do you want to update Ryujinx to the latest version?"; SecondaryText.Text = $"{Program.Version} -> {newVersion}"; diff --git a/src/Ryujinx/Modules/Updater/Updater.cs b/src/Ryujinx/Modules/Updater/Updater.cs index 2fed43622..16fbc36ae 100644 --- a/src/Ryujinx/Modules/Updater/Updater.cs +++ b/src/Ryujinx/Modules/Updater/Updater.cs @@ -5,9 +5,9 @@ using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.Common.Utilities; -using Ryujinx.Ui; -using Ryujinx.Ui.Common.Models.Github; -using Ryujinx.Ui.Widgets; +using Ryujinx.UI; +using Ryujinx.UI.Common.Models.Github; +using Ryujinx.UI.Widgets; using System; using System.Collections.Generic; using System.IO; diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs index 3b5cd770b..1845c512e 100644 --- a/src/Ryujinx/Program.cs +++ b/src/Ryujinx/Program.cs @@ -6,12 +6,12 @@ using Ryujinx.Common.SystemInterop; using Ryujinx.Modules; using Ryujinx.SDL2.Common; -using Ryujinx.Ui; -using Ryujinx.Ui.Common; -using Ryujinx.Ui.Common.Configuration; -using Ryujinx.Ui.Common.Helper; -using Ryujinx.Ui.Common.SystemInfo; -using Ryujinx.Ui.Widgets; +using Ryujinx.UI; +using Ryujinx.UI.Common; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; +using Ryujinx.UI.Common.SystemInfo; +using Ryujinx.UI.Widgets; using SixLabors.ImageSharp.Formats.Jpeg; using System; using System.Collections.Generic; diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj index 830e0e6c1..68bf98981 100644 --- a/src/Ryujinx/Ryujinx.csproj +++ b/src/Ryujinx/Ryujinx.csproj @@ -45,7 +45,7 @@ - + @@ -80,24 +80,24 @@ - - - - - - - + + + + + + + - - - - - - - + + + + + + + diff --git a/src/Ryujinx/Ui/Applet/ErrorAppletDialog.cs b/src/Ryujinx/UI/Applet/ErrorAppletDialog.cs similarity index 85% rename from src/Ryujinx/Ui/Applet/ErrorAppletDialog.cs rename to src/Ryujinx/UI/Applet/ErrorAppletDialog.cs index c6bcf3f28..7f8cc0e95 100644 --- a/src/Ryujinx/Ui/Applet/ErrorAppletDialog.cs +++ b/src/Ryujinx/UI/Applet/ErrorAppletDialog.cs @@ -1,14 +1,14 @@ using Gtk; -using Ryujinx.Ui.Common.Configuration; +using Ryujinx.UI.Common.Configuration; using System.Reflection; -namespace Ryujinx.Ui.Applet +namespace Ryujinx.UI.Applet { internal class ErrorAppletDialog : MessageDialog { public ErrorAppletDialog(Window parentWindow, DialogFlags dialogFlags, MessageType messageType, string[] buttons) : base(parentWindow, dialogFlags, messageType, ButtonsType.None, null) { - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png"); + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); int responseId = 0; diff --git a/src/Ryujinx/Ui/Applet/GtkDynamicTextInputHandler.cs b/src/Ryujinx/UI/Applet/GtkDynamicTextInputHandler.cs similarity index 97% rename from src/Ryujinx/Ui/Applet/GtkDynamicTextInputHandler.cs rename to src/Ryujinx/UI/Applet/GtkDynamicTextInputHandler.cs index 3a3da4650..0e560b789 100644 --- a/src/Ryujinx/Ui/Applet/GtkDynamicTextInputHandler.cs +++ b/src/Ryujinx/UI/Applet/GtkDynamicTextInputHandler.cs @@ -1,10 +1,10 @@ using Gtk; -using Ryujinx.HLE.Ui; +using Ryujinx.HLE.UI; using Ryujinx.Input.GTK3; -using Ryujinx.Ui.Widgets; +using Ryujinx.UI.Widgets; using System.Threading; -namespace Ryujinx.Ui.Applet +namespace Ryujinx.UI.Applet { /// /// Class that forwards key events to a GTK Entry so they can be processed into text. diff --git a/src/Ryujinx/Ui/Applet/GtkHostUiHandler.cs b/src/Ryujinx/UI/Applet/GtkHostUIHandler.cs similarity index 93% rename from src/Ryujinx/Ui/Applet/GtkHostUiHandler.cs rename to src/Ryujinx/UI/Applet/GtkHostUIHandler.cs index 241e5e6cf..1d918d21b 100644 --- a/src/Ryujinx/Ui/Applet/GtkHostUiHandler.cs +++ b/src/Ryujinx/UI/Applet/GtkHostUIHandler.cs @@ -1,27 +1,27 @@ using Gtk; using Ryujinx.HLE.HOS.Applets; using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; -using Ryujinx.HLE.Ui; -using Ryujinx.Ui.Widgets; +using Ryujinx.HLE.UI; +using Ryujinx.UI.Widgets; using System; using System.Threading; -namespace Ryujinx.Ui.Applet +namespace Ryujinx.UI.Applet { - internal class GtkHostUiHandler : IHostUiHandler + internal class GtkHostUIHandler : IHostUIHandler { private readonly Window _parent; - public IHostUiTheme HostUiTheme { get; } + public IHostUITheme HostUITheme { get; } - public GtkHostUiHandler(Window parent) + public GtkHostUIHandler(Window parent) { _parent = parent; - HostUiTheme = new GtkHostUiTheme(parent); + HostUITheme = new GtkHostUITheme(parent); } - public bool DisplayMessageDialog(ControllerAppletUiArgs args) + public bool DisplayMessageDialog(ControllerAppletUIArgs args) { string playerCount = args.PlayerCountMin == args.PlayerCountMax ? $"exactly {args.PlayerCountMin}" : $"{args.PlayerCountMin}-{args.PlayerCountMax}"; @@ -81,7 +81,7 @@ public bool DisplayMessageDialog(string title, string message) return okPressed; } - public bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText) + public bool DisplayInputDialog(SoftwareKeyboardUIArgs args, out string userText) { ManualResetEvent dialogCloseEvent = new(false); diff --git a/src/Ryujinx/Ui/Applet/GtkHostUiTheme.cs b/src/Ryujinx/UI/Applet/GtkHostUITheme.cs similarity index 96% rename from src/Ryujinx/Ui/Applet/GtkHostUiTheme.cs rename to src/Ryujinx/UI/Applet/GtkHostUITheme.cs index 1bc010591..52d1123bb 100644 --- a/src/Ryujinx/Ui/Applet/GtkHostUiTheme.cs +++ b/src/Ryujinx/UI/Applet/GtkHostUITheme.cs @@ -1,10 +1,10 @@ using Gtk; -using Ryujinx.HLE.Ui; +using Ryujinx.HLE.UI; using System.Diagnostics; -namespace Ryujinx.Ui.Applet +namespace Ryujinx.UI.Applet { - internal class GtkHostUiTheme : IHostUiTheme + internal class GtkHostUITheme : IHostUITheme { private const int RenderSurfaceWidth = 32; private const int RenderSurfaceHeight = 32; @@ -17,7 +17,7 @@ internal class GtkHostUiTheme : IHostUiTheme public ThemeColor SelectionBackgroundColor { get; } public ThemeColor SelectionForegroundColor { get; } - public GtkHostUiTheme(Window parent) + public GtkHostUITheme(Window parent) { Entry entry = new(); entry.SetStateFlags(StateFlags.Selected, true); diff --git a/src/Ryujinx/Ui/Applet/SwkbdAppletDialog.cs b/src/Ryujinx/UI/Applet/SwkbdAppletDialog.cs similarity index 99% rename from src/Ryujinx/Ui/Applet/SwkbdAppletDialog.cs rename to src/Ryujinx/UI/Applet/SwkbdAppletDialog.cs index c1f3d77c1..8045da91e 100644 --- a/src/Ryujinx/Ui/Applet/SwkbdAppletDialog.cs +++ b/src/Ryujinx/UI/Applet/SwkbdAppletDialog.cs @@ -3,7 +3,7 @@ using System; using System.Linq; -namespace Ryujinx.Ui.Applet +namespace Ryujinx.UI.Applet { public class SwkbdAppletDialog : MessageDialog { diff --git a/src/Ryujinx/Ui/Helper/MetalHelper.cs b/src/Ryujinx/UI/Helper/MetalHelper.cs similarity index 99% rename from src/Ryujinx/Ui/Helper/MetalHelper.cs rename to src/Ryujinx/UI/Helper/MetalHelper.cs index a7af2aed2..c2c32d3ae 100644 --- a/src/Ryujinx/Ui/Helper/MetalHelper.cs +++ b/src/Ryujinx/UI/Helper/MetalHelper.cs @@ -3,7 +3,7 @@ using System.Runtime.InteropServices; using System.Runtime.Versioning; -namespace Ryujinx.Ui.Helper +namespace Ryujinx.UI.Helper { public delegate void UpdateBoundsCallbackDelegate(Window window); diff --git a/src/Ryujinx/Ui/Helper/SortHelper.cs b/src/Ryujinx/UI/Helper/SortHelper.cs similarity index 94% rename from src/Ryujinx/Ui/Helper/SortHelper.cs rename to src/Ryujinx/UI/Helper/SortHelper.cs index 4e625f922..3e3fbeaae 100644 --- a/src/Ryujinx/Ui/Helper/SortHelper.cs +++ b/src/Ryujinx/UI/Helper/SortHelper.cs @@ -1,8 +1,8 @@ using Gtk; -using Ryujinx.Ui.Common.Helper; +using Ryujinx.UI.Common.Helper; using System; -namespace Ryujinx.Ui.Helper +namespace Ryujinx.UI.Helper { static class SortHelper { diff --git a/src/Ryujinx/Ui/Helper/ThemeHelper.cs b/src/Ryujinx/UI/Helper/ThemeHelper.cs similarity index 66% rename from src/Ryujinx/Ui/Helper/ThemeHelper.cs rename to src/Ryujinx/UI/Helper/ThemeHelper.cs index 5cd9ad520..e1fed1c4d 100644 --- a/src/Ryujinx/Ui/Helper/ThemeHelper.cs +++ b/src/Ryujinx/UI/Helper/ThemeHelper.cs @@ -1,34 +1,34 @@ using Gtk; using Ryujinx.Common; using Ryujinx.Common.Logging; -using Ryujinx.Ui.Common.Configuration; +using Ryujinx.UI.Common.Configuration; using System.IO; -namespace Ryujinx.Ui.Helper +namespace Ryujinx.UI.Helper { static class ThemeHelper { public static void ApplyTheme() { - if (!ConfigurationState.Instance.Ui.EnableCustomTheme) + if (!ConfigurationState.Instance.UI.EnableCustomTheme) { return; } - if (File.Exists(ConfigurationState.Instance.Ui.CustomThemePath) && (Path.GetExtension(ConfigurationState.Instance.Ui.CustomThemePath) == ".css")) + if (File.Exists(ConfigurationState.Instance.UI.CustomThemePath) && (Path.GetExtension(ConfigurationState.Instance.UI.CustomThemePath) == ".css")) { CssProvider cssProvider = new(); - cssProvider.LoadFromPath(ConfigurationState.Instance.Ui.CustomThemePath); + cssProvider.LoadFromPath(ConfigurationState.Instance.UI.CustomThemePath); StyleContext.AddProviderForScreen(Gdk.Screen.Default, cssProvider, 800); } else { - Logger.Warning?.Print(LogClass.Application, $"The \"custom_theme_path\" section in \"{ReleaseInformation.ConfigName}\" contains an invalid path: \"{ConfigurationState.Instance.Ui.CustomThemePath}\"."); + Logger.Warning?.Print(LogClass.Application, $"The \"custom_theme_path\" section in \"{ReleaseInformation.ConfigName}\" contains an invalid path: \"{ConfigurationState.Instance.UI.CustomThemePath}\"."); - ConfigurationState.Instance.Ui.CustomThemePath.Value = ""; - ConfigurationState.Instance.Ui.EnableCustomTheme.Value = false; + ConfigurationState.Instance.UI.CustomThemePath.Value = ""; + ConfigurationState.Instance.UI.EnableCustomTheme.Value = false; ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); } } diff --git a/src/Ryujinx/Ui/MainWindow.cs b/src/Ryujinx/UI/MainWindow.cs similarity index 93% rename from src/Ryujinx/Ui/MainWindow.cs rename to src/Ryujinx/UI/MainWindow.cs index 8bfe09cf1..2908f1a8d 100644 --- a/src/Ryujinx/Ui/MainWindow.cs +++ b/src/Ryujinx/UI/MainWindow.cs @@ -26,14 +26,14 @@ using Ryujinx.Input.HLE; using Ryujinx.Input.SDL2; using Ryujinx.Modules; -using Ryujinx.Ui.App.Common; -using Ryujinx.Ui.Applet; -using Ryujinx.Ui.Common; -using Ryujinx.Ui.Common.Configuration; -using Ryujinx.Ui.Common.Helper; -using Ryujinx.Ui.Helper; -using Ryujinx.Ui.Widgets; -using Ryujinx.Ui.Windows; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Applet; +using Ryujinx.UI.Common; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; +using Ryujinx.UI.Helper; +using Ryujinx.UI.Widgets; +using Ryujinx.UI.Windows; using Silk.NET.Vulkan; using SPB.Graphics.Vulkan; using System; @@ -45,7 +45,7 @@ using GUI = Gtk.Builder.ObjectAttribute; using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState; -namespace Ryujinx.Ui +namespace Ryujinx.UI { public class MainWindow : Window { @@ -61,7 +61,7 @@ public class MainWindow : Window private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution; private readonly ApplicationLibrary _applicationLibrary; - private readonly GtkHostUiHandler _uiHandler; + private readonly GtkHostUIHandler _uiHandler; private readonly AutoResetEvent _deviceExitStatus; private readonly ListStore _tableStore; @@ -99,7 +99,7 @@ public class MainWindow : Window [GUI] MenuItem _simulateWakeUpMessage; [GUI] MenuItem _scanAmiibo; [GUI] MenuItem _takeScreenshot; - [GUI] MenuItem _hideUi; + [GUI] MenuItem _hideUI; [GUI] MenuItem _fullScreen; [GUI] CheckMenuItem _startFullScreen; [GUI] CheckMenuItem _showConsole; @@ -143,7 +143,7 @@ public class MainWindow : Window #pragma warning restore CS0649, IDE0044, CS0169, IDE0051 - public MainWindow() : this(new Builder("Ryujinx.Ui.MainWindow.glade")) { } + public MainWindow() : this(new Builder("Ryujinx.UI.MainWindow.glade")) { } private MainWindow(Builder builder) : base(builder.GetRawOwnedObject("_mainWin")) { @@ -154,7 +154,7 @@ private MainWindow(Builder builder) : base(builder.GetRawOwnedObject("_mainWin") SetWindowSizePosition(); - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png"); + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); Title = $"Ryujinx {Program.Version}"; // Hide emulation context status bar. @@ -182,7 +182,7 @@ private MainWindow(Builder builder) : base(builder.GetRawOwnedObject("_mainWin") // Instantiate GUI objects. _applicationLibrary = new ApplicationLibrary(_virtualFileSystem); - _uiHandler = new GtkHostUiHandler(this); + _uiHandler = new GtkHostUIHandler(this); _deviceExitStatus = new AutoResetEvent(false); WindowStateEvent += WindowStateEvent_Changed; @@ -210,24 +210,24 @@ private MainWindow(Builder builder) : base(builder.GetRawOwnedObject("_mainWin") ConfigurationState.Instance.Multiplayer.Mode.Event += UpdateMultiplayerMode; ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateMultiplayerLanInterfaceId; - if (ConfigurationState.Instance.Ui.StartFullscreen) + if (ConfigurationState.Instance.UI.StartFullscreen) { _startFullScreen.Active = true; } - _showConsole.Active = ConfigurationState.Instance.Ui.ShowConsole.Value; + _showConsole.Active = ConfigurationState.Instance.UI.ShowConsole.Value; _showConsole.Visible = ConsoleHelper.SetConsoleWindowStateSupported; _actionMenu.Sensitive = false; _pauseEmulation.Sensitive = false; _resumeEmulation.Sensitive = false; - _nspShown.Active = ConfigurationState.Instance.Ui.ShownFileTypes.NSP.Value; - _pfs0Shown.Active = ConfigurationState.Instance.Ui.ShownFileTypes.PFS0.Value; - _xciShown.Active = ConfigurationState.Instance.Ui.ShownFileTypes.XCI.Value; - _ncaShown.Active = ConfigurationState.Instance.Ui.ShownFileTypes.NCA.Value; - _nroShown.Active = ConfigurationState.Instance.Ui.ShownFileTypes.NRO.Value; - _nsoShown.Active = ConfigurationState.Instance.Ui.ShownFileTypes.NSO.Value; + _nspShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value; + _pfs0Shown.Active = ConfigurationState.Instance.UI.ShownFileTypes.PFS0.Value; + _xciShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value; + _ncaShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.NCA.Value; + _nroShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.NRO.Value; + _nsoShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.NSO.Value; _nspShown.Toggled += NSP_Shown_Toggled; _pfs0Shown.Toggled += PFS0_Shown_Toggled; @@ -238,43 +238,43 @@ private MainWindow(Builder builder) : base(builder.GetRawOwnedObject("_mainWin") _fileTypesSubMenu.Visible = FileAssociationHelper.IsTypeAssociationSupported; - if (ConfigurationState.Instance.Ui.GuiColumns.FavColumn) + if (ConfigurationState.Instance.UI.GuiColumns.FavColumn) { _favToggle.Active = true; } - if (ConfigurationState.Instance.Ui.GuiColumns.IconColumn) + if (ConfigurationState.Instance.UI.GuiColumns.IconColumn) { _iconToggle.Active = true; } - if (ConfigurationState.Instance.Ui.GuiColumns.AppColumn) + if (ConfigurationState.Instance.UI.GuiColumns.AppColumn) { _appToggle.Active = true; } - if (ConfigurationState.Instance.Ui.GuiColumns.DevColumn) + if (ConfigurationState.Instance.UI.GuiColumns.DevColumn) { _developerToggle.Active = true; } - if (ConfigurationState.Instance.Ui.GuiColumns.VersionColumn) + if (ConfigurationState.Instance.UI.GuiColumns.VersionColumn) { _versionToggle.Active = true; } - if (ConfigurationState.Instance.Ui.GuiColumns.TimePlayedColumn) + if (ConfigurationState.Instance.UI.GuiColumns.TimePlayedColumn) { _timePlayedToggle.Active = true; } - if (ConfigurationState.Instance.Ui.GuiColumns.LastPlayedColumn) + if (ConfigurationState.Instance.UI.GuiColumns.LastPlayedColumn) { _lastPlayedToggle.Active = true; } - if (ConfigurationState.Instance.Ui.GuiColumns.FileExtColumn) + if (ConfigurationState.Instance.UI.GuiColumns.FileExtColumn) { _fileExtToggle.Active = true; } - if (ConfigurationState.Instance.Ui.GuiColumns.FileSizeColumn) + if (ConfigurationState.Instance.UI.GuiColumns.FileSizeColumn) { _fileSizeToggle.Active = true; } - if (ConfigurationState.Instance.Ui.GuiColumns.PathColumn) + if (ConfigurationState.Instance.UI.GuiColumns.PathColumn) { _pathToggle.Active = true; } @@ -307,8 +307,8 @@ private MainWindow(Builder builder) : base(builder.GetRawOwnedObject("_mainWin") _tableStore.SetSortFunc(6, SortHelper.LastPlayedSort); _tableStore.SetSortFunc(8, SortHelper.FileSizeSort); - int columnId = ConfigurationState.Instance.Ui.ColumnSort.SortColumnId; - bool ascending = ConfigurationState.Instance.Ui.ColumnSort.SortAscending; + int columnId = ConfigurationState.Instance.UI.ColumnSort.SortColumnId; + bool ascending = ConfigurationState.Instance.UI.ColumnSort.SortAscending; _tableStore.SetSortColumnId(columnId, ascending ? SortType.Ascending : SortType.Descending); @@ -316,12 +316,12 @@ private MainWindow(Builder builder) : base(builder.GetRawOwnedObject("_mainWin") _gameTable.SearchColumn = 2; _gameTable.SearchEqualFunc = (model, col, key, iter) => !((string)model.GetValue(iter, col)).Contains(key, StringComparison.InvariantCultureIgnoreCase); - _hideUi.Label = _hideUi.Label.Replace("SHOWUIKEY", ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUi.ToString()); + _hideUI.Label = _hideUI.Label.Replace("SHOWUIKEY", ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI.ToString()); UpdateColumns(); UpdateGameTable(); - ConfigurationState.Instance.Ui.GameDirs.Event += (sender, args) => + ConfigurationState.Instance.UI.GameDirs.Event += (sender, args) => { if (args.OldValue != args.NewValue) { @@ -401,43 +401,43 @@ private void UpdateColumns() CellRendererToggle favToggle = new(); favToggle.Toggled += FavToggle_Toggled; - if (ConfigurationState.Instance.Ui.GuiColumns.FavColumn) + if (ConfigurationState.Instance.UI.GuiColumns.FavColumn) { _gameTable.AppendColumn("Fav", favToggle, "active", 0); } - if (ConfigurationState.Instance.Ui.GuiColumns.IconColumn) + if (ConfigurationState.Instance.UI.GuiColumns.IconColumn) { _gameTable.AppendColumn("Icon", new CellRendererPixbuf(), "pixbuf", 1); } - if (ConfigurationState.Instance.Ui.GuiColumns.AppColumn) + if (ConfigurationState.Instance.UI.GuiColumns.AppColumn) { _gameTable.AppendColumn("Application", new CellRendererText(), "text", 2); } - if (ConfigurationState.Instance.Ui.GuiColumns.DevColumn) + if (ConfigurationState.Instance.UI.GuiColumns.DevColumn) { _gameTable.AppendColumn("Developer", new CellRendererText(), "text", 3); } - if (ConfigurationState.Instance.Ui.GuiColumns.VersionColumn) + if (ConfigurationState.Instance.UI.GuiColumns.VersionColumn) { _gameTable.AppendColumn("Version", new CellRendererText(), "text", 4); } - if (ConfigurationState.Instance.Ui.GuiColumns.TimePlayedColumn) + if (ConfigurationState.Instance.UI.GuiColumns.TimePlayedColumn) { _gameTable.AppendColumn("Time Played", new CellRendererText(), "text", 5); } - if (ConfigurationState.Instance.Ui.GuiColumns.LastPlayedColumn) + if (ConfigurationState.Instance.UI.GuiColumns.LastPlayedColumn) { _gameTable.AppendColumn("Last Played", new CellRendererText(), "text", 6); } - if (ConfigurationState.Instance.Ui.GuiColumns.FileExtColumn) + if (ConfigurationState.Instance.UI.GuiColumns.FileExtColumn) { _gameTable.AppendColumn("File Ext", new CellRendererText(), "text", 7); } - if (ConfigurationState.Instance.Ui.GuiColumns.FileSizeColumn) + if (ConfigurationState.Instance.UI.GuiColumns.FileSizeColumn) { _gameTable.AppendColumn("File Size", new CellRendererText(), "text", 8); } - if (ConfigurationState.Instance.Ui.GuiColumns.PathColumn) + if (ConfigurationState.Instance.UI.GuiColumns.PathColumn) { _gameTable.AppendColumn("Path", new CellRendererText(), "text", 9); } @@ -679,7 +679,7 @@ private SurfaceKHR CreateVulkanSurface(Instance instance, Vk vk) return new SurfaceKHR((ulong)((VulkanRenderer)RendererWidget).CreateWindowSurface(instance.Handle)); } - private void SetupProgressUiHandlers() + private void SetupProgressUIHandlers() { if (_emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null) { @@ -732,7 +732,7 @@ public void UpdateGameTable() 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; }) @@ -924,7 +924,7 @@ public void RunApplication(string path, bool startFullscreen = false) return; } - SetupProgressUiHandlers(); + SetupProgressUIHandlers(); _currentEmulatedGamePath = path; @@ -981,7 +981,7 @@ private void SwitchToRenderWidget(bool startFullscreen = false) { ToggleExtraWidgets(false); } - else if (startFullscreen || ConfigurationState.Instance.Ui.StartFullscreen.Value) + else if (startFullscreen || ConfigurationState.Instance.UI.StartFullscreen.Value) { FullScreen_Toggled(null, null); } @@ -1243,8 +1243,8 @@ private void Column_Clicked(object sender, EventArgs args) { TreeViewColumn column = (TreeViewColumn)sender; - ConfigurationState.Instance.Ui.ColumnSort.SortColumnId.Value = column.SortColumnId; - ConfigurationState.Instance.Ui.ColumnSort.SortAscending.Value = column.SortOrder == SortType.Ascending; + ConfigurationState.Instance.UI.ColumnSort.SortColumnId.Value = column.SortColumnId; + ConfigurationState.Instance.UI.ColumnSort.SortAscending.Value = column.SortOrder == SortType.Ascending; SaveConfig(); } @@ -1407,12 +1407,12 @@ private void Window_Close(object sender, DeleteEventArgs args) private void SetWindowSizePosition() { - DefaultWidth = ConfigurationState.Instance.Ui.WindowStartup.WindowSizeWidth; - DefaultHeight = ConfigurationState.Instance.Ui.WindowStartup.WindowSizeHeight; + DefaultWidth = ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth; + DefaultHeight = ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight; - Move(ConfigurationState.Instance.Ui.WindowStartup.WindowPositionX, ConfigurationState.Instance.Ui.WindowStartup.WindowPositionY); + Move(ConfigurationState.Instance.UI.WindowStartup.WindowPositionX, ConfigurationState.Instance.UI.WindowStartup.WindowPositionY); - if (ConfigurationState.Instance.Ui.WindowStartup.WindowMaximized) + if (ConfigurationState.Instance.UI.WindowStartup.WindowMaximized) { Maximize(); } @@ -1423,11 +1423,11 @@ private void SaveWindowSizePosition() GetSize(out int windowWidth, out int windowHeight); GetPosition(out int windowXPos, out int windowYPos); - ConfigurationState.Instance.Ui.WindowStartup.WindowMaximized.Value = IsMaximized; - ConfigurationState.Instance.Ui.WindowStartup.WindowSizeWidth.Value = windowWidth; - ConfigurationState.Instance.Ui.WindowStartup.WindowSizeHeight.Value = windowHeight; - ConfigurationState.Instance.Ui.WindowStartup.WindowPositionX.Value = windowXPos; - ConfigurationState.Instance.Ui.WindowStartup.WindowPositionY.Value = windowYPos; + ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value = IsMaximized; + ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth.Value = windowWidth; + ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight.Value = windowHeight; + ConfigurationState.Instance.UI.WindowStartup.WindowPositionX.Value = windowXPos; + ConfigurationState.Instance.UI.WindowStartup.WindowPositionY.Value = windowYPos; SaveConfig(); } @@ -1677,14 +1677,14 @@ private void FullScreen_Toggled(object sender, EventArgs args) private void StartFullScreen_Toggled(object sender, EventArgs args) { - ConfigurationState.Instance.Ui.StartFullscreen.Value = _startFullScreen.Active; + ConfigurationState.Instance.UI.StartFullscreen.Value = _startFullScreen.Active; SaveConfig(); } private void ShowConsole_Toggled(object sender, EventArgs args) { - ConfigurationState.Instance.Ui.ShowConsole.Value = _showConsole.Active; + ConfigurationState.Instance.UI.ShowConsole.Value = _showConsole.Active; SaveConfig(); } @@ -1702,7 +1702,7 @@ private void Settings_Pressed(object sender, EventArgs args) settingsWindow.Show(); } - private void HideUi_Pressed(object sender, EventArgs args) + private void HideUI_Pressed(object sender, EventArgs args) { ToggleExtraWidgets(false); } @@ -1807,7 +1807,7 @@ private void About_Pressed(object sender, EventArgs args) private void Fav_Toggled(object sender, EventArgs args) { - ConfigurationState.Instance.Ui.GuiColumns.FavColumn.Value = _favToggle.Active; + ConfigurationState.Instance.UI.GuiColumns.FavColumn.Value = _favToggle.Active; SaveConfig(); UpdateColumns(); @@ -1815,7 +1815,7 @@ private void Fav_Toggled(object sender, EventArgs args) private void Icon_Toggled(object sender, EventArgs args) { - ConfigurationState.Instance.Ui.GuiColumns.IconColumn.Value = _iconToggle.Active; + ConfigurationState.Instance.UI.GuiColumns.IconColumn.Value = _iconToggle.Active; SaveConfig(); UpdateColumns(); @@ -1823,7 +1823,7 @@ private void Icon_Toggled(object sender, EventArgs args) private void App_Toggled(object sender, EventArgs args) { - ConfigurationState.Instance.Ui.GuiColumns.AppColumn.Value = _appToggle.Active; + ConfigurationState.Instance.UI.GuiColumns.AppColumn.Value = _appToggle.Active; SaveConfig(); UpdateColumns(); @@ -1831,7 +1831,7 @@ private void App_Toggled(object sender, EventArgs args) private void Developer_Toggled(object sender, EventArgs args) { - ConfigurationState.Instance.Ui.GuiColumns.DevColumn.Value = _developerToggle.Active; + ConfigurationState.Instance.UI.GuiColumns.DevColumn.Value = _developerToggle.Active; SaveConfig(); UpdateColumns(); @@ -1839,7 +1839,7 @@ private void Developer_Toggled(object sender, EventArgs args) private void Version_Toggled(object sender, EventArgs args) { - ConfigurationState.Instance.Ui.GuiColumns.VersionColumn.Value = _versionToggle.Active; + ConfigurationState.Instance.UI.GuiColumns.VersionColumn.Value = _versionToggle.Active; SaveConfig(); UpdateColumns(); @@ -1847,7 +1847,7 @@ private void Version_Toggled(object sender, EventArgs args) private void TimePlayed_Toggled(object sender, EventArgs args) { - ConfigurationState.Instance.Ui.GuiColumns.TimePlayedColumn.Value = _timePlayedToggle.Active; + ConfigurationState.Instance.UI.GuiColumns.TimePlayedColumn.Value = _timePlayedToggle.Active; SaveConfig(); UpdateColumns(); @@ -1855,7 +1855,7 @@ private void TimePlayed_Toggled(object sender, EventArgs args) private void LastPlayed_Toggled(object sender, EventArgs args) { - ConfigurationState.Instance.Ui.GuiColumns.LastPlayedColumn.Value = _lastPlayedToggle.Active; + ConfigurationState.Instance.UI.GuiColumns.LastPlayedColumn.Value = _lastPlayedToggle.Active; SaveConfig(); UpdateColumns(); @@ -1863,7 +1863,7 @@ private void LastPlayed_Toggled(object sender, EventArgs args) private void FileExt_Toggled(object sender, EventArgs args) { - ConfigurationState.Instance.Ui.GuiColumns.FileExtColumn.Value = _fileExtToggle.Active; + ConfigurationState.Instance.UI.GuiColumns.FileExtColumn.Value = _fileExtToggle.Active; SaveConfig(); UpdateColumns(); @@ -1871,7 +1871,7 @@ private void FileExt_Toggled(object sender, EventArgs args) private void FileSize_Toggled(object sender, EventArgs args) { - ConfigurationState.Instance.Ui.GuiColumns.FileSizeColumn.Value = _fileSizeToggle.Active; + ConfigurationState.Instance.UI.GuiColumns.FileSizeColumn.Value = _fileSizeToggle.Active; SaveConfig(); UpdateColumns(); @@ -1879,7 +1879,7 @@ private void FileSize_Toggled(object sender, EventArgs args) private void Path_Toggled(object sender, EventArgs args) { - ConfigurationState.Instance.Ui.GuiColumns.PathColumn.Value = _pathToggle.Active; + ConfigurationState.Instance.UI.GuiColumns.PathColumn.Value = _pathToggle.Active; SaveConfig(); UpdateColumns(); @@ -1887,7 +1887,7 @@ private void Path_Toggled(object sender, EventArgs args) private void NSP_Shown_Toggled(object sender, EventArgs args) { - ConfigurationState.Instance.Ui.ShownFileTypes.NSP.Value = _nspShown.Active; + ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value = _nspShown.Active; SaveConfig(); UpdateGameTable(); @@ -1895,7 +1895,7 @@ private void NSP_Shown_Toggled(object sender, EventArgs args) private void PFS0_Shown_Toggled(object sender, EventArgs args) { - ConfigurationState.Instance.Ui.ShownFileTypes.PFS0.Value = _pfs0Shown.Active; + ConfigurationState.Instance.UI.ShownFileTypes.PFS0.Value = _pfs0Shown.Active; SaveConfig(); UpdateGameTable(); @@ -1903,7 +1903,7 @@ private void PFS0_Shown_Toggled(object sender, EventArgs args) private void XCI_Shown_Toggled(object sender, EventArgs args) { - ConfigurationState.Instance.Ui.ShownFileTypes.XCI.Value = _xciShown.Active; + ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value = _xciShown.Active; SaveConfig(); UpdateGameTable(); @@ -1911,7 +1911,7 @@ private void XCI_Shown_Toggled(object sender, EventArgs args) private void NCA_Shown_Toggled(object sender, EventArgs args) { - ConfigurationState.Instance.Ui.ShownFileTypes.NCA.Value = _ncaShown.Active; + ConfigurationState.Instance.UI.ShownFileTypes.NCA.Value = _ncaShown.Active; SaveConfig(); UpdateGameTable(); @@ -1919,7 +1919,7 @@ private void NCA_Shown_Toggled(object sender, EventArgs args) private void NRO_Shown_Toggled(object sender, EventArgs args) { - ConfigurationState.Instance.Ui.ShownFileTypes.NRO.Value = _nroShown.Active; + ConfigurationState.Instance.UI.ShownFileTypes.NRO.Value = _nroShown.Active; SaveConfig(); UpdateGameTable(); @@ -1927,7 +1927,7 @@ private void NRO_Shown_Toggled(object sender, EventArgs args) private void NSO_Shown_Toggled(object sender, EventArgs args) { - ConfigurationState.Instance.Ui.ShownFileTypes.NSO.Value = _nsoShown.Active; + ConfigurationState.Instance.UI.ShownFileTypes.NSO.Value = _nsoShown.Active; SaveConfig(); UpdateGameTable(); diff --git a/src/Ryujinx/Ui/MainWindow.glade b/src/Ryujinx/UI/MainWindow.glade similarity index 99% rename from src/Ryujinx/Ui/MainWindow.glade rename to src/Ryujinx/UI/MainWindow.glade index 58d5d9558..d1b6872a9 100644 --- a/src/Ryujinx/Ui/MainWindow.glade +++ b/src/Ryujinx/UI/MainWindow.glade @@ -437,12 +437,12 @@ - + True False Hide UI (SHOWUIKEY to show) True - + diff --git a/src/Ryujinx/Ui/OpenGLRenderer.cs b/src/Ryujinx/UI/OpenGLRenderer.cs similarity index 96% rename from src/Ryujinx/Ui/OpenGLRenderer.cs rename to src/Ryujinx/UI/OpenGLRenderer.cs index d10445b00..1fdabc754 100644 --- a/src/Ryujinx/Ui/OpenGLRenderer.cs +++ b/src/Ryujinx/UI/OpenGLRenderer.cs @@ -12,7 +12,7 @@ using System; using System.Runtime.InteropServices; -namespace Ryujinx.Ui +namespace Ryujinx.UI { public partial class OpenGLRenderer : RendererWidgetBase { @@ -120,7 +120,7 @@ protected override void Dispose(bool disposing) } catch (ContextException e) { - Logger.Warning?.Print(LogClass.Ui, $"Failed to bind OpenGL context: {e}"); + Logger.Warning?.Print(LogClass.UI, $"Failed to bind OpenGL context: {e}"); } Device?.DisposeGpu(); @@ -133,7 +133,7 @@ protected override void Dispose(bool disposing) } catch (ContextException e) { - Logger.Warning?.Print(LogClass.Ui, $"Failed to unbind OpenGL context: {e}"); + Logger.Warning?.Print(LogClass.UI, $"Failed to unbind OpenGL context: {e}"); } _openGLContext?.Dispose(); diff --git a/src/Ryujinx/Ui/OpenToolkitBindingsContext.cs b/src/Ryujinx/UI/OpenToolkitBindingsContext.cs similarity index 95% rename from src/Ryujinx/Ui/OpenToolkitBindingsContext.cs rename to src/Ryujinx/UI/OpenToolkitBindingsContext.cs index 49dd5da8e..1224ccfe0 100644 --- a/src/Ryujinx/Ui/OpenToolkitBindingsContext.cs +++ b/src/Ryujinx/UI/OpenToolkitBindingsContext.cs @@ -1,7 +1,7 @@ using SPB.Graphics; using System; -namespace Ryujinx.Ui +namespace Ryujinx.UI { public class OpenToolkitBindingsContext : OpenTK.IBindingsContext { diff --git a/src/Ryujinx/Ui/RendererWidgetBase.cs b/src/Ryujinx/UI/RendererWidgetBase.cs similarity index 99% rename from src/Ryujinx/Ui/RendererWidgetBase.cs rename to src/Ryujinx/UI/RendererWidgetBase.cs index 7794e0448..e27d06044 100644 --- a/src/Ryujinx/Ui/RendererWidgetBase.cs +++ b/src/Ryujinx/UI/RendererWidgetBase.cs @@ -9,9 +9,9 @@ using Ryujinx.Input; using Ryujinx.Input.GTK3; using Ryujinx.Input.HLE; -using Ryujinx.Ui.Common.Configuration; -using Ryujinx.Ui.Common.Helper; -using Ryujinx.Ui.Widgets; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; +using Ryujinx.UI.Widgets; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; @@ -26,7 +26,7 @@ using ScalingFilter = Ryujinx.Graphics.GAL.ScalingFilter; using Switch = Ryujinx.HLE.Switch; -namespace Ryujinx.Ui +namespace Ryujinx.UI { public abstract class RendererWidgetBase : DrawingArea { @@ -659,8 +659,8 @@ private bool UpdateFrame() Renderer.Screenshot(); } - if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ShowUi) && - !_prevHotkeyState.HasFlag(KeyboardHotkeyState.ShowUi)) + if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ShowUI) && + !_prevHotkeyState.HasFlag(KeyboardHotkeyState.ShowUI)) { (Toplevel as MainWindow).ToggleExtraWidgets(true); } @@ -739,7 +739,7 @@ private enum KeyboardHotkeyState None = 0, ToggleVSync = 1 << 0, Screenshot = 1 << 1, - ShowUi = 1 << 2, + ShowUI = 1 << 2, Pause = 1 << 3, ToggleMute = 1 << 4, ResScaleUp = 1 << 5, @@ -762,9 +762,9 @@ private KeyboardHotkeyState GetHotkeyState() state |= KeyboardHotkeyState.Screenshot; } - if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUi)) + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI)) { - state |= KeyboardHotkeyState.ShowUi; + state |= KeyboardHotkeyState.ShowUI; } if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause)) diff --git a/src/Ryujinx/Ui/SPBOpenGLContext.cs b/src/Ryujinx/UI/SPBOpenGLContext.cs similarity index 98% rename from src/Ryujinx/Ui/SPBOpenGLContext.cs rename to src/Ryujinx/UI/SPBOpenGLContext.cs index 0c5e57360..97feb4345 100644 --- a/src/Ryujinx/Ui/SPBOpenGLContext.cs +++ b/src/Ryujinx/UI/SPBOpenGLContext.cs @@ -5,7 +5,7 @@ using SPB.Platform; using SPB.Windowing; -namespace Ryujinx.Ui +namespace Ryujinx.UI { class SPBOpenGLContext : IOpenGLContext { diff --git a/src/Ryujinx/Ui/StatusUpdatedEventArgs.cs b/src/Ryujinx/UI/StatusUpdatedEventArgs.cs similarity index 97% rename from src/Ryujinx/Ui/StatusUpdatedEventArgs.cs rename to src/Ryujinx/UI/StatusUpdatedEventArgs.cs index 72e7d7f5b..db467ebfc 100644 --- a/src/Ryujinx/Ui/StatusUpdatedEventArgs.cs +++ b/src/Ryujinx/UI/StatusUpdatedEventArgs.cs @@ -1,6 +1,6 @@ using System; -namespace Ryujinx.Ui +namespace Ryujinx.UI { public class StatusUpdatedEventArgs : EventArgs { diff --git a/src/Ryujinx/Ui/VulkanRenderer.cs b/src/Ryujinx/UI/VulkanRenderer.cs similarity index 98% rename from src/Ryujinx/Ui/VulkanRenderer.cs rename to src/Ryujinx/UI/VulkanRenderer.cs index e1aae0965..eefc56999 100644 --- a/src/Ryujinx/Ui/VulkanRenderer.cs +++ b/src/Ryujinx/UI/VulkanRenderer.cs @@ -1,7 +1,7 @@ using Gdk; using Ryujinx.Common.Configuration; using Ryujinx.Input.HLE; -using Ryujinx.Ui.Helper; +using Ryujinx.UI.Helper; using SPB.Graphics.Vulkan; using SPB.Platform.Metal; using SPB.Platform.Win32; @@ -10,7 +10,7 @@ using System; using System.Runtime.InteropServices; -namespace Ryujinx.Ui +namespace Ryujinx.UI { public partial class VulkanRenderer : RendererWidgetBase { diff --git a/src/Ryujinx/Ui/Widgets/GameTableContextMenu.Designer.cs b/src/Ryujinx/UI/Widgets/GameTableContextMenu.Designer.cs similarity index 99% rename from src/Ryujinx/Ui/Widgets/GameTableContextMenu.Designer.cs rename to src/Ryujinx/UI/Widgets/GameTableContextMenu.Designer.cs index 162c172d9..8ee1cd2f3 100644 --- a/src/Ryujinx/Ui/Widgets/GameTableContextMenu.Designer.cs +++ b/src/Ryujinx/UI/Widgets/GameTableContextMenu.Designer.cs @@ -1,7 +1,7 @@ using Gtk; using System; -namespace Ryujinx.Ui.Widgets +namespace Ryujinx.UI.Widgets { public partial class GameTableContextMenu : Menu { diff --git a/src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs b/src/Ryujinx/UI/Widgets/GameTableContextMenu.cs similarity index 98% rename from src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs rename to src/Ryujinx/UI/Widgets/GameTableContextMenu.cs index eb9f52d73..dc0dd4c50 100644 --- a/src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs +++ b/src/Ryujinx/UI/Widgets/GameTableContextMenu.cs @@ -16,10 +16,10 @@ using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS.Services.Account.Acc; -using Ryujinx.Ui.App.Common; -using Ryujinx.Ui.Common.Configuration; -using Ryujinx.Ui.Common.Helper; -using Ryujinx.Ui.Windows; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; +using Ryujinx.UI.Windows; using System; using System.Buffers; using System.Collections.Generic; @@ -28,7 +28,7 @@ using System.Reflection; using System.Threading; -namespace Ryujinx.Ui.Widgets +namespace Ryujinx.UI.Widgets { public partial class GameTableContextMenu : Menu { @@ -189,7 +189,7 @@ private void ExtractSection(NcaSectionType ncaSectionType, int programIndex = 0) _dialog = new MessageDialog(null, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Cancel, null) { Title = "Ryujinx - NCA Section Extractor", - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png"), + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"), SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(_titleFilePath)}...", WindowPosition = WindowPosition.Center, }; @@ -320,7 +320,7 @@ private void ExtractSection(NcaSectionType ncaSectionType, int programIndex = 0) MessageDialog dialog = new(null, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Ok, null) { Title = "Ryujinx - NCA Section Extractor", - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png"), + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"), SecondaryText = "Extraction completed successfully.", WindowPosition = WindowPosition.Center, }; diff --git a/src/Ryujinx/Ui/Widgets/GtkDialog.cs b/src/Ryujinx/UI/Widgets/GtkDialog.cs similarity index 96% rename from src/Ryujinx/Ui/Widgets/GtkDialog.cs rename to src/Ryujinx/UI/Widgets/GtkDialog.cs index 51e777fa1..567f9ad67 100644 --- a/src/Ryujinx/Ui/Widgets/GtkDialog.cs +++ b/src/Ryujinx/UI/Widgets/GtkDialog.cs @@ -1,10 +1,10 @@ using Gtk; using Ryujinx.Common.Logging; -using Ryujinx.Ui.Common.Configuration; +using Ryujinx.UI.Common.Configuration; using System.Collections.Generic; using System.Reflection; -namespace Ryujinx.Ui.Widgets +namespace Ryujinx.UI.Widgets { internal class GtkDialog : MessageDialog { @@ -14,7 +14,7 @@ private GtkDialog(string title, string mainText, string secondaryText, MessageTy : base(null, DialogFlags.Modal, messageType, buttonsType, null) { Title = title; - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png"); + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); Text = mainText; SecondaryText = secondaryText; WindowPosition = WindowPosition.Center; diff --git a/src/Ryujinx/Ui/Widgets/GtkInputDialog.cs b/src/Ryujinx/UI/Widgets/GtkInputDialog.cs similarity index 97% rename from src/Ryujinx/Ui/Widgets/GtkInputDialog.cs rename to src/Ryujinx/UI/Widgets/GtkInputDialog.cs index 622980921..fd85248b7 100644 --- a/src/Ryujinx/Ui/Widgets/GtkInputDialog.cs +++ b/src/Ryujinx/UI/Widgets/GtkInputDialog.cs @@ -1,6 +1,6 @@ using Gtk; -namespace Ryujinx.Ui.Widgets +namespace Ryujinx.UI.Widgets { public class GtkInputDialog : MessageDialog { diff --git a/src/Ryujinx/Ui/Widgets/ProfileDialog.cs b/src/Ryujinx/UI/Widgets/ProfileDialog.cs similarity index 89% rename from src/Ryujinx/Ui/Widgets/ProfileDialog.cs rename to src/Ryujinx/UI/Widgets/ProfileDialog.cs index 0731b37a1..f8aa6345f 100644 --- a/src/Ryujinx/Ui/Widgets/ProfileDialog.cs +++ b/src/Ryujinx/UI/Widgets/ProfileDialog.cs @@ -1,10 +1,10 @@ using Gtk; -using Ryujinx.Ui.Common.Configuration; +using Ryujinx.UI.Common.Configuration; using System; using System.Reflection; using GUI = Gtk.Builder.ObjectAttribute; -namespace Ryujinx.Ui.Widgets +namespace Ryujinx.UI.Widgets { public class ProfileDialog : Dialog { @@ -15,12 +15,12 @@ public class ProfileDialog : Dialog [GUI] Label _errorMessage; #pragma warning restore CS0649, IDE0044 - public ProfileDialog() : this(new Builder("Ryujinx.Ui.Widgets.ProfileDialog.glade")) { } + public ProfileDialog() : this(new Builder("Ryujinx.UI.Widgets.ProfileDialog.glade")) { } private ProfileDialog(Builder builder) : base(builder.GetRawOwnedObject("_profileDialog")) { builder.Autoconnect(this); - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png"); + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); } private void OkToggle_Activated(object sender, EventArgs args) diff --git a/src/Ryujinx/Ui/Widgets/ProfileDialog.glade b/src/Ryujinx/UI/Widgets/ProfileDialog.glade similarity index 100% rename from src/Ryujinx/Ui/Widgets/ProfileDialog.glade rename to src/Ryujinx/UI/Widgets/ProfileDialog.glade diff --git a/src/Ryujinx/Ui/Widgets/RawInputToTextEntry.cs b/src/Ryujinx/UI/Widgets/RawInputToTextEntry.cs similarity index 95% rename from src/Ryujinx/Ui/Widgets/RawInputToTextEntry.cs rename to src/Ryujinx/UI/Widgets/RawInputToTextEntry.cs index e82a3d492..6470790e8 100644 --- a/src/Ryujinx/Ui/Widgets/RawInputToTextEntry.cs +++ b/src/Ryujinx/UI/Widgets/RawInputToTextEntry.cs @@ -1,6 +1,6 @@ using Gtk; -namespace Ryujinx.Ui.Widgets +namespace Ryujinx.UI.Widgets { public class RawInputToTextEntry : Entry { diff --git a/src/Ryujinx/Ui/Widgets/UserErrorDialog.cs b/src/Ryujinx/UI/Widgets/UserErrorDialog.cs similarity index 97% rename from src/Ryujinx/Ui/Widgets/UserErrorDialog.cs rename to src/Ryujinx/UI/Widgets/UserErrorDialog.cs index 63a280e6d..f0b55cd8b 100644 --- a/src/Ryujinx/Ui/Widgets/UserErrorDialog.cs +++ b/src/Ryujinx/UI/Widgets/UserErrorDialog.cs @@ -1,8 +1,8 @@ using Gtk; -using Ryujinx.Ui.Common; -using Ryujinx.Ui.Common.Helper; +using Ryujinx.UI.Common; +using Ryujinx.UI.Common.Helper; -namespace Ryujinx.Ui.Widgets +namespace Ryujinx.UI.Widgets { internal class UserErrorDialog : MessageDialog { diff --git a/src/Ryujinx/Ui/Windows/AboutWindow.Designer.cs b/src/Ryujinx/UI/Windows/AboutWindow.Designer.cs similarity index 97% rename from src/Ryujinx/Ui/Windows/AboutWindow.Designer.cs rename to src/Ryujinx/UI/Windows/AboutWindow.Designer.cs index 345026334..fd912ef9a 100644 --- a/src/Ryujinx/Ui/Windows/AboutWindow.Designer.cs +++ b/src/Ryujinx/UI/Windows/AboutWindow.Designer.cs @@ -1,9 +1,9 @@ using Gtk; using Pango; -using Ryujinx.Ui.Common.Configuration; +using Ryujinx.UI.Common.Configuration; using System.Reflection; -namespace Ryujinx.Ui.Windows +namespace Ryujinx.UI.Windows { public partial class AboutWindow : Window { @@ -88,7 +88,7 @@ private void InitializeComponent() // // _ryujinxLogo // - _ryujinxLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png", 100, 100)) + _ryujinxLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png", 100, 100)) { Margin = 10, MarginStart = 15, @@ -223,7 +223,7 @@ private void InitializeComponent() // // _patreonLogo // - _patreonLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Patreon_Light.png", 30, 30)) + _patreonLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Patreon_Light.png", 30, 30)) { Margin = 10, }; @@ -253,7 +253,7 @@ private void InitializeComponent() // // _githubLogo // - _githubLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_GitHub_Light.png", 30, 30)) + _githubLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_GitHub_Light.png", 30, 30)) { Margin = 10, }; @@ -283,7 +283,7 @@ private void InitializeComponent() // // _discordLogo // - _discordLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Discord_Light.png", 30, 30)) + _discordLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Discord_Light.png", 30, 30)) { Margin = 10, }; @@ -313,7 +313,7 @@ private void InitializeComponent() // // _twitterLogo // - _twitterLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Twitter_Light.png", 30, 30)) + _twitterLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Twitter_Light.png", 30, 30)) { Margin = 10, }; diff --git a/src/Ryujinx/Ui/Windows/AboutWindow.cs b/src/Ryujinx/UI/Windows/AboutWindow.cs similarity index 95% rename from src/Ryujinx/Ui/Windows/AboutWindow.cs rename to src/Ryujinx/UI/Windows/AboutWindow.cs index ba12bb68b..f4bb533c3 100644 --- a/src/Ryujinx/Ui/Windows/AboutWindow.cs +++ b/src/Ryujinx/UI/Windows/AboutWindow.cs @@ -1,18 +1,18 @@ using Gtk; using Ryujinx.Common.Utilities; -using Ryujinx.Ui.Common.Helper; +using Ryujinx.UI.Common.Helper; using System.Net.Http; using System.Net.NetworkInformation; using System.Reflection; using System.Threading.Tasks; -namespace Ryujinx.Ui.Windows +namespace Ryujinx.UI.Windows { public partial class AboutWindow : Window { public AboutWindow() : base($"Ryujinx {Program.Version} - About") { - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(OpenHelper)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png"); + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(OpenHelper)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); InitializeComponent(); _ = DownloadPatronsJson(); diff --git a/src/Ryujinx/Ui/Windows/AmiiboWindow.Designer.cs b/src/Ryujinx/UI/Windows/AmiiboWindow.Designer.cs similarity index 99% rename from src/Ryujinx/Ui/Windows/AmiiboWindow.Designer.cs rename to src/Ryujinx/UI/Windows/AmiiboWindow.Designer.cs index cb27c34cb..3bf73318f 100644 --- a/src/Ryujinx/Ui/Windows/AmiiboWindow.Designer.cs +++ b/src/Ryujinx/UI/Windows/AmiiboWindow.Designer.cs @@ -1,6 +1,6 @@ using Gtk; -namespace Ryujinx.Ui.Windows +namespace Ryujinx.UI.Windows { public partial class AmiiboWindow : Window { diff --git a/src/Ryujinx/Ui/Windows/AmiiboWindow.cs b/src/Ryujinx/UI/Windows/AmiiboWindow.cs similarity index 98% rename from src/Ryujinx/Ui/Windows/AmiiboWindow.cs rename to src/Ryujinx/UI/Windows/AmiiboWindow.cs index a2a5bce6f..d8c0b0c0d 100644 --- a/src/Ryujinx/Ui/Windows/AmiiboWindow.cs +++ b/src/Ryujinx/UI/Windows/AmiiboWindow.cs @@ -4,9 +4,9 @@ using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using Ryujinx.Common.Utilities; -using Ryujinx.Ui.Common.Configuration; -using Ryujinx.Ui.Common.Models.Amiibo; -using Ryujinx.Ui.Widgets; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Models.Amiibo; +using Ryujinx.UI.Widgets; using System; using System.Collections.Generic; using System.IO; @@ -18,7 +18,7 @@ using System.Threading.Tasks; using Window = Gtk.Window; -namespace Ryujinx.Ui.Windows +namespace Ryujinx.UI.Windows { public partial class AmiiboWindow : Window { @@ -52,7 +52,7 @@ public bool UseRandomUuid public AmiiboWindow() : base($"Ryujinx {Program.Version} - Amiibo") { - Icon = new Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png"); + Icon = new Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); InitializeComponent(); @@ -66,7 +66,7 @@ public AmiiboWindow() : base($"Ryujinx {Program.Version} - Amiibo") _amiiboJsonPath = System.IO.Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", "Amiibo.json"); _amiiboList = new List(); - _amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.Ui.Common/Resources/Logo_Amiibo.png"); + _amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.UI.Common/Resources/Logo_Amiibo.png"); _amiiboImage.Pixbuf = new Pixbuf(_amiiboLogoBytes); _scanButton.Sensitive = false; diff --git a/src/Ryujinx/Ui/Windows/AvatarWindow.cs b/src/Ryujinx/UI/Windows/AvatarWindow.cs similarity index 98% rename from src/Ryujinx/Ui/Windows/AvatarWindow.cs rename to src/Ryujinx/UI/Windows/AvatarWindow.cs index 3d3ff7c3c..7cddc362b 100644 --- a/src/Ryujinx/Ui/Windows/AvatarWindow.cs +++ b/src/Ryujinx/UI/Windows/AvatarWindow.cs @@ -8,7 +8,7 @@ using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common.Memory; using Ryujinx.HLE.FileSystem; -using Ryujinx.Ui.Common.Configuration; +using Ryujinx.UI.Common.Configuration; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; @@ -20,7 +20,7 @@ using System.Reflection; using Image = SixLabors.ImageSharp.Image; -namespace Ryujinx.Ui.Windows +namespace Ryujinx.UI.Windows { public class AvatarWindow : Window { @@ -36,7 +36,7 @@ public class AvatarWindow : Window public AvatarWindow() : base($"Ryujinx {Program.Version} - Manage Accounts - Avatar") { - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png"); + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); CanFocus = false; Resizable = false; diff --git a/src/Ryujinx/Ui/Windows/CheatWindow.cs b/src/Ryujinx/UI/Windows/CheatWindow.cs similarity index 97% rename from src/Ryujinx/Ui/Windows/CheatWindow.cs rename to src/Ryujinx/UI/Windows/CheatWindow.cs index afccec53c..73ee870cd 100644 --- a/src/Ryujinx/Ui/Windows/CheatWindow.cs +++ b/src/Ryujinx/UI/Windows/CheatWindow.cs @@ -1,14 +1,14 @@ using Gtk; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS; -using Ryujinx.Ui.App.Common; +using Ryujinx.UI.App.Common; using System; using System.Collections.Generic; using System.IO; using System.Linq; using GUI = Gtk.Builder.ObjectAttribute; -namespace Ryujinx.Ui.Windows +namespace Ryujinx.UI.Windows { public class CheatWindow : Window { @@ -22,7 +22,7 @@ public class CheatWindow : Window [GUI] Button _saveButton; #pragma warning restore CS0649, IDE0044 - public CheatWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : this(new Builder("Ryujinx.Ui.Windows.CheatWindow.glade"), virtualFileSystem, titleId, titleName, titlePath) { } + public CheatWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : this(new Builder("Ryujinx.UI.Windows.CheatWindow.glade"), virtualFileSystem, titleId, titleName, titlePath) { } private CheatWindow(Builder builder, VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : base(builder.GetRawOwnedObject("_cheatWindow")) { diff --git a/src/Ryujinx/Ui/Windows/CheatWindow.glade b/src/Ryujinx/UI/Windows/CheatWindow.glade similarity index 100% rename from src/Ryujinx/Ui/Windows/CheatWindow.glade rename to src/Ryujinx/UI/Windows/CheatWindow.glade diff --git a/src/Ryujinx/Ui/Windows/ControllerWindow.cs b/src/Ryujinx/UI/Windows/ControllerWindow.cs similarity index 99% rename from src/Ryujinx/Ui/Windows/ControllerWindow.cs rename to src/Ryujinx/UI/Windows/ControllerWindow.cs index ebf22ab60..954113443 100644 --- a/src/Ryujinx/Ui/Windows/ControllerWindow.cs +++ b/src/Ryujinx/UI/Windows/ControllerWindow.cs @@ -9,8 +9,8 @@ using Ryujinx.Input; using Ryujinx.Input.Assigner; using Ryujinx.Input.GTK3; -using Ryujinx.Ui.Common.Configuration; -using Ryujinx.Ui.Widgets; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Widgets; using System; using System.Collections.Generic; using System.IO; @@ -22,7 +22,7 @@ using GUI = Gtk.Builder.ObjectAttribute; using Key = Ryujinx.Common.Configuration.Hid.Key; -namespace Ryujinx.Ui.Windows +namespace Ryujinx.UI.Windows { public class ControllerWindow : Window { @@ -117,7 +117,7 @@ public class ControllerWindow : Window private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - public ControllerWindow(MainWindow mainWindow, PlayerIndex controllerId) : this(mainWindow, new Builder("Ryujinx.Ui.Windows.ControllerWindow.glade"), controllerId) { } + public ControllerWindow(MainWindow mainWindow, PlayerIndex controllerId) : this(mainWindow, new Builder("Ryujinx.UI.Windows.ControllerWindow.glade"), controllerId) { } private ControllerWindow(MainWindow mainWindow, Builder builder, PlayerIndex controllerId) : base(builder.GetRawOwnedObject("_controllerWin")) { @@ -127,7 +127,7 @@ private ControllerWindow(MainWindow mainWindow, Builder builder, PlayerIndex con // NOTE: To get input in this window, we need to bind a custom keyboard driver instead of using the InputManager one as the main window isn't focused... _gtk3KeyboardDriver = new GTK3KeyboardDriver(this); - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png"); + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); builder.Autoconnect(this); @@ -377,10 +377,10 @@ private void SetControllerSpecificFields() { _controllerImage.Pixbuf = _controllerType.ActiveId switch { - "ProController" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Controller_ProCon.svg", 400, 400), - "JoyconLeft" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Controller_JoyConLeft.svg", 400, 500), - "JoyconRight" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Controller_JoyConRight.svg", 400, 500), - _ => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Controller_JoyConPair.svg", 400, 500), + "ProController" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Controller_ProCon.svg", 400, 400), + "JoyconLeft" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Controller_JoyConLeft.svg", 400, 500), + "JoyconRight" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Controller_JoyConRight.svg", 400, 500), + _ => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Controller_JoyConPair.svg", 400, 500), }; } } diff --git a/src/Ryujinx/Ui/Windows/ControllerWindow.glade b/src/Ryujinx/UI/Windows/ControllerWindow.glade similarity index 100% rename from src/Ryujinx/Ui/Windows/ControllerWindow.glade rename to src/Ryujinx/UI/Windows/ControllerWindow.glade diff --git a/src/Ryujinx/Ui/Windows/DlcWindow.cs b/src/Ryujinx/UI/Windows/DlcWindow.cs similarity index 98% rename from src/Ryujinx/Ui/Windows/DlcWindow.cs rename to src/Ryujinx/UI/Windows/DlcWindow.cs index 9f7179467..aed1a015d 100644 --- a/src/Ryujinx/Ui/Windows/DlcWindow.cs +++ b/src/Ryujinx/UI/Windows/DlcWindow.cs @@ -9,13 +9,13 @@ using Ryujinx.Common.Configuration; using Ryujinx.Common.Utilities; using Ryujinx.HLE.FileSystem; -using Ryujinx.Ui.Widgets; +using Ryujinx.UI.Widgets; using System; using System.Collections.Generic; using System.IO; using GUI = Gtk.Builder.ObjectAttribute; -namespace Ryujinx.Ui.Windows +namespace Ryujinx.UI.Windows { public class DlcWindow : Window { @@ -32,7 +32,7 @@ public class DlcWindow : Window [GUI] TreeSelection _dlcTreeSelection; #pragma warning restore CS0649, IDE0044 - public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Ui.Windows.DlcWindow.glade"), virtualFileSystem, titleId, titleName) { } + public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.UI.Windows.DlcWindow.glade"), virtualFileSystem, titleId, titleName) { } private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_dlcWindow")) { diff --git a/src/Ryujinx/Ui/Windows/DlcWindow.glade b/src/Ryujinx/UI/Windows/DlcWindow.glade similarity index 100% rename from src/Ryujinx/Ui/Windows/DlcWindow.glade rename to src/Ryujinx/UI/Windows/DlcWindow.glade diff --git a/src/Ryujinx/Ui/Windows/SettingsWindow.cs b/src/Ryujinx/UI/Windows/SettingsWindow.cs similarity index 98% rename from src/Ryujinx/Ui/Windows/SettingsWindow.cs rename to src/Ryujinx/UI/Windows/SettingsWindow.cs index dabef14dd..270a8ad4a 100644 --- a/src/Ryujinx/Ui/Windows/SettingsWindow.cs +++ b/src/Ryujinx/UI/Windows/SettingsWindow.cs @@ -9,10 +9,10 @@ using Ryujinx.Common.GraphicsDriver; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.Services.Time.TimeZone; -using Ryujinx.Ui.Common.Configuration; -using Ryujinx.Ui.Common.Configuration.System; -using Ryujinx.Ui.Helper; -using Ryujinx.Ui.Widgets; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Configuration.System; +using Ryujinx.UI.Helper; +using Ryujinx.UI.Widgets; using System; using System.Collections.Generic; using System.Globalization; @@ -23,7 +23,7 @@ using System.Threading.Tasks; using GUI = Gtk.Builder.ObjectAttribute; -namespace Ryujinx.Ui.Windows +namespace Ryujinx.UI.Windows { public class SettingsWindow : Window { @@ -120,11 +120,11 @@ public class SettingsWindow : Window #pragma warning restore CS0649, IDE0044 - public SettingsWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(parent, new Builder("Ryujinx.Ui.Windows.SettingsWindow.glade"), virtualFileSystem, contentManager) { } + public SettingsWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(parent, new Builder("Ryujinx.UI.Windows.SettingsWindow.glade"), virtualFileSystem, contentManager) { } private SettingsWindow(MainWindow parent, Builder builder, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : base(builder.GetRawOwnedObject("_settingsWin")) { - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png"); + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); _parent = parent; @@ -311,7 +311,7 @@ private SettingsWindow(MainWindow parent, Builder builder, VirtualFileSystem vir _directMouseAccess.Click(); } - if (ConfigurationState.Instance.Ui.EnableCustomTheme) + if (ConfigurationState.Instance.UI.EnableCustomTheme) { _custThemeToggle.Click(); } @@ -366,7 +366,7 @@ private SettingsWindow(MainWindow parent, Builder builder, VirtualFileSystem vir _multiLanSelect.SetActiveId(ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value); _multiModeSelect.SetActiveId(ConfigurationState.Instance.Multiplayer.Mode.Value.ToString()); - _custThemePath.Buffer.Text = ConfigurationState.Instance.Ui.CustomThemePath; + _custThemePath.Buffer.Text = ConfigurationState.Instance.UI.CustomThemePath; _resScaleText.Buffer.Text = ConfigurationState.Instance.Graphics.ResScaleCustom.Value.ToString(); _scalingFilterLevel.Value = ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value; _resScaleText.Visible = _resScaleCombo.ActiveId == "-1"; @@ -379,7 +379,7 @@ private SettingsWindow(MainWindow parent, Builder builder, VirtualFileSystem vir _gameDirsBoxStore = new ListStore(typeof(string)); _gameDirsBox.Model = _gameDirsBoxStore; - foreach (string gameDir in ConfigurationState.Instance.Ui.GameDirs.Value) + foreach (string gameDir in ConfigurationState.Instance.UI.GameDirs.Value) { _gameDirsBoxStore.AppendValues(gameDir); } @@ -568,7 +568,7 @@ private void SaveSettings() _gameDirsBoxStore.IterNext(ref treeIter); } - ConfigurationState.Instance.Ui.GameDirs.Value = gameDirs; + ConfigurationState.Instance.UI.GameDirs.Value = gameDirs; _directoryChanged = false; } @@ -640,11 +640,11 @@ private void SaveSettings() ConfigurationState.Instance.System.IgnoreMissingServices.Value = _ignoreToggle.Active; ConfigurationState.Instance.Hid.EnableKeyboard.Value = _directKeyboardAccess.Active; ConfigurationState.Instance.Hid.EnableMouse.Value = _directMouseAccess.Active; - ConfigurationState.Instance.Ui.EnableCustomTheme.Value = _custThemeToggle.Active; + ConfigurationState.Instance.UI.EnableCustomTheme.Value = _custThemeToggle.Active; ConfigurationState.Instance.System.Language.Value = Enum.Parse(_systemLanguageSelect.ActiveId); ConfigurationState.Instance.System.Region.Value = Enum.Parse(_systemRegionSelect.ActiveId); ConfigurationState.Instance.System.SystemTimeOffset.Value = _systemTimeOffset; - ConfigurationState.Instance.Ui.CustomThemePath.Value = _custThemePath.Buffer.Text; + ConfigurationState.Instance.UI.CustomThemePath.Value = _custThemePath.Buffer.Text; ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = _graphicsShadersDumpPath.Buffer.Text; ConfigurationState.Instance.System.FsGlobalAccessLogMode.Value = (int)_fsLogSpinAdjustment.Value; ConfigurationState.Instance.Graphics.MaxAnisotropy.Value = float.Parse(_anisotropy.ActiveId, CultureInfo.InvariantCulture); diff --git a/src/Ryujinx/Ui/Windows/SettingsWindow.glade b/src/Ryujinx/UI/Windows/SettingsWindow.glade similarity index 100% rename from src/Ryujinx/Ui/Windows/SettingsWindow.glade rename to src/Ryujinx/UI/Windows/SettingsWindow.glade diff --git a/src/Ryujinx/Ui/Windows/TitleUpdateWindow.cs b/src/Ryujinx/UI/Windows/TitleUpdateWindow.cs similarity index 98% rename from src/Ryujinx/Ui/Windows/TitleUpdateWindow.cs rename to src/Ryujinx/UI/Windows/TitleUpdateWindow.cs index 51918eeab..1df929336 100644 --- a/src/Ryujinx/Ui/Windows/TitleUpdateWindow.cs +++ b/src/Ryujinx/UI/Windows/TitleUpdateWindow.cs @@ -9,8 +9,8 @@ using Ryujinx.Common.Configuration; using Ryujinx.Common.Utilities; using Ryujinx.HLE.FileSystem; -using Ryujinx.Ui.App.Common; -using Ryujinx.Ui.Widgets; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Widgets; using System; using System.Collections.Generic; using System.IO; @@ -18,7 +18,7 @@ using GUI = Gtk.Builder.ObjectAttribute; using SpanHelpers = LibHac.Common.SpanHelpers; -namespace Ryujinx.Ui.Windows +namespace Ryujinx.UI.Windows { public class TitleUpdateWindow : Window { @@ -38,7 +38,7 @@ public class TitleUpdateWindow : Window [GUI] RadioButton _noUpdateRadioButton; #pragma warning restore CS0649, IDE0044 - public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Ui.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, titleId, titleName) { } + public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.UI.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, titleId, titleName) { } private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_titleUpdateWindow")) { diff --git a/src/Ryujinx/Ui/Windows/TitleUpdateWindow.glade b/src/Ryujinx/UI/Windows/TitleUpdateWindow.glade similarity index 100% rename from src/Ryujinx/Ui/Windows/TitleUpdateWindow.glade rename to src/Ryujinx/UI/Windows/TitleUpdateWindow.glade diff --git a/src/Ryujinx/Ui/Windows/UserProfilesManagerWindow.Designer.cs b/src/Ryujinx/UI/Windows/UserProfilesManagerWindow.Designer.cs similarity index 99% rename from src/Ryujinx/Ui/Windows/UserProfilesManagerWindow.Designer.cs rename to src/Ryujinx/UI/Windows/UserProfilesManagerWindow.Designer.cs index 804bd3fb0..bc5a18f95 100644 --- a/src/Ryujinx/Ui/Windows/UserProfilesManagerWindow.Designer.cs +++ b/src/Ryujinx/UI/Windows/UserProfilesManagerWindow.Designer.cs @@ -2,7 +2,7 @@ using Pango; using System; -namespace Ryujinx.Ui.Windows +namespace Ryujinx.UI.Windows { public partial class UserProfilesManagerWindow : Window { diff --git a/src/Ryujinx/Ui/Windows/UserProfilesManagerWindow.cs b/src/Ryujinx/UI/Windows/UserProfilesManagerWindow.cs similarity index 98% rename from src/Ryujinx/Ui/Windows/UserProfilesManagerWindow.cs rename to src/Ryujinx/UI/Windows/UserProfilesManagerWindow.cs index 3d503f64e..d1e5fa9fc 100644 --- a/src/Ryujinx/Ui/Windows/UserProfilesManagerWindow.cs +++ b/src/Ryujinx/UI/Windows/UserProfilesManagerWindow.cs @@ -2,8 +2,8 @@ using Ryujinx.Common.Memory; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.Services.Account.Acc; -using Ryujinx.Ui.Common.Configuration; -using Ryujinx.Ui.Widgets; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Widgets; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Processing; using System; @@ -14,7 +14,7 @@ using System.Threading.Tasks; using Image = SixLabors.ImageSharp.Image; -namespace Ryujinx.Ui.Windows +namespace Ryujinx.UI.Windows { public partial class UserProfilesManagerWindow : Window { @@ -32,7 +32,7 @@ public partial class UserProfilesManagerWindow : Window public UserProfilesManagerWindow(AccountManager accountManager, ContentManager contentManager, VirtualFileSystem virtualFileSystem) : base($"Ryujinx {Program.Version} - Manage User Profiles") { - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png"); + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); InitializeComponent(); From 18909195d1361a18af866634f4b1bdd4443aa24c Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Sun, 11 Feb 2024 02:12:43 +0000 Subject: [PATCH 066/126] Old buttons (#6237) --- src/Ryujinx.Ava/Assets/Locales/en_US.json | 4 +++ .../UI/Controls/ApplicationContextMenu.axaml | 9 +++++++ .../Controls/ApplicationContextMenu.axaml.cs | 26 +++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/src/Ryujinx.Ava/Assets/Locales/en_US.json b/src/Ryujinx.Ava/Assets/Locales/en_US.json index 6ac9ae948..b02613a7c 100644 --- a/src/Ryujinx.Ava/Assets/Locales/en_US.json +++ b/src/Ryujinx.Ava/Assets/Locales/en_US.json @@ -73,6 +73,10 @@ "GameListContextMenuCreateShortcut": "Create Application Shortcut", "GameListContextMenuCreateShortcutToolTip": "Create a Desktop Shortcut that launches the selected Application", "GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application", + "GameListContextMenuOpenModsDirectory": "Open Mods Directory", + "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", + "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", "StatusBarGamesLoaded": "{0}/{1} Games Loaded", "StatusBarSystemVersion": "System Version: {0}", "LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected", diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml b/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml index 5bdeb8ad3..dd0926fc9 100644 --- a/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml +++ b/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml @@ -51,6 +51,15 @@ Header="{locale:Locale GameListContextMenuManageMod}" ToolTip.Tip="{locale:Locale GameListContextMenuManageModToolTip}" /> + + + Date: Sun, 11 Feb 2024 13:21:54 +0100 Subject: [PATCH 067/126] infra: Restore Nuget packages for linux-arm64 for Flatpak Signed-off-by: Mary Guillemard --- .github/workflows/flatpak.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml index f529bea03..1f4d826bb 100644 --- a/.github/workflows/flatpak.yml +++ b/.github/workflows/flatpak.yml @@ -51,7 +51,9 @@ jobs: - name: Restore Nuget packages # With .NET 8.0.100, Microsoft.NET.ILLink.Tasks isn't restored by default and only seems to appears when publishing. # So we just publish to grab the dependencies - run: dotnet publish -c Release -r linux-x64 Ryujinx/${{ env.RYUJINX_PROJECT_FILE }} --self-contained + run: | + dotnet publish -c Release -r linux-x64 Ryujinx/${{ env.RYUJINX_PROJECT_FILE }} --self-contained + dotnet publish -c Release -r linux-arm64 Ryujinx/${{ env.RYUJINX_PROJECT_FILE }} --self-contained - name: Generate nuget_sources.json shell: python From baf94e0e3e8c9140764b5109a2912a5e3cd9cb63 Mon Sep 17 00:00:00 2001 From: Mary Guillemard Date: Sun, 11 Feb 2024 16:45:58 +0100 Subject: [PATCH 068/126] infra: Force add linux-x64 apphost in flathub nuget source (#6302) Required when building on the arm64 runner. Signed-off-by: Mary Guillemard --- .github/workflows/flatpak.yml | 74 ++++++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 19 deletions(-) diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml index 1f4d826bb..bfed328c9 100644 --- a/.github/workflows/flatpak.yml +++ b/.github/workflows/flatpak.yml @@ -58,33 +58,69 @@ jobs: - name: Generate nuget_sources.json shell: python run: | + import hashlib from pathlib import Path import base64 import binascii import json import os + import urllib.request sources = [] - for path in Path(os.environ['NUGET_PACKAGES']).glob('**/*.nupkg.sha512'): - name = path.parent.parent.name - version = path.parent.name - filename = '{}.{}.nupkg'.format(name, version) - url = 'https://api.nuget.org/v3-flatcontainer/{}/{}/{}'.format(name, version, filename) - - with path.open() as fp: - sha512 = binascii.hexlify(base64.b64decode(fp.read())).decode('ascii') - - sources.append({ - 'type': 'file', - 'url': url, - 'sha512': sha512, - 'dest': os.environ['NUGET_SOURCES_DESTDIR'], - 'dest-filename': filename, - }) - - with open('flathub/nuget_sources.json', 'w') as fp: - json.dump(sources, fp, indent=4) + + def create_source_from_external(name, version): + full_dir_path = Path(os.environ["NUGET_PACKAGES"]).joinpath(name).joinpath(version) + os.makedirs(full_dir_path, exist_ok=True) + + filename = "{}.{}.nupkg".format(name, version) + url = "https://api.nuget.org/v3-flatcontainer/{}/{}/{}".format( + name, version, filename + ) + + print(f"Processing {url}...") + response = urllib.request.urlopen(url) + sha512 = hashlib.sha512(response.read()).hexdigest() + + return { + "type": "file", + "url": url, + "sha512": sha512, + "dest": os.environ["NUGET_SOURCES_DESTDIR"], + "dest-filename": filename, + } + + + has_added_x64_apphost = False + + for path in Path(os.environ["NUGET_PACKAGES"]).glob("**/*.nupkg.sha512"): + name = path.parent.parent.name + version = path.parent.name + filename = "{}.{}.nupkg".format(name, version) + url = "https://api.nuget.org/v3-flatcontainer/{}/{}/{}".format( + name, version, filename + ) + + with path.open() as fp: + sha512 = binascii.hexlify(base64.b64decode(fp.read())).decode("ascii") + + sources.append( + { + "type": "file", + "url": url, + "sha512": sha512, + "dest": os.environ["NUGET_SOURCES_DESTDIR"], + "dest-filename": filename, + } + ) + + # .NET will not add current installed application host to the list, force inject it here. + if not has_added_x64_apphost and name.startswith('microsoft.netcore.app.host'): + sources.append(create_source_from_external("microsoft.netcore.app.host.linux-x64", version)) + has_added_x64_apphost = True + + with open("flathub/nuget_sources.json", "w") as fp: + json.dump(sources, fp, indent=4) - name: Update flatpak metadata id: metadata From 946633276b557a8e522b5e09790c9827cf51f8d6 Mon Sep 17 00:00:00 2001 From: jcm Date: Sun, 11 Feb 2024 12:04:39 -0600 Subject: [PATCH 069/126] macOS: Stop storing user data in Documents for some users; fix symlinks (#6241) * macOS: Stop storing user data in Documents for some users; fix symlinks * Use SupportedOSPlatform tag, catch exceptions, log warning instead of error * Provide best path hints to user if symlink fixup fails --------- Co-authored-by: jcm --- .../Configuration/AppDataManager.cs | 90 ++++++++++++++++--- .../Configuration/ConfigurationFileFormat.cs | 2 +- .../Configuration/ConfigurationState.cs | 12 +++ 3 files changed, 92 insertions(+), 12 deletions(-) diff --git a/src/Ryujinx.Common/Configuration/AppDataManager.cs b/src/Ryujinx.Common/Configuration/AppDataManager.cs index f3df0fc9d..26b587daa 100644 --- a/src/Ryujinx.Common/Configuration/AppDataManager.cs +++ b/src/Ryujinx.Common/Configuration/AppDataManager.cs @@ -2,6 +2,7 @@ using Ryujinx.Common.Utilities; using System; using System.IO; +using System.Runtime.Versioning; namespace Ryujinx.Common.Configuration { @@ -95,18 +96,9 @@ public static void Initialize(string baseDirPath) BaseDirPath = Path.GetFullPath(BaseDirPath); // convert relative paths - // NOTE: Moves the Ryujinx folder in `~/.config` to `~/Library/Application Support` if one is found - // and a Ryujinx folder does not already exist in Application Support. - // Also creates a symlink from `~/.config/Ryujinx` to `~/Library/Application Support/Ryujinx` to preserve backwards compatibility. - // This should be removed in the future. - if (OperatingSystem.IsMacOS() && Mode == LaunchMode.UserProfile) + if (IsPathSymlink(BaseDirPath)) { - string oldConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir); - if (Path.Exists(oldConfigPath) && !IsPathSymlink(oldConfigPath) && !Path.Exists(BaseDirPath)) - { - FileSystemUtils.MoveDirectory(oldConfigPath, BaseDirPath); - Directory.CreateSymbolicLink(oldConfigPath, BaseDirPath); - } + Logger.Warning?.Print(LogClass.Application, $"Application data directory is a symlink. This may be unintended."); } SetupBasePaths(); @@ -245,6 +237,82 @@ private static bool IsPathSymlink(string path) return (attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint; } + [SupportedOSPlatform("macos")] + public static void FixMacOSConfigurationFolders() + { + string oldConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + ".config", DefaultBaseDir); + if (Path.Exists(oldConfigPath) && !IsPathSymlink(oldConfigPath) && !Path.Exists(BaseDirPath)) + { + FileSystemUtils.MoveDirectory(oldConfigPath, BaseDirPath); + Directory.CreateSymbolicLink(oldConfigPath, BaseDirPath); + } + + string correctApplicationDataDirectoryPath = + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir); + if (IsPathSymlink(correctApplicationDataDirectoryPath)) + { + //copy the files somewhere temporarily + string tempPath = Path.Combine(Path.GetTempPath(), DefaultBaseDir); + try + { + FileSystemUtils.CopyDirectory(correctApplicationDataDirectoryPath, tempPath, true); + } + catch (Exception exception) + { + Logger.Error?.Print(LogClass.Application, + $"Critical error copying Ryujinx application data into the temp folder. {exception}"); + try + { + FileSystemInfo resolvedDirectoryInfo = + Directory.ResolveLinkTarget(correctApplicationDataDirectoryPath, true); + string resolvedPath = resolvedDirectoryInfo.FullName; + Logger.Error?.Print(LogClass.Application, $"Please manually move your Ryujinx data from {resolvedPath} to {correctApplicationDataDirectoryPath}, and remove the symlink."); + } + catch (Exception symlinkException) + { + Logger.Error?.Print(LogClass.Application, $"Unable to resolve the symlink for Ryujinx application data: {symlinkException}. Follow the symlink at {correctApplicationDataDirectoryPath} and move your data back to the Application Support folder."); + } + return; + } + + //delete the symlink + try + { + //This will fail if this is an actual directory, so there is no way we can actually delete user data here. + File.Delete(correctApplicationDataDirectoryPath); + } + catch (Exception exception) + { + Logger.Error?.Print(LogClass.Application, + $"Critical error deleting the Ryujinx application data folder symlink at {correctApplicationDataDirectoryPath}. {exception}"); + try + { + FileSystemInfo resolvedDirectoryInfo = + Directory.ResolveLinkTarget(correctApplicationDataDirectoryPath, true); + string resolvedPath = resolvedDirectoryInfo.FullName; + Logger.Error?.Print(LogClass.Application, $"Please manually move your Ryujinx data from {resolvedPath} to {correctApplicationDataDirectoryPath}, and remove the symlink."); + } + catch (Exception symlinkException) + { + Logger.Error?.Print(LogClass.Application, $"Unable to resolve the symlink for Ryujinx application data: {symlinkException}. Follow the symlink at {correctApplicationDataDirectoryPath} and move your data back to the Application Support folder."); + } + return; + } + + //put the files back + try + { + FileSystemUtils.CopyDirectory(tempPath, correctApplicationDataDirectoryPath, true); + } + catch (Exception exception) + { + Logger.Error?.Print(LogClass.Application, + $"Critical error copying Ryujinx application data into the correct location. {exception}. Please manually move your application data from {tempPath} to {correctApplicationDataDirectoryPath}."); + } + } + } + public static string GetModsPath() => CustomModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultModsDir)).FullName; public static string GetSdModsPath() => CustomSdModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultSdcardDir, "atmosphere")).FullName; } diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs index 0ee51d830..0f6c21ef2 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs @@ -15,7 +15,7 @@ public class ConfigurationFileFormat /// /// The current version of the file format /// - public const int CurrentVersion = 48; + public const int CurrentVersion = 49; /// /// Version of the configuration file format diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs index 1d6934ce3..b7f36087c 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs @@ -1430,6 +1430,18 @@ public void Load(ConfigurationFileFormat configurationFileFormat, string configu configurationFileUpdated = true; } + if (configurationFileFormat.Version < 49) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 49."); + + if (OperatingSystem.IsMacOS()) + { + AppDataManager.FixMacOSConfigurationFolders(); + } + + configurationFileUpdated = true; + } + Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog; Graphics.ResScale.Value = configurationFileFormat.ResScale; Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom; From 904a5ffcb4d7339efed24156409e67b58c0e77bc Mon Sep 17 00:00:00 2001 From: jcm Date: Sun, 11 Feb 2024 17:10:21 -0600 Subject: [PATCH 070/126] Handle exceptions when checking user data directory for symlink (#6304) Co-authored-by: jcm --- src/Ryujinx.Common/Configuration/AppDataManager.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.Common/Configuration/AppDataManager.cs b/src/Ryujinx.Common/Configuration/AppDataManager.cs index 26b587daa..deaa03def 100644 --- a/src/Ryujinx.Common/Configuration/AppDataManager.cs +++ b/src/Ryujinx.Common/Configuration/AppDataManager.cs @@ -233,8 +233,15 @@ private static void SetupBasePaths() // Should be removed, when the existence of the old directory isn't checked anymore. private static bool IsPathSymlink(string path) { - FileAttributes attributes = File.GetAttributes(path); - return (attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint; + try + { + FileAttributes attributes = File.GetAttributes(path); + return (attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint; + } + catch + { + return false; + } } [SupportedOSPlatform("macos")] From dfb14a56077707a22379592f812a3e26a24a4367 Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Thu, 15 Feb 2024 09:41:43 +0000 Subject: [PATCH 071/126] Updaters: Fix ARM Linux Updater (#6316) * Remove Arch Checks * Fix ARM Linux updater --- src/Ryujinx.Ava/Assets/Locales/en_US.json | 2 -- src/Ryujinx.Ava/Modules/Updater/Updater.cs | 17 ++--------------- src/Ryujinx/Modules/Updater/Updater.cs | 13 ++----------- 3 files changed, 4 insertions(+), 28 deletions(-) diff --git a/src/Ryujinx.Ava/Assets/Locales/en_US.json b/src/Ryujinx.Ava/Assets/Locales/en_US.json index b02613a7c..1ecc5da46 100644 --- a/src/Ryujinx.Ava/Assets/Locales/en_US.json +++ b/src/Ryujinx.Ava/Assets/Locales/en_US.json @@ -333,8 +333,6 @@ "DialogUpdaterAddingFilesMessage": "Adding New Update...", "DialogUpdaterCompleteMessage": "Update Complete!", "DialogUpdaterRestartMessage": "Do you want to restart Ryujinx now?", - "DialogUpdaterArchNotSupportedMessage": "You are not running a supported system architecture!", - "DialogUpdaterArchNotSupportedSubMessage": "(Only x64 systems are supported!)", "DialogUpdaterNoInternetMessage": "You are not connected to the Internet!", "DialogUpdaterNoInternetSubMessage": "Please verify that you have a working Internet connection!", "DialogUpdaterDirtyBuildMessage": "You Cannot update a Dirty build of Ryujinx!", diff --git a/src/Ryujinx.Ava/Modules/Updater/Updater.cs b/src/Ryujinx.Ava/Modules/Updater/Updater.cs index bd211fa5b..5795f34f0 100644 --- a/src/Ryujinx.Ava/Modules/Updater/Updater.cs +++ b/src/Ryujinx.Ava/Modules/Updater/Updater.cs @@ -68,7 +68,8 @@ public static async Task BeginParse(Window mainWindow, bool showVersionUpToDate) } else if (OperatingSystem.IsLinux()) { - _platformExt = "linux_x64.tar.gz"; + var arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "arm64" : "x64"; + _platformExt = $"linux_{arch}.tar.gz"; } Version newVersion; @@ -637,20 +638,6 @@ private static void InstallUpdate(TaskDialog taskDialog, string updateFile) public static bool CanUpdate(bool showWarnings) { #if !DISABLE_UPDATER - if (RuntimeInformation.OSArchitecture != Architecture.X64 && !OperatingSystem.IsMacOS()) - { - if (showWarnings) - { - Dispatcher.UIThread.InvokeAsync(() => - ContentDialogHelper.CreateWarningDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedMessage], - LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedSubMessage]) - ); - } - - return false; - } - if (!NetworkInterface.GetIsNetworkAvailable()) { if (showWarnings) diff --git a/src/Ryujinx/Modules/Updater/Updater.cs b/src/Ryujinx/Modules/Updater/Updater.cs index 16fbc36ae..6c0f9ccea 100644 --- a/src/Ryujinx/Modules/Updater/Updater.cs +++ b/src/Ryujinx/Modules/Updater/Updater.cs @@ -78,7 +78,8 @@ public static async Task BeginParse(MainWindow mainWindow, bool showVersionUpToD } else if (OperatingSystem.IsLinux()) { - _platformExt = "linux_x64.tar.gz"; + var arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "arm64" : "x64"; + _platformExt = $"linux_{arch}.tar.gz"; artifactIndex = 0; } @@ -512,16 +513,6 @@ await Task.Run(() => public static bool CanUpdate(bool showWarnings) { #if !DISABLE_UPDATER - if (RuntimeInformation.OSArchitecture != Architecture.X64) - { - if (showWarnings) - { - GtkDialog.CreateWarningDialog("You are not running a supported system architecture!", "(Only x64 systems are supported!)"); - } - - return false; - } - if (!NetworkInterface.GetIsNetworkAvailable()) { if (showWarnings) From d1a093e5ca736228b96364539b8dc1522c9a0928 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Thu, 15 Feb 2024 15:48:22 -0300 Subject: [PATCH 072/126] Stub VSMS related ioctls (#6313) * Stub VSMS related ioctls * Clean up usings --- .../NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs | 33 +++++++++++++++++++ .../NvHostCtrlGpu/Types/NumVsmsArguments.cs | 11 +++++++ .../Types/VsmsMappingArguments.cs | 13 ++++++++ 3 files changed, 57 insertions(+) create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/NumVsmsArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/VsmsMappingArguments.cs diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs index d287c6d9e..29198617f 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs @@ -50,6 +50,12 @@ public override NvInternalResult Ioctl(NvIoctl command, Span arguments) case 0x06: result = CallIoctlMethod(GetTpcMasks, arguments); break; + case 0x12: + result = CallIoctlMethod(NumVsms, arguments); + break; + case 0x13: + result = CallIoctlMethod(VsmsMapping, arguments); + break; case 0x14: result = CallIoctlMethod(GetActiveSlotMask, arguments); break; @@ -76,6 +82,12 @@ public override NvInternalResult Ioctl3(NvIoctl command, Span arguments, S case 0x06: result = CallIoctlMethod(GetTpcMasks, arguments, inlineOutBuffer); break; + case 0x12: + result = CallIoctlMethod(NumVsms, arguments); + break; + case 0x13: + result = CallIoctlMethod(VsmsMapping, arguments); + break; } } @@ -216,6 +228,27 @@ private NvInternalResult GetTpcMasks(ref GetTpcMasksArguments arguments, ref int return NvInternalResult.Success; } + private NvInternalResult NumVsms(ref NumVsmsArguments arguments) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + arguments.NumVsms = 2; + + return NvInternalResult.Success; + } + + private NvInternalResult VsmsMapping(ref VsmsMappingArguments arguments) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + arguments.Sm0GpcIndex = 0; + arguments.Sm0TpcIndex = 0; + arguments.Sm1GpcIndex = 0; + arguments.Sm1TpcIndex = 1; + + return NvInternalResult.Success; + } + private NvInternalResult GetActiveSlotMask(ref GetActiveSlotMaskArguments arguments) { Logger.Stub?.PrintStub(LogClass.ServiceNv); diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/NumVsmsArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/NumVsmsArguments.cs new file mode 100644 index 000000000..fb5013a70 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/NumVsmsArguments.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct NumVsmsArguments + { + public uint NumVsms; + public uint Reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/VsmsMappingArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/VsmsMappingArguments.cs new file mode 100644 index 000000000..1b56fcc44 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/VsmsMappingArguments.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct VsmsMappingArguments + { + public byte Sm0GpcIndex; + public byte Sm0TpcIndex; + public byte Sm1GpcIndex; + public byte Sm1TpcIndex; + } +} From 74fe81432930b3bef60b3f741ebe5776d9d32e5d Mon Sep 17 00:00:00 2001 From: gdkchan Date: Thu, 15 Feb 2024 16:04:30 -0300 Subject: [PATCH 073/126] Remove Vulkan SubgroupSizeControl enablement code (#6317) --- .../VulkanInitialization.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs index dd7bcf10f..3b1321c58 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs @@ -486,20 +486,6 @@ internal static Device CreateDevice(Vk api, VulkanPhysicalDevice physicalDevice, pExtendedFeatures = &featuresFragmentShaderInterlock; } - PhysicalDeviceSubgroupSizeControlFeaturesEXT featuresSubgroupSizeControl; - - if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_subgroup_size_control")) - { - featuresSubgroupSizeControl = new PhysicalDeviceSubgroupSizeControlFeaturesEXT - { - SType = StructureType.PhysicalDeviceSubgroupSizeControlFeaturesExt, - PNext = pExtendedFeatures, - SubgroupSizeControl = true, - }; - - pExtendedFeatures = &featuresSubgroupSizeControl; - } - PhysicalDeviceCustomBorderColorFeaturesEXT featuresCustomBorderColor; if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_custom_border_color") && From 74a18b7c1820ae2094894cd2108c8c3a9bc03260 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Thu, 15 Feb 2024 16:16:01 -0300 Subject: [PATCH 074/126] Fix PermissionLocked check on UnmapProcessCodeMemory (#6314) --- src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs index b065e9c58..6470742d9 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs @@ -673,9 +673,9 @@ public Result UnmapProcessCodeMemory(ulong dst, ulong src, ulong size) MemoryState.UnmapProcessCodeMemoryAllowed, KMemoryPermission.None, KMemoryPermission.None, - MemoryAttribute.Mask, + MemoryAttribute.Mask & ~MemoryAttribute.PermissionLocked, MemoryAttribute.None, - MemoryAttribute.IpcAndDeviceMapped | MemoryAttribute.PermissionLocked, + MemoryAttribute.IpcAndDeviceMapped, out MemoryState state, out _, out _); From e37735ed2630a807c709d2d3e2099f6c1f2b10fe Mon Sep 17 00:00:00 2001 From: gdkchan Date: Thu, 15 Feb 2024 19:06:26 -0300 Subject: [PATCH 075/126] Implement X8Z24 texture format (#6315) --- src/Ryujinx.Graphics.GAL/Format.cs | 4 +++ .../Engine/Types/ZetaFormat.cs | 28 +++++++++---------- src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs | 2 ++ .../Image/TextureCompatibility.cs | 3 +- src/Ryujinx.Graphics.OpenGL/FormatTable.cs | 13 +++++++++ src/Ryujinx.Graphics.OpenGL/Framebuffer.cs | 16 ++--------- .../Image/TextureCopy.cs | 11 ++------ src/Ryujinx.Graphics.Vulkan/EnumConversion.cs | 4 +-- .../FormatCapabilities.cs | 2 +- src/Ryujinx.Graphics.Vulkan/FormatTable.cs | 1 + src/Ryujinx.Graphics.Vulkan/TextureStorage.cs | 3 +- 11 files changed, 45 insertions(+), 42 deletions(-) diff --git a/src/Ryujinx.Graphics.GAL/Format.cs b/src/Ryujinx.Graphics.GAL/Format.cs index 035543560..bd3b38a9a 100644 --- a/src/Ryujinx.Graphics.GAL/Format.cs +++ b/src/Ryujinx.Graphics.GAL/Format.cs @@ -148,6 +148,7 @@ public enum Format B8G8R8A8Unorm, B8G8R8A8Srgb, B10G10R10A2Unorm, + X8UintD24Unorm, } public static class FormatExtensions @@ -269,6 +270,7 @@ public static int GetScalarSize(this Format format) case Format.D16Unorm: return 2; case Format.S8UintD24Unorm: + case Format.X8UintD24Unorm: case Format.D32Float: case Format.D24UnormS8Uint: return 4; @@ -349,6 +351,7 @@ public static bool HasDepth(this Format format) case Format.D16Unorm: case Format.D24UnormS8Uint: case Format.S8UintD24Unorm: + case Format.X8UintD24Unorm: case Format.D32Float: case Format.D32FloatS8Uint: return true; @@ -633,6 +636,7 @@ public static bool IsDepthOrStencil(this Format format) case Format.D16Unorm: case Format.D24UnormS8Uint: case Format.S8UintD24Unorm: + case Format.X8UintD24Unorm: case Format.D32Float: case Format.D32FloatS8Uint: case Format.S8Uint: diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Types/ZetaFormat.cs b/src/Ryujinx.Graphics.Gpu/Engine/Types/ZetaFormat.cs index e2a044e72..88fbe88f9 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Types/ZetaFormat.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Types/ZetaFormat.cs @@ -8,13 +8,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Types /// enum ZetaFormat { - D32Float = 0xa, - D16Unorm = 0x13, - D24UnormS8Uint = 0x14, - D24Unorm = 0x15, - S8UintD24Unorm = 0x16, + Zf32 = 0xa, + Z16 = 0x13, + Z24S8 = 0x14, + X8Z24 = 0x15, + S8Z24 = 0x16, S8Uint = 0x17, - D32FloatS8Uint = 0x19, + Zf32X24S8 = 0x19, } static class ZetaFormatConverter @@ -29,14 +29,14 @@ public static FormatInfo Convert(this ZetaFormat format) return format switch { #pragma warning disable IDE0055 // Disable formatting - ZetaFormat.D32Float => new FormatInfo(Format.D32Float, 1, 1, 4, 1), - ZetaFormat.D16Unorm => new FormatInfo(Format.D16Unorm, 1, 1, 2, 1), - ZetaFormat.D24UnormS8Uint => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2), - ZetaFormat.D24Unorm => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 1), - ZetaFormat.S8UintD24Unorm => new FormatInfo(Format.S8UintD24Unorm, 1, 1, 4, 2), - ZetaFormat.S8Uint => new FormatInfo(Format.S8Uint, 1, 1, 1, 1), - ZetaFormat.D32FloatS8Uint => new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2), - _ => FormatInfo.Default, + ZetaFormat.Zf32 => new FormatInfo(Format.D32Float, 1, 1, 4, 1), + ZetaFormat.Z16 => new FormatInfo(Format.D16Unorm, 1, 1, 2, 1), + ZetaFormat.Z24S8 => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2), + ZetaFormat.X8Z24 => new FormatInfo(Format.X8UintD24Unorm, 1, 1, 4, 1), + ZetaFormat.S8Z24 => new FormatInfo(Format.S8UintD24Unorm, 1, 1, 4, 2), + ZetaFormat.S8Uint => new FormatInfo(Format.S8Uint, 1, 1, 1, 1), + ZetaFormat.Zf32X24S8 => new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2), + _ => FormatInfo.Default, #pragma warning restore IDE0055 }; } diff --git a/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs b/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs index 0af0725a2..e30b29ef1 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs @@ -185,6 +185,7 @@ private enum TextureFormat : uint G24R8RUintGUnormBUnormAUnorm = G24R8 | RUint | GUnorm | BUnorm | AUnorm, // 0x24a0e Z24S8RUintGUnormBUnormAUnorm = Z24S8 | RUint | GUnorm | BUnorm | AUnorm, // 0x24a29 Z24S8RUintGUnormBUintAUint = Z24S8 | RUint | GUnorm | BUint | AUint, // 0x48a29 + X8Z24RUnormGUintBUintAUint = X8Z24 | RUnorm | GUint | BUint | AUint, // 0x4912a S8Z24RUnormGUintBUintAUint = S8Z24 | RUnorm | GUint | BUint | AUint, // 0x4912b R32B24G8RFloatGUintBUnormAUnorm = R32B24G8 | RFloat | GUint | BUnorm | AUnorm, // 0x25385 Zf32X24S8RFloatGUintBUnormAUnorm = Zf32X24S8 | RFloat | GUint | BUnorm | AUnorm, // 0x253b0 @@ -410,6 +411,7 @@ private enum VertexAttributeFormat : uint { TextureFormat.G24R8RUintGUnormBUnormAUnorm, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) }, { TextureFormat.Z24S8RUintGUnormBUnormAUnorm, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) }, { TextureFormat.Z24S8RUintGUnormBUintAUint, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) }, + { TextureFormat.X8Z24RUnormGUintBUintAUint, new FormatInfo(Format.X8UintD24Unorm, 1, 1, 4, 2) }, { TextureFormat.S8Z24RUnormGUintBUintAUint, new FormatInfo(Format.S8UintD24Unorm, 1, 1, 4, 2) }, { TextureFormat.R32B24G8RFloatGUintBUnormAUnorm, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) }, { TextureFormat.Zf32X24S8RFloatGUintBUnormAUnorm, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) }, diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs index eef38948d..5b930fa47 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs @@ -242,7 +242,8 @@ public static TextureMatchQuality FormatMatches(TextureInfo lhs, TextureInfo rhs return TextureMatchQuality.FormatAlias; } else if ((lhs.FormatInfo.Format == Format.D24UnormS8Uint || - lhs.FormatInfo.Format == Format.S8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm) + lhs.FormatInfo.Format == Format.S8UintD24Unorm || + lhs.FormatInfo.Format == Format.X8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm) { return TextureMatchQuality.FormatAlias; } diff --git a/src/Ryujinx.Graphics.OpenGL/FormatTable.cs b/src/Ryujinx.Graphics.OpenGL/FormatTable.cs index c7e3e4e28..4cf4dc760 100644 --- a/src/Ryujinx.Graphics.OpenGL/FormatTable.cs +++ b/src/Ryujinx.Graphics.OpenGL/FormatTable.cs @@ -68,6 +68,7 @@ static FormatTable() Add(Format.S8Uint, new FormatInfo(1, false, false, All.StencilIndex8, PixelFormat.StencilIndex, PixelType.UnsignedByte)); Add(Format.D16Unorm, new FormatInfo(1, false, false, All.DepthComponent16, PixelFormat.DepthComponent, PixelType.UnsignedShort)); Add(Format.S8UintD24Unorm, new FormatInfo(1, false, false, All.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248)); + Add(Format.X8UintD24Unorm, new FormatInfo(1, false, false, All.DepthComponent24, PixelFormat.DepthComponent, PixelType.UnsignedInt)); Add(Format.D32Float, new FormatInfo(1, false, false, All.DepthComponent32f, PixelFormat.DepthComponent, PixelType.Float)); Add(Format.D24UnormS8Uint, new FormatInfo(1, false, false, All.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248)); Add(Format.D32FloatS8Uint, new FormatInfo(1, false, false, All.Depth32fStencil8, PixelFormat.DepthStencil, PixelType.Float32UnsignedInt248Rev)); @@ -224,5 +225,17 @@ public static SizedInternalFormat GetImageFormat(Format format) { return _tableImage[(int)format]; } + + public static bool IsPackedDepthStencil(Format format) + { + return format == Format.D24UnormS8Uint || + format == Format.D32FloatS8Uint || + format == Format.S8UintD24Unorm; + } + + public static bool IsDepthOnly(Format format) + { + return format == Format.D16Unorm || format == Format.D32Float || format == Format.X8UintD24Unorm; + } } } diff --git a/src/Ryujinx.Graphics.OpenGL/Framebuffer.cs b/src/Ryujinx.Graphics.OpenGL/Framebuffer.cs index 3b79c5d6b..394b8bc76 100644 --- a/src/Ryujinx.Graphics.OpenGL/Framebuffer.cs +++ b/src/Ryujinx.Graphics.OpenGL/Framebuffer.cs @@ -119,11 +119,11 @@ private static void SetDrawBuffersImpl(int colorsCount) private static FramebufferAttachment GetAttachment(Format format) { - if (IsPackedDepthStencilFormat(format)) + if (FormatTable.IsPackedDepthStencil(format)) { return FramebufferAttachment.DepthStencilAttachment; } - else if (IsDepthOnlyFormat(format)) + else if (FormatTable.IsDepthOnly(format)) { return FramebufferAttachment.DepthAttachment; } @@ -133,18 +133,6 @@ private static FramebufferAttachment GetAttachment(Format format) } } - private static bool IsPackedDepthStencilFormat(Format format) - { - return format == Format.D24UnormS8Uint || - format == Format.D32FloatS8Uint || - format == Format.S8UintD24Unorm; - } - - private static bool IsDepthOnlyFormat(Format format) - { - return format == Format.D16Unorm || format == Format.D32Float; - } - public int GetColorLayerCount(int index) { return _colors[index]?.Info.GetDepthOrLayers() ?? 0; diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs index 128f481f6..89bd5e4ff 100644 --- a/src/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs +++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs @@ -294,7 +294,7 @@ private static FramebufferAttachment AttachmentForFormat(Format format) { return FramebufferAttachment.DepthStencilAttachment; } - else if (IsDepthOnly(format)) + else if (FormatTable.IsDepthOnly(format)) { return FramebufferAttachment.DepthAttachment; } @@ -324,11 +324,11 @@ private static void Attach(FramebufferTarget target, Format format, int handle, private static ClearBufferMask GetMask(Format format) { - if (format == Format.D24UnormS8Uint || format == Format.D32FloatS8Uint || format == Format.S8UintD24Unorm) + if (FormatTable.IsPackedDepthStencil(format)) { return ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit; } - else if (IsDepthOnly(format)) + else if (FormatTable.IsDepthOnly(format)) { return ClearBufferMask.DepthBufferBit; } @@ -342,11 +342,6 @@ private static ClearBufferMask GetMask(Format format) } } - private static bool IsDepthOnly(Format format) - { - return format == Format.D16Unorm || format == Format.D32Float; - } - public TextureView BgraSwap(TextureView from) { TextureView to = (TextureView)_renderer.CreateTexture(from.Info); diff --git a/src/Ryujinx.Graphics.Vulkan/EnumConversion.cs b/src/Ryujinx.Graphics.Vulkan/EnumConversion.cs index e10027057..f9243bf83 100644 --- a/src/Ryujinx.Graphics.Vulkan/EnumConversion.cs +++ b/src/Ryujinx.Graphics.Vulkan/EnumConversion.cs @@ -376,7 +376,7 @@ public static ImageAspectFlags ConvertAspectFlags(this Format format) { return format switch { - Format.D16Unorm or Format.D32Float => ImageAspectFlags.DepthBit, + Format.D16Unorm or Format.D32Float or Format.X8UintD24Unorm => ImageAspectFlags.DepthBit, Format.S8Uint => ImageAspectFlags.StencilBit, Format.D24UnormS8Uint or Format.D32FloatS8Uint or @@ -389,7 +389,7 @@ public static ImageAspectFlags ConvertAspectFlags(this Format format, DepthStenc { return format switch { - Format.D16Unorm or Format.D32Float => ImageAspectFlags.DepthBit, + Format.D16Unorm or Format.D32Float or Format.X8UintD24Unorm => ImageAspectFlags.DepthBit, Format.S8Uint => ImageAspectFlags.StencilBit, Format.D24UnormS8Uint or Format.D32FloatS8Uint or diff --git a/src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs b/src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs index 7307a0ee0..9ea8cec4b 100644 --- a/src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs +++ b/src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs @@ -220,7 +220,7 @@ public VkFormat ConvertToVertexVkFormat(Format srcFormat) public static bool IsD24S8(Format format) { - return format == Format.D24UnormS8Uint || format == Format.S8UintD24Unorm; + return format == Format.D24UnormS8Uint || format == Format.S8UintD24Unorm || format == Format.X8UintD24Unorm; } private static bool IsRGB16IntFloat(Format format) diff --git a/src/Ryujinx.Graphics.Vulkan/FormatTable.cs b/src/Ryujinx.Graphics.Vulkan/FormatTable.cs index 596a665fc..98796d9bf 100644 --- a/src/Ryujinx.Graphics.Vulkan/FormatTable.cs +++ b/src/Ryujinx.Graphics.Vulkan/FormatTable.cs @@ -67,6 +67,7 @@ static FormatTable() Add(Format.S8Uint, VkFormat.S8Uint); Add(Format.D16Unorm, VkFormat.D16Unorm); Add(Format.S8UintD24Unorm, VkFormat.D24UnormS8Uint); + Add(Format.X8UintD24Unorm, VkFormat.X8D24UnormPack32); Add(Format.D32Float, VkFormat.D32Sfloat); Add(Format.D24UnormS8Uint, VkFormat.D24UnormS8Uint); Add(Format.D32FloatS8Uint, VkFormat.D32SfloatS8Uint); diff --git a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs index b763fa987..bba659215 100644 --- a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs +++ b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs @@ -154,9 +154,8 @@ public TextureStorage CreateAliasedColorForDepthStorageUnsafe(Format format) { Format.S8Uint => Format.R8Unorm, Format.D16Unorm => Format.R16Unorm, - Format.S8UintD24Unorm => Format.R8G8B8A8Unorm, + Format.D24UnormS8Uint or Format.S8UintD24Unorm or Format.X8UintD24Unorm => Format.R8G8B8A8Unorm, Format.D32Float => Format.R32Float, - Format.D24UnormS8Uint => Format.R8G8B8A8Unorm, Format.D32FloatS8Uint => Format.R32G32Float, _ => throw new ArgumentException($"\"{format}\" is not a supported depth or stencil format."), }; From 4218311e6aa2a6b134e56f4206f9ef87d863419e Mon Sep 17 00:00:00 2001 From: riperiperi Date: Sat, 17 Feb 2024 00:41:30 +0000 Subject: [PATCH 076/126] Vulkan: Use push descriptors for uniform bindings when possible (#6154) * Fix Push Descriptors * Use push descriptor templates * Use reserved bindings * Formatting * Disable when using MVK ("my heart will go on" starts playing as thousands of mac users shed a tear in unison) * Introduce limit on push descriptor binding number The bitmask used for updating push descriptors is ulong, so only 64 bindings can be tracked for now. * Address feedback * Fix logic for binding rejection Should only offset limit when reserved bindings are less than the requested one. * Workaround pascal and older nv bug * Add GPU number detection for nvidia * Only do workaround if it's valid to do so. --- src/Ryujinx.Graphics.Vulkan/Constants.cs | 1 + .../DescriptorSetTemplate.cs | 102 ++++++++++++++++- .../DescriptorSetTemplateUpdater.cs | 12 ++ .../DescriptorSetUpdater.cs | 89 +++++++++++++-- .../HardwareCapabilities.cs | 3 + src/Ryujinx.Graphics.Vulkan/PipelineBase.cs | 2 +- .../PipelineLayoutCacheEntry.cs | 40 +++++++ .../ShaderCollection.cs | 103 +++++++++++++++++- src/Ryujinx.Graphics.Vulkan/Vendor.cs | 3 + .../VulkanConfiguration.cs | 2 +- src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs | 54 ++++++++- 11 files changed, 395 insertions(+), 16 deletions(-) diff --git a/src/Ryujinx.Graphics.Vulkan/Constants.cs b/src/Ryujinx.Graphics.Vulkan/Constants.cs index cd6122112..20ce65818 100644 --- a/src/Ryujinx.Graphics.Vulkan/Constants.cs +++ b/src/Ryujinx.Graphics.Vulkan/Constants.cs @@ -16,6 +16,7 @@ static class Constants public const int MaxStorageBufferBindings = MaxStorageBuffersPerStage * MaxShaderStages; public const int MaxTextureBindings = MaxTexturesPerStage * MaxShaderStages; public const int MaxImageBindings = MaxImagesPerStage * MaxShaderStages; + public const int MaxPushDescriptorBinding = 64; public const ulong SparseBufferAlignment = 0x10000; } diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetTemplate.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetTemplate.cs index 0c0004b95..b9abd8fcd 100644 --- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetTemplate.cs +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetTemplate.cs @@ -1,19 +1,32 @@ using Ryujinx.Graphics.GAL; using Silk.NET.Vulkan; using System; +using System.Numerics; using System.Runtime.CompilerServices; namespace Ryujinx.Graphics.Vulkan { class DescriptorSetTemplate : IDisposable { + /// + /// Renderdoc seems to crash when doing a templated uniform update with count > 1 on a push descriptor. + /// When this is true, consecutive buffers are always updated individually. + /// + private const bool RenderdocPushCountBug = true; + private readonly VulkanRenderer _gd; private readonly Device _device; public readonly DescriptorUpdateTemplate Template; public readonly int Size; - public unsafe DescriptorSetTemplate(VulkanRenderer gd, Device device, ResourceBindingSegment[] segments, PipelineLayoutCacheEntry plce, PipelineBindPoint pbp, int setIndex) + public unsafe DescriptorSetTemplate( + VulkanRenderer gd, + Device device, + ResourceBindingSegment[] segments, + PipelineLayoutCacheEntry plce, + PipelineBindPoint pbp, + int setIndex) { _gd = gd; _device = device; @@ -137,6 +150,93 @@ public unsafe DescriptorSetTemplate(VulkanRenderer gd, Device device, ResourceBi Template = result; } + public unsafe DescriptorSetTemplate( + VulkanRenderer gd, + Device device, + ResourceDescriptorCollection descriptors, + long updateMask, + PipelineLayoutCacheEntry plce, + PipelineBindPoint pbp, + int setIndex) + { + _gd = gd; + _device = device; + + // Create a template from the set usages. Assumes the descriptor set is updated in segment order then binding order. + int segmentCount = BitOperations.PopCount((ulong)updateMask); + + DescriptorUpdateTemplateEntry* entries = stackalloc DescriptorUpdateTemplateEntry[segmentCount]; + int entry = 0; + nuint structureOffset = 0; + + void AddBinding(int binding, int count) + { + entries[entry++] = new DescriptorUpdateTemplateEntry() + { + DescriptorType = DescriptorType.UniformBuffer, + DstBinding = (uint)binding, + DescriptorCount = (uint)count, + Offset = structureOffset, + Stride = (nuint)Unsafe.SizeOf() + }; + + structureOffset += (nuint)(Unsafe.SizeOf() * count); + } + + int startBinding = 0; + int bindingCount = 0; + + foreach (ResourceDescriptor descriptor in descriptors.Descriptors) + { + for (int i = 0; i < descriptor.Count; i++) + { + int binding = descriptor.Binding + i; + + if ((updateMask & (1L << binding)) != 0) + { + if (bindingCount > 0 && (RenderdocPushCountBug || startBinding + bindingCount != binding)) + { + AddBinding(startBinding, bindingCount); + + bindingCount = 0; + } + + if (bindingCount == 0) + { + startBinding = binding; + } + + bindingCount++; + } + } + } + + if (bindingCount > 0) + { + AddBinding(startBinding, bindingCount); + } + + Size = (int)structureOffset; + + var info = new DescriptorUpdateTemplateCreateInfo() + { + SType = StructureType.DescriptorUpdateTemplateCreateInfo, + DescriptorUpdateEntryCount = (uint)entry, + PDescriptorUpdateEntries = entries, + + TemplateType = DescriptorUpdateTemplateType.PushDescriptorsKhr, + DescriptorSetLayout = plce.DescriptorSetLayouts[setIndex], + PipelineBindPoint = pbp, + PipelineLayout = plce.PipelineLayout, + Set = (uint)setIndex, + }; + + DescriptorUpdateTemplate result; + gd.Api.CreateDescriptorUpdateTemplate(device, &info, null, &result).ThrowOnError(); + + Template = result; + } + public unsafe void Dispose() { _gd.Api.DestroyDescriptorUpdateTemplate(_device, Template, null); diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetTemplateUpdater.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetTemplateUpdater.cs index 1eb9dce75..88db7e769 100644 --- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetTemplateUpdater.cs +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetTemplateUpdater.cs @@ -52,11 +52,23 @@ public DescriptorSetTemplateWriter Begin(DescriptorSetTemplate template) return new DescriptorSetTemplateWriter(new Span(_data.Pointer, template.Size)); } + public DescriptorSetTemplateWriter Begin(int maxSize) + { + EnsureSize(maxSize); + + return new DescriptorSetTemplateWriter(new Span(_data.Pointer, maxSize)); + } + public void Commit(VulkanRenderer gd, Device device, DescriptorSet set) { gd.Api.UpdateDescriptorSetWithTemplate(device, set, _activeTemplate.Template, _data.Pointer); } + public void CommitPushDescriptor(VulkanRenderer gd, CommandBufferScoped cbs, DescriptorSetTemplate template, PipelineLayout layout) + { + gd.PushDescriptorApi.CmdPushDescriptorSetWithTemplate(cbs.CommandBuffer, template.Template, layout, 0, _data.Pointer); + } + public void Dispose() { _data?.Dispose(); diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs index 6615d8ce0..d40b201da 100644 --- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs @@ -4,6 +4,7 @@ using Silk.NET.Vulkan; using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using CompareOp = Ryujinx.Graphics.GAL.CompareOp; using Format = Ryujinx.Graphics.GAL.Format; using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo; @@ -61,6 +62,8 @@ public BufferRef(Auto buffer, ref BufferRange range) private BitMapStruct> _storageSet; private BitMapStruct> _uniformMirrored; private BitMapStruct> _storageMirrored; + private readonly int[] _uniformSetPd; + private int _pdSequence = 1; private bool _updateDescriptorCacheCbIndex; @@ -106,6 +109,8 @@ public DescriptorSetUpdater(VulkanRenderer gd, Device device, PipelineBase pipel _bufferTextures = new BufferView[Constants.MaxTexturesPerStage]; _bufferImages = new BufferView[Constants.MaxImagesPerStage]; + _uniformSetPd = new int[Constants.MaxUniformBufferBindings]; + var initialImageInfo = new DescriptorImageInfo { ImageLayout = ImageLayout.General, @@ -193,6 +198,7 @@ internal void Rebind(Auto buffer, int offset, int size) if (BindingOverlaps(ref info, bindingOffset, offset, size)) { _uniformSet.Clear(binding); + _uniformSetPd[binding] = 0; SignalDirty(DirtyFlags.Uniform); } } @@ -223,8 +229,30 @@ internal void Rebind(Auto buffer, int offset, int size) }); } - public void SetProgram(ShaderCollection program) + public void AdvancePdSequence() + { + if (++_pdSequence == 0) + { + _pdSequence = 1; + } + } + + public void SetProgram(CommandBufferScoped cbs, ShaderCollection program, bool isBound) { + if (!program.HasSameLayout(_program)) + { + // When the pipeline layout changes, push descriptor bindings are invalidated. + + AdvancePdSequence(); + + if (_gd.IsNvidiaPreTuring && !program.UsePushDescriptors && _program?.UsePushDescriptors == true && isBound) + { + // On older nvidia GPUs, we need to clear out the active push descriptor bindings when switching + // to normal descriptors. Keeping them bound can prevent buffers from binding properly in future. + ClearAndBindUniformBufferPd(cbs); + } + } + _program = program; _updateDescriptorCacheCbIndex = true; _dirty = DirtyFlags.All; @@ -402,6 +430,7 @@ public void SetUniformBuffers(CommandBuffer commandBuffer, ReadOnlySpan()); + foreach (ResourceBindingSegment segment in bindingSegments) { int binding = segment.Binding; int count = segment.Count; - bool doUpdate = false; + ReadOnlySpan uniformBuffers = _uniformBuffers; for (int i = 0; i < count; i++) { @@ -688,17 +721,58 @@ private void UpdateAndBindUniformBufferPd(CommandBufferScoped cbs, PipelineBindP if (_uniformSet.Set(index)) { ref BufferRef buffer = ref _uniformBufferRefs[index]; - UpdateBuffer(cbs, ref _uniformBuffers[index], ref buffer, dummyBuffer, true); - doUpdate = true; + + bool mirrored = UpdateBuffer(cbs, ref _uniformBuffers[index], ref buffer, dummyBuffer, true); + + _uniformMirrored.Set(index, mirrored); + } + + if (_uniformSetPd[index] != sequence) + { + // Need to set this push descriptor (even if the buffer binding has not changed) + + _uniformSetPd[index] = sequence; + updatedBindings |= 1L << index; + + writer.Push(MemoryMarshal.CreateReadOnlySpan(ref _uniformBuffers[index], 1)); } } + } + + if (updatedBindings > 0) + { + DescriptorSetTemplate template = _program.GetPushDescriptorTemplate(updatedBindings); + _templateUpdater.CommitPushDescriptor(_gd, cbs, template, _program.PipelineLayout); + } + } - if (doUpdate) + private void ClearAndBindUniformBufferPd(CommandBufferScoped cbs) + { + var bindingSegments = _program.BindingSegments[PipelineBase.UniformSetIndex]; + + long updatedBindings = 0; + DescriptorSetTemplateWriter writer = _templateUpdater.Begin(32 * Unsafe.SizeOf()); + + foreach (ResourceBindingSegment segment in bindingSegments) + { + int binding = segment.Binding; + int count = segment.Count; + + for (int i = 0; i < count; i++) { - ReadOnlySpan uniformBuffers = _uniformBuffers; - UpdateBuffers(cbs, pbp, binding, uniformBuffers.Slice(binding, count), DescriptorType.UniformBuffer); + int index = binding + i; + updatedBindings |= 1L << index; + + var bufferInfo = new DescriptorBufferInfo(); + writer.Push(MemoryMarshal.CreateReadOnlySpan(ref bufferInfo, 1)); } } + + if (updatedBindings > 0) + { + DescriptorSetTemplate template = _program.GetPushDescriptorTemplate(updatedBindings); + _templateUpdater.CommitPushDescriptor(_gd, cbs, template, _program.PipelineLayout); + } } private void Initialize(CommandBufferScoped cbs, int setIndex, DescriptorSetCollection dsc) @@ -724,6 +798,7 @@ public void SignalCommandBufferChange() _uniformSet.Clear(); _storageSet.Clear(); + AdvancePdSequence(); } private static void SwapBuffer(BufferRef[] list, Auto from, Auto to) diff --git a/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs b/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs index 98c777eed..b6694bcb3 100644 --- a/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs +++ b/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs @@ -34,6 +34,7 @@ readonly struct HardwareCapabilities public readonly bool SupportsMultiView; public readonly bool SupportsNullDescriptors; public readonly bool SupportsPushDescriptors; + public readonly uint MaxPushDescriptors; public readonly bool SupportsPrimitiveTopologyListRestart; public readonly bool SupportsPrimitiveTopologyPatchListRestart; public readonly bool SupportsTransformFeedback; @@ -71,6 +72,7 @@ public HardwareCapabilities( bool supportsMultiView, bool supportsNullDescriptors, bool supportsPushDescriptors, + uint maxPushDescriptors, bool supportsPrimitiveTopologyListRestart, bool supportsPrimitiveTopologyPatchListRestart, bool supportsTransformFeedback, @@ -107,6 +109,7 @@ public HardwareCapabilities( SupportsMultiView = supportsMultiView; SupportsNullDescriptors = supportsNullDescriptors; SupportsPushDescriptors = supportsPushDescriptors; + MaxPushDescriptors = maxPushDescriptors; SupportsPrimitiveTopologyListRestart = supportsPrimitiveTopologyListRestart; SupportsPrimitiveTopologyPatchListRestart = supportsPrimitiveTopologyPatchListRestart; SupportsTransformFeedback = supportsTransformFeedback; diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs index 3aef1317a..3b3f59259 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -976,7 +976,7 @@ public void SetProgram(IProgram program) _program = internalProgram; - _descriptorSetUpdater.SetProgram(internalProgram); + _descriptorSetUpdater.SetProgram(Cbs, internalProgram, _currentPipelineHandle != 0); _newState.PipelineLayout = internalProgram.PipelineLayout; _newState.StagesCount = (uint)stages.Length; diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs index 2840dda0f..f388d9e88 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs @@ -31,6 +31,11 @@ class PipelineLayoutCacheEntry private int _dsLastCbIndex; private int _dsLastSubmissionCount; + private readonly Dictionary _pdTemplates; + private readonly ResourceDescriptorCollection _pdDescriptors; + private long _lastPdUsage; + private DescriptorSetTemplate _lastPdTemplate; + private PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, int setsCount) { _gd = gd; @@ -72,6 +77,12 @@ public PipelineLayoutCacheEntry( _consumedDescriptorsPerSet[setIndex] = count; } + + if (usePushDescriptors) + { + _pdDescriptors = setDescriptors[0]; + _pdTemplates = new(); + } } public void UpdateCommandBufferIndex(int commandBufferIndex) @@ -143,10 +154,39 @@ private static Span GetDescriptorPoolSizes(Span sets = usePushDescriptors ? + BuildPushDescriptorSets(gd, resourceLayout.Sets) : resourceLayout.Sets; + + _plce = gd.PipelineLayoutCache.GetOrCreate(gd, device, sets, usePushDescriptors); HasMinimalLayout = isMinimal; UsePushDescriptors = usePushDescriptors; Stages = stages; - ClearSegments = BuildClearSegments(resourceLayout.Sets); + ClearSegments = BuildClearSegments(sets); BindingSegments = BuildBindingSegments(resourceLayout.SetUsages); - Templates = BuildTemplates(); + Templates = BuildTemplates(usePushDescriptors); _compileTask = Task.CompletedTask; _firstBackgroundUse = false; @@ -139,6 +146,76 @@ public ShaderCollection( _firstBackgroundUse = !fromCache; } + private static bool CanUsePushDescriptors(VulkanRenderer gd, ResourceLayout layout, bool isCompute) + { + // If binding 3 is immediately used, use an alternate set of reserved bindings. + ReadOnlyCollection uniformUsage = layout.SetUsages[0].Usages; + bool hasBinding3 = uniformUsage.Any(x => x.Binding == 3); + int[] reserved = isCompute ? Array.Empty() : gd.GetPushDescriptorReservedBindings(hasBinding3); + + // Can't use any of the reserved usages. + for (int i = 0; i < uniformUsage.Count; i++) + { + var binding = uniformUsage[i].Binding; + + if (reserved.Contains(binding) || + binding >= Constants.MaxPushDescriptorBinding || + binding >= gd.Capabilities.MaxPushDescriptors + reserved.Count(id => id < binding)) + { + return false; + } + } + + return true; + } + + private static ReadOnlyCollection BuildPushDescriptorSets( + VulkanRenderer gd, + ReadOnlyCollection sets) + { + // The reserved bindings were selected when determining if push descriptors could be used. + int[] reserved = gd.GetPushDescriptorReservedBindings(false); + + var result = new ResourceDescriptorCollection[sets.Count]; + + for (int i = 0; i < sets.Count; i++) + { + if (i == 0) + { + // Push descriptors apply here. Remove reserved bindings. + ResourceDescriptorCollection original = sets[i]; + + var pdUniforms = new ResourceDescriptor[original.Descriptors.Count]; + int j = 0; + + foreach (ResourceDescriptor descriptor in original.Descriptors) + { + if (reserved.Contains(descriptor.Binding)) + { + // If the binding is reserved, set its descriptor count to 0. + pdUniforms[j++] = new ResourceDescriptor( + descriptor.Binding, + 0, + descriptor.Type, + descriptor.Stages); + } + else + { + pdUniforms[j++] = descriptor; + } + } + + result[i] = new ResourceDescriptorCollection(new(pdUniforms)); + } + else + { + result[i] = sets[i]; + } + } + + return new(result); + } + private static ResourceBindingSegment[][] BuildClearSegments(ReadOnlyCollection sets) { ResourceBindingSegment[][] segments = new ResourceBindingSegment[sets.Count][]; @@ -243,12 +320,18 @@ private static ResourceBindingSegment[][] BuildBindingSegments(ReadOnlyCollectio return segments; } - private DescriptorSetTemplate[] BuildTemplates() + private DescriptorSetTemplate[] BuildTemplates(bool usePushDescriptors) { var templates = new DescriptorSetTemplate[BindingSegments.Length]; for (int setIndex = 0; setIndex < BindingSegments.Length; setIndex++) { + if (usePushDescriptors && setIndex == 0) + { + // Push descriptors get updated using templates owned by the pipeline layout. + continue; + } + ResourceBindingSegment[] segments = BindingSegments[setIndex]; if (segments != null && segments.Length > 0) @@ -433,6 +516,11 @@ public byte[] GetBinary() return null; } + public DescriptorSetTemplate GetPushDescriptorTemplate(long updateMask) + { + return _plce.GetPushDescriptorTemplate(IsCompute ? PipelineBindPoint.Compute : PipelineBindPoint.Graphics, updateMask); + } + public void AddComputePipeline(ref SpecData key, Auto pipeline) { (_computePipelineCache ??= new()).Add(ref key, pipeline); @@ -493,6 +581,11 @@ public Auto GetNewDescriptorSetCollection(int setIndex, return _plce.GetNewDescriptorSetCollection(setIndex, out isNew); } + public bool HasSameLayout(ShaderCollection other) + { + return other != null && _plce == other._plce; + } + protected virtual void Dispose(bool disposing) { if (disposing) diff --git a/src/Ryujinx.Graphics.Vulkan/Vendor.cs b/src/Ryujinx.Graphics.Vulkan/Vendor.cs index 2d2f17b25..ff841dec9 100644 --- a/src/Ryujinx.Graphics.Vulkan/Vendor.cs +++ b/src/Ryujinx.Graphics.Vulkan/Vendor.cs @@ -20,6 +20,9 @@ static partial class VendorUtils [GeneratedRegex("Radeon (((HD|R(5|7|9|X)) )?((M?[2-6]\\d{2}(\\D|$))|([7-8]\\d{3}(\\D|$))|Fury|Nano))|(Pro Duo)")] public static partial Regex AmdGcnRegex(); + [GeneratedRegex("NVIDIA GeForce (R|G)?TX? (\\d{3}\\d?)M?")] + public static partial Regex NvidiaConsumerClassRegex(); + public static Vendor FromId(uint id) { return id switch diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanConfiguration.cs b/src/Ryujinx.Graphics.Vulkan/VulkanConfiguration.cs index a1fdc4aed..596c0e176 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanConfiguration.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanConfiguration.cs @@ -4,7 +4,7 @@ static class VulkanConfiguration { public const bool UseFastBufferUpdates = true; public const bool UseUnsafeBlit = true; - public const bool UsePushDescriptors = false; + public const bool UsePushDescriptors = true; public const bool ForceD24S8Unsupported = false; public const bool ForceRGB16IntFloatUnsupported = false; diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index 48f05fa19..6aa46b79a 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -76,10 +76,15 @@ public sealed class VulkanRenderer : IRenderer private readonly Func _getRequiredExtensions; private readonly string _preferredGpuId; + private int[] _pdReservedBindings; + private readonly static int[] _pdReservedBindingsNvn = { 3, 18, 21, 36, 30 }; + private readonly static int[] _pdReservedBindingsOgl = { 17, 18, 34, 35, 36 }; + internal Vendor Vendor { get; private set; } internal bool IsAmdWindows { get; private set; } internal bool IsIntelWindows { get; private set; } internal bool IsAmdGcn { get; private set; } + internal bool IsNvidiaPreTuring { get; private set; } internal bool IsMoltenVk { get; private set; } internal bool IsTBDR { get; private set; } internal bool IsSharedMemory { get; private set; } @@ -191,6 +196,19 @@ private unsafe void LoadFeatures(uint maxQueueCount, uint queueFamilyIndex) SType = StructureType.PhysicalDevicePortabilitySubsetPropertiesKhr, }; + bool supportsPushDescriptors = _physicalDevice.IsDeviceExtensionPresent(KhrPushDescriptor.ExtensionName); + + PhysicalDevicePushDescriptorPropertiesKHR propertiesPushDescriptor = new PhysicalDevicePushDescriptorPropertiesKHR() + { + SType = StructureType.PhysicalDevicePushDescriptorPropertiesKhr + }; + + if (supportsPushDescriptors) + { + propertiesPushDescriptor.PNext = properties2.PNext; + properties2.PNext = &propertiesPushDescriptor; + } + PhysicalDeviceFeatures2 features2 = new() { SType = StructureType.PhysicalDeviceFeatures2, @@ -320,7 +338,8 @@ private unsafe void LoadFeatures(uint maxQueueCount, uint queueFamilyIndex) _physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName), features2.Features.MultiViewport && !(IsMoltenVk && Vendor == Vendor.Amd), // Workaround for AMD on MoltenVK issue featuresRobustness2.NullDescriptor || IsMoltenVk, - _physicalDevice.IsDeviceExtensionPresent(KhrPushDescriptor.ExtensionName), + supportsPushDescriptors && !IsMoltenVk, + propertiesPushDescriptor.MaxPushDescriptors, featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart, featuresPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart, supportsTransformFeedback, @@ -400,6 +419,25 @@ private void SetupContext(GraphicsDebugLevel logLevel) _initialized = true; } + internal int[] GetPushDescriptorReservedBindings(bool isOgl) + { + // The first call of this method determines what push descriptor layout is used for all shaders on this renderer. + // This is chosen to minimize shaders that can't fit their uniforms on the device's max number of push descriptors. + if (_pdReservedBindings == null) + { + if (Capabilities.MaxPushDescriptors <= Constants.MaxUniformBuffersPerStage * 2) + { + _pdReservedBindings = isOgl ? _pdReservedBindingsOgl : _pdReservedBindingsNvn; + } + else + { + _pdReservedBindings = Array.Empty(); + } + } + + return _pdReservedBindings; + } + public BufferHandle CreateBuffer(int size, BufferAccess access) { return BufferManager.CreateWithHandle(this, size, access.HasFlag(BufferAccess.SparseCompatible), access.Convert(), default, access == BufferAccess.Stream); @@ -716,6 +754,20 @@ private unsafe void PrintGpuInformation() IsAmdGcn = !IsMoltenVk && Vendor == Vendor.Amd && VendorUtils.AmdGcnRegex().IsMatch(GpuRenderer); + if (Vendor == Vendor.Nvidia) + { + var match = VendorUtils.NvidiaConsumerClassRegex().Match(GpuRenderer); + + if (match != null && int.TryParse(match.Groups[2].Value, out int gpuNumber)) + { + IsNvidiaPreTuring = gpuNumber < 2000; + } + else if (GpuDriver.Contains("TITAN") && !GpuDriver.Contains("RTX")) + { + IsNvidiaPreTuring = true; + } + } + Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})"); } From 31ed061beae779b0a750e1344c76a75af8275f91 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Sat, 17 Feb 2024 03:21:37 +0000 Subject: [PATCH 077/126] Vulkan: Improve texture barrier usage, timing and batching (#6240) * WIP barrier batch * Add store op to image usage barrier * Dispose the barrier batch * Fix encoding? * Handle read and write on the load op barrier. Load op consumes read accesses but does not add one, as the only other operation that can read is another load. * Simplify null check * Insert barriers on program change in case stale bindings are reintroduced * Not sure how I messed this one up * Improve location of bindings barrier update This is also important for emergency deferred clear * Update src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs Co-authored-by: Mary Guillemard --------- Co-authored-by: Mary Guillemard --- src/Ryujinx.Graphics.GAL/IPipeline.cs | 2 +- .../Commands/SetImageCommand.cs | 7 +- .../Multithreading/ThreadedPipeline.cs | 4 +- .../Image/TextureBindingsManager.cs | 4 +- .../Memory/BufferManager.cs | 2 +- src/Ryujinx.Graphics.OpenGL/Pipeline.cs | 2 +- src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs | 225 ++++++++++++++++++ .../DescriptorSetUpdater.cs | 102 ++++++-- .../Effects/FsrScalingFilter.cs | 2 +- .../Effects/FxaaPostProcessingEffect.cs | 2 +- .../Effects/SmaaPostProcessingEffect.cs | 6 +- .../FramebufferParams.cs | 48 +--- src/Ryujinx.Graphics.Vulkan/HelperShader.cs | 4 +- src/Ryujinx.Graphics.Vulkan/PipelineBase.cs | 37 ++- src/Ryujinx.Graphics.Vulkan/PipelineFull.cs | 1 + src/Ryujinx.Graphics.Vulkan/TextureStorage.cs | 120 ++++------ src/Ryujinx.Graphics.Vulkan/TextureView.cs | 33 ++- src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs | 5 + 18 files changed, 436 insertions(+), 170 deletions(-) create mode 100644 src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs diff --git a/src/Ryujinx.Graphics.GAL/IPipeline.cs b/src/Ryujinx.Graphics.GAL/IPipeline.cs index f5978cefa..3ba084aa5 100644 --- a/src/Ryujinx.Graphics.GAL/IPipeline.cs +++ b/src/Ryujinx.Graphics.GAL/IPipeline.cs @@ -58,7 +58,7 @@ void DrawIndexed( void SetIndexBuffer(BufferRange buffer, IndexType type); - void SetImage(int binding, ITexture texture, Format imageFormat); + void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat); void SetLineParameters(float width, bool smooth); diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageCommand.cs index b4e966ca8..243480a81 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageCommand.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageCommand.cs @@ -1,17 +1,20 @@ using Ryujinx.Graphics.GAL.Multithreading.Model; using Ryujinx.Graphics.GAL.Multithreading.Resources; +using Ryujinx.Graphics.Shader; namespace Ryujinx.Graphics.GAL.Multithreading.Commands { struct SetImageCommand : IGALCommand, IGALCommand { public readonly CommandType CommandType => CommandType.SetImage; + private ShaderStage _stage; private int _binding; private TableRef _texture; private Format _imageFormat; - public void Set(int binding, TableRef texture, Format imageFormat) + public void Set(ShaderStage stage, int binding, TableRef texture, Format imageFormat) { + _stage = stage; _binding = binding; _texture = texture; _imageFormat = imageFormat; @@ -19,7 +22,7 @@ public void Set(int binding, TableRef texture, Format imageFormat) public static void Run(ref SetImageCommand command, ThreadedRenderer threaded, IRenderer renderer) { - renderer.Pipeline.SetImage(command._binding, command._texture.GetAs(threaded)?.Base, command._imageFormat); + renderer.Pipeline.SetImage(command._stage, command._binding, command._texture.GetAs(threaded)?.Base, command._imageFormat); } } } diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs index f40d9896c..ad50bddf4 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs @@ -177,9 +177,9 @@ public void SetFrontFace(FrontFace frontFace) _renderer.QueueCommand(); } - public void SetImage(int binding, ITexture texture, Format imageFormat) + public void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat) { - _renderer.New().Set(binding, Ref(texture), imageFormat); + _renderer.New().Set(stage, binding, Ref(texture), imageFormat); _renderer.QueueCommand(); } diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs index 963bd947d..ef5d0deaa 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs @@ -634,7 +634,7 @@ private bool CommitImageBindings(TexturePool pool, ShaderStage stage, int stageI state.Texture = hostTextureRebind; state.ImageFormat = format; - _context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTextureRebind, format); + _context.Renderer.Pipeline.SetImage(stage, bindingInfo.Binding, hostTextureRebind, format); } continue; @@ -692,7 +692,7 @@ private bool CommitImageBindings(TexturePool pool, ShaderStage stage, int stageI state.ImageFormat = format; - _context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format); + _context.Renderer.Pipeline.SetImage(stage, bindingInfo.Binding, hostTexture, format); } state.CachedTexture = texture; diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs index c65602b5b..1f02b9d7f 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs @@ -484,7 +484,7 @@ private void CommitBufferTextureBindings() if (binding.IsImage) { - _context.Renderer.Pipeline.SetImage(binding.BindingInfo.Binding, binding.Texture, binding.Format); + _context.Renderer.Pipeline.SetImage(binding.Stage, binding.BindingInfo.Binding, binding.Texture, binding.Format); } else { diff --git a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs index 923c85d7e..e863c696b 100644 --- a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs +++ b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs @@ -935,7 +935,7 @@ public void SetFrontFace(FrontFace frontFace) SetFrontFace(_frontFace = frontFace.Convert()); } - public void SetImage(int binding, ITexture texture, Format imageFormat) + public void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat) { if ((uint)binding < SavedImages) { diff --git a/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs b/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs new file mode 100644 index 000000000..3b44c98c1 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs @@ -0,0 +1,225 @@ +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Vulkan +{ + internal class BarrierBatch : IDisposable + { + private const int MaxBarriersPerCall = 16; + + private readonly VulkanRenderer _gd; + + private readonly NativeArray _memoryBarrierBatch = new(MaxBarriersPerCall); + private readonly NativeArray _bufferBarrierBatch = new(MaxBarriersPerCall); + private readonly NativeArray _imageBarrierBatch = new(MaxBarriersPerCall); + + private readonly List> _memoryBarriers = new(); + private readonly List> _bufferBarriers = new(); + private readonly List> _imageBarriers = new(); + private int _queuedBarrierCount; + + public BarrierBatch(VulkanRenderer gd) + { + _gd = gd; + } + + private readonly record struct StageFlags : IEquatable + { + public readonly PipelineStageFlags Source; + public readonly PipelineStageFlags Dest; + + public StageFlags(PipelineStageFlags source, PipelineStageFlags dest) + { + Source = source; + Dest = dest; + } + } + + private readonly struct BarrierWithStageFlags where T : unmanaged + { + public readonly StageFlags Flags; + public readonly T Barrier; + + public BarrierWithStageFlags(StageFlags flags, T barrier) + { + Flags = flags; + Barrier = barrier; + } + + public BarrierWithStageFlags(PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags, T barrier) + { + Flags = new StageFlags(srcStageFlags, dstStageFlags); + Barrier = barrier; + } + } + + private void QueueBarrier(List> list, T barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags) where T : unmanaged + { + list.Add(new BarrierWithStageFlags(srcStageFlags, dstStageFlags, barrier)); + _queuedBarrierCount++; + } + + public void QueueBarrier(MemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags) + { + QueueBarrier(_memoryBarriers, barrier, srcStageFlags, dstStageFlags); + } + + public void QueueBarrier(BufferMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags) + { + QueueBarrier(_bufferBarriers, barrier, srcStageFlags, dstStageFlags); + } + + public void QueueBarrier(ImageMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags) + { + QueueBarrier(_imageBarriers, barrier, srcStageFlags, dstStageFlags); + } + + public unsafe void Flush(CommandBuffer cb, bool insideRenderPass, Action endRenderPass) + { + while (_queuedBarrierCount > 0) + { + int memoryCount = 0; + int bufferCount = 0; + int imageCount = 0; + + bool hasBarrier = false; + StageFlags flags = default; + + static void AddBarriers( + Span target, + ref int queuedBarrierCount, + ref bool hasBarrier, + ref StageFlags flags, + ref int count, + List> list) where T : unmanaged + { + int firstMatch = -1; + + for (int i = 0; i < list.Count; i++) + { + BarrierWithStageFlags barrier = list[i]; + + if (!hasBarrier) + { + flags = barrier.Flags; + hasBarrier = true; + + target[count++] = barrier.Barrier; + queuedBarrierCount--; + firstMatch = i; + + if (count >= target.Length) + { + break; + } + } + else + { + if (flags.Equals(barrier.Flags)) + { + target[count++] = barrier.Barrier; + queuedBarrierCount--; + + if (firstMatch == -1) + { + firstMatch = i; + } + + if (count >= target.Length) + { + break; + } + } + else + { + // Delete consumed barriers from the first match to the current non-match. + if (firstMatch != -1) + { + int deleteCount = i - firstMatch; + list.RemoveRange(firstMatch, deleteCount); + i -= deleteCount; + + firstMatch = -1; + } + } + } + } + + if (firstMatch == 0) + { + list.Clear(); + } + else if (firstMatch != -1) + { + int deleteCount = list.Count - firstMatch; + + list.RemoveRange(firstMatch, deleteCount); + } + } + + if (insideRenderPass) + { + // 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. + + if (!_gd.IsMoltenVk) + { + foreach (var barrier in _imageBarriers) + { + _memoryBarriers.Add(new BarrierWithStageFlags( + barrier.Flags, + new MemoryBarrier() + { + SType = StructureType.MemoryBarrier, + SrcAccessMask = barrier.Barrier.SrcAccessMask, + DstAccessMask = barrier.Barrier.DstAccessMask + })); + } + + _imageBarriers.Clear(); + } + } + + 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); + + if (hasBarrier) + { + PipelineStageFlags srcStageFlags = flags.Source; + + if (insideRenderPass) + { + // Inside a render pass, barrier stages can only be from rasterization. + srcStageFlags &= ~PipelineStageFlags.ComputeShaderBit; + } + + _gd.Api.CmdPipelineBarrier( + cb, + srcStageFlags, + flags.Dest, + 0, + (uint)memoryCount, + _memoryBarrierBatch.Pointer, + (uint)bufferCount, + _bufferBarrierBatch.Pointer, + (uint)imageCount, + _imageBarrierBatch.Pointer); + } + } + } + + public void Dispose() + { + _memoryBarrierBatch.Dispose(); + _bufferBarrierBatch.Dispose(); + _imageBarrierBatch.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs index d40b201da..946e3bc14 100644 --- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs @@ -35,6 +35,36 @@ public BufferRef(Auto buffer, ref BufferRange range) } } + private record struct TextureRef + { + public ShaderStage Stage; + public TextureStorage Storage; + public Auto View; + public Auto Sampler; + + public TextureRef(ShaderStage stage, TextureStorage storage, Auto view, Auto sampler) + { + Stage = stage; + Storage = storage; + View = view; + Sampler = sampler; + } + } + + private record struct ImageRef + { + public ShaderStage Stage; + public TextureStorage Storage; + public Auto View; + + public ImageRef(ShaderStage stage, TextureStorage storage, Auto view) + { + Stage = stage; + Storage = storage; + View = view; + } + } + private readonly VulkanRenderer _gd; private readonly Device _device; private readonly PipelineBase _pipeline; @@ -42,9 +72,8 @@ public BufferRef(Auto buffer, ref BufferRange range) private readonly BufferRef[] _uniformBufferRefs; private readonly BufferRef[] _storageBufferRefs; - private readonly Auto[] _textureRefs; - private readonly Auto[] _samplerRefs; - private readonly Auto[] _imageRefs; + private readonly TextureRef[] _textureRefs; + private readonly ImageRef[] _imageRefs; private readonly TextureBuffer[] _bufferTextureRefs; private readonly TextureBuffer[] _bufferImageRefs; private readonly Format[] _bufferImageFormats; @@ -95,9 +124,8 @@ public DescriptorSetUpdater(VulkanRenderer gd, Device device, PipelineBase pipel _uniformBufferRefs = new BufferRef[Constants.MaxUniformBufferBindings]; _storageBufferRefs = new BufferRef[Constants.MaxStorageBufferBindings]; - _textureRefs = new Auto[Constants.MaxTextureBindings * 2]; - _samplerRefs = new Auto[Constants.MaxTextureBindings * 2]; - _imageRefs = new Auto[Constants.MaxImageBindings * 2]; + _textureRefs = new TextureRef[Constants.MaxTextureBindings * 2]; + _imageRefs = new ImageRef[Constants.MaxImageBindings * 2]; _bufferTextureRefs = new TextureBuffer[Constants.MaxTextureBindings * 2]; _bufferImageRefs = new TextureBuffer[Constants.MaxImageBindings * 2]; _bufferImageFormats = new Format[Constants.MaxImageBindings * 2]; @@ -229,6 +257,33 @@ internal void Rebind(Auto buffer, int offset, int size) }); } + public void InsertBindingBarriers(CommandBufferScoped cbs) + { + foreach (ResourceBindingSegment segment in _program.BindingSegments[PipelineBase.TextureSetIndex]) + { + if (segment.Type == ResourceType.TextureAndSampler) + { + for (int i = 0; i < segment.Count; i++) + { + ref var texture = ref _textureRefs[segment.Binding + i]; + texture.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, texture.Stage.ConvertToPipelineStageFlags()); + } + } + } + + foreach (ResourceBindingSegment segment in _program.BindingSegments[PipelineBase.ImageSetIndex]) + { + if (segment.Type == ResourceType.Image) + { + for (int i = 0; i < segment.Count; i++) + { + ref var image = ref _imageRefs[segment.Binding + i]; + image.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, image.Stage.ConvertToPipelineStageFlags()); + } + } + } + } + public void AdvancePdSequence() { if (++_pdSequence == 0) @@ -258,7 +313,12 @@ public void SetProgram(CommandBufferScoped cbs, ShaderCollection program, bool i _dirty = DirtyFlags.All; } - public void SetImage(int binding, ITexture image, Format imageFormat) + public void SetImage( + CommandBufferScoped cbs, + ShaderStage stage, + int binding, + ITexture image, + Format imageFormat) { if (image is TextureBuffer imageBuffer) { @@ -267,11 +327,13 @@ public void SetImage(int binding, ITexture image, Format imageFormat) } else if (image is TextureView view) { - _imageRefs[binding] = view.GetView(imageFormat).GetIdentityImageView(); + view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags()); + + _imageRefs[binding] = new(stage, view.Storage, view.GetView(imageFormat).GetIdentityImageView()); } else { - _imageRefs[binding] = null; + _imageRefs[binding] = default; _bufferImageRefs[binding] = null; _bufferImageFormats[binding] = default; } @@ -281,7 +343,7 @@ public void SetImage(int binding, ITexture image, Format imageFormat) public void SetImage(int binding, Auto image) { - _imageRefs[binding] = image; + _imageRefs[binding] = new(ShaderStage.Compute, null, image); SignalDirty(DirtyFlags.Image); } @@ -366,15 +428,13 @@ public void SetTextureAndSampler( } else if (texture is TextureView view) { - view.Storage.InsertWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags()); + view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags()); - _textureRefs[binding] = view.GetImageView(); - _samplerRefs[binding] = ((SamplerHolder)sampler)?.GetSampler(); + _textureRefs[binding] = new(stage, view.Storage, view.GetImageView(), ((SamplerHolder)sampler)?.GetSampler()); } else { - _textureRefs[binding] = null; - _samplerRefs[binding] = null; + _textureRefs[binding] = default; _bufferTextureRefs[binding] = null; } @@ -390,10 +450,9 @@ public void SetTextureAndSamplerIdentitySwizzle( { if (texture is TextureView view) { - view.Storage.InsertWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags()); + view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags()); - _textureRefs[binding] = view.GetIdentityImageView(); - _samplerRefs[binding] = ((SamplerHolder)sampler)?.GetSampler(); + _textureRefs[binding] = new(stage, view.Storage, view.GetIdentityImageView(), ((SamplerHolder)sampler)?.GetSampler()); SignalDirty(DirtyFlags.Texture); } @@ -608,9 +667,10 @@ private void UpdateAndBind(CommandBufferScoped cbs, int setIndex, PipelineBindPo for (int i = 0; i < count; i++) { ref var texture = ref textures[i]; + ref var refs = ref _textureRefs[binding + i]; - texture.ImageView = _textureRefs[binding + i]?.Get(cbs).Value ?? default; - texture.Sampler = _samplerRefs[binding + i]?.Get(cbs).Value ?? default; + texture.ImageView = refs.View?.Get(cbs).Value ?? default; + texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default; if (texture.ImageView.Handle == 0) { @@ -645,7 +705,7 @@ private void UpdateAndBind(CommandBufferScoped cbs, int setIndex, PipelineBindPo for (int i = 0; i < count; i++) { - images[i].ImageView = _imageRefs[binding + i]?.Get(cbs).Value ?? default; + images[i].ImageView = _imageRefs[binding + i].View?.Get(cbs).Value ?? default; } tu.Push(images[..count]); diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs b/src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs index 5c0fc468b..5a5ddf8c8 100644 --- a/src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs +++ b/src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs @@ -154,7 +154,7 @@ public void Run( int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim; _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) }); - _pipeline.SetImage(0, _intermediaryTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)); + _pipeline.SetImage(ShaderStage.Compute, 0, _intermediaryTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)); _pipeline.DispatchCompute(dispatchX, dispatchY, 1); _pipeline.ComputeBarrier(); diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs b/src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs index a7dd8eee8..c12933335 100644 --- a/src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs +++ b/src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs @@ -75,7 +75,7 @@ public TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize); var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize); - _pipeline.SetImage(0, _texture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)); + _pipeline.SetImage(ShaderStage.Compute, 0, _texture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)); _pipeline.DispatchCompute(dispatchX, dispatchY, 1); _pipeline.ComputeBarrier(); diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs b/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs index be392fe0e..259be9d64 100644 --- a/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs +++ b/src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs @@ -219,7 +219,7 @@ public TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int buffer.Holder.SetDataUnchecked(buffer.Offset, resolutionBuffer); _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) }); - _pipeline.SetImage(0, _edgeOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)); + _pipeline.SetImage(ShaderStage.Compute, 0, _edgeOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)); _pipeline.DispatchCompute(dispatchX, dispatchY, 1); _pipeline.ComputeBarrier(); @@ -229,7 +229,7 @@ public TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _edgeOutputTexture, _samplerLinear); _pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _areaTexture, _samplerLinear); _pipeline.SetTextureAndSampler(ShaderStage.Compute, 4, _searchTexture, _samplerLinear); - _pipeline.SetImage(0, _blendOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)); + _pipeline.SetImage(ShaderStage.Compute, 0, _blendOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)); _pipeline.DispatchCompute(dispatchX, dispatchY, 1); _pipeline.ComputeBarrier(); @@ -238,7 +238,7 @@ public TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int _pipeline.Specialize(_specConstants); _pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _blendOutputTexture, _samplerLinear); _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear); - _pipeline.SetImage(0, _outputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)); + _pipeline.SetImage(ShaderStage.Compute, 0, _outputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)); _pipeline.DispatchCompute(dispatchX, dispatchY, 1); _pipeline.ComputeBarrier(); diff --git a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs index af22f2656..8079e5ff9 100644 --- a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs +++ b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs @@ -243,41 +243,6 @@ public unsafe Auto Create(Vk api, CommandBufferScoped cbs return new Auto(new DisposableFramebuffer(api, _device, framebuffer), null, _attachments); } - public void UpdateModifications() - { - if (_colors != null) - { - for (int index = 0; index < _colors.Length; index++) - { - _colors[index].Storage.SetModification( - AccessFlags.ColorAttachmentWriteBit, - PipelineStageFlags.ColorAttachmentOutputBit); - } - } - - _depthStencil?.Storage.SetModification( - AccessFlags.DepthStencilAttachmentWriteBit, - PipelineStageFlags.LateFragmentTestsBit); - } - - public void InsertClearBarrier(CommandBufferScoped cbs, int index) - { - _colorsCanonical?[index]?.Storage?.InsertReadToWriteBarrier( - cbs, - AccessFlags.ColorAttachmentWriteBit, - PipelineStageFlags.ColorAttachmentOutputBit, - insideRenderPass: true); - } - - public void InsertClearBarrierDS(CommandBufferScoped cbs) - { - _depthStencil?.Storage?.InsertReadToWriteBarrier( - cbs, - AccessFlags.DepthStencilAttachmentWriteBit, - PipelineStageFlags.LateFragmentTestsBit, - insideRenderPass: true); - } - public TextureView[] GetAttachmentViews() { var result = new TextureView[_attachments.Length]; @@ -297,23 +262,20 @@ public RenderPassCacheKey GetRenderPassCacheKey() return new RenderPassCacheKey(_depthStencil, _colorsCanonical); } - public void InsertLoadOpBarriers(CommandBufferScoped cbs) + public void InsertLoadOpBarriers(VulkanRenderer gd, CommandBufferScoped cbs) { if (_colors != null) { foreach (var color in _colors) { // If Clear or DontCare were used, this would need to be write bit. - color.Storage?.InsertWriteToReadBarrier(cbs, AccessFlags.ColorAttachmentReadBit, PipelineStageFlags.ColorAttachmentOutputBit); - color.Storage?.SetModification(AccessFlags.ColorAttachmentWriteBit, PipelineStageFlags.ColorAttachmentOutputBit); + color.Storage?.QueueLoadOpBarrier(cbs, false); } } - if (_depthStencil != null) - { - _depthStencil.Storage?.InsertWriteToReadBarrier(cbs, AccessFlags.DepthStencilAttachmentReadBit, PipelineStageFlags.EarlyFragmentTestsBit); - _depthStencil.Storage?.SetModification(AccessFlags.DepthStencilAttachmentWriteBit, PipelineStageFlags.LateFragmentTestsBit); - } + _depthStencil?.Storage?.QueueLoadOpBarrier(cbs, true); + + gd.Barriers.Flush(cbs.CommandBuffer, false, null); } public (Auto renderPass, Auto framebuffer) GetPassAndFramebuffer( diff --git a/src/Ryujinx.Graphics.Vulkan/HelperShader.cs b/src/Ryujinx.Graphics.Vulkan/HelperShader.cs index c0ded5b3b..3efb1119f 100644 --- a/src/Ryujinx.Graphics.Vulkan/HelperShader.cs +++ b/src/Ryujinx.Graphics.Vulkan/HelperShader.cs @@ -1039,7 +1039,7 @@ public void CopyIncompatibleFormats( var dstView = Create2DLayerView(dst, dstLayer + z, dstLevel + l); _pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Compute, 0, srcView, null); - _pipeline.SetImage(0, dstView, dstFormat); + _pipeline.SetImage(ShaderStage.Compute, 0, dstView, dstFormat); int dispatchX = (Math.Min(srcView.Info.Width, dstView.Info.Width) + 31) / 32; int dispatchY = (Math.Min(srcView.Info.Height, dstView.Info.Height) + 31) / 32; @@ -1168,7 +1168,7 @@ public void CopyMSToNonMS(VulkanRenderer gd, CommandBufferScoped cbs, TextureVie var dstView = Create2DLayerView(dst, dstLayer + z, 0); _pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Compute, 0, srcView, null); - _pipeline.SetImage(0, dstView, format); + _pipeline.SetImage(ShaderStage.Compute, 0, dstView, format); _pipeline.DispatchCompute(dispatchX, dispatchY, 1); diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs index 3b3f59259..2bcab5143 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -36,6 +36,7 @@ class PipelineBase : IDisposable private PipelineState _newState; private bool _graphicsStateDirty; private bool _computeStateDirty; + private bool _bindingBarriersDirty; private PrimitiveTopology _topology; private ulong _currentPipelineHandle; @@ -248,14 +249,14 @@ public unsafe void ClearRenderTargetColor(int index, int layer, int layerCount, CreateRenderPass(); } + Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate); + BeginRenderPass(); var clearValue = new ClearValue(new ClearColorValue(color.Red, color.Green, color.Blue, color.Alpha)); var attachment = new ClearAttachment(ImageAspectFlags.ColorBit, (uint)index, clearValue); var clearRect = FramebufferParams.GetClearRect(ClearScissor, layer, layerCount); - FramebufferParams.InsertClearBarrier(Cbs, index); - Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect); } @@ -286,13 +287,13 @@ public unsafe void ClearRenderTargetDepthStencil(int layer, int layerCount, floa CreateRenderPass(); } + Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate); + BeginRenderPass(); var attachment = new ClearAttachment(flags, 0, clearValue); var clearRect = FramebufferParams.GetClearRect(ClearScissor, layer, layerCount); - FramebufferParams.InsertClearBarrierDS(Cbs); - Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect); } @@ -887,9 +888,9 @@ public void SetFrontFace(FrontFace frontFace) SignalStateChange(); } - public void SetImage(int binding, ITexture image, Format imageFormat) + public void SetImage(ShaderStage stage, int binding, ITexture image, Format imageFormat) { - _descriptorSetUpdater.SetImage(binding, image, imageFormat); + _descriptorSetUpdater.SetImage(Cbs, stage, binding, image, imageFormat); } public void SetImage(int binding, Auto image) @@ -977,6 +978,7 @@ public void SetProgram(IProgram program) _program = internalProgram; _descriptorSetUpdater.SetProgram(Cbs, internalProgram, _currentPipelineHandle != 0); + _bindingBarriersDirty = true; _newState.PipelineLayout = internalProgram.PipelineLayout; _newState.StagesCount = (uint)stages.Length; @@ -1066,7 +1068,6 @@ public void SetRenderTargetColorMasks(ReadOnlySpan componentMask) private void SetRenderTargetsInternal(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked) { CreateFramebuffer(colors, depthStencil, filterWriteMasked); - FramebufferParams?.UpdateModifications(); CreateRenderPass(); SignalStateChange(); SignalAttachmentChange(); @@ -1520,8 +1521,18 @@ private void RecreateComputePipelineIfNeeded() CreatePipeline(PipelineBindPoint.Compute); _computeStateDirty = false; Pbp = PipelineBindPoint.Compute; + + if (_bindingBarriersDirty) + { + // Stale barriers may have been activated by switching program. Emit any that are relevant. + _descriptorSetUpdater.InsertBindingBarriers(Cbs); + + _bindingBarriersDirty = false; + } } + Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate); + _descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Compute); } @@ -1575,8 +1586,18 @@ private bool RecreateGraphicsPipelineIfNeeded() _graphicsStateDirty = false; Pbp = PipelineBindPoint.Graphics; + + if (_bindingBarriersDirty) + { + // Stale barriers may have been activated by switching program. Emit any that are relevant. + _descriptorSetUpdater.InsertBindingBarriers(Cbs); + + _bindingBarriersDirty = false; + } } + Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate); + _descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Graphics); return true; @@ -1630,6 +1651,8 @@ private unsafe void BeginRenderPass() { if (!RenderPassActive) { + FramebufferParams.InsertLoadOpBarriers(Gd, Cbs); + var renderArea = new Rect2D(null, new Extent2D(FramebufferParams.Width, FramebufferParams.Height)); var clearValue = new ClearValue(); diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs b/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs index 6c4419cd2..4987548cd 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs @@ -269,6 +269,7 @@ public void FlushCommandsImpl() PreloadCbs = null; } + Gd.Barriers.Flush(Cbs.CommandBuffer, false, null); CommandBuffer = (Cbs = Gd.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer; Gd.RegisterFlush(); diff --git a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs index bba659215..230dbd4e8 100644 --- a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs +++ b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs @@ -433,99 +433,65 @@ private bool NeedsD24S8Conversion() return FormatCapabilities.IsD24S8(Info.Format) && VkFormat == VkFormat.D32SfloatS8Uint; } - public void SetModification(AccessFlags accessFlags, PipelineStageFlags stage) + public void QueueLoadOpBarrier(CommandBufferScoped cbs, bool depthStencil) { - _lastModificationAccess = accessFlags; - _lastModificationStage = stage; - } + PipelineStageFlags srcStageFlags = _lastReadStage | _lastModificationStage; + PipelineStageFlags dstStageFlags = depthStencil ? + PipelineStageFlags.EarlyFragmentTestsBit | PipelineStageFlags.LateFragmentTestsBit : + PipelineStageFlags.ColorAttachmentOutputBit; - public void InsertReadToWriteBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags, bool insideRenderPass) - { - var lastReadStage = _lastReadStage; + AccessFlags srcAccessFlags = _lastModificationAccess | _lastReadAccess; + AccessFlags dstAccessFlags = depthStencil ? + AccessFlags.DepthStencilAttachmentWriteBit | AccessFlags.DepthStencilAttachmentReadBit : + AccessFlags.ColorAttachmentWriteBit | AccessFlags.ColorAttachmentReadBit; - if (insideRenderPass) + if (srcAccessFlags != AccessFlags.None) { - // We can't have barrier from compute inside a render pass, - // as it is invalid to specify compute in the subpass dependency stage mask. - - lastReadStage &= ~PipelineStageFlags.ComputeShaderBit; - } + ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags(); + ImageMemoryBarrier barrier = TextureView.GetImageBarrier( + _imageAuto.Get(cbs).Value, + srcAccessFlags, + dstAccessFlags, + aspectFlags, + 0, + 0, + _info.GetLayers(), + _info.Levels); - if (lastReadStage != PipelineStageFlags.None) - { - // This would result in a validation error, but is - // required on MoltenVK as the generic barrier results in - // severe texture flickering in some scenarios. - if (_gd.IsMoltenVk) - { - ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags(); - TextureView.InsertImageBarrier( - _gd.Api, - cbs.CommandBuffer, - _imageAuto.Get(cbs).Value, - _lastReadAccess, - dstAccessFlags, - _lastReadStage, - dstStageFlags, - aspectFlags, - 0, - 0, - _info.GetLayers(), - _info.Levels); - } - else - { - TextureView.InsertMemoryBarrier( - _gd.Api, - cbs.CommandBuffer, - _lastReadAccess, - dstAccessFlags, - lastReadStage, - dstStageFlags); - } + _gd.Barriers.QueueBarrier(barrier, srcStageFlags, dstStageFlags); - _lastReadAccess = AccessFlags.None; _lastReadStage = PipelineStageFlags.None; + _lastReadAccess = AccessFlags.None; } + + _lastModificationStage = depthStencil ? + PipelineStageFlags.LateFragmentTestsBit : + PipelineStageFlags.ColorAttachmentOutputBit; + + _lastModificationAccess = depthStencil ? + AccessFlags.DepthStencilAttachmentWriteBit : + AccessFlags.ColorAttachmentWriteBit; } - public void InsertWriteToReadBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags) + public void QueueWriteToReadBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags) { _lastReadAccess |= dstAccessFlags; _lastReadStage |= dstStageFlags; if (_lastModificationAccess != AccessFlags.None) { - // This would result in a validation error, but is - // required on MoltenVK as the generic barrier results in - // severe texture flickering in some scenarios. - if (_gd.IsMoltenVk) - { - ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags(); - TextureView.InsertImageBarrier( - _gd.Api, - cbs.CommandBuffer, - _imageAuto.Get(cbs).Value, - _lastModificationAccess, - dstAccessFlags, - _lastModificationStage, - dstStageFlags, - aspectFlags, - 0, - 0, - _info.GetLayers(), - _info.Levels); - } - else - { - TextureView.InsertMemoryBarrier( - _gd.Api, - cbs.CommandBuffer, - _lastModificationAccess, - dstAccessFlags, - _lastModificationStage, - dstStageFlags); - } + ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags(); + ImageMemoryBarrier barrier = TextureView.GetImageBarrier( + _imageAuto.Get(cbs).Value, + _lastModificationAccess, + dstAccessFlags, + aspectFlags, + 0, + 0, + _info.GetLayers(), + _info.Levels); + + _gd.Barriers.QueueBarrier(barrier, _lastModificationStage, dstStageFlags); _lastModificationAccess = AccessFlags.None; } diff --git a/src/Ryujinx.Graphics.Vulkan/TextureView.cs b/src/Ryujinx.Graphics.Vulkan/TextureView.cs index ef511565c..31d139652 100644 --- a/src/Ryujinx.Graphics.Vulkan/TextureView.cs +++ b/src/Ryujinx.Graphics.Vulkan/TextureView.cs @@ -497,21 +497,17 @@ public static unsafe void InsertMemoryBarrier( null); } - public static unsafe void InsertImageBarrier( - Vk api, - CommandBuffer commandBuffer, + public static ImageMemoryBarrier GetImageBarrier( Image image, AccessFlags srcAccessMask, AccessFlags dstAccessMask, - PipelineStageFlags srcStageMask, - PipelineStageFlags dstStageMask, ImageAspectFlags aspectFlags, int firstLayer, int firstLevel, int layers, int levels) { - ImageMemoryBarrier memoryBarrier = new() + return new() { SType = StructureType.ImageMemoryBarrier, SrcAccessMask = srcAccessMask, @@ -523,6 +519,31 @@ public static unsafe void InsertImageBarrier( NewLayout = ImageLayout.General, SubresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, (uint)levels, (uint)firstLayer, (uint)layers), }; + } + + public static unsafe void InsertImageBarrier( + Vk api, + CommandBuffer commandBuffer, + Image image, + AccessFlags srcAccessMask, + AccessFlags dstAccessMask, + PipelineStageFlags srcStageMask, + PipelineStageFlags dstStageMask, + ImageAspectFlags aspectFlags, + int firstLayer, + int firstLevel, + int layers, + int levels) + { + ImageMemoryBarrier memoryBarrier = GetImageBarrier( + image, + srcAccessMask, + dstAccessMask, + aspectFlags, + firstLayer, + firstLevel, + layers, + levels); api.CmdPipelineBarrier( commandBuffer, diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index 6aa46b79a..434545fe0 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -68,6 +68,8 @@ public sealed class VulkanRenderer : IRenderer internal HelperShader HelperShader { get; private set; } internal PipelineFull PipelineInternal => _pipeline; + internal BarrierBatch Barriers { get; private set; } + public IPipeline Pipeline => _pipeline; public IWindow Window => _window; @@ -381,6 +383,8 @@ private unsafe void LoadFeatures(uint maxQueueCount, uint queueFamilyIndex) HelperShader = new HelperShader(this, _device); + Barriers = new BarrierBatch(this); + _counters = new Counters(this, _device, _pipeline); } @@ -914,6 +918,7 @@ public unsafe void Dispose() BufferManager.Dispose(); DescriptorSetManager.Dispose(); PipelineLayoutCache.Dispose(); + Barriers.Dispose(); MemoryAllocator.Dispose(); From 103e7cb021965a73bd7e68001a4b2b645ce1bcc2 Mon Sep 17 00:00:00 2001 From: Exhigh Date: Sat, 17 Feb 2024 21:49:50 +0400 Subject: [PATCH 078/126] hid: Stub SetTouchScreenResolution (#6322) * hid: Implement SetTouchScreenResolution * Fix Tomb Raider I-III Remastered from crashing without enabling Ignore Missing Services * PR Feedback: Update Comments --- src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs index 1d1b145cc..bcc87f53b 100644 --- a/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs +++ b/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs @@ -1821,5 +1821,18 @@ public ResultCode SetTouchScreenConfiguration(ServiceCtx context) return ResultCode.Success; } + + [CommandCmif(1004)] // 17.0.0+ + // SetTouchScreenResolution(int width, int height, nn::applet::AppletResourceUserId) + public ResultCode SetTouchScreenResolution(ServiceCtx context) + { + int width = context.RequestData.ReadInt32(); + int height = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { width, height, appletResourceUserId }); + + return ResultCode.Success; + } } } From 42340fc743aec49a5bebfc3969e24f070e436e06 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sat, 17 Feb 2024 17:30:54 -0300 Subject: [PATCH 079/126] LightningJit: Add a limit on the number of instructions per function for Arm64 (#6328) --- .../LightningJit/Arm64/Target/Arm64/Decoder.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Decoder.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Decoder.cs index 738b8a32d..e9ba8ba21 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Decoder.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Decoder.cs @@ -8,7 +8,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64 { static class Decoder { - private const int MaxInstructionsPerBlock = 1000; + private const int MaxInstructionsPerFunction = 10000; private const uint NzcvFlags = 0xfu << 28; private const uint CFlag = 0x1u << 29; @@ -22,10 +22,11 @@ public static MultiBlock DecodeMulti(CpuPreset cpuPreset, IMemoryManager memoryM bool hasHostCall = false; bool hasMemoryInstruction = false; + int totalInsts = 0; while (true) { - Block block = Decode(cpuPreset, memoryManager, address, ref useMask, ref hasHostCall, ref hasMemoryInstruction); + Block block = Decode(cpuPreset, memoryManager, address, ref totalInsts, ref useMask, ref hasHostCall, ref hasMemoryInstruction); if (!block.IsTruncated && TryGetBranchTarget(block, out ulong targetAddress)) { @@ -230,6 +231,7 @@ private static Block Decode( CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, + ref int totalInsts, ref RegisterMask useMask, ref bool hasHostCall, ref bool hasMemoryInstruction) @@ -272,7 +274,7 @@ private static Block Decode( uint tempGprUseMask = gprUseMask | instGprReadMask | instGprWriteMask; - if (CalculateAvailableTemps(tempGprUseMask) < CalculateRequiredGprTemps(tempGprUseMask) || insts.Count >= MaxInstructionsPerBlock) + if (CalculateAvailableTemps(tempGprUseMask) < CalculateRequiredGprTemps(tempGprUseMask) || totalInsts++ >= MaxInstructionsPerFunction) { isTruncated = true; address -= 4UL; From 6ef89461699518a9b140c5b094c7b32f78824a35 Mon Sep 17 00:00:00 2001 From: Mary Guillemard Date: Mon, 19 Feb 2024 21:18:46 +0100 Subject: [PATCH 080/126] Avalonia: Fix gamescope once and for all (#6301) Enable input focus proxy, makes WM_TAKE_FOCUS capability to be exposed. This fix menu on gamescope, for real this time.... Signed-off-by: Mary Guillemard --- src/Ryujinx.Ava/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Ryujinx.Ava/Program.cs b/src/Ryujinx.Ava/Program.cs index 7751bb520..702240ba3 100644 --- a/src/Ryujinx.Ava/Program.cs +++ b/src/Ryujinx.Ava/Program.cs @@ -59,6 +59,7 @@ public static AppBuilder BuildAvaloniaApp() { EnableMultiTouch = true, EnableIme = true, + EnableInputFocusProxy = true, RenderingMode = new[] { X11RenderingMode.Glx, X11RenderingMode.Software }, }) .With(new Win32PlatformOptions From 6f5fcb79706033ca31d83c66041dc266293c2fb5 Mon Sep 17 00:00:00 2001 From: MetrosexualGarbodor <79612681+MetrosexualGarbodor@users.noreply.github.com> Date: Mon, 19 Feb 2024 22:27:15 +0000 Subject: [PATCH 081/126] Avalonia UI: Update English tooltips (#6305) * update English tooltips * missed a dot --- src/Ryujinx.Ava/Assets/Locales/en_US.json | 28 +++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Ryujinx.Ava/Assets/Locales/en_US.json b/src/Ryujinx.Ava/Assets/Locales/en_US.json index 1ecc5da46..2febf90ec 100644 --- a/src/Ryujinx.Ava/Assets/Locales/en_US.json +++ b/src/Ryujinx.Ava/Assets/Locales/en_US.json @@ -155,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "Native (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", "SettingsTabGraphicsAspectRatio": "Aspect Ratio:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -452,13 +452,13 @@ "CustomThemePathTooltip": "Path to custom GUI theme", "CustomThemeBrowseTooltip": "Browse for a custom GUI theme", "DockModeToggleTooltip": "Docked mode makes the emulated system behave as a docked Nintendo Switch. This improves graphical fidelity in most games. Conversely, disabling this will make the emulated system behave as a handheld Nintendo Switch, reducing graphics quality.\n\nConfigure player 1 controls if planning to use docked mode; configure handheld controls if planning to use handheld mode.\n\nLeave ON if unsure.", - "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.", - "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", "RegionTooltip": "Change System Region", "LanguageTooltip": "Change System Language", "TimezoneTooltip": "Change System TimeZone", "TimeTooltip": "Change System Time", - "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference. We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", "PptcToggleTooltip": "Saves translated JIT functions so that they do not need to be translated every time the game loads.\n\nReduces stuttering and significantly speeds up boot times after the first boot of a game.\n\nLeave ON if unsure.", "FsIntegrityToggleTooltip": "Checks for corrupt files when booting a game, and if corrupt files are detected, displays a hash error in the log.\n\nHas no impact on performance and is meant to help troubleshooting.\n\nLeave ON if unsure.", "AudioBackendTooltip": "Changes the backend used to render audio.\n\nSDL2 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound.\n\nSet to SDL2 if unsure.", @@ -472,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", "GalThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", "ShaderCacheToggleTooltip": "Saves a disk shader cache which reduces stuttering in subsequent runs.\n\nLeave ON if unsure.", - "ResolutionScaleTooltip": "Resolution Scale applied to applicable render targets", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", "ResolutionScaleEntryTooltip": "Floating point resolution scale, such as 1.5. Non-integral scales are more likely to cause issues or crash.", - "AnisotropyTooltip": "Level of Anisotropic Filtering (set to Auto to use the value requested by the game)", - "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", "ShaderDumpPathTooltip": "Graphics Shaders Dump Path", "FileLogTooltip": "Saves console logging to a log file on disk. Does not affect performance.", "StubLogTooltip": "Prints stub log messages in the console. Does not affect performance.", @@ -616,9 +616,9 @@ "UserProfilesName": "Name:", "UserProfilesUserId": "User ID:", "SettingsTabGraphicsBackend": "Graphics Backend", - "SettingsTabGraphicsBackendTooltip": "Graphics Backend to use", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", "SettingsEnableTextureRecompression": "Enable Texture Recompression", - "SettingsEnableTextureRecompressionTooltip": "Compresses certain textures in order to reduce VRAM usage.\n\nRecommended for use with GPUs that have less than 4GiB VRAM.\n\nLeave OFF if unsure.", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", "SettingsTabGraphicsPreferredGpu": "Preferred GPU", "SettingsTabGraphicsPreferredGpuTooltip": "Select the graphics card that will be used with the Vulkan graphics backend.\n\nDoes not affect the GPU that OpenGL will use.\n\nSet to the GPU flagged as \"dGPU\" if unsure. If there isn't one, leave untouched.", "SettingsAppRequiredRestartMessage": "Ryujinx Restart Required", @@ -644,12 +644,12 @@ "Recover": "Recover", "UserProfilesRecoverHeading": "Saves were found for the following accounts", "UserProfilesRecoverEmptyList": "No profiles to recover", - "GraphicsAATooltip": "Applies anti-aliasing to the game render", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", "GraphicsAALabel": "Anti-Aliasing:", "GraphicsScalingFilterLabel": "Scaling Filter:", - "GraphicsScalingFilterTooltip": "Enables Framebuffer Scaling", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", "GraphicsScalingFilterLevelLabel": "Level", - "GraphicsScalingFilterLevelTooltip": "Set Scaling Filter Level", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA Low", "SmaaMedium": "SMAA Medium", "SmaaHigh": "SMAA High", @@ -657,12 +657,12 @@ "UserEditorTitle": "Edit User", "UserEditorTitleCreate": "Create User", "SettingsTabNetworkInterface": "Network Interface:", - "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", "NetworkInterfaceDefault": "Default", "PackagingShaders": "Packaging Shaders", "AboutChangelogButton": "View Changelog on GitHub", "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser.", "SettingsTabNetworkMultiplayer": "Multiplayer", "MultiplayerMode": "Mode:", - "MultiplayerModeTooltip": "Change multiplayer mode" + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." } From 4f63782baccb530dcf9c5da1a32c6f257e61bc28 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Thu, 22 Feb 2024 02:41:08 +0000 Subject: [PATCH 082/126] Vulkan: Fix barrier batching past limit (#6339) If more than 16 barriers were queued at one time, the _queuedBarrierCount would no longer match the number of remaining barriers, because when breaking out of the loop consuming them it deleted all barriers, not just the 16 that were consumed. Should fix freezes that started occurring with #6240. Fixes issue #6338. --- src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs b/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs index 3b44c98c1..aa158f036 100644 --- a/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs +++ b/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs @@ -95,6 +95,7 @@ static void AddBarriers( List> list) where T : unmanaged { int firstMatch = -1; + int end = list.Count; for (int i = 0; i < list.Count; i++) { @@ -111,6 +112,7 @@ static void AddBarriers( if (count >= target.Length) { + end = i + 1; break; } } @@ -128,6 +130,7 @@ static void AddBarriers( if (count >= target.Length) { + end = i + 1; break; } } @@ -146,13 +149,13 @@ static void AddBarriers( } } - if (firstMatch == 0) + if (firstMatch == 0 && end == list.Count) { list.Clear(); } else if (firstMatch != -1) { - int deleteCount = list.Count - firstMatch; + int deleteCount = end - firstMatch; list.RemoveRange(firstMatch, deleteCount); } From 79f6c18a9b1c0b73a7a2324c916ecc371cfd02ad Mon Sep 17 00:00:00 2001 From: riperiperi Date: Thu, 22 Feb 2024 02:52:13 +0000 Subject: [PATCH 083/126] Vulkan: Disable push descriptors on older NVIDIA GPUs (#6340) Disables push descriptors on older NVIDIA GPUs (10xx and below), since it is clearly broken beyond comprehension. The existing workaround wasn't good enough and a more thorough one will probably cost more performance than the feature gains. The workaround has been removed. Fixes #6331. --- .../DescriptorSetUpdater.cs | 36 ------------------- .../ShaderCollection.cs | 1 + 2 files changed, 1 insertion(+), 36 deletions(-) diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs index 946e3bc14..765686025 100644 --- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs @@ -299,13 +299,6 @@ public void SetProgram(CommandBufferScoped cbs, ShaderCollection program, bool i // When the pipeline layout changes, push descriptor bindings are invalidated. AdvancePdSequence(); - - if (_gd.IsNvidiaPreTuring && !program.UsePushDescriptors && _program?.UsePushDescriptors == true && isBound) - { - // On older nvidia GPUs, we need to clear out the active push descriptor bindings when switching - // to normal descriptors. Keeping them bound can prevent buffers from binding properly in future. - ClearAndBindUniformBufferPd(cbs); - } } _program = program; @@ -806,35 +799,6 @@ private void UpdateAndBindUniformBufferPd(CommandBufferScoped cbs, PipelineBindP } } - private void ClearAndBindUniformBufferPd(CommandBufferScoped cbs) - { - var bindingSegments = _program.BindingSegments[PipelineBase.UniformSetIndex]; - - long updatedBindings = 0; - DescriptorSetTemplateWriter writer = _templateUpdater.Begin(32 * Unsafe.SizeOf()); - - foreach (ResourceBindingSegment segment in bindingSegments) - { - int binding = segment.Binding; - int count = segment.Count; - - for (int i = 0; i < count; i++) - { - int index = binding + i; - updatedBindings |= 1L << index; - - var bufferInfo = new DescriptorBufferInfo(); - writer.Push(MemoryMarshal.CreateReadOnlySpan(ref bufferInfo, 1)); - } - } - - if (updatedBindings > 0) - { - DescriptorSetTemplate template = _program.GetPushDescriptorTemplate(updatedBindings); - _templateUpdater.CommitPushDescriptor(_gd, cbs, template, _program.PipelineLayout); - } - } - private void Initialize(CommandBufferScoped cbs, int setIndex, DescriptorSetCollection dsc) { // We don't support clearing texture descriptors currently. diff --git a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs index 3c35a6f0e..7f687fb4c 100644 --- a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs +++ b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs @@ -111,6 +111,7 @@ public ShaderCollection( bool usePushDescriptors = !isMinimal && VulkanConfiguration.UsePushDescriptors && _gd.Capabilities.SupportsPushDescriptors && + !_gd.IsNvidiaPreTuring && !IsCompute && CanUsePushDescriptors(gd, resourceLayout, IsCompute); From ba91f5d401bf5f9fc39aaa30feac2600e82f9c42 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Thu, 22 Feb 2024 09:43:22 +0000 Subject: [PATCH 084/126] Vulkan: Properly reset barrier batch when splitting due to mismatching flags (#6345) Forgot to set the end variable here. Should stop it from crashing when this path is taken. --- src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs b/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs index aa158f036..24642af2d 100644 --- a/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs +++ b/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs @@ -144,6 +144,7 @@ static void AddBarriers( i -= deleteCount; firstMatch = -1; + end = list.Count; } } } From 167f50bbcd5b2378a038e540877be4d9b71a12f6 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Thu, 22 Feb 2024 11:03:07 -0300 Subject: [PATCH 085/126] Implement virtual buffer dependencies (#6190) * Implement virtual buffer copies * Introduce TranslateAndCreateMultiBuffersPhysicalOnly, use it for copy and clear * Rename VirtualBufferCache to VirtualRangeCache * Fix potential issue where virtual range could exist in the cache, without a physical buffer * Fix bug that could cause copy with negative size on CopyToDependantVirtualBuffer * Remove virtual copy back for SyncAction * GetData XML docs * Make field readonly * Fix virtual buffer modification tracking * Remove CopyFromDependantVirtualBuffers from ExternalFlush * Move things around a little to avoid perf impact - Inline null check for CopyFromDependantVirtualBuffers - Remove extra method call for SynchronizeMemoryWithVirtualCopyBack, prefer calling CopyFromDependantVirtualBuffers separately * Fix up XML doc --------- Co-authored-by: riperiperi --- src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs | 215 +++++++++++++++++- .../Memory/BufferCache.cs | 184 ++++++++++++--- .../Memory/MemoryManager.cs | 8 +- .../Memory/MultiRangeBuffer.cs | 187 ++++++++++++++- ...ualBufferCache.cs => VirtualRangeCache.cs} | 20 +- 5 files changed, 569 insertions(+), 45 deletions(-) rename src/Ryujinx.Graphics.Gpu/Memory/{VirtualBufferCache.cs => VirtualRangeCache.cs} (92%) diff --git a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs index 12461e96e..e01e5142c 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs @@ -5,6 +5,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; namespace Ryujinx.Graphics.Gpu.Memory { @@ -65,6 +67,9 @@ class Buffer : IRange, ISyncActionHandler, IDisposable private readonly Action _loadDelegate; private readonly Action _modifiedDelegate; + private HashSet _virtualDependencies; + private readonly ReaderWriterLockSlim _virtualDependenciesLock; + private int _sequenceNumber; private readonly bool _useGranular; @@ -152,6 +157,8 @@ public Buffer( _externalFlushDelegate = new RegionSignal(ExternalFlush); _loadDelegate = new Action(LoadRegion); _modifiedDelegate = new Action(RegionModified); + + _virtualDependenciesLock = new ReaderWriterLockSlim(); } /// @@ -220,6 +227,7 @@ public bool FullyContains(ulong address, ulong size) /// /// Start address of the range to synchronize /// Size in bytes of the range to synchronize + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SynchronizeMemory(ulong address, ulong size) { if (_useGranular) @@ -239,6 +247,7 @@ public void SynchronizeMemory(ulong address, ulong size) else { _context.Renderer.SetBufferData(Handle, 0, _physicalMemory.GetSpan(Address, (int)Size)); + CopyToDependantVirtualBuffers(); } _sequenceNumber = _context.SequenceNumber; @@ -460,6 +469,8 @@ private void LoadRegion(ulong mAddress, ulong mSize) int offset = (int)(mAddress - Address); _context.Renderer.SetBufferData(Handle, offset, _physicalMemory.GetSpan(mAddress, (int)mSize)); + + CopyToDependantVirtualBuffers(mAddress, mSize); } /// @@ -520,6 +531,7 @@ public void ForceDirty(ulong mAddress, ulong mSize) /// The offset of the destination buffer to copy into public void CopyTo(Buffer destination, int dstOffset) { + CopyFromDependantVirtualBuffers(); _context.Renderer.Pipeline.CopyBuffer(Handle, destination.Handle, 0, dstOffset, (int)Size); } @@ -536,7 +548,7 @@ public void Flush(ulong address, ulong size) using PinnedSpan data = _context.Renderer.GetBufferData(Handle, offset, (int)size); // TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers. - _physicalMemory.WriteUntracked(address, data.Get()); + _physicalMemory.WriteUntracked(address, CopyFromDependantVirtualBuffers(data.Get(), address, size)); } /// @@ -617,6 +629,207 @@ public void Unmapped(ulong address, ulong size) UnmappedSequence++; } + /// + /// Adds a virtual buffer dependency, indicating that a virtual buffer depends on data from this buffer. + /// + /// Dependant virtual buffer + public void AddVirtualDependency(MultiRangeBuffer virtualBuffer) + { + _virtualDependenciesLock.EnterWriteLock(); + + try + { + (_virtualDependencies ??= new()).Add(virtualBuffer); + } + finally + { + _virtualDependenciesLock.ExitWriteLock(); + } + } + + /// + /// Removes a virtual buffer dependency, indicating that a virtual buffer no longer depends on data from this buffer. + /// + /// Dependant virtual buffer + public void RemoveVirtualDependency(MultiRangeBuffer virtualBuffer) + { + _virtualDependenciesLock.EnterWriteLock(); + + try + { + if (_virtualDependencies != null) + { + _virtualDependencies.Remove(virtualBuffer); + + if (_virtualDependencies.Count == 0) + { + _virtualDependencies = null; + } + } + } + finally + { + _virtualDependenciesLock.ExitWriteLock(); + } + } + + /// + /// Copies the buffer data to all virtual buffers that depends on it. + /// + public void CopyToDependantVirtualBuffers() + { + CopyToDependantVirtualBuffers(Address, Size); + } + + /// + /// Copies the buffer data inside the specifide range to all virtual buffers that depends on it. + /// + /// Address of the range + /// Size of the range in bytes + public void CopyToDependantVirtualBuffers(ulong address, ulong size) + { + if (_virtualDependencies != null) + { + foreach (var virtualBuffer in _virtualDependencies) + { + CopyToDependantVirtualBuffer(virtualBuffer, address, size); + } + } + } + + /// + /// Copies all modified ranges from all virtual buffers back into this buffer. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyFromDependantVirtualBuffers() + { + if (_virtualDependencies != null) + { + CopyFromDependantVirtualBuffersImpl(); + } + } + + /// + /// Copies all modified ranges from all virtual buffers back into this buffer. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private void CopyFromDependantVirtualBuffersImpl() + { + foreach (var virtualBuffer in _virtualDependencies.OrderBy(x => x.ModificationSequenceNumber)) + { + virtualBuffer.ConsumeModifiedRegion(this, (mAddress, mSize) => + { + // Get offset inside both this and the virtual buffer. + // Note that sometimes there is no right answer for the virtual offset, + // as the same physical range might be mapped multiple times inside a virtual buffer. + // We just assume it does not happen in practice as it can only be implemented correctly + // when the host has support for proper sparse mapping. + + ulong mEndAddress = mAddress + mSize; + mAddress = Math.Max(mAddress, Address); + mSize = Math.Min(mEndAddress, EndAddress) - mAddress; + + int physicalOffset = (int)(mAddress - Address); + int virtualOffset = virtualBuffer.Range.FindOffset(new(mAddress, mSize)); + + _context.Renderer.Pipeline.CopyBuffer(virtualBuffer.Handle, Handle, virtualOffset, physicalOffset, (int)mSize); + }); + } + } + + /// + /// Copies all overlapping modified ranges from all virtual buffers back into this buffer, and returns an updated span with the data. + /// + /// Span where the unmodified data will be taken from for the output + /// Address of the region to copy + /// Size of the region to copy in bytes + /// A span with , and the data for all modified ranges if any + private ReadOnlySpan CopyFromDependantVirtualBuffers(ReadOnlySpan dataSpan, ulong address, ulong size) + { + _virtualDependenciesLock.EnterReadLock(); + + try + { + if (_virtualDependencies != null) + { + byte[] storage = dataSpan.ToArray(); + + foreach (var virtualBuffer in _virtualDependencies.OrderBy(x => x.ModificationSequenceNumber)) + { + virtualBuffer.ConsumeModifiedRegion(address, size, (mAddress, mSize) => + { + // Get offset inside both this and the virtual buffer. + // Note that sometimes there is no right answer for the virtual offset, + // as the same physical range might be mapped multiple times inside a virtual buffer. + // We just assume it does not happen in practice as it can only be implemented correctly + // when the host has support for proper sparse mapping. + + ulong mEndAddress = mAddress + mSize; + mAddress = Math.Max(mAddress, address); + mSize = Math.Min(mEndAddress, address + size) - mAddress; + + int physicalOffset = (int)(mAddress - Address); + int virtualOffset = virtualBuffer.Range.FindOffset(new(mAddress, mSize)); + + _context.Renderer.Pipeline.CopyBuffer(virtualBuffer.Handle, Handle, virtualOffset, physicalOffset, (int)size); + virtualBuffer.GetData(storage.AsSpan().Slice((int)(mAddress - address), (int)mSize), virtualOffset, (int)mSize); + }); + } + + dataSpan = storage; + } + } + finally + { + _virtualDependenciesLock.ExitReadLock(); + } + + return dataSpan; + } + + /// + /// Copies the buffer data to the specified virtual buffer. + /// + /// Virtual buffer to copy the data into + public void CopyToDependantVirtualBuffer(MultiRangeBuffer virtualBuffer) + { + CopyToDependantVirtualBuffer(virtualBuffer, Address, Size); + } + + /// + /// Copies the buffer data inside the given range to the specified virtual buffer. + /// + /// Virtual buffer to copy the data into + /// Address of the range + /// Size of the range in bytes + public void CopyToDependantVirtualBuffer(MultiRangeBuffer virtualBuffer, ulong address, ulong size) + { + // Broadcast data to all ranges of the virtual buffer that are contained inside this buffer. + + ulong lastOffset = 0; + + while (virtualBuffer.TryGetPhysicalOffset(this, lastOffset, out ulong srcOffset, out ulong dstOffset, out ulong copySize)) + { + ulong innerOffset = address - Address; + ulong innerEndOffset = (address + size) - Address; + + lastOffset = dstOffset + copySize; + + // Clamp range to the specified range. + ulong copySrcOffset = Math.Max(srcOffset, innerOffset); + ulong copySrcEndOffset = Math.Min(innerEndOffset, srcOffset + copySize); + + if (copySrcEndOffset > copySrcOffset) + { + copySize = copySrcEndOffset - copySrcOffset; + dstOffset += copySrcOffset - srcOffset; + srcOffset = copySrcOffset; + + _context.Renderer.Pipeline.CopyBuffer(Handle, virtualBuffer.Handle, (int)srcOffset, (int)dstOffset, (int)copySize); + } + } + } + /// /// Increments the buffer reference count. /// diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs index bd9aa39c8..c6284780d 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; namespace Ryujinx.Graphics.Gpu.Memory { @@ -46,6 +47,7 @@ class BufferCache : IDisposable private readonly Dictionary _dirtyCache; private readonly Dictionary _modifiedCache; private bool _pruneCaches; + private int _virtualModifiedSequenceNumber; public event Action NotifyBuffersModified; @@ -125,7 +127,7 @@ public MultiRange TranslateAndCreateBuffer(MemoryManager memoryManager, ulong gp /// /// Performs address translation of the GPU virtual address, and creates - /// new buffers, if needed, for the specified range. + /// new physical and virtual buffers, if needed, for the specified range. /// /// GPU memory manager where the buffer is mapped /// Start GPU virtual address of the buffer @@ -138,12 +140,10 @@ public MultiRange TranslateAndCreateMultiBuffers(MemoryManager memoryManager, ul return new MultiRange(MemoryManager.PteUnmapped, size); } - bool supportsSparse = _context.Capabilities.SupportsSparseBuffer; - // Fast path not taken for non-contiguous ranges, // since multi-range buffers are not coalesced, so a buffer that covers // the entire cached range might not actually exist. - if (memoryManager.VirtualBufferCache.TryGetOrAddRange(gpuVa, size, supportsSparse, out MultiRange range) && + if (memoryManager.VirtualRangeCache.TryGetOrAddRange(gpuVa, size, out MultiRange range) && range.Count == 1) { return range; @@ -154,6 +154,50 @@ public MultiRange TranslateAndCreateMultiBuffers(MemoryManager memoryManager, ul return range; } + /// + /// Performs address translation of the GPU virtual address, and creates + /// new physical buffers, if needed, for the specified range. + /// + /// GPU memory manager where the buffer is mapped + /// Start GPU virtual address of the buffer + /// Size in bytes of the buffer + /// Physical ranges of the buffer, after address translation + public MultiRange TranslateAndCreateMultiBuffersPhysicalOnly(MemoryManager memoryManager, ulong gpuVa, ulong size) + { + if (gpuVa == 0) + { + return new MultiRange(MemoryManager.PteUnmapped, size); + } + + // Fast path not taken for non-contiguous ranges, + // since multi-range buffers are not coalesced, so a buffer that covers + // the entire cached range might not actually exist. + if (memoryManager.VirtualRangeCache.TryGetOrAddRange(gpuVa, size, out MultiRange range) && + range.Count == 1) + { + return range; + } + + for (int i = 0; i < range.Count; i++) + { + MemoryRange subRange = range.GetSubRange(i); + + if (subRange.Address != MemoryManager.PteUnmapped) + { + if (range.Count > 1) + { + CreateBuffer(subRange.Address, subRange.Size, SparseBufferAlignmentSize); + } + else + { + CreateBuffer(subRange.Address, subRange.Size); + } + } + } + + return range; + } + /// /// Creates a new buffer for the specified range, if it does not yet exist. /// This can be used to ensure the existance of a buffer. @@ -263,43 +307,110 @@ private void CreateMultiRangeBuffer(MultiRange range) } } - BufferRange[] storages = new BufferRange[range.Count]; + MultiRangeBuffer multiRangeBuffer; + MemoryRange[] alignedSubRanges = new MemoryRange[range.Count]; ulong alignmentMask = SparseBufferAlignmentSize - 1; - for (int i = 0; i < range.Count; i++) + if (_context.Capabilities.SupportsSparseBuffer) { - MemoryRange subRange = range.GetSubRange(i); + BufferRange[] storages = new BufferRange[range.Count]; - if (subRange.Address != MemoryManager.PteUnmapped) + for (int i = 0; i < range.Count; i++) { - ulong endAddress = subRange.Address + subRange.Size; + MemoryRange subRange = range.GetSubRange(i); - ulong alignedAddress = subRange.Address & ~alignmentMask; - ulong alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask; - ulong alignedSize = alignedEndAddress - alignedAddress; + if (subRange.Address != MemoryManager.PteUnmapped) + { + ulong endAddress = subRange.Address + subRange.Size; - Buffer buffer = _buffers.FindFirstOverlap(alignedAddress, alignedSize); - BufferRange bufferRange = buffer.GetRange(alignedAddress, alignedSize, false); + ulong alignedAddress = subRange.Address & ~alignmentMask; + ulong alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask; + ulong alignedSize = alignedEndAddress - alignedAddress; - storages[i] = bufferRange; - alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize); + Buffer buffer = _buffers.FindFirstOverlap(alignedAddress, alignedSize); + BufferRange bufferRange = buffer.GetRange(alignedAddress, alignedSize, false); + + alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize); + storages[i] = bufferRange; + } + else + { + ulong alignedSize = (subRange.Size + alignmentMask) & ~alignmentMask; + + alignedSubRanges[i] = new MemoryRange(MemoryManager.PteUnmapped, alignedSize); + storages[i] = new BufferRange(BufferHandle.Null, 0, (int)alignedSize); + } } - else + + multiRangeBuffer = new(_context, new MultiRange(alignedSubRanges), storages); + } + else + { + for (int i = 0; i < range.Count; i++) { - ulong alignedSize = (subRange.Size + alignmentMask) & ~alignmentMask; + MemoryRange subRange = range.GetSubRange(i); + + if (subRange.Address != MemoryManager.PteUnmapped) + { + ulong endAddress = subRange.Address + subRange.Size; + + ulong alignedAddress = subRange.Address & ~alignmentMask; + ulong alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask; + ulong alignedSize = alignedEndAddress - alignedAddress; + + alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize); + } + else + { + ulong alignedSize = (subRange.Size + alignmentMask) & ~alignmentMask; - storages[i] = new BufferRange(BufferHandle.Null, 0, (int)alignedSize); - alignedSubRanges[i] = new MemoryRange(MemoryManager.PteUnmapped, alignedSize); + alignedSubRanges[i] = new MemoryRange(MemoryManager.PteUnmapped, alignedSize); + } } - } - MultiRangeBuffer multiRangeBuffer = new(_context, new MultiRange(alignedSubRanges), storages); + multiRangeBuffer = new(_context, new MultiRange(alignedSubRanges)); + + UpdateVirtualBufferDependencies(multiRangeBuffer); + } _multiRangeBuffers.Add(multiRangeBuffer); } + /// + /// Adds two-way dependencies to all physical buffers contained within a given virtual buffer. + /// + /// Virtual buffer to have dependencies added + private void UpdateVirtualBufferDependencies(MultiRangeBuffer virtualBuffer) + { + virtualBuffer.ClearPhysicalDependencies(); + + ulong dstOffset = 0; + + HashSet physicalBuffers = new(); + + for (int i = 0; i < virtualBuffer.Range.Count; i++) + { + MemoryRange subRange = virtualBuffer.Range.GetSubRange(i); + + if (subRange.Address != MemoryManager.PteUnmapped) + { + Buffer buffer = _buffers.FindFirstOverlap(subRange.Address, subRange.Size); + + virtualBuffer.AddPhysicalDependency(buffer, subRange.Address, dstOffset, subRange.Size); + physicalBuffers.Add(buffer); + } + + dstOffset += subRange.Size; + } + + foreach (var buffer in physicalBuffers) + { + buffer.CopyToDependantVirtualBuffer(virtualBuffer); + } + } + /// /// Performs address translation of the GPU virtual address, and attempts to force /// the buffer in the region as dirty. @@ -620,8 +731,8 @@ private void ShrinkOverlapsBufferIfNeeded() /// Size in bytes of the copy public void CopyBuffer(MemoryManager memoryManager, ulong srcVa, ulong dstVa, ulong size) { - MultiRange srcRange = TranslateAndCreateMultiBuffers(memoryManager, srcVa, size); - MultiRange dstRange = TranslateAndCreateMultiBuffers(memoryManager, dstVa, size); + MultiRange srcRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, srcVa, size); + MultiRange dstRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, dstVa, size); if (srcRange.Count == 1 && dstRange.Count == 1) { @@ -701,6 +812,8 @@ private void CopyBufferSingleRange(MemoryManager memoryManager, ulong srcAddress dstBuffer.ClearModified(dstAddress, size); memoryManager.Physical.WriteTrackedResource(dstAddress, memoryManager.Physical.GetSpan(srcAddress, (int)size), ResourceKind.Buffer); } + + dstBuffer.CopyToDependantVirtualBuffers(dstAddress, size); } /// @@ -715,7 +828,7 @@ private void CopyBufferSingleRange(MemoryManager memoryManager, ulong srcAddress /// Value to be written into the buffer public void ClearBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size, uint value) { - MultiRange range = TranslateAndCreateMultiBuffers(memoryManager, gpuVa, size); + MultiRange range = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, gpuVa, size); for (int index = 0; index < range.Count; index++) { @@ -727,6 +840,8 @@ public void ClearBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size, ui _context.Renderer.Pipeline.ClearBuffer(buffer.Handle, offset, (int)subRange.Size, value); memoryManager.Physical.FillTrackedResource(subRange.Address, subRange.Size, value, ResourceKind.Buffer); + + buffer.CopyToDependantVirtualBuffers(subRange.Address, subRange.Size); } } @@ -806,6 +921,11 @@ private MultiRangeBuffer GetBuffer(MultiRange range, bool write = false) } } + if (write && buffer != null && !_context.Capabilities.SupportsSparseBuffer) + { + buffer.AddModifiedRegion(range, ++_virtualModifiedSequenceNumber); + } + return buffer; } @@ -825,6 +945,7 @@ private Buffer GetBuffer(ulong address, ulong size, bool write = false) { buffer = _buffers.FindFirstOverlap(address, size); + buffer.CopyFromDependantVirtualBuffers(); buffer.SynchronizeMemory(address, size); if (write) @@ -849,14 +970,14 @@ public void SynchronizeBufferRange(MultiRange range) if (range.Count == 1) { MemoryRange subRange = range.GetSubRange(0); - SynchronizeBufferRange(subRange.Address, subRange.Size); + SynchronizeBufferRange(subRange.Address, subRange.Size, copyBackVirtual: true); } else { for (int index = 0; index < range.Count; index++) { MemoryRange subRange = range.GetSubRange(index); - SynchronizeBufferRange(subRange.Address, subRange.Size); + SynchronizeBufferRange(subRange.Address, subRange.Size, copyBackVirtual: false); } } } @@ -866,12 +987,19 @@ public void SynchronizeBufferRange(MultiRange range) /// /// Start address of the memory range /// Size in bytes of the memory range - private void SynchronizeBufferRange(ulong address, ulong size) + /// Whether virtual buffers that uses this buffer as backing memory should have its data copied back if modified + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SynchronizeBufferRange(ulong address, ulong size, bool copyBackVirtual) { if (size != 0) { Buffer buffer = _buffers.FindFirstOverlap(address, size); + if (copyBackVirtual) + { + buffer.CopyFromDependantVirtualBuffers(); + } + buffer.SynchronizeMemory(address, size); } } diff --git a/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs b/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs index 74d527051..30f87813f 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs @@ -40,9 +40,9 @@ public class MemoryManager : IWritableBlock internal PhysicalMemory Physical { get; } /// - /// Virtual buffer cache. + /// Virtual range cache. /// - internal VirtualBufferCache VirtualBufferCache { get; } + internal VirtualRangeCache VirtualRangeCache { get; } /// /// Cache of GPU counters. @@ -56,12 +56,12 @@ public class MemoryManager : IWritableBlock internal MemoryManager(PhysicalMemory physicalMemory) { Physical = physicalMemory; - VirtualBufferCache = new VirtualBufferCache(this); + VirtualRangeCache = new VirtualRangeCache(this); CounterCache = new CounterCache(); _pageTable = new ulong[PtLvl0Size][]; MemoryUnmapped += Physical.TextureCache.MemoryUnmappedHandler; MemoryUnmapped += Physical.BufferCache.MemoryUnmappedHandler; - MemoryUnmapped += VirtualBufferCache.MemoryUnmappedHandler; + MemoryUnmapped += VirtualRangeCache.MemoryUnmappedHandler; MemoryUnmapped += CounterCache.MemoryUnmappedHandler; } diff --git a/src/Ryujinx.Graphics.Gpu/Memory/MultiRangeBuffer.cs b/src/Ryujinx.Graphics.Gpu/Memory/MultiRangeBuffer.cs index e039a7a43..d92b0836e 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/MultiRangeBuffer.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/MultiRangeBuffer.cs @@ -1,6 +1,7 @@ using Ryujinx.Graphics.GAL; using Ryujinx.Memory.Range; using System; +using System.Collections.Generic; namespace Ryujinx.Graphics.Gpu.Memory { @@ -21,12 +22,73 @@ class MultiRangeBuffer : IMultiRangeItem, IDisposable /// public MultiRange Range { get; } + /// + /// Ever increasing counter value indicating when the buffer was modified relative to other buffers. + /// + public int ModificationSequenceNumber { get; private set; } + + /// + /// Physical buffer dependency entry. + /// + private readonly struct PhysicalDependency + { + /// + /// Physical buffer. + /// + public readonly Buffer PhysicalBuffer; + + /// + /// Offset of the range on the physical buffer. + /// + public readonly ulong PhysicalOffset; + + /// + /// Offset of the range on the virtual buffer. + /// + public readonly ulong VirtualOffset; + + /// + /// Size of the range. + /// + public readonly ulong Size; + + /// + /// Creates a new physical dependency. + /// + /// Physical buffer + /// Offset of the range on the physical buffer + /// Offset of the range on the virtual buffer + /// Size of the range + public PhysicalDependency(Buffer physicalBuffer, ulong physicalOffset, ulong virtualOffset, ulong size) + { + PhysicalBuffer = physicalBuffer; + PhysicalOffset = physicalOffset; + VirtualOffset = virtualOffset; + Size = size; + } + } + + private List _dependencies; + private BufferModifiedRangeList _modifiedRanges = null; + + /// + /// Creates a new instance of the buffer. + /// + /// GPU context that the buffer belongs to + /// Range of memory where the data is mapped + public MultiRangeBuffer(GpuContext context, MultiRange range) + { + _context = context; + Range = range; + Handle = context.Renderer.CreateBuffer((int)range.GetSize()); + } + /// /// Creates a new instance of the buffer. /// /// GPU context that the buffer belongs to /// Range of memory where the data is mapped - /// Backing memory for the buffers + /// Backing memory for the buffer public MultiRangeBuffer(GpuContext context, MultiRange range, ReadOnlySpan storages) { _context = context; @@ -49,11 +111,134 @@ public BufferRange GetRange(MultiRange range) return new BufferRange(Handle, offset, (int)range.GetSize()); } + /// + /// Removes all physical buffer dependencies. + /// + public void ClearPhysicalDependencies() + { + _dependencies?.Clear(); + } + + /// + /// Adds a physical buffer dependency. + /// + /// Physical buffer to be added + /// Address inside the physical buffer where the virtual buffer range is located + /// Offset inside the virtual buffer where the physical range is located + /// Size of the range in bytes + public void AddPhysicalDependency(Buffer buffer, ulong rangeAddress, ulong dstOffset, ulong rangeSize) + { + (_dependencies ??= new()).Add(new(buffer, rangeAddress - buffer.Address, dstOffset, rangeSize)); + buffer.AddVirtualDependency(this); + } + + /// + /// Tries to get the physical range corresponding to the given physical buffer. + /// + /// Physical buffer + /// Minimum virtual offset that a range match can have + /// Physical offset of the match + /// Virtual offset of the match, always greater than or equal + /// Size of the range match + /// True if a match was found for the given parameters, false otherwise + public bool TryGetPhysicalOffset(Buffer buffer, ulong minimumVirtOffset, out ulong physicalOffset, out ulong virtualOffset, out ulong size) + { + physicalOffset = 0; + virtualOffset = 0; + size = 0; + + if (_dependencies != null) + { + foreach (var dependency in _dependencies) + { + if (dependency.PhysicalBuffer == buffer && dependency.VirtualOffset >= minimumVirtOffset) + { + physicalOffset = dependency.PhysicalOffset; + virtualOffset = dependency.VirtualOffset; + size = dependency.Size; + + return true; + } + } + } + + return false; + } + + /// + /// Adds a modified virtual memory range. + /// + /// + /// This is only required when the host does not support sparse buffers, otherwise only physical buffers need to track modification. + /// + /// Modified range + /// ModificationSequenceNumber + public void AddModifiedRegion(MultiRange range, int modifiedSequenceNumber) + { + _modifiedRanges ??= new(_context, null, null); + + for (int i = 0; i < range.Count; i++) + { + MemoryRange subRange = range.GetSubRange(i); + + _modifiedRanges.SignalModified(subRange.Address, subRange.Size); + } + + ModificationSequenceNumber = modifiedSequenceNumber; + } + + /// + /// Calls the specified for all modified ranges that overlaps with . + /// + /// Buffer to have its range checked + /// Action to perform for modified ranges + public void ConsumeModifiedRegion(Buffer buffer, Action rangeAction) + { + ConsumeModifiedRegion(buffer.Address, buffer.Size, rangeAction); + } + + /// + /// Calls the specified for all modified ranges that overlaps with and . + /// + /// Address of the region to consume + /// Size of the region to consume + /// Action to perform for modified ranges + public void ConsumeModifiedRegion(ulong address, ulong size, Action rangeAction) + { + if (_modifiedRanges != null) + { + _modifiedRanges.GetRanges(address, size, rangeAction); + _modifiedRanges.Clear(address, size); + } + } + + /// + /// Gets data from the specified region of the buffer, and places it on . + /// + /// Span to put the data into + /// Offset of the buffer to get the data from + /// Size of the data in bytes + public void GetData(Span output, int offset, int size) + { + using PinnedSpan data = _context.Renderer.GetBufferData(Handle, offset, size); + data.Get().CopyTo(output); + } + /// /// Disposes the host buffer. /// public void Dispose() { + if (_dependencies != null) + { + foreach (var dependency in _dependencies) + { + dependency.PhysicalBuffer.RemoveVirtualDependency(this); + } + + _dependencies = null; + } + _context.Renderer.DeleteBuffer(Handle); } } diff --git a/src/Ryujinx.Graphics.Gpu/Memory/VirtualBufferCache.cs b/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs similarity index 92% rename from src/Ryujinx.Graphics.Gpu/Memory/VirtualBufferCache.cs rename to src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs index 858c5e3b0..889f5c9ca 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/VirtualBufferCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs @@ -6,9 +6,9 @@ namespace Ryujinx.Graphics.Gpu.Memory { /// - /// Virtual buffer cache. + /// Virtual range cache. /// - class VirtualBufferCache + class VirtualRangeCache { private readonly MemoryManager _memoryManager; @@ -68,10 +68,10 @@ public bool OverlapsWith(ulong address, ulong size) private int _hasDeferredUnmaps; /// - /// Creates a new instance of the virtual buffer cache. + /// Creates a new instance of the virtual range cache. /// - /// Memory manager that the virtual buffer cache belongs to - public VirtualBufferCache(MemoryManager memoryManager) + /// Memory manager that the virtual range cache belongs to + public VirtualRangeCache(MemoryManager memoryManager) { _memoryManager = memoryManager; _virtualRanges = new RangeList(); @@ -102,10 +102,9 @@ void EnqueueUnmap() /// /// GPU virtual address to get the physical range from /// Size in bytes of the region - /// Indicates host support for sparse buffer mapping of non-contiguous ranges /// Physical range for the specified GPU virtual region /// True if the range already existed, false if a new one was created and added - public bool TryGetOrAddRange(ulong gpuVa, ulong size, bool supportsSparse, out MultiRange range) + public bool TryGetOrAddRange(ulong gpuVa, ulong size, out MultiRange range) { VirtualRange[] overlaps = _virtualRangeOverlaps; int overlapsCount; @@ -158,7 +157,7 @@ public bool TryGetOrAddRange(ulong gpuVa, ulong size, bool supportsSparse, out M } else { - found = true; + found = overlap0.Range.Count == 1 || IsSparseAligned(overlap0.Range); range = overlap0.Range.Slice(gpuVa - overlap0.Address, size); } } @@ -175,11 +174,10 @@ public bool TryGetOrAddRange(ulong gpuVa, ulong size, bool supportsSparse, out M ShrinkOverlapsBufferIfNeeded(); // If the the range is not properly aligned for sparse mapping, - // or if the host does not support sparse mapping, let's just - // force it to a single range. + // let's just force it to a single range. // This might cause issues in some applications that uses sparse // mappings. - if (!IsSparseAligned(range) || !supportsSparse) + if (!IsSparseAligned(range)) { range = new MultiRange(range.GetSubRange(0).Address, size); } From c43fb92bbf92dbeedd6b0e6299457e6136919f84 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Thu, 22 Feb 2024 13:55:29 -0300 Subject: [PATCH 086/126] Ensure service init runs after Horizon constructor (#6342) --- src/Ryujinx.HLE/HOS/Horizon.cs | 3 +-- src/Ryujinx.HLE/Switch.cs | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs index cd1719580..cd3365ce2 100644 --- a/src/Ryujinx.HLE/HOS/Horizon.cs +++ b/src/Ryujinx.HLE/HOS/Horizon.cs @@ -250,7 +250,6 @@ public Horizon(Switch device) SurfaceFlinger = new SurfaceFlinger(device); InitializeAudioRenderer(TickSource); - InitializeServices(); } private void InitializeAudioRenderer(ITickSource tickSource) @@ -301,7 +300,7 @@ private void InitializeAudioRenderer(ITickSource tickSource) AudioManager.Start(); } - private void InitializeServices() + public void InitializeServices() { SmRegistry = new SmRegistry(); SmServer = new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext, SmRegistry)); diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs index 498714dcd..912a39b04 100644 --- a/src/Ryujinx.HLE/Switch.cs +++ b/src/Ryujinx.HLE/Switch.cs @@ -55,6 +55,7 @@ public Switch(HLEConfiguration configuration) Processes = new ProcessLoader(this); TamperMachine = new TamperMachine(); + System.InitializeServices(); System.State.SetLanguage(Configuration.SystemLanguage); System.State.SetRegion(Configuration.Region); From 57d8afd0c99bb43d1ba1e3cc630d257c5da92741 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Thu, 22 Feb 2024 17:43:19 +0000 Subject: [PATCH 087/126] OpenGL: Mask out all color outputs with no fragment shader (#6341) * OpenGL: Mask out all color outputs with no fragment shader This appears to match Vulkan's behaviour, which is needed for stencil shadows in Penny's Big Breakaway. It's far from the only issue, you can try the Full Bindless PR if you want to see it in a more intact state. * Remove unused member --- src/Ryujinx.Graphics.OpenGL/Pipeline.cs | 2 +- src/Ryujinx.Graphics.OpenGL/Program.cs | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs index e863c696b..0757fcd99 100644 --- a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs +++ b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs @@ -1117,7 +1117,7 @@ public void SetProgram(IProgram program) prg.Bind(); } - if (prg.HasFragmentShader && _fragmentOutputMap != (uint)prg.FragmentOutputMap) + if (_fragmentOutputMap != (uint)prg.FragmentOutputMap) { _fragmentOutputMap = (uint)prg.FragmentOutputMap; diff --git a/src/Ryujinx.Graphics.OpenGL/Program.cs b/src/Ryujinx.Graphics.OpenGL/Program.cs index cc9120c7c..19de06f8f 100644 --- a/src/Ryujinx.Graphics.OpenGL/Program.cs +++ b/src/Ryujinx.Graphics.OpenGL/Program.cs @@ -30,7 +30,6 @@ public bool IsLinked private ProgramLinkStatus _status = ProgramLinkStatus.Incomplete; private int[] _shaderHandles; - public bool HasFragmentShader; public int FragmentOutputMap { get; } public Program(ShaderSource[] shaders, int fragmentOutputMap) @@ -40,6 +39,7 @@ public Program(ShaderSource[] shaders, int fragmentOutputMap) GL.ProgramParameter(Handle, ProgramParameterName.ProgramBinaryRetrievableHint, 1); _shaderHandles = new int[shaders.Length]; + bool hasFragmentShader = false; for (int index = 0; index < shaders.Length; index++) { @@ -47,7 +47,7 @@ public Program(ShaderSource[] shaders, int fragmentOutputMap) if (shader.Stage == ShaderStage.Fragment) { - HasFragmentShader = true; + hasFragmentShader = true; } int shaderHandle = GL.CreateShader(shader.Stage.Convert()); @@ -71,7 +71,7 @@ public Program(ShaderSource[] shaders, int fragmentOutputMap) GL.LinkProgram(Handle); - FragmentOutputMap = fragmentOutputMap; + FragmentOutputMap = hasFragmentShader ? fragmentOutputMap : 0; } public Program(ReadOnlySpan code, bool hasFragmentShader, int fragmentOutputMap) @@ -91,8 +91,7 @@ public Program(ReadOnlySpan code, bool hasFragmentShader, int fragmentOutp } } - HasFragmentShader = hasFragmentShader; - FragmentOutputMap = fragmentOutputMap; + FragmentOutputMap = hasFragmentShader ? fragmentOutputMap : 0; } public void Bind() From d4d0a48bfe89d6e8e12ce16829bb2c440b56007c Mon Sep 17 00:00:00 2001 From: gdkchan Date: Thu, 22 Feb 2024 16:58:33 -0300 Subject: [PATCH 088/126] Migrate Audio service to new IPC (#6285) * Migrate audren to new IPC * Migrate audout * Migrate audin * Migrate hwopus * Bye bye old audio service * Switch volume control to IHardwareDeviceDriver * Somewhat unrelated changes * Remove Concentus reference from HLE * Implement OpenAudioRendererForManualExecution * Remove SetVolume/GetVolume methods that are not necessary * Remove SetVolume/GetVolume methods that are not necessary (2) * Fix incorrect volume update * PR feedback * PR feedback * Stub audrec * Init outParameter * Make FinalOutputRecorderParameter/Internal readonly * Make FinalOutputRecorder IDisposable * Fix HardwareOpusDecoderManager parameter buffers * Opus work buffer size and error handling improvements * Add AudioInProtocolName enum * Fix potential divisions by zero --- .../OpenALHardwareDeviceDriver.cs | 25 +- .../OpenALHardwareDeviceSession.cs | 22 +- .../SDL2HardwareDeviceDriver.cs | 8 +- .../SDL2HardwareDeviceSession.cs | 6 +- .../SoundIoHardwareDeviceDriver.cs | 25 +- .../SoundIoHardwareDeviceSession.cs | 17 +- .../CompatLayerHardwareDeviceDriver.cs | 12 +- .../Dummy/DummyHardwareDeviceDriver.cs | 8 +- .../Dummy/DummyHardwareDeviceSessionOutput.cs | 4 +- src/Ryujinx.Audio/Input/AudioInputManager.cs | 8 +- .../Integration/HardwareDeviceImpl.cs | 4 +- .../Integration/IHardwareDeviceDriver.cs | 4 +- .../Output/AudioOutputManager.cs | 47 +-- .../Renderer/Dsp/AudioProcessor.cs | 34 +- .../Renderer/Server/AudioRendererManager.cs | 28 +- src/Ryujinx.HLE/HOS/Horizon.cs | 95 +---- .../Kernel/SupervisorCall/ExternalEvent.cs | 25 ++ .../HOS/Kernel/SupervisorCall/Syscall.cs | 32 ++ .../HOS/Services/Audio/AudioIn/AudioIn.cs | 108 ----- .../Services/Audio/AudioIn/AudioInServer.cs | 200 --------- .../HOS/Services/Audio/AudioIn/IAudioIn.cs | 34 -- .../HOS/Services/Audio/AudioInManager.cs | 40 -- .../Services/Audio/AudioInManagerServer.cs | 243 ----------- .../HOS/Services/Audio/AudioOut/AudioOut.cs | 108 ----- .../Services/Audio/AudioOut/AudioOutServer.cs | 181 -------- .../HOS/Services/Audio/AudioOut/IAudioOut.cs | 33 -- .../HOS/Services/Audio/AudioOutManager.cs | 40 -- .../Services/Audio/AudioOutManagerServer.cs | 166 -------- .../Audio/AudioRenderer/AudioDevice.cs | 174 -------- .../Audio/AudioRenderer/AudioDeviceServer.cs | 320 --------------- .../Audio/AudioRenderer/AudioKernelEvent.cs | 25 -- .../Audio/AudioRenderer/AudioRenderer.cs | 122 ------ .../AudioRenderer/AudioRendererServer.cs | 215 ---------- .../Audio/AudioRenderer/IAudioDevice.cs | 18 - .../Audio/AudioRenderer/IAudioRenderer.cs | 22 - .../Services/Audio/AudioRendererManager.cs | 67 --- .../Audio/AudioRendererManagerServer.cs | 116 ------ .../HardwareOpusDecoderManager/Decoder.cs | 27 -- .../DecoderCommon.cs | 92 ----- .../HardwareOpusDecoderManager/IDecoder.cs | 11 - .../IHardwareOpusDecoder.cs | 116 ------ .../MultiSampleDecoder.cs | 28 -- .../HOS/Services/Audio/IAudioController.cs | 8 - .../HOS/Services/Audio/IAudioInManager.cs | 12 - .../Audio/IAudioInManagerForApplet.cs | 8 - .../Audio/IAudioInManagerForDebugger.cs | 8 - .../HOS/Services/Audio/IAudioOutManager.cs | 12 - .../Audio/IAudioOutManagerForApplet.cs | 8 - .../Audio/IAudioOutManagerForDebugger.cs | 8 - .../Services/Audio/IAudioRendererManager.cs | 19 - .../Audio/IAudioRendererManagerForApplet.cs | 8 - .../Audio/IAudioRendererManagerForDebugger.cs | 8 - .../HOS/Services/Audio/IAudioSnoopManager.cs | 8 - .../Audio/IFinalOutputRecorderManager.cs | 8 - .../IFinalOutputRecorderManagerForApplet.cs | 8 - .../IFinalOutputRecorderManagerForDebugger.cs | 8 - .../Audio/IHardwareOpusDecoderManager.cs | 227 ---------- .../HOS/Services/Audio/ResultCode.cs | 21 - .../Services/Audio/Types/OpusPacketHeader.cs | 23 -- .../Services/Audio/Types/OpusParametersEx.cs | 15 - src/Ryujinx.HLE/Ryujinx.HLE.csproj | 6 - src/Ryujinx.HLE/Switch.cs | 6 +- src/Ryujinx.Horizon.Common/IExternalEvent.cs | 10 + src/Ryujinx.Horizon.Common/ISyscallApi.cs | 5 + src/Ryujinx.Horizon.Common/Result.cs | 5 + .../Hipc/HipcGenerator.cs | 4 +- src/Ryujinx.Horizon/Arp/ArpIpcServer.cs | 1 + src/Ryujinx.Horizon/Audio/AudioMain.cs | 17 + src/Ryujinx.Horizon/Audio/AudioManagers.cs | 78 ++++ .../Audio/AudioUserIpcServer.cs | 55 +++ src/Ryujinx.Horizon/Audio/HwopusIpcServer.cs | 46 +++ src/Ryujinx.Horizon/Audio/HwopusMain.cs | 17 + src/Ryujinx.Horizon/Bcat/BcatIpcServer.cs | 1 + .../Friends/FriendsIpcServer.cs | 1 + src/Ryujinx.Horizon/HorizonOptions.cs | 14 +- src/Ryujinx.Horizon/Hshl/HshlIpcServer.cs | 1 + src/Ryujinx.Horizon/Ins/InsIpcServer.cs | 1 + src/Ryujinx.Horizon/Lbl/LblIpcServer.cs | 1 + src/Ryujinx.Horizon/LogManager/LmIpcServer.cs | 1 + src/Ryujinx.Horizon/MmNv/MmNvIpcServer.cs | 1 + src/Ryujinx.Horizon/Ngc/NgcIpcServer.cs | 2 +- src/Ryujinx.Horizon/Ovln/OvlnIpcServer.cs | 1 + src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs | 1 + src/Ryujinx.Horizon/Psc/PscIpcServer.cs | 1 + src/Ryujinx.Horizon/Ryujinx.Horizon.csproj | 7 + src/Ryujinx.Horizon/Sdk/Account/Uid.cs | 2 +- src/Ryujinx.Horizon/Sdk/Applet/AppletId.cs | 71 ++++ .../Sdk/Applet/AppletResourceUserId.cs | 15 + src/Ryujinx.Horizon/Sdk/Audio/AudioEvent.cs | 50 +++ src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs | 12 + .../Sdk/Audio/Detail/AudioDevice.cs | 252 ++++++++++++ .../Sdk/Audio/Detail/AudioIn.cs | 171 ++++++++ .../Sdk/Audio/Detail/AudioInManager.cs | 130 ++++++ .../Sdk/Audio/Detail/AudioInProtocol.cs | 23 ++ .../Sdk/Audio/Detail/AudioInProtocolName.cs | 8 + .../Sdk/Audio/Detail/AudioOut.cs | 154 +++++++ .../Sdk/Audio/Detail/AudioOutManager.cs | 93 +++++ .../Sdk/Audio/Detail/AudioRenderer.cs | 187 +++++++++ .../Sdk/Audio/Detail/AudioRendererManager.cs | 132 ++++++ .../Detail/AudioRendererParameterInternal.cs | 14 + .../Sdk/Audio/Detail/AudioSnoopManager.cs | 30 ++ .../Sdk/Audio/Detail/DeviceName.cs | 30 ++ .../Sdk/Audio/Detail/FinalOutputRecorder.cs | 147 +++++++ .../Detail/FinalOutputRecorderManager.cs | 23 ++ .../Detail/FinalOutputRecorderParameter.cs | 17 + .../FinalOutputRecorderParameterInternal.cs | 21 + .../Sdk/Audio/Detail/IAudioDevice.cs | 24 ++ .../Sdk/Audio/Detail/IAudioIn.cs | 26 ++ .../Sdk/Audio/Detail/IAudioInManager.cs | 43 ++ .../Sdk/Audio/Detail/IAudioOut.cs | 25 ++ .../Sdk/Audio/Detail/IAudioOutManager.cs | 32 ++ .../Sdk/Audio/Detail/IAudioRenderer.cs | 24 ++ .../Sdk/Audio/Detail/IAudioRendererManager.cs | 29 ++ .../Sdk/Audio/Detail/IAudioSnoopManager.cs | 12 + .../Sdk/Audio/Detail/IFinalOutputRecorder.cs | 22 + .../Detail/IFinalOutputRecorderManager.cs | 16 + src/Ryujinx.Horizon/Sdk/Codec/CodecResult.cs | 16 + .../Sdk/Codec/Detail/HardwareOpusDecoder.cs | 336 +++++++++++++++ .../Detail/HardwareOpusDecoderManager.cs | 386 ++++++++++++++++++ .../HardwareOpusDecoderParameterInternal.cs | 11 + .../HardwareOpusDecoderParameterInternalEx.cs | 13 + ...pusMultiStreamDecoderParameterInternal.cs} | 6 +- ...sMultiStreamDecoderParameterInternalEx.cs} | 10 +- .../Sdk/Codec/Detail/IHardwareOpusDecoder.cs | 20 + .../Detail/IHardwareOpusDecoderManager.cs | 19 + .../Sdk/Codec/Detail}/OpusDecoderFlags.cs | 2 +- src/Ryujinx.Horizon/ServiceTable.cs | 3 + src/Ryujinx.Horizon/Srepo/SrepoIpcServer.cs | 1 + src/Ryujinx.Horizon/Usb/UsbIpcServer.cs | 1 + src/Ryujinx.Horizon/Wlan/WlanIpcServer.cs | 1 + 130 files changed, 3096 insertions(+), 3174 deletions(-) create mode 100644 src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/ExternalEvent.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManagerServer.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/Decoder.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/DecoderCommon.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IDecoder.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/MultiSampleDecoder.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusParametersEx.cs create mode 100644 src/Ryujinx.Horizon.Common/IExternalEvent.cs create mode 100644 src/Ryujinx.Horizon/Audio/AudioMain.cs create mode 100644 src/Ryujinx.Horizon/Audio/AudioManagers.cs create mode 100644 src/Ryujinx.Horizon/Audio/AudioUserIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Audio/HwopusIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Audio/HwopusMain.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Applet/AppletId.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Applet/AppletResourceUserId.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/AudioEvent.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioIn.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocol.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocolName.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOut.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOutManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererParameterInternal.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioSnoopManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/DeviceName.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorder.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameter.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameterInternal.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioDevice.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioIn.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioInManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOut.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOutManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRendererManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioSnoopManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorder.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorderManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Codec/CodecResult.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternal.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternalEx.cs rename src/{Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParameters.cs => Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternal.cs} (65%) rename src/{Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParametersEx.cs => Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternalEx.cs} (64%) create mode 100644 src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoder.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoderManager.cs rename src/{Ryujinx.HLE/HOS/Services/Audio/Types => Ryujinx.Horizon/Sdk/Codec/Detail}/OpusDecoderFlags.cs (72%) diff --git a/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs b/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs index 744a4bc56..01286992f 100644 --- a/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs +++ b/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs @@ -20,6 +20,25 @@ public class OpenALHardwareDeviceDriver : IHardwareDeviceDriver private bool _stillRunning; private readonly Thread _updaterThread; + private float _volume; + + public float Volume + { + get + { + return _volume; + } + set + { + _volume = value; + + foreach (OpenALHardwareDeviceSession session in _sessions.Keys) + { + session.UpdateMasterVolume(value); + } + } + } + public OpenALHardwareDeviceDriver() { _device = ALC.OpenDevice(""); @@ -34,6 +53,8 @@ public OpenALHardwareDeviceDriver() Name = "HardwareDeviceDriver.OpenAL", }; + _volume = 1f; + _updaterThread.Start(); } @@ -52,7 +73,7 @@ public static bool IsSupported } } - public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume) + public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount) { if (channelCount == 0) { @@ -73,7 +94,7 @@ public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMem throw new ArgumentException($"{channelCount}"); } - OpenALHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount, volume); + OpenALHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount); _sessions.TryAdd(session, 0); diff --git a/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs index a52821616..3b9129130 100644 --- a/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs +++ b/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs @@ -16,10 +16,11 @@ class OpenALHardwareDeviceSession : HardwareDeviceSessionOutputBase private bool _isActive; private readonly Queue _queuedBuffers; private ulong _playedSampleCount; + private float _volume; private readonly object _lock = new(); - public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) + public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) { _driver = driver; _queuedBuffers = new Queue(); @@ -27,7 +28,7 @@ public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMe _targetFormat = GetALFormat(); _isActive = false; _playedSampleCount = 0; - SetVolume(requestedVolume); + SetVolume(1f); } private ALFormat GetALFormat() @@ -85,17 +86,22 @@ public override void QueueBuffer(AudioBuffer buffer) public override void SetVolume(float volume) { - lock (_lock) - { - AL.Source(_sourceId, ALSourcef.Gain, volume); - } + _volume = volume; + + UpdateMasterVolume(_driver.Volume); } public override float GetVolume() { - AL.GetSource(_sourceId, ALSourcef.Gain, out float volume); + return _volume; + } - return volume; + public void UpdateMasterVolume(float newVolume) + { + lock (_lock) + { + AL.Source(_sourceId, ALSourcef.Gain, newVolume * _volume); + } } public override void Start() diff --git a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs index b83e63dbc..e39bfe549 100644 --- a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs +++ b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs @@ -20,6 +20,8 @@ public class SDL2HardwareDeviceDriver : IHardwareDeviceDriver private readonly bool _supportSurroundConfiguration; + public float Volume { get; set; } + // TODO: Add this to SDL2-CS // NOTE: We use a DllImport here because of marshaling issue for spec. #pragma warning disable SYSLIB1054 @@ -48,6 +50,8 @@ public SDL2HardwareDeviceDriver() { _supportSurroundConfiguration = spec.channels >= 6; } + + Volume = 1f; } public static bool IsSupported => IsSupportedInternal(); @@ -74,7 +78,7 @@ public ManualResetEvent GetPauseEvent() return _pauseEvent; } - public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume) + public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount) { if (channelCount == 0) { @@ -91,7 +95,7 @@ public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMem throw new NotImplementedException("Input direction is currently not implemented on SDL2 backend!"); } - SDL2HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount, volume); + SDL2HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount); _sessions.TryAdd(session, 0); diff --git a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs index 7a683f4ed..00188ba58 100644 --- a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs +++ b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs @@ -26,7 +26,7 @@ class SDL2HardwareDeviceSession : HardwareDeviceSessionOutputBase private float _volume; private readonly ushort _nativeSampleFormat; - public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) + public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) { _driver = driver; _updateRequiredEvent = _driver.GetUpdateRequiredEvent(); @@ -37,7 +37,7 @@ public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemory _nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat); _sampleCount = uint.MaxValue; _started = false; - _volume = requestedVolume; + _volume = 1f; } private void EnsureAudioStreamSetup(AudioBuffer buffer) @@ -99,7 +99,7 @@ private unsafe void Update(IntPtr userdata, IntPtr stream, int streamLength) streamSpan.Clear(); // Apply volume to written data - SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_volume * SDL_MIX_MAXVOLUME)); + SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_driver.Volume * _volume * SDL_MIX_MAXVOLUME)); } ulong sampleCount = GetSampleCount(samples.Length); diff --git a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs index ff0392882..e3e5d2913 100644 --- a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs +++ b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs @@ -19,6 +19,25 @@ public class SoundIoHardwareDeviceDriver : IHardwareDeviceDriver private readonly ConcurrentDictionary _sessions; private int _disposeState; + private float _volume = 1f; + + public float Volume + { + get + { + return _volume; + } + set + { + _volume = value; + + foreach (SoundIoHardwareDeviceSession session in _sessions.Keys) + { + session.UpdateMasterVolume(value); + } + } + } + public SoundIoHardwareDeviceDriver() { _audioContext = SoundIoContext.Create(); @@ -122,7 +141,7 @@ public ManualResetEvent GetPauseEvent() return _pauseEvent; } - public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume) + public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount) { if (channelCount == 0) { @@ -134,14 +153,12 @@ public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMem sampleRate = Constants.TargetSampleRate; } - volume = Math.Clamp(volume, 0, 1); - if (direction != Direction.Output) { throw new NotImplementedException("Input direction is currently not implemented on SoundIO backend!"); } - SoundIoHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount, volume); + SoundIoHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount); _sessions.TryAdd(session, 0); diff --git a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs index 123cfd27a..f60982e30 100644 --- a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs +++ b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs @@ -18,16 +18,18 @@ class SoundIoHardwareDeviceSession : HardwareDeviceSessionOutputBase private readonly DynamicRingBuffer _ringBuffer; private ulong _playedSampleCount; private readonly ManualResetEvent _updateRequiredEvent; + private float _volume; private int _disposeState; - public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) + public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) { _driver = driver; _updateRequiredEvent = _driver.GetUpdateRequiredEvent(); _queuedBuffers = new ConcurrentQueue(); _ringBuffer = new DynamicRingBuffer(); + _volume = 1f; - SetupOutputStream(requestedVolume); + SetupOutputStream(driver.Volume); } private void SetupOutputStream(float requestedVolume) @@ -47,7 +49,7 @@ public override ulong GetPlayedSampleCount() public override float GetVolume() { - return _outputStream.Volume; + return _volume; } public override void PrepareToClose() { } @@ -63,7 +65,14 @@ public override void QueueBuffer(AudioBuffer buffer) public override void SetVolume(float volume) { - _outputStream.SetVolume(volume); + _volume = volume; + + _outputStream.SetVolume(_driver.Volume * volume); + } + + public void UpdateMasterVolume(float newVolume) + { + _outputStream.SetVolume(newVolume * _volume); } public override void Start() diff --git a/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs b/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs index 3f3806c3e..a2c2cdcd0 100644 --- a/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs +++ b/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs @@ -16,6 +16,12 @@ public class CompatLayerHardwareDeviceDriver : IHardwareDeviceDriver public static bool IsSupported => true; + public float Volume + { + get => _realDriver.Volume; + set => _realDriver.Volume = value; + } + public CompatLayerHardwareDeviceDriver(IHardwareDeviceDriver realDevice) { _realDriver = realDevice; @@ -90,7 +96,7 @@ private SampleFormat SelectHardwareSampleFormat(SampleFormat targetSampleFormat) throw new ArgumentException("No valid sample format configuration found!"); } - public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume) + public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount) { if (channelCount == 0) { @@ -102,8 +108,6 @@ public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMem sampleRate = Constants.TargetSampleRate; } - volume = Math.Clamp(volume, 0, 1); - if (!_realDriver.SupportsDirection(direction)) { if (direction == Direction.Input) @@ -119,7 +123,7 @@ public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMem SampleFormat hardwareSampleFormat = SelectHardwareSampleFormat(sampleFormat); uint hardwareChannelCount = SelectHardwareChannelCount(channelCount); - IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, hardwareSampleFormat, sampleRate, hardwareChannelCount, volume); + IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, hardwareSampleFormat, sampleRate, hardwareChannelCount); if (hardwareChannelCount == channelCount && hardwareSampleFormat == sampleFormat) { diff --git a/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs index bac21c448..3a3c1d1b1 100644 --- a/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs +++ b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs @@ -14,13 +14,17 @@ public class DummyHardwareDeviceDriver : IHardwareDeviceDriver public static bool IsSupported => true; + public float Volume { get; set; } + public DummyHardwareDeviceDriver() { _updateRequiredEvent = new ManualResetEvent(false); _pauseEvent = new ManualResetEvent(true); + + Volume = 1f; } - public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume) + public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount) { if (sampleRate == 0) { @@ -34,7 +38,7 @@ public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMem if (direction == Direction.Output) { - return new DummyHardwareDeviceSessionOutput(this, memoryManager, sampleFormat, sampleRate, channelCount, volume); + return new DummyHardwareDeviceSessionOutput(this, memoryManager, sampleFormat, sampleRate, channelCount); } return new DummyHardwareDeviceSessionInput(this, memoryManager); diff --git a/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs index 1c248faaa..34cf653c2 100644 --- a/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs +++ b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs @@ -13,9 +13,9 @@ internal class DummyHardwareDeviceSessionOutput : HardwareDeviceSessionOutputBas private ulong _playedSampleCount; - public DummyHardwareDeviceSessionOutput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) + public DummyHardwareDeviceSessionOutput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) { - _volume = requestedVolume; + _volume = 1f; _manager = manager; } diff --git a/src/Ryujinx.Audio/Input/AudioInputManager.cs b/src/Ryujinx.Audio/Input/AudioInputManager.cs index 4d1796c96..d56997e9c 100644 --- a/src/Ryujinx.Audio/Input/AudioInputManager.cs +++ b/src/Ryujinx.Audio/Input/AudioInputManager.cs @@ -166,7 +166,6 @@ internal void Unregister(AudioInputSystem input) /// /// If true, filter disconnected devices /// The list of all audio inputs name -#pragma warning disable CA1822 // Mark member as static public string[] ListAudioIns(bool filtered) { if (filtered) @@ -176,7 +175,6 @@ public string[] ListAudioIns(bool filtered) return new[] { Constants.DefaultDeviceInputName }; } -#pragma warning restore CA1822 /// /// Open a new . @@ -188,8 +186,6 @@ public string[] ListAudioIns(bool filtered) /// The input device name wanted by the user /// The sample format to use /// The user configuration - /// The applet resource user id of the application - /// The process handle of the application /// A reporting an error or a success public ResultCode OpenAudioIn(out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, @@ -197,9 +193,7 @@ public ResultCode OpenAudioIn(out string outputDeviceName, IVirtualMemoryManager memoryManager, string inputDeviceName, SampleFormat sampleFormat, - ref AudioInputConfiguration parameter, - ulong appletResourceUserId, - uint processHandle) + ref AudioInputConfiguration parameter) { int sessionId = AcquireSessionId(); diff --git a/src/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs b/src/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs index 576954b96..1369f953a 100644 --- a/src/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs +++ b/src/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs @@ -13,9 +13,9 @@ public class HardwareDeviceImpl : IHardwareDevice private readonly byte[] _buffer; - public HardwareDeviceImpl(IHardwareDeviceDriver deviceDriver, uint channelCount, uint sampleRate, float volume) + public HardwareDeviceImpl(IHardwareDeviceDriver deviceDriver, uint channelCount, uint sampleRate) { - _session = deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, null, SampleFormat.PcmInt16, sampleRate, channelCount, volume); + _session = deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, null, SampleFormat.PcmInt16, sampleRate, channelCount); _channelCount = channelCount; _sampleRate = sampleRate; _currentBufferTag = 0; diff --git a/src/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs b/src/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs index 9c812fb9a..95b0e4e5e 100644 --- a/src/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs +++ b/src/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs @@ -16,7 +16,9 @@ public enum Direction Output, } - IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume = 1f); + float Volume { get; set; } + + IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount); ManualResetEvent GetUpdateRequiredEvent(); ManualResetEvent GetPauseEvent(); diff --git a/src/Ryujinx.Audio/Output/AudioOutputManager.cs b/src/Ryujinx.Audio/Output/AudioOutputManager.cs index 5232357bb..308cd1564 100644 --- a/src/Ryujinx.Audio/Output/AudioOutputManager.cs +++ b/src/Ryujinx.Audio/Output/AudioOutputManager.cs @@ -165,12 +165,10 @@ internal void Unregister(AudioOutputSystem output) /// Get the list of all audio outputs name. /// /// The list of all audio outputs name -#pragma warning disable CA1822 // Mark member as static public string[] ListAudioOuts() { return new[] { Constants.DefaultDeviceOutputName }; } -#pragma warning restore CA1822 /// /// Open a new . @@ -182,9 +180,6 @@ public string[] ListAudioOuts() /// The input device name wanted by the user /// The sample format to use /// The user configuration - /// The applet resource user id of the application - /// The process handle of the application - /// The volume level to request /// A reporting an error or a success public ResultCode OpenAudioOut(out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, @@ -192,16 +187,13 @@ public ResultCode OpenAudioOut(out string outputDeviceName, IVirtualMemoryManager memoryManager, string inputDeviceName, SampleFormat sampleFormat, - ref AudioInputConfiguration parameter, - ulong appletResourceUserId, - uint processHandle, - float volume) + ref AudioInputConfiguration parameter) { int sessionId = AcquireSessionId(); _sessionsBufferEvents[sessionId].Clear(); - IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount, volume); + IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount); AudioOutputSystem audioOut = new(this, _lock, deviceSession, _sessionsBufferEvents[sessionId]); @@ -234,41 +226,6 @@ public ResultCode OpenAudioOut(out string outputDeviceName, return result; } - /// - /// Sets the volume for all output devices. - /// - /// The volume to set. - public void SetVolume(float volume) - { - if (_sessions != null) - { - foreach (AudioOutputSystem session in _sessions) - { - session?.SetVolume(volume); - } - } - } - - /// - /// Gets the volume for all output devices. - /// - /// A float indicating the volume level. - public float GetVolume() - { - if (_sessions != null) - { - foreach (AudioOutputSystem session in _sessions) - { - if (session != null) - { - return session.GetVolume(); - } - } - } - - return 0.0f; - } - public void Dispose() { GC.SuppressFinalize(this); diff --git a/src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs b/src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs index 9c885b2cf..3e11df056 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs @@ -45,7 +45,6 @@ public AudioProcessor() _event = new ManualResetEvent(false); } -#pragma warning disable IDE0051 // Remove unused private member private static uint GetHardwareChannelCount(IHardwareDeviceDriver deviceDriver) { // Get the real device driver (In case the compat layer is on top of it). @@ -59,9 +58,8 @@ private static uint GetHardwareChannelCount(IHardwareDeviceDriver deviceDriver) // NOTE: We default to stereo as this will get downmixed to mono by the compat layer if it's not compatible. return 2; } -#pragma warning restore IDE0051 - public void Start(IHardwareDeviceDriver deviceDriver, float volume) + public void Start(IHardwareDeviceDriver deviceDriver) { OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax]; @@ -70,7 +68,7 @@ public void Start(IHardwareDeviceDriver deviceDriver, float volume) for (int i = 0; i < OutputDevices.Length; i++) { // TODO: Don't hardcode sample rate. - OutputDevices[i] = new HardwareDeviceImpl(deviceDriver, channelCount, Constants.TargetSampleRate, volume); + OutputDevices[i] = new HardwareDeviceImpl(deviceDriver, channelCount, Constants.TargetSampleRate); } _mailbox = new Mailbox(); @@ -231,33 +229,6 @@ private void Work() _mailbox.SendResponse(MailboxMessage.Stop); } - public float GetVolume() - { - if (OutputDevices != null) - { - foreach (IHardwareDevice outputDevice in OutputDevices) - { - if (outputDevice != null) - { - return outputDevice.GetVolume(); - } - } - } - - return 0f; - } - - public void SetVolume(float volume) - { - if (OutputDevices != null) - { - foreach (IHardwareDevice outputDevice in OutputDevices) - { - outputDevice?.SetVolume(volume); - } - } - } - public void Dispose() { GC.SuppressFinalize(this); @@ -269,6 +240,7 @@ protected virtual void Dispose(bool disposing) if (disposing) { _event.Dispose(); + _mailbox?.Dispose(); } } } diff --git a/src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs b/src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs index 0dbbd26c8..e334a89f6 100644 --- a/src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs +++ b/src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs @@ -177,12 +177,12 @@ private bool HasAnyActiveRendererLocked() /// /// Start the and worker thread. /// - private void StartLocked(float volume) + private void StartLocked() { _isRunning = true; // TODO: virtual device mapping (IAudioDevice) - Processor.Start(_deviceDriver, volume); + Processor.Start(_deviceDriver); _workerThread = new Thread(SendCommands) { @@ -254,7 +254,7 @@ private void SendCommands() /// Register a new . /// /// The to register. - private void Register(AudioRenderSystem renderer, float volume) + private void Register(AudioRenderSystem renderer) { lock (_sessionLock) { @@ -265,7 +265,7 @@ private void Register(AudioRenderSystem renderer, float volume) { if (!_isRunning) { - StartLocked(volume); + StartLocked(); } } } @@ -312,8 +312,7 @@ public ResultCode OpenAudioRenderer( ulong appletResourceUserId, ulong workBufferAddress, ulong workBufferSize, - uint processHandle, - float volume) + uint processHandle) { int sessionId = AcquireSessionId(); @@ -338,7 +337,7 @@ public ResultCode OpenAudioRenderer( { renderer = audioRenderer; - Register(renderer, volume); + Register(renderer); } else { @@ -350,21 +349,6 @@ public ResultCode OpenAudioRenderer( return result; } - public float GetVolume() - { - if (Processor != null) - { - return Processor.GetVolume(); - } - - return 0f; - } - - public void SetVolume(float volume) - { - Processor?.SetVolume(volume); - } - public void Dispose() { GC.SuppressFinalize(this); diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs index cd3365ce2..64b08e309 100644 --- a/src/Ryujinx.HLE/HOS/Horizon.cs +++ b/src/Ryujinx.HLE/HOS/Horizon.cs @@ -4,12 +4,6 @@ using LibHac.Fs.Shim; using LibHac.FsSystem; using LibHac.Tools.FsSystem; -using Ryujinx.Audio; -using Ryujinx.Audio.Input; -using Ryujinx.Audio.Integration; -using Ryujinx.Audio.Output; -using Ryujinx.Audio.Renderer.Device; -using Ryujinx.Audio.Renderer.Server; using Ryujinx.Cpu; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.Kernel; @@ -20,7 +14,6 @@ using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy; using Ryujinx.HLE.HOS.Services.Apm; -using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer; using Ryujinx.HLE.HOS.Services.Caps; using Ryujinx.HLE.HOS.Services.Mii; using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; @@ -61,11 +54,6 @@ public class Horizon : IDisposable internal ITickSource TickSource { get; } internal SurfaceFlinger SurfaceFlinger { get; private set; } - internal AudioManager AudioManager { get; private set; } - internal AudioOutputManager AudioOutputManager { get; private set; } - internal AudioInputManager AudioInputManager { get; private set; } - internal AudioRendererManager AudioRendererManager { get; private set; } - internal VirtualDeviceSessionRegistry AudioDeviceSessionRegistry { get; private set; } public SystemStateMgr State { get; private set; } @@ -79,8 +67,6 @@ public class Horizon : IDisposable internal ServerBase SmServer { get; private set; } internal ServerBase BsdServer { get; private set; } - internal ServerBase AudRenServer { get; private set; } - internal ServerBase AudOutServer { get; private set; } internal ServerBase FsServer { get; private set; } internal ServerBase HidServer { get; private set; } internal ServerBase NvDrvServer { get; private set; } @@ -248,56 +234,6 @@ public Horizon(Switch device) HostSyncpoint = new NvHostSyncpt(device); SurfaceFlinger = new SurfaceFlinger(device); - - InitializeAudioRenderer(TickSource); - } - - private void InitializeAudioRenderer(ITickSource tickSource) - { - AudioManager = new AudioManager(); - AudioOutputManager = new AudioOutputManager(); - AudioInputManager = new AudioInputManager(); - AudioRendererManager = new AudioRendererManager(tickSource); - AudioRendererManager.SetVolume(Device.Configuration.AudioVolume); - AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry(Device.AudioDeviceDriver); - - IWritableEvent[] audioOutputRegisterBufferEvents = new IWritableEvent[Constants.AudioOutSessionCountMax]; - - for (int i = 0; i < audioOutputRegisterBufferEvents.Length; i++) - { - KEvent registerBufferEvent = new(KernelContext); - - audioOutputRegisterBufferEvents[i] = new AudioKernelEvent(registerBufferEvent); - } - - AudioOutputManager.Initialize(Device.AudioDeviceDriver, audioOutputRegisterBufferEvents); - AudioOutputManager.SetVolume(Device.Configuration.AudioVolume); - - IWritableEvent[] audioInputRegisterBufferEvents = new IWritableEvent[Constants.AudioInSessionCountMax]; - - for (int i = 0; i < audioInputRegisterBufferEvents.Length; i++) - { - KEvent registerBufferEvent = new(KernelContext); - - audioInputRegisterBufferEvents[i] = new AudioKernelEvent(registerBufferEvent); - } - - AudioInputManager.Initialize(Device.AudioDeviceDriver, audioInputRegisterBufferEvents); - - IWritableEvent[] systemEvents = new IWritableEvent[Constants.AudioRendererSessionCountMax]; - - for (int i = 0; i < systemEvents.Length; i++) - { - KEvent systemEvent = new(KernelContext); - - systemEvents[i] = new AudioKernelEvent(systemEvent); - } - - AudioManager.Initialize(Device.AudioDeviceDriver.GetUpdateRequiredEvent(), AudioOutputManager.Update, AudioInputManager.Update); - - AudioRendererManager.Initialize(systemEvents, Device.AudioDeviceDriver); - - AudioManager.Start(); } public void InitializeServices() @@ -310,8 +246,6 @@ public void InitializeServices() SmServer.InitDone.WaitOne(); BsdServer = new ServerBase(KernelContext, "BsdServer"); - AudRenServer = new ServerBase(KernelContext, "AudioRendererServer"); - AudOutServer = new ServerBase(KernelContext, "AudioOutServer"); FsServer = new ServerBase(KernelContext, "FsServer"); HidServer = new ServerBase(KernelContext, "HidServer"); NvDrvServer = new ServerBase(KernelContext, "NvservicesServer"); @@ -329,7 +263,13 @@ private void StartNewServices() HorizonFsClient fsClient = new(this); ServiceTable = new ServiceTable(); - var services = ServiceTable.GetServices(new HorizonOptions(Device.Configuration.IgnoreMissingServices, LibHacHorizonManager.BcatClient, fsClient, AccountManager)); + var services = ServiceTable.GetServices(new HorizonOptions + (Device.Configuration.IgnoreMissingServices, + LibHacHorizonManager.BcatClient, + fsClient, + AccountManager, + Device.AudioDeviceDriver, + TickSource)); foreach (var service in services) { @@ -384,17 +324,6 @@ public void ChangeDockedModeState(bool newState) } } - public void SetVolume(float volume) - { - AudioOutputManager.SetVolume(volume); - AudioRendererManager.SetVolume(volume); - } - - public float GetVolume() - { - return AudioOutputManager.GetVolume() == 0 ? AudioRendererManager.GetVolume() : AudioOutputManager.GetVolume(); - } - public void ReturnFocus() { AppletState.SetFocus(true); @@ -458,11 +387,7 @@ protected virtual void Dispose(bool disposing) // "Soft" stops AudioRenderer and AudioManager to avoid some sound between resume and stop. if (IsPaused) { - AudioManager.StopUpdates(); - TogglePauseEmulation(false); - - AudioRendererManager.StopSendingCommands(); } KProcess terminationProcess = new(KernelContext); @@ -513,12 +438,6 @@ protected virtual void Dispose(bool disposing) // This is safe as KThread that are likely to call ioctls are going to be terminated by the post handler hook on the SVC facade. INvDrvServices.Destroy(); - AudioManager.Dispose(); - AudioOutputManager.Dispose(); - AudioInputManager.Dispose(); - - AudioRendererManager.Dispose(); - if (LibHacHorizonManager.ApplicationClient != null) { LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure(); diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/ExternalEvent.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/ExternalEvent.cs new file mode 100644 index 000000000..738d6b64a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/ExternalEvent.cs @@ -0,0 +1,25 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + readonly struct ExternalEvent : IExternalEvent + { + private readonly KWritableEvent _writableEvent; + + public ExternalEvent(KWritableEvent writableEvent) + { + _writableEvent = writableEvent; + } + + public readonly void Signal() + { + _writableEvent.Signal(); + } + + public readonly void Clear() + { + _writableEvent.Clear(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs index b07f5194e..6595ecef2 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs @@ -8,6 +8,7 @@ using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.Horizon.Common; +using Ryujinx.Memory; using System; using System.Buffers; using System.Threading; @@ -3142,6 +3143,37 @@ public Result SynchronizePreemptionState() } #pragma warning restore CA1822 + // Not actual syscalls, used by HLE services and such. + + public IExternalEvent GetExternalEvent(int handle) + { + KWritableEvent writableEvent = KernelStatic.GetCurrentProcess().HandleTable.GetObject(handle); + + if (writableEvent == null) + { + return null; + } + + return new ExternalEvent(writableEvent); + } + + public IVirtualMemoryManager GetMemoryManagerByProcessHandle(int handle) + { + return KernelStatic.GetCurrentProcess().HandleTable.GetKProcess(handle).CpuMemory; + } + + public ulong GetTransferMemoryAddress(int handle) + { + KTransferMemory transferMemory = KernelStatic.GetCurrentProcess().HandleTable.GetObject(handle); + + if (transferMemory == null) + { + return 0; + } + + return transferMemory.Address; + } + private static bool IsPointingInsideKernel(ulong address) { return (address + 0x1000000000) < 0xffffff000; diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs deleted file mode 100644 index acf83f488..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs +++ /dev/null @@ -1,108 +0,0 @@ -using Ryujinx.Audio.Common; -using Ryujinx.Audio.Input; -using Ryujinx.Audio.Integration; -using Ryujinx.HLE.HOS.Kernel; -using Ryujinx.HLE.HOS.Kernel.Threading; -using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer; -using System; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioIn -{ - class AudioIn : IAudioIn - { - private readonly AudioInputSystem _system; - private readonly uint _processHandle; - private readonly KernelContext _kernelContext; - - public AudioIn(AudioInputSystem system, KernelContext kernelContext, uint processHandle) - { - _system = system; - _kernelContext = kernelContext; - _processHandle = processHandle; - } - - public ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer buffer) - { - return (ResultCode)_system.AppendBuffer(bufferTag, ref buffer); - } - - public ResultCode AppendUacBuffer(ulong bufferTag, ref AudioUserBuffer buffer, uint handle) - { - return (ResultCode)_system.AppendUacBuffer(bufferTag, ref buffer, handle); - } - - public bool ContainsBuffer(ulong bufferTag) - { - return _system.ContainsBuffer(bufferTag); - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _system.Dispose(); - - _kernelContext.Syscall.CloseHandle((int)_processHandle); - } - } - - public bool FlushBuffers() - { - return _system.FlushBuffers(); - } - - public uint GetBufferCount() - { - return _system.GetBufferCount(); - } - - public ResultCode GetReleasedBuffers(Span releasedBuffers, out uint releasedCount) - { - return (ResultCode)_system.GetReleasedBuffers(releasedBuffers, out releasedCount); - } - - public AudioDeviceState GetState() - { - return _system.GetState(); - } - - public float GetVolume() - { - return _system.GetVolume(); - } - - public KEvent RegisterBufferEvent() - { - IWritableEvent outEvent = _system.RegisterBufferEvent(); - - if (outEvent is AudioKernelEvent kernelEvent) - { - return kernelEvent.Event; - } - else - { - throw new NotImplementedException(); - } - } - - public void SetVolume(float volume) - { - _system.SetVolume(volume); - } - - public ResultCode Start() - { - return (ResultCode)_system.Start(); - } - - public ResultCode Stop() - { - return (ResultCode)_system.Stop(); - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs deleted file mode 100644 index 3f138021c..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs +++ /dev/null @@ -1,200 +0,0 @@ -using Ryujinx.Audio.Common; -using Ryujinx.Cpu; -using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel.Threading; -using Ryujinx.Horizon.Common; -using Ryujinx.Memory; -using System; -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioIn -{ - class AudioInServer : DisposableIpcService - { - private readonly IAudioIn _impl; - - public AudioInServer(IAudioIn impl) - { - _impl = impl; - } - - [CommandCmif(0)] - // GetAudioInState() -> u32 state - public ResultCode GetAudioInState(ServiceCtx context) - { - context.ResponseData.Write((uint)_impl.GetState()); - - return ResultCode.Success; - } - - [CommandCmif(1)] - // Start() - public ResultCode Start(ServiceCtx context) - { - return _impl.Start(); - } - - [CommandCmif(2)] - // Stop() - public ResultCode StopAudioIn(ServiceCtx context) - { - return _impl.Stop(); - } - - [CommandCmif(3)] - // AppendAudioInBuffer(u64 tag, buffer) - public ResultCode AppendAudioInBuffer(ServiceCtx context) - { - ulong position = context.Request.SendBuff[0].Position; - - ulong bufferTag = context.RequestData.ReadUInt64(); - - AudioUserBuffer data = MemoryHelper.Read(context.Memory, position); - - return _impl.AppendBuffer(bufferTag, ref data); - } - - [CommandCmif(4)] - // RegisterBufferEvent() -> handle - public ResultCode RegisterBufferEvent(ServiceCtx context) - { - KEvent bufferEvent = _impl.RegisterBufferEvent(); - - if (context.Process.HandleTable.GenerateHandle(bufferEvent.ReadableEvent, out int handle) != Result.Success) - { - throw new InvalidOperationException("Out of handles!"); - } - - context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); - - return ResultCode.Success; - } - - [CommandCmif(5)] - // GetReleasedAudioInBuffers() -> (u32 count, buffer tags) - public ResultCode GetReleasedAudioInBuffers(ServiceCtx context) - { - ulong position = context.Request.ReceiveBuff[0].Position; - ulong size = context.Request.ReceiveBuff[0].Size; - - using WritableRegion outputRegion = context.Memory.GetWritableRegion((ulong)position, (int)size); - ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast(outputRegion.Memory.Span), out uint releasedCount); - - context.ResponseData.Write(releasedCount); - - return result; - } - - [CommandCmif(6)] - // ContainsAudioInBuffer(u64 tag) -> b8 - public ResultCode ContainsAudioInBuffer(ServiceCtx context) - { - ulong bufferTag = context.RequestData.ReadUInt64(); - - context.ResponseData.Write(_impl.ContainsBuffer(bufferTag)); - - return ResultCode.Success; - } - - [CommandCmif(7)] // 3.0.0+ - // AppendUacInBuffer(u64 tag, handle, buffer) - public ResultCode AppendUacInBuffer(ServiceCtx context) - { - ulong position = context.Request.SendBuff[0].Position; - - ulong bufferTag = context.RequestData.ReadUInt64(); - uint handle = (uint)context.Request.HandleDesc.ToCopy[0]; - - AudioUserBuffer data = MemoryHelper.Read(context.Memory, position); - - return _impl.AppendUacBuffer(bufferTag, ref data, handle); - } - - [CommandCmif(8)] // 3.0.0+ - // AppendAudioInBufferAuto(u64 tag, buffer) - public ResultCode AppendAudioInBufferAuto(ServiceCtx context) - { - (ulong position, _) = context.Request.GetBufferType0x21(); - - ulong bufferTag = context.RequestData.ReadUInt64(); - - AudioUserBuffer data = MemoryHelper.Read(context.Memory, position); - - return _impl.AppendBuffer(bufferTag, ref data); - } - - [CommandCmif(9)] // 3.0.0+ - // GetReleasedAudioInBuffersAuto() -> (u32 count, buffer tags) - public ResultCode GetReleasedAudioInBuffersAuto(ServiceCtx context) - { - (ulong position, ulong size) = context.Request.GetBufferType0x22(); - - using WritableRegion outputRegion = context.Memory.GetWritableRegion(position, (int)size); - ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast(outputRegion.Memory.Span), out uint releasedCount); - - context.ResponseData.Write(releasedCount); - - return result; - } - - [CommandCmif(10)] // 3.0.0+ - // AppendUacInBufferAuto(u64 tag, handle, buffer) - public ResultCode AppendUacInBufferAuto(ServiceCtx context) - { - (ulong position, _) = context.Request.GetBufferType0x21(); - - ulong bufferTag = context.RequestData.ReadUInt64(); - uint handle = (uint)context.Request.HandleDesc.ToCopy[0]; - - AudioUserBuffer data = MemoryHelper.Read(context.Memory, position); - - return _impl.AppendUacBuffer(bufferTag, ref data, handle); - } - - [CommandCmif(11)] // 4.0.0+ - // GetAudioInBufferCount() -> u32 - public ResultCode GetAudioInBufferCount(ServiceCtx context) - { - context.ResponseData.Write(_impl.GetBufferCount()); - - return ResultCode.Success; - } - - [CommandCmif(12)] // 4.0.0+ - // SetAudioInVolume(s32) - public ResultCode SetAudioInVolume(ServiceCtx context) - { - float volume = context.RequestData.ReadSingle(); - - _impl.SetVolume(volume); - - return ResultCode.Success; - } - - [CommandCmif(13)] // 4.0.0+ - // GetAudioInVolume() -> s32 - public ResultCode GetAudioInVolume(ServiceCtx context) - { - context.ResponseData.Write(_impl.GetVolume()); - - return ResultCode.Success; - } - - [CommandCmif(14)] // 6.0.0+ - // FlushAudioInBuffers() -> b8 - public ResultCode FlushAudioInBuffers(ServiceCtx context) - { - context.ResponseData.Write(_impl.FlushBuffers()); - - return ResultCode.Success; - } - - protected override void Dispose(bool isDisposing) - { - if (isDisposing) - { - _impl.Dispose(); - } - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs deleted file mode 100644 index 4e67303df..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Ryujinx.Audio.Common; -using Ryujinx.HLE.HOS.Kernel.Threading; -using System; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioIn -{ - interface IAudioIn : IDisposable - { - AudioDeviceState GetState(); - - ResultCode Start(); - - ResultCode Stop(); - - ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer buffer); - - // NOTE: This is broken by design... not quite sure what it's used for (if anything in production). - ResultCode AppendUacBuffer(ulong bufferTag, ref AudioUserBuffer buffer, uint handle); - - KEvent RegisterBufferEvent(); - - ResultCode GetReleasedBuffers(Span releasedBuffers, out uint releasedCount); - - bool ContainsBuffer(ulong bufferTag); - - uint GetBufferCount(); - - bool FlushBuffers(); - - void SetVolume(float volume); - - float GetVolume(); - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs deleted file mode 100644 index 1e759e0ca..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Ryujinx.Audio.Common; -using Ryujinx.Audio.Input; -using Ryujinx.HLE.HOS.Services.Audio.AudioIn; -using AudioInManagerImpl = Ryujinx.Audio.Input.AudioInputManager; - -namespace Ryujinx.HLE.HOS.Services.Audio -{ - class AudioInManager : IAudioInManager - { - private readonly AudioInManagerImpl _impl; - - public AudioInManager(AudioInManagerImpl impl) - { - _impl = impl; - } - - public string[] ListAudioIns(bool filtered) - { - return _impl.ListAudioIns(filtered); - } - - public ResultCode OpenAudioIn(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle) - { - var memoryManager = context.Process.HandleTable.GetKProcess((int)processHandle).CpuMemory; - - ResultCode result = (ResultCode)_impl.OpenAudioIn(out outputDeviceName, out outputConfiguration, out AudioInputSystem inSystem, memoryManager, inputDeviceName, SampleFormat.PcmInt16, ref parameter, appletResourceUserId, processHandle); - - if (result == ResultCode.Success) - { - obj = new AudioIn.AudioIn(inSystem, context.Device.System.KernelContext, processHandle); - } - else - { - obj = null; - } - - return result; - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs deleted file mode 100644 index 1b35a62d8..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs +++ /dev/null @@ -1,243 +0,0 @@ -using Ryujinx.Audio.Common; -using Ryujinx.Common; -using Ryujinx.Common.Logging; -using Ryujinx.Cpu; -using Ryujinx.HLE.HOS.Services.Audio.AudioIn; -using System.Text; - -namespace Ryujinx.HLE.HOS.Services.Audio -{ - [Service("audin:u")] - class AudioInManagerServer : IpcService - { - private const int AudioInNameSize = 0x100; - - private readonly IAudioInManager _impl; - - public AudioInManagerServer(ServiceCtx context) : this(context, new AudioInManager(context.Device.System.AudioInputManager)) { } - - public AudioInManagerServer(ServiceCtx context, IAudioInManager impl) : base(context.Device.System.AudOutServer) - { - _impl = impl; - } - - [CommandCmif(0)] - // ListAudioIns() -> (u32, buffer) - public ResultCode ListAudioIns(ServiceCtx context) - { - string[] deviceNames = _impl.ListAudioIns(false); - - ulong position = context.Request.ReceiveBuff[0].Position; - ulong size = context.Request.ReceiveBuff[0].Size; - - ulong basePosition = position; - - int count = 0; - - foreach (string name in deviceNames) - { - byte[] buffer = Encoding.ASCII.GetBytes(name); - - if ((position - basePosition) + (ulong)buffer.Length > size) - { - Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); - - break; - } - - context.Memory.Write(position, buffer); - MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioInNameSize - buffer.Length); - - position += AudioInNameSize; - count++; - } - - context.ResponseData.Write(count); - - return ResultCode.Success; - } - - [CommandCmif(1)] - // OpenAudioIn(AudioInInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle, buffer name) - // -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object, buffer name) - public ResultCode OpenAudioIn(ServiceCtx context) - { - AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct(); - ulong appletResourceUserId = context.RequestData.ReadUInt64(); - - ulong deviceNameInputPosition = context.Request.SendBuff[0].Position; - ulong deviceNameInputSize = context.Request.SendBuff[0].Size; - - ulong deviceNameOutputPosition = context.Request.ReceiveBuff[0].Position; -#pragma warning disable IDE0059 // Remove unnecessary value assignment - ulong deviceNameOutputSize = context.Request.ReceiveBuff[0].Size; -#pragma warning restore IDE0059 - - uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0]; - - string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize); - - ResultCode resultCode = _impl.OpenAudioIn(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle); - - if (resultCode == ResultCode.Success) - { - context.ResponseData.WriteStruct(outputConfiguration); - - byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName); - - context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw); - MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioInNameSize - outputDeviceNameRaw.Length); - - MakeObject(context, new AudioInServer(obj)); - } - - return resultCode; - } - - [CommandCmif(2)] // 3.0.0+ - // ListAudioInsAuto() -> (u32, buffer) - public ResultCode ListAudioInsAuto(ServiceCtx context) - { - string[] deviceNames = _impl.ListAudioIns(false); - - (ulong position, ulong size) = context.Request.GetBufferType0x22(); - - ulong basePosition = position; - - int count = 0; - - foreach (string name in deviceNames) - { - byte[] buffer = Encoding.ASCII.GetBytes(name); - - if ((position - basePosition) + (ulong)buffer.Length > size) - { - Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); - - break; - } - - context.Memory.Write(position, buffer); - MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioInNameSize - buffer.Length); - - position += AudioInNameSize; - count++; - } - - context.ResponseData.Write(count); - - return ResultCode.Success; - } - - [CommandCmif(3)] // 3.0.0+ - // OpenAudioInAuto(AudioInInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle, buffer) - // -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object, buffer name) - public ResultCode OpenAudioInAuto(ServiceCtx context) - { - AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct(); - ulong appletResourceUserId = context.RequestData.ReadUInt64(); - - (ulong deviceNameInputPosition, ulong deviceNameInputSize) = context.Request.GetBufferType0x21(); -#pragma warning disable IDE0059 // Remove unnecessary value assignment - (ulong deviceNameOutputPosition, ulong deviceNameOutputSize) = context.Request.GetBufferType0x22(); -#pragma warning restore IDE0059 - - uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0]; - - string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize); - - ResultCode resultCode = _impl.OpenAudioIn(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle); - - if (resultCode == ResultCode.Success) - { - context.ResponseData.WriteStruct(outputConfiguration); - - byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName); - - context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw); - MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioInNameSize - outputDeviceNameRaw.Length); - - MakeObject(context, new AudioInServer(obj)); - } - - return resultCode; - } - - [CommandCmif(4)] // 3.0.0+ - // ListAudioInsAutoFiltered() -> (u32, buffer) - public ResultCode ListAudioInsAutoFiltered(ServiceCtx context) - { - string[] deviceNames = _impl.ListAudioIns(true); - - (ulong position, ulong size) = context.Request.GetBufferType0x22(); - - ulong basePosition = position; - - int count = 0; - - foreach (string name in deviceNames) - { - byte[] buffer = Encoding.ASCII.GetBytes(name); - - if ((position - basePosition) + (ulong)buffer.Length > size) - { - Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); - - break; - } - - context.Memory.Write(position, buffer); - MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioInNameSize - buffer.Length); - - position += AudioInNameSize; - count++; - } - - context.ResponseData.Write(count); - - return ResultCode.Success; - } - - [CommandCmif(5)] // 5.0.0+ - // OpenAudioInProtocolSpecified(b64 protocol_specified_related, AudioInInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle, buffer name) - // -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object, buffer name) - public ResultCode OpenAudioInProtocolSpecified(ServiceCtx context) - { - // NOTE: We always assume that only the default device will be plugged (we never report any USB Audio Class type devices). -#pragma warning disable IDE0059 // Remove unnecessary value assignment - bool protocolSpecifiedRelated = context.RequestData.ReadUInt64() == 1; -#pragma warning restore IDE0059 - - AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct(); - ulong appletResourceUserId = context.RequestData.ReadUInt64(); - - ulong deviceNameInputPosition = context.Request.SendBuff[0].Position; - ulong deviceNameInputSize = context.Request.SendBuff[0].Size; - - ulong deviceNameOutputPosition = context.Request.ReceiveBuff[0].Position; -#pragma warning disable IDE0051, IDE0059 // Remove unused private member - ulong deviceNameOutputSize = context.Request.ReceiveBuff[0].Size; -#pragma warning restore IDE0051, IDE0059 - - uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0]; - - string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize); - - ResultCode resultCode = _impl.OpenAudioIn(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle); - - if (resultCode == ResultCode.Success) - { - context.ResponseData.WriteStruct(outputConfiguration); - - byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName); - - context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw); - MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioInNameSize - outputDeviceNameRaw.Length); - - MakeObject(context, new AudioInServer(obj)); - } - - return resultCode; - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs deleted file mode 100644 index 2ccf0657f..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs +++ /dev/null @@ -1,108 +0,0 @@ -using Ryujinx.Audio.Common; -using Ryujinx.Audio.Integration; -using Ryujinx.Audio.Output; -using Ryujinx.HLE.HOS.Kernel; -using Ryujinx.HLE.HOS.Kernel.Threading; -using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer; -using System; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioOut -{ - class AudioOut : IAudioOut - { - private readonly AudioOutputSystem _system; - private readonly uint _processHandle; - private readonly KernelContext _kernelContext; - - public AudioOut(AudioOutputSystem system, KernelContext kernelContext, uint processHandle) - { - _system = system; - _kernelContext = kernelContext; - _processHandle = processHandle; - } - - public ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer buffer) - { - return (ResultCode)_system.AppendBuffer(bufferTag, ref buffer); - } - - public bool ContainsBuffer(ulong bufferTag) - { - return _system.ContainsBuffer(bufferTag); - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _system.Dispose(); - - _kernelContext.Syscall.CloseHandle((int)_processHandle); - } - } - - public bool FlushBuffers() - { - return _system.FlushBuffers(); - } - - public uint GetBufferCount() - { - return _system.GetBufferCount(); - } - - public ulong GetPlayedSampleCount() - { - return _system.GetPlayedSampleCount(); - } - - public ResultCode GetReleasedBuffers(Span releasedBuffers, out uint releasedCount) - { - return (ResultCode)_system.GetReleasedBuffer(releasedBuffers, out releasedCount); - } - - public AudioDeviceState GetState() - { - return _system.GetState(); - } - - public float GetVolume() - { - return _system.GetVolume(); - } - - public KEvent RegisterBufferEvent() - { - IWritableEvent outEvent = _system.RegisterBufferEvent(); - - if (outEvent is AudioKernelEvent kernelEvent) - { - return kernelEvent.Event; - } - else - { - throw new NotImplementedException(); - } - } - - public void SetVolume(float volume) - { - _system.SetVolume(volume); - } - - public ResultCode Start() - { - return (ResultCode)_system.Start(); - } - - public ResultCode Stop() - { - return (ResultCode)_system.Stop(); - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs deleted file mode 100644 index e1b252631..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs +++ /dev/null @@ -1,181 +0,0 @@ -using Ryujinx.Audio.Common; -using Ryujinx.Cpu; -using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel.Threading; -using Ryujinx.Horizon.Common; -using Ryujinx.Memory; -using System; -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioOut -{ - class AudioOutServer : DisposableIpcService - { - private readonly IAudioOut _impl; - - public AudioOutServer(IAudioOut impl) - { - _impl = impl; - } - - [CommandCmif(0)] - // GetAudioOutState() -> u32 state - public ResultCode GetAudioOutState(ServiceCtx context) - { - context.ResponseData.Write((uint)_impl.GetState()); - - return ResultCode.Success; - } - - [CommandCmif(1)] - // Start() - public ResultCode Start(ServiceCtx context) - { - return _impl.Start(); - } - - [CommandCmif(2)] - // Stop() - public ResultCode Stop(ServiceCtx context) - { - return _impl.Stop(); - } - - [CommandCmif(3)] - // AppendAudioOutBuffer(u64 bufferTag, buffer buffer) - public ResultCode AppendAudioOutBuffer(ServiceCtx context) - { - ulong position = context.Request.SendBuff[0].Position; - - ulong bufferTag = context.RequestData.ReadUInt64(); - - AudioUserBuffer data = MemoryHelper.Read(context.Memory, position); - - return _impl.AppendBuffer(bufferTag, ref data); - } - - [CommandCmif(4)] - // RegisterBufferEvent() -> handle - public ResultCode RegisterBufferEvent(ServiceCtx context) - { - KEvent bufferEvent = _impl.RegisterBufferEvent(); - - if (context.Process.HandleTable.GenerateHandle(bufferEvent.ReadableEvent, out int handle) != Result.Success) - { - throw new InvalidOperationException("Out of handles!"); - } - - context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); - - return ResultCode.Success; - } - - [CommandCmif(5)] - // GetReleasedAudioOutBuffers() -> (u32 count, buffer tags) - public ResultCode GetReleasedAudioOutBuffers(ServiceCtx context) - { - ulong position = context.Request.ReceiveBuff[0].Position; - ulong size = context.Request.ReceiveBuff[0].Size; - - using WritableRegion outputRegion = context.Memory.GetWritableRegion(position, (int)size); - ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast(outputRegion.Memory.Span), out uint releasedCount); - - context.ResponseData.Write(releasedCount); - - return result; - } - - [CommandCmif(6)] - // ContainsAudioOutBuffer(u64 tag) -> b8 - public ResultCode ContainsAudioOutBuffer(ServiceCtx context) - { - ulong bufferTag = context.RequestData.ReadUInt64(); - - context.ResponseData.Write(_impl.ContainsBuffer(bufferTag)); - - return ResultCode.Success; - } - - [CommandCmif(7)] // 3.0.0+ - // AppendAudioOutBufferAuto(u64 tag, buffer) - public ResultCode AppendAudioOutBufferAuto(ServiceCtx context) - { - (ulong position, _) = context.Request.GetBufferType0x21(); - - ulong bufferTag = context.RequestData.ReadUInt64(); - - AudioUserBuffer data = MemoryHelper.Read(context.Memory, position); - - return _impl.AppendBuffer(bufferTag, ref data); - } - - [CommandCmif(8)] // 3.0.0+ - // GetReleasedAudioOutBuffersAuto() -> (u32 count, buffer tags) - public ResultCode GetReleasedAudioOutBuffersAuto(ServiceCtx context) - { - (ulong position, ulong size) = context.Request.GetBufferType0x22(); - - using WritableRegion outputRegion = context.Memory.GetWritableRegion(position, (int)size); - ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast(outputRegion.Memory.Span), out uint releasedCount); - - context.ResponseData.Write(releasedCount); - - return result; - } - - [CommandCmif(9)] // 4.0.0+ - // GetAudioOutBufferCount() -> u32 - public ResultCode GetAudioOutBufferCount(ServiceCtx context) - { - context.ResponseData.Write(_impl.GetBufferCount()); - - return ResultCode.Success; - } - - [CommandCmif(10)] // 4.0.0+ - // GetAudioOutPlayedSampleCount() -> u64 - public ResultCode GetAudioOutPlayedSampleCount(ServiceCtx context) - { - context.ResponseData.Write(_impl.GetPlayedSampleCount()); - - return ResultCode.Success; - } - - [CommandCmif(11)] // 4.0.0+ - // FlushAudioOutBuffers() -> b8 - public ResultCode FlushAudioOutBuffers(ServiceCtx context) - { - context.ResponseData.Write(_impl.FlushBuffers()); - - return ResultCode.Success; - } - - [CommandCmif(12)] // 6.0.0+ - // SetAudioOutVolume(s32) - public ResultCode SetAudioOutVolume(ServiceCtx context) - { - float volume = context.RequestData.ReadSingle(); - - _impl.SetVolume(volume); - - return ResultCode.Success; - } - - [CommandCmif(13)] // 6.0.0+ - // GetAudioOutVolume() -> s32 - public ResultCode GetAudioOutVolume(ServiceCtx context) - { - context.ResponseData.Write(_impl.GetVolume()); - - return ResultCode.Success; - } - - protected override void Dispose(bool isDisposing) - { - if (isDisposing) - { - _impl.Dispose(); - } - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs deleted file mode 100644 index 8c8c68629..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Ryujinx.Audio.Common; -using Ryujinx.HLE.HOS.Kernel.Threading; -using System; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioOut -{ - interface IAudioOut : IDisposable - { - AudioDeviceState GetState(); - - ResultCode Start(); - - ResultCode Stop(); - - ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer buffer); - - KEvent RegisterBufferEvent(); - - ResultCode GetReleasedBuffers(Span releasedBuffers, out uint releasedCount); - - bool ContainsBuffer(ulong bufferTag); - - uint GetBufferCount(); - - ulong GetPlayedSampleCount(); - - bool FlushBuffers(); - - void SetVolume(float volume); - - float GetVolume(); - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs deleted file mode 100644 index c45a485bc..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Ryujinx.Audio.Common; -using Ryujinx.Audio.Output; -using Ryujinx.HLE.HOS.Services.Audio.AudioOut; -using AudioOutManagerImpl = Ryujinx.Audio.Output.AudioOutputManager; - -namespace Ryujinx.HLE.HOS.Services.Audio -{ - class AudioOutManager : IAudioOutManager - { - private readonly AudioOutManagerImpl _impl; - - public AudioOutManager(AudioOutManagerImpl impl) - { - _impl = impl; - } - - public string[] ListAudioOuts() - { - return _impl.ListAudioOuts(); - } - - public ResultCode OpenAudioOut(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle, float volume) - { - var memoryManager = context.Process.HandleTable.GetKProcess((int)processHandle).CpuMemory; - - ResultCode result = (ResultCode)_impl.OpenAudioOut(out outputDeviceName, out outputConfiguration, out AudioOutputSystem outSystem, memoryManager, inputDeviceName, SampleFormat.PcmInt16, ref parameter, appletResourceUserId, processHandle, volume); - - if (result == ResultCode.Success) - { - obj = new AudioOut.AudioOut(outSystem, context.Device.System.KernelContext, processHandle); - } - else - { - obj = null; - } - - return result; - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs deleted file mode 100644 index 79ae6a141..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs +++ /dev/null @@ -1,166 +0,0 @@ -using Ryujinx.Audio.Common; -using Ryujinx.Common; -using Ryujinx.Common.Logging; -using Ryujinx.Cpu; -using Ryujinx.HLE.HOS.Services.Audio.AudioOut; -using System.Text; - -namespace Ryujinx.HLE.HOS.Services.Audio -{ - [Service("audout:u")] - class AudioOutManagerServer : IpcService - { - private const int AudioOutNameSize = 0x100; - - private readonly IAudioOutManager _impl; - - public AudioOutManagerServer(ServiceCtx context) : this(context, new AudioOutManager(context.Device.System.AudioOutputManager)) { } - - public AudioOutManagerServer(ServiceCtx context, IAudioOutManager impl) : base(context.Device.System.AudOutServer) - { - _impl = impl; - } - - [CommandCmif(0)] - // ListAudioOuts() -> (u32, buffer) - public ResultCode ListAudioOuts(ServiceCtx context) - { - string[] deviceNames = _impl.ListAudioOuts(); - - ulong position = context.Request.ReceiveBuff[0].Position; - ulong size = context.Request.ReceiveBuff[0].Size; - - ulong basePosition = position; - - int count = 0; - - foreach (string name in deviceNames) - { - byte[] buffer = Encoding.ASCII.GetBytes(name); - - if ((position - basePosition) + (ulong)buffer.Length > size) - { - Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); - - break; - } - - context.Memory.Write(position, buffer); - MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioOutNameSize - buffer.Length); - - position += AudioOutNameSize; - count++; - } - - context.ResponseData.Write(count); - - return ResultCode.Success; - } - - [CommandCmif(1)] - // OpenAudioOut(AudioOutInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle process_handle, buffer name_in) - // -> (AudioOutInputConfiguration output_config, object, buffer name_out) - public ResultCode OpenAudioOut(ServiceCtx context) - { - AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct(); - ulong appletResourceUserId = context.RequestData.ReadUInt64(); - - ulong deviceNameInputPosition = context.Request.SendBuff[0].Position; - ulong deviceNameInputSize = context.Request.SendBuff[0].Size; - - ulong deviceNameOutputPosition = context.Request.ReceiveBuff[0].Position; -#pragma warning disable IDE0059 // Remove unnecessary value assignment - ulong deviceNameOutputSize = context.Request.ReceiveBuff[0].Size; -#pragma warning restore IDE0059 - - uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0]; - - string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize); - - ResultCode resultCode = _impl.OpenAudioOut(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle, context.Device.Configuration.AudioVolume); - - if (resultCode == ResultCode.Success) - { - context.ResponseData.WriteStruct(outputConfiguration); - - byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName); - - context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw); - MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioOutNameSize - outputDeviceNameRaw.Length); - - MakeObject(context, new AudioOutServer(obj)); - } - - return resultCode; - } - - [CommandCmif(2)] // 3.0.0+ - // ListAudioOutsAuto() -> (u32, buffer) - public ResultCode ListAudioOutsAuto(ServiceCtx context) - { - string[] deviceNames = _impl.ListAudioOuts(); - - (ulong position, ulong size) = context.Request.GetBufferType0x22(); - - ulong basePosition = position; - - int count = 0; - - foreach (string name in deviceNames) - { - byte[] buffer = Encoding.ASCII.GetBytes(name); - - if ((position - basePosition) + (ulong)buffer.Length > size) - { - Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); - - break; - } - - context.Memory.Write(position, buffer); - MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioOutNameSize - buffer.Length); - - position += AudioOutNameSize; - count++; - } - - context.ResponseData.Write(count); - - return ResultCode.Success; - } - - [CommandCmif(3)] // 3.0.0+ - // OpenAudioOut(AudioOutInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle process_handle, buffer name_in) - // -> (AudioOutInputConfiguration output_config, object, buffer name_out) - public ResultCode OpenAudioOutAuto(ServiceCtx context) - { - AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct(); - ulong appletResourceUserId = context.RequestData.ReadUInt64(); - - (ulong deviceNameInputPosition, ulong deviceNameInputSize) = context.Request.GetBufferType0x21(); -#pragma warning disable IDE0059 // Remove unnecessary value assignment - (ulong deviceNameOutputPosition, ulong deviceNameOutputSize) = context.Request.GetBufferType0x22(); -#pragma warning restore IDE0059 - - uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0]; - - string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize); - - ResultCode resultCode = _impl.OpenAudioOut(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle, context.Device.Configuration.AudioVolume); - - if (resultCode == ResultCode.Success) - { - context.ResponseData.WriteStruct(outputConfiguration); - - byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName); - - context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw); - MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioOutNameSize - outputDeviceNameRaw.Length); - - MakeObject(context, new AudioOutServer(obj)); - } - - return resultCode; - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs deleted file mode 100644 index 6497a3b84..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs +++ /dev/null @@ -1,174 +0,0 @@ -using Ryujinx.Audio.Renderer.Device; -using Ryujinx.Audio.Renderer.Server; -using Ryujinx.HLE.HOS.Kernel; -using Ryujinx.HLE.HOS.Kernel.Threading; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer -{ - class AudioDevice : IAudioDevice - { - private readonly VirtualDeviceSession[] _sessions; -#pragma warning disable IDE0052 // Remove unread private member - private readonly ulong _appletResourceId; - private readonly int _revision; -#pragma warning restore IDE0052 - private readonly bool _isUsbDeviceSupported; - - private readonly VirtualDeviceSessionRegistry _registry; - private readonly KEvent _systemEvent; - - public AudioDevice(VirtualDeviceSessionRegistry registry, KernelContext context, ulong appletResourceId, int revision) - { - _registry = registry; - _appletResourceId = appletResourceId; - _revision = revision; - - BehaviourContext behaviourContext = new(); - behaviourContext.SetUserRevision(revision); - - _isUsbDeviceSupported = behaviourContext.IsAudioUsbDeviceOutputSupported(); - _sessions = _registry.GetSessionByAppletResourceId(appletResourceId); - - // TODO: support the 3 different events correctly when we will have hot plugable audio devices. - _systemEvent = new KEvent(context); - _systemEvent.ReadableEvent.Signal(); - } - - private bool TryGetDeviceByName(out VirtualDeviceSession result, string name, bool ignoreRevLimitation = false) - { - result = null; - - foreach (VirtualDeviceSession session in _sessions) - { - if (session.Device.Name.Equals(name)) - { - if (!ignoreRevLimitation && !_isUsbDeviceSupported && session.Device.IsUsbDevice()) - { - return false; - } - - result = session; - - return true; - } - } - - return false; - } - - public string GetActiveAudioDeviceName() - { - VirtualDevice device = _registry.ActiveDevice; - - if (!_isUsbDeviceSupported && device.IsUsbDevice()) - { - device = _registry.DefaultDevice; - } - - return device.Name; - } - - public uint GetActiveChannelCount() - { - VirtualDevice device = _registry.ActiveDevice; - - if (!_isUsbDeviceSupported && device.IsUsbDevice()) - { - device = _registry.DefaultDevice; - } - - return device.ChannelCount; - } - - public ResultCode GetAudioDeviceOutputVolume(string name, out float volume) - { - if (TryGetDeviceByName(out VirtualDeviceSession result, name)) - { - volume = result.Volume; - } - else - { - volume = 0.0f; - } - - return ResultCode.Success; - } - - public ResultCode SetAudioDeviceOutputVolume(string name, float volume) - { - if (TryGetDeviceByName(out VirtualDeviceSession result, name, true)) - { - if (!_isUsbDeviceSupported && result.Device.IsUsbDevice()) - { - result = _sessions[0]; - } - - result.Volume = volume; - } - - return ResultCode.Success; - } - - public string GetActiveAudioOutputDeviceName() - { - return _registry.ActiveDevice.GetOutputDeviceName(); - } - - public string[] ListAudioDeviceName() - { - int deviceCount = _sessions.Length; - - if (!_isUsbDeviceSupported) - { - deviceCount--; - } - - string[] result = new string[deviceCount]; - - int i = 0; - - foreach (VirtualDeviceSession session in _sessions) - { - if (!_isUsbDeviceSupported && session.Device.IsUsbDevice()) - { - continue; - } - - result[i] = session.Device.Name; - - i++; - } - - return result; - } - - public string[] ListAudioOutputDeviceName() - { - int deviceCount = _sessions.Length; - - string[] result = new string[deviceCount]; - - for (int i = 0; i < deviceCount; i++) - { - result[i] = _sessions[i].Device.GetOutputDeviceName(); - } - - return result; - } - - public KEvent QueryAudioDeviceInputEvent() - { - return _systemEvent; - } - - public KEvent QueryAudioDeviceOutputEvent() - { - return _systemEvent; - } - - public KEvent QueryAudioDeviceSystemEvent() - { - return _systemEvent; - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs deleted file mode 100644 index 6206215d5..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs +++ /dev/null @@ -1,320 +0,0 @@ -using Ryujinx.Common.Logging; -using Ryujinx.Cpu; -using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel.Threading; -using Ryujinx.Horizon.Common; -using System; -using System.Text; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer -{ - class AudioDeviceServer : IpcService - { - private const int AudioDeviceNameSize = 0x100; - - private readonly IAudioDevice _impl; - - public AudioDeviceServer(IAudioDevice impl) - { - _impl = impl; - } - - [CommandCmif(0)] - // ListAudioDeviceName() -> (u32, buffer) - public ResultCode ListAudioDeviceName(ServiceCtx context) - { - string[] deviceNames = _impl.ListAudioDeviceName(); - - ulong position = context.Request.ReceiveBuff[0].Position; - ulong size = context.Request.ReceiveBuff[0].Size; - - ulong basePosition = position; - - int count = 0; - - foreach (string name in deviceNames) - { - byte[] buffer = Encoding.ASCII.GetBytes(name); - - if ((position - basePosition) + (ulong)buffer.Length > size) - { - break; - } - - context.Memory.Write(position, buffer); - MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioDeviceNameSize - buffer.Length); - - position += AudioDeviceNameSize; - count++; - } - - context.ResponseData.Write(count); - - return ResultCode.Success; - } - - [CommandCmif(1)] - // SetAudioDeviceOutputVolume(f32 volume, buffer name) - public ResultCode SetAudioDeviceOutputVolume(ServiceCtx context) - { - float volume = context.RequestData.ReadSingle(); - - ulong position = context.Request.SendBuff[0].Position; - ulong size = context.Request.SendBuff[0].Size; - - string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size); - - return _impl.SetAudioDeviceOutputVolume(deviceName, volume); - } - - [CommandCmif(2)] - // GetAudioDeviceOutputVolume(buffer name) -> f32 volume - public ResultCode GetAudioDeviceOutputVolume(ServiceCtx context) - { - ulong position = context.Request.SendBuff[0].Position; - ulong size = context.Request.SendBuff[0].Size; - - string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size); - - ResultCode result = _impl.GetAudioDeviceOutputVolume(deviceName, out float volume); - - if (result == ResultCode.Success) - { - context.ResponseData.Write(volume); - } - - return result; - } - - [CommandCmif(3)] - // GetActiveAudioDeviceName() -> buffer - public ResultCode GetActiveAudioDeviceName(ServiceCtx context) - { - string name = _impl.GetActiveAudioDeviceName(); - - ulong position = context.Request.ReceiveBuff[0].Position; - ulong size = context.Request.ReceiveBuff[0].Size; - - byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(name + "\0"); - - if ((ulong)deviceNameBuffer.Length <= size) - { - context.Memory.Write(position, deviceNameBuffer); - } - else - { - Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); - } - - return ResultCode.Success; - } - - [CommandCmif(4)] - // QueryAudioDeviceSystemEvent() -> handle - public ResultCode QueryAudioDeviceSystemEvent(ServiceCtx context) - { - KEvent deviceSystemEvent = _impl.QueryAudioDeviceSystemEvent(); - - if (context.Process.HandleTable.GenerateHandle(deviceSystemEvent.ReadableEvent, out int handle) != Result.Success) - { - throw new InvalidOperationException("Out of handles!"); - } - - context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); - - Logger.Stub?.PrintStub(LogClass.ServiceAudio); - - return ResultCode.Success; - } - - [CommandCmif(5)] - // GetActiveChannelCount() -> u32 - public ResultCode GetActiveChannelCount(ServiceCtx context) - { - context.ResponseData.Write(_impl.GetActiveChannelCount()); - - Logger.Stub?.PrintStub(LogClass.ServiceAudio); - - return ResultCode.Success; - } - - [CommandCmif(6)] // 3.0.0+ - // ListAudioDeviceNameAuto() -> (u32, buffer) - public ResultCode ListAudioDeviceNameAuto(ServiceCtx context) - { - string[] deviceNames = _impl.ListAudioDeviceName(); - - (ulong position, ulong size) = context.Request.GetBufferType0x22(); - - ulong basePosition = position; - - int count = 0; - - foreach (string name in deviceNames) - { - byte[] buffer = Encoding.ASCII.GetBytes(name); - - if ((position - basePosition) + (ulong)buffer.Length > size) - { - break; - } - - context.Memory.Write(position, buffer); - MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioDeviceNameSize - buffer.Length); - - position += AudioDeviceNameSize; - count++; - } - - context.ResponseData.Write(count); - - return ResultCode.Success; - } - - [CommandCmif(7)] // 3.0.0+ - // SetAudioDeviceOutputVolumeAuto(f32 volume, buffer name) - public ResultCode SetAudioDeviceOutputVolumeAuto(ServiceCtx context) - { - float volume = context.RequestData.ReadSingle(); - - (ulong position, ulong size) = context.Request.GetBufferType0x21(); - - string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size); - - return _impl.SetAudioDeviceOutputVolume(deviceName, volume); - } - - [CommandCmif(8)] // 3.0.0+ - // GetAudioDeviceOutputVolumeAuto(buffer name) -> f32 - public ResultCode GetAudioDeviceOutputVolumeAuto(ServiceCtx context) - { - (ulong position, ulong size) = context.Request.GetBufferType0x21(); - - string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size); - - ResultCode result = _impl.GetAudioDeviceOutputVolume(deviceName, out float volume); - - if (result == ResultCode.Success) - { - context.ResponseData.Write(volume); - } - - return ResultCode.Success; - } - - [CommandCmif(10)] // 3.0.0+ - // GetActiveAudioDeviceNameAuto() -> buffer - public ResultCode GetActiveAudioDeviceNameAuto(ServiceCtx context) - { - string name = _impl.GetActiveAudioDeviceName(); - - (ulong position, ulong size) = context.Request.GetBufferType0x22(); - - byte[] deviceNameBuffer = Encoding.UTF8.GetBytes(name + '\0'); - - if ((ulong)deviceNameBuffer.Length <= size) - { - context.Memory.Write(position, deviceNameBuffer); - } - else - { - Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); - } - - return ResultCode.Success; - } - - [CommandCmif(11)] // 3.0.0+ - // QueryAudioDeviceInputEvent() -> handle - public ResultCode QueryAudioDeviceInputEvent(ServiceCtx context) - { - KEvent deviceInputEvent = _impl.QueryAudioDeviceInputEvent(); - - if (context.Process.HandleTable.GenerateHandle(deviceInputEvent.ReadableEvent, out int handle) != Result.Success) - { - throw new InvalidOperationException("Out of handles!"); - } - - context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); - - Logger.Stub?.PrintStub(LogClass.ServiceAudio); - - return ResultCode.Success; - } - - [CommandCmif(12)] // 3.0.0+ - // QueryAudioDeviceOutputEvent() -> handle - public ResultCode QueryAudioDeviceOutputEvent(ServiceCtx context) - { - KEvent deviceOutputEvent = _impl.QueryAudioDeviceOutputEvent(); - - if (context.Process.HandleTable.GenerateHandle(deviceOutputEvent.ReadableEvent, out int handle) != Result.Success) - { - throw new InvalidOperationException("Out of handles!"); - } - - context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); - - Logger.Stub?.PrintStub(LogClass.ServiceAudio); - - return ResultCode.Success; - } - - [CommandCmif(13)] // 13.0.0+ - // GetActiveAudioOutputDeviceName() -> buffer - public ResultCode GetActiveAudioOutputDeviceName(ServiceCtx context) - { - string name = _impl.GetActiveAudioOutputDeviceName(); - - ulong position = context.Request.ReceiveBuff[0].Position; - ulong size = context.Request.ReceiveBuff[0].Size; - - byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(name + "\0"); - - if ((ulong)deviceNameBuffer.Length <= size) - { - context.Memory.Write(position, deviceNameBuffer); - } - else - { - Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); - } - - return ResultCode.Success; - } - - [CommandCmif(14)] // 13.0.0+ - // ListAudioOutputDeviceName() -> (u32, buffer) - public ResultCode ListAudioOutputDeviceName(ServiceCtx context) - { - string[] deviceNames = _impl.ListAudioOutputDeviceName(); - - ulong position = context.Request.ReceiveBuff[0].Position; - ulong size = context.Request.ReceiveBuff[0].Size; - - ulong basePosition = position; - - int count = 0; - - foreach (string name in deviceNames) - { - byte[] buffer = Encoding.ASCII.GetBytes(name); - - if ((position - basePosition) + (ulong)buffer.Length > size) - { - break; - } - - context.Memory.Write(position, buffer); - MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioDeviceNameSize - buffer.Length); - - position += AudioDeviceNameSize; - count++; - } - - context.ResponseData.Write(count); - - return ResultCode.Success; - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs deleted file mode 100644 index 414c70a43..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Ryujinx.Audio.Integration; -using Ryujinx.HLE.HOS.Kernel.Threading; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer -{ - class AudioKernelEvent : IWritableEvent - { - public KEvent Event { get; } - - public AudioKernelEvent(KEvent evnt) - { - Event = evnt; - } - - public void Clear() - { - Event.WritableEvent.Clear(); - } - - public void Signal() - { - Event.WritableEvent.Signal(); - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs deleted file mode 100644 index 88456be3e..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs +++ /dev/null @@ -1,122 +0,0 @@ -using Ryujinx.Audio.Integration; -using Ryujinx.Audio.Renderer.Server; -using Ryujinx.HLE.HOS.Kernel.Threading; -using System; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer -{ - class AudioRenderer : IAudioRenderer - { - private readonly AudioRenderSystem _impl; - - public AudioRenderer(AudioRenderSystem impl) - { - _impl = impl; - } - - public ResultCode ExecuteAudioRendererRendering() - { - return (ResultCode)_impl.ExecuteAudioRendererRendering(); - } - - public uint GetMixBufferCount() - { - return _impl.GetMixBufferCount(); - } - - public uint GetRenderingTimeLimit() - { - return _impl.GetRenderingTimeLimit(); - } - - public uint GetSampleCount() - { - return _impl.GetSampleCount(); - } - - public uint GetSampleRate() - { - return _impl.GetSampleRate(); - } - - public int GetState() - { - if (_impl.IsActive()) - { - return 0; - } - - return 1; - } - - public ResultCode QuerySystemEvent(out KEvent systemEvent) - { - ResultCode resultCode = (ResultCode)_impl.QuerySystemEvent(out IWritableEvent outEvent); - - if (resultCode == ResultCode.Success) - { - if (outEvent is AudioKernelEvent kernelEvent) - { - systemEvent = kernelEvent.Event; - } - else - { - throw new NotImplementedException(); - } - } - else - { - systemEvent = null; - } - - return resultCode; - } - - public ResultCode RequestUpdate(Memory output, Memory performanceOutput, ReadOnlyMemory input) - { - return (ResultCode)_impl.Update(output, performanceOutput, input); - } - - public void SetRenderingTimeLimit(uint percent) - { - _impl.SetRenderingTimeLimitPercent(percent); - } - - public ResultCode Start() - { - _impl.Start(); - - return ResultCode.Success; - } - - public ResultCode Stop() - { - _impl.Stop(); - - return ResultCode.Success; - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _impl.Dispose(); - } - } - - public void SetVoiceDropParameter(float voiceDropParameter) - { - _impl.SetVoiceDropParameter(voiceDropParameter); - } - - public float GetVoiceDropParameter() - { - return _impl.GetVoiceDropParameter(); - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs deleted file mode 100644 index baea01072..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs +++ /dev/null @@ -1,215 +0,0 @@ -using Ryujinx.Common.Logging; -using Ryujinx.Common.Memory; -using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel.Threading; -using Ryujinx.Horizon.Common; -using System; -using System.Buffers; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer -{ - class AudioRendererServer : DisposableIpcService - { - private readonly IAudioRenderer _impl; - - public AudioRendererServer(IAudioRenderer impl) - { - _impl = impl; - } - - [CommandCmif(0)] - // GetSampleRate() -> u32 - public ResultCode GetSampleRate(ServiceCtx context) - { - context.ResponseData.Write(_impl.GetSampleRate()); - - return ResultCode.Success; - } - - [CommandCmif(1)] - // GetSampleCount() -> u32 - public ResultCode GetSampleCount(ServiceCtx context) - { - context.ResponseData.Write(_impl.GetSampleCount()); - - return ResultCode.Success; - } - - [CommandCmif(2)] - // GetMixBufferCount() -> u32 - public ResultCode GetMixBufferCount(ServiceCtx context) - { - context.ResponseData.Write(_impl.GetMixBufferCount()); - - return ResultCode.Success; - } - - [CommandCmif(3)] - // GetState() -> u32 - public ResultCode GetState(ServiceCtx context) - { - context.ResponseData.Write(_impl.GetState()); - - return ResultCode.Success; - } - - [CommandCmif(4)] - // RequestUpdate(buffer input) - // -> (buffer output, buffer performanceOutput) - public ResultCode RequestUpdate(ServiceCtx context) - { - ulong inputPosition = context.Request.SendBuff[0].Position; - ulong inputSize = context.Request.SendBuff[0].Size; - - ulong outputPosition = context.Request.ReceiveBuff[0].Position; - ulong outputSize = context.Request.ReceiveBuff[0].Size; - - ulong performanceOutputPosition = context.Request.ReceiveBuff[1].Position; - ulong performanceOutputSize = context.Request.ReceiveBuff[1].Size; - - ReadOnlyMemory input = context.Memory.GetSpan(inputPosition, (int)inputSize).ToArray(); - - using IMemoryOwner outputOwner = ByteMemoryPool.RentCleared(outputSize); - using IMemoryOwner performanceOutputOwner = ByteMemoryPool.RentCleared(performanceOutputSize); - Memory output = outputOwner.Memory; - Memory performanceOutput = performanceOutputOwner.Memory; - - using MemoryHandle outputHandle = output.Pin(); - using MemoryHandle performanceOutputHandle = performanceOutput.Pin(); - - ResultCode result = _impl.RequestUpdate(output, performanceOutput, input); - - if (result == ResultCode.Success) - { - context.Memory.Write(outputPosition, output.Span); - context.Memory.Write(performanceOutputPosition, performanceOutput.Span); - } - else - { - Logger.Error?.Print(LogClass.ServiceAudio, $"Error while processing renderer update: 0x{(int)result:X}"); - } - - return result; - } - - [CommandCmif(5)] - // Start() - public ResultCode Start(ServiceCtx context) - { - return _impl.Start(); - } - - [CommandCmif(6)] - // Stop() - public ResultCode Stop(ServiceCtx context) - { - return _impl.Stop(); - } - - [CommandCmif(7)] - // QuerySystemEvent() -> handle - public ResultCode QuerySystemEvent(ServiceCtx context) - { - ResultCode result = _impl.QuerySystemEvent(out KEvent systemEvent); - - if (result == ResultCode.Success) - { - if (context.Process.HandleTable.GenerateHandle(systemEvent.ReadableEvent, out int handle) != Result.Success) - { - throw new InvalidOperationException("Out of handles!"); - } - - context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); - } - - return result; - } - - [CommandCmif(8)] - // SetAudioRendererRenderingTimeLimit(u32 limit) - public ResultCode SetAudioRendererRenderingTimeLimit(ServiceCtx context) - { - uint limit = context.RequestData.ReadUInt32(); - - _impl.SetRenderingTimeLimit(limit); - - return ResultCode.Success; - } - - [CommandCmif(9)] - // GetAudioRendererRenderingTimeLimit() -> u32 limit - public ResultCode GetAudioRendererRenderingTimeLimit(ServiceCtx context) - { - uint limit = _impl.GetRenderingTimeLimit(); - - context.ResponseData.Write(limit); - - return ResultCode.Success; - } - - [CommandCmif(10)] // 3.0.0+ - // RequestUpdateAuto(buffer input) - // -> (buffer output, buffer performanceOutput) - public ResultCode RequestUpdateAuto(ServiceCtx context) - { - (ulong inputPosition, ulong inputSize) = context.Request.GetBufferType0x21(); - (ulong outputPosition, ulong outputSize) = context.Request.GetBufferType0x22(0); - (ulong performanceOutputPosition, ulong performanceOutputSize) = context.Request.GetBufferType0x22(1); - - ReadOnlyMemory input = context.Memory.GetSpan(inputPosition, (int)inputSize).ToArray(); - - Memory output = new byte[outputSize]; - Memory performanceOutput = new byte[performanceOutputSize]; - - using MemoryHandle outputHandle = output.Pin(); - using MemoryHandle performanceOutputHandle = performanceOutput.Pin(); - - ResultCode result = _impl.RequestUpdate(output, performanceOutput, input); - - if (result == ResultCode.Success) - { - context.Memory.Write(outputPosition, output.Span); - context.Memory.Write(performanceOutputPosition, performanceOutput.Span); - } - - return result; - } - - [CommandCmif(11)] // 3.0.0+ - // ExecuteAudioRendererRendering() - public ResultCode ExecuteAudioRendererRendering(ServiceCtx context) - { - return _impl.ExecuteAudioRendererRendering(); - } - - [CommandCmif(12)] // 15.0.0+ - // SetVoiceDropParameter(f32 voiceDropParameter) - public ResultCode SetVoiceDropParameter(ServiceCtx context) - { - float voiceDropParameter = context.RequestData.ReadSingle(); - - _impl.SetVoiceDropParameter(voiceDropParameter); - - return ResultCode.Success; - } - - [CommandCmif(13)] // 15.0.0+ - // GetVoiceDropParameter() -> f32 voiceDropParameter - public ResultCode GetVoiceDropParameter(ServiceCtx context) - { - float voiceDropParameter = _impl.GetVoiceDropParameter(); - - context.ResponseData.Write(voiceDropParameter); - - return ResultCode.Success; - } - - protected override void Dispose(bool isDisposing) - { - if (isDisposing) - { - _impl.Dispose(); - } - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs deleted file mode 100644 index 0f181a477..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Ryujinx.HLE.HOS.Kernel.Threading; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer -{ - interface IAudioDevice - { - string[] ListAudioDeviceName(); - ResultCode SetAudioDeviceOutputVolume(string name, float volume); - ResultCode GetAudioDeviceOutputVolume(string name, out float volume); - string GetActiveAudioDeviceName(); - KEvent QueryAudioDeviceSystemEvent(); - uint GetActiveChannelCount(); - KEvent QueryAudioDeviceInputEvent(); - KEvent QueryAudioDeviceOutputEvent(); - string GetActiveAudioOutputDeviceName(); - string[] ListAudioOutputDeviceName(); - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs deleted file mode 100644 index 6bb4a5dec..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Ryujinx.HLE.HOS.Kernel.Threading; -using System; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer -{ - interface IAudioRenderer : IDisposable - { - uint GetSampleRate(); - uint GetSampleCount(); - uint GetMixBufferCount(); - int GetState(); - ResultCode RequestUpdate(Memory output, Memory performanceOutput, ReadOnlyMemory input); - ResultCode Start(); - ResultCode Stop(); - ResultCode QuerySystemEvent(out KEvent systemEvent); - void SetRenderingTimeLimit(uint percent); - uint GetRenderingTimeLimit(); - ResultCode ExecuteAudioRendererRendering(); - void SetVoiceDropParameter(float voiceDropParameter); - float GetVoiceDropParameter(); - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs deleted file mode 100644 index 87d0001e3..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Ryujinx.Audio.Renderer.Device; -using Ryujinx.Audio.Renderer.Parameter; -using Ryujinx.Audio.Renderer.Server; -using Ryujinx.HLE.HOS.Kernel.Memory; -using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer; - -using AudioRendererManagerImpl = Ryujinx.Audio.Renderer.Server.AudioRendererManager; - -namespace Ryujinx.HLE.HOS.Services.Audio -{ - class AudioRendererManager : IAudioRendererManager - { - private readonly AudioRendererManagerImpl _impl; - private readonly VirtualDeviceSessionRegistry _registry; - - public AudioRendererManager(AudioRendererManagerImpl impl, VirtualDeviceSessionRegistry registry) - { - _impl = impl; - _registry = registry; - } - - public ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context, out IAudioDevice outObject, int revision, ulong appletResourceUserId) - { - outObject = new AudioDevice(_registry, context.Device.System.KernelContext, appletResourceUserId, revision); - - return ResultCode.Success; - } - - public ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter) - { - return AudioRendererManagerImpl.GetWorkBufferSize(ref parameter); - } - - public ResultCode OpenAudioRenderer( - ServiceCtx context, - out IAudioRenderer obj, - ref AudioRendererConfiguration parameter, - ulong workBufferSize, - ulong appletResourceUserId, - KTransferMemory workBufferTransferMemory, - uint processHandle) - { - var memoryManager = context.Process.HandleTable.GetKProcess((int)processHandle).CpuMemory; - - ResultCode result = (ResultCode)_impl.OpenAudioRenderer( - out AudioRenderSystem renderer, - memoryManager, - ref parameter, - appletResourceUserId, - workBufferTransferMemory.Address, - workBufferTransferMemory.Size, - processHandle, - context.Device.Configuration.AudioVolume); - - if (result == ResultCode.Success) - { - obj = new AudioRenderer.AudioRenderer(renderer); - } - else - { - obj = null; - } - - return result; - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManagerServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManagerServer.cs deleted file mode 100644 index 38a841d82..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManagerServer.cs +++ /dev/null @@ -1,116 +0,0 @@ -using Ryujinx.Audio.Renderer.Parameter; -using Ryujinx.Audio.Renderer.Server; -using Ryujinx.Common; -using Ryujinx.Common.Logging; -using Ryujinx.HLE.HOS.Kernel.Memory; -using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer; - -namespace Ryujinx.HLE.HOS.Services.Audio -{ - [Service("audren:u")] - class AudioRendererManagerServer : IpcService - { - private const int InitialRevision = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('1' << 24); - - private readonly IAudioRendererManager _impl; - - public AudioRendererManagerServer(ServiceCtx context) : this(context, new AudioRendererManager(context.Device.System.AudioRendererManager, context.Device.System.AudioDeviceSessionRegistry)) { } - - public AudioRendererManagerServer(ServiceCtx context, IAudioRendererManager impl) : base(context.Device.System.AudRenServer) - { - _impl = impl; - } - - [CommandCmif(0)] - // OpenAudioRenderer(nn::audio::detail::AudioRendererParameterInternal parameter, u64 workBufferSize, nn::applet::AppletResourceUserId appletResourceId, pid, handle workBuffer, handle processHandle) - // -> object - public ResultCode OpenAudioRenderer(ServiceCtx context) - { - AudioRendererConfiguration parameter = context.RequestData.ReadStruct(); - ulong workBufferSize = context.RequestData.ReadUInt64(); - ulong appletResourceUserId = context.RequestData.ReadUInt64(); - - int transferMemoryHandle = context.Request.HandleDesc.ToCopy[0]; - KTransferMemory workBufferTransferMemory = context.Process.HandleTable.GetObject(transferMemoryHandle); - uint processHandle = (uint)context.Request.HandleDesc.ToCopy[1]; - - ResultCode result = _impl.OpenAudioRenderer( - context, - out IAudioRenderer renderer, - ref parameter, - workBufferSize, - appletResourceUserId, - workBufferTransferMemory, - processHandle); - - if (result == ResultCode.Success) - { - MakeObject(context, new AudioRendererServer(renderer)); - } - - context.Device.System.KernelContext.Syscall.CloseHandle(transferMemoryHandle); - context.Device.System.KernelContext.Syscall.CloseHandle((int)processHandle); - - return result; - } - - [CommandCmif(1)] - // GetWorkBufferSize(nn::audio::detail::AudioRendererParameterInternal parameter) -> u64 workBufferSize - public ResultCode GetAudioRendererWorkBufferSize(ServiceCtx context) - { - AudioRendererConfiguration parameter = context.RequestData.ReadStruct(); - - if (BehaviourContext.CheckValidRevision(parameter.Revision)) - { - ulong size = _impl.GetWorkBufferSize(ref parameter); - - context.ResponseData.Write(size); - - Logger.Debug?.Print(LogClass.ServiceAudio, $"WorkBufferSize is 0x{size:x16}."); - - return ResultCode.Success; - } - else - { - context.ResponseData.Write(0L); - - Logger.Warning?.Print(LogClass.ServiceAudio, $"Library Revision REV{BehaviourContext.GetRevisionNumber(parameter.Revision)} is not supported!"); - - return ResultCode.UnsupportedRevision; - } - } - - [CommandCmif(2)] - // GetAudioDeviceService(nn::applet::AppletResourceUserId) -> object - public ResultCode GetAudioDeviceService(ServiceCtx context) - { - ulong appletResourceUserId = context.RequestData.ReadUInt64(); - - ResultCode result = _impl.GetAudioDeviceServiceWithRevisionInfo(context, out IAudioDevice device, InitialRevision, appletResourceUserId); - - if (result == ResultCode.Success) - { - MakeObject(context, new AudioDeviceServer(device)); - } - - return result; - } - - [CommandCmif(4)] // 4.0.0+ - // GetAudioDeviceServiceWithRevisionInfo(s32 revision, nn::applet::AppletResourceUserId appletResourceId) -> object - public ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context) - { - int revision = context.RequestData.ReadInt32(); - ulong appletResourceUserId = context.RequestData.ReadUInt64(); - - ResultCode result = _impl.GetAudioDeviceServiceWithRevisionInfo(context, out IAudioDevice device, revision, appletResourceUserId); - - if (result == ResultCode.Success) - { - MakeObject(context, new AudioDeviceServer(device)); - } - - return result; - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/Decoder.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/Decoder.cs deleted file mode 100644 index c5dd2f00d..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/Decoder.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Concentus.Structs; - -namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager -{ - class Decoder : IDecoder - { - private readonly OpusDecoder _decoder; - - public int SampleRate => _decoder.SampleRate; - public int ChannelsCount => _decoder.NumChannels; - - public Decoder(int sampleRate, int channelsCount) - { - _decoder = new OpusDecoder(sampleRate, channelsCount); - } - - public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize) - { - return _decoder.Decode(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize); - } - - public void ResetState() - { - _decoder.ResetState(); - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/DecoderCommon.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/DecoderCommon.cs deleted file mode 100644 index 9ff511a50..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/DecoderCommon.cs +++ /dev/null @@ -1,92 +0,0 @@ -using Concentus; -using Concentus.Enums; -using Concentus.Structs; -using Ryujinx.HLE.HOS.Services.Audio.Types; -using System; -using System.Runtime.CompilerServices; - -namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager -{ - static class DecoderCommon - { - private static ResultCode GetPacketNumSamples(this IDecoder decoder, out int numSamples, byte[] packet) - { - int result = OpusPacketInfo.GetNumSamples(packet, 0, packet.Length, decoder.SampleRate); - - numSamples = result; - - if (result == OpusError.OPUS_INVALID_PACKET) - { - return ResultCode.OpusInvalidInput; - } - else if (result == OpusError.OPUS_BAD_ARG) - { - return ResultCode.OpusInvalidInput; - } - - return ResultCode.Success; - } - - public static ResultCode DecodeInterleaved( - this IDecoder decoder, - bool reset, - ReadOnlySpan input, - out short[] outPcmData, - ulong outputSize, - out uint outConsumed, - out int outSamples) - { - outPcmData = null; - outConsumed = 0; - outSamples = 0; - - int streamSize = input.Length; - - if (streamSize < Unsafe.SizeOf()) - { - return ResultCode.OpusInvalidInput; - } - - OpusPacketHeader header = OpusPacketHeader.FromSpan(input); - int headerSize = Unsafe.SizeOf(); - uint totalSize = header.length + (uint)headerSize; - - if (totalSize > streamSize) - { - return ResultCode.OpusInvalidInput; - } - - byte[] opusData = input.Slice(headerSize, (int)header.length).ToArray(); - - ResultCode result = decoder.GetPacketNumSamples(out int numSamples, opusData); - - if (result == ResultCode.Success) - { - if ((uint)numSamples * (uint)decoder.ChannelsCount * sizeof(short) > outputSize) - { - return ResultCode.OpusInvalidInput; - } - - outPcmData = new short[numSamples * decoder.ChannelsCount]; - - if (reset) - { - decoder.ResetState(); - } - - try - { - outSamples = decoder.Decode(opusData, 0, opusData.Length, outPcmData, 0, outPcmData.Length / decoder.ChannelsCount); - outConsumed = totalSize; - } - catch (OpusException) - { - // TODO: as OpusException doesn't provide us the exact error code, this is kind of inaccurate in some cases... - return ResultCode.OpusInvalidInput; - } - } - - return ResultCode.Success; - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IDecoder.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IDecoder.cs deleted file mode 100644 index cc83be5e3..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IDecoder.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager -{ - interface IDecoder - { - int SampleRate { get; } - int ChannelsCount { get; } - - int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize); - void ResetState(); - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs deleted file mode 100644 index 3d5d2839f..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs +++ /dev/null @@ -1,116 +0,0 @@ -using Ryujinx.HLE.HOS.Services.Audio.Types; -using System; -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager -{ - class IHardwareOpusDecoder : IpcService - { - private readonly IDecoder _decoder; - private readonly OpusDecoderFlags _flags; - - public IHardwareOpusDecoder(int sampleRate, int channelsCount, OpusDecoderFlags flags) - { - _decoder = new Decoder(sampleRate, channelsCount); - _flags = flags; - } - - public IHardwareOpusDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, OpusDecoderFlags flags, byte[] mapping) - { - _decoder = new MultiSampleDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping); - _flags = flags; - } - - [CommandCmif(0)] - // DecodeInterleavedOld(buffer) -> (u32, u32, buffer) - public ResultCode DecodeInterleavedOld(ServiceCtx context) - { - return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: false); - } - - [CommandCmif(2)] - // DecodeInterleavedForMultiStreamOld(buffer) -> (u32, u32, buffer) - public ResultCode DecodeInterleavedForMultiStreamOld(ServiceCtx context) - { - return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: false); - } - - [CommandCmif(4)] // 6.0.0+ - // DecodeInterleavedWithPerfOld(buffer) -> (u32, u32, u64, buffer) - public ResultCode DecodeInterleavedWithPerfOld(ServiceCtx context) - { - return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: true); - } - - [CommandCmif(5)] // 6.0.0+ - // DecodeInterleavedForMultiStreamWithPerfOld(buffer) -> (u32, u32, u64, buffer) - public ResultCode DecodeInterleavedForMultiStreamWithPerfOld(ServiceCtx context) - { - return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: true); - } - - [CommandCmif(6)] // 6.0.0+ - // DecodeInterleavedWithPerfAndResetOld(bool reset, buffer) -> (u32, u32, u64, buffer) - public ResultCode DecodeInterleavedWithPerfAndResetOld(ServiceCtx context) - { - bool reset = context.RequestData.ReadBoolean(); - - return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset, withPerf: true); - } - - [CommandCmif(7)] // 6.0.0+ - // DecodeInterleavedForMultiStreamWithPerfAndResetOld(bool reset, buffer) -> (u32, u32, u64, buffer) - public ResultCode DecodeInterleavedForMultiStreamWithPerfAndResetOld(ServiceCtx context) - { - bool reset = context.RequestData.ReadBoolean(); - - return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset, withPerf: true); - } - - [CommandCmif(8)] // 7.0.0+ - // DecodeInterleaved(bool reset, buffer) -> (u32, u32, u64, buffer) - public ResultCode DecodeInterleaved(ServiceCtx context) - { - bool reset = context.RequestData.ReadBoolean(); - - return DecodeInterleavedInternal(context, _flags, reset, withPerf: true); - } - - [CommandCmif(9)] // 7.0.0+ - // DecodeInterleavedForMultiStream(bool reset, buffer) -> (u32, u32, u64, buffer) - public ResultCode DecodeInterleavedForMultiStream(ServiceCtx context) - { - bool reset = context.RequestData.ReadBoolean(); - - return DecodeInterleavedInternal(context, _flags, reset, withPerf: true); - } - - private ResultCode DecodeInterleavedInternal(ServiceCtx context, OpusDecoderFlags flags, bool reset, bool withPerf) - { - ulong inPosition = context.Request.SendBuff[0].Position; - ulong inSize = context.Request.SendBuff[0].Size; - ulong outputPosition = context.Request.ReceiveBuff[0].Position; - ulong outputSize = context.Request.ReceiveBuff[0].Size; - - ReadOnlySpan input = context.Memory.GetSpan(inPosition, (int)inSize); - - ResultCode result = _decoder.DecodeInterleaved(reset, input, out short[] outPcmData, outputSize, out uint outConsumed, out int outSamples); - - if (result == ResultCode.Success) - { - context.Memory.Write(outputPosition, MemoryMarshal.Cast(outPcmData.AsSpan())); - - context.ResponseData.Write(outConsumed); - context.ResponseData.Write(outSamples); - - if (withPerf) - { - // This is the time the DSP took to process the request, TODO: fill this. - context.ResponseData.Write(0UL); - } - } - - return result; - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/MultiSampleDecoder.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/MultiSampleDecoder.cs deleted file mode 100644 index 910bb23ee..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/MultiSampleDecoder.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Concentus.Structs; - -namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager -{ - class MultiSampleDecoder : IDecoder - { - private readonly OpusMSDecoder _decoder; - - public int SampleRate => _decoder.SampleRate; - public int ChannelsCount { get; } - - public MultiSampleDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping) - { - ChannelsCount = channelsCount; - _decoder = new OpusMSDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping); - } - - public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize) - { - return _decoder.DecodeMultistream(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize, 0); - } - - public void ResetState() - { - _decoder.ResetState(); - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs deleted file mode 100644 index a250ec799..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio -{ - [Service("audctl")] - class IAudioController : IpcService - { - public IAudioController(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs deleted file mode 100644 index 861e9f2dc..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Ryujinx.Audio.Common; -using Ryujinx.HLE.HOS.Services.Audio.AudioIn; - -namespace Ryujinx.HLE.HOS.Services.Audio -{ - interface IAudioInManager - { - public string[] ListAudioIns(bool filtered); - - public ResultCode OpenAudioIn(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle); - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs deleted file mode 100644 index d0c385b56..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio -{ - [Service("audin:a")] - class IAudioInManagerForApplet : IpcService - { - public IAudioInManagerForApplet(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs deleted file mode 100644 index 120136158..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio -{ - [Service("audin:d")] - class IAudioInManagerForDebugger : IpcService - { - public IAudioInManagerForDebugger(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs deleted file mode 100644 index cd7cbe41c..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Ryujinx.Audio.Common; -using Ryujinx.HLE.HOS.Services.Audio.AudioOut; - -namespace Ryujinx.HLE.HOS.Services.Audio -{ - interface IAudioOutManager - { - public string[] ListAudioOuts(); - - public ResultCode OpenAudioOut(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle, float volume); - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs deleted file mode 100644 index 9925777e2..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio -{ - [Service("audout:a")] - class IAudioOutManagerForApplet : IpcService - { - public IAudioOutManagerForApplet(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs deleted file mode 100644 index c41767a01..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio -{ - [Service("audout:d")] - class IAudioOutManagerForDebugger : IpcService - { - public IAudioOutManagerForDebugger(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs deleted file mode 100644 index 112e246c0..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Ryujinx.Audio.Renderer.Parameter; -using Ryujinx.HLE.HOS.Kernel.Memory; -using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer; - -namespace Ryujinx.HLE.HOS.Services.Audio -{ - interface IAudioRendererManager - { - // TODO: Remove ServiceCtx argument - // BODY: This is only needed by the legacy backend. Refactor this when removing the legacy backend. - ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context, out IAudioDevice outObject, int revision, ulong appletResourceUserId); - - // TODO: Remove ServiceCtx argument - // BODY: This is only needed by the legacy backend. Refactor this when removing the legacy backend. - ResultCode OpenAudioRenderer(ServiceCtx context, out IAudioRenderer obj, ref AudioRendererConfiguration parameter, ulong workBufferSize, ulong appletResourceUserId, KTransferMemory workBufferTransferMemory, uint processHandle); - - ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter); - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs deleted file mode 100644 index dd767993d..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio -{ - [Service("audren:a")] - class IAudioRendererManagerForApplet : IpcService - { - public IAudioRendererManagerForApplet(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs deleted file mode 100644 index cd2af09b2..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio -{ - [Service("audren:d")] - class IAudioRendererManagerForDebugger : IpcService - { - public IAudioRendererManagerForDebugger(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs deleted file mode 100644 index aa9789ac5..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio -{ - [Service("auddev")] // 6.0.0+ - class IAudioSnoopManager : IpcService - { - public IAudioSnoopManager(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs deleted file mode 100644 index 9b58213e9..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio -{ - [Service("audrec:u")] - class IFinalOutputRecorderManager : IpcService - { - public IFinalOutputRecorderManager(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs deleted file mode 100644 index e2d62eee3..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio -{ - [Service("audrec:a")] - class IFinalOutputRecorderManagerForApplet : IpcService - { - public IFinalOutputRecorderManagerForApplet(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs deleted file mode 100644 index 7ded79435..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio -{ - [Service("audrec:d")] - class IFinalOutputRecorderManagerForDebugger : IpcService - { - public IFinalOutputRecorderManagerForDebugger(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs deleted file mode 100644 index 514b51a51..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs +++ /dev/null @@ -1,227 +0,0 @@ -using Ryujinx.Common; -using Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager; -using Ryujinx.HLE.HOS.Services.Audio.Types; -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Audio -{ - [Service("hwopus")] - class IHardwareOpusDecoderManager : IpcService - { - public IHardwareOpusDecoderManager(ServiceCtx context) { } - - [CommandCmif(0)] - // Initialize(bytes<8, 4>, u32, handle) -> object - public ResultCode Initialize(ServiceCtx context) - { - int sampleRate = context.RequestData.ReadInt32(); - int channelsCount = context.RequestData.ReadInt32(); - - MakeObject(context, new IHardwareOpusDecoder(sampleRate, channelsCount, OpusDecoderFlags.None)); - - // Close transfer memory immediately as we don't use it. - context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]); - - return ResultCode.Success; - } - - [CommandCmif(1)] - // GetWorkBufferSize(bytes<8, 4>) -> u32 - public ResultCode GetWorkBufferSize(ServiceCtx context) - { - int sampleRate = context.RequestData.ReadInt32(); - int channelsCount = context.RequestData.ReadInt32(); - - int opusDecoderSize = GetOpusDecoderSize(channelsCount); - - int frameSize = BitUtils.AlignUp(channelsCount * 1920 / (48000 / sampleRate), 64); - int totalSize = opusDecoderSize + 1536 + frameSize; - - context.ResponseData.Write(totalSize); - - return ResultCode.Success; - } - - [CommandCmif(2)] // 3.0.0+ - // InitializeForMultiStream(u32, handle, buffer, 0x19>) -> object - public ResultCode InitializeForMultiStream(ServiceCtx context) - { - ulong parametersAddress = context.Request.PtrBuff[0].Position; - - OpusMultiStreamParameters parameters = context.Memory.Read(parametersAddress); - - MakeObject(context, new IHardwareOpusDecoder(parameters.SampleRate, parameters.ChannelsCount, OpusDecoderFlags.None)); - - // Close transfer memory immediately as we don't use it. - context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]); - - return ResultCode.Success; - } - - [CommandCmif(3)] // 3.0.0+ - // GetWorkBufferSizeForMultiStream(buffer, 0x19>) -> u32 - public ResultCode GetWorkBufferSizeForMultiStream(ServiceCtx context) - { - ulong parametersAddress = context.Request.PtrBuff[0].Position; - - OpusMultiStreamParameters parameters = context.Memory.Read(parametersAddress); - - int opusDecoderSize = GetOpusMultistreamDecoderSize(parameters.NumberOfStreams, parameters.NumberOfStereoStreams); - - int streamSize = BitUtils.AlignUp(parameters.NumberOfStreams * 1500, 64); - int frameSize = BitUtils.AlignUp(parameters.ChannelsCount * 1920 / (48000 / parameters.SampleRate), 64); - int totalSize = opusDecoderSize + streamSize + frameSize; - - context.ResponseData.Write(totalSize); - - return ResultCode.Success; - } - - [CommandCmif(4)] // 12.0.0+ - // InitializeEx(OpusParametersEx, u32, handle) -> object - public ResultCode InitializeEx(ServiceCtx context) - { - OpusParametersEx parameters = context.RequestData.ReadStruct(); - - // UseLargeFrameSize can be ignored due to not relying on fixed size buffers for storing the decoded result. - MakeObject(context, new IHardwareOpusDecoder(parameters.SampleRate, parameters.ChannelsCount, parameters.Flags)); - - // Close transfer memory immediately as we don't use it. - context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]); - - return ResultCode.Success; - } - - [CommandCmif(5)] // 12.0.0+ - // GetWorkBufferSizeEx(OpusParametersEx) -> u32 - public ResultCode GetWorkBufferSizeEx(ServiceCtx context) - { - OpusParametersEx parameters = context.RequestData.ReadStruct(); - - int opusDecoderSize = GetOpusDecoderSize(parameters.ChannelsCount); - - int frameSizeMono48KHz = parameters.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920; - int frameSize = BitUtils.AlignUp(parameters.ChannelsCount * frameSizeMono48KHz / (48000 / parameters.SampleRate), 64); - int totalSize = opusDecoderSize + 1536 + frameSize; - - context.ResponseData.Write(totalSize); - - return ResultCode.Success; - } - - [CommandCmif(6)] // 12.0.0+ - // InitializeForMultiStreamEx(u32, handle, buffer, 0x19>) -> object - public ResultCode InitializeForMultiStreamEx(ServiceCtx context) - { - ulong parametersAddress = context.Request.PtrBuff[0].Position; - - OpusMultiStreamParametersEx parameters = context.Memory.Read(parametersAddress); - - byte[] mappings = MemoryMarshal.Cast(parameters.ChannelMappings.AsSpan()).ToArray(); - - // UseLargeFrameSize can be ignored due to not relying on fixed size buffers for storing the decoded result. - MakeObject(context, new IHardwareOpusDecoder( - parameters.SampleRate, - parameters.ChannelsCount, - parameters.NumberOfStreams, - parameters.NumberOfStereoStreams, - parameters.Flags, - mappings)); - - // Close transfer memory immediately as we don't use it. - context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]); - - return ResultCode.Success; - } - - [CommandCmif(7)] // 12.0.0+ - // GetWorkBufferSizeForMultiStreamEx(buffer, 0x19>) -> u32 - public ResultCode GetWorkBufferSizeForMultiStreamEx(ServiceCtx context) - { - ulong parametersAddress = context.Request.PtrBuff[0].Position; - - OpusMultiStreamParametersEx parameters = context.Memory.Read(parametersAddress); - - int opusDecoderSize = GetOpusMultistreamDecoderSize(parameters.NumberOfStreams, parameters.NumberOfStereoStreams); - - int frameSizeMono48KHz = parameters.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920; - int streamSize = BitUtils.AlignUp(parameters.NumberOfStreams * 1500, 64); - int frameSize = BitUtils.AlignUp(parameters.ChannelsCount * frameSizeMono48KHz / (48000 / parameters.SampleRate), 64); - int totalSize = opusDecoderSize + streamSize + frameSize; - - context.ResponseData.Write(totalSize); - - return ResultCode.Success; - } - - [CommandCmif(8)] // 16.0.0+ - // GetWorkBufferSizeExEx(OpusParametersEx) -> u32 - public ResultCode GetWorkBufferSizeExEx(ServiceCtx context) - { - // NOTE: GetWorkBufferSizeEx use hardcoded values to compute the returned size. - // GetWorkBufferSizeExEx fixes that by using dynamic values. - // Since we're already doing that, it's fine to call it directly. - - return GetWorkBufferSizeEx(context); - } - - [CommandCmif(9)] // 16.0.0+ - // GetWorkBufferSizeForMultiStreamExEx(buffer, 0x19>) -> u32 - public ResultCode GetWorkBufferSizeForMultiStreamExEx(ServiceCtx context) - { - // NOTE: GetWorkBufferSizeForMultiStreamEx use hardcoded values to compute the returned size. - // GetWorkBufferSizeForMultiStreamExEx fixes that by using dynamic values. - // Since we're already doing that, it's fine to call it directly. - - return GetWorkBufferSizeForMultiStreamEx(context); - } - - private static int GetOpusMultistreamDecoderSize(int streams, int coupledStreams) - { - if (streams < 1 || coupledStreams > streams || coupledStreams < 0) - { - return 0; - } - - int coupledSize = GetOpusDecoderSize(2); - int monoSize = GetOpusDecoderSize(1); - - return Align4(monoSize - GetOpusDecoderAllocSize(1)) * (streams - coupledStreams) + - Align4(coupledSize - GetOpusDecoderAllocSize(2)) * coupledStreams + 0xb90c; - } - - private static int Align4(int value) - { - return BitUtils.AlignUp(value, 4); - } - - private static int GetOpusDecoderSize(int channelsCount) - { - const int SilkDecoderSize = 0x2160; - - if (channelsCount < 1 || channelsCount > 2) - { - return 0; - } - - int celtDecoderSize = GetCeltDecoderSize(channelsCount); - int opusDecoderSize = GetOpusDecoderAllocSize(channelsCount) | 0x4c; - - return opusDecoderSize + SilkDecoderSize + celtDecoderSize; - } - - private static int GetOpusDecoderAllocSize(int channelsCount) - { - return (channelsCount * 0x800 + 0x4803) & -0x800; - } - - private static int GetCeltDecoderSize(int channelsCount) - { - const int DecodeBufferSize = 0x2030; - const int Overlap = 120; - const int EBandsCount = 21; - - return (DecodeBufferSize + Overlap * 4) * channelsCount + EBandsCount * 16 + 0x50; - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs deleted file mode 100644 index c1d49109c..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio -{ - enum ResultCode - { - ModuleId = 153, - ErrorCodeShift = 9, - - Success = 0, - - DeviceNotFound = (1 << ErrorCodeShift) | ModuleId, - UnsupportedRevision = (2 << ErrorCodeShift) | ModuleId, - UnsupportedSampleRate = (3 << ErrorCodeShift) | ModuleId, - BufferSizeTooSmall = (4 << ErrorCodeShift) | ModuleId, - OpusInvalidInput = (6 << ErrorCodeShift) | ModuleId, - TooManyBuffersInUse = (8 << ErrorCodeShift) | ModuleId, - InvalidChannelCount = (10 << ErrorCodeShift) | ModuleId, - InvalidOperation = (513 << ErrorCodeShift) | ModuleId, - InvalidHandle = (1536 << ErrorCodeShift) | ModuleId, - OutputAlreadyStarted = (1540 << ErrorCodeShift) | ModuleId, - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs deleted file mode 100644 index 099769b3a..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Buffers.Binary; -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Audio.Types -{ - [StructLayout(LayoutKind.Sequential)] - struct OpusPacketHeader - { - public uint length; - public uint finalRange; - - public static OpusPacketHeader FromSpan(ReadOnlySpan data) - { - OpusPacketHeader header = MemoryMarshal.Cast(data)[0]; - - header.length = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(header.length) : header.length; - header.finalRange = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(header.finalRange) : header.finalRange; - - return header; - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusParametersEx.cs b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusParametersEx.cs deleted file mode 100644 index 4d1e0c824..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusParametersEx.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Ryujinx.Common.Memory; -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Audio.Types -{ - [StructLayout(LayoutKind.Sequential, Size = 0x10)] - struct OpusParametersEx - { - public int SampleRate; - public int ChannelsCount; - public OpusDecoderFlags Flags; - - Array4 Padding1; - } -} diff --git a/src/Ryujinx.HLE/Ryujinx.HLE.csproj b/src/Ryujinx.HLE/Ryujinx.HLE.csproj index dbcb82212..0fcf9e4b5 100644 --- a/src/Ryujinx.HLE/Ryujinx.HLE.csproj +++ b/src/Ryujinx.HLE/Ryujinx.HLE.csproj @@ -21,7 +21,6 @@ - @@ -30,11 +29,6 @@ - - - NU1605 - - diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs index 912a39b04..81c3ab473 100644 --- a/src/Ryujinx.HLE/Switch.cs +++ b/src/Ryujinx.HLE/Switch.cs @@ -117,12 +117,12 @@ public void PresentFrame(Action swapBuffersCallback) public void SetVolume(float volume) { - System.SetVolume(Math.Clamp(volume, 0, 1)); + AudioDeviceDriver.Volume = Math.Clamp(volume, 0f, 1f); } public float GetVolume() { - return System.GetVolume(); + return AudioDeviceDriver.Volume; } public void EnableCheats() @@ -132,7 +132,7 @@ public void EnableCheats() public bool IsAudioMuted() { - return System.GetVolume() == 0; + return AudioDeviceDriver.Volume == 0; } public void DisposeGpu() diff --git a/src/Ryujinx.Horizon.Common/IExternalEvent.cs b/src/Ryujinx.Horizon.Common/IExternalEvent.cs new file mode 100644 index 000000000..dedf4c72a --- /dev/null +++ b/src/Ryujinx.Horizon.Common/IExternalEvent.cs @@ -0,0 +1,10 @@ +using System; + +namespace Ryujinx.Horizon.Common +{ + public interface IExternalEvent + { + void Signal(); + void Clear(); + } +} diff --git a/src/Ryujinx.Horizon.Common/ISyscallApi.cs b/src/Ryujinx.Horizon.Common/ISyscallApi.cs index 20277f344..3d6da0416 100644 --- a/src/Ryujinx.Horizon.Common/ISyscallApi.cs +++ b/src/Ryujinx.Horizon.Common/ISyscallApi.cs @@ -1,3 +1,4 @@ +using Ryujinx.Memory; using System; namespace Ryujinx.Horizon.Common @@ -29,5 +30,9 @@ public interface ISyscallApi Result CreatePort(out int serverPortHandle, out int clientPortHandle, int maxSessions, bool isLight, string name); Result ManageNamedPort(out int handle, string name, int maxSessions); Result ConnectToPort(out int clientSessionHandle, int clientPortHandle); + + IExternalEvent GetExternalEvent(int handle); + IVirtualMemoryManager GetMemoryManagerByProcessHandle(int handle); + ulong GetTransferMemoryAddress(int handle); } } diff --git a/src/Ryujinx.Horizon.Common/Result.cs b/src/Ryujinx.Horizon.Common/Result.cs index d313554e3..4b120b847 100644 --- a/src/Ryujinx.Horizon.Common/Result.cs +++ b/src/Ryujinx.Horizon.Common/Result.cs @@ -36,6 +36,11 @@ public Result(int module, int description) ErrorCode = module | (description << ModuleBits); } + public Result(int errorCode) + { + ErrorCode = errorCode; + } + public readonly override bool Equals(object obj) { return obj is Result result && result.Equals(this); diff --git a/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs b/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs index a65ec3abd..19667290f 100644 --- a/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs +++ b/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs @@ -286,13 +286,13 @@ private static void GenerateMethod(CodeGenerator generator, Compilation compilat { if (IsNonSpanOutBuffer(compilation, parameter)) { - generator.AppendLine($"using var {argName} = CommandSerialization.GetWritableRegion(processor.GetBufferRange({outArgIndex++}));"); + generator.AppendLine($"using var {argName} = CommandSerialization.GetWritableRegion(processor.GetBufferRange({index}));"); argName = $"out {GenerateSpanCastElement0(canonicalTypeName, $"{argName}.Memory.Span")}"; } else { - outParameters.Add(new OutParameter(argName, canonicalTypeName, index, argType)); + outParameters.Add(new OutParameter(argName, canonicalTypeName, outArgIndex++, argType)); argName = $"out {canonicalTypeName} {argName}"; } diff --git a/src/Ryujinx.Horizon/Arp/ArpIpcServer.cs b/src/Ryujinx.Horizon/Arp/ArpIpcServer.cs index 6080b4750..a6017b8a6 100644 --- a/src/Ryujinx.Horizon/Arp/ArpIpcServer.cs +++ b/src/Ryujinx.Horizon/Arp/ArpIpcServer.cs @@ -56,6 +56,7 @@ public void Shutdown() { _applicationInstanceManager.Dispose(); _serverManager.Dispose(); + _sm.Dispose(); } } } diff --git a/src/Ryujinx.Horizon/Audio/AudioMain.cs b/src/Ryujinx.Horizon/Audio/AudioMain.cs new file mode 100644 index 000000000..92c9e804f --- /dev/null +++ b/src/Ryujinx.Horizon/Audio/AudioMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Audio +{ + class AudioMain : IService + { + public static void Main(ServiceTable serviceTable) + { + AudioUserIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Audio/AudioManagers.cs b/src/Ryujinx.Horizon/Audio/AudioManagers.cs new file mode 100644 index 000000000..493a6f9b5 --- /dev/null +++ b/src/Ryujinx.Horizon/Audio/AudioManagers.cs @@ -0,0 +1,78 @@ +using Ryujinx.Audio; +using Ryujinx.Audio.Input; +using Ryujinx.Audio.Integration; +using Ryujinx.Audio.Output; +using Ryujinx.Audio.Renderer.Device; +using Ryujinx.Audio.Renderer.Server; +using Ryujinx.Cpu; +using Ryujinx.Horizon.Sdk.Audio; +using System; + +namespace Ryujinx.Horizon.Audio +{ + class AudioManagers : IDisposable + { + public AudioManager AudioManager { get; } + public AudioOutputManager AudioOutputManager { get; } + public AudioInputManager AudioInputManager { get; } + public AudioRendererManager AudioRendererManager { get; } + public VirtualDeviceSessionRegistry AudioDeviceSessionRegistry { get; } + + public AudioManagers(IHardwareDeviceDriver audioDeviceDriver, ITickSource tickSource) + { + AudioManager = new AudioManager(); + AudioOutputManager = new AudioOutputManager(); + AudioInputManager = new AudioInputManager(); + AudioRendererManager = new AudioRendererManager(tickSource); + AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry(audioDeviceDriver); + + IWritableEvent[] audioOutputRegisterBufferEvents = new IWritableEvent[Constants.AudioOutSessionCountMax]; + + for (int i = 0; i < audioOutputRegisterBufferEvents.Length; i++) + { + audioOutputRegisterBufferEvents[i] = new AudioEvent(); + } + + AudioOutputManager.Initialize(audioDeviceDriver, audioOutputRegisterBufferEvents); + + IWritableEvent[] audioInputRegisterBufferEvents = new IWritableEvent[Constants.AudioInSessionCountMax]; + + for (int i = 0; i < audioInputRegisterBufferEvents.Length; i++) + { + audioInputRegisterBufferEvents[i] = new AudioEvent(); + } + + AudioInputManager.Initialize(audioDeviceDriver, audioInputRegisterBufferEvents); + + IWritableEvent[] systemEvents = new IWritableEvent[Constants.AudioRendererSessionCountMax]; + + for (int i = 0; i < systemEvents.Length; i++) + { + systemEvents[i] = new AudioEvent(); + } + + AudioManager.Initialize(audioDeviceDriver.GetUpdateRequiredEvent(), AudioOutputManager.Update, AudioInputManager.Update); + + AudioRendererManager.Initialize(systemEvents, audioDeviceDriver); + + AudioManager.Start(); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + AudioManager.Dispose(); + AudioOutputManager.Dispose(); + AudioInputManager.Dispose(); + AudioRendererManager.Dispose(); + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Audio/AudioUserIpcServer.cs b/src/Ryujinx.Horizon/Audio/AudioUserIpcServer.cs new file mode 100644 index 000000000..20c824e1e --- /dev/null +++ b/src/Ryujinx.Horizon/Audio/AudioUserIpcServer.cs @@ -0,0 +1,55 @@ +using Ryujinx.Horizon.Sdk.Audio.Detail; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Audio +{ + class AudioUserIpcServer + { + private const int MaxSessionsCount = 30; + + private const int PointerBufferSize = 0xB40; + private const int MaxDomains = 0; + private const int MaxDomainObjects = 0; + private const int MaxPortsCount = 1; + + private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + private AudioManagers _managers; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, MaxSessionsCount); + _managers = new AudioManagers(HorizonStatic.Options.AudioDeviceDriver, HorizonStatic.Options.TickSource); + + AudioRendererManager audioRendererManager = new(_managers.AudioRendererManager, _managers.AudioDeviceSessionRegistry); + AudioOutManager audioOutManager = new(_managers.AudioOutputManager); + AudioInManager audioInManager = new(_managers.AudioInputManager); + FinalOutputRecorderManager finalOutputRecorderManager = new(); + + _serverManager.RegisterObjectForServer(audioRendererManager, ServiceName.Encode("audren:u"), MaxSessionsCount); + _serverManager.RegisterObjectForServer(audioOutManager, ServiceName.Encode("audout:u"), MaxSessionsCount); + _serverManager.RegisterObjectForServer(audioInManager, ServiceName.Encode("audin:u"), MaxSessionsCount); + _serverManager.RegisterObjectForServer(finalOutputRecorderManager, ServiceName.Encode("audrec:u"), MaxSessionsCount); + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + _managers.Dispose(); + _sm.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Audio/HwopusIpcServer.cs b/src/Ryujinx.Horizon/Audio/HwopusIpcServer.cs new file mode 100644 index 000000000..e60e033cc --- /dev/null +++ b/src/Ryujinx.Horizon/Audio/HwopusIpcServer.cs @@ -0,0 +1,46 @@ +using Ryujinx.Horizon.Sdk.Codec.Detail; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Audio +{ + class HwopusIpcServer + { + private const int MaxSessionsCount = 24; + + private const int PointerBufferSize = 0x1000; + private const int MaxDomains = 8; + private const int MaxDomainObjects = 256; + private const int MaxPortsCount = 1; + + private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, MaxSessionsCount); + + HardwareOpusDecoderManager hardwareOpusDecoderManager = new(); + + _serverManager.RegisterObjectForServer(hardwareOpusDecoderManager, ServiceName.Encode("hwopus"), MaxSessionsCount); + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + _sm.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Audio/HwopusMain.cs b/src/Ryujinx.Horizon/Audio/HwopusMain.cs new file mode 100644 index 000000000..04eee3fad --- /dev/null +++ b/src/Ryujinx.Horizon/Audio/HwopusMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Audio +{ + class HwopusMain : IService + { + public static void Main(ServiceTable serviceTable) + { + HwopusIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Bcat/BcatIpcServer.cs b/src/Ryujinx.Horizon/Bcat/BcatIpcServer.cs index dd4e5b53a..8da3971cf 100644 --- a/src/Ryujinx.Horizon/Bcat/BcatIpcServer.cs +++ b/src/Ryujinx.Horizon/Bcat/BcatIpcServer.cs @@ -44,6 +44,7 @@ public void ServiceRequests() public void Shutdown() { _serverManager.Dispose(); + _sm.Dispose(); } } } diff --git a/src/Ryujinx.Horizon/Friends/FriendsIpcServer.cs b/src/Ryujinx.Horizon/Friends/FriendsIpcServer.cs index 523c617a8..a12c0cae8 100644 --- a/src/Ryujinx.Horizon/Friends/FriendsIpcServer.cs +++ b/src/Ryujinx.Horizon/Friends/FriendsIpcServer.cs @@ -44,6 +44,7 @@ public void ServiceRequests() public void Shutdown() { _serverManager.Dispose(); + _sm.Dispose(); } } } diff --git a/src/Ryujinx.Horizon/HorizonOptions.cs b/src/Ryujinx.Horizon/HorizonOptions.cs index 794620569..a24ce7f61 100644 --- a/src/Ryujinx.Horizon/HorizonOptions.cs +++ b/src/Ryujinx.Horizon/HorizonOptions.cs @@ -1,4 +1,6 @@ using LibHac; +using Ryujinx.Audio.Integration; +using Ryujinx.Cpu; using Ryujinx.Horizon.Sdk.Account; using Ryujinx.Horizon.Sdk.Fs; @@ -12,14 +14,24 @@ public readonly struct HorizonOptions public HorizonClient BcatClient { get; } public IFsClient FsClient { get; } public IEmulatorAccountManager AccountManager { get; } + public IHardwareDeviceDriver AudioDeviceDriver { get; } + public ITickSource TickSource { get; } - public HorizonOptions(bool ignoreMissingServices, HorizonClient bcatClient, IFsClient fsClient, IEmulatorAccountManager accountManager) + public HorizonOptions( + bool ignoreMissingServices, + HorizonClient bcatClient, + IFsClient fsClient, + IEmulatorAccountManager accountManager, + IHardwareDeviceDriver audioDeviceDriver, + ITickSource tickSource) { IgnoreMissingServices = ignoreMissingServices; ThrowOnInvalidCommandIds = true; BcatClient = bcatClient; FsClient = fsClient; AccountManager = accountManager; + AudioDeviceDriver = audioDeviceDriver; + TickSource = tickSource; } } } diff --git a/src/Ryujinx.Horizon/Hshl/HshlIpcServer.cs b/src/Ryujinx.Horizon/Hshl/HshlIpcServer.cs index d7d89e24b..b1cc7259d 100644 --- a/src/Ryujinx.Horizon/Hshl/HshlIpcServer.cs +++ b/src/Ryujinx.Horizon/Hshl/HshlIpcServer.cs @@ -42,6 +42,7 @@ public void ServiceRequests() public void Shutdown() { _serverManager.Dispose(); + _sm.Dispose(); } } } diff --git a/src/Ryujinx.Horizon/Ins/InsIpcServer.cs b/src/Ryujinx.Horizon/Ins/InsIpcServer.cs index bb2749d5f..4e06dcadd 100644 --- a/src/Ryujinx.Horizon/Ins/InsIpcServer.cs +++ b/src/Ryujinx.Horizon/Ins/InsIpcServer.cs @@ -42,6 +42,7 @@ public void ServiceRequests() public void Shutdown() { _serverManager.Dispose(); + _sm.Dispose(); } } } diff --git a/src/Ryujinx.Horizon/Lbl/LblIpcServer.cs b/src/Ryujinx.Horizon/Lbl/LblIpcServer.cs index 6b5421653..f25fc54b1 100644 --- a/src/Ryujinx.Horizon/Lbl/LblIpcServer.cs +++ b/src/Ryujinx.Horizon/Lbl/LblIpcServer.cs @@ -38,6 +38,7 @@ public void ServiceRequests() public void Shutdown() { _serverManager.Dispose(); + _sm.Dispose(); } } } diff --git a/src/Ryujinx.Horizon/LogManager/LmIpcServer.cs b/src/Ryujinx.Horizon/LogManager/LmIpcServer.cs index d023ff927..6bb4e11c7 100644 --- a/src/Ryujinx.Horizon/LogManager/LmIpcServer.cs +++ b/src/Ryujinx.Horizon/LogManager/LmIpcServer.cs @@ -38,6 +38,7 @@ public void ServiceRequests() public void Shutdown() { _serverManager.Dispose(); + _sm.Dispose(); } } } diff --git a/src/Ryujinx.Horizon/MmNv/MmNvIpcServer.cs b/src/Ryujinx.Horizon/MmNv/MmNvIpcServer.cs index c52a294f5..b3ce81182 100644 --- a/src/Ryujinx.Horizon/MmNv/MmNvIpcServer.cs +++ b/src/Ryujinx.Horizon/MmNv/MmNvIpcServer.cs @@ -38,6 +38,7 @@ public void ServiceRequests() public void Shutdown() { _serverManager.Dispose(); + _sm.Dispose(); } } } diff --git a/src/Ryujinx.Horizon/Ngc/NgcIpcServer.cs b/src/Ryujinx.Horizon/Ngc/NgcIpcServer.cs index b2a74fb22..ec73f96ae 100644 --- a/src/Ryujinx.Horizon/Ngc/NgcIpcServer.cs +++ b/src/Ryujinx.Horizon/Ngc/NgcIpcServer.cs @@ -3,7 +3,6 @@ using Ryujinx.Horizon.Sdk.Ngc.Detail; using Ryujinx.Horizon.Sdk.Sf.Hipc; using Ryujinx.Horizon.Sdk.Sm; -using System; namespace Ryujinx.Horizon.Ngc { @@ -46,6 +45,7 @@ public void Shutdown() { _serverManager.Dispose(); _profanityFilter.Dispose(); + _sm.Dispose(); } } } diff --git a/src/Ryujinx.Horizon/Ovln/OvlnIpcServer.cs b/src/Ryujinx.Horizon/Ovln/OvlnIpcServer.cs index c4580a861..d4257be8d 100644 --- a/src/Ryujinx.Horizon/Ovln/OvlnIpcServer.cs +++ b/src/Ryujinx.Horizon/Ovln/OvlnIpcServer.cs @@ -43,6 +43,7 @@ public void ServiceRequests() public void Shutdown() { _serverManager.Dispose(); + _sm.Dispose(); } } } diff --git a/src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs b/src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs index 1902cde23..669a64594 100644 --- a/src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs +++ b/src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs @@ -51,6 +51,7 @@ public void Shutdown() { _arp.Dispose(); _serverManager.Dispose(); + _sm.Dispose(); } } } diff --git a/src/Ryujinx.Horizon/Psc/PscIpcServer.cs b/src/Ryujinx.Horizon/Psc/PscIpcServer.cs index d6ac65685..8e574ddda 100644 --- a/src/Ryujinx.Horizon/Psc/PscIpcServer.cs +++ b/src/Ryujinx.Horizon/Psc/PscIpcServer.cs @@ -45,6 +45,7 @@ public void ServiceRequests() public void Shutdown() { _serverManager.Dispose(); + _sm.Dispose(); } } } diff --git a/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj b/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj index ae40f7b5e..d1f572d5c 100644 --- a/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj +++ b/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj @@ -5,6 +5,7 @@ + @@ -12,7 +13,13 @@ + + + + NU1605 + + diff --git a/src/Ryujinx.Horizon/Sdk/Account/Uid.cs b/src/Ryujinx.Horizon/Sdk/Account/Uid.cs index ada2c02ba..d612f4792 100644 --- a/src/Ryujinx.Horizon/Sdk/Account/Uid.cs +++ b/src/Ryujinx.Horizon/Sdk/Account/Uid.cs @@ -5,7 +5,7 @@ namespace Ryujinx.Horizon.Sdk.Account { - [StructLayout(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)] public readonly record struct Uid { public readonly ulong High; diff --git a/src/Ryujinx.Horizon/Sdk/Applet/AppletId.cs b/src/Ryujinx.Horizon/Sdk/Applet/AppletId.cs new file mode 100644 index 000000000..2b81fbf6f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Applet/AppletId.cs @@ -0,0 +1,71 @@ +namespace Ryujinx.Horizon.Sdk.Applet +{ + enum AppletId : uint + { + None = 0x00, + Application = 0x01, + OverlayApplet = 0x02, + SystemAppletMenu = 0x03, + SystemApplication = 0x04, + LibraryAppletAuth = 0x0A, + LibraryAppletCabinet = 0x0B, + LibraryAppletController = 0x0C, + LibraryAppletDataErase = 0x0D, + LibraryAppletError = 0x0E, + LibraryAppletNetConnect = 0x0F, + LibraryAppletPlayerSelect = 0x10, + LibraryAppletSwkbd = 0x11, + LibraryAppletMiiEdit = 0x12, + LibraryAppletWeb = 0x13, + LibraryAppletShop = 0x14, + LibraryAppletPhotoViewer = 0x15, + LibraryAppletSet = 0x16, + LibraryAppletOfflineWeb = 0x17, + LibraryAppletLoginShare = 0x18, + LibraryAppletWifiWebAuth = 0x19, + LibraryAppletMyPage = 0x1A, + LibraryAppletGift = 0x1B, + LibraryAppletUserMigration = 0x1C, + LibraryAppletPreomiaSys = 0x1D, + LibraryAppletStory = 0x1E, + LibraryAppletPreomiaUsr = 0x1F, + LibraryAppletPreomiaUsrDummy = 0x20, + LibraryAppletSample = 0x21, + LibraryAppletPromoteQualification = 0x22, + LibraryAppletOfflineWebFw17 = 0x32, + LibraryAppletOfflineWeb2Fw17 = 0x33, + LibraryAppletLoginShareFw17 = 0x35, + LibraryAppletLoginShare2Fw17 = 0x36, + LibraryAppletLoginShare3Fw17 = 0x37, + Unknown38 = 0x38, + DevlopmentTool = 0x3E8, + CombinationLA = 0x3F1, + AeSystemApplet = 0x3F2, + AeOverlayApplet = 0x3F3, + AeStarter = 0x3F4, + AeLibraryAppletAlone = 0x3F5, + AeLibraryApplet1 = 0x3F6, + AeLibraryApplet2 = 0x3F7, + AeLibraryApplet3 = 0x3F8, + AeLibraryApplet4 = 0x3F9, + AppletISA = 0x3FA, + AppletIOA = 0x3FB, + AppletISTA = 0x3FC, + AppletILA1 = 0x3FD, + AppletILA2 = 0x3FE, + CombinationLAFw17 = 0x700000DC, + AeSystemAppletFw17 = 0x700000E6, + AeOverlayAppletFw17 = 0x700000E7, + AeStarterFw17 = 0x700000E8, + AeLibraryAppletAloneFw17 = 0x700000E9, + AeLibraryApplet1Fw17 = 0x700000EA, + AeLibraryApplet2Fw17 = 0x700000EB, + AeLibraryApplet3Fw17 = 0x700000EC, + AeLibraryApplet4Fw17 = 0x700000ED, + AppletISAFw17 = 0x700000F0, + AppletIOAFw17 = 0x700000F1, + AppletISTAFw17 = 0x700000F2, + AppletILA1Fw17 = 0x700000F3, + AppletILA2Fw17 = 0x700000F4, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Applet/AppletResourceUserId.cs b/src/Ryujinx.Horizon/Sdk/Applet/AppletResourceUserId.cs new file mode 100644 index 000000000..00e2ad368 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Applet/AppletResourceUserId.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Applet +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x8)] + readonly struct AppletResourceUserId + { + public readonly ulong Id; + + public AppletResourceUserId(ulong id) + { + Id = id; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/AudioEvent.cs b/src/Ryujinx.Horizon/Sdk/Audio/AudioEvent.cs new file mode 100644 index 000000000..efa8d5bc1 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/AudioEvent.cs @@ -0,0 +1,50 @@ +using Ryujinx.Audio.Integration; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.OsTypes; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio +{ + class AudioEvent : IWritableEvent, IDisposable + { + private SystemEventType _systemEvent; + private readonly IExternalEvent _externalEvent; + + public AudioEvent() + { + Os.CreateSystemEvent(out _systemEvent, EventClearMode.ManualClear, interProcess: true); + + // We need to do this because the event will be signalled from a different thread. + _externalEvent = HorizonStatic.Syscall.GetExternalEvent(Os.GetWritableHandleOfSystemEvent(ref _systemEvent)); + } + + public void Signal() + { + _externalEvent.Signal(); + } + + public void Clear() + { + _externalEvent.Clear(); + } + + public int GetReadableHandle() + { + return Os.GetReadableHandleOfSystemEvent(ref _systemEvent); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Os.DestroySystemEvent(ref _systemEvent); + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs b/src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs new file mode 100644 index 000000000..c18bfee9f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Audio +{ + static class AudioResult + { + private const int ModuleId = 153; + + public static Result DeviceNotFound => new(ModuleId, 1); + public static Result UnsupportedRevision => new(ModuleId, 2); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs new file mode 100644 index 000000000..f67ea7298 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs @@ -0,0 +1,252 @@ +using Ryujinx.Audio.Renderer.Device; +using Ryujinx.Audio.Renderer.Server; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class AudioDevice : IAudioDevice, IDisposable + { + private readonly VirtualDeviceSessionRegistry _registry; + private readonly VirtualDeviceSession[] _sessions; + private readonly bool _isUsbDeviceSupported; + + private SystemEventType _audioEvent; + private SystemEventType _audioInputEvent; + private SystemEventType _audioOutputEvent; + + public AudioDevice(VirtualDeviceSessionRegistry registry, AppletResourceUserId appletResourceId, uint revision) + { + _registry = registry; + + BehaviourContext behaviourContext = new(); + behaviourContext.SetUserRevision((int)revision); + + _isUsbDeviceSupported = behaviourContext.IsAudioUsbDeviceOutputSupported(); + _sessions = registry.GetSessionByAppletResourceId(appletResourceId.Id); + + Os.CreateSystemEvent(out _audioEvent, EventClearMode.AutoClear, interProcess: true); + Os.CreateSystemEvent(out _audioInputEvent, EventClearMode.AutoClear, interProcess: true); + Os.CreateSystemEvent(out _audioOutputEvent, EventClearMode.AutoClear, interProcess: true); + } + + private bool TryGetDeviceByName(out VirtualDeviceSession result, string name, bool ignoreRevLimitation = false) + { + result = null; + + foreach (VirtualDeviceSession session in _sessions) + { + if (session.Device.Name.Equals(name)) + { + if (!ignoreRevLimitation && !_isUsbDeviceSupported && session.Device.IsUsbDevice()) + { + return false; + } + + result = session; + + return true; + } + } + + return false; + } + + [CmifCommand(0)] + public Result ListAudioDeviceName([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span names, out int nameCount) + { + int count = 0; + + foreach (VirtualDeviceSession session in _sessions) + { + if (!_isUsbDeviceSupported && session.Device.IsUsbDevice()) + { + continue; + } + + if (count >= names.Length) + { + break; + } + + names[count] = new DeviceName(session.Device.Name); + + count++; + } + + nameCount = count; + + return Result.Success; + } + + [CmifCommand(1)] + public Result SetAudioDeviceOutputVolume([Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan name, float volume) + { + if (name.Length > 0 && TryGetDeviceByName(out VirtualDeviceSession result, name[0].ToString(), ignoreRevLimitation: true)) + { + if (!_isUsbDeviceSupported && result.Device.IsUsbDevice()) + { + result = _sessions[0]; + } + + result.Volume = volume; + } + + return Result.Success; + } + + [CmifCommand(2)] + public Result GetAudioDeviceOutputVolume([Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan name, out float volume) + { + if (name.Length > 0 && TryGetDeviceByName(out VirtualDeviceSession result, name[0].ToString())) + { + volume = result.Volume; + } + else + { + volume = 0f; + } + + return Result.Success; + } + + [CmifCommand(3)] + public Result GetActiveAudioDeviceName([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span name) + { + VirtualDevice device = _registry.ActiveDevice; + + if (!_isUsbDeviceSupported && device.IsUsbDevice()) + { + device = _registry.DefaultDevice; + } + + if (name.Length > 0) + { + name[0] = new DeviceName(device.Name); + } + + return Result.Success; + } + + [CmifCommand(4)] + public Result QueryAudioDeviceSystemEvent([CopyHandle] out int eventHandle) + { + eventHandle = Os.GetReadableHandleOfSystemEvent(ref _audioEvent); + + return Result.Success; + } + + [CmifCommand(5)] + public Result GetActiveChannelCount(out int channelCount) + { + VirtualDevice device = _registry.ActiveDevice; + + if (!_isUsbDeviceSupported && device.IsUsbDevice()) + { + device = _registry.DefaultDevice; + } + + channelCount = (int)device.ChannelCount; + + return Result.Success; + } + + [CmifCommand(6)] // 3.0.0+ + public Result ListAudioDeviceNameAuto([Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span names, out int nameCount) + { + return ListAudioDeviceName(names, out nameCount); + } + + [CmifCommand(7)] // 3.0.0+ + public Result SetAudioDeviceOutputVolumeAuto([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan name, float volume) + { + return SetAudioDeviceOutputVolume(name, volume); + } + + [CmifCommand(8)] // 3.0.0+ + public Result GetAudioDeviceOutputVolumeAuto([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan name, out float volume) + { + return GetAudioDeviceOutputVolume(name, out volume); + } + + [CmifCommand(10)] // 3.0.0+ + public Result GetActiveAudioDeviceNameAuto([Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span name) + { + return GetActiveAudioDeviceName(name); + } + + [CmifCommand(11)] // 3.0.0+ + public Result QueryAudioDeviceInputEvent([CopyHandle] out int eventHandle) + { + eventHandle = Os.GetReadableHandleOfSystemEvent(ref _audioInputEvent); + + return Result.Success; + } + + [CmifCommand(12)] // 3.0.0+ + public Result QueryAudioDeviceOutputEvent([CopyHandle] out int eventHandle) + { + eventHandle = Os.GetReadableHandleOfSystemEvent(ref _audioOutputEvent); + + return Result.Success; + } + + [CmifCommand(13)] // 13.0.0+ + public Result GetActiveAudioOutputDeviceName([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span name) + { + if (name.Length > 0) + { + name[0] = new DeviceName(_registry.ActiveDevice.GetOutputDeviceName()); + } + + return Result.Success; + } + + [CmifCommand(14)] // 13.0.0+ + public Result ListAudioOutputDeviceName([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span names, out int nameCount) + { + int count = 0; + + foreach (VirtualDeviceSession session in _sessions) + { + if (!_isUsbDeviceSupported && session.Device.IsUsbDevice()) + { + continue; + } + + if (count >= names.Length) + { + break; + } + + names[count] = new DeviceName(session.Device.GetOutputDeviceName()); + + count++; + } + + nameCount = count; + + return Result.Success; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Os.DestroySystemEvent(ref _audioEvent); + Os.DestroySystemEvent(ref _audioInputEvent); + Os.DestroySystemEvent(ref _audioOutputEvent); + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioIn.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioIn.cs new file mode 100644 index 000000000..464ede581 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioIn.cs @@ -0,0 +1,171 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Input; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class AudioIn : IAudioIn, IDisposable + { + private readonly AudioInputSystem _impl; + private int _processHandle; + + public AudioIn(AudioInputSystem impl, int processHandle) + { + _impl = impl; + _processHandle = processHandle; + } + + [CmifCommand(0)] + public Result GetAudioInState(out AudioDeviceState state) + { + state = _impl.GetState(); + + return Result.Success; + } + + [CmifCommand(1)] + public Result Start() + { + return new Result((int)_impl.Start()); + } + + [CmifCommand(2)] + public Result Stop() + { + return new Result((int)_impl.Stop()); + } + + [CmifCommand(3)] + public Result AppendAudioInBuffer(ulong bufferTag, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan buffer) + { + AudioUserBuffer userBuffer = default; + + if (buffer.Length > 0) + { + userBuffer = buffer[0]; + } + + return new Result((int)_impl.AppendBuffer(bufferTag, ref userBuffer)); + } + + [CmifCommand(4)] + public Result RegisterBufferEvent([CopyHandle] out int eventHandle) + { + eventHandle = 0; + + if (_impl.RegisterBufferEvent() is AudioEvent audioEvent) + { + eventHandle = audioEvent.GetReadableHandle(); + } + + return Result.Success; + } + + [CmifCommand(5)] + public Result GetReleasedAudioInBuffers(out uint count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span bufferTags) + { + return new Result((int)_impl.GetReleasedBuffers(bufferTags, out count)); + } + + [CmifCommand(6)] + public Result ContainsAudioInBuffer(out bool contains, ulong bufferTag) + { + contains = _impl.ContainsBuffer(bufferTag); + + return Result.Success; + } + + [CmifCommand(7)] // 3.0.0+ + public Result AppendUacInBuffer( + ulong bufferTag, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan buffer, + [CopyHandle] int eventHandle) + { + AudioUserBuffer userBuffer = default; + + if (buffer.Length > 0) + { + userBuffer = buffer[0]; + } + + return new Result((int)_impl.AppendUacBuffer(bufferTag, ref userBuffer, (uint)eventHandle)); + } + + [CmifCommand(8)] // 3.0.0+ + public Result AppendAudioInBufferAuto(ulong bufferTag, [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan buffer) + { + return AppendAudioInBuffer(bufferTag, buffer); + } + + [CmifCommand(9)] // 3.0.0+ + public Result GetReleasedAudioInBuffersAuto(out uint count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span bufferTags) + { + return GetReleasedAudioInBuffers(out count, bufferTags); + } + + [CmifCommand(10)] // 3.0.0+ + public Result AppendUacInBufferAuto( + ulong bufferTag, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan buffer, + [CopyHandle] int eventHandle) + { + return AppendUacInBuffer(bufferTag, buffer, eventHandle); + } + + [CmifCommand(11)] // 4.0.0+ + public Result GetAudioInBufferCount(out uint bufferCount) + { + bufferCount = _impl.GetBufferCount(); + + return Result.Success; + } + + [CmifCommand(12)] // 4.0.0+ + public Result SetDeviceGain(float gain) + { + _impl.SetVolume(gain); + + return Result.Success; + } + + [CmifCommand(13)] // 4.0.0+ + public Result GetDeviceGain(out float gain) + { + gain = _impl.GetVolume(); + + return Result.Success; + } + + [CmifCommand(14)] // 6.0.0+ + public Result FlushAudioInBuffers(out bool pending) + { + pending = _impl.FlushBuffers(); + + return Result.Success; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _impl.Dispose(); + + if (_processHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_processHandle); + + _processHandle = 0; + } + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInManager.cs new file mode 100644 index 000000000..d5d047201 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInManager.cs @@ -0,0 +1,130 @@ +using Ryujinx.Audio; +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Input; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class AudioInManager : IAudioInManager + { + private readonly AudioInputManager _impl; + + public AudioInManager(AudioInputManager impl) + { + _impl = impl; + } + + [CmifCommand(0)] + public Result ListAudioIns(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span names) + { + string[] deviceNames = _impl.ListAudioIns(filtered: false); + + count = 0; + + foreach (string deviceName in deviceNames) + { + if (count >= names.Length) + { + break; + } + + names[count++] = new DeviceName(deviceName); + } + + return Result.Success; + } + + [CmifCommand(1)] + public Result OpenAudioIn( + out AudioOutputConfiguration outputConfiguration, + out IAudioIn audioIn, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span outName, + AudioInputConfiguration parameter, + AppletResourceUserId appletResourceId, + [CopyHandle] int processHandle, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan name, + [ClientProcessId] ulong pid) + { + var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle); + + ResultCode rc = _impl.OpenAudioIn( + out string outputDeviceName, + out outputConfiguration, + out AudioInputSystem inSystem, + clientMemoryManager, + name.Length > 0 ? name[0].ToString() : string.Empty, + SampleFormat.PcmInt16, + ref parameter); + + if (rc == ResultCode.Success && outName.Length > 0) + { + outName[0] = new DeviceName(outputDeviceName); + } + + audioIn = new AudioIn(inSystem, processHandle); + + return new Result((int)rc); + } + + [CmifCommand(2)] // 3.0.0+ + public Result ListAudioInsAuto(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span names) + { + return ListAudioIns(out count, names); + } + + [CmifCommand(3)] // 3.0.0+ + public Result OpenAudioInAuto( + out AudioOutputConfiguration outputConfig, + out IAudioIn audioIn, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span outName, + AudioInputConfiguration parameter, + AppletResourceUserId appletResourceId, + [CopyHandle] int processHandle, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan name, + [ClientProcessId] ulong pid) + { + return OpenAudioIn(out outputConfig, out audioIn, outName, parameter, appletResourceId, processHandle, name, pid); + } + + [CmifCommand(4)] // 3.0.0+ + public Result ListAudioInsAutoFiltered(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span names) + { + string[] deviceNames = _impl.ListAudioIns(filtered: true); + + count = 0; + + foreach (string deviceName in deviceNames) + { + if (count >= names.Length) + { + break; + } + + names[count++] = new DeviceName(deviceName); + } + + return Result.Success; + } + + [CmifCommand(5)] // 5.0.0+ + public Result OpenAudioInProtocolSpecified( + out AudioOutputConfiguration outputConfig, + out IAudioIn audioIn, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span outName, + AudioInProtocol protocol, + AudioInputConfiguration parameter, + AppletResourceUserId appletResourceId, + [CopyHandle] int processHandle, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan name, + [ClientProcessId] ulong pid) + { + // NOTE: We always assume that only the default device will be plugged (we never report any USB Audio Class type devices). + + return OpenAudioIn(out outputConfig, out audioIn, outName, parameter, appletResourceId, processHandle, name, pid); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocol.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocol.cs new file mode 100644 index 000000000..48785f1c0 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocol.cs @@ -0,0 +1,23 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x1)] + struct AudioInProtocol + { + public AudioInProtocolName Name; + public Array7 Padding; + + public AudioInProtocol(AudioInProtocolName name) + { + Name = name; + Padding = new(); + } + + public override readonly string ToString() + { + return Name.ToString(); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocolName.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocolName.cs new file mode 100644 index 000000000..68d283cc5 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocolName.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + enum AudioInProtocolName : byte + { + DeviceIn = 0, + UacIn = 1, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOut.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOut.cs new file mode 100644 index 000000000..7607e2643 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOut.cs @@ -0,0 +1,154 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Output; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class AudioOut : IAudioOut, IDisposable + { + private readonly AudioOutputSystem _impl; + private int _processHandle; + + public AudioOut(AudioOutputSystem impl, int processHandle) + { + _impl = impl; + _processHandle = processHandle; + } + + [CmifCommand(0)] + public Result GetAudioOutState(out AudioDeviceState state) + { + state = _impl.GetState(); + + return Result.Success; + } + + [CmifCommand(1)] + public Result Start() + { + return new Result((int)_impl.Start()); + } + + [CmifCommand(2)] + public Result Stop() + { + return new Result((int)_impl.Stop()); + } + + [CmifCommand(3)] + public Result AppendAudioOutBuffer(ulong bufferTag, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan buffer) + { + AudioUserBuffer userBuffer = default; + + if (buffer.Length > 0) + { + userBuffer = buffer[0]; + } + + return new Result((int)_impl.AppendBuffer(bufferTag, ref userBuffer)); + } + + [CmifCommand(4)] + public Result RegisterBufferEvent([CopyHandle] out int eventHandle) + { + eventHandle = 0; + + if (_impl.RegisterBufferEvent() is AudioEvent audioEvent) + { + eventHandle = audioEvent.GetReadableHandle(); + } + + return Result.Success; + } + + [CmifCommand(5)] + public Result GetReleasedAudioOutBuffers(out uint count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span bufferTags) + { + return new Result((int)_impl.GetReleasedBuffer(bufferTags, out count)); + } + + [CmifCommand(6)] + public Result ContainsAudioOutBuffer(out bool contains, ulong bufferTag) + { + contains = _impl.ContainsBuffer(bufferTag); + + return Result.Success; + } + + [CmifCommand(7)] // 3.0.0+ + public Result AppendAudioOutBufferAuto(ulong bufferTag, [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan buffer) + { + return AppendAudioOutBuffer(bufferTag, buffer); + } + + [CmifCommand(8)] // 3.0.0+ + public Result GetReleasedAudioOutBuffersAuto(out uint count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span bufferTags) + { + return GetReleasedAudioOutBuffers(out count, bufferTags); + } + + [CmifCommand(9)] // 4.0.0+ + public Result GetAudioOutBufferCount(out uint bufferCount) + { + bufferCount = _impl.GetBufferCount(); + + return Result.Success; + } + + [CmifCommand(10)] // 4.0.0+ + public Result GetAudioOutPlayedSampleCount(out ulong sampleCount) + { + sampleCount = _impl.GetPlayedSampleCount(); + + return Result.Success; + } + + [CmifCommand(11)] // 4.0.0+ + public Result FlushAudioOutBuffers(out bool pending) + { + pending = _impl.FlushBuffers(); + + return Result.Success; + } + + [CmifCommand(12)] // 6.0.0+ + public Result SetAudioOutVolume(float volume) + { + _impl.SetVolume(volume); + + return Result.Success; + } + + [CmifCommand(13)] // 6.0.0+ + public Result GetAudioOutVolume(out float volume) + { + volume = _impl.GetVolume(); + + return Result.Success; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _impl.Dispose(); + + if (_processHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_processHandle); + + _processHandle = 0; + } + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOutManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOutManager.cs new file mode 100644 index 000000000..3d129470c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOutManager.cs @@ -0,0 +1,93 @@ +using Ryujinx.Audio; +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Output; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class AudioOutManager : IAudioOutManager + { + private readonly AudioOutputManager _impl; + + public AudioOutManager(AudioOutputManager impl) + { + _impl = impl; + } + + [CmifCommand(0)] + public Result ListAudioOuts(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span names) + { + string[] deviceNames = _impl.ListAudioOuts(); + + count = 0; + + foreach (string deviceName in deviceNames) + { + if (count >= names.Length) + { + break; + } + + names[count++] = new DeviceName(deviceName); + } + + return Result.Success; + } + + [CmifCommand(1)] + public Result OpenAudioOut( + out AudioOutputConfiguration outputConfig, + out IAudioOut audioOut, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span outName, + AudioInputConfiguration parameter, + AppletResourceUserId appletResourceId, + [CopyHandle] int processHandle, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan name, + [ClientProcessId] ulong pid) + { + var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle); + + ResultCode rc = _impl.OpenAudioOut( + out string outputDeviceName, + out outputConfig, + out AudioOutputSystem outSystem, + clientMemoryManager, + name.Length > 0 ? name[0].ToString() : string.Empty, + SampleFormat.PcmInt16, + ref parameter); + + if (rc == ResultCode.Success && outName.Length > 0) + { + outName[0] = new DeviceName(outputDeviceName); + } + + audioOut = new AudioOut(outSystem, processHandle); + + return new Result((int)rc); + } + + [CmifCommand(2)] // 3.0.0+ + public Result ListAudioOutsAuto(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span names) + { + return ListAudioOuts(out count, names); + } + + [CmifCommand(3)] // 3.0.0+ + public Result OpenAudioOutAuto( + out AudioOutputConfiguration outputConfig, + out IAudioOut audioOut, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span outName, + AudioInputConfiguration parameter, + AppletResourceUserId appletResourceId, + [CopyHandle] int processHandle, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan name, + [ClientProcessId] ulong pid) + { + return OpenAudioOut(out outputConfig, out audioOut, outName, parameter, appletResourceId, processHandle, name, pid); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs new file mode 100644 index 000000000..776df641a --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs @@ -0,0 +1,187 @@ +using Ryujinx.Audio; +using Ryujinx.Audio.Integration; +using Ryujinx.Audio.Renderer.Server; +using Ryujinx.Common.Memory; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; +using System.Buffers; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class AudioRenderer : IAudioRenderer, IDisposable + { + private readonly AudioRenderSystem _renderSystem; + private int _workBufferHandle; + private int _processHandle; + + public AudioRenderer(AudioRenderSystem renderSystem, int workBufferHandle, int processHandle) + { + _renderSystem = renderSystem; + _workBufferHandle = workBufferHandle; + _processHandle = processHandle; + } + + [CmifCommand(0)] + public Result GetSampleRate(out int sampleRate) + { + sampleRate = (int)_renderSystem.GetSampleRate(); + + return Result.Success; + } + + [CmifCommand(1)] + public Result GetSampleCount(out int sampleCount) + { + sampleCount = (int)_renderSystem.GetSampleCount(); + + return Result.Success; + } + + [CmifCommand(2)] + public Result GetMixBufferCount(out int mixBufferCount) + { + mixBufferCount = (int)_renderSystem.GetMixBufferCount(); + + return Result.Success; + } + + [CmifCommand(3)] + public Result GetState(out int state) + { + state = _renderSystem.IsActive() ? 0 : 1; + + return Result.Success; + } + + [CmifCommand(4)] + public Result RequestUpdate( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span output, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span performanceOutput, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan input) + { + using IMemoryOwner outputOwner = ByteMemoryPool.Rent(output.Length); + using IMemoryOwner performanceOutputOwner = ByteMemoryPool.Rent(performanceOutput.Length); + + Memory outputMemory = outputOwner.Memory; + Memory performanceOutputMemory = performanceOutputOwner.Memory; + + using MemoryHandle outputHandle = outputMemory.Pin(); + using MemoryHandle performanceOutputHandle = performanceOutputMemory.Pin(); + + Result result = new Result((int)_renderSystem.Update(outputMemory, performanceOutputMemory, input.ToArray())); + + outputMemory.Span.CopyTo(output); + performanceOutputMemory.Span.CopyTo(performanceOutput); + + return result; + } + + [CmifCommand(5)] + public Result Start() + { + _renderSystem.Start(); + + return Result.Success; + } + + [CmifCommand(6)] + public Result Stop() + { + _renderSystem.Stop(); + + return Result.Success; + } + + [CmifCommand(7)] + public Result QuerySystemEvent([CopyHandle] out int eventHandle) + { + ResultCode rc = _renderSystem.QuerySystemEvent(out IWritableEvent systemEvent); + + eventHandle = 0; + + if (rc == ResultCode.Success && systemEvent is AudioEvent audioEvent) + { + eventHandle = audioEvent.GetReadableHandle(); + } + + return new Result((int)rc); + } + + [CmifCommand(8)] + public Result SetRenderingTimeLimit(int percent) + { + _renderSystem.SetRenderingTimeLimitPercent((uint)percent); + + return Result.Success; + } + + [CmifCommand(9)] + public Result GetRenderingTimeLimit(out int percent) + { + percent = (int)_renderSystem.GetRenderingTimeLimit(); + + return Result.Success; + } + + [CmifCommand(10)] // 3.0.0+ + public Result RequestUpdateAuto( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span output, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span performanceOutput, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan input) + { + return RequestUpdate(output, performanceOutput, input); + } + + [CmifCommand(11)] // 3.0.0+ + public Result ExecuteAudioRendererRendering() + { + return new Result((int)_renderSystem.ExecuteAudioRendererRendering()); + } + + [CmifCommand(12)] // 15.0.0+ + public Result SetVoiceDropParameter(float voiceDropParameter) + { + _renderSystem.SetVoiceDropParameter(voiceDropParameter); + + return Result.Success; + } + + [CmifCommand(13)] // 15.0.0+ + public Result GetVoiceDropParameter(out float voiceDropParameter) + { + voiceDropParameter = _renderSystem.GetVoiceDropParameter(); + + return Result.Success; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _renderSystem.Dispose(); + + if (_workBufferHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_workBufferHandle); + + _workBufferHandle = 0; + } + + if (_processHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_processHandle); + + _processHandle = 0; + } + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererManager.cs new file mode 100644 index 000000000..7138d27ce --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererManager.cs @@ -0,0 +1,132 @@ +using Ryujinx.Audio.Renderer.Device; +using Ryujinx.Audio.Renderer.Server; +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class AudioRendererManager : IAudioRendererManager + { + private const uint InitialRevision = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('1' << 24); + + private readonly Ryujinx.Audio.Renderer.Server.AudioRendererManager _impl; + private readonly VirtualDeviceSessionRegistry _registry; + + public AudioRendererManager(Ryujinx.Audio.Renderer.Server.AudioRendererManager impl, VirtualDeviceSessionRegistry registry) + { + _impl = impl; + _registry = registry; + } + + [CmifCommand(0)] + public Result OpenAudioRenderer( + out IAudioRenderer renderer, + AudioRendererParameterInternal parameter, + [CopyHandle] int workBufferHandle, + [CopyHandle] int processHandle, + ulong workBufferSize, + AppletResourceUserId appletResourceId, + [ClientProcessId] ulong pid) + { + var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle); + ulong workBufferAddress = HorizonStatic.Syscall.GetTransferMemoryAddress(workBufferHandle); + + Result result = new Result((int)_impl.OpenAudioRenderer( + out var renderSystem, + clientMemoryManager, + ref parameter.Configuration, + appletResourceId.Id, + workBufferAddress, + workBufferSize, + (uint)processHandle)); + + if (result.IsSuccess) + { + renderer = new AudioRenderer(renderSystem, workBufferHandle, processHandle); + } + else + { + renderer = null; + + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + HorizonStatic.Syscall.CloseHandle(processHandle); + } + + return result; + } + + [CmifCommand(1)] + public Result GetWorkBufferSize(out long workBufferSize, AudioRendererParameterInternal parameter) + { + if (BehaviourContext.CheckValidRevision(parameter.Configuration.Revision)) + { + workBufferSize = (long)Ryujinx.Audio.Renderer.Server.AudioRendererManager.GetWorkBufferSize(ref parameter.Configuration); + + Logger.Debug?.Print(LogClass.ServiceAudio, $"WorkBufferSize is 0x{workBufferSize:x16}."); + + return Result.Success; + } + else + { + workBufferSize = 0; + + Logger.Warning?.Print(LogClass.ServiceAudio, $"Library Revision REV{BehaviourContext.GetRevisionNumber(parameter.Configuration.Revision)} is not supported!"); + + return AudioResult.UnsupportedRevision; + } + } + + [CmifCommand(2)] + public Result GetAudioDeviceService(out IAudioDevice audioDevice, AppletResourceUserId appletResourceId) + { + audioDevice = new AudioDevice(_registry, appletResourceId, InitialRevision); + + return Result.Success; + } + + [CmifCommand(3)] // 3.0.0+ + public Result OpenAudioRendererForManualExecution( + out IAudioRenderer renderer, + AudioRendererParameterInternal parameter, + ulong workBufferAddress, + [CopyHandle] int processHandle, + ulong workBufferSize, + AppletResourceUserId appletResourceId, + [ClientProcessId] ulong pid) + { + var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle); + + Result result = new Result((int)_impl.OpenAudioRenderer( + out var renderSystem, + clientMemoryManager, + ref parameter.Configuration, + appletResourceId.Id, + workBufferAddress, + workBufferSize, + (uint)processHandle)); + + if (result.IsSuccess) + { + renderer = new AudioRenderer(renderSystem, 0, processHandle); + } + else + { + renderer = null; + + HorizonStatic.Syscall.CloseHandle(processHandle); + } + + return result; + } + + [CmifCommand(4)] // 4.0.0+ + public Result GetAudioDeviceServiceWithRevisionInfo(out IAudioDevice audioDevice, AppletResourceUserId appletResourceId, uint revision) + { + audioDevice = new AudioDevice(_registry, appletResourceId, revision); + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererParameterInternal.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererParameterInternal.cs new file mode 100644 index 000000000..e5fcf7b3b --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererParameterInternal.cs @@ -0,0 +1,14 @@ +using Ryujinx.Audio.Renderer.Parameter; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + struct AudioRendererParameterInternal + { + public AudioRendererConfiguration Configuration; + + public AudioRendererParameterInternal(AudioRendererConfiguration configuration) + { + Configuration = configuration; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioSnoopManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioSnoopManager.cs new file mode 100644 index 000000000..cf1fe3d1d --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioSnoopManager.cs @@ -0,0 +1,30 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class AudioSnoopManager : IAudioSnoopManager + { + // Note: The interface changed completely on firmware 17.0.0, this implementation is for older firmware. + + [CmifCommand(0)] + public Result EnableDspUsageMeasurement() + { + return Result.Success; + } + + [CmifCommand(1)] + public Result DisableDspUsageMeasurement() + { + return Result.Success; + } + + [CmifCommand(6)] + public Result GetDspUsage(out uint usage) + { + usage = 0; + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/DeviceName.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/DeviceName.cs new file mode 100644 index 000000000..b77e2f402 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/DeviceName.cs @@ -0,0 +1,30 @@ +using Ryujinx.Common.Memory; +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x100, Pack = 1)] + struct DeviceName + { + public Array256 Name; + + public DeviceName(string name) + { + Name = new(); + Encoding.ASCII.GetBytes(name, Name.AsSpan()); + } + + public override string ToString() + { + int length = Name.AsSpan().IndexOf((byte)0); + if (length < 0) + { + length = 0x100; + } + + return Encoding.ASCII.GetString(Name.AsSpan()[..length]); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorder.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorder.cs new file mode 100644 index 000000000..393914371 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorder.cs @@ -0,0 +1,147 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class FinalOutputRecorder : IFinalOutputRecorder, IDisposable + { + private int _processHandle; + private SystemEventType _event; + + public FinalOutputRecorder(int processHandle) + { + _processHandle = processHandle; + Os.CreateSystemEvent(out _event, EventClearMode.ManualClear, interProcess: true); + } + + [CmifCommand(0)] + public Result GetFinalOutputRecorderState(out uint state) + { + state = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceAudio); + + return Result.Success; + } + + [CmifCommand(1)] + public Result Start() + { + Logger.Stub?.PrintStub(LogClass.ServiceAudio); + + return Result.Success; + } + + [CmifCommand(2)] + public Result Stop() + { + Logger.Stub?.PrintStub(LogClass.ServiceAudio); + + return Result.Success; + } + + [CmifCommand(3)] + public Result AppendFinalOutputRecorderBuffer([Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan buffer, ulong bufferClientPtr) + { + Logger.Stub?.PrintStub(LogClass.ServiceAudio, new { bufferClientPtr }); + + return Result.Success; + } + + [CmifCommand(4)] + public Result RegisterBufferEvent([CopyHandle] out int eventHandle) + { + eventHandle = Os.GetReadableHandleOfSystemEvent(ref _event); + + Logger.Stub?.PrintStub(LogClass.ServiceAudio); + + return Result.Success; + } + + [CmifCommand(5)] + public Result GetReleasedFinalOutputRecorderBuffers([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span buffer, out uint count, out ulong released) + { + count = 0; + released = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceAudio); + + return Result.Success; + } + + [CmifCommand(6)] + public Result ContainsFinalOutputRecorderBuffer(ulong bufferPointer, out bool contains) + { + contains = false; + + Logger.Stub?.PrintStub(LogClass.ServiceAudio, new { bufferPointer }); + + return Result.Success; + } + + [CmifCommand(7)] + public Result GetFinalOutputRecorderBufferEndTime(ulong bufferPointer, out ulong released) + { + released = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceAudio, new { bufferPointer }); + + return Result.Success; + } + + [CmifCommand(8)] // 3.0.0+ + public Result AppendFinalOutputRecorderBufferAuto([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan buffer, ulong bufferClientPtr) + { + return AppendFinalOutputRecorderBuffer(buffer, bufferClientPtr); + } + + [CmifCommand(9)] // 3.0.0+ + public Result GetReleasedFinalOutputRecorderBuffersAuto([Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span buffer, out uint count, out ulong released) + { + return GetReleasedFinalOutputRecorderBuffers(buffer, out count, out released); + } + + [CmifCommand(10)] // 6.0.0+ + public Result FlushFinalOutputRecorderBuffers(out bool pending) + { + pending = false; + + Logger.Stub?.PrintStub(LogClass.ServiceAudio); + + return Result.Success; + } + + [CmifCommand(11)] // 9.0.0+ + public Result AttachWorkBuffer(FinalOutputRecorderParameterInternal parameter) + { + Logger.Stub?.PrintStub(LogClass.ServiceAudio, new { parameter }); + + return Result.Success; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Os.DestroySystemEvent(ref _event); + + if (_processHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_processHandle); + + _processHandle = 0; + } + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderManager.cs new file mode 100644 index 000000000..76491bb79 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderManager.cs @@ -0,0 +1,23 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class FinalOutputRecorderManager : IFinalOutputRecorderManager + { + [CmifCommand(0)] + public Result OpenFinalOutputRecorder( + out IFinalOutputRecorder recorder, + FinalOutputRecorderParameter parameter, + [CopyHandle] int processHandle, + out FinalOutputRecorderParameterInternal outParameter, + AppletResourceUserId appletResourceId) + { + recorder = new FinalOutputRecorder(processHandle); + outParameter = new(parameter.SampleRate, 2, 0); + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameter.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameter.cs new file mode 100644 index 000000000..afa060fca --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameter.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x4)] + readonly struct FinalOutputRecorderParameter + { + public readonly uint SampleRate; + public readonly uint Padding; + + public FinalOutputRecorderParameter(uint sampleRate) + { + SampleRate = sampleRate; + Padding = 0; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameterInternal.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameterInternal.cs new file mode 100644 index 000000000..e88398eba --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameterInternal.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x4)] + readonly struct FinalOutputRecorderParameterInternal + { + public readonly uint SampleRate; + public readonly uint ChannelCount; + public readonly uint UseLargeFrameSize; + public readonly uint Padding; + + public FinalOutputRecorderParameterInternal(uint sampleRate, uint channelCount, uint useLargeFrameSize) + { + SampleRate = sampleRate; + ChannelCount = channelCount; + UseLargeFrameSize = useLargeFrameSize; + Padding = 0; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioDevice.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioDevice.cs new file mode 100644 index 000000000..3df1fe227 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioDevice.cs @@ -0,0 +1,24 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IAudioDevice : IServiceObject + { + Result ListAudioDeviceName(Span names, out int nameCount); + Result SetAudioDeviceOutputVolume(ReadOnlySpan name, float volume); + Result GetAudioDeviceOutputVolume(ReadOnlySpan name, out float volume); + Result GetActiveAudioDeviceName(Span name); + Result QueryAudioDeviceSystemEvent(out int eventHandle); + Result GetActiveChannelCount(out int channelCount); + Result ListAudioDeviceNameAuto(Span names, out int nameCount); + Result SetAudioDeviceOutputVolumeAuto(ReadOnlySpan name, float volume); + Result GetAudioDeviceOutputVolumeAuto(ReadOnlySpan name, out float volume); + Result GetActiveAudioDeviceNameAuto(Span name); + Result QueryAudioDeviceInputEvent(out int eventHandle); + Result QueryAudioDeviceOutputEvent(out int eventHandle); + Result GetActiveAudioOutputDeviceName(Span name); + Result ListAudioOutputDeviceName(Span names, out int nameCount); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioIn.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioIn.cs new file mode 100644 index 000000000..bdc3bcf62 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioIn.cs @@ -0,0 +1,26 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IAudioIn : IServiceObject + { + Result GetAudioInState(out AudioDeviceState state); + Result Start(); + Result Stop(); + Result AppendAudioInBuffer(ulong bufferTag, ReadOnlySpan buffer); + Result RegisterBufferEvent(out int eventHandle); + Result GetReleasedAudioInBuffers(out uint count, Span bufferTags); + Result ContainsAudioInBuffer(out bool contains, ulong bufferTag); + Result AppendUacInBuffer(ulong bufferTag, ReadOnlySpan buffer, int eventHandle); + Result AppendAudioInBufferAuto(ulong bufferTag, ReadOnlySpan buffer); + Result GetReleasedAudioInBuffersAuto(out uint count, Span bufferTags); + Result AppendUacInBufferAuto(ulong bufferTag, ReadOnlySpan buffer, int eventHandle); + Result GetAudioInBufferCount(out uint bufferCount); + Result SetDeviceGain(float gain); + Result GetDeviceGain(out float gain); + Result FlushAudioInBuffers(out bool pending); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioInManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioInManager.cs new file mode 100644 index 000000000..e7f32fbd2 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioInManager.cs @@ -0,0 +1,43 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IAudioInManager : IServiceObject + { + Result ListAudioIns(out int count, Span names); + Result OpenAudioIn( + out AudioOutputConfiguration outputConfig, + out IAudioIn audioIn, + Span outName, + AudioInputConfiguration parameter, + AppletResourceUserId appletResourceId, + int processHandle, + ReadOnlySpan name, + ulong pid); + Result ListAudioInsAuto(out int count, Span names); + Result OpenAudioInAuto( + out AudioOutputConfiguration outputConfig, + out IAudioIn audioIn, + Span outName, + AudioInputConfiguration parameter, + AppletResourceUserId appletResourceId, + int processHandle, + ReadOnlySpan name, + ulong pid); + Result ListAudioInsAutoFiltered(out int count, Span names); + Result OpenAudioInProtocolSpecified( + out AudioOutputConfiguration outputConfig, + out IAudioIn audioIn, + Span outName, + AudioInProtocol protocol, + AudioInputConfiguration parameter, + AppletResourceUserId appletResourceId, + int processHandle, + ReadOnlySpan name, + ulong pid); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOut.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOut.cs new file mode 100644 index 000000000..1b2009260 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOut.cs @@ -0,0 +1,25 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IAudioOut : IServiceObject + { + Result GetAudioOutState(out AudioDeviceState state); + Result Start(); + Result Stop(); + Result AppendAudioOutBuffer(ulong bufferTag, ReadOnlySpan buffer); + Result RegisterBufferEvent(out int eventHandle); + Result GetReleasedAudioOutBuffers(out uint count, Span bufferTags); + Result ContainsAudioOutBuffer(out bool contains, ulong bufferTag); + Result AppendAudioOutBufferAuto(ulong bufferTag, ReadOnlySpan buffer); + Result GetReleasedAudioOutBuffersAuto(out uint count, Span bufferTags); + Result GetAudioOutBufferCount(out uint bufferCount); + Result GetAudioOutPlayedSampleCount(out ulong sampleCount); + Result FlushAudioOutBuffers(out bool pending); + Result SetAudioOutVolume(float volume); + Result GetAudioOutVolume(out float volume); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOutManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOutManager.cs new file mode 100644 index 000000000..40d62836b --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOutManager.cs @@ -0,0 +1,32 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IAudioOutManager : IServiceObject + { + Result ListAudioOuts(out int count, Span names); + Result OpenAudioOut( + out AudioOutputConfiguration outputConfig, + out IAudioOut audioOut, + Span outName, + AudioInputConfiguration inputConfig, + AppletResourceUserId appletResourceId, + int processHandle, + ReadOnlySpan name, + ulong pid); + Result ListAudioOutsAuto(out int count, Span names); + Result OpenAudioOutAuto( + out AudioOutputConfiguration outputConfig, + out IAudioOut audioOut, + Span outName, + AudioInputConfiguration inputConfig, + AppletResourceUserId appletResourceId, + int processHandle, + ReadOnlySpan name, + ulong pid); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs new file mode 100644 index 000000000..e4ca2e8ec --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs @@ -0,0 +1,24 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IAudioRenderer : IServiceObject + { + Result GetSampleRate(out int sampleRate); + Result GetSampleCount(out int sampleCount); + Result GetMixBufferCount(out int mixBufferCount); + Result GetState(out int state); + Result RequestUpdate(Span output, Span performanceOutput, ReadOnlySpan input); + Result Start(); + Result Stop(); + Result QuerySystemEvent(out int eventHandle); + Result SetRenderingTimeLimit(int percent); + Result GetRenderingTimeLimit(out int percent); + Result RequestUpdateAuto(Span output, Span performanceOutput, ReadOnlySpan input); + Result ExecuteAudioRendererRendering(); + Result SetVoiceDropParameter(float voiceDropParameter); + Result GetVoiceDropParameter(out float voiceDropParameter); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRendererManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRendererManager.cs new file mode 100644 index 000000000..fe95a2084 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRendererManager.cs @@ -0,0 +1,29 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IAudioRendererManager : IServiceObject + { + Result OpenAudioRenderer( + out IAudioRenderer renderer, + AudioRendererParameterInternal parameter, + int processHandle, + int workBufferHandle, + ulong workBufferSize, + AppletResourceUserId appletUserId, + ulong pid); + Result GetWorkBufferSize(out long workBufferSize, AudioRendererParameterInternal parameter); + Result GetAudioDeviceService(out IAudioDevice audioDevice, AppletResourceUserId appletUserId); + Result OpenAudioRendererForManualExecution( + out IAudioRenderer renderer, + AudioRendererParameterInternal parameter, + ulong workBufferAddress, + int processHandle, + ulong workBufferSize, + AppletResourceUserId appletUserId, + ulong pid); + Result GetAudioDeviceServiceWithRevisionInfo(out IAudioDevice audioDevice, AppletResourceUserId appletUserId, uint revision); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioSnoopManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioSnoopManager.cs new file mode 100644 index 000000000..72853886a --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioSnoopManager.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IAudioSnoopManager : IServiceObject + { + Result EnableDspUsageMeasurement(); + Result DisableDspUsageMeasurement(); + Result GetDspUsage(out uint usage); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorder.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorder.cs new file mode 100644 index 000000000..be21c38b7 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorder.cs @@ -0,0 +1,22 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IFinalOutputRecorder : IServiceObject + { + Result GetFinalOutputRecorderState(out uint state); + Result Start(); + Result Stop(); + Result AppendFinalOutputRecorderBuffer(ReadOnlySpan buffer, ulong bufferClientPtr); + Result RegisterBufferEvent(out int eventHandle); + Result GetReleasedFinalOutputRecorderBuffers(Span buffer, out uint count, out ulong released); + Result ContainsFinalOutputRecorderBuffer(ulong bufferPointer, out bool contains); + Result GetFinalOutputRecorderBufferEndTime(ulong bufferPointer, out ulong released); + Result AppendFinalOutputRecorderBufferAuto(ReadOnlySpan buffer, ulong bufferClientPtr); + Result GetReleasedFinalOutputRecorderBuffersAuto(Span buffer, out uint count, out ulong released); + Result FlushFinalOutputRecorderBuffers(out bool pending); + Result AttachWorkBuffer(FinalOutputRecorderParameterInternal parameter); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorderManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorderManager.cs new file mode 100644 index 000000000..bac41ca91 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorderManager.cs @@ -0,0 +1,16 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IFinalOutputRecorderManager : IServiceObject + { + Result OpenFinalOutputRecorder( + out IFinalOutputRecorder recorder, + FinalOutputRecorderParameter parameter, + int processHandle, + out FinalOutputRecorderParameterInternal outParameter, + AppletResourceUserId appletResourceId); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/CodecResult.cs b/src/Ryujinx.Horizon/Sdk/Codec/CodecResult.cs new file mode 100644 index 000000000..21508b7f1 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/CodecResult.cs @@ -0,0 +1,16 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Codec +{ + static class CodecResult + { + private const int ModuleId = 111; + + public static Result InvalidLength => new(ModuleId, 3); + public static Result OpusBadArg => new(ModuleId, 130); + public static Result OpusInvalidPacket => new(ModuleId, 133); + public static Result InvalidNumberOfStreams => new(ModuleId, 1000); + public static Result InvalidSampleRate => new(ModuleId, 1001); + public static Result InvalidChannelCount => new(ModuleId, 1002); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs new file mode 100644 index 000000000..5d2798582 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs @@ -0,0 +1,336 @@ +using Concentus; +using Concentus.Enums; +using Concentus.Structs; +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; +using System.Buffers.Binary; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Codec.Detail +{ + partial class HardwareOpusDecoder : IHardwareOpusDecoder, IDisposable + { + [StructLayout(LayoutKind.Sequential)] + private struct OpusPacketHeader + { + public uint Length; + public uint FinalRange; + + public static OpusPacketHeader FromSpan(ReadOnlySpan data) + { + return new() + { + Length = BinaryPrimitives.ReadUInt32BigEndian(data), + FinalRange = BinaryPrimitives.ReadUInt32BigEndian(data[sizeof(uint)..]), + }; + } + } + + private interface IDecoder + { + int SampleRate { get; } + int ChannelsCount { get; } + + int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize); + void ResetState(); + } + + private class Decoder : IDecoder + { + private readonly OpusDecoder _decoder; + + public int SampleRate => _decoder.SampleRate; + public int ChannelsCount => _decoder.NumChannels; + + public Decoder(int sampleRate, int channelsCount) + { + _decoder = new OpusDecoder(sampleRate, channelsCount); + } + + public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize) + { + return _decoder.Decode(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize); + } + + public void ResetState() + { + _decoder.ResetState(); + } + } + + private class MultiSampleDecoder : IDecoder + { + private readonly OpusMSDecoder _decoder; + + public int SampleRate => _decoder.SampleRate; + public int ChannelsCount { get; } + + public MultiSampleDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping) + { + ChannelsCount = channelsCount; + _decoder = new OpusMSDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping); + } + + public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize) + { + return _decoder.DecodeMultistream(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize, 0); + } + + public void ResetState() + { + _decoder.ResetState(); + } + } + + private readonly IDecoder _decoder; + private int _workBufferHandle; + + private HardwareOpusDecoder(int workBufferHandle) + { + _workBufferHandle = workBufferHandle; + } + + public HardwareOpusDecoder(int sampleRate, int channelsCount, int workBufferHandle) : this(workBufferHandle) + { + _decoder = new Decoder(sampleRate, channelsCount); + } + + public HardwareOpusDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping, int workBufferHandle) : this(workBufferHandle) + { + _decoder = new MultiSampleDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping); + } + + [CmifCommand(0)] + public Result DecodeInterleavedOld( + out int outConsumed, + out int outSamples, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span output, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan input) + { + return DecodeInterleavedInternal(out outConsumed, out outSamples, out _, output, input, reset: false, withPerf: false); + } + + [CmifCommand(1)] + public Result SetContext(ReadOnlySpan context) + { + Logger.Stub?.PrintStub(LogClass.ServiceAudio); + + return Result.Success; + } + + [CmifCommand(2)] // 3.0.0+ + public Result DecodeInterleavedForMultiStreamOld( + out int outConsumed, + out int outSamples, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span output, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan input) + { + return DecodeInterleavedInternal(out outConsumed, out outSamples, out _, output, input, reset: false, withPerf: false); + } + + [CmifCommand(3)] // 3.0.0+ + public Result SetContextForMultiStream(ReadOnlySpan arg0) + { + Logger.Stub?.PrintStub(LogClass.ServiceAudio); + + return Result.Success; + } + + [CmifCommand(4)] // 4.0.0+ + public Result DecodeInterleavedWithPerfOld( + out int outConsumed, + out long timeTaken, + out int outSamples, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span output, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan input) + { + return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset: false, withPerf: true); + } + + [CmifCommand(5)] // 4.0.0+ + public Result DecodeInterleavedForMultiStreamWithPerfOld( + out int outConsumed, + out long timeTaken, + out int outSamples, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span output, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan input) + { + return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset: false, withPerf: true); + } + + [CmifCommand(6)] // 6.0.0+ + public Result DecodeInterleavedWithPerfAndResetOld( + out int outConsumed, + out long timeTaken, + out int outSamples, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span output, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan input, + bool reset) + { + return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset, withPerf: true); + } + + [CmifCommand(7)] // 6.0.0+ + public Result DecodeInterleavedForMultiStreamWithPerfAndResetOld( + out int outConsumed, + out long timeTaken, + out int outSamples, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span output, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan input, + bool reset) + { + return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset, withPerf: true); + } + + [CmifCommand(8)] // 7.0.0+ + public Result DecodeInterleaved( + out int outConsumed, + out long timeTaken, + out int outSamples, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span output, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] ReadOnlySpan input, + bool reset) + { + return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset, withPerf: true); + } + + [CmifCommand(9)] // 7.0.0+ + public Result DecodeInterleavedForMultiStream( + out int outConsumed, + out long timeTaken, + out int outSamples, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span output, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] ReadOnlySpan input, + bool reset) + { + return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset, withPerf: true); + } + + private Result DecodeInterleavedInternal( + out int outConsumed, + out int outSamples, + out long timeTaken, + Span output, + ReadOnlySpan input, + bool reset, + bool withPerf) + { + timeTaken = 0; + + Result result = DecodeInterleaved(_decoder, reset, input, out short[] outPcmData, output.Length, out outConsumed, out outSamples); + + if (withPerf) + { + // This is the time the DSP took to process the request, TODO: fill this. + timeTaken = 0; + } + + MemoryMarshal.Cast(outPcmData).CopyTo(output[..(outPcmData.Length * sizeof(short))]); + + return result; + } + + private static Result GetPacketNumSamples(IDecoder decoder, out int numSamples, byte[] packet) + { + int result = OpusPacketInfo.GetNumSamples(packet, 0, packet.Length, decoder.SampleRate); + + numSamples = result; + + if (result == OpusError.OPUS_INVALID_PACKET) + { + return CodecResult.OpusInvalidPacket; + } + else if (result == OpusError.OPUS_BAD_ARG) + { + return CodecResult.OpusBadArg; + } + + return Result.Success; + } + + private static Result DecodeInterleaved( + IDecoder decoder, + bool reset, + ReadOnlySpan input, + out short[] outPcmData, + int outputSize, + out int outConsumed, + out int outSamples) + { + outPcmData = null; + outConsumed = 0; + outSamples = 0; + + int streamSize = input.Length; + + if (streamSize < Unsafe.SizeOf()) + { + return CodecResult.InvalidLength; + } + + OpusPacketHeader header = OpusPacketHeader.FromSpan(input); + int headerSize = Unsafe.SizeOf(); + uint totalSize = header.Length + (uint)headerSize; + + if (totalSize > streamSize) + { + return CodecResult.InvalidLength; + } + + byte[] opusData = input.Slice(headerSize, (int)header.Length).ToArray(); + + Result result = GetPacketNumSamples(decoder, out int numSamples, opusData); + + if (result.IsSuccess) + { + if ((uint)numSamples * (uint)decoder.ChannelsCount * sizeof(short) > outputSize) + { + return CodecResult.InvalidLength; + } + + outPcmData = new short[numSamples * decoder.ChannelsCount]; + + if (reset) + { + decoder.ResetState(); + } + + try + { + outSamples = decoder.Decode(opusData, 0, opusData.Length, outPcmData, 0, outPcmData.Length / decoder.ChannelsCount); + outConsumed = (int)totalSize; + } + catch (OpusException) + { + // TODO: As OpusException doesn't return the exact error code, this is inaccurate in some cases... + return CodecResult.InvalidLength; + } + } + + return Result.Success; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + if (_workBufferHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_workBufferHandle); + + _workBufferHandle = 0; + } + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderManager.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderManager.cs new file mode 100644 index 000000000..acec66e82 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderManager.cs @@ -0,0 +1,386 @@ +using Ryujinx.Common; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Codec.Detail +{ + partial class HardwareOpusDecoderManager : IHardwareOpusDecoderManager + { + [CmifCommand(0)] + public Result OpenHardwareOpusDecoder( + out IHardwareOpusDecoder decoder, + HardwareOpusDecoderParameterInternal parameter, + [CopyHandle] int workBufferHandle, + int workBufferSize) + { + decoder = null; + + if (!IsValidSampleRate(parameter.SampleRate)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidSampleRate; + } + + if (!IsValidChannelCount(parameter.ChannelsCount)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidChannelCount; + } + + decoder = new HardwareOpusDecoder(parameter.SampleRate, parameter.ChannelsCount, workBufferHandle); + + return Result.Success; + } + + [CmifCommand(1)] + public Result GetWorkBufferSize(out int size, HardwareOpusDecoderParameterInternal parameter) + { + size = 0; + + if (!IsValidChannelCount(parameter.ChannelsCount)) + { + return CodecResult.InvalidChannelCount; + } + + if (!IsValidSampleRate(parameter.SampleRate)) + { + return CodecResult.InvalidSampleRate; + } + + int opusDecoderSize = GetOpusDecoderSize(parameter.ChannelsCount); + + int sampleRateRatio = parameter.SampleRate != 0 ? 48000 / parameter.SampleRate : 0; + int frameSize = BitUtils.AlignUp(sampleRateRatio != 0 ? parameter.ChannelsCount * 1920 / sampleRateRatio : 0, 64); + size = opusDecoderSize + 1536 + frameSize; + + return Result.Success; + } + + [CmifCommand(2)] // 3.0.0+ + public Result OpenHardwareOpusDecoderForMultiStream( + out IHardwareOpusDecoder decoder, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x110)] in HardwareOpusMultiStreamDecoderParameterInternal parameter, + [CopyHandle] int workBufferHandle, + int workBufferSize) + { + decoder = null; + + if (!IsValidSampleRate(parameter.SampleRate)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidSampleRate; + } + + if (!IsValidMultiChannelCount(parameter.ChannelsCount)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidChannelCount; + } + + if (!IsValidNumberOfStreams(parameter.NumberOfStreams, parameter.NumberOfStereoStreams, parameter.ChannelsCount)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidNumberOfStreams; + } + + decoder = new HardwareOpusDecoder( + parameter.SampleRate, + parameter.ChannelsCount, + parameter.NumberOfStreams, + parameter.NumberOfStereoStreams, + parameter.ChannelMappings.AsSpan().ToArray(), + workBufferHandle); + + return Result.Success; + } + + [CmifCommand(3)] // 3.0.0+ + public Result GetWorkBufferSizeForMultiStream( + out int size, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x110)] in HardwareOpusMultiStreamDecoderParameterInternal parameter) + { + size = 0; + + if (!IsValidMultiChannelCount(parameter.ChannelsCount)) + { + return CodecResult.InvalidChannelCount; + } + + if (!IsValidSampleRate(parameter.SampleRate)) + { + return CodecResult.InvalidSampleRate; + } + + if (!IsValidNumberOfStreams(parameter.NumberOfStreams, parameter.NumberOfStereoStreams, parameter.ChannelsCount)) + { + return CodecResult.InvalidSampleRate; + } + + int opusDecoderSize = GetOpusMultistreamDecoderSize(parameter.NumberOfStreams, parameter.NumberOfStereoStreams); + + int streamSize = BitUtils.AlignUp(parameter.NumberOfStreams * 1500, 64); + int sampleRateRatio = parameter.SampleRate != 0 ? 48000 / parameter.SampleRate : 0; + int frameSize = BitUtils.AlignUp(sampleRateRatio != 0 ? parameter.ChannelsCount * 1920 / sampleRateRatio : 0, 64); + size = opusDecoderSize + streamSize + frameSize; + + return Result.Success; + } + + [CmifCommand(4)] // 12.0.0+ + public Result OpenHardwareOpusDecoderEx( + out IHardwareOpusDecoder decoder, + HardwareOpusDecoderParameterInternalEx parameter, + [CopyHandle] int workBufferHandle, + int workBufferSize) + { + decoder = null; + + if (!IsValidChannelCount(parameter.ChannelsCount)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidChannelCount; + } + + if (!IsValidSampleRate(parameter.SampleRate)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidSampleRate; + } + + decoder = new HardwareOpusDecoder(parameter.SampleRate, parameter.ChannelsCount, workBufferHandle); + + return Result.Success; + } + + [CmifCommand(5)] // 12.0.0+ + public Result GetWorkBufferSizeEx(out int size, HardwareOpusDecoderParameterInternalEx parameter) + { + return GetWorkBufferSizeExImpl(out size, in parameter, fromDsp: false); + } + + [CmifCommand(6)] // 12.0.0+ + public Result OpenHardwareOpusDecoderForMultiStreamEx( + out IHardwareOpusDecoder decoder, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x118)] in HardwareOpusMultiStreamDecoderParameterInternalEx parameter, + [CopyHandle] int workBufferHandle, + int workBufferSize) + { + decoder = null; + + if (!IsValidSampleRate(parameter.SampleRate)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidSampleRate; + } + + if (!IsValidMultiChannelCount(parameter.ChannelsCount)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidChannelCount; + } + + if (!IsValidNumberOfStreams(parameter.NumberOfStreams, parameter.NumberOfStereoStreams, parameter.ChannelsCount)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidNumberOfStreams; + } + + decoder = new HardwareOpusDecoder( + parameter.SampleRate, + parameter.ChannelsCount, + parameter.NumberOfStreams, + parameter.NumberOfStereoStreams, + parameter.ChannelMappings.AsSpan().ToArray(), + workBufferHandle); + + return Result.Success; + } + + [CmifCommand(7)] // 12.0.0+ + public Result GetWorkBufferSizeForMultiStreamEx( + out int size, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x118)] in HardwareOpusMultiStreamDecoderParameterInternalEx parameter) + { + return GetWorkBufferSizeForMultiStreamExImpl(out size, in parameter, fromDsp: false); + } + + [CmifCommand(8)] // 16.0.0+ + public Result GetWorkBufferSizeExEx(out int size, HardwareOpusDecoderParameterInternalEx parameter) + { + return GetWorkBufferSizeExImpl(out size, in parameter, fromDsp: true); + } + + [CmifCommand(9)] // 16.0.0+ + public Result GetWorkBufferSizeForMultiStreamExEx( + out int size, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x118)] in HardwareOpusMultiStreamDecoderParameterInternalEx parameter) + { + return GetWorkBufferSizeForMultiStreamExImpl(out size, in parameter, fromDsp: true); + } + + private Result GetWorkBufferSizeExImpl(out int size, in HardwareOpusDecoderParameterInternalEx parameter, bool fromDsp) + { + size = 0; + + if (!IsValidChannelCount(parameter.ChannelsCount)) + { + return CodecResult.InvalidChannelCount; + } + + if (!IsValidSampleRate(parameter.SampleRate)) + { + return CodecResult.InvalidSampleRate; + } + + int opusDecoderSize = fromDsp ? GetDspOpusDecoderSize(parameter.ChannelsCount) : GetOpusDecoderSize(parameter.ChannelsCount); + + int frameSizeMono48KHz = parameter.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920; + int sampleRateRatio = parameter.SampleRate != 0 ? 48000 / parameter.SampleRate : 0; + int frameSize = BitUtils.AlignUp(sampleRateRatio != 0 ? parameter.ChannelsCount * frameSizeMono48KHz / sampleRateRatio : 0, 64); + size = opusDecoderSize + 1536 + frameSize; + + return Result.Success; + } + + private Result GetWorkBufferSizeForMultiStreamExImpl(out int size, in HardwareOpusMultiStreamDecoderParameterInternalEx parameter, bool fromDsp) + { + size = 0; + + if (!IsValidMultiChannelCount(parameter.ChannelsCount)) + { + return CodecResult.InvalidChannelCount; + } + + if (!IsValidSampleRate(parameter.SampleRate)) + { + return CodecResult.InvalidSampleRate; + } + + if (!IsValidNumberOfStreams(parameter.NumberOfStreams, parameter.NumberOfStereoStreams, parameter.ChannelsCount)) + { + return CodecResult.InvalidSampleRate; + } + + int opusDecoderSize = fromDsp + ? GetDspOpusMultistreamDecoderSize(parameter.NumberOfStreams, parameter.NumberOfStereoStreams) + : GetOpusMultistreamDecoderSize(parameter.NumberOfStreams, parameter.NumberOfStereoStreams); + + int frameSizeMono48KHz = parameter.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920; + int streamSize = BitUtils.AlignUp(parameter.NumberOfStreams * 1500, 64); + int sampleRateRatio = parameter.SampleRate != 0 ? 48000 / parameter.SampleRate : 0; + int frameSize = BitUtils.AlignUp(sampleRateRatio != 0 ? parameter.ChannelsCount * frameSizeMono48KHz / sampleRateRatio : 0, 64); + size = opusDecoderSize + streamSize + frameSize; + + return Result.Success; + } + + private static int GetDspOpusDecoderSize(int channelsCount) + { + // TODO: Figure out the size returned here. + // Not really important because we don't use the work buffer, and the size being lower is fine. + + return 0; + } + + private static int GetDspOpusMultistreamDecoderSize(int streams, int coupledStreams) + { + // TODO: Figure out the size returned here. + // Not really important because we don't use the work buffer, and the size being lower is fine. + + return 0; + } + + private static int GetOpusDecoderSize(int channelsCount) + { + const int SilkDecoderSize = 0x2160; + + if (channelsCount < 1 || channelsCount > 2) + { + return 0; + } + + int celtDecoderSize = GetCeltDecoderSize(channelsCount); + int opusDecoderSize = GetOpusDecoderAllocSize(channelsCount) | 0x50; + + return opusDecoderSize + SilkDecoderSize + celtDecoderSize; + } + + private static int GetOpusMultistreamDecoderSize(int streams, int coupledStreams) + { + if (streams < 1 || coupledStreams > streams || coupledStreams < 0) + { + return 0; + } + + int coupledSize = GetOpusDecoderSize(2); + int monoSize = GetOpusDecoderSize(1); + + return Align4(monoSize - GetOpusDecoderAllocSize(1)) * (streams - coupledStreams) + + Align4(coupledSize - GetOpusDecoderAllocSize(2)) * coupledStreams + 0xb920; + } + + private static int Align4(int value) + { + return BitUtils.AlignUp(value, 4); + } + + private static int GetOpusDecoderAllocSize(int channelsCount) + { + return channelsCount * 0x800 + 0x4800; + } + + private static int GetCeltDecoderSize(int channelsCount) + { + const int DecodeBufferSize = 0x2030; + const int Overlap = 120; + const int EBandsCount = 21; + + return (DecodeBufferSize + Overlap * 4) * channelsCount + EBandsCount * 16 + 0x54; + } + + private static bool IsValidChannelCount(int channelsCount) + { + return channelsCount > 0 && channelsCount <= 2; + } + + private static bool IsValidMultiChannelCount(int channelsCount) + { + return channelsCount > 0 && channelsCount <= 255; + } + + private static bool IsValidSampleRate(int sampleRate) + { + switch (sampleRate) + { + case 8000: + case 12000: + case 16000: + case 24000: + case 48000: + return true; + } + + return false; + } + + private static bool IsValidNumberOfStreams(int numberOfStreams, int numberOfStereoStreams, int channelsCount) + { + return numberOfStreams > 0 && + numberOfStreams + numberOfStereoStreams <= channelsCount && + numberOfStereoStreams >= 0 && + numberOfStereoStreams <= numberOfStreams; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternal.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternal.cs new file mode 100644 index 000000000..271a592c1 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternal.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Codec.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x4)] + struct HardwareOpusDecoderParameterInternal + { + public int SampleRate; + public int ChannelsCount; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternalEx.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternalEx.cs new file mode 100644 index 000000000..e2b81c771 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternalEx.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Codec.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x4)] + struct HardwareOpusDecoderParameterInternalEx + { + public int SampleRate; + public int ChannelsCount; + public OpusDecoderFlags Flags; + public uint Reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParameters.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternal.cs similarity index 65% rename from src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParameters.cs rename to src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternal.cs index fd63a4f79..98536a4f8 100644 --- a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParameters.cs +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternal.cs @@ -1,15 +1,15 @@ using Ryujinx.Common.Memory; using System.Runtime.InteropServices; -namespace Ryujinx.HLE.HOS.Services.Audio.Types +namespace Ryujinx.Horizon.Sdk.Codec.Detail { [StructLayout(LayoutKind.Sequential, Size = 0x110)] - struct OpusMultiStreamParameters + struct HardwareOpusMultiStreamDecoderParameterInternal { public int SampleRate; public int ChannelsCount; public int NumberOfStreams; public int NumberOfStereoStreams; - public Array64 ChannelMappings; + public Array256 ChannelMappings; } } diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParametersEx.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternalEx.cs similarity index 64% rename from src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParametersEx.cs rename to src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternalEx.cs index 1315c734e..8f8615dff 100644 --- a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParametersEx.cs +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternalEx.cs @@ -1,19 +1,17 @@ using Ryujinx.Common.Memory; using System.Runtime.InteropServices; -namespace Ryujinx.HLE.HOS.Services.Audio.Types +namespace Ryujinx.Horizon.Sdk.Codec.Detail { [StructLayout(LayoutKind.Sequential, Size = 0x118)] - struct OpusMultiStreamParametersEx + struct HardwareOpusMultiStreamDecoderParameterInternalEx { public int SampleRate; public int ChannelsCount; public int NumberOfStreams; public int NumberOfStereoStreams; public OpusDecoderFlags Flags; - - Array4 Padding1; - - public Array64 ChannelMappings; + public uint Reserved; + public Array256 ChannelMappings; } } diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoder.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoder.cs new file mode 100644 index 000000000..ae09ad15a --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoder.cs @@ -0,0 +1,20 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Codec.Detail +{ + interface IHardwareOpusDecoder : IServiceObject + { + Result DecodeInterleavedOld(out int outConsumed, out int outSamples, Span output, ReadOnlySpan input); + Result SetContext(ReadOnlySpan context); + Result DecodeInterleavedForMultiStreamOld(out int outConsumed, out int outSamples, Span output, ReadOnlySpan input); + Result SetContextForMultiStream(ReadOnlySpan context); + Result DecodeInterleavedWithPerfOld(out int outConsumed, out long timeTaken, out int outSamples, Span output, ReadOnlySpan input); + Result DecodeInterleavedForMultiStreamWithPerfOld(out int outConsumed, out long timeTaken, out int outSamples, Span output, ReadOnlySpan input); + Result DecodeInterleavedWithPerfAndResetOld(out int outConsumed, out long timeTaken, out int outSamples, Span output, ReadOnlySpan input, bool reset); + Result DecodeInterleavedForMultiStreamWithPerfAndResetOld(out int outConsumed, out long timeTaken, out int outSamples, Span output, ReadOnlySpan input, bool reset); + Result DecodeInterleaved(out int outConsumed, out long timeTaken, out int outSamples, Span output, ReadOnlySpan input, bool reset); + Result DecodeInterleavedForMultiStream(out int outConsumed, out long timeTaken, out int outSamples, Span output, ReadOnlySpan input, bool reset); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoderManager.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoderManager.cs new file mode 100644 index 000000000..fb6c787b6 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoderManager.cs @@ -0,0 +1,19 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Codec.Detail +{ + interface IHardwareOpusDecoderManager : IServiceObject + { + Result OpenHardwareOpusDecoder(out IHardwareOpusDecoder decoder, HardwareOpusDecoderParameterInternal parameter, int workBufferHandle, int workBufferSize); + Result GetWorkBufferSize(out int size, HardwareOpusDecoderParameterInternal parameter); + Result OpenHardwareOpusDecoderForMultiStream(out IHardwareOpusDecoder decoder, in HardwareOpusMultiStreamDecoderParameterInternal parameter, int workBufferHandle, int workBufferSize); + Result GetWorkBufferSizeForMultiStream(out int size, in HardwareOpusMultiStreamDecoderParameterInternal parameter); + Result OpenHardwareOpusDecoderEx(out IHardwareOpusDecoder decoder, HardwareOpusDecoderParameterInternalEx parameter, int workBufferHandle, int workBufferSize); + Result GetWorkBufferSizeEx(out int size, HardwareOpusDecoderParameterInternalEx parameter); + Result OpenHardwareOpusDecoderForMultiStreamEx(out IHardwareOpusDecoder decoder, in HardwareOpusMultiStreamDecoderParameterInternalEx parameter, int workBufferHandle, int workBufferSize); + Result GetWorkBufferSizeForMultiStreamEx(out int size, in HardwareOpusMultiStreamDecoderParameterInternalEx parameter); + Result GetWorkBufferSizeExEx(out int size, HardwareOpusDecoderParameterInternalEx parameter); + Result GetWorkBufferSizeForMultiStreamExEx(out int size, in HardwareOpusMultiStreamDecoderParameterInternalEx parameter); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusDecoderFlags.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/OpusDecoderFlags.cs similarity index 72% rename from src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusDecoderFlags.cs rename to src/Ryujinx.Horizon/Sdk/Codec/Detail/OpusDecoderFlags.cs index 572535a92..d630b10f4 100644 --- a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusDecoderFlags.cs +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/OpusDecoderFlags.cs @@ -1,6 +1,6 @@ using System; -namespace Ryujinx.HLE.HOS.Services.Audio.Types +namespace Ryujinx.Horizon.Sdk.Codec.Detail { [Flags] enum OpusDecoderFlags : uint diff --git a/src/Ryujinx.Horizon/ServiceTable.cs b/src/Ryujinx.Horizon/ServiceTable.cs index f3fe51940..b81e62a47 100644 --- a/src/Ryujinx.Horizon/ServiceTable.cs +++ b/src/Ryujinx.Horizon/ServiceTable.cs @@ -1,4 +1,5 @@ using Ryujinx.Horizon.Arp; +using Ryujinx.Horizon.Audio; using Ryujinx.Horizon.Bcat; using Ryujinx.Horizon.Friends; using Ryujinx.Horizon.Hshl; @@ -39,9 +40,11 @@ void RegisterService() where T : IService } RegisterService(); + RegisterService(); RegisterService(); RegisterService(); RegisterService(); + RegisterService(); // TODO: Merge with audio once we can start multiple threads. RegisterService(); RegisterService(); RegisterService(); diff --git a/src/Ryujinx.Horizon/Srepo/SrepoIpcServer.cs b/src/Ryujinx.Horizon/Srepo/SrepoIpcServer.cs index 2060782cc..44d008224 100644 --- a/src/Ryujinx.Horizon/Srepo/SrepoIpcServer.cs +++ b/src/Ryujinx.Horizon/Srepo/SrepoIpcServer.cs @@ -41,6 +41,7 @@ public void ServiceRequests() public void Shutdown() { _serverManager.Dispose(); + _sm.Dispose(); } } } diff --git a/src/Ryujinx.Horizon/Usb/UsbIpcServer.cs b/src/Ryujinx.Horizon/Usb/UsbIpcServer.cs index 38eeed496..a04b81f97 100644 --- a/src/Ryujinx.Horizon/Usb/UsbIpcServer.cs +++ b/src/Ryujinx.Horizon/Usb/UsbIpcServer.cs @@ -66,6 +66,7 @@ public void ServiceRequests() public void Shutdown() { _serverManager.Dispose(); + _sm.Dispose(); } } } diff --git a/src/Ryujinx.Horizon/Wlan/WlanIpcServer.cs b/src/Ryujinx.Horizon/Wlan/WlanIpcServer.cs index c7b336231..776b9a7cb 100644 --- a/src/Ryujinx.Horizon/Wlan/WlanIpcServer.cs +++ b/src/Ryujinx.Horizon/Wlan/WlanIpcServer.cs @@ -54,6 +54,7 @@ public void ServiceRequests() public void Shutdown() { _serverManager.Dispose(); + _sm.Dispose(); } } } From 08384ee5a8d7466bf83fbff6d7c9d1076e838dba Mon Sep 17 00:00:00 2001 From: jhorv <38920027+jhorv@users.noreply.github.com> Date: Fri, 23 Feb 2024 19:12:24 -0500 Subject: [PATCH 089/126] IPC code gen improvements (#6352) * HipcGenerator: skip do-nothing call to MemoryMarshal.Cast() in generated code * HipcGenerator: fix method name typos * HipcGenerator: make generated methods use stackalloc for `isBufferMapAlias` bool array * HipcGenerator: make generated GetCommandHandlers() method return a FrozenDictionary * HipcGenerator: return `FrozenDictionary<,>.Empty` when there are no command implementations, otherwise create `FrozenDictionary` from a `IEnumerable>`` instead of a `Dictionary<,>`` --- .../Hipc/HipcGenerator.cs | 92 +++++++++++-------- .../Sdk/Sf/HipcCommandProcessor.cs | 4 +- 2 files changed, 54 insertions(+), 42 deletions(-) diff --git a/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs b/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs index 19667290f..4c8d441b6 100644 --- a/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs +++ b/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs @@ -74,6 +74,7 @@ public void Execute(GeneratorExecutionContext context) generator.AppendLine("using Ryujinx.Horizon.Sdk.Sf.Cmif;"); generator.AppendLine("using Ryujinx.Horizon.Sdk.Sf.Hipc;"); generator.AppendLine("using System;"); + generator.AppendLine("using System.Collections.Frozen;"); generator.AppendLine("using System.Collections.Generic;"); generator.AppendLine("using System.Runtime.CompilerServices;"); generator.AppendLine("using System.Runtime.InteropServices;"); @@ -115,67 +116,76 @@ private static string GetNamespaceName(SyntaxNode syntaxNode) private static void GenerateMethodTable(CodeGenerator generator, Compilation compilation, CommandInterface commandInterface) { generator.EnterScope($"public IReadOnlyDictionary GetCommandHandlers()"); - generator.EnterScope($"return new Dictionary()"); - foreach (var method in commandInterface.CommandImplementations) + if (commandInterface.CommandImplementations.Count == 0) { - foreach (var commandId in GetAttributeAguments(compilation, method, TypeCommandAttribute, 0)) - { - string[] args = new string[method.ParameterList.Parameters.Count]; + generator.AppendLine("return FrozenDictionary.Empty;"); + } + else + { + generator.EnterScope($"return FrozenDictionary.ToFrozenDictionary(new []"); - if (args.Length == 0) - { - generator.AppendLine($"{{ {commandId}, new CommandHandler({method.Identifier.Text}, Array.Empty()) }},"); - } - else + foreach (var method in commandInterface.CommandImplementations) + { + foreach (var commandId in GetAttributeArguments(compilation, method, TypeCommandAttribute, 0)) { - int index = 0; + string[] args = new string[method.ParameterList.Parameters.Count]; - foreach (var parameter in method.ParameterList.Parameters) + if (args.Length == 0) { - string canonicalTypeName = GetCanonicalTypeNameWithGenericArguments(compilation, parameter.Type); - CommandArgType argType = GetCommandArgType(compilation, parameter); - - string arg; + generator.AppendLine($"KeyValuePair.Create({commandId}, new CommandHandler({method.Identifier.Text}, Array.Empty())),"); + } + else + { + int index = 0; - if (argType == CommandArgType.Buffer) + foreach (var parameter in method.ParameterList.Parameters) { - string bufferFlags = GetFirstAttributeAgument(compilation, parameter, TypeBufferAttribute, 0); - string bufferFixedSize = GetFirstAttributeAgument(compilation, parameter, TypeBufferAttribute, 1); + string canonicalTypeName = GetCanonicalTypeNameWithGenericArguments(compilation, parameter.Type); + CommandArgType argType = GetCommandArgType(compilation, parameter); - if (bufferFixedSize != null) + string arg; + + if (argType == CommandArgType.Buffer) + { + string bufferFlags = GetFirstAttributeArgument(compilation, parameter, TypeBufferAttribute, 0); + string bufferFixedSize = GetFirstAttributeArgument(compilation, parameter, TypeBufferAttribute, 1); + + if (bufferFixedSize != null) + { + arg = $"new CommandArg({bufferFlags} | HipcBufferFlags.FixedSize, {bufferFixedSize})"; + } + else + { + arg = $"new CommandArg({bufferFlags})"; + } + } + else if (argType == CommandArgType.InArgument || argType == CommandArgType.OutArgument) { - arg = $"new CommandArg({bufferFlags} | HipcBufferFlags.FixedSize, {bufferFixedSize})"; + string alignment = GetTypeAlignmentExpression(compilation, parameter.Type); + + arg = $"new CommandArg(CommandArgType.{argType}, Unsafe.SizeOf<{canonicalTypeName}>(), {alignment})"; } else { - arg = $"new CommandArg({bufferFlags})"; + arg = $"new CommandArg(CommandArgType.{argType})"; } - } - else if (argType == CommandArgType.InArgument || argType == CommandArgType.OutArgument) - { - string alignment = GetTypeAlignmentExpression(compilation, parameter.Type); - arg = $"new CommandArg(CommandArgType.{argType}, Unsafe.SizeOf<{canonicalTypeName}>(), {alignment})"; - } - else - { - arg = $"new CommandArg(CommandArgType.{argType})"; + args[index++] = arg; } - args[index++] = arg; + generator.AppendLine($"KeyValuePair.Create({commandId}, new CommandHandler({method.Identifier.Text}, {string.Join(", ", args)})),"); } - - generator.AppendLine($"{{ {commandId}, new CommandHandler({method.Identifier.Text}, {string.Join(", ", args)}) }},"); } } + + generator.LeaveScope(");"); } - generator.LeaveScope(";"); generator.LeaveScope(); } - private static IEnumerable GetAttributeAguments(Compilation compilation, SyntaxNode syntaxNode, string attributeName, int argIndex) + private static IEnumerable GetAttributeArguments(Compilation compilation, SyntaxNode syntaxNode, string attributeName, int argIndex) { ISymbol symbol = compilation.GetSemanticModel(syntaxNode.SyntaxTree).GetDeclaredSymbol(syntaxNode); @@ -188,9 +198,9 @@ private static IEnumerable GetAttributeAguments(Compilation compilation, } } - private static string GetFirstAttributeAgument(Compilation compilation, SyntaxNode syntaxNode, string attributeName, int argIndex) + private static string GetFirstAttributeArgument(Compilation compilation, SyntaxNode syntaxNode, string attributeName, int argIndex) { - return GetAttributeAguments(compilation, syntaxNode, attributeName, argIndex).FirstOrDefault(); + return GetAttributeArguments(compilation, syntaxNode, attributeName, argIndex).FirstOrDefault(); } private static void GenerateMethod(CodeGenerator generator, Compilation compilation, MethodDeclarationSyntax method) @@ -233,7 +243,7 @@ private static void GenerateMethod(CodeGenerator generator, Compilation compilat if (buffersCount != 0) { - generator.AppendLine($"bool[] {IsBufferMapAliasVariableName} = new bool[{method.ParameterList.Parameters.Count}];"); + generator.AppendLine($"Span {IsBufferMapAliasVariableName} = stackalloc bool[{method.ParameterList.Parameters.Count}];"); generator.AppendLine(); generator.AppendLine($"{ResultVariableName} = processor.ProcessBuffers(ref context, {IsBufferMapAliasVariableName}, runtimeMetadata);"); @@ -719,7 +729,9 @@ private static string GenerateSpanCastElement0(string targetType, string input) private static string GenerateSpanCast(string targetType, string input) { - return $"MemoryMarshal.Cast({input})"; + return targetType == "byte" + ? input + : $"MemoryMarshal.Cast({input})"; } private static bool HasAttribute(Compilation compilation, ParameterSyntax parameterSyntax, string fullAttributeName) diff --git a/src/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs b/src/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs index f7694a74d..dc34f791a 100644 --- a/src/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs +++ b/src/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs @@ -127,7 +127,7 @@ public PointerAndSize GetBufferRange(int argIndex) return _bufferRanges[argIndex]; } - public Result ProcessBuffers(ref ServiceDispatchContext context, bool[] isBufferMapAlias, ServerMessageRuntimeMetadata runtimeMetadata) + public Result ProcessBuffers(ref ServiceDispatchContext context, scoped Span isBufferMapAlias, ServerMessageRuntimeMetadata runtimeMetadata) { bool mapAliasBuffersValid = true; @@ -246,7 +246,7 @@ private static bool IsMapTransferModeValid(HipcBufferFlags flags, HipcBufferMode return mode == HipcBufferMode.Normal; } - public void SetOutBuffers(HipcMessageData response, bool[] isBufferMapAlias) + public void SetOutBuffers(HipcMessageData response, ReadOnlySpan isBufferMapAlias) { int recvPointerIndex = 0; From 4d0dbbfae2d8d227d37271a945fef4bcbeb8300a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 24 Feb 2024 01:17:24 +0100 Subject: [PATCH 090/126] nuget: bump System.Drawing.Common from 8.0.1 to 8.0.2 (#6308) Bumps [System.Drawing.Common](https://github.com/dotnet/winforms) from 8.0.1 to 8.0.2. - [Release notes](https://github.com/dotnet/winforms/releases) - [Changelog](https://github.com/dotnet/winforms/blob/main/docs/release-activity.md) - [Commits](https://github.com/dotnet/winforms/compare/v8.0.1...v8.0.2) --- updated-dependencies: - dependency-name: System.Drawing.Common dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 3fa3bfe2c..412b33a6e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -45,7 +45,7 @@ - + From 87f238be60fac8cf88beef9d859a11c661488d4b Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sat, 24 Feb 2024 19:02:20 -0300 Subject: [PATCH 091/126] Change packed aliasing formats to UInt (#6358) --- src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs b/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs index e30b29ef1..da9e5c3a9 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs @@ -674,9 +674,9 @@ public static bool TryGetTextureFormat(uint encoded, bool isSrgb, out FormatInfo { 1 => new FormatInfo(Format.R8Unorm, 1, 1, 1, 1), 2 => new FormatInfo(Format.R16Unorm, 1, 1, 2, 1), - 4 => new FormatInfo(Format.R32Float, 1, 1, 4, 1), - 8 => new FormatInfo(Format.R32G32Float, 1, 1, 8, 2), - 16 => new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16, 4), + 4 => new FormatInfo(Format.R32Uint, 1, 1, 4, 1), + 8 => new FormatInfo(Format.R32G32Uint, 1, 1, 8, 2), + 16 => new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16, 4), _ => format, }; } From 53b5985da6b9d7b281d9fc25b93bfd1d1918a107 Mon Sep 17 00:00:00 2001 From: Mary Guillemard Date: Sat, 2 Mar 2024 11:16:46 +0100 Subject: [PATCH 092/126] Avalonia: only enable gamescope workaround under it (#6374) Signed-off-by: Mary Guillemard --- src/Ryujinx.Ava/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ryujinx.Ava/Program.cs b/src/Ryujinx.Ava/Program.cs index 702240ba3..aecc585fc 100644 --- a/src/Ryujinx.Ava/Program.cs +++ b/src/Ryujinx.Ava/Program.cs @@ -59,7 +59,7 @@ public static AppBuilder BuildAvaloniaApp() { EnableMultiTouch = true, EnableIme = true, - EnableInputFocusProxy = true, + EnableInputFocusProxy = Environment.GetEnvironmentVariable("XDG_CURRENT_DESKTOP") == "gamescope", RenderingMode = new[] { X11RenderingMode.Glx, X11RenderingMode.Software }, }) .With(new Win32PlatformOptions From ec6cb0abb4b7669895b6e96fd7581c93b5abd691 Mon Sep 17 00:00:00 2001 From: Mary Guillemard Date: Sat, 2 Mar 2024 12:51:05 +0100 Subject: [PATCH 093/126] infra: Make Avalonia the default UI (#6375) * misc: Move Ryujinx project to Ryujinx.Gtk3 This breaks release CI for now but that's fine. Signed-off-by: Mary Guillemard * misc: Move Ryujinx.Ava project to Ryujinx This breaks CI for now, but it's fine. Signed-off-by: Mary Guillemard * infra: Make Avalonia the default UI Should fix CI after the previous changes. GTK3 isn't build by the release job anymore, only by PR CI. This also ensure that the test-ava update package is still generated to allow update from the old testing channel. Signed-off-by: Mary Guillemard * Fix missing copy in create_app_bundle.sh Signed-off-by: Mary Guillemard * Fix syntax error Signed-off-by: Mary Guillemard --------- Signed-off-by: Mary Guillemard --- .editorconfig | 8 +- .github/labeler.yml | 2 +- .github/workflows/build.yml | 22 +- .github/workflows/nightly_pr_comment.yml | 10 +- .github/workflows/release.yml | 13 +- Ryujinx.sln | 4 +- distribution/linux/Ryujinx.sh | 4 - distribution/macos/create_app_bundle.sh | 4 +- distribution/macos/create_macos_build_ava.sh | 17 +- src/Ryujinx.Ava/Program.cs | 237 --------- src/Ryujinx.Ava/Ryujinx.Ava.csproj | 167 ------ .../Input/GTK3/GTK3Keyboard.cs | 0 .../Input/GTK3/GTK3KeyboardDriver.cs | 0 .../Input/GTK3/GTK3MappingHelper.cs | 0 .../Input/GTK3/GTK3Mouse.cs | 0 .../Input/GTK3/GTK3MouseDriver.cs | 0 .../Modules/Updater/UpdateDialog.cs | 4 +- .../Modules/Updater/UpdateDialog.glade | 0 .../Modules/Updater/Updater.cs | 482 ++++++------------ src/Ryujinx.Gtk3/Program.cs | 378 ++++++++++++++ src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj | 104 ++++ src/{Ryujinx.Ava => Ryujinx.Gtk3}/Ryujinx.ico | Bin .../UI/Applet/ErrorAppletDialog.cs | 2 +- .../UI/Applet/GtkDynamicTextInputHandler.cs | 0 .../UI/Applet/GtkHostUIHandler.cs | 0 .../UI/Applet/GtkHostUITheme.cs | 0 .../UI/Applet/SwkbdAppletDialog.cs | 0 .../UI/Helper/MetalHelper.cs | 0 .../UI/Helper/SortHelper.cs | 0 .../UI/Helper/ThemeHelper.cs | 0 .../UI/MainWindow.cs | 2 +- .../UI/MainWindow.glade | 0 .../UI/OpenGLRenderer.cs | 0 .../UI/OpenToolkitBindingsContext.cs | 0 .../UI/RendererWidgetBase.cs | 0 .../UI/SPBOpenGLContext.cs | 0 .../UI/StatusUpdatedEventArgs.cs | 0 .../UI/VulkanRenderer.cs | 0 .../Widgets/GameTableContextMenu.Designer.cs | 0 .../UI/Widgets/GameTableContextMenu.cs | 2 +- .../UI/Widgets/GtkDialog.cs | 0 .../UI/Widgets/GtkInputDialog.cs | 0 .../UI/Widgets/ProfileDialog.cs | 2 +- .../UI/Widgets/ProfileDialog.glade | 0 .../UI/Widgets/RawInputToTextEntry.cs | 0 .../UI/Widgets/UserErrorDialog.cs | 0 .../UI/Windows/AboutWindow.Designer.cs | 0 .../UI/Windows/AboutWindow.cs | 0 .../UI/Windows/AmiiboWindow.Designer.cs | 0 .../UI/Windows/AmiiboWindow.cs | 0 .../UI/Windows/AvatarWindow.cs | 0 .../UI/Windows/CheatWindow.cs | 2 +- .../UI/Windows/CheatWindow.glade | 0 .../UI/Windows/ControllerWindow.cs | 2 +- .../UI/Windows/ControllerWindow.glade | 0 .../UI/Windows/DlcWindow.cs | 2 +- .../UI/Windows/DlcWindow.glade | 0 .../UI/Windows/SettingsWindow.cs | 2 +- .../UI/Windows/SettingsWindow.glade | 0 .../UI/Windows/TitleUpdateWindow.cs | 2 +- .../UI/Windows/TitleUpdateWindow.glade | 0 .../UserProfilesManagerWindow.Designer.cs | 0 .../UI/Windows/UserProfilesManagerWindow.cs | 0 src/{Ryujinx.Ava => Ryujinx}/App.axaml | 0 src/{Ryujinx.Ava => Ryujinx}/App.axaml.cs | 0 src/{Ryujinx.Ava => Ryujinx}/AppHost.cs | 0 .../Assets/Fonts/SegoeFluentIcons.ttf | Bin .../Assets/Icons/Controller_JoyConLeft.svg | 0 .../Assets/Icons/Controller_JoyConPair.svg | 0 .../Assets/Icons/Controller_JoyConRight.svg | 0 .../Assets/Icons/Controller_ProCon.svg | 0 .../Assets/Locales/de_DE.json | 0 .../Assets/Locales/el_GR.json | 0 .../Assets/Locales/en_US.json | 0 .../Assets/Locales/es_ES.json | 0 .../Assets/Locales/fr_FR.json | 0 .../Assets/Locales/he_IL.json | 0 .../Assets/Locales/it_IT.json | 0 .../Assets/Locales/ja_JP.json | 0 .../Assets/Locales/ko_KR.json | 0 .../Assets/Locales/pl_PL.json | 0 .../Assets/Locales/pt_BR.json | 0 .../Assets/Locales/ru_RU.json | 0 .../Assets/Locales/tr_TR.json | 0 .../Assets/Locales/uk_UA.json | 0 .../Assets/Locales/zh_CN.json | 0 .../Assets/Locales/zh_TW.json | 0 .../Assets/Styles/Styles.xaml | 0 .../Assets/Styles/Themes.xaml | 0 .../Common/ApplicationHelper.cs | 0 .../Common/ApplicationSort.cs | 0 .../Common/KeyboardHotkeyState.cs | 0 .../Common/Locale/LocaleExtension.cs | 0 .../Common/Locale/LocaleManager.cs | 2 +- .../Input/AvaloniaKeyboard.cs | 0 .../Input/AvaloniaKeyboardDriver.cs | 0 .../Input/AvaloniaKeyboardMappingHelper.cs | 0 .../Input/AvaloniaMouse.cs | 0 .../Input/AvaloniaMouseDriver.cs | 0 src/Ryujinx/Modules/Updater/Updater.cs | 480 +++++++++++------ src/Ryujinx/Program.cs | 309 +++-------- src/Ryujinx/Ryujinx.csproj | 130 +++-- .../UI/Applet/AvaHostUIHandler.cs | 0 .../Applet/AvaloniaDynamicTextInputHandler.cs | 0 .../UI/Applet/AvaloniaHostUITheme.cs | 0 .../UI/Applet/ControllerAppletDialog.axaml | 0 .../UI/Applet/ControllerAppletDialog.axaml.cs | 8 +- .../UI/Applet/ErrorAppletWindow.axaml | 0 .../UI/Applet/ErrorAppletWindow.axaml.cs | 0 .../UI/Applet/SwkbdAppletDialog.axaml | 0 .../UI/Applet/SwkbdAppletDialog.axaml.cs | 0 .../UI/Controls/ApplicationContextMenu.axaml | 0 .../Controls/ApplicationContextMenu.axaml.cs | 0 .../UI/Controls/ApplicationGridView.axaml | 0 .../UI/Controls/ApplicationGridView.axaml.cs | 0 .../UI/Controls/ApplicationListView.axaml | 0 .../UI/Controls/ApplicationListView.axaml.cs | 0 .../UI/Controls/NavigationDialogHost.axaml | 0 .../UI/Controls/NavigationDialogHost.axaml.cs | 0 .../UI/Controls/SliderScroll.axaml.cs | 0 .../UI/Controls/UpdateWaitWindow.axaml | 0 .../UI/Controls/UpdateWaitWindow.axaml.cs | 0 .../UI/Helpers/ApplicationOpenedEventArgs.cs | 0 .../UI/Helpers/BitmapArrayValueConverter.cs | 0 .../UI/Helpers/ButtonKeyAssigner.cs | 0 .../UI/Helpers/ContentDialogHelper.cs | 0 .../UI/Helpers/Glyph.cs | 0 .../UI/Helpers/GlyphValueConverter.cs | 0 .../UI/Helpers/KeyValueConverter.cs | 0 .../UI/Helpers/LocalizedNeverConverter.cs | 0 .../UI/Helpers/LoggerAdapter.cs | 0 .../UI/Helpers/MiniCommand.cs | 0 .../UI/Helpers/NotificationHelper.cs | 0 .../UI/Helpers/OffscreenTextBox.cs | 0 .../UI/Helpers/TimeZoneConverter.cs | 0 .../UI/Helpers/UserErrorDialog.cs | 0 .../UI/Helpers/UserResult.cs | 0 .../UI/Helpers/Win32NativeInterop.cs | 0 .../UI/Models/CheatNode.cs | 0 .../UI/Models/ControllerModel.cs | 0 .../UI/Models/DeviceType.cs | 0 .../UI/Models/DownloadableContentModel.cs | 0 .../Models/Generic/LastPlayedSortComparer.cs | 0 .../Models/Generic/TimePlayedSortComparer.cs | 0 .../UI/Models/InputConfiguration.cs | 0 .../UI/Models/ModModel.cs | 0 .../UI/Models/PlayerModel.cs | 0 .../UI/Models/ProfileImageModel.cs | 0 .../UI/Models/SaveModel.cs | 0 .../UI/Models/StatusUpdatedEventArgs.cs | 0 .../UI/Models/TempProfile.cs | 0 .../UI/Models/TimeZone.cs | 0 .../UI/Models/TitleUpdateModel.cs | 0 .../UI/Models/UserProfile.cs | 0 .../UI/Renderer/EmbeddedWindow.cs | 0 .../UI/Renderer/EmbeddedWindowOpenGL.cs | 0 .../UI/Renderer/EmbeddedWindowVulkan.cs | 0 .../UI/Renderer/OpenTKBindingsContext.cs | 0 .../UI/Renderer/RendererHost.axaml | 0 .../UI/Renderer/RendererHost.axaml.cs | 0 .../UI/Renderer/SPBOpenGLContext.cs | 0 .../UI/ViewModels/AboutWindowViewModel.cs | 0 .../UI/ViewModels/AmiiboWindowViewModel.cs | 0 .../UI/ViewModels/BaseModel.cs | 0 .../UI/ViewModels/ControllerInputViewModel.cs | 0 .../DownloadableContentManagerViewModel.cs | 0 .../UI/ViewModels/MainWindowViewModel.cs | 0 .../UI/ViewModels/ModManagerViewModel.cs | 0 .../UI/ViewModels/MotionInputViewModel.cs | 0 .../UI/ViewModels/RumbleInputViewModel.cs | 0 .../UI/ViewModels/SettingsViewModel.cs | 0 .../UI/ViewModels/TitleUpdateViewModel.cs | 0 .../UserFirmwareAvatarSelectorViewModel.cs | 0 .../UserProfileImageSelectorViewModel.cs | 0 .../UI/ViewModels/UserProfileViewModel.cs | 0 .../UI/ViewModels/UserSaveManagerViewModel.cs | 0 .../UI/Views/Input/ControllerInputView.axaml | 0 .../Views/Input/ControllerInputView.axaml.cs | 0 .../UI/Views/Input/MotionInputView.axaml | 0 .../UI/Views/Input/MotionInputView.axaml.cs | 0 .../UI/Views/Input/RumbleInputView.axaml | 0 .../UI/Views/Input/RumbleInputView.axaml.cs | 0 .../UI/Views/Main/MainMenuBarView.axaml | 0 .../UI/Views/Main/MainMenuBarView.axaml.cs | 2 +- .../UI/Views/Main/MainStatusBarView.axaml | 0 .../UI/Views/Main/MainStatusBarView.axaml.cs | 0 .../UI/Views/Main/MainViewControls.axaml | 0 .../UI/Views/Main/MainViewControls.axaml.cs | 0 .../UI/Views/Settings/SettingsAudioView.axaml | 0 .../Views/Settings/SettingsAudioView.axaml.cs | 0 .../UI/Views/Settings/SettingsCPUView.axaml | 0 .../Views/Settings/SettingsCPUView.axaml.cs | 0 .../Views/Settings/SettingsGraphicsView.axaml | 0 .../Settings/SettingsGraphicsView.axaml.cs | 0 .../Views/Settings/SettingsHotkeysView.axaml | 0 .../Settings/SettingsHotkeysView.axaml.cs | 0 .../UI/Views/Settings/SettingsInputView.axaml | 0 .../Views/Settings/SettingsInputView.axaml.cs | 0 .../Views/Settings/SettingsLoggingView.axaml | 0 .../Settings/SettingsLoggingView.axaml.cs | 0 .../Views/Settings/SettingsNetworkView.axaml | 0 .../Settings/SettingsNetworkView.axaml.cs | 0 .../Views/Settings/SettingsSystemView.axaml | 0 .../Settings/SettingsSystemView.axaml.cs | 0 .../UI/Views/Settings/SettingsUIView.axaml | 0 .../UI/Views/Settings/SettingsUIView.axaml.cs | 0 .../UI/Views/User/UserEditorView.axaml | 0 .../UI/Views/User/UserEditorView.axaml.cs | 0 .../User/UserFirmwareAvatarSelectorView.axaml | 0 .../UserFirmwareAvatarSelectorView.axaml.cs | 0 .../User/UserProfileImageSelectorView.axaml | 0 .../UserProfileImageSelectorView.axaml.cs | 0 .../UI/Views/User/UserRecovererView.axaml | 0 .../UI/Views/User/UserRecovererView.axaml.cs | 0 .../UI/Views/User/UserSaveManagerView.axaml | 0 .../Views/User/UserSaveManagerView.axaml.cs | 0 .../UI/Views/User/UserSelectorView.axaml | 0 .../UI/Views/User/UserSelectorView.axaml.cs | 0 .../UI/Windows/AboutWindow.axaml | 0 .../UI/Windows/AboutWindow.axaml.cs | 0 .../UI/Windows/AmiiboWindow.axaml | 0 .../UI/Windows/AmiiboWindow.axaml.cs | 0 .../UI/Windows/CheatWindow.axaml | 0 .../UI/Windows/CheatWindow.axaml.cs | 0 .../Windows/ContentDialogOverlayWindow.axaml | 0 .../ContentDialogOverlayWindow.axaml.cs | 0 .../DownloadableContentManagerWindow.axaml | 0 .../DownloadableContentManagerWindow.axaml.cs | 0 .../UI/Windows/IconColorPicker.cs | 0 .../UI/Windows/MainWindow.axaml | 0 .../UI/Windows/MainWindow.axaml.cs | 0 .../UI/Windows/ModManagerWindow.axaml | 0 .../UI/Windows/ModManagerWindow.axaml.cs | 0 .../UI/Windows/SettingsWindow.axaml | 2 +- .../UI/Windows/SettingsWindow.axaml.cs | 0 .../UI/Windows/StyleableWindow.cs | 0 .../UI/Windows/TitleUpdateWindow.axaml | 0 .../UI/Windows/TitleUpdateWindow.axaml.cs | 0 src/{Ryujinx.Ava => Ryujinx}/app.manifest | 0 239 files changed, 1205 insertions(+), 1202 deletions(-) delete mode 100644 src/Ryujinx.Ava/Program.cs delete mode 100644 src/Ryujinx.Ava/Ryujinx.Ava.csproj rename src/{Ryujinx => Ryujinx.Gtk3}/Input/GTK3/GTK3Keyboard.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/Input/GTK3/GTK3KeyboardDriver.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/Input/GTK3/GTK3MappingHelper.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/Input/GTK3/GTK3Mouse.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/Input/GTK3/GTK3MouseDriver.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/Modules/Updater/UpdateDialog.cs (93%) rename src/{Ryujinx => Ryujinx.Gtk3}/Modules/Updater/UpdateDialog.glade (100%) rename src/{Ryujinx.Ava => Ryujinx.Gtk3}/Modules/Updater/Updater.cs (50%) create mode 100644 src/Ryujinx.Gtk3/Program.cs create mode 100644 src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj rename src/{Ryujinx.Ava => Ryujinx.Gtk3}/Ryujinx.ico (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Applet/ErrorAppletDialog.cs (91%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Applet/GtkDynamicTextInputHandler.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Applet/GtkHostUIHandler.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Applet/GtkHostUITheme.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Applet/SwkbdAppletDialog.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Helper/MetalHelper.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Helper/SortHelper.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Helper/ThemeHelper.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/MainWindow.cs (99%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/MainWindow.glade (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/OpenGLRenderer.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/OpenToolkitBindingsContext.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/RendererWidgetBase.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/SPBOpenGLContext.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/StatusUpdatedEventArgs.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/VulkanRenderer.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Widgets/GameTableContextMenu.Designer.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Widgets/GameTableContextMenu.cs (99%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Widgets/GtkDialog.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Widgets/GtkInputDialog.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Widgets/ProfileDialog.cs (94%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Widgets/ProfileDialog.glade (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Widgets/RawInputToTextEntry.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Widgets/UserErrorDialog.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Windows/AboutWindow.Designer.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Windows/AboutWindow.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Windows/AmiiboWindow.Designer.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Windows/AmiiboWindow.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Windows/AvatarWindow.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Windows/CheatWindow.cs (98%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Windows/CheatWindow.glade (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Windows/ControllerWindow.cs (99%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Windows/ControllerWindow.glade (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Windows/DlcWindow.cs (98%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Windows/DlcWindow.glade (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Windows/SettingsWindow.cs (99%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Windows/SettingsWindow.glade (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Windows/TitleUpdateWindow.cs (98%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Windows/TitleUpdateWindow.glade (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Windows/UserProfilesManagerWindow.Designer.cs (100%) rename src/{Ryujinx => Ryujinx.Gtk3}/UI/Windows/UserProfilesManagerWindow.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/App.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/App.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/AppHost.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/Assets/Fonts/SegoeFluentIcons.ttf (100%) rename src/{Ryujinx.Ava => Ryujinx}/Assets/Icons/Controller_JoyConLeft.svg (100%) rename src/{Ryujinx.Ava => Ryujinx}/Assets/Icons/Controller_JoyConPair.svg (100%) rename src/{Ryujinx.Ava => Ryujinx}/Assets/Icons/Controller_JoyConRight.svg (100%) rename src/{Ryujinx.Ava => Ryujinx}/Assets/Icons/Controller_ProCon.svg (100%) rename src/{Ryujinx.Ava => Ryujinx}/Assets/Locales/de_DE.json (100%) rename src/{Ryujinx.Ava => Ryujinx}/Assets/Locales/el_GR.json (100%) rename src/{Ryujinx.Ava => Ryujinx}/Assets/Locales/en_US.json (100%) rename src/{Ryujinx.Ava => Ryujinx}/Assets/Locales/es_ES.json (100%) rename src/{Ryujinx.Ava => Ryujinx}/Assets/Locales/fr_FR.json (100%) rename src/{Ryujinx.Ava => Ryujinx}/Assets/Locales/he_IL.json (100%) rename src/{Ryujinx.Ava => Ryujinx}/Assets/Locales/it_IT.json (100%) rename src/{Ryujinx.Ava => Ryujinx}/Assets/Locales/ja_JP.json (100%) rename src/{Ryujinx.Ava => Ryujinx}/Assets/Locales/ko_KR.json (100%) rename src/{Ryujinx.Ava => Ryujinx}/Assets/Locales/pl_PL.json (100%) rename src/{Ryujinx.Ava => Ryujinx}/Assets/Locales/pt_BR.json (100%) rename src/{Ryujinx.Ava => Ryujinx}/Assets/Locales/ru_RU.json (100%) rename src/{Ryujinx.Ava => Ryujinx}/Assets/Locales/tr_TR.json (100%) rename src/{Ryujinx.Ava => Ryujinx}/Assets/Locales/uk_UA.json (100%) rename src/{Ryujinx.Ava => Ryujinx}/Assets/Locales/zh_CN.json (100%) rename src/{Ryujinx.Ava => Ryujinx}/Assets/Locales/zh_TW.json (100%) rename src/{Ryujinx.Ava => Ryujinx}/Assets/Styles/Styles.xaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/Assets/Styles/Themes.xaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/Common/ApplicationHelper.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/Common/ApplicationSort.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/Common/KeyboardHotkeyState.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/Common/Locale/LocaleExtension.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/Common/Locale/LocaleManager.cs (99%) rename src/{Ryujinx.Ava => Ryujinx}/Input/AvaloniaKeyboard.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/Input/AvaloniaKeyboardDriver.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/Input/AvaloniaKeyboardMappingHelper.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/Input/AvaloniaMouse.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/Input/AvaloniaMouseDriver.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Applet/AvaHostUIHandler.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Applet/AvaloniaDynamicTextInputHandler.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Applet/AvaloniaHostUITheme.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Applet/ControllerAppletDialog.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Applet/ControllerAppletDialog.axaml.cs (91%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Applet/ErrorAppletWindow.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Applet/ErrorAppletWindow.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Applet/SwkbdAppletDialog.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Applet/SwkbdAppletDialog.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Controls/ApplicationContextMenu.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Controls/ApplicationContextMenu.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Controls/ApplicationGridView.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Controls/ApplicationGridView.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Controls/ApplicationListView.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Controls/ApplicationListView.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Controls/NavigationDialogHost.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Controls/NavigationDialogHost.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Controls/SliderScroll.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Controls/UpdateWaitWindow.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Controls/UpdateWaitWindow.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Helpers/ApplicationOpenedEventArgs.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Helpers/BitmapArrayValueConverter.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Helpers/ButtonKeyAssigner.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Helpers/ContentDialogHelper.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Helpers/Glyph.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Helpers/GlyphValueConverter.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Helpers/KeyValueConverter.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Helpers/LocalizedNeverConverter.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Helpers/LoggerAdapter.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Helpers/MiniCommand.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Helpers/NotificationHelper.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Helpers/OffscreenTextBox.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Helpers/TimeZoneConverter.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Helpers/UserErrorDialog.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Helpers/UserResult.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Helpers/Win32NativeInterop.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Models/CheatNode.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Models/ControllerModel.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Models/DeviceType.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Models/DownloadableContentModel.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Models/Generic/LastPlayedSortComparer.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Models/Generic/TimePlayedSortComparer.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Models/InputConfiguration.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Models/ModModel.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Models/PlayerModel.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Models/ProfileImageModel.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Models/SaveModel.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Models/StatusUpdatedEventArgs.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Models/TempProfile.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Models/TimeZone.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Models/TitleUpdateModel.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Models/UserProfile.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Renderer/EmbeddedWindow.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Renderer/EmbeddedWindowOpenGL.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Renderer/EmbeddedWindowVulkan.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Renderer/OpenTKBindingsContext.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Renderer/RendererHost.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Renderer/RendererHost.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Renderer/SPBOpenGLContext.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/ViewModels/AboutWindowViewModel.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/ViewModels/AmiiboWindowViewModel.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/ViewModels/BaseModel.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/ViewModels/ControllerInputViewModel.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/ViewModels/DownloadableContentManagerViewModel.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/ViewModels/MainWindowViewModel.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/ViewModels/ModManagerViewModel.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/ViewModels/MotionInputViewModel.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/ViewModels/RumbleInputViewModel.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/ViewModels/SettingsViewModel.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/ViewModels/TitleUpdateViewModel.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/ViewModels/UserProfileImageSelectorViewModel.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/ViewModels/UserProfileViewModel.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/ViewModels/UserSaveManagerViewModel.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Input/ControllerInputView.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Input/ControllerInputView.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Input/MotionInputView.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Input/MotionInputView.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Input/RumbleInputView.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Input/RumbleInputView.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Main/MainMenuBarView.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Main/MainMenuBarView.axaml.cs (99%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Main/MainStatusBarView.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Main/MainStatusBarView.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Main/MainViewControls.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Main/MainViewControls.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Settings/SettingsAudioView.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Settings/SettingsAudioView.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Settings/SettingsCPUView.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Settings/SettingsCPUView.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Settings/SettingsGraphicsView.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Settings/SettingsGraphicsView.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Settings/SettingsHotkeysView.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Settings/SettingsHotkeysView.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Settings/SettingsInputView.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Settings/SettingsInputView.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Settings/SettingsLoggingView.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Settings/SettingsLoggingView.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Settings/SettingsNetworkView.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Settings/SettingsNetworkView.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Settings/SettingsSystemView.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Settings/SettingsSystemView.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Settings/SettingsUIView.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/Settings/SettingsUIView.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/User/UserEditorView.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/User/UserEditorView.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/User/UserFirmwareAvatarSelectorView.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/User/UserFirmwareAvatarSelectorView.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/User/UserProfileImageSelectorView.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/User/UserProfileImageSelectorView.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/User/UserRecovererView.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/User/UserRecovererView.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/User/UserSaveManagerView.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/User/UserSaveManagerView.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/User/UserSelectorView.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Views/User/UserSelectorView.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Windows/AboutWindow.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Windows/AboutWindow.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Windows/AmiiboWindow.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Windows/AmiiboWindow.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Windows/CheatWindow.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Windows/CheatWindow.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Windows/ContentDialogOverlayWindow.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Windows/ContentDialogOverlayWindow.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Windows/DownloadableContentManagerWindow.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Windows/DownloadableContentManagerWindow.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Windows/IconColorPicker.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Windows/MainWindow.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Windows/MainWindow.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Windows/ModManagerWindow.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Windows/ModManagerWindow.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Windows/SettingsWindow.axaml (98%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Windows/SettingsWindow.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Windows/StyleableWindow.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Windows/TitleUpdateWindow.axaml (100%) rename src/{Ryujinx.Ava => Ryujinx}/UI/Windows/TitleUpdateWindow.axaml.cs (100%) rename src/{Ryujinx.Ava => Ryujinx}/app.manifest (100%) diff --git a/.editorconfig b/.editorconfig index 1eaf77ae6..76edc491c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -259,12 +259,12 @@ dotnet_diagnostic.CA1861.severity = none # Disable "Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison, but keep in mind that this might cause subtle changes in behavior, so make sure to conduct thorough testing after applying the suggestion, or if culturally sensitive comparison is not required, consider using 'StringComparison.OrdinalIgnoreCase'" dotnet_diagnostic.CA1862.severity = none -[src/Ryujinx.HLE/HOS/Services/**.cs] -# Disable "mark members as static" rule for services +[src/Ryujinx/UI/ViewModels/**.cs] +# Disable "mark members as static" rule for ViewModels dotnet_diagnostic.CA1822.severity = none -[src/Ryujinx.Ava/UI/ViewModels/**.cs] -# Disable "mark members as static" rule for ViewModels +[src/Ryujinx.HLE/HOS/Services/**.cs] +# Disable "mark members as static" rule for services dotnet_diagnostic.CA1822.severity = none [src/Ryujinx.Tests/Cpu/*.cs] diff --git a/.github/labeler.yml b/.github/labeler.yml index b967cc776..cd7650a9d 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -20,7 +20,7 @@ gpu: gui: - changed-files: - - any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.UI.Common/**', 'src/Ryujinx.UI.LocaleGenerator/**', 'src/Ryujinx.Ava/**'] + - any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.UI.Common/**', 'src/Ryujinx.UI.LocaleGenerator/**', 'src/Ryujinx.Gtk3/**'] horizon: - changed-files: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 598f23c5e..9e11302fd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -67,15 +67,15 @@ jobs: run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' - - name: Publish Ryujinx.Ava - run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_ava -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Ava --self-contained true + - name: Publish Ryujinx.Gtk3 + run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_gtk -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Gtk3 --self-contained true if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' - name: Set executable bit run: | chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh chmod +x ./publish_sdl2_headless/Ryujinx.Headless.SDL2 ./publish_sdl2_headless/Ryujinx.sh - chmod +x ./publish_ava/Ryujinx.Ava ./publish_ava/Ryujinx.sh + chmod +x ./publish_gtk/Ryujinx.Gtk3 ./publish_gtk/Ryujinx.sh if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest' - name: Upload Ryujinx artifact @@ -92,11 +92,11 @@ jobs: path: publish_sdl2_headless if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' - - name: Upload Ryujinx.Ava artifact + - name: Upload Ryujinx.Gtk3 artifact uses: actions/upload-artifact@v4 with: - name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }} - path: publish_ava + name: gtk-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }} + path: publish_gtk if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' build_macos: @@ -140,19 +140,19 @@ jobs: shell: bash if: github.event_name == 'pull_request' - - name: Publish macOS Ryujinx.Ava + - name: Publish macOS Ryujinx run: | - ./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER" + ./distribution/macos/create_macos_build_ava.sh . publish_tmp publish ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER" - name: Publish macOS Ryujinx.Headless.SDL2 run: | ./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER" - - name: Upload Ryujinx.Ava artifact + - name: Upload Ryujinx artifact uses: actions/upload-artifact@v4 with: - name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal - path: "publish_ava/*.tar.gz" + name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal + path: "publish/*.tar.gz" if: github.event_name == 'pull_request' - name: Upload Ryujinx.Headless.SDL2 artifact diff --git a/.github/workflows/nightly_pr_comment.yml b/.github/workflows/nightly_pr_comment.yml index f59a6be1f..38850df06 100644 --- a/.github/workflows/nightly_pr_comment.yml +++ b/.github/workflows/nightly_pr_comment.yml @@ -39,24 +39,24 @@ jobs: return core.error(`No artifacts found`); } let body = `Download the artifacts for this pull request:\n`; - let hidden_avalonia_artifacts = `\n\n
Experimental GUI (Avalonia)\n`; + let hidden_gtk_artifacts = `\n\n
Old GUI (GTK3)\n`; let hidden_headless_artifacts = `\n\n
GUI-less (SDL2)\n`; let hidden_debug_artifacts = `\n\n
Only for Developers\n`; for (const art of artifacts) { if(art.name.includes('Debug')) { hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; - } else if(art.name.includes('ava-ryujinx')) { - hidden_avalonia_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; + } else if(art.name.includes('gtk-ryujinx')) { + hidden_gtk_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; } else if(art.name.includes('sdl2-ryujinx-headless')) { hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; } else { body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; } } - hidden_avalonia_artifacts += `\n
`; + hidden_gtk_artifacts += `\n
`; hidden_headless_artifacts += `\n
`; hidden_debug_artifacts += `\n
`; - body += hidden_avalonia_artifacts; + body += hidden_gtk_artifacts; body += hidden_headless_artifacts; body += hidden_debug_artifacts; diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ac598684f..133c2fad0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -86,14 +86,13 @@ jobs: - name: Publish run: | - dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_gtk/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained true + dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained true dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained true - dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Ava --self-contained true - name: Packing Windows builds if: matrix.platform.os == 'windows-latest' run: | - pushd publish_gtk + pushd publish_ava 7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish popd @@ -102,6 +101,7 @@ jobs: popd pushd publish_ava + mv publish/Ryujinx.exe publish/Ryujinx.Ava.exe 7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish popd shell: bash @@ -109,7 +109,7 @@ jobs: - name: Packing Linux builds if: matrix.platform.os == 'ubuntu-latest' run: | - pushd publish_gtk + pushd publish_ava chmod +x publish/Ryujinx.sh publish/Ryujinx tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish popd @@ -120,6 +120,7 @@ jobs: popd pushd publish_ava + mv publish/Ryujinx publish/Ryujinx.Ava chmod +x publish/Ryujinx.sh publish/Ryujinx.Ava tar -czvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish popd @@ -183,10 +184,10 @@ jobs: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs shell: bash - - name: Publish macOS Ryujinx.Ava + - name: Publish macOS Ryujinx run: | ./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release - + - name: Publish macOS Ryujinx.Headless.SDL2 run: | ./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release diff --git a/Ryujinx.sln b/Ryujinx.sln index 47a5c714c..b8304164d 100644 --- a/Ryujinx.sln +++ b/Ryujinx.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.1.32228.430 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "src\Ryujinx\Ryujinx.csproj", "{074045D4-3ED2-4711-9169-E385F2BFB5A0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Gtk3", "src\Ryujinx.Gtk3\Ryujinx.Gtk3.csproj", "{074045D4-3ED2-4711-9169-E385F2BFB5A0}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests", "src\Ryujinx.Tests\Ryujinx.Tests.csproj", "{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}" EndProject @@ -69,7 +69,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Headless.SDL2", "sr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.FFmpeg", "src\Ryujinx.Graphics.Nvdec.FFmpeg\Ryujinx.Graphics.Nvdec.FFmpeg.csproj", "{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ava", "src\Ryujinx.Ava\Ryujinx.Ava.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "src\Ryujinx\Ryujinx.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.UI.Common", "src\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj", "{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}" EndProject diff --git a/distribution/linux/Ryujinx.sh b/distribution/linux/Ryujinx.sh index 6cce4d213..30eb14399 100755 --- a/distribution/linux/Ryujinx.sh +++ b/distribution/linux/Ryujinx.sh @@ -6,10 +6,6 @@ if [ -f "$SCRIPT_DIR/Ryujinx.Headless.SDL2" ]; then RYUJINX_BIN="Ryujinx.Headless.SDL2" fi -if [ -f "$SCRIPT_DIR/Ryujinx.Ava" ]; then - RYUJINX_BIN="Ryujinx.Ava" -fi - if [ -f "$SCRIPT_DIR/Ryujinx" ]; then RYUJINX_BIN="Ryujinx" fi diff --git a/distribution/macos/create_app_bundle.sh b/distribution/macos/create_app_bundle.sh index 858c06f59..0fa54eadd 100755 --- a/distribution/macos/create_app_bundle.sh +++ b/distribution/macos/create_app_bundle.sh @@ -14,8 +14,8 @@ mkdir "$APP_BUNDLE_DIRECTORY/Contents/Frameworks" mkdir "$APP_BUNDLE_DIRECTORY/Contents/MacOS" mkdir "$APP_BUNDLE_DIRECTORY/Contents/Resources" -# Copy executables first -cp "$PUBLISH_DIRECTORY/Ryujinx.Ava" "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx" +# Copy executable and nsure executable can be executed +cp "$PUBLISH_DIRECTORY/Ryujinx" "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx" chmod u+x "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx" # Then all libraries diff --git a/distribution/macos/create_macos_build_ava.sh b/distribution/macos/create_macos_build_ava.sh index 80594a40a..23eafc129 100755 --- a/distribution/macos/create_macos_build_ava.sh +++ b/distribution/macos/create_macos_build_ava.sh @@ -22,9 +22,9 @@ EXTRA_ARGS=$8 if [ "$VERSION" == "1.1.0" ]; then - RELEASE_TAR_FILE_NAME=test-ava-ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar + RELEASE_TAR_FILE_NAME=ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar else - RELEASE_TAR_FILE_NAME=test-ava-ryujinx-$VERSION-macos_universal.app.tar + RELEASE_TAR_FILE_NAME=ryujinx-$VERSION-macos_universal.app.tar fi ARM64_APP_BUNDLE="$TEMP_DIRECTORY/output_arm64/Ryujinx.app" @@ -38,9 +38,9 @@ mkdir -p "$TEMP_DIRECTORY" DOTNET_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained true $EXTRA_ARGS) dotnet restore -dotnet build -c "$CONFIGURATION" src/Ryujinx.Ava -dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Ava -dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Ava +dotnet build -c "$CONFIGURATION" src/Ryujinx +dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx +dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx # Get rid of the support library for ARMeilleure for x64 (that's only for arm64) rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib" @@ -108,6 +108,13 @@ tar --exclude "Ryujinx.app/Contents/MacOS/Ryujinx" -cvf "$RELEASE_TAR_FILE_NAME" python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "Ryujinx.app/Contents/MacOS/Ryujinx" "Ryujinx.app/Contents/MacOS/Ryujinx" gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz" rm "$RELEASE_TAR_FILE_NAME" + +# Create legacy update package for Avalonia to not left behind old testers. +if [ "$VERSION" != "1.1.0" ]; +then + cp $RELEASE_TAR_FILE_NAME.gz test-ava-ryujinx-$VERSION-macos_universal.app.tar.gz +fi + popd echo "Done" \ No newline at end of file diff --git a/src/Ryujinx.Ava/Program.cs b/src/Ryujinx.Ava/Program.cs deleted file mode 100644 index aecc585fc..000000000 --- a/src/Ryujinx.Ava/Program.cs +++ /dev/null @@ -1,237 +0,0 @@ -using Avalonia; -using Avalonia.Threading; -using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Ava.UI.Windows; -using Ryujinx.Common; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.GraphicsDriver; -using Ryujinx.Common.Logging; -using Ryujinx.Common.SystemInterop; -using Ryujinx.Modules; -using Ryujinx.SDL2.Common; -using Ryujinx.UI.Common; -using Ryujinx.UI.Common.Configuration; -using Ryujinx.UI.Common.Helper; -using Ryujinx.UI.Common.SystemInfo; -using System; -using System.IO; -using System.Runtime.InteropServices; -using System.Threading.Tasks; - -namespace Ryujinx.Ava -{ - internal partial class Program - { - public static double WindowScaleFactor { get; set; } - public static double DesktopScaleFactor { get; set; } = 1.0; - public static string Version { get; private set; } - public static string ConfigurationPath { get; private set; } - public static bool PreviewerDetached { get; private set; } - - [LibraryImport("user32.dll", SetLastError = true)] - public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type); - - private const uint MbIconwarning = 0x30; - - public static void Main(string[] args) - { - Version = ReleaseInformation.Version; - - if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134)) - { - _ = MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MbIconwarning); - } - - PreviewerDetached = true; - - Initialize(args); - - LoggerAdapter.Register(); - - BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); - } - - public static AppBuilder BuildAvaloniaApp() - { - return AppBuilder.Configure() - .UsePlatformDetect() - .With(new X11PlatformOptions - { - EnableMultiTouch = true, - EnableIme = true, - EnableInputFocusProxy = Environment.GetEnvironmentVariable("XDG_CURRENT_DESKTOP") == "gamescope", - RenderingMode = new[] { X11RenderingMode.Glx, X11RenderingMode.Software }, - }) - .With(new Win32PlatformOptions - { - WinUICompositionBackdropCornerRadius = 8.0f, - RenderingMode = new[] { Win32RenderingMode.AngleEgl, Win32RenderingMode.Software }, - }) - .UseSkia(); - } - - private static void Initialize(string[] args) - { - // Parse arguments - CommandLineState.ParseArguments(args); - - // Delete backup files after updating. - Task.Run(Updater.CleanupUpdate); - - Console.Title = $"Ryujinx Console {Version}"; - - // Hook unhandled exception and process exit events. - AppDomain.CurrentDomain.UnhandledException += (sender, e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating); - AppDomain.CurrentDomain.ProcessExit += (sender, e) => Exit(); - - // Setup base data directory. - AppDataManager.Initialize(CommandLineState.BaseDirPathArg); - - // Initialize the configuration. - ConfigurationState.Initialize(); - - // Initialize the logger system. - LoggerModule.Initialize(); - - // Initialize Discord integration. - DiscordIntegrationModule.Initialize(); - - // Initialize SDL2 driver - SDL2Driver.MainThreadDispatcher = action => Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Input); - - ReloadConfig(); - - WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor(); - - // Logging system information. - PrintSystemInfo(); - - // Enable OGL multithreading on the driver, when available. - DriverUtilities.ToggleOGLThreading(ConfigurationState.Instance.Graphics.BackendThreading == BackendThreading.Off); - - // Check if keys exists. - if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys"))) - { - if (!(AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys")))) - { - MainWindow.ShowKeyErrorOnLoad = true; - } - } - - if (CommandLineState.LaunchPathArg != null) - { - MainWindow.DeferLoadApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg); - } - } - - public static void ReloadConfig() - { - string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName); - string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName); - - // Now load the configuration as the other subsystems are now registered - if (File.Exists(localConfigurationPath)) - { - ConfigurationPath = localConfigurationPath; - } - else if (File.Exists(appDataConfigurationPath)) - { - ConfigurationPath = appDataConfigurationPath; - } - - if (ConfigurationPath == null) - { - // No configuration, we load the default values and save it to disk - ConfigurationPath = appDataConfigurationPath; - - ConfigurationState.Instance.LoadDefault(); - ConfigurationState.Instance.ToFileFormat().SaveConfig(ConfigurationPath); - } - else - { - if (ConfigurationFileFormat.TryLoad(ConfigurationPath, out ConfigurationFileFormat configurationFileFormat)) - { - ConfigurationState.Instance.Load(configurationFileFormat, ConfigurationPath); - } - else - { - ConfigurationState.Instance.LoadDefault(); - - Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location {ConfigurationPath}"); - } - } - - // Check if graphics backend was overridden - if (CommandLineState.OverrideGraphicsBackend != null) - { - if (CommandLineState.OverrideGraphicsBackend.ToLower() == "opengl") - { - ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.OpenGl; - } - else if (CommandLineState.OverrideGraphicsBackend.ToLower() == "vulkan") - { - ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.Vulkan; - } - } - - // Check if docked mode was overriden. - if (CommandLineState.OverrideDockedMode.HasValue) - { - ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value; - } - - // Check if HideCursor was overridden. - if (CommandLineState.OverrideHideCursor is not null) - { - ConfigurationState.Instance.HideCursor.Value = CommandLineState.OverrideHideCursor!.ToLower() switch - { - "never" => HideCursorMode.Never, - "onidle" => HideCursorMode.OnIdle, - "always" => HideCursorMode.Always, - _ => ConfigurationState.Instance.HideCursor.Value, - }; - } - } - - private static void PrintSystemInfo() - { - Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}"); - SystemInfo.Gather().Print(); - - Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(Logger.GetEnabledLevels().Count == 0 ? "" : string.Join(", ", Logger.GetEnabledLevels()))}"); - - if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom) - { - Logger.Notice.Print(LogClass.Application, $"Launch Mode: Custom Path {AppDataManager.BaseDirPath}"); - } - else - { - Logger.Notice.Print(LogClass.Application, $"Launch Mode: {AppDataManager.Mode}"); - } - } - - private static void ProcessUnhandledException(Exception ex, bool isTerminating) - { - string message = $"Unhandled exception caught: {ex}"; - - Logger.Error?.PrintMsg(LogClass.Application, message); - - if (Logger.Error == null) - { - Logger.Notice.PrintMsg(LogClass.Application, message); - } - - if (isTerminating) - { - Exit(); - } - } - - public static void Exit() - { - DiscordIntegrationModule.Exit(); - - Logger.Shutdown(); - } - } -} diff --git a/src/Ryujinx.Ava/Ryujinx.Ava.csproj b/src/Ryujinx.Ava/Ryujinx.Ava.csproj deleted file mode 100644 index 91c2744f0..000000000 --- a/src/Ryujinx.Ava/Ryujinx.Ava.csproj +++ /dev/null @@ -1,167 +0,0 @@ - - - net8.0 - win-x64;osx-x64;linux-x64 - Exe - true - 1.0.0-dirty - $(DefineConstants);$(ExtraDefineConstants) - - - Ryujinx.Ava - Ryujinx.ico - true - true - app.manifest - - - - - - - - true - false - true - partial - - - - - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Always - alsoft.ini - - - Always - THIRDPARTY.md - - - Always - LICENSE.txt - - - - - - Always - - - Always - mime\Ryujinx.xml - - - - - - Designer - - - - MSBuild:Compile - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Ryujinx/Input/GTK3/GTK3Keyboard.cs b/src/Ryujinx.Gtk3/Input/GTK3/GTK3Keyboard.cs similarity index 100% rename from src/Ryujinx/Input/GTK3/GTK3Keyboard.cs rename to src/Ryujinx.Gtk3/Input/GTK3/GTK3Keyboard.cs diff --git a/src/Ryujinx/Input/GTK3/GTK3KeyboardDriver.cs b/src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs similarity index 100% rename from src/Ryujinx/Input/GTK3/GTK3KeyboardDriver.cs rename to src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs diff --git a/src/Ryujinx/Input/GTK3/GTK3MappingHelper.cs b/src/Ryujinx.Gtk3/Input/GTK3/GTK3MappingHelper.cs similarity index 100% rename from src/Ryujinx/Input/GTK3/GTK3MappingHelper.cs rename to src/Ryujinx.Gtk3/Input/GTK3/GTK3MappingHelper.cs diff --git a/src/Ryujinx/Input/GTK3/GTK3Mouse.cs b/src/Ryujinx.Gtk3/Input/GTK3/GTK3Mouse.cs similarity index 100% rename from src/Ryujinx/Input/GTK3/GTK3Mouse.cs rename to src/Ryujinx.Gtk3/Input/GTK3/GTK3Mouse.cs diff --git a/src/Ryujinx/Input/GTK3/GTK3MouseDriver.cs b/src/Ryujinx.Gtk3/Input/GTK3/GTK3MouseDriver.cs similarity index 100% rename from src/Ryujinx/Input/GTK3/GTK3MouseDriver.cs rename to src/Ryujinx.Gtk3/Input/GTK3/GTK3MouseDriver.cs diff --git a/src/Ryujinx/Modules/Updater/UpdateDialog.cs b/src/Ryujinx.Gtk3/Modules/Updater/UpdateDialog.cs similarity index 93% rename from src/Ryujinx/Modules/Updater/UpdateDialog.cs rename to src/Ryujinx.Gtk3/Modules/Updater/UpdateDialog.cs index ec24cdc89..43bde9420 100644 --- a/src/Ryujinx/Modules/Updater/UpdateDialog.cs +++ b/src/Ryujinx.Gtk3/Modules/Updater/UpdateDialog.cs @@ -25,7 +25,7 @@ public class UpdateDialog : Gtk.Window private readonly string _buildUrl; private bool _restartQuery; - public UpdateDialog(MainWindow mainWindow, Version newVersion, string buildUrl) : this(new Builder("Ryujinx.Modules.Updater.UpdateDialog.glade"), mainWindow, newVersion, buildUrl) { } + public UpdateDialog(MainWindow mainWindow, Version newVersion, string buildUrl) : this(new Builder("Ryujinx.Gtk3.Modules.Updater.UpdateDialog.glade"), mainWindow, newVersion, buildUrl) { } private UpdateDialog(Builder builder, MainWindow mainWindow, Version newVersion, string buildUrl) : base(builder.GetRawOwnedObject("UpdateDialog")) { @@ -34,7 +34,7 @@ private UpdateDialog(Builder builder, MainWindow mainWindow, Version newVersion, _mainWindow = mainWindow; _buildUrl = buildUrl; - Icon = new Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); + Icon = new Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Gtk3.UI.Common.Resources.Logo_Ryujinx.png"); MainText.Text = "Do you want to update Ryujinx to the latest version?"; SecondaryText.Text = $"{Program.Version} -> {newVersion}"; diff --git a/src/Ryujinx/Modules/Updater/UpdateDialog.glade b/src/Ryujinx.Gtk3/Modules/Updater/UpdateDialog.glade similarity index 100% rename from src/Ryujinx/Modules/Updater/UpdateDialog.glade rename to src/Ryujinx.Gtk3/Modules/Updater/UpdateDialog.glade diff --git a/src/Ryujinx.Ava/Modules/Updater/Updater.cs b/src/Ryujinx.Gtk3/Modules/Updater/Updater.cs similarity index 50% rename from src/Ryujinx.Ava/Modules/Updater/Updater.cs rename to src/Ryujinx.Gtk3/Modules/Updater/Updater.cs index 5795f34f0..8b006f63f 100644 --- a/src/Ryujinx.Ava/Modules/Updater/Updater.cs +++ b/src/Ryujinx.Gtk3/Modules/Updater/Updater.cs @@ -1,75 +1,93 @@ -using Avalonia.Controls; -using Avalonia.Threading; -using FluentAvalonia.UI.Controls; +using Gtk; using ICSharpCode.SharpZipLib.GZip; using ICSharpCode.SharpZipLib.Tar; using ICSharpCode.SharpZipLib.Zip; -using Ryujinx.Ava; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Helpers; using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.Common.Utilities; -using Ryujinx.UI.Common.Helper; +using Ryujinx.UI; using Ryujinx.UI.Common.Models.Github; +using Ryujinx.UI.Widgets; using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Net.NetworkInformation; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Ryujinx.Modules { - internal static class Updater + public static class Updater { private const string GitHubApiUrl = "https://api.github.com"; - private static readonly GithubReleasesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + private const int ConnectionCount = 4; + + internal static bool Running; private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory; private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update"); private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish"); - private const int ConnectionCount = 4; private static string _buildVer; private static string _platformExt; private static string _buildUrl; private static long _buildSize; - private static bool _updateSuccessful; - private static bool _running; - private static readonly string[] _windowsDependencyDirs = Array.Empty(); + private static readonly GithubReleasesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + // On Windows, GtkSharp.Dependencies adds these extra dirs that must be cleaned during updates. + private static readonly string[] _windowsDependencyDirs = { "bin", "etc", "lib", "share" }; - public static async Task BeginParse(Window mainWindow, bool showVersionUpToDate) + private static HttpClient ConstructHttpClient() { - if (_running) + HttpClient result = new(); + + // Required by GitHub to interact with APIs. + result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0"); + + return result; + } + + public static async Task BeginParse(MainWindow mainWindow, bool showVersionUpToDate) + { + if (Running) { return; } - _running = true; + Running = true; + mainWindow.UpdateMenuItem.Sensitive = false; + + int artifactIndex = -1; // Detect current platform if (OperatingSystem.IsMacOS()) { - _platformExt = "macos_universal.app.tar.gz"; + _platformExt = "osx_x64.zip"; + artifactIndex = 1; } else if (OperatingSystem.IsWindows()) { _platformExt = "win_x64.zip"; + artifactIndex = 2; } else if (OperatingSystem.IsLinux()) { var arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "arm64" : "x64"; _platformExt = $"linux_{arch}.tar.gz"; + artifactIndex = 0; + } + + if (artifactIndex == -1) + { + GtkDialog.CreateErrorDialog("Your platform is not supported!"); + + return; } Version newVersion; @@ -81,14 +99,9 @@ public static async Task BeginParse(Window mainWindow, bool showVersionUpToDate) } catch { + GtkDialog.CreateWarningDialog("Failed to convert the current Ryujinx version.", "Cancelling Update!"); Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!"); - await ContentDialogHelper.CreateWarningDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage], - LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); - - _running = false; - return; } @@ -96,15 +109,16 @@ await ContentDialogHelper.CreateWarningDialog( try { using HttpClient jsonClient = ConstructHttpClient(); - string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest"; + + // Fetch latest build information string fetchedJson = await jsonClient.GetStringAsync(buildInfoUrl); var fetched = JsonHelper.Deserialize(fetchedJson, _serializerContext.GithubReleasesJsonResponse); _buildVer = fetched.Name; foreach (var asset in fetched.Assets) { - if (asset.Name.StartsWith("test-ava-ryujinx") && asset.Name.EndsWith(_platformExt)) + if (asset.Name.StartsWith("gtk-ryujinx") && asset.Name.EndsWith(_platformExt)) { _buildUrl = asset.BrowserDownloadUrl; @@ -112,13 +126,9 @@ await ContentDialogHelper.CreateWarningDialog( { if (showVersionUpToDate) { - await ContentDialogHelper.CreateUpdaterInfoDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], - ""); + GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", ""); } - _running = false; - return; } @@ -126,29 +136,20 @@ await ContentDialogHelper.CreateUpdaterInfoDialog( } } - // If build not done, assume no new update are available. - if (_buildUrl is null) + if (_buildUrl == null) { if (showVersionUpToDate) { - await ContentDialogHelper.CreateUpdaterInfoDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], - ""); + GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", ""); } - _running = false; - return; } } catch (Exception exception) { Logger.Error?.Print(LogClass.Application, exception.Message); - - await ContentDialogHelper.CreateErrorDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]); - - _running = false; + GtkDialog.CreateErrorDialog("An error occurred when trying to get release information from GitHub Release. This can be caused if a new release is being compiled by GitHub Actions. Try again in a few minutes."); return; } @@ -159,13 +160,8 @@ await ContentDialogHelper.CreateErrorDialog( } catch { - Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!"); - - await ContentDialogHelper.CreateWarningDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage], - LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); - - _running = false; + GtkDialog.CreateWarningDialog("Failed to convert the received Ryujinx version from GitHub Release.", "Cancelling Update!"); + Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from GitHub Release!"); return; } @@ -174,12 +170,11 @@ await ContentDialogHelper.CreateWarningDialog( { if (showVersionUpToDate) { - await ContentDialogHelper.CreateUpdaterInfoDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], - ""); + GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", ""); } - _running = false; + Running = false; + mainWindow.UpdateMenuItem.Sensitive = true; return; } @@ -202,39 +197,13 @@ await ContentDialogHelper.CreateUpdaterInfoDialog( _buildSize = -1; } - await Dispatcher.UIThread.InvokeAsync(async () => - { - // Show a message asking the user if they want to update - var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog( - LocaleManager.Instance[LocaleKeys.RyujinxUpdater], - LocaleManager.Instance[LocaleKeys.RyujinxUpdaterMessage], - $"{Program.Version} -> {newVersion}"); - - if (shouldUpdate) - { - await UpdateRyujinx(mainWindow, _buildUrl); - } - else - { - _running = false; - } - }); - } - - private static HttpClient ConstructHttpClient() - { - HttpClient result = new(); - - // Required by GitHub to interact with APIs. - result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0"); - - return result; + // Show a message asking the user if they want to update + UpdateDialog updateDialog = new(mainWindow, newVersion, _buildUrl); + updateDialog.Show(); } - private static async Task UpdateRyujinx(Window parent, string downloadUrl) + public static void UpdateRyujinx(UpdateDialog updateDialog, string downloadUrl) { - _updateSuccessful = false; - // Empty update dir, although it shouldn't ever have anything inside it if (Directory.Exists(_updateDir)) { @@ -245,93 +214,22 @@ private static async Task UpdateRyujinx(Window parent, string downloadUrl) string updateFile = Path.Combine(_updateDir, "update.bin"); - TaskDialog taskDialog = new() - { - Header = LocaleManager.Instance[LocaleKeys.RyujinxUpdater], - SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading], - IconSource = new SymbolIconSource { Symbol = Symbol.Download }, - ShowProgressBar = true, - XamlRoot = parent, - }; + // Download the update .zip + updateDialog.MainText.Text = "Downloading Update..."; + updateDialog.ProgressBar.Value = 0; + updateDialog.ProgressBar.MaxValue = 100; - taskDialog.Opened += (s, e) => + if (_buildSize >= 0) { - if (_buildSize >= 0) - { - DoUpdateWithMultipleThreads(taskDialog, downloadUrl, updateFile); - } - else - { - DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); - } - }; - - await taskDialog.ShowAsync(true); - - if (_updateSuccessful) + DoUpdateWithMultipleThreads(updateDialog, downloadUrl, updateFile); + } + else { - bool shouldRestart = true; - - if (!OperatingSystem.IsMacOS()) - { - shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater], - LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage], - LocaleManager.Instance[LocaleKeys.DialogUpdaterRestartMessage]); - } - - if (shouldRestart) - { - List arguments = CommandLineState.Arguments.ToList(); - string executableDirectory = AppDomain.CurrentDomain.BaseDirectory; - - // On macOS we perform the update at relaunch. - if (OperatingSystem.IsMacOS()) - { - string baseBundlePath = Path.GetFullPath(Path.Combine(executableDirectory, "..", "..")); - string newBundlePath = Path.Combine(_updateDir, "Ryujinx.app"); - string updaterScriptPath = Path.Combine(newBundlePath, "Contents", "Resources", "updater.sh"); - string currentPid = Environment.ProcessId.ToString(); - - arguments.InsertRange(0, new List { updaterScriptPath, baseBundlePath, newBundlePath, currentPid }); - Process.Start("/bin/bash", arguments); - } - else - { - // Find the process name. - string ryuName = Path.GetFileName(Environment.ProcessPath); - - // Some operating systems can see the renamed executable, so strip off the .ryuold if found. - if (ryuName.EndsWith(".ryuold")) - { - ryuName = ryuName[..^7]; - } - - // Fallback if the executable could not be found. - if (!Path.Exists(Path.Combine(executableDirectory, ryuName))) - { - ryuName = OperatingSystem.IsWindows() ? "Ryujinx.Ava.exe" : "Ryujinx.Ava"; - } - - ProcessStartInfo processStart = new(ryuName) - { - UseShellExecute = true, - WorkingDirectory = executableDirectory, - }; - - foreach (string argument in CommandLineState.Arguments) - { - processStart.ArgumentList.Add(argument); - } - - Process.Start(processStart); - } - - Environment.Exit(0); - } + DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile); } } - private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile) + private static void DoUpdateWithMultipleThreads(UpdateDialog updateDialog, string downloadUrl, string updateFile) { // Multi-Threaded Updater long chunkSize = _buildSize / ConnectionCount; @@ -355,7 +253,6 @@ private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string do // TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient. using WebClient client = new(); #pragma warning restore SYSLIB0014 - webClients.Add(client); if (i == ConnectionCount - 1) @@ -375,7 +272,7 @@ private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string do Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage); Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage); - taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal); + updateDialog.ProgressBar.Value = totalProgressPercentage / ConnectionCount; }; client.DownloadDataCompleted += (_, args) => @@ -386,8 +283,6 @@ private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string do { webClients[index].Dispose(); - taskDialog.Hide(); - return; } @@ -405,24 +300,18 @@ private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string do File.WriteAllBytes(updateFile, mergedFileBytes); - // On macOS, ensure that we remove the quarantine bit to prevent Gatekeeper from blocking execution. - if (OperatingSystem.IsMacOS()) - { - using Process xattrProcess = Process.Start("xattr", new List { "-d", "com.apple.quarantine", updateFile }); - - xattrProcess.WaitForExit(); - } - try { - InstallUpdate(taskDialog, updateFile); + InstallUpdate(updateDialog, updateFile); } catch (Exception e) { Logger.Warning?.Print(LogClass.Application, e.Message); Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater."); - DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); + DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile); + + return; } } }; @@ -441,14 +330,14 @@ private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string do webClient.CancelAsync(); } - DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); + DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile); return; } } } - private static void DoUpdateWithSingleThreadWorker(TaskDialog taskDialog, string downloadUrl, string updateFile) + private static void DoUpdateWithSingleThreadWorker(UpdateDialog updateDialog, string downloadUrl, string updateFile) { using HttpClient client = new(); // We do not want to timeout while downloading @@ -474,165 +363,151 @@ private static void DoUpdateWithSingleThreadWorker(TaskDialog taskDialog, string byteWritten += readSize; - taskDialog.SetProgressBarState(GetPercentage(byteWritten, totalBytes), TaskDialogProgressState.Normal); - + updateDialog.ProgressBar.Value = ((double)byteWritten / totalBytes) * 100; updateFileStream.Write(buffer, 0, readSize); } - InstallUpdate(taskDialog, updateFile); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static double GetPercentage(double value, double max) - { - return max == 0 ? 0 : value / max * 100; + InstallUpdate(updateDialog, updateFile); } - private static void DoUpdateWithSingleThread(TaskDialog taskDialog, string downloadUrl, string updateFile) + private static void DoUpdateWithSingleThread(UpdateDialog updateDialog, string downloadUrl, string updateFile) { - Thread worker = new(() => DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile)) + Thread worker = new(() => DoUpdateWithSingleThreadWorker(updateDialog, downloadUrl, updateFile)) { Name = "Updater.SingleThreadWorker", }; - worker.Start(); } - [SupportedOSPlatform("linux")] - [SupportedOSPlatform("macos")] - private static void ExtractTarGzipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath) + private static async void InstallUpdate(UpdateDialog updateDialog, string updateFile) { - using Stream inStream = File.OpenRead(archivePath); - using GZipInputStream gzipStream = new(inStream); - using TarInputStream tarStream = new(gzipStream, Encoding.ASCII); - - TarEntry tarEntry; + // Extract Update + updateDialog.MainText.Text = "Extracting Update..."; + updateDialog.ProgressBar.Value = 0; - while ((tarEntry = tarStream.GetNextEntry()) is not null) + if (OperatingSystem.IsLinux()) { - if (tarEntry.IsDirectory) + using Stream inStream = File.OpenRead(updateFile); + using Stream gzipStream = new GZipInputStream(inStream); + using TarInputStream tarStream = new(gzipStream, Encoding.ASCII); + updateDialog.ProgressBar.MaxValue = inStream.Length; + + await Task.Run(() => { - continue; - } + TarEntry tarEntry; - string outPath = Path.Combine(outputDirectoryPath, tarEntry.Name); + if (!OperatingSystem.IsWindows()) + { + while ((tarEntry = tarStream.GetNextEntry()) != null) + { + if (tarEntry.IsDirectory) + { + continue; + } - Directory.CreateDirectory(Path.GetDirectoryName(outPath)); + string outPath = Path.Combine(_updateDir, tarEntry.Name); - using FileStream outStream = File.OpenWrite(outPath); - tarStream.CopyEntryContents(outStream); + Directory.CreateDirectory(Path.GetDirectoryName(outPath)); - File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode); - File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc)); + using FileStream outStream = File.OpenWrite(outPath); + tarStream.CopyEntryContents(outStream); - Dispatcher.UIThread.Post(() => - { - if (tarEntry is null) - { - return; - } + File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode); + File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc)); - taskDialog.SetProgressBarState(GetPercentage(tarEntry.Size, inStream.Length), TaskDialogProgressState.Normal); - }); - } - } + TarEntry entry = tarEntry; - private static void ExtractZipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath) - { - using Stream inStream = File.OpenRead(archivePath); - using ZipFile zipFile = new(inStream); + Application.Invoke(delegate + { + updateDialog.ProgressBar.Value += entry.Size; + }); + } + } + }); - double count = 0; - foreach (ZipEntry zipEntry in zipFile) + updateDialog.ProgressBar.Value = inStream.Length; + } + else { - count++; - if (zipEntry.IsDirectory) - { - continue; - } + using Stream inStream = File.OpenRead(updateFile); + using ZipFile zipFile = new(inStream); + updateDialog.ProgressBar.MaxValue = zipFile.Count; - string outPath = Path.Combine(outputDirectoryPath, zipEntry.Name); + await Task.Run(() => + { + foreach (ZipEntry zipEntry in zipFile) + { + if (zipEntry.IsDirectory) + { + continue; + } - Directory.CreateDirectory(Path.GetDirectoryName(outPath)); + string outPath = Path.Combine(_updateDir, zipEntry.Name); - using Stream zipStream = zipFile.GetInputStream(zipEntry); - using FileStream outStream = File.OpenWrite(outPath); + Directory.CreateDirectory(Path.GetDirectoryName(outPath)); - zipStream.CopyTo(outStream); + using Stream zipStream = zipFile.GetInputStream(zipEntry); + using FileStream outStream = File.OpenWrite(outPath); + zipStream.CopyTo(outStream); - File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc)); + File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc)); - Dispatcher.UIThread.Post(() => - { - taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal); + Application.Invoke(delegate + { + updateDialog.ProgressBar.Value++; + }); + } }); } - } - - private static void InstallUpdate(TaskDialog taskDialog, string updateFile) - { - // Extract Update - taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting]; - taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); - - if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) - { - ExtractTarGzipFile(taskDialog, updateFile, _updateDir); - } - else if (OperatingSystem.IsWindows()) - { - ExtractZipFile(taskDialog, updateFile, _updateDir); - } - else - { - throw new NotSupportedException(); - } // Delete downloaded zip File.Delete(updateFile); List allFiles = EnumerateFilesToDelete().ToList(); - taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterRenaming]; - taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); + updateDialog.MainText.Text = "Renaming Old Files..."; + updateDialog.ProgressBar.Value = 0; + updateDialog.ProgressBar.MaxValue = allFiles.Count; - // NOTE: On macOS, replacement is delayed to the restart phase. - if (!OperatingSystem.IsMacOS()) + // Replace old files + await Task.Run(() => { - // Replace old files - double count = 0; foreach (string file in allFiles) { - count++; try { File.Move(file, file + ".ryuold"); - Dispatcher.UIThread.InvokeAsync(() => + Application.Invoke(delegate { - taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal); + updateDialog.ProgressBar.Value++; }); } catch { - Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file)); + Logger.Warning?.Print(LogClass.Application, "Updater was unable to rename file: " + file); } } - Dispatcher.UIThread.InvokeAsync(() => + Application.Invoke(delegate { - taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles]; - taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); + updateDialog.MainText.Text = "Adding New Files..."; + updateDialog.ProgressBar.Value = 0; + updateDialog.ProgressBar.MaxValue = Directory.GetFiles(_updatePublishDir, "*", SearchOption.AllDirectories).Length; }); - MoveAllFilesOver(_updatePublishDir, _homeDir, taskDialog); + MoveAllFilesOver(_updatePublishDir, _homeDir, updateDialog); + }); - Directory.Delete(_updateDir, true); - } + Directory.Delete(_updateDir, true); - _updateSuccessful = true; + updateDialog.MainText.Text = "Update Complete!"; + updateDialog.SecondaryText.Text = "Do you want to restart Ryujinx now?"; + updateDialog.Modal = true; - taskDialog.Hide(); + updateDialog.ProgressBar.Hide(); + updateDialog.YesButton.Show(); + updateDialog.NoButton.Show(); } public static bool CanUpdate(bool showWarnings) @@ -642,11 +517,7 @@ public static bool CanUpdate(bool showWarnings) { if (showWarnings) { - Dispatcher.UIThread.InvokeAsync(() => - ContentDialogHelper.CreateWarningDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetMessage], - LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage]) - ); + GtkDialog.CreateWarningDialog("You are not connected to the Internet!", "Please verify that you have a working Internet connection!"); } return false; @@ -656,11 +527,7 @@ public static bool CanUpdate(bool showWarnings) { if (showWarnings) { - Dispatcher.UIThread.InvokeAsync(() => - ContentDialogHelper.CreateWarningDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildMessage], - LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]) - ); + GtkDialog.CreateWarningDialog("You cannot update a Dirty build of Ryujinx!", "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version."); } return false; @@ -672,19 +539,11 @@ public static bool CanUpdate(bool showWarnings) { if (ReleaseInformation.IsFlatHubBuild) { - Dispatcher.UIThread.InvokeAsync(() => - ContentDialogHelper.CreateWarningDialog( - LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], - LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage]) - ); + GtkDialog.CreateWarningDialog("Updater Disabled!", "Please update Ryujinx via FlatHub."); } else { - Dispatcher.UIThread.InvokeAsync(() => - ContentDialogHelper.CreateWarningDialog( - LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], - LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]) - ); + GtkDialog.CreateWarningDialog("Updater Disabled!", "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version."); } } @@ -698,7 +557,7 @@ private static IEnumerable EnumerateFilesToDelete() var files = Directory.EnumerateFiles(_homeDir); // All files directly in base dir. // Determine and exclude user files only when the updater is running, not when cleaning old files - if (_running && !OperatingSystem.IsMacOS()) + if (Running) { // Compare the loose files in base directory against the loose files from the incoming update, and store foreign ones in a user list. var oldFiles = Directory.EnumerateFiles(_homeDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName); @@ -724,9 +583,8 @@ private static IEnumerable EnumerateFilesToDelete() return files.Where(f => !new FileInfo(f).Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System)); } - private static void MoveAllFilesOver(string root, string dest, TaskDialog taskDialog) + private static void MoveAllFilesOver(string root, string dest, UpdateDialog dialog) { - int total = Directory.GetFiles(root, "*", SearchOption.AllDirectories).Length; foreach (string directory in Directory.GetDirectories(root)) { string dirName = Path.GetFileName(directory); @@ -736,28 +594,28 @@ private static void MoveAllFilesOver(string root, string dest, TaskDialog taskDi Directory.CreateDirectory(Path.Combine(dest, dirName)); } - MoveAllFilesOver(directory, Path.Combine(dest, dirName), taskDialog); + MoveAllFilesOver(directory, Path.Combine(dest, dirName), dialog); } - double count = 0; foreach (string file in Directory.GetFiles(root)) { - count++; - File.Move(file, Path.Combine(dest, Path.GetFileName(file)), true); - Dispatcher.UIThread.InvokeAsync(() => + Application.Invoke(delegate { - taskDialog.SetProgressBarState(GetPercentage(count, total), TaskDialogProgressState.Normal); + dialog.ProgressBar.Value++; }); } } public static void CleanupUpdate() { - foreach (string file in Directory.GetFiles(_homeDir, "*.ryuold", SearchOption.AllDirectories)) + foreach (string file in EnumerateFilesToDelete()) { - File.Delete(file); + if (Path.GetExtension(file).EndsWith(".ryuold")) + { + File.Delete(file); + } } } } diff --git a/src/Ryujinx.Gtk3/Program.cs b/src/Ryujinx.Gtk3/Program.cs new file mode 100644 index 000000000..1845c512e --- /dev/null +++ b/src/Ryujinx.Gtk3/Program.cs @@ -0,0 +1,378 @@ +using Gtk; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.GraphicsDriver; +using Ryujinx.Common.Logging; +using Ryujinx.Common.SystemInterop; +using Ryujinx.Modules; +using Ryujinx.SDL2.Common; +using Ryujinx.UI; +using Ryujinx.UI.Common; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; +using Ryujinx.UI.Common.SystemInfo; +using Ryujinx.UI.Widgets; +using SixLabors.ImageSharp.Formats.Jpeg; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace Ryujinx +{ + partial class Program + { + public static double WindowScaleFactor { get; private set; } + + public static string Version { get; private set; } + + public static string ConfigurationPath { get; set; } + + public static string CommandLineProfile { get; set; } + + private const string X11LibraryName = "libX11"; + + [LibraryImport(X11LibraryName)] + private static partial int XInitThreads(); + + [LibraryImport("user32.dll", SetLastError = true)] + public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type); + + [LibraryImport("libc", SetLastError = true)] + private static partial int setenv([MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string value, int overwrite); + + private const uint MbIconWarning = 0x30; + + static Program() + { + if (OperatingSystem.IsLinux()) + { + NativeLibrary.SetDllImportResolver(typeof(Program).Assembly, (name, assembly, path) => + { + if (name != X11LibraryName) + { + return IntPtr.Zero; + } + + if (!NativeLibrary.TryLoad("libX11.so.6", assembly, path, out IntPtr result)) + { + if (!NativeLibrary.TryLoad("libX11.so", assembly, path, out result)) + { + return IntPtr.Zero; + } + } + + return result; + }); + } + } + + static void Main(string[] args) + { + Version = ReleaseInformation.Version; + + if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134)) + { + MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MbIconWarning); + } + + // Parse arguments + CommandLineState.ParseArguments(args); + + // Hook unhandled exception and process exit events. + GLib.ExceptionManager.UnhandledException += (GLib.UnhandledExceptionArgs e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating); + AppDomain.CurrentDomain.UnhandledException += (object sender, UnhandledExceptionEventArgs e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating); + AppDomain.CurrentDomain.ProcessExit += (object sender, EventArgs e) => Exit(); + + // Make process DPI aware for proper window sizing on high-res screens. + ForceDpiAware.Windows(); + WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor(); + + // Delete backup files after updating. + Task.Run(Updater.CleanupUpdate); + + Console.Title = $"Ryujinx Console {Version}"; + + // NOTE: GTK3 doesn't init X11 in a multi threaded way. + // This ends up causing race condition and abort of XCB when a context is created by SPB (even if SPB do call XInitThreads). + if (OperatingSystem.IsLinux()) + { + if (XInitThreads() == 0) + { + throw new NotSupportedException("Failed to initialize multi-threading support."); + } + + Environment.SetEnvironmentVariable("GDK_BACKEND", "x11"); + setenv("GDK_BACKEND", "x11", 1); + } + + if (OperatingSystem.IsMacOS()) + { + string baseDirectory = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory); + string resourcesDataDir; + + if (Path.GetFileName(baseDirectory) == "MacOS") + { + resourcesDataDir = Path.Combine(Directory.GetParent(baseDirectory).FullName, "Resources"); + } + else + { + resourcesDataDir = baseDirectory; + } + + static void SetEnvironmentVariableNoCaching(string key, string value) + { + int res = setenv(key, value, 1); + Debug.Assert(res != -1); + } + + // On macOS, GTK3 needs XDG_DATA_DIRS to be set, otherwise it will try searching for "gschemas.compiled" in system directories. + SetEnvironmentVariableNoCaching("XDG_DATA_DIRS", Path.Combine(resourcesDataDir, "share")); + + // On macOS, GTK3 needs GDK_PIXBUF_MODULE_FILE to be set, otherwise it will try searching for "loaders.cache" in system directories. + SetEnvironmentVariableNoCaching("GDK_PIXBUF_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gdk-pixbuf-2.0", "2.10.0", "loaders.cache")); + + SetEnvironmentVariableNoCaching("GTK_IM_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gtk-3.0", "3.0.0", "immodules.cache")); + } + + string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine); + Environment.SetEnvironmentVariable("Path", $"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")};{systemPath}"); + + // Setup base data directory. + AppDataManager.Initialize(CommandLineState.BaseDirPathArg); + + // Initialize the configuration. + ConfigurationState.Initialize(); + + // Initialize the logger system. + LoggerModule.Initialize(); + + // Initialize Discord integration. + DiscordIntegrationModule.Initialize(); + + // Initialize SDL2 driver + SDL2Driver.MainThreadDispatcher = action => + { + Application.Invoke(delegate + { + action(); + }); + }; + + // Sets ImageSharp Jpeg Encoder Quality. + SixLabors.ImageSharp.Configuration.Default.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder() + { + Quality = 100, + }); + + string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName); + string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName); + + // Now load the configuration as the other subsystems are now registered + ConfigurationPath = File.Exists(localConfigurationPath) + ? localConfigurationPath + : File.Exists(appDataConfigurationPath) + ? appDataConfigurationPath + : null; + + if (ConfigurationPath == null) + { + // No configuration, we load the default values and save it to disk + ConfigurationPath = appDataConfigurationPath; + + ConfigurationState.Instance.LoadDefault(); + ConfigurationState.Instance.ToFileFormat().SaveConfig(ConfigurationPath); + } + else + { + if (ConfigurationFileFormat.TryLoad(ConfigurationPath, out ConfigurationFileFormat configurationFileFormat)) + { + ConfigurationState.Instance.Load(configurationFileFormat, ConfigurationPath); + } + else + { + ConfigurationState.Instance.LoadDefault(); + + Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location {ConfigurationPath}"); + } + } + + // Check if graphics backend was overridden. + if (CommandLineState.OverrideGraphicsBackend != null) + { + if (CommandLineState.OverrideGraphicsBackend.ToLower() == "opengl") + { + ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.OpenGl; + } + else if (CommandLineState.OverrideGraphicsBackend.ToLower() == "vulkan") + { + ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.Vulkan; + } + } + + // Check if HideCursor was overridden. + if (CommandLineState.OverrideHideCursor is not null) + { + ConfigurationState.Instance.HideCursor.Value = CommandLineState.OverrideHideCursor!.ToLower() switch + { + "never" => HideCursorMode.Never, + "onidle" => HideCursorMode.OnIdle, + "always" => HideCursorMode.Always, + _ => ConfigurationState.Instance.HideCursor.Value, + }; + } + + // Check if docked mode was overridden. + if (CommandLineState.OverrideDockedMode.HasValue) + { + ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value; + } + + // Logging system information. + PrintSystemInfo(); + + // Enable OGL multithreading on the driver, when available. + BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading; + DriverUtilities.ToggleOGLThreading(threadingMode == BackendThreading.Off); + + // Initialize Gtk. + Application.Init(); + + // Check if keys exists. + bool hasSystemProdKeys = File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")); + bool hasCommonProdKeys = AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys")); + if (!hasSystemProdKeys && !hasCommonProdKeys) + { + UserErrorDialog.CreateUserErrorDialog(UserError.NoKeys); + } + + // Show the main window UI. + MainWindow mainWindow = new(); + mainWindow.Show(); + + if (OperatingSystem.IsLinux()) + { + int currentVmMaxMapCount = LinuxHelper.VmMaxMapCount; + + if (LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount) + { + Logger.Warning?.Print(LogClass.Application, $"The value of vm.max_map_count is lower than {LinuxHelper.RecommendedVmMaxMapCount}. ({currentVmMaxMapCount})"); + + if (LinuxHelper.PkExecPath is not null) + { + var buttonTexts = new Dictionary() + { + { 0, "Yes, until the next restart" }, + { 1, "Yes, permanently" }, + { 2, "No" }, + }; + + ResponseType response = GtkDialog.CreateCustomDialog( + "Ryujinx - Low limit for memory mappings detected", + $"Would you like to increase the value of vm.max_map_count to {LinuxHelper.RecommendedVmMaxMapCount}?", + "Some games might try to create more memory mappings than currently allowed. " + + "Ryujinx will crash as soon as this limit gets exceeded.", + buttonTexts, + MessageType.Question); + + int rc; + + switch ((int)response) + { + case 0: + rc = LinuxHelper.RunPkExec($"echo {LinuxHelper.RecommendedVmMaxMapCount} > {LinuxHelper.VmMaxMapCountPath}"); + if (rc == 0) + { + Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount} until the next restart."); + } + else + { + Logger.Error?.Print(LogClass.Application, $"Unable to change vm.max_map_count. Process exited with code: {rc}"); + } + break; + case 1: + rc = LinuxHelper.RunPkExec($"echo \"vm.max_map_count = {LinuxHelper.RecommendedVmMaxMapCount}\" > {LinuxHelper.SysCtlConfigPath} && sysctl -p {LinuxHelper.SysCtlConfigPath}"); + if (rc == 0) + { + Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount}. Written to config: {LinuxHelper.SysCtlConfigPath}"); + } + else + { + Logger.Error?.Print(LogClass.Application, $"Unable to write new value for vm.max_map_count to config. Process exited with code: {rc}"); + } + break; + } + } + else + { + GtkDialog.CreateWarningDialog( + "Max amount of memory mappings is lower than recommended.", + $"The current value of vm.max_map_count ({currentVmMaxMapCount}) is lower than {LinuxHelper.RecommendedVmMaxMapCount}." + + "Some games might try to create more memory mappings than currently allowed. " + + "Ryujinx will crash as soon as this limit gets exceeded.\n\n" + + "You might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that."); + } + } + } + + if (CommandLineState.LaunchPathArg != null) + { + mainWindow.RunApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg); + } + + if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false)) + { + Updater.BeginParse(mainWindow, false).ContinueWith(task => + { + Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}"); + }, TaskContinuationOptions.OnlyOnFaulted); + } + + Application.Run(); + } + + private static void PrintSystemInfo() + { + Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}"); + SystemInfo.Gather().Print(); + + var enabledLogs = Logger.GetEnabledLevels(); + Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(enabledLogs.Count == 0 ? "" : string.Join(", ", enabledLogs))}"); + + if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom) + { + Logger.Notice.Print(LogClass.Application, $"Launch Mode: Custom Path {AppDataManager.BaseDirPath}"); + } + else + { + Logger.Notice.Print(LogClass.Application, $"Launch Mode: {AppDataManager.Mode}"); + } + } + + private static void ProcessUnhandledException(Exception ex, bool isTerminating) + { + string message = $"Unhandled exception caught: {ex}"; + + Logger.Error?.PrintMsg(LogClass.Application, message); + + if (Logger.Error == null) + { + Logger.Notice.PrintMsg(LogClass.Application, message); + } + + if (isTerminating) + { + Exit(); + } + } + + public static void Exit() + { + DiscordIntegrationModule.Exit(); + + Logger.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj b/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj new file mode 100644 index 000000000..68bf98981 --- /dev/null +++ b/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj @@ -0,0 +1,104 @@ + + + + net8.0 + win-x64;osx-x64;linux-x64 + Exe + true + 1.0.0-dirty + $(DefineConstants);$(ExtraDefineConstants) + + true + true + + + + true + false + true + partial + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + alsoft.ini + + + Always + THIRDPARTY.md + + + Always + LICENSE.txt + + + + + + Always + + + Always + mime\Ryujinx.xml + + + + + + false + Ryujinx.ico + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx.Ava/Ryujinx.ico b/src/Ryujinx.Gtk3/Ryujinx.ico similarity index 100% rename from src/Ryujinx.Ava/Ryujinx.ico rename to src/Ryujinx.Gtk3/Ryujinx.ico diff --git a/src/Ryujinx/UI/Applet/ErrorAppletDialog.cs b/src/Ryujinx.Gtk3/UI/Applet/ErrorAppletDialog.cs similarity index 91% rename from src/Ryujinx/UI/Applet/ErrorAppletDialog.cs rename to src/Ryujinx.Gtk3/UI/Applet/ErrorAppletDialog.cs index 7f8cc0e95..cb8103cae 100644 --- a/src/Ryujinx/UI/Applet/ErrorAppletDialog.cs +++ b/src/Ryujinx.Gtk3/UI/Applet/ErrorAppletDialog.cs @@ -8,7 +8,7 @@ internal class ErrorAppletDialog : MessageDialog { public ErrorAppletDialog(Window parentWindow, DialogFlags dialogFlags, MessageType messageType, string[] buttons) : base(parentWindow, dialogFlags, messageType, ButtonsType.None, null) { - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Gtk3.UI.Common.Resources.Logo_Ryujinx.png"); int responseId = 0; diff --git a/src/Ryujinx/UI/Applet/GtkDynamicTextInputHandler.cs b/src/Ryujinx.Gtk3/UI/Applet/GtkDynamicTextInputHandler.cs similarity index 100% rename from src/Ryujinx/UI/Applet/GtkDynamicTextInputHandler.cs rename to src/Ryujinx.Gtk3/UI/Applet/GtkDynamicTextInputHandler.cs diff --git a/src/Ryujinx/UI/Applet/GtkHostUIHandler.cs b/src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs similarity index 100% rename from src/Ryujinx/UI/Applet/GtkHostUIHandler.cs rename to src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs diff --git a/src/Ryujinx/UI/Applet/GtkHostUITheme.cs b/src/Ryujinx.Gtk3/UI/Applet/GtkHostUITheme.cs similarity index 100% rename from src/Ryujinx/UI/Applet/GtkHostUITheme.cs rename to src/Ryujinx.Gtk3/UI/Applet/GtkHostUITheme.cs diff --git a/src/Ryujinx/UI/Applet/SwkbdAppletDialog.cs b/src/Ryujinx.Gtk3/UI/Applet/SwkbdAppletDialog.cs similarity index 100% rename from src/Ryujinx/UI/Applet/SwkbdAppletDialog.cs rename to src/Ryujinx.Gtk3/UI/Applet/SwkbdAppletDialog.cs diff --git a/src/Ryujinx/UI/Helper/MetalHelper.cs b/src/Ryujinx.Gtk3/UI/Helper/MetalHelper.cs similarity index 100% rename from src/Ryujinx/UI/Helper/MetalHelper.cs rename to src/Ryujinx.Gtk3/UI/Helper/MetalHelper.cs diff --git a/src/Ryujinx/UI/Helper/SortHelper.cs b/src/Ryujinx.Gtk3/UI/Helper/SortHelper.cs similarity index 100% rename from src/Ryujinx/UI/Helper/SortHelper.cs rename to src/Ryujinx.Gtk3/UI/Helper/SortHelper.cs diff --git a/src/Ryujinx/UI/Helper/ThemeHelper.cs b/src/Ryujinx.Gtk3/UI/Helper/ThemeHelper.cs similarity index 100% rename from src/Ryujinx/UI/Helper/ThemeHelper.cs rename to src/Ryujinx.Gtk3/UI/Helper/ThemeHelper.cs diff --git a/src/Ryujinx/UI/MainWindow.cs b/src/Ryujinx.Gtk3/UI/MainWindow.cs similarity index 99% rename from src/Ryujinx/UI/MainWindow.cs rename to src/Ryujinx.Gtk3/UI/MainWindow.cs index 2908f1a8d..d1ca6ce6a 100644 --- a/src/Ryujinx/UI/MainWindow.cs +++ b/src/Ryujinx.Gtk3/UI/MainWindow.cs @@ -143,7 +143,7 @@ public class MainWindow : Window #pragma warning restore CS0649, IDE0044, CS0169, IDE0051 - public MainWindow() : this(new Builder("Ryujinx.UI.MainWindow.glade")) { } + public MainWindow() : this(new Builder("Ryujinx.Gtk3.UI.MainWindow.glade")) { } private MainWindow(Builder builder) : base(builder.GetRawOwnedObject("_mainWin")) { diff --git a/src/Ryujinx/UI/MainWindow.glade b/src/Ryujinx.Gtk3/UI/MainWindow.glade similarity index 100% rename from src/Ryujinx/UI/MainWindow.glade rename to src/Ryujinx.Gtk3/UI/MainWindow.glade diff --git a/src/Ryujinx/UI/OpenGLRenderer.cs b/src/Ryujinx.Gtk3/UI/OpenGLRenderer.cs similarity index 100% rename from src/Ryujinx/UI/OpenGLRenderer.cs rename to src/Ryujinx.Gtk3/UI/OpenGLRenderer.cs diff --git a/src/Ryujinx/UI/OpenToolkitBindingsContext.cs b/src/Ryujinx.Gtk3/UI/OpenToolkitBindingsContext.cs similarity index 100% rename from src/Ryujinx/UI/OpenToolkitBindingsContext.cs rename to src/Ryujinx.Gtk3/UI/OpenToolkitBindingsContext.cs diff --git a/src/Ryujinx/UI/RendererWidgetBase.cs b/src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs similarity index 100% rename from src/Ryujinx/UI/RendererWidgetBase.cs rename to src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs diff --git a/src/Ryujinx/UI/SPBOpenGLContext.cs b/src/Ryujinx.Gtk3/UI/SPBOpenGLContext.cs similarity index 100% rename from src/Ryujinx/UI/SPBOpenGLContext.cs rename to src/Ryujinx.Gtk3/UI/SPBOpenGLContext.cs diff --git a/src/Ryujinx/UI/StatusUpdatedEventArgs.cs b/src/Ryujinx.Gtk3/UI/StatusUpdatedEventArgs.cs similarity index 100% rename from src/Ryujinx/UI/StatusUpdatedEventArgs.cs rename to src/Ryujinx.Gtk3/UI/StatusUpdatedEventArgs.cs diff --git a/src/Ryujinx/UI/VulkanRenderer.cs b/src/Ryujinx.Gtk3/UI/VulkanRenderer.cs similarity index 100% rename from src/Ryujinx/UI/VulkanRenderer.cs rename to src/Ryujinx.Gtk3/UI/VulkanRenderer.cs diff --git a/src/Ryujinx/UI/Widgets/GameTableContextMenu.Designer.cs b/src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.Designer.cs similarity index 100% rename from src/Ryujinx/UI/Widgets/GameTableContextMenu.Designer.cs rename to src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.Designer.cs diff --git a/src/Ryujinx/UI/Widgets/GameTableContextMenu.cs b/src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.cs similarity index 99% rename from src/Ryujinx/UI/Widgets/GameTableContextMenu.cs rename to src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.cs index dc0dd4c50..c8236223a 100644 --- a/src/Ryujinx/UI/Widgets/GameTableContextMenu.cs +++ b/src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.cs @@ -189,7 +189,7 @@ private void ExtractSection(NcaSectionType ncaSectionType, int programIndex = 0) _dialog = new MessageDialog(null, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Cancel, null) { Title = "Ryujinx - NCA Section Extractor", - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"), + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Gtk3.UI.Common.Resources.Logo_Ryujinx.png"), SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(_titleFilePath)}...", WindowPosition = WindowPosition.Center, }; diff --git a/src/Ryujinx/UI/Widgets/GtkDialog.cs b/src/Ryujinx.Gtk3/UI/Widgets/GtkDialog.cs similarity index 100% rename from src/Ryujinx/UI/Widgets/GtkDialog.cs rename to src/Ryujinx.Gtk3/UI/Widgets/GtkDialog.cs diff --git a/src/Ryujinx/UI/Widgets/GtkInputDialog.cs b/src/Ryujinx.Gtk3/UI/Widgets/GtkInputDialog.cs similarity index 100% rename from src/Ryujinx/UI/Widgets/GtkInputDialog.cs rename to src/Ryujinx.Gtk3/UI/Widgets/GtkInputDialog.cs diff --git a/src/Ryujinx/UI/Widgets/ProfileDialog.cs b/src/Ryujinx.Gtk3/UI/Widgets/ProfileDialog.cs similarity index 94% rename from src/Ryujinx/UI/Widgets/ProfileDialog.cs rename to src/Ryujinx.Gtk3/UI/Widgets/ProfileDialog.cs index f8aa6345f..3b3e2fbbe 100644 --- a/src/Ryujinx/UI/Widgets/ProfileDialog.cs +++ b/src/Ryujinx.Gtk3/UI/Widgets/ProfileDialog.cs @@ -15,7 +15,7 @@ public class ProfileDialog : Dialog [GUI] Label _errorMessage; #pragma warning restore CS0649, IDE0044 - public ProfileDialog() : this(new Builder("Ryujinx.UI.Widgets.ProfileDialog.glade")) { } + public ProfileDialog() : this(new Builder("Ryujinx.Gtk3.UI.Widgets.ProfileDialog.glade")) { } private ProfileDialog(Builder builder) : base(builder.GetRawOwnedObject("_profileDialog")) { diff --git a/src/Ryujinx/UI/Widgets/ProfileDialog.glade b/src/Ryujinx.Gtk3/UI/Widgets/ProfileDialog.glade similarity index 100% rename from src/Ryujinx/UI/Widgets/ProfileDialog.glade rename to src/Ryujinx.Gtk3/UI/Widgets/ProfileDialog.glade diff --git a/src/Ryujinx/UI/Widgets/RawInputToTextEntry.cs b/src/Ryujinx.Gtk3/UI/Widgets/RawInputToTextEntry.cs similarity index 100% rename from src/Ryujinx/UI/Widgets/RawInputToTextEntry.cs rename to src/Ryujinx.Gtk3/UI/Widgets/RawInputToTextEntry.cs diff --git a/src/Ryujinx/UI/Widgets/UserErrorDialog.cs b/src/Ryujinx.Gtk3/UI/Widgets/UserErrorDialog.cs similarity index 100% rename from src/Ryujinx/UI/Widgets/UserErrorDialog.cs rename to src/Ryujinx.Gtk3/UI/Widgets/UserErrorDialog.cs diff --git a/src/Ryujinx/UI/Windows/AboutWindow.Designer.cs b/src/Ryujinx.Gtk3/UI/Windows/AboutWindow.Designer.cs similarity index 100% rename from src/Ryujinx/UI/Windows/AboutWindow.Designer.cs rename to src/Ryujinx.Gtk3/UI/Windows/AboutWindow.Designer.cs diff --git a/src/Ryujinx/UI/Windows/AboutWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/AboutWindow.cs similarity index 100% rename from src/Ryujinx/UI/Windows/AboutWindow.cs rename to src/Ryujinx.Gtk3/UI/Windows/AboutWindow.cs diff --git a/src/Ryujinx/UI/Windows/AmiiboWindow.Designer.cs b/src/Ryujinx.Gtk3/UI/Windows/AmiiboWindow.Designer.cs similarity index 100% rename from src/Ryujinx/UI/Windows/AmiiboWindow.Designer.cs rename to src/Ryujinx.Gtk3/UI/Windows/AmiiboWindow.Designer.cs diff --git a/src/Ryujinx/UI/Windows/AmiiboWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/AmiiboWindow.cs similarity index 100% rename from src/Ryujinx/UI/Windows/AmiiboWindow.cs rename to src/Ryujinx.Gtk3/UI/Windows/AmiiboWindow.cs diff --git a/src/Ryujinx/UI/Windows/AvatarWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs similarity index 100% rename from src/Ryujinx/UI/Windows/AvatarWindow.cs rename to src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs diff --git a/src/Ryujinx/UI/Windows/CheatWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/CheatWindow.cs similarity index 98% rename from src/Ryujinx/UI/Windows/CheatWindow.cs rename to src/Ryujinx.Gtk3/UI/Windows/CheatWindow.cs index 73ee870cd..96ed0723e 100644 --- a/src/Ryujinx/UI/Windows/CheatWindow.cs +++ b/src/Ryujinx.Gtk3/UI/Windows/CheatWindow.cs @@ -22,7 +22,7 @@ public class CheatWindow : Window [GUI] Button _saveButton; #pragma warning restore CS0649, IDE0044 - public CheatWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : this(new Builder("Ryujinx.UI.Windows.CheatWindow.glade"), virtualFileSystem, titleId, titleName, titlePath) { } + public CheatWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : this(new Builder("Ryujinx.Gtk3.UI.Windows.CheatWindow.glade"), virtualFileSystem, titleId, titleName, titlePath) { } private CheatWindow(Builder builder, VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : base(builder.GetRawOwnedObject("_cheatWindow")) { diff --git a/src/Ryujinx/UI/Windows/CheatWindow.glade b/src/Ryujinx.Gtk3/UI/Windows/CheatWindow.glade similarity index 100% rename from src/Ryujinx/UI/Windows/CheatWindow.glade rename to src/Ryujinx.Gtk3/UI/Windows/CheatWindow.glade diff --git a/src/Ryujinx/UI/Windows/ControllerWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.cs similarity index 99% rename from src/Ryujinx/UI/Windows/ControllerWindow.cs rename to src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.cs index 954113443..6712657f8 100644 --- a/src/Ryujinx/UI/Windows/ControllerWindow.cs +++ b/src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.cs @@ -117,7 +117,7 @@ public class ControllerWindow : Window private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - public ControllerWindow(MainWindow mainWindow, PlayerIndex controllerId) : this(mainWindow, new Builder("Ryujinx.UI.Windows.ControllerWindow.glade"), controllerId) { } + public ControllerWindow(MainWindow mainWindow, PlayerIndex controllerId) : this(mainWindow, new Builder("Ryujinx.Gtk3.UI.Windows.ControllerWindow.glade"), controllerId) { } private ControllerWindow(MainWindow mainWindow, Builder builder, PlayerIndex controllerId) : base(builder.GetRawOwnedObject("_controllerWin")) { diff --git a/src/Ryujinx/UI/Windows/ControllerWindow.glade b/src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.glade similarity index 100% rename from src/Ryujinx/UI/Windows/ControllerWindow.glade rename to src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.glade diff --git a/src/Ryujinx/UI/Windows/DlcWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/DlcWindow.cs similarity index 98% rename from src/Ryujinx/UI/Windows/DlcWindow.cs rename to src/Ryujinx.Gtk3/UI/Windows/DlcWindow.cs index aed1a015d..388f11089 100644 --- a/src/Ryujinx/UI/Windows/DlcWindow.cs +++ b/src/Ryujinx.Gtk3/UI/Windows/DlcWindow.cs @@ -32,7 +32,7 @@ public class DlcWindow : Window [GUI] TreeSelection _dlcTreeSelection; #pragma warning restore CS0649, IDE0044 - public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.UI.Windows.DlcWindow.glade"), virtualFileSystem, titleId, titleName) { } + public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Gtk3.UI.Windows.DlcWindow.glade"), virtualFileSystem, titleId, titleName) { } private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_dlcWindow")) { diff --git a/src/Ryujinx/UI/Windows/DlcWindow.glade b/src/Ryujinx.Gtk3/UI/Windows/DlcWindow.glade similarity index 100% rename from src/Ryujinx/UI/Windows/DlcWindow.glade rename to src/Ryujinx.Gtk3/UI/Windows/DlcWindow.glade diff --git a/src/Ryujinx/UI/Windows/SettingsWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs similarity index 99% rename from src/Ryujinx/UI/Windows/SettingsWindow.cs rename to src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs index 270a8ad4a..dc467c0f2 100644 --- a/src/Ryujinx/UI/Windows/SettingsWindow.cs +++ b/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs @@ -120,7 +120,7 @@ public class SettingsWindow : Window #pragma warning restore CS0649, IDE0044 - public SettingsWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(parent, new Builder("Ryujinx.UI.Windows.SettingsWindow.glade"), virtualFileSystem, contentManager) { } + public SettingsWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(parent, new Builder("Ryujinx.Gtk3.UI.Windows.SettingsWindow.glade"), virtualFileSystem, contentManager) { } private SettingsWindow(MainWindow parent, Builder builder, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : base(builder.GetRawOwnedObject("_settingsWin")) { diff --git a/src/Ryujinx/UI/Windows/SettingsWindow.glade b/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.glade similarity index 100% rename from src/Ryujinx/UI/Windows/SettingsWindow.glade rename to src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.glade diff --git a/src/Ryujinx/UI/Windows/TitleUpdateWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.cs similarity index 98% rename from src/Ryujinx/UI/Windows/TitleUpdateWindow.cs rename to src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.cs index 1df929336..74b2330ee 100644 --- a/src/Ryujinx/UI/Windows/TitleUpdateWindow.cs +++ b/src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.cs @@ -38,7 +38,7 @@ public class TitleUpdateWindow : Window [GUI] RadioButton _noUpdateRadioButton; #pragma warning restore CS0649, IDE0044 - public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.UI.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, titleId, titleName) { } + public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Gtk3.UI.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, titleId, titleName) { } private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_titleUpdateWindow")) { diff --git a/src/Ryujinx/UI/Windows/TitleUpdateWindow.glade b/src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.glade similarity index 100% rename from src/Ryujinx/UI/Windows/TitleUpdateWindow.glade rename to src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.glade diff --git a/src/Ryujinx/UI/Windows/UserProfilesManagerWindow.Designer.cs b/src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.Designer.cs similarity index 100% rename from src/Ryujinx/UI/Windows/UserProfilesManagerWindow.Designer.cs rename to src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.Designer.cs diff --git a/src/Ryujinx/UI/Windows/UserProfilesManagerWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.cs similarity index 100% rename from src/Ryujinx/UI/Windows/UserProfilesManagerWindow.cs rename to src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.cs diff --git a/src/Ryujinx.Ava/App.axaml b/src/Ryujinx/App.axaml similarity index 100% rename from src/Ryujinx.Ava/App.axaml rename to src/Ryujinx/App.axaml diff --git a/src/Ryujinx.Ava/App.axaml.cs b/src/Ryujinx/App.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/App.axaml.cs rename to src/Ryujinx/App.axaml.cs diff --git a/src/Ryujinx.Ava/AppHost.cs b/src/Ryujinx/AppHost.cs similarity index 100% rename from src/Ryujinx.Ava/AppHost.cs rename to src/Ryujinx/AppHost.cs diff --git a/src/Ryujinx.Ava/Assets/Fonts/SegoeFluentIcons.ttf b/src/Ryujinx/Assets/Fonts/SegoeFluentIcons.ttf similarity index 100% rename from src/Ryujinx.Ava/Assets/Fonts/SegoeFluentIcons.ttf rename to src/Ryujinx/Assets/Fonts/SegoeFluentIcons.ttf diff --git a/src/Ryujinx.Ava/Assets/Icons/Controller_JoyConLeft.svg b/src/Ryujinx/Assets/Icons/Controller_JoyConLeft.svg similarity index 100% rename from src/Ryujinx.Ava/Assets/Icons/Controller_JoyConLeft.svg rename to src/Ryujinx/Assets/Icons/Controller_JoyConLeft.svg diff --git a/src/Ryujinx.Ava/Assets/Icons/Controller_JoyConPair.svg b/src/Ryujinx/Assets/Icons/Controller_JoyConPair.svg similarity index 100% rename from src/Ryujinx.Ava/Assets/Icons/Controller_JoyConPair.svg rename to src/Ryujinx/Assets/Icons/Controller_JoyConPair.svg diff --git a/src/Ryujinx.Ava/Assets/Icons/Controller_JoyConRight.svg b/src/Ryujinx/Assets/Icons/Controller_JoyConRight.svg similarity index 100% rename from src/Ryujinx.Ava/Assets/Icons/Controller_JoyConRight.svg rename to src/Ryujinx/Assets/Icons/Controller_JoyConRight.svg diff --git a/src/Ryujinx.Ava/Assets/Icons/Controller_ProCon.svg b/src/Ryujinx/Assets/Icons/Controller_ProCon.svg similarity index 100% rename from src/Ryujinx.Ava/Assets/Icons/Controller_ProCon.svg rename to src/Ryujinx/Assets/Icons/Controller_ProCon.svg diff --git a/src/Ryujinx.Ava/Assets/Locales/de_DE.json b/src/Ryujinx/Assets/Locales/de_DE.json similarity index 100% rename from src/Ryujinx.Ava/Assets/Locales/de_DE.json rename to src/Ryujinx/Assets/Locales/de_DE.json diff --git a/src/Ryujinx.Ava/Assets/Locales/el_GR.json b/src/Ryujinx/Assets/Locales/el_GR.json similarity index 100% rename from src/Ryujinx.Ava/Assets/Locales/el_GR.json rename to src/Ryujinx/Assets/Locales/el_GR.json diff --git a/src/Ryujinx.Ava/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json similarity index 100% rename from src/Ryujinx.Ava/Assets/Locales/en_US.json rename to src/Ryujinx/Assets/Locales/en_US.json diff --git a/src/Ryujinx.Ava/Assets/Locales/es_ES.json b/src/Ryujinx/Assets/Locales/es_ES.json similarity index 100% rename from src/Ryujinx.Ava/Assets/Locales/es_ES.json rename to src/Ryujinx/Assets/Locales/es_ES.json diff --git a/src/Ryujinx.Ava/Assets/Locales/fr_FR.json b/src/Ryujinx/Assets/Locales/fr_FR.json similarity index 100% rename from src/Ryujinx.Ava/Assets/Locales/fr_FR.json rename to src/Ryujinx/Assets/Locales/fr_FR.json diff --git a/src/Ryujinx.Ava/Assets/Locales/he_IL.json b/src/Ryujinx/Assets/Locales/he_IL.json similarity index 100% rename from src/Ryujinx.Ava/Assets/Locales/he_IL.json rename to src/Ryujinx/Assets/Locales/he_IL.json diff --git a/src/Ryujinx.Ava/Assets/Locales/it_IT.json b/src/Ryujinx/Assets/Locales/it_IT.json similarity index 100% rename from src/Ryujinx.Ava/Assets/Locales/it_IT.json rename to src/Ryujinx/Assets/Locales/it_IT.json diff --git a/src/Ryujinx.Ava/Assets/Locales/ja_JP.json b/src/Ryujinx/Assets/Locales/ja_JP.json similarity index 100% rename from src/Ryujinx.Ava/Assets/Locales/ja_JP.json rename to src/Ryujinx/Assets/Locales/ja_JP.json diff --git a/src/Ryujinx.Ava/Assets/Locales/ko_KR.json b/src/Ryujinx/Assets/Locales/ko_KR.json similarity index 100% rename from src/Ryujinx.Ava/Assets/Locales/ko_KR.json rename to src/Ryujinx/Assets/Locales/ko_KR.json diff --git a/src/Ryujinx.Ava/Assets/Locales/pl_PL.json b/src/Ryujinx/Assets/Locales/pl_PL.json similarity index 100% rename from src/Ryujinx.Ava/Assets/Locales/pl_PL.json rename to src/Ryujinx/Assets/Locales/pl_PL.json diff --git a/src/Ryujinx.Ava/Assets/Locales/pt_BR.json b/src/Ryujinx/Assets/Locales/pt_BR.json similarity index 100% rename from src/Ryujinx.Ava/Assets/Locales/pt_BR.json rename to src/Ryujinx/Assets/Locales/pt_BR.json diff --git a/src/Ryujinx.Ava/Assets/Locales/ru_RU.json b/src/Ryujinx/Assets/Locales/ru_RU.json similarity index 100% rename from src/Ryujinx.Ava/Assets/Locales/ru_RU.json rename to src/Ryujinx/Assets/Locales/ru_RU.json diff --git a/src/Ryujinx.Ava/Assets/Locales/tr_TR.json b/src/Ryujinx/Assets/Locales/tr_TR.json similarity index 100% rename from src/Ryujinx.Ava/Assets/Locales/tr_TR.json rename to src/Ryujinx/Assets/Locales/tr_TR.json diff --git a/src/Ryujinx.Ava/Assets/Locales/uk_UA.json b/src/Ryujinx/Assets/Locales/uk_UA.json similarity index 100% rename from src/Ryujinx.Ava/Assets/Locales/uk_UA.json rename to src/Ryujinx/Assets/Locales/uk_UA.json diff --git a/src/Ryujinx.Ava/Assets/Locales/zh_CN.json b/src/Ryujinx/Assets/Locales/zh_CN.json similarity index 100% rename from src/Ryujinx.Ava/Assets/Locales/zh_CN.json rename to src/Ryujinx/Assets/Locales/zh_CN.json diff --git a/src/Ryujinx.Ava/Assets/Locales/zh_TW.json b/src/Ryujinx/Assets/Locales/zh_TW.json similarity index 100% rename from src/Ryujinx.Ava/Assets/Locales/zh_TW.json rename to src/Ryujinx/Assets/Locales/zh_TW.json diff --git a/src/Ryujinx.Ava/Assets/Styles/Styles.xaml b/src/Ryujinx/Assets/Styles/Styles.xaml similarity index 100% rename from src/Ryujinx.Ava/Assets/Styles/Styles.xaml rename to src/Ryujinx/Assets/Styles/Styles.xaml diff --git a/src/Ryujinx.Ava/Assets/Styles/Themes.xaml b/src/Ryujinx/Assets/Styles/Themes.xaml similarity index 100% rename from src/Ryujinx.Ava/Assets/Styles/Themes.xaml rename to src/Ryujinx/Assets/Styles/Themes.xaml diff --git a/src/Ryujinx.Ava/Common/ApplicationHelper.cs b/src/Ryujinx/Common/ApplicationHelper.cs similarity index 100% rename from src/Ryujinx.Ava/Common/ApplicationHelper.cs rename to src/Ryujinx/Common/ApplicationHelper.cs diff --git a/src/Ryujinx.Ava/Common/ApplicationSort.cs b/src/Ryujinx/Common/ApplicationSort.cs similarity index 100% rename from src/Ryujinx.Ava/Common/ApplicationSort.cs rename to src/Ryujinx/Common/ApplicationSort.cs diff --git a/src/Ryujinx.Ava/Common/KeyboardHotkeyState.cs b/src/Ryujinx/Common/KeyboardHotkeyState.cs similarity index 100% rename from src/Ryujinx.Ava/Common/KeyboardHotkeyState.cs rename to src/Ryujinx/Common/KeyboardHotkeyState.cs diff --git a/src/Ryujinx.Ava/Common/Locale/LocaleExtension.cs b/src/Ryujinx/Common/Locale/LocaleExtension.cs similarity index 100% rename from src/Ryujinx.Ava/Common/Locale/LocaleExtension.cs rename to src/Ryujinx/Common/Locale/LocaleExtension.cs diff --git a/src/Ryujinx.Ava/Common/Locale/LocaleManager.cs b/src/Ryujinx/Common/Locale/LocaleManager.cs similarity index 99% rename from src/Ryujinx.Ava/Common/Locale/LocaleManager.cs rename to src/Ryujinx/Common/Locale/LocaleManager.cs index b2f3e7ab9..e973fcc06 100644 --- a/src/Ryujinx.Ava/Common/Locale/LocaleManager.cs +++ b/src/Ryujinx/Common/Locale/LocaleManager.cs @@ -143,7 +143,7 @@ public void LoadLanguage(string languageCode) private static Dictionary LoadJsonLanguage(string languageCode = DefaultLanguageCode) { var localeStrings = new Dictionary(); - string languageJson = EmbeddedResources.ReadAllText($"Ryujinx.Ava/Assets/Locales/{languageCode}.json"); + string languageJson = EmbeddedResources.ReadAllText($"Ryujinx/Assets/Locales/{languageCode}.json"); var strings = JsonHelper.Deserialize(languageJson, CommonJsonContext.Default.StringDictionary); foreach (var item in strings) diff --git a/src/Ryujinx.Ava/Input/AvaloniaKeyboard.cs b/src/Ryujinx/Input/AvaloniaKeyboard.cs similarity index 100% rename from src/Ryujinx.Ava/Input/AvaloniaKeyboard.cs rename to src/Ryujinx/Input/AvaloniaKeyboard.cs diff --git a/src/Ryujinx.Ava/Input/AvaloniaKeyboardDriver.cs b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs similarity index 100% rename from src/Ryujinx.Ava/Input/AvaloniaKeyboardDriver.cs rename to src/Ryujinx/Input/AvaloniaKeyboardDriver.cs diff --git a/src/Ryujinx.Ava/Input/AvaloniaKeyboardMappingHelper.cs b/src/Ryujinx/Input/AvaloniaKeyboardMappingHelper.cs similarity index 100% rename from src/Ryujinx.Ava/Input/AvaloniaKeyboardMappingHelper.cs rename to src/Ryujinx/Input/AvaloniaKeyboardMappingHelper.cs diff --git a/src/Ryujinx.Ava/Input/AvaloniaMouse.cs b/src/Ryujinx/Input/AvaloniaMouse.cs similarity index 100% rename from src/Ryujinx.Ava/Input/AvaloniaMouse.cs rename to src/Ryujinx/Input/AvaloniaMouse.cs diff --git a/src/Ryujinx.Ava/Input/AvaloniaMouseDriver.cs b/src/Ryujinx/Input/AvaloniaMouseDriver.cs similarity index 100% rename from src/Ryujinx.Ava/Input/AvaloniaMouseDriver.cs rename to src/Ryujinx/Input/AvaloniaMouseDriver.cs diff --git a/src/Ryujinx/Modules/Updater/Updater.cs b/src/Ryujinx/Modules/Updater/Updater.cs index 6c0f9ccea..d8346c8eb 100644 --- a/src/Ryujinx/Modules/Updater/Updater.cs +++ b/src/Ryujinx/Modules/Updater/Updater.cs @@ -1,93 +1,75 @@ -using Gtk; +using Avalonia.Controls; +using Avalonia.Threading; +using FluentAvalonia.UI.Controls; using ICSharpCode.SharpZipLib.GZip; using ICSharpCode.SharpZipLib.Tar; using ICSharpCode.SharpZipLib.Zip; +using Ryujinx.Ava; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.Common.Utilities; -using Ryujinx.UI; +using Ryujinx.UI.Common.Helper; using Ryujinx.UI.Common.Models.Github; -using Ryujinx.UI.Widgets; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Net.NetworkInformation; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Ryujinx.Modules { - public static class Updater + internal static class Updater { private const string GitHubApiUrl = "https://api.github.com"; - private const int ConnectionCount = 4; - - internal static bool Running; + private static readonly GithubReleasesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory; private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update"); private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish"); + private const int ConnectionCount = 4; private static string _buildVer; private static string _platformExt; private static string _buildUrl; private static long _buildSize; + private static bool _updateSuccessful; + private static bool _running; - private static readonly GithubReleasesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - - // On Windows, GtkSharp.Dependencies adds these extra dirs that must be cleaned during updates. - private static readonly string[] _windowsDependencyDirs = { "bin", "etc", "lib", "share" }; + private static readonly string[] _windowsDependencyDirs = Array.Empty(); - private static HttpClient ConstructHttpClient() + public static async Task BeginParse(Window mainWindow, bool showVersionUpToDate) { - HttpClient result = new(); - - // Required by GitHub to interact with APIs. - result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0"); - - return result; - } - - public static async Task BeginParse(MainWindow mainWindow, bool showVersionUpToDate) - { - if (Running) + if (_running) { return; } - Running = true; - mainWindow.UpdateMenuItem.Sensitive = false; - - int artifactIndex = -1; + _running = true; // Detect current platform if (OperatingSystem.IsMacOS()) { - _platformExt = "osx_x64.zip"; - artifactIndex = 1; + _platformExt = "macos_universal.app.tar.gz"; } else if (OperatingSystem.IsWindows()) { _platformExt = "win_x64.zip"; - artifactIndex = 2; } else if (OperatingSystem.IsLinux()) { var arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "arm64" : "x64"; _platformExt = $"linux_{arch}.tar.gz"; - artifactIndex = 0; - } - - if (artifactIndex == -1) - { - GtkDialog.CreateErrorDialog("Your platform is not supported!"); - - return; } Version newVersion; @@ -99,9 +81,14 @@ public static async Task BeginParse(MainWindow mainWindow, bool showVersionUpToD } catch { - GtkDialog.CreateWarningDialog("Failed to convert the current Ryujinx version.", "Cancelling Update!"); Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!"); + await ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage], + LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); + + _running = false; + return; } @@ -109,9 +96,8 @@ public static async Task BeginParse(MainWindow mainWindow, bool showVersionUpToD try { using HttpClient jsonClient = ConstructHttpClient(); - string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest"; - // Fetch latest build information + string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest"; string fetchedJson = await jsonClient.GetStringAsync(buildInfoUrl); var fetched = JsonHelper.Deserialize(fetchedJson, _serializerContext.GithubReleasesJsonResponse); _buildVer = fetched.Name; @@ -126,9 +112,13 @@ public static async Task BeginParse(MainWindow mainWindow, bool showVersionUpToD { if (showVersionUpToDate) { - GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", ""); + await ContentDialogHelper.CreateUpdaterInfoDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], + ""); } + _running = false; + return; } @@ -136,20 +126,29 @@ public static async Task BeginParse(MainWindow mainWindow, bool showVersionUpToD } } - if (_buildUrl == null) + // If build not done, assume no new update are available. + if (_buildUrl is null) { if (showVersionUpToDate) { - GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", ""); + await ContentDialogHelper.CreateUpdaterInfoDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], + ""); } + _running = false; + return; } } catch (Exception exception) { Logger.Error?.Print(LogClass.Application, exception.Message); - GtkDialog.CreateErrorDialog("An error occurred when trying to get release information from GitHub Release. This can be caused if a new release is being compiled by GitHub Actions. Try again in a few minutes."); + + await ContentDialogHelper.CreateErrorDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]); + + _running = false; return; } @@ -160,8 +159,13 @@ public static async Task BeginParse(MainWindow mainWindow, bool showVersionUpToD } catch { - GtkDialog.CreateWarningDialog("Failed to convert the received Ryujinx version from GitHub Release.", "Cancelling Update!"); - Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from GitHub Release!"); + Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!"); + + await ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage], + LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); + + _running = false; return; } @@ -170,11 +174,12 @@ public static async Task BeginParse(MainWindow mainWindow, bool showVersionUpToD { if (showVersionUpToDate) { - GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", ""); + await ContentDialogHelper.CreateUpdaterInfoDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], + ""); } - Running = false; - mainWindow.UpdateMenuItem.Sensitive = true; + _running = false; return; } @@ -197,13 +202,39 @@ public static async Task BeginParse(MainWindow mainWindow, bool showVersionUpToD _buildSize = -1; } - // Show a message asking the user if they want to update - UpdateDialog updateDialog = new(mainWindow, newVersion, _buildUrl); - updateDialog.Show(); + await Dispatcher.UIThread.InvokeAsync(async () => + { + // Show a message asking the user if they want to update + var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog( + LocaleManager.Instance[LocaleKeys.RyujinxUpdater], + LocaleManager.Instance[LocaleKeys.RyujinxUpdaterMessage], + $"{Program.Version} -> {newVersion}"); + + if (shouldUpdate) + { + await UpdateRyujinx(mainWindow, _buildUrl); + } + else + { + _running = false; + } + }); + } + + private static HttpClient ConstructHttpClient() + { + HttpClient result = new(); + + // Required by GitHub to interact with APIs. + result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0"); + + return result; } - public static void UpdateRyujinx(UpdateDialog updateDialog, string downloadUrl) + private static async Task UpdateRyujinx(Window parent, string downloadUrl) { + _updateSuccessful = false; + // Empty update dir, although it shouldn't ever have anything inside it if (Directory.Exists(_updateDir)) { @@ -214,22 +245,93 @@ public static void UpdateRyujinx(UpdateDialog updateDialog, string downloadUrl) string updateFile = Path.Combine(_updateDir, "update.bin"); - // Download the update .zip - updateDialog.MainText.Text = "Downloading Update..."; - updateDialog.ProgressBar.Value = 0; - updateDialog.ProgressBar.MaxValue = 100; + TaskDialog taskDialog = new() + { + Header = LocaleManager.Instance[LocaleKeys.RyujinxUpdater], + SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading], + IconSource = new SymbolIconSource { Symbol = Symbol.Download }, + ShowProgressBar = true, + XamlRoot = parent, + }; - if (_buildSize >= 0) + taskDialog.Opened += (s, e) => { - DoUpdateWithMultipleThreads(updateDialog, downloadUrl, updateFile); - } - else + if (_buildSize >= 0) + { + DoUpdateWithMultipleThreads(taskDialog, downloadUrl, updateFile); + } + else + { + DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); + } + }; + + await taskDialog.ShowAsync(true); + + if (_updateSuccessful) { - DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile); + bool shouldRestart = true; + + if (!OperatingSystem.IsMacOS()) + { + shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater], + LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage], + LocaleManager.Instance[LocaleKeys.DialogUpdaterRestartMessage]); + } + + if (shouldRestart) + { + List arguments = CommandLineState.Arguments.ToList(); + string executableDirectory = AppDomain.CurrentDomain.BaseDirectory; + + // On macOS we perform the update at relaunch. + if (OperatingSystem.IsMacOS()) + { + string baseBundlePath = Path.GetFullPath(Path.Combine(executableDirectory, "..", "..")); + string newBundlePath = Path.Combine(_updateDir, "Ryujinx.app"); + string updaterScriptPath = Path.Combine(newBundlePath, "Contents", "Resources", "updater.sh"); + string currentPid = Environment.ProcessId.ToString(); + + arguments.InsertRange(0, new List { updaterScriptPath, baseBundlePath, newBundlePath, currentPid }); + Process.Start("/bin/bash", arguments); + } + else + { + // Find the process name. + string ryuName = Path.GetFileName(Environment.ProcessPath); + + // Some operating systems can see the renamed executable, so strip off the .ryuold if found. + if (ryuName.EndsWith(".ryuold")) + { + ryuName = ryuName[..^7]; + } + + // Fallback if the executable could not be found. + if (!Path.Exists(Path.Combine(executableDirectory, ryuName))) + { + ryuName = OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx"; + } + + ProcessStartInfo processStart = new(ryuName) + { + UseShellExecute = true, + WorkingDirectory = executableDirectory, + }; + + foreach (string argument in CommandLineState.Arguments) + { + processStart.ArgumentList.Add(argument); + } + + Process.Start(processStart); + } + + Environment.Exit(0); + } } } - private static void DoUpdateWithMultipleThreads(UpdateDialog updateDialog, string downloadUrl, string updateFile) + private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile) { // Multi-Threaded Updater long chunkSize = _buildSize / ConnectionCount; @@ -253,6 +355,7 @@ private static void DoUpdateWithMultipleThreads(UpdateDialog updateDialog, strin // TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient. using WebClient client = new(); #pragma warning restore SYSLIB0014 + webClients.Add(client); if (i == ConnectionCount - 1) @@ -272,7 +375,7 @@ private static void DoUpdateWithMultipleThreads(UpdateDialog updateDialog, strin Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage); Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage); - updateDialog.ProgressBar.Value = totalProgressPercentage / ConnectionCount; + taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal); }; client.DownloadDataCompleted += (_, args) => @@ -283,6 +386,8 @@ private static void DoUpdateWithMultipleThreads(UpdateDialog updateDialog, strin { webClients[index].Dispose(); + taskDialog.Hide(); + return; } @@ -300,18 +405,24 @@ private static void DoUpdateWithMultipleThreads(UpdateDialog updateDialog, strin File.WriteAllBytes(updateFile, mergedFileBytes); + // On macOS, ensure that we remove the quarantine bit to prevent Gatekeeper from blocking execution. + if (OperatingSystem.IsMacOS()) + { + using Process xattrProcess = Process.Start("xattr", new List { "-d", "com.apple.quarantine", updateFile }); + + xattrProcess.WaitForExit(); + } + try { - InstallUpdate(updateDialog, updateFile); + InstallUpdate(taskDialog, updateFile); } catch (Exception e) { Logger.Warning?.Print(LogClass.Application, e.Message); Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater."); - DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile); - - return; + DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); } } }; @@ -330,14 +441,14 @@ private static void DoUpdateWithMultipleThreads(UpdateDialog updateDialog, strin webClient.CancelAsync(); } - DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile); + DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); return; } } } - private static void DoUpdateWithSingleThreadWorker(UpdateDialog updateDialog, string downloadUrl, string updateFile) + private static void DoUpdateWithSingleThreadWorker(TaskDialog taskDialog, string downloadUrl, string updateFile) { using HttpClient client = new(); // We do not want to timeout while downloading @@ -363,151 +474,165 @@ private static void DoUpdateWithSingleThreadWorker(UpdateDialog updateDialog, st byteWritten += readSize; - updateDialog.ProgressBar.Value = ((double)byteWritten / totalBytes) * 100; + taskDialog.SetProgressBarState(GetPercentage(byteWritten, totalBytes), TaskDialogProgressState.Normal); + updateFileStream.Write(buffer, 0, readSize); } - InstallUpdate(updateDialog, updateFile); + InstallUpdate(taskDialog, updateFile); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static double GetPercentage(double value, double max) + { + return max == 0 ? 0 : value / max * 100; } - private static void DoUpdateWithSingleThread(UpdateDialog updateDialog, string downloadUrl, string updateFile) + private static void DoUpdateWithSingleThread(TaskDialog taskDialog, string downloadUrl, string updateFile) { - Thread worker = new(() => DoUpdateWithSingleThreadWorker(updateDialog, downloadUrl, updateFile)) + Thread worker = new(() => DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile)) { Name = "Updater.SingleThreadWorker", }; + worker.Start(); } - private static async void InstallUpdate(UpdateDialog updateDialog, string updateFile) + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + private static void ExtractTarGzipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath) { - // Extract Update - updateDialog.MainText.Text = "Extracting Update..."; - updateDialog.ProgressBar.Value = 0; + using Stream inStream = File.OpenRead(archivePath); + using GZipInputStream gzipStream = new(inStream); + using TarInputStream tarStream = new(gzipStream, Encoding.ASCII); - if (OperatingSystem.IsLinux()) - { - using Stream inStream = File.OpenRead(updateFile); - using Stream gzipStream = new GZipInputStream(inStream); - using TarInputStream tarStream = new(gzipStream, Encoding.ASCII); - updateDialog.ProgressBar.MaxValue = inStream.Length; + TarEntry tarEntry; - await Task.Run(() => + while ((tarEntry = tarStream.GetNextEntry()) is not null) + { + if (tarEntry.IsDirectory) { - TarEntry tarEntry; - - if (!OperatingSystem.IsWindows()) - { - while ((tarEntry = tarStream.GetNextEntry()) != null) - { - if (tarEntry.IsDirectory) - { - continue; - } - - string outPath = Path.Combine(_updateDir, tarEntry.Name); + continue; + } - Directory.CreateDirectory(Path.GetDirectoryName(outPath)); + string outPath = Path.Combine(outputDirectoryPath, tarEntry.Name); - using FileStream outStream = File.OpenWrite(outPath); - tarStream.CopyEntryContents(outStream); + Directory.CreateDirectory(Path.GetDirectoryName(outPath)); - File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode); - File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc)); + using FileStream outStream = File.OpenWrite(outPath); + tarStream.CopyEntryContents(outStream); - TarEntry entry = tarEntry; + File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode); + File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc)); - Application.Invoke(delegate - { - updateDialog.ProgressBar.Value += entry.Size; - }); - } + Dispatcher.UIThread.Post(() => + { + if (tarEntry is null) + { + return; } - }); - updateDialog.ProgressBar.Value = inStream.Length; + taskDialog.SetProgressBarState(GetPercentage(tarEntry.Size, inStream.Length), TaskDialogProgressState.Normal); + }); } - else - { - using Stream inStream = File.OpenRead(updateFile); - using ZipFile zipFile = new(inStream); - updateDialog.ProgressBar.MaxValue = zipFile.Count; + } - await Task.Run(() => + private static void ExtractZipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath) + { + using Stream inStream = File.OpenRead(archivePath); + using ZipFile zipFile = new(inStream); + + double count = 0; + foreach (ZipEntry zipEntry in zipFile) + { + count++; + if (zipEntry.IsDirectory) { - foreach (ZipEntry zipEntry in zipFile) - { - if (zipEntry.IsDirectory) - { - continue; - } + continue; + } - string outPath = Path.Combine(_updateDir, zipEntry.Name); + string outPath = Path.Combine(outputDirectoryPath, zipEntry.Name); - Directory.CreateDirectory(Path.GetDirectoryName(outPath)); + Directory.CreateDirectory(Path.GetDirectoryName(outPath)); - using Stream zipStream = zipFile.GetInputStream(zipEntry); - using FileStream outStream = File.OpenWrite(outPath); - zipStream.CopyTo(outStream); + using Stream zipStream = zipFile.GetInputStream(zipEntry); + using FileStream outStream = File.OpenWrite(outPath); - File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc)); + zipStream.CopyTo(outStream); - Application.Invoke(delegate - { - updateDialog.ProgressBar.Value++; - }); - } + File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc)); + + Dispatcher.UIThread.Post(() => + { + taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal); }); } + } + + private static void InstallUpdate(TaskDialog taskDialog, string updateFile) + { + // Extract Update + taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting]; + taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); + + if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + ExtractTarGzipFile(taskDialog, updateFile, _updateDir); + } + else if (OperatingSystem.IsWindows()) + { + ExtractZipFile(taskDialog, updateFile, _updateDir); + } + else + { + throw new NotSupportedException(); + } // Delete downloaded zip File.Delete(updateFile); List allFiles = EnumerateFilesToDelete().ToList(); - updateDialog.MainText.Text = "Renaming Old Files..."; - updateDialog.ProgressBar.Value = 0; - updateDialog.ProgressBar.MaxValue = allFiles.Count; + taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterRenaming]; + taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); - // Replace old files - await Task.Run(() => + // NOTE: On macOS, replacement is delayed to the restart phase. + if (!OperatingSystem.IsMacOS()) { + // Replace old files + double count = 0; foreach (string file in allFiles) { + count++; try { File.Move(file, file + ".ryuold"); - Application.Invoke(delegate + Dispatcher.UIThread.InvokeAsync(() => { - updateDialog.ProgressBar.Value++; + taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal); }); } catch { - Logger.Warning?.Print(LogClass.Application, "Updater was unable to rename file: " + file); + Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file)); } } - Application.Invoke(delegate + Dispatcher.UIThread.InvokeAsync(() => { - updateDialog.MainText.Text = "Adding New Files..."; - updateDialog.ProgressBar.Value = 0; - updateDialog.ProgressBar.MaxValue = Directory.GetFiles(_updatePublishDir, "*", SearchOption.AllDirectories).Length; + taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles]; + taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); }); - MoveAllFilesOver(_updatePublishDir, _homeDir, updateDialog); - }); + MoveAllFilesOver(_updatePublishDir, _homeDir, taskDialog); - Directory.Delete(_updateDir, true); + Directory.Delete(_updateDir, true); + } - updateDialog.MainText.Text = "Update Complete!"; - updateDialog.SecondaryText.Text = "Do you want to restart Ryujinx now?"; - updateDialog.Modal = true; + _updateSuccessful = true; - updateDialog.ProgressBar.Hide(); - updateDialog.YesButton.Show(); - updateDialog.NoButton.Show(); + taskDialog.Hide(); } public static bool CanUpdate(bool showWarnings) @@ -517,7 +642,11 @@ public static bool CanUpdate(bool showWarnings) { if (showWarnings) { - GtkDialog.CreateWarningDialog("You are not connected to the Internet!", "Please verify that you have a working Internet connection!"); + Dispatcher.UIThread.InvokeAsync(() => + ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetMessage], + LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage]) + ); } return false; @@ -527,7 +656,11 @@ public static bool CanUpdate(bool showWarnings) { if (showWarnings) { - GtkDialog.CreateWarningDialog("You cannot update a Dirty build of Ryujinx!", "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version."); + Dispatcher.UIThread.InvokeAsync(() => + ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildMessage], + LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]) + ); } return false; @@ -539,11 +672,19 @@ public static bool CanUpdate(bool showWarnings) { if (ReleaseInformation.IsFlatHubBuild) { - GtkDialog.CreateWarningDialog("Updater Disabled!", "Please update Ryujinx via FlatHub."); + Dispatcher.UIThread.InvokeAsync(() => + ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], + LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage]) + ); } else { - GtkDialog.CreateWarningDialog("Updater Disabled!", "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version."); + Dispatcher.UIThread.InvokeAsync(() => + ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], + LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]) + ); } } @@ -557,7 +698,7 @@ private static IEnumerable EnumerateFilesToDelete() var files = Directory.EnumerateFiles(_homeDir); // All files directly in base dir. // Determine and exclude user files only when the updater is running, not when cleaning old files - if (Running) + if (_running && !OperatingSystem.IsMacOS()) { // Compare the loose files in base directory against the loose files from the incoming update, and store foreign ones in a user list. var oldFiles = Directory.EnumerateFiles(_homeDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName); @@ -583,8 +724,9 @@ private static IEnumerable EnumerateFilesToDelete() return files.Where(f => !new FileInfo(f).Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System)); } - private static void MoveAllFilesOver(string root, string dest, UpdateDialog dialog) + private static void MoveAllFilesOver(string root, string dest, TaskDialog taskDialog) { + int total = Directory.GetFiles(root, "*", SearchOption.AllDirectories).Length; foreach (string directory in Directory.GetDirectories(root)) { string dirName = Path.GetFileName(directory); @@ -594,28 +736,28 @@ private static void MoveAllFilesOver(string root, string dest, UpdateDialog dial Directory.CreateDirectory(Path.Combine(dest, dirName)); } - MoveAllFilesOver(directory, Path.Combine(dest, dirName), dialog); + MoveAllFilesOver(directory, Path.Combine(dest, dirName), taskDialog); } + double count = 0; foreach (string file in Directory.GetFiles(root)) { + count++; + File.Move(file, Path.Combine(dest, Path.GetFileName(file)), true); - Application.Invoke(delegate + Dispatcher.UIThread.InvokeAsync(() => { - dialog.ProgressBar.Value++; + taskDialog.SetProgressBarState(GetPercentage(count, total), TaskDialogProgressState.Normal); }); } } public static void CleanupUpdate() { - foreach (string file in EnumerateFilesToDelete()) + foreach (string file in Directory.GetFiles(_homeDir, "*.ryuold", SearchOption.AllDirectories)) { - if (Path.GetExtension(file).EndsWith(".ryuold")) - { - File.Delete(file); - } + File.Delete(file); } } } diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs index 1845c512e..aecc585fc 100644 --- a/src/Ryujinx/Program.cs +++ b/src/Ryujinx/Program.cs @@ -1,4 +1,7 @@ -using Gtk; +using Avalonia; +using Avalonia.Threading; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Windows; using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.GraphicsDriver; @@ -6,139 +9,80 @@ using Ryujinx.Common.SystemInterop; using Ryujinx.Modules; using Ryujinx.SDL2.Common; -using Ryujinx.UI; using Ryujinx.UI.Common; using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Helper; using Ryujinx.UI.Common.SystemInfo; -using Ryujinx.UI.Widgets; -using SixLabors.ImageSharp.Formats.Jpeg; using System; -using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Threading.Tasks; -namespace Ryujinx +namespace Ryujinx.Ava { - partial class Program + internal partial class Program { - public static double WindowScaleFactor { get; private set; } - + public static double WindowScaleFactor { get; set; } + public static double DesktopScaleFactor { get; set; } = 1.0; public static string Version { get; private set; } - - public static string ConfigurationPath { get; set; } - - public static string CommandLineProfile { get; set; } - - private const string X11LibraryName = "libX11"; - - [LibraryImport(X11LibraryName)] - private static partial int XInitThreads(); + public static string ConfigurationPath { get; private set; } + public static bool PreviewerDetached { get; private set; } [LibraryImport("user32.dll", SetLastError = true)] public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type); - [LibraryImport("libc", SetLastError = true)] - private static partial int setenv([MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string value, int overwrite); - - private const uint MbIconWarning = 0x30; + private const uint MbIconwarning = 0x30; - static Program() - { - if (OperatingSystem.IsLinux()) - { - NativeLibrary.SetDllImportResolver(typeof(Program).Assembly, (name, assembly, path) => - { - if (name != X11LibraryName) - { - return IntPtr.Zero; - } - - if (!NativeLibrary.TryLoad("libX11.so.6", assembly, path, out IntPtr result)) - { - if (!NativeLibrary.TryLoad("libX11.so", assembly, path, out result)) - { - return IntPtr.Zero; - } - } - - return result; - }); - } - } - - static void Main(string[] args) + public static void Main(string[] args) { Version = ReleaseInformation.Version; if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134)) { - MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MbIconWarning); + _ = MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MbIconwarning); } - // Parse arguments - CommandLineState.ParseArguments(args); - - // Hook unhandled exception and process exit events. - GLib.ExceptionManager.UnhandledException += (GLib.UnhandledExceptionArgs e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating); - AppDomain.CurrentDomain.UnhandledException += (object sender, UnhandledExceptionEventArgs e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating); - AppDomain.CurrentDomain.ProcessExit += (object sender, EventArgs e) => Exit(); + PreviewerDetached = true; - // Make process DPI aware for proper window sizing on high-res screens. - ForceDpiAware.Windows(); - WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor(); + Initialize(args); - // Delete backup files after updating. - Task.Run(Updater.CleanupUpdate); + LoggerAdapter.Register(); - Console.Title = $"Ryujinx Console {Version}"; - - // NOTE: GTK3 doesn't init X11 in a multi threaded way. - // This ends up causing race condition and abort of XCB when a context is created by SPB (even if SPB do call XInitThreads). - if (OperatingSystem.IsLinux()) - { - if (XInitThreads() == 0) - { - throw new NotSupportedException("Failed to initialize multi-threading support."); - } - - Environment.SetEnvironmentVariable("GDK_BACKEND", "x11"); - setenv("GDK_BACKEND", "x11", 1); - } - - if (OperatingSystem.IsMacOS()) - { - string baseDirectory = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory); - string resourcesDataDir; + BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); + } - if (Path.GetFileName(baseDirectory) == "MacOS") + public static AppBuilder BuildAvaloniaApp() + { + return AppBuilder.Configure() + .UsePlatformDetect() + .With(new X11PlatformOptions { - resourcesDataDir = Path.Combine(Directory.GetParent(baseDirectory).FullName, "Resources"); - } - else + EnableMultiTouch = true, + EnableIme = true, + EnableInputFocusProxy = Environment.GetEnvironmentVariable("XDG_CURRENT_DESKTOP") == "gamescope", + RenderingMode = new[] { X11RenderingMode.Glx, X11RenderingMode.Software }, + }) + .With(new Win32PlatformOptions { - resourcesDataDir = baseDirectory; - } - - static void SetEnvironmentVariableNoCaching(string key, string value) - { - int res = setenv(key, value, 1); - Debug.Assert(res != -1); - } + WinUICompositionBackdropCornerRadius = 8.0f, + RenderingMode = new[] { Win32RenderingMode.AngleEgl, Win32RenderingMode.Software }, + }) + .UseSkia(); + } - // On macOS, GTK3 needs XDG_DATA_DIRS to be set, otherwise it will try searching for "gschemas.compiled" in system directories. - SetEnvironmentVariableNoCaching("XDG_DATA_DIRS", Path.Combine(resourcesDataDir, "share")); + private static void Initialize(string[] args) + { + // Parse arguments + CommandLineState.ParseArguments(args); - // On macOS, GTK3 needs GDK_PIXBUF_MODULE_FILE to be set, otherwise it will try searching for "loaders.cache" in system directories. - SetEnvironmentVariableNoCaching("GDK_PIXBUF_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gdk-pixbuf-2.0", "2.10.0", "loaders.cache")); + // Delete backup files after updating. + Task.Run(Updater.CleanupUpdate); - SetEnvironmentVariableNoCaching("GTK_IM_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gtk-3.0", "3.0.0", "immodules.cache")); - } + Console.Title = $"Ryujinx Console {Version}"; - string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine); - Environment.SetEnvironmentVariable("Path", $"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")};{systemPath}"); + // Hook unhandled exception and process exit events. + AppDomain.CurrentDomain.UnhandledException += (sender, e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating); + AppDomain.CurrentDomain.ProcessExit += (sender, e) => Exit(); // Setup base data directory. AppDataManager.Initialize(CommandLineState.BaseDirPathArg); @@ -153,29 +97,47 @@ static void SetEnvironmentVariableNoCaching(string key, string value) DiscordIntegrationModule.Initialize(); // Initialize SDL2 driver - SDL2Driver.MainThreadDispatcher = action => + SDL2Driver.MainThreadDispatcher = action => Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Input); + + ReloadConfig(); + + WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor(); + + // Logging system information. + PrintSystemInfo(); + + // Enable OGL multithreading on the driver, when available. + DriverUtilities.ToggleOGLThreading(ConfigurationState.Instance.Graphics.BackendThreading == BackendThreading.Off); + + // Check if keys exists. + if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys"))) { - Application.Invoke(delegate + if (!(AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys")))) { - action(); - }); - }; + MainWindow.ShowKeyErrorOnLoad = true; + } + } - // Sets ImageSharp Jpeg Encoder Quality. - SixLabors.ImageSharp.Configuration.Default.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder() + if (CommandLineState.LaunchPathArg != null) { - Quality = 100, - }); + MainWindow.DeferLoadApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg); + } + } + public static void ReloadConfig() + { string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName); string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName); // Now load the configuration as the other subsystems are now registered - ConfigurationPath = File.Exists(localConfigurationPath) - ? localConfigurationPath - : File.Exists(appDataConfigurationPath) - ? appDataConfigurationPath - : null; + if (File.Exists(localConfigurationPath)) + { + ConfigurationPath = localConfigurationPath; + } + else if (File.Exists(appDataConfigurationPath)) + { + ConfigurationPath = appDataConfigurationPath; + } if (ConfigurationPath == null) { @@ -199,7 +161,7 @@ static void SetEnvironmentVariableNoCaching(string key, string value) } } - // Check if graphics backend was overridden. + // Check if graphics backend was overridden if (CommandLineState.OverrideGraphicsBackend != null) { if (CommandLineState.OverrideGraphicsBackend.ToLower() == "opengl") @@ -212,6 +174,12 @@ static void SetEnvironmentVariableNoCaching(string key, string value) } } + // Check if docked mode was overriden. + if (CommandLineState.OverrideDockedMode.HasValue) + { + ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value; + } + // Check if HideCursor was overridden. if (CommandLineState.OverrideHideCursor is not null) { @@ -223,114 +191,6 @@ static void SetEnvironmentVariableNoCaching(string key, string value) _ => ConfigurationState.Instance.HideCursor.Value, }; } - - // Check if docked mode was overridden. - if (CommandLineState.OverrideDockedMode.HasValue) - { - ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value; - } - - // Logging system information. - PrintSystemInfo(); - - // Enable OGL multithreading on the driver, when available. - BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading; - DriverUtilities.ToggleOGLThreading(threadingMode == BackendThreading.Off); - - // Initialize Gtk. - Application.Init(); - - // Check if keys exists. - bool hasSystemProdKeys = File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")); - bool hasCommonProdKeys = AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys")); - if (!hasSystemProdKeys && !hasCommonProdKeys) - { - UserErrorDialog.CreateUserErrorDialog(UserError.NoKeys); - } - - // Show the main window UI. - MainWindow mainWindow = new(); - mainWindow.Show(); - - if (OperatingSystem.IsLinux()) - { - int currentVmMaxMapCount = LinuxHelper.VmMaxMapCount; - - if (LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount) - { - Logger.Warning?.Print(LogClass.Application, $"The value of vm.max_map_count is lower than {LinuxHelper.RecommendedVmMaxMapCount}. ({currentVmMaxMapCount})"); - - if (LinuxHelper.PkExecPath is not null) - { - var buttonTexts = new Dictionary() - { - { 0, "Yes, until the next restart" }, - { 1, "Yes, permanently" }, - { 2, "No" }, - }; - - ResponseType response = GtkDialog.CreateCustomDialog( - "Ryujinx - Low limit for memory mappings detected", - $"Would you like to increase the value of vm.max_map_count to {LinuxHelper.RecommendedVmMaxMapCount}?", - "Some games might try to create more memory mappings than currently allowed. " + - "Ryujinx will crash as soon as this limit gets exceeded.", - buttonTexts, - MessageType.Question); - - int rc; - - switch ((int)response) - { - case 0: - rc = LinuxHelper.RunPkExec($"echo {LinuxHelper.RecommendedVmMaxMapCount} > {LinuxHelper.VmMaxMapCountPath}"); - if (rc == 0) - { - Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount} until the next restart."); - } - else - { - Logger.Error?.Print(LogClass.Application, $"Unable to change vm.max_map_count. Process exited with code: {rc}"); - } - break; - case 1: - rc = LinuxHelper.RunPkExec($"echo \"vm.max_map_count = {LinuxHelper.RecommendedVmMaxMapCount}\" > {LinuxHelper.SysCtlConfigPath} && sysctl -p {LinuxHelper.SysCtlConfigPath}"); - if (rc == 0) - { - Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount}. Written to config: {LinuxHelper.SysCtlConfigPath}"); - } - else - { - Logger.Error?.Print(LogClass.Application, $"Unable to write new value for vm.max_map_count to config. Process exited with code: {rc}"); - } - break; - } - } - else - { - GtkDialog.CreateWarningDialog( - "Max amount of memory mappings is lower than recommended.", - $"The current value of vm.max_map_count ({currentVmMaxMapCount}) is lower than {LinuxHelper.RecommendedVmMaxMapCount}." + - "Some games might try to create more memory mappings than currently allowed. " + - "Ryujinx will crash as soon as this limit gets exceeded.\n\n" + - "You might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that."); - } - } - } - - if (CommandLineState.LaunchPathArg != null) - { - mainWindow.RunApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg); - } - - if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false)) - { - Updater.BeginParse(mainWindow, false).ContinueWith(task => - { - Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}"); - }, TaskContinuationOptions.OnlyOnFaulted); - } - - Application.Run(); } private static void PrintSystemInfo() @@ -338,8 +198,7 @@ private static void PrintSystemInfo() Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}"); SystemInfo.Gather().Print(); - var enabledLogs = Logger.GetEnabledLevels(); - Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(enabledLogs.Count == 0 ? "" : string.Join(", ", enabledLogs))}"); + Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(Logger.GetEnabledLevels().Count == 0 ? "" : string.Join(", ", Logger.GetEnabledLevels()))}"); if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom) { diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj index 68bf98981..b3d312f62 100644 --- a/src/Ryujinx/Ryujinx.csproj +++ b/src/Ryujinx/Ryujinx.csproj @@ -1,5 +1,4 @@ - - + net8.0 win-x64;osx-x64;linux-x64 @@ -7,11 +6,17 @@ true 1.0.0-dirty $(DefineConstants);$(ExtraDefineConstants) - - true + - + Ryujinx.ico true + true + app.manifest + + + + true false @@ -19,33 +24,56 @@ partial + + + true + + - - - - + + + + + + + + + + + + - - + + + + + + + + - - + @@ -73,32 +101,66 @@ - - - false - Ryujinx.ico - - - - - - - - - - + + Designer + + + + MSBuild:Compile + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx.Ava/UI/Applet/AvaHostUIHandler.cs b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Applet/AvaHostUIHandler.cs rename to src/Ryujinx/UI/Applet/AvaHostUIHandler.cs diff --git a/src/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs b/src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs rename to src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs diff --git a/src/Ryujinx.Ava/UI/Applet/AvaloniaHostUITheme.cs b/src/Ryujinx/UI/Applet/AvaloniaHostUITheme.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Applet/AvaloniaHostUITheme.cs rename to src/Ryujinx/UI/Applet/AvaloniaHostUITheme.cs diff --git a/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml b/src/Ryujinx/UI/Applet/ControllerAppletDialog.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml rename to src/Ryujinx/UI/Applet/ControllerAppletDialog.axaml diff --git a/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs b/src/Ryujinx/UI/Applet/ControllerAppletDialog.axaml.cs similarity index 91% rename from src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs rename to src/Ryujinx/UI/Applet/ControllerAppletDialog.axaml.cs index 279af07c3..5a98b1645 100644 --- a/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs +++ b/src/Ryujinx/UI/Applet/ControllerAppletDialog.axaml.cs @@ -17,10 +17,10 @@ namespace Ryujinx.Ava.UI.Applet { internal partial class ControllerAppletDialog : UserControl { - private const string ProControllerResource = "Ryujinx.Ava/Assets/Icons/Controller_ProCon.svg"; - private const string JoyConPairResource = "Ryujinx.Ava/Assets/Icons/Controller_JoyConPair.svg"; - private const string JoyConLeftResource = "Ryujinx.Ava/Assets/Icons/Controller_JoyConLeft.svg"; - private const string JoyConRightResource = "Ryujinx.Ava/Assets/Icons/Controller_JoyConRight.svg"; + private const string ProControllerResource = "Ryujinx/Assets/Icons/Controller_ProCon.svg"; + private const string JoyConPairResource = "Ryujinx/Assets/Icons/Controller_JoyConPair.svg"; + private const string JoyConLeftResource = "Ryujinx/Assets/Icons/Controller_JoyConLeft.svg"; + private const string JoyConRightResource = "Ryujinx/Assets/Icons/Controller_JoyConRight.svg"; public static SvgImage ProControllerImage => GetResource(ProControllerResource); public static SvgImage JoyconPairImage => GetResource(JoyConPairResource); diff --git a/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml b/src/Ryujinx/UI/Applet/ErrorAppletWindow.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml rename to src/Ryujinx/UI/Applet/ErrorAppletWindow.axaml diff --git a/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml.cs b/src/Ryujinx/UI/Applet/ErrorAppletWindow.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml.cs rename to src/Ryujinx/UI/Applet/ErrorAppletWindow.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml b/src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml rename to src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml diff --git a/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs b/src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs rename to src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml rename to src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs rename to src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml b/src/Ryujinx/UI/Controls/ApplicationGridView.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml rename to src/Ryujinx/UI/Controls/ApplicationGridView.axaml diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml.cs b/src/Ryujinx/UI/Controls/ApplicationGridView.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml.cs rename to src/Ryujinx/UI/Controls/ApplicationGridView.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml b/src/Ryujinx/UI/Controls/ApplicationListView.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml rename to src/Ryujinx/UI/Controls/ApplicationListView.axaml diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml.cs b/src/Ryujinx/UI/Controls/ApplicationListView.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml.cs rename to src/Ryujinx/UI/Controls/ApplicationListView.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml b/src/Ryujinx/UI/Controls/NavigationDialogHost.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml rename to src/Ryujinx/UI/Controls/NavigationDialogHost.axaml diff --git a/src/Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml.cs b/src/Ryujinx/UI/Controls/NavigationDialogHost.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml.cs rename to src/Ryujinx/UI/Controls/NavigationDialogHost.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Controls/SliderScroll.axaml.cs b/src/Ryujinx/UI/Controls/SliderScroll.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Controls/SliderScroll.axaml.cs rename to src/Ryujinx/UI/Controls/SliderScroll.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml b/src/Ryujinx/UI/Controls/UpdateWaitWindow.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml rename to src/Ryujinx/UI/Controls/UpdateWaitWindow.axaml diff --git a/src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml.cs b/src/Ryujinx/UI/Controls/UpdateWaitWindow.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml.cs rename to src/Ryujinx/UI/Controls/UpdateWaitWindow.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Helpers/ApplicationOpenedEventArgs.cs b/src/Ryujinx/UI/Helpers/ApplicationOpenedEventArgs.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Helpers/ApplicationOpenedEventArgs.cs rename to src/Ryujinx/UI/Helpers/ApplicationOpenedEventArgs.cs diff --git a/src/Ryujinx.Ava/UI/Helpers/BitmapArrayValueConverter.cs b/src/Ryujinx/UI/Helpers/BitmapArrayValueConverter.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Helpers/BitmapArrayValueConverter.cs rename to src/Ryujinx/UI/Helpers/BitmapArrayValueConverter.cs diff --git a/src/Ryujinx.Ava/UI/Helpers/ButtonKeyAssigner.cs b/src/Ryujinx/UI/Helpers/ButtonKeyAssigner.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Helpers/ButtonKeyAssigner.cs rename to src/Ryujinx/UI/Helpers/ButtonKeyAssigner.cs diff --git a/src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs b/src/Ryujinx/UI/Helpers/ContentDialogHelper.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs rename to src/Ryujinx/UI/Helpers/ContentDialogHelper.cs diff --git a/src/Ryujinx.Ava/UI/Helpers/Glyph.cs b/src/Ryujinx/UI/Helpers/Glyph.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Helpers/Glyph.cs rename to src/Ryujinx/UI/Helpers/Glyph.cs diff --git a/src/Ryujinx.Ava/UI/Helpers/GlyphValueConverter.cs b/src/Ryujinx/UI/Helpers/GlyphValueConverter.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Helpers/GlyphValueConverter.cs rename to src/Ryujinx/UI/Helpers/GlyphValueConverter.cs diff --git a/src/Ryujinx.Ava/UI/Helpers/KeyValueConverter.cs b/src/Ryujinx/UI/Helpers/KeyValueConverter.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Helpers/KeyValueConverter.cs rename to src/Ryujinx/UI/Helpers/KeyValueConverter.cs diff --git a/src/Ryujinx.Ava/UI/Helpers/LocalizedNeverConverter.cs b/src/Ryujinx/UI/Helpers/LocalizedNeverConverter.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Helpers/LocalizedNeverConverter.cs rename to src/Ryujinx/UI/Helpers/LocalizedNeverConverter.cs diff --git a/src/Ryujinx.Ava/UI/Helpers/LoggerAdapter.cs b/src/Ryujinx/UI/Helpers/LoggerAdapter.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Helpers/LoggerAdapter.cs rename to src/Ryujinx/UI/Helpers/LoggerAdapter.cs diff --git a/src/Ryujinx.Ava/UI/Helpers/MiniCommand.cs b/src/Ryujinx/UI/Helpers/MiniCommand.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Helpers/MiniCommand.cs rename to src/Ryujinx/UI/Helpers/MiniCommand.cs diff --git a/src/Ryujinx.Ava/UI/Helpers/NotificationHelper.cs b/src/Ryujinx/UI/Helpers/NotificationHelper.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Helpers/NotificationHelper.cs rename to src/Ryujinx/UI/Helpers/NotificationHelper.cs diff --git a/src/Ryujinx.Ava/UI/Helpers/OffscreenTextBox.cs b/src/Ryujinx/UI/Helpers/OffscreenTextBox.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Helpers/OffscreenTextBox.cs rename to src/Ryujinx/UI/Helpers/OffscreenTextBox.cs diff --git a/src/Ryujinx.Ava/UI/Helpers/TimeZoneConverter.cs b/src/Ryujinx/UI/Helpers/TimeZoneConverter.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Helpers/TimeZoneConverter.cs rename to src/Ryujinx/UI/Helpers/TimeZoneConverter.cs diff --git a/src/Ryujinx.Ava/UI/Helpers/UserErrorDialog.cs b/src/Ryujinx/UI/Helpers/UserErrorDialog.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Helpers/UserErrorDialog.cs rename to src/Ryujinx/UI/Helpers/UserErrorDialog.cs diff --git a/src/Ryujinx.Ava/UI/Helpers/UserResult.cs b/src/Ryujinx/UI/Helpers/UserResult.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Helpers/UserResult.cs rename to src/Ryujinx/UI/Helpers/UserResult.cs diff --git a/src/Ryujinx.Ava/UI/Helpers/Win32NativeInterop.cs b/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Helpers/Win32NativeInterop.cs rename to src/Ryujinx/UI/Helpers/Win32NativeInterop.cs diff --git a/src/Ryujinx.Ava/UI/Models/CheatNode.cs b/src/Ryujinx/UI/Models/CheatNode.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Models/CheatNode.cs rename to src/Ryujinx/UI/Models/CheatNode.cs diff --git a/src/Ryujinx.Ava/UI/Models/ControllerModel.cs b/src/Ryujinx/UI/Models/ControllerModel.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Models/ControllerModel.cs rename to src/Ryujinx/UI/Models/ControllerModel.cs diff --git a/src/Ryujinx.Ava/UI/Models/DeviceType.cs b/src/Ryujinx/UI/Models/DeviceType.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Models/DeviceType.cs rename to src/Ryujinx/UI/Models/DeviceType.cs diff --git a/src/Ryujinx.Ava/UI/Models/DownloadableContentModel.cs b/src/Ryujinx/UI/Models/DownloadableContentModel.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Models/DownloadableContentModel.cs rename to src/Ryujinx/UI/Models/DownloadableContentModel.cs diff --git a/src/Ryujinx.Ava/UI/Models/Generic/LastPlayedSortComparer.cs b/src/Ryujinx/UI/Models/Generic/LastPlayedSortComparer.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Models/Generic/LastPlayedSortComparer.cs rename to src/Ryujinx/UI/Models/Generic/LastPlayedSortComparer.cs diff --git a/src/Ryujinx.Ava/UI/Models/Generic/TimePlayedSortComparer.cs b/src/Ryujinx/UI/Models/Generic/TimePlayedSortComparer.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Models/Generic/TimePlayedSortComparer.cs rename to src/Ryujinx/UI/Models/Generic/TimePlayedSortComparer.cs diff --git a/src/Ryujinx.Ava/UI/Models/InputConfiguration.cs b/src/Ryujinx/UI/Models/InputConfiguration.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Models/InputConfiguration.cs rename to src/Ryujinx/UI/Models/InputConfiguration.cs diff --git a/src/Ryujinx.Ava/UI/Models/ModModel.cs b/src/Ryujinx/UI/Models/ModModel.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Models/ModModel.cs rename to src/Ryujinx/UI/Models/ModModel.cs diff --git a/src/Ryujinx.Ava/UI/Models/PlayerModel.cs b/src/Ryujinx/UI/Models/PlayerModel.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Models/PlayerModel.cs rename to src/Ryujinx/UI/Models/PlayerModel.cs diff --git a/src/Ryujinx.Ava/UI/Models/ProfileImageModel.cs b/src/Ryujinx/UI/Models/ProfileImageModel.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Models/ProfileImageModel.cs rename to src/Ryujinx/UI/Models/ProfileImageModel.cs diff --git a/src/Ryujinx.Ava/UI/Models/SaveModel.cs b/src/Ryujinx/UI/Models/SaveModel.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Models/SaveModel.cs rename to src/Ryujinx/UI/Models/SaveModel.cs diff --git a/src/Ryujinx.Ava/UI/Models/StatusUpdatedEventArgs.cs b/src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Models/StatusUpdatedEventArgs.cs rename to src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs diff --git a/src/Ryujinx.Ava/UI/Models/TempProfile.cs b/src/Ryujinx/UI/Models/TempProfile.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Models/TempProfile.cs rename to src/Ryujinx/UI/Models/TempProfile.cs diff --git a/src/Ryujinx.Ava/UI/Models/TimeZone.cs b/src/Ryujinx/UI/Models/TimeZone.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Models/TimeZone.cs rename to src/Ryujinx/UI/Models/TimeZone.cs diff --git a/src/Ryujinx.Ava/UI/Models/TitleUpdateModel.cs b/src/Ryujinx/UI/Models/TitleUpdateModel.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Models/TitleUpdateModel.cs rename to src/Ryujinx/UI/Models/TitleUpdateModel.cs diff --git a/src/Ryujinx.Ava/UI/Models/UserProfile.cs b/src/Ryujinx/UI/Models/UserProfile.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Models/UserProfile.cs rename to src/Ryujinx/UI/Models/UserProfile.cs diff --git a/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs b/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs rename to src/Ryujinx/UI/Renderer/EmbeddedWindow.cs diff --git a/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindowOpenGL.cs b/src/Ryujinx/UI/Renderer/EmbeddedWindowOpenGL.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Renderer/EmbeddedWindowOpenGL.cs rename to src/Ryujinx/UI/Renderer/EmbeddedWindowOpenGL.cs diff --git a/src/Ryujinx.Ava/UI/Renderer/EmbeddedWindowVulkan.cs b/src/Ryujinx/UI/Renderer/EmbeddedWindowVulkan.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Renderer/EmbeddedWindowVulkan.cs rename to src/Ryujinx/UI/Renderer/EmbeddedWindowVulkan.cs diff --git a/src/Ryujinx.Ava/UI/Renderer/OpenTKBindingsContext.cs b/src/Ryujinx/UI/Renderer/OpenTKBindingsContext.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Renderer/OpenTKBindingsContext.cs rename to src/Ryujinx/UI/Renderer/OpenTKBindingsContext.cs diff --git a/src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml b/src/Ryujinx/UI/Renderer/RendererHost.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml rename to src/Ryujinx/UI/Renderer/RendererHost.axaml diff --git a/src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs b/src/Ryujinx/UI/Renderer/RendererHost.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs rename to src/Ryujinx/UI/Renderer/RendererHost.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Renderer/SPBOpenGLContext.cs b/src/Ryujinx/UI/Renderer/SPBOpenGLContext.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Renderer/SPBOpenGLContext.cs rename to src/Ryujinx/UI/Renderer/SPBOpenGLContext.cs diff --git a/src/Ryujinx.Ava/UI/ViewModels/AboutWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs similarity index 100% rename from src/Ryujinx.Ava/UI/ViewModels/AboutWindowViewModel.cs rename to src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs diff --git a/src/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/AmiiboWindowViewModel.cs similarity index 100% rename from src/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs rename to src/Ryujinx/UI/ViewModels/AmiiboWindowViewModel.cs diff --git a/src/Ryujinx.Ava/UI/ViewModels/BaseModel.cs b/src/Ryujinx/UI/ViewModels/BaseModel.cs similarity index 100% rename from src/Ryujinx.Ava/UI/ViewModels/BaseModel.cs rename to src/Ryujinx/UI/ViewModels/BaseModel.cs diff --git a/src/Ryujinx.Ava/UI/ViewModels/ControllerInputViewModel.cs b/src/Ryujinx/UI/ViewModels/ControllerInputViewModel.cs similarity index 100% rename from src/Ryujinx.Ava/UI/ViewModels/ControllerInputViewModel.cs rename to src/Ryujinx/UI/ViewModels/ControllerInputViewModel.cs diff --git a/src/Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs b/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs similarity index 100% rename from src/Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs rename to src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs diff --git a/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs similarity index 100% rename from src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs rename to src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs diff --git a/src/Ryujinx.Ava/UI/ViewModels/ModManagerViewModel.cs b/src/Ryujinx/UI/ViewModels/ModManagerViewModel.cs similarity index 100% rename from src/Ryujinx.Ava/UI/ViewModels/ModManagerViewModel.cs rename to src/Ryujinx/UI/ViewModels/ModManagerViewModel.cs diff --git a/src/Ryujinx.Ava/UI/ViewModels/MotionInputViewModel.cs b/src/Ryujinx/UI/ViewModels/MotionInputViewModel.cs similarity index 100% rename from src/Ryujinx.Ava/UI/ViewModels/MotionInputViewModel.cs rename to src/Ryujinx/UI/ViewModels/MotionInputViewModel.cs diff --git a/src/Ryujinx.Ava/UI/ViewModels/RumbleInputViewModel.cs b/src/Ryujinx/UI/ViewModels/RumbleInputViewModel.cs similarity index 100% rename from src/Ryujinx.Ava/UI/ViewModels/RumbleInputViewModel.cs rename to src/Ryujinx/UI/ViewModels/RumbleInputViewModel.cs diff --git a/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs similarity index 100% rename from src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs rename to src/Ryujinx/UI/ViewModels/SettingsViewModel.cs diff --git a/src/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs b/src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs similarity index 100% rename from src/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs rename to src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs diff --git a/src/Ryujinx.Ava/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs b/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs similarity index 100% rename from src/Ryujinx.Ava/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs rename to src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs diff --git a/src/Ryujinx.Ava/UI/ViewModels/UserProfileImageSelectorViewModel.cs b/src/Ryujinx/UI/ViewModels/UserProfileImageSelectorViewModel.cs similarity index 100% rename from src/Ryujinx.Ava/UI/ViewModels/UserProfileImageSelectorViewModel.cs rename to src/Ryujinx/UI/ViewModels/UserProfileImageSelectorViewModel.cs diff --git a/src/Ryujinx.Ava/UI/ViewModels/UserProfileViewModel.cs b/src/Ryujinx/UI/ViewModels/UserProfileViewModel.cs similarity index 100% rename from src/Ryujinx.Ava/UI/ViewModels/UserProfileViewModel.cs rename to src/Ryujinx/UI/ViewModels/UserProfileViewModel.cs diff --git a/src/Ryujinx.Ava/UI/ViewModels/UserSaveManagerViewModel.cs b/src/Ryujinx/UI/ViewModels/UserSaveManagerViewModel.cs similarity index 100% rename from src/Ryujinx.Ava/UI/ViewModels/UserSaveManagerViewModel.cs rename to src/Ryujinx/UI/ViewModels/UserSaveManagerViewModel.cs diff --git a/src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml rename to src/Ryujinx/UI/Views/Input/ControllerInputView.axaml diff --git a/src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml.cs b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml.cs rename to src/Ryujinx/UI/Views/Input/ControllerInputView.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Views/Input/MotionInputView.axaml b/src/Ryujinx/UI/Views/Input/MotionInputView.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Input/MotionInputView.axaml rename to src/Ryujinx/UI/Views/Input/MotionInputView.axaml diff --git a/src/Ryujinx.Ava/UI/Views/Input/MotionInputView.axaml.cs b/src/Ryujinx/UI/Views/Input/MotionInputView.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Input/MotionInputView.axaml.cs rename to src/Ryujinx/UI/Views/Input/MotionInputView.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Views/Input/RumbleInputView.axaml b/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Input/RumbleInputView.axaml rename to src/Ryujinx/UI/Views/Input/RumbleInputView.axaml diff --git a/src/Ryujinx.Ava/UI/Views/Input/RumbleInputView.axaml.cs b/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Input/RumbleInputView.axaml.cs rename to src/Ryujinx/UI/Views/Input/RumbleInputView.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml rename to src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml diff --git a/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs similarity index 99% rename from src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs rename to src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs index 8dff50868..dc50ce265 100644 --- a/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs @@ -55,7 +55,7 @@ private static MenuItem[] GenerateLanguageMenuItems() { List menuItems = new(); - string localePath = "Ryujinx.Ava/Assets/Locales"; + string localePath = "Ryujinx/Assets/Locales"; string localeExt = ".json"; string[] localesPath = EmbeddedResources.GetAllAvailableResources(localePath, localeExt); diff --git a/src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml b/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml rename to src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml diff --git a/src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml.cs b/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml.cs rename to src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml b/src/Ryujinx/UI/Views/Main/MainViewControls.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml rename to src/Ryujinx/UI/Views/Main/MainViewControls.axaml diff --git a/src/Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml.cs b/src/Ryujinx/UI/Views/Main/MainViewControls.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml.cs rename to src/Ryujinx/UI/Views/Main/MainViewControls.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsAudioView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Settings/SettingsAudioView.axaml rename to src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsAudioView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Settings/SettingsAudioView.axaml.cs rename to src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsCPUView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Settings/SettingsCPUView.axaml rename to src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsCPUView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Settings/SettingsCPUView.axaml.cs rename to src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsGraphicsView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Settings/SettingsGraphicsView.axaml rename to src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsGraphicsView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Settings/SettingsGraphicsView.axaml.cs rename to src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsHotkeysView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Settings/SettingsHotkeysView.axaml rename to src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsHotkeysView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Settings/SettingsHotkeysView.axaml.cs rename to src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsInputView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Settings/SettingsInputView.axaml rename to src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsInputView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Settings/SettingsInputView.axaml.cs rename to src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsLoggingView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Settings/SettingsLoggingView.axaml rename to src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsLoggingView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Settings/SettingsLoggingView.axaml.cs rename to src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsNetworkView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsNetworkView.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Settings/SettingsNetworkView.axaml rename to src/Ryujinx/UI/Views/Settings/SettingsNetworkView.axaml diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsNetworkView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsNetworkView.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Settings/SettingsNetworkView.axaml.cs rename to src/Ryujinx/UI/Views/Settings/SettingsNetworkView.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsSystemView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Settings/SettingsSystemView.axaml rename to src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsSystemView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Settings/SettingsSystemView.axaml.cs rename to src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsUIView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Settings/SettingsUIView.axaml rename to src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsUIView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Views/Settings/SettingsUIView.axaml.cs rename to src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Views/User/UserEditorView.axaml b/src/Ryujinx/UI/Views/User/UserEditorView.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Views/User/UserEditorView.axaml rename to src/Ryujinx/UI/Views/User/UserEditorView.axaml diff --git a/src/Ryujinx.Ava/UI/Views/User/UserEditorView.axaml.cs b/src/Ryujinx/UI/Views/User/UserEditorView.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Views/User/UserEditorView.axaml.cs rename to src/Ryujinx/UI/Views/User/UserEditorView.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Views/User/UserFirmwareAvatarSelectorView.axaml b/src/Ryujinx/UI/Views/User/UserFirmwareAvatarSelectorView.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Views/User/UserFirmwareAvatarSelectorView.axaml rename to src/Ryujinx/UI/Views/User/UserFirmwareAvatarSelectorView.axaml diff --git a/src/Ryujinx.Ava/UI/Views/User/UserFirmwareAvatarSelectorView.axaml.cs b/src/Ryujinx/UI/Views/User/UserFirmwareAvatarSelectorView.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Views/User/UserFirmwareAvatarSelectorView.axaml.cs rename to src/Ryujinx/UI/Views/User/UserFirmwareAvatarSelectorView.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Views/User/UserProfileImageSelectorView.axaml b/src/Ryujinx/UI/Views/User/UserProfileImageSelectorView.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Views/User/UserProfileImageSelectorView.axaml rename to src/Ryujinx/UI/Views/User/UserProfileImageSelectorView.axaml diff --git a/src/Ryujinx.Ava/UI/Views/User/UserProfileImageSelectorView.axaml.cs b/src/Ryujinx/UI/Views/User/UserProfileImageSelectorView.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Views/User/UserProfileImageSelectorView.axaml.cs rename to src/Ryujinx/UI/Views/User/UserProfileImageSelectorView.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Views/User/UserRecovererView.axaml b/src/Ryujinx/UI/Views/User/UserRecovererView.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Views/User/UserRecovererView.axaml rename to src/Ryujinx/UI/Views/User/UserRecovererView.axaml diff --git a/src/Ryujinx.Ava/UI/Views/User/UserRecovererView.axaml.cs b/src/Ryujinx/UI/Views/User/UserRecovererView.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Views/User/UserRecovererView.axaml.cs rename to src/Ryujinx/UI/Views/User/UserRecovererView.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml b/src/Ryujinx/UI/Views/User/UserSaveManagerView.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml rename to src/Ryujinx/UI/Views/User/UserSaveManagerView.axaml diff --git a/src/Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml.cs b/src/Ryujinx/UI/Views/User/UserSaveManagerView.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml.cs rename to src/Ryujinx/UI/Views/User/UserSaveManagerView.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml b/src/Ryujinx/UI/Views/User/UserSelectorView.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml rename to src/Ryujinx/UI/Views/User/UserSelectorView.axaml diff --git a/src/Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml.cs b/src/Ryujinx/UI/Views/User/UserSelectorView.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml.cs rename to src/Ryujinx/UI/Views/User/UserSelectorView.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml b/src/Ryujinx/UI/Windows/AboutWindow.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml rename to src/Ryujinx/UI/Windows/AboutWindow.axaml diff --git a/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml.cs b/src/Ryujinx/UI/Windows/AboutWindow.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml.cs rename to src/Ryujinx/UI/Windows/AboutWindow.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml b/src/Ryujinx/UI/Windows/AmiiboWindow.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml rename to src/Ryujinx/UI/Windows/AmiiboWindow.axaml diff --git a/src/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml.cs b/src/Ryujinx/UI/Windows/AmiiboWindow.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml.cs rename to src/Ryujinx/UI/Windows/AmiiboWindow.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml b/src/Ryujinx/UI/Windows/CheatWindow.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml rename to src/Ryujinx/UI/Windows/CheatWindow.axaml diff --git a/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs b/src/Ryujinx/UI/Windows/CheatWindow.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs rename to src/Ryujinx/UI/Windows/CheatWindow.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Windows/ContentDialogOverlayWindow.axaml b/src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Windows/ContentDialogOverlayWindow.axaml rename to src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml diff --git a/src/Ryujinx.Ava/UI/Windows/ContentDialogOverlayWindow.axaml.cs b/src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Windows/ContentDialogOverlayWindow.axaml.cs rename to src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml b/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml rename to src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml diff --git a/src/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml.cs b/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml.cs rename to src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Windows/IconColorPicker.cs b/src/Ryujinx/UI/Windows/IconColorPicker.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Windows/IconColorPicker.cs rename to src/Ryujinx/UI/Windows/IconColorPicker.cs diff --git a/src/Ryujinx.Ava/UI/Windows/MainWindow.axaml b/src/Ryujinx/UI/Windows/MainWindow.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Windows/MainWindow.axaml rename to src/Ryujinx/UI/Windows/MainWindow.axaml diff --git a/src/Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs rename to src/Ryujinx/UI/Windows/MainWindow.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml b/src/Ryujinx/UI/Windows/ModManagerWindow.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml rename to src/Ryujinx/UI/Windows/ModManagerWindow.axaml diff --git a/src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml.cs b/src/Ryujinx/UI/Windows/ModManagerWindow.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml.cs rename to src/Ryujinx/UI/Windows/ModManagerWindow.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml b/src/Ryujinx/UI/Windows/SettingsWindow.axaml similarity index 98% rename from src/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml rename to src/Ryujinx/UI/Windows/SettingsWindow.axaml index 40cac90dd..de3c2291a 100644 --- a/src/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml +++ b/src/Ryujinx/UI/Windows/SettingsWindow.axaml @@ -76,7 +76,7 @@ Tag="CpuPage"> diff --git a/src/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs b/src/Ryujinx/UI/Windows/SettingsWindow.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs rename to src/Ryujinx/UI/Windows/SettingsWindow.axaml.cs diff --git a/src/Ryujinx.Ava/UI/Windows/StyleableWindow.cs b/src/Ryujinx/UI/Windows/StyleableWindow.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Windows/StyleableWindow.cs rename to src/Ryujinx/UI/Windows/StyleableWindow.cs diff --git a/src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml b/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml similarity index 100% rename from src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml rename to src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml diff --git a/src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs b/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs similarity index 100% rename from src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs rename to src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs diff --git a/src/Ryujinx.Ava/app.manifest b/src/Ryujinx/app.manifest similarity index 100% rename from src/Ryujinx.Ava/app.manifest rename to src/Ryujinx/app.manifest From bc4d99a0786dbcbfde62d3bdeb98ed3d12c94852 Mon Sep 17 00:00:00 2001 From: Mary Guillemard Date: Sat, 2 Mar 2024 12:58:03 +0100 Subject: [PATCH 094/126] ci: try to fix toctou on release creation Signed-off-by: Mary Guillemard --- .github/workflows/release.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 133c2fad0..9843487a3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,6 +44,17 @@ jobs: sha: context.sha }) + - name: Create release + uses: ncipollo/release-action@v1 + with: + name: ${{ steps.version_info.outputs.build_version }} + tag: ${{ steps.version_info.outputs.build_version }} + body: "For more information about this release please check out the official [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog)." + omitBodyDuringUpdate: true + owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }} + repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }} + token: ${{ secrets.RELEASE_TOKEN }} + release: name: Release for ${{ matrix.platform.name }} runs-on: ${{ matrix.platform.os }} From 2505a1abcd5f31e4364bfb7f9313546f1840e5e1 Mon Sep 17 00:00:00 2001 From: Mary Guillemard Date: Tue, 5 Mar 2024 17:32:13 +0100 Subject: [PATCH 095/126] misc: Remove myself from reviews I have been mostly inactive on the project for the past year and a half apart from handling CI and reviews because of a lack of motivation and time. --- .github/dependabot.yml | 4 ++-- .github/reviewers.yml | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 491676acb..20bdc19d1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,7 +7,7 @@ updates: labels: - "infra" reviewers: - - marysaka + - TSRBerry commit-message: prefix: "ci" @@ -19,7 +19,7 @@ updates: labels: - "infra" reviewers: - - marysaka + - TSRBerry commit-message: prefix: nuget groups: diff --git a/.github/reviewers.yml b/.github/reviewers.yml index 052594f23..46c0d5c11 100644 --- a/.github/reviewers.yml +++ b/.github/reviewers.yml @@ -1,31 +1,24 @@ -audio: - - marysaka cpu: - gdkchan - riperiperi - - marysaka - LDj3SNuD gpu: - gdkchan - riperiperi - - marysaka gui: - Ack77 - emmauss - TSRBerry - - marysaka horizon: - gdkchan - Ack77 - - marysaka - TSRBerry infra: - - marysaka - TSRBerry default: From 4e1a60328e1236c5f54a36c147914b2c13a770d4 Mon Sep 17 00:00:00 2001 From: Kyle <59298462+Kfollen93@users.noreply.github.com> Date: Thu, 7 Mar 2024 14:49:57 -0800 Subject: [PATCH 096/126] Add title of game to screenshot text (#6266) * Add sanitize method * Add app name to screenshot text output * Add app name to screenshot text --- src/Ryujinx.Common/Utilities/FileSystemUtils.cs | 8 ++++++++ src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs | 9 +++++++-- src/Ryujinx/AppHost.cs | 6 +++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/Ryujinx.Common/Utilities/FileSystemUtils.cs b/src/Ryujinx.Common/Utilities/FileSystemUtils.cs index e76c2b60b..a57fa8a78 100644 --- a/src/Ryujinx.Common/Utilities/FileSystemUtils.cs +++ b/src/Ryujinx.Common/Utilities/FileSystemUtils.cs @@ -1,4 +1,6 @@ +using System.Collections.Generic; using System.IO; +using System.Linq; namespace Ryujinx.Common.Utilities { @@ -44,5 +46,11 @@ public static void MoveDirectory(string sourceDir, string destinationDir) CopyDirectory(sourceDir, destinationDir, true); Directory.Delete(sourceDir, true); } + + public static string SanitizeFileName(string fileName) + { + var reservedChars = new HashSet(Path.GetInvalidFileNameChars()); + return string.Concat(fileName.Select(c => reservedChars.Contains(c) ? '_' : c)); + } } } diff --git a/src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs b/src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs index e27d06044..0e636792d 100644 --- a/src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs +++ b/src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs @@ -3,6 +3,7 @@ using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL.Multithreading; using Ryujinx.Graphics.Gpu; @@ -378,8 +379,12 @@ private unsafe void Renderer_ScreenCaptured(object sender, ScreenCaptureImageInf { lock (this) { - var currentTime = DateTime.Now; - string filename = $"ryujinx_capture_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png"; + string applicationName = Device.Processes.ActiveApplication.Name; + string sanitizedApplicationName = FileSystemUtils.SanitizeFileName(applicationName); + DateTime currentTime = DateTime.Now; + + string filename = $"{sanitizedApplicationName}_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png"; + string directory = AppDataManager.Mode switch { AppDataManager.LaunchMode.Portable or AppDataManager.LaunchMode.Custom => System.IO.Path.Combine(AppDataManager.BaseDirPath, "screenshots"), diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index 04cec9579..2620ea68c 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -22,6 +22,7 @@ using Ryujinx.Common.Configuration.Multiplayer; using Ryujinx.Common.Logging; using Ryujinx.Common.SystemInterop; +using Ryujinx.Common.Utilities; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL.Multithreading; using Ryujinx.Graphics.Gpu; @@ -279,8 +280,11 @@ private void Renderer_ScreenCaptured(object sender, ScreenCaptureImageInfo e) { lock (_lockObject) { + string applicationName = Device.Processes.ActiveApplication.Name; + string sanitizedApplicationName = FileSystemUtils.SanitizeFileName(applicationName); DateTime currentTime = DateTime.Now; - string filename = $"ryujinx_capture_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png"; + + string filename = $"{sanitizedApplicationName}_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png"; string directory = AppDataManager.Mode switch { From dda0f26067103312cc93d2174eaefe2d9980ee74 Mon Sep 17 00:00:00 2001 From: MutantAura <44103205+MutantAura@users.noreply.github.com> Date: Thu, 7 Mar 2024 23:38:56 +0000 Subject: [PATCH 097/126] UI: Update minimum window size to 800x500 (#6425) --- src/Ryujinx/UI/Windows/MainWindow.axaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml b/src/Ryujinx/UI/Windows/MainWindow.axaml index 4def7c281..6c2042f93 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml @@ -14,8 +14,8 @@ WindowState="{Binding WindowState}" Width="{Binding WindowWidth}" Height="{Binding WindowHeight}" - MinWidth="1092" - MinHeight="672" + MinWidth="800" + MinHeight="500" d:DesignHeight="720" d:DesignWidth="1280" x:DataType="viewModels:MainWindowViewModel" From 50458b2472cf106b2fae9945867cf1e740ee6a80 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Thu, 7 Mar 2024 20:55:54 -0300 Subject: [PATCH 098/126] LightningJit: Disable some cache ops and CTR_EL0 access on Windows Arm (#6326) * LightningJit: Disable some cache ops and CTR_EL0 access on Windows Arm * Format whitespace * Delete unused code * Fix typo Co-authored-by: riperiperi --------- Co-authored-by: riperiperi --- .../LightningJit/Arm64/InstName.cs | 1 + .../LightningJit/Arm64/SysUtils.cs | 48 +++++++++++++++++++ .../Arm64/Target/Arm64/Decoder.cs | 7 ++- .../Arm64/Target/Arm64/InstEmitMemory.cs | 8 ++++ .../Arm64/Target/Arm64/InstEmitSystem.cs | 15 ++++-- 5 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm64/SysUtils.cs diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/InstName.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/InstName.cs index 58d78ae6e..365640645 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm64/InstName.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/InstName.cs @@ -1106,6 +1106,7 @@ public static bool IsSystemOrCall(this InstName name) case InstName.Mrs: case InstName.MsrImm: case InstName.MsrReg: + case InstName.Sysl: return true; } diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/SysUtils.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/SysUtils.cs new file mode 100644 index 000000000..69689a391 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/SysUtils.cs @@ -0,0 +1,48 @@ +using System.Diagnostics; + +namespace Ryujinx.Cpu.LightningJit.Arm64 +{ + static class SysUtils + { + public static (uint, uint, uint, uint) UnpackOp1CRnCRmOp2(uint encoding) + { + uint op1 = (encoding >> 16) & 7; + uint crn = (encoding >> 12) & 0xf; + uint crm = (encoding >> 8) & 0xf; + uint op2 = (encoding >> 5) & 7; + + return (op1, crn, crm, op2); + } + + public static bool IsCacheInstEl0(uint encoding) + { + (uint op1, uint crn, uint crm, uint op2) = UnpackOp1CRnCRmOp2(encoding); + + return ((op1 << 11) | (crn << 7) | (crm << 3) | op2) switch + { + 0b011_0111_0100_001 => true, // DC ZVA + 0b011_0111_1010_001 => true, // DC CVAC + 0b011_0111_1100_001 => true, // DC CVAP + 0b011_0111_1011_001 => true, // DC CVAU + 0b011_0111_1110_001 => true, // DC CIVAC + 0b011_0111_0101_001 => true, // IC IVAU + _ => false, + }; + } + + public static bool IsCacheInstUciTrapped(uint encoding) + { + (uint op1, uint crn, uint crm, uint op2) = UnpackOp1CRnCRmOp2(encoding); + + return ((op1 << 11) | (crn << 7) | (crm << 3) | op2) switch + { + 0b011_0111_1010_001 => true, // DC CVAC + 0b011_0111_1100_001 => true, // DC CVAP + 0b011_0111_1011_001 => true, // DC CVAU + 0b011_0111_1110_001 => true, // DC CIVAC + 0b011_0111_0101_001 => true, // IC IVAU + _ => false, + }; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Decoder.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Decoder.cs index e9ba8ba21..00a1758f2 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Decoder.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Decoder.cs @@ -257,7 +257,7 @@ private static Block Decode( (name, flags, AddressForm addressForm) = InstTable.GetInstNameAndFlags(encoding, cpuPreset.Version, cpuPreset.Features); - if (name.IsPrivileged()) + if (name.IsPrivileged() || (name == InstName.Sys && IsPrivilegedSys(encoding))) { name = InstName.UdfPermUndef; flags = InstFlags.None; @@ -341,6 +341,11 @@ private static Block Decode( return new(startAddress, address, insts, !isTruncated && !name.IsException(), isTruncated, isLoopEnd); } + private static bool IsPrivilegedSys(uint encoding) + { + return !SysUtils.IsCacheInstEl0(encoding); + } + private static bool IsMrsNzcv(uint encoding) { return (encoding & ~0x1fu) == 0xd53b4200u; diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitMemory.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitMemory.cs index ece1520fd..e03d9373a 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitMemory.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitMemory.cs @@ -13,6 +13,14 @@ static class InstEmitMemory public static void RewriteSysInstruction(int asBits, MemoryManagerType mmType, CodeWriter writer, RegisterAllocator regAlloc, uint encoding) { + // TODO: Handle IC instruction, it should invalidate the JIT cache. + + if (InstEmitSystem.IsCacheInstForbidden(encoding)) + { + // Current OS does not allow cache maintenance instructions from user mode, just do nothing. + return; + } + int rtIndex = RegisterUtils.ExtractRt(encoding); if (rtIndex == RegisterUtils.ZrIndex) { diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs index 3d4204fc1..82cb29d73 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs @@ -69,7 +69,7 @@ public static void RewriteInstruction( asm.LdrRiUn(Register((int)rd), Register(regAlloc.FixedContextRegister), NativeContextOffsets.TpidrEl0Offset); } } - else if ((encoding & ~0x1f) == 0xd53b0020 && IsAppleOS()) // mrs x0, ctr_el0 + else if ((encoding & ~0x1f) == 0xd53b0020 && IsCtrEl0AccessForbidden()) // mrs x0, ctr_el0 { uint rd = encoding & 0x1f; @@ -115,7 +115,7 @@ public static bool NeedsCall(uint encoding) { return true; } - else if ((encoding & ~0x1f) == 0xd53b0020 && IsAppleOS()) // mrs x0, ctr_el0 + else if ((encoding & ~0x1f) == 0xd53b0020 && IsCtrEl0AccessForbidden()) // mrs x0, ctr_el0 { return true; } @@ -127,9 +127,16 @@ public static bool NeedsCall(uint encoding) return false; } - private static bool IsAppleOS() + private static bool IsCtrEl0AccessForbidden() { - return OperatingSystem.IsMacOS() || OperatingSystem.IsIOS(); + // Only Linux allows accessing CTR_EL0 from user mode. + return OperatingSystem.IsWindows() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS(); + } + + public static bool IsCacheInstForbidden(uint encoding) + { + // Windows does not allow the cache maintenance instructions to be used from user mode. + return OperatingSystem.IsWindows() && SysUtils.IsCacheInstUciTrapped(encoding); } public static bool NeedsContextStoreLoad(InstName name) From 3924bd1a4364455ab8a5747e3cb0b3000dbaa589 Mon Sep 17 00:00:00 2001 From: TSRBerry <20988865+TSRBerry@users.noreply.github.com> Date: Fri, 8 Mar 2024 17:16:32 +0100 Subject: [PATCH 099/126] Update dependencies from SixLabors to the latest version before the license change (#6440) * nuget: bump SixLabors.ImageSharp from 1.0.4 to 2.1.3 (#3976) * nuget: bump SixLabors.ImageSharp from 1.0.4 to 2.1.3 Bumps [SixLabors.ImageSharp](https://github.com/SixLabors/ImageSharp) from 1.0.4 to 2.1.3. - [Release notes](https://github.com/SixLabors/ImageSharp/releases) - [Commits](https://github.com/SixLabors/ImageSharp/compare/v1.0.4...v2.1.3) --- updated-dependencies: - dependency-name: SixLabors.ImageSharp dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Update for 2.x changes Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mary * Update SixLabors.ImageSharp to 2.1.7 This is the latest version we can update to without the license change. * Update SixLabors.ImageSharp.Drawing to v1.0.0 This is the latest version we can update to without the license change. --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mary --- Directory.Packages.props | 4 +- .../SoftwareKeyboardRendererBase.cs | 38 +++++++++---------- src/Ryujinx/UI/Windows/IconColorPicker.cs | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 412b33a6e..00e6a25b0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -42,8 +42,8 @@ - - + + diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs index 75c648ff1..0b87f87ad 100644 --- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs @@ -44,10 +44,10 @@ internal class SoftwareKeyboardRendererBase private readonly Color _textSelectedColor; private readonly Color _textOverCursorColor; - private readonly IBrush _panelBrush; - private readonly IBrush _disabledBrush; - private readonly IBrush _cursorBrush; - private readonly IBrush _selectionBoxBrush; + private readonly Brush _panelBrush; + private readonly Brush _disabledBrush; + private readonly Brush _cursorBrush; + private readonly Brush _selectionBoxBrush; private readonly Pen _textBoxOutlinePen; private readonly Pen _cursorPen; @@ -97,10 +97,10 @@ public SoftwareKeyboardRendererBase(IHostUITheme uiTheme) _cursorBrush = new SolidBrush(_textNormalColor); _selectionBoxBrush = new SolidBrush(selectionBackgroundColor); - _textBoxOutlinePen = new Pen(borderColor, _textBoxOutlineWidth); - _cursorPen = new Pen(_textNormalColor, cursorWidth); - _selectionBoxPen = new Pen(selectionBackgroundColor, cursorWidth); - _padPressedPen = new Pen(borderColor, _padPressedPenWidth); + _textBoxOutlinePen = Pens.Solid(borderColor, _textBoxOutlineWidth); + _cursorPen = Pens.Solid(_textNormalColor, cursorWidth); + _selectionBoxPen = Pens.Solid(selectionBackgroundColor, cursorWidth); + _padPressedPen = Pens.Solid(borderColor, _padPressedPenWidth); _inputTextFontSize = 20; @@ -178,7 +178,7 @@ private static Image LoadResource(Stream resourceStream, int newWidth, int newHe private static void SetGraphicsOptions(IImageProcessingContext context) { context.GetGraphicsOptions().Antialias = true; - context.GetShapeGraphicsOptions().GraphicsOptions.Antialias = true; + context.GetDrawingOptions().GraphicsOptions.Antialias = true; } private void DrawImmutableElements() @@ -293,31 +293,31 @@ private void ComputeConstants() } private static RectangleF MeasureString(string text, Font font) { - RendererOptions options = new(font); + TextOptions options = new(font); if (text == "") { - FontRectangle emptyRectangle = TextMeasurer.Measure(" ", options); + FontRectangle emptyRectangle = TextMeasurer.MeasureSize(" ", options); return new RectangleF(0, emptyRectangle.Y, 0, emptyRectangle.Height); } - FontRectangle rectangle = TextMeasurer.Measure(text, options); + FontRectangle rectangle = TextMeasurer.MeasureSize(text, options); return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height); } private static RectangleF MeasureString(ReadOnlySpan text, Font font) { - RendererOptions options = new(font); + TextOptions options = new(font); if (text == "") { - FontRectangle emptyRectangle = TextMeasurer.Measure(" ", options); + FontRectangle emptyRectangle = TextMeasurer.MeasureSize(" ", options); return new RectangleF(0, emptyRectangle.Y, 0, emptyRectangle.Height); } - FontRectangle rectangle = TextMeasurer.Measure(text, options); + FontRectangle rectangle = TextMeasurer.MeasureSize(text, options); return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height); } @@ -350,7 +350,7 @@ private void DrawTextBox(IImageProcessingContext context, SoftwareKeyboardUIStat // Draw the cursor on top of the text and redraw the text with a different color if necessary. Color cursorTextColor; - IBrush cursorBrush; + Brush cursorBrush; Pen cursorPen; float cursorPositionYTop = inputTextY + 1; @@ -435,7 +435,7 @@ private void DrawTextBox(IImageProcessingContext context, SoftwareKeyboardUIStat new PointF(cursorPositionXLeft, cursorPositionYBottom), }; - context.DrawLines(cursorPen, points); + context.DrawLine(cursorPen, points); } else { @@ -562,12 +562,12 @@ public void CopyImageToBuffer() // Convert the pixel format used in the image to the one used in the Switch surface. - if (!_surface.TryGetSinglePixelSpan(out Span pixels)) + if (!_surface.DangerousTryGetSinglePixelMemory(out Memory pixels)) { return; } - _bufferData = MemoryMarshal.AsBytes(pixels).ToArray(); + _bufferData = MemoryMarshal.AsBytes(pixels.Span).ToArray(); Span dataConvert = MemoryMarshal.Cast(_bufferData); Debug.Assert(_bufferData.Length == _surfaceInfo.Size); diff --git a/src/Ryujinx/UI/Windows/IconColorPicker.cs b/src/Ryujinx/UI/Windows/IconColorPicker.cs index 4c75a5ff9..72660351a 100644 --- a/src/Ryujinx/UI/Windows/IconColorPicker.cs +++ b/src/Ryujinx/UI/Windows/IconColorPicker.cs @@ -127,7 +127,7 @@ public static Color GetColor(Image image) public static Bgra32[] GetBuffer(Image image) { - return image.TryGetSinglePixelSpan(out var data) ? data.ToArray() : Array.Empty(); + return image.DangerousTryGetSinglePixelMemory(out var data) ? data.ToArray() : Array.Empty(); } private static int GetColorScore(Dictionary dominantColorBin, int maxHitCount, PaletteColor color) From a3a63d43948b79450d1a0ee963ea4796cb3532a0 Mon Sep 17 00:00:00 2001 From: jhorv <38920027+jhorv@users.noreply.github.com> Date: Sat, 9 Mar 2024 19:01:51 -0500 Subject: [PATCH 100/126] Refactor memory managers to a common base class, consolidate Read() method logic (#6360) * - add new abstract class `VirtualMemoryManagerBase` - rename `MemoryManagerBase` to `VirtualMemoryManagerRefCountedBase` and derive from `VirtualMemoryManagerBase` - change `AddressSpaceManager`, `HvMemoryManager`, `MemoryManager`, and `MemoryManagerHostMapped` to implement abstract members and use the inherited `void VirtualMemoryManagerBase.Read(TVirtual va, Span data)` implementation. * move property `AddressSpaceSize` up by the other properties --- src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs | 114 ++++-------------- src/Ryujinx.Cpu/Jit/MemoryManager.cs | 107 ++++------------ .../Jit/MemoryManagerHostMapped.cs | 54 ++------- ... => VirtualMemoryManagerRefCountedBase.cs} | 5 +- src/Ryujinx.Memory/AddressSpaceManager.cs | 85 ++----------- .../VirtualMemoryManagerBase.cs | 91 ++++++++++++++ 6 files changed, 161 insertions(+), 295 deletions(-) rename src/Ryujinx.Cpu/{MemoryManagerBase.cs => VirtualMemoryManagerRefCountedBase.cs} (70%) create mode 100644 src/Ryujinx.Memory/VirtualMemoryManagerBase.cs diff --git a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs index 2f9743ab4..6e864f4ca 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs @@ -16,12 +16,8 @@ namespace Ryujinx.Cpu.AppleHv /// Represents a CPU memory manager which maps guest virtual memory directly onto the Hypervisor page table. ///
[SupportedOSPlatform("macos")] - public class HvMemoryManager : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock + public class HvMemoryManager : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock { - public const int PageBits = 12; - public const int PageSize = 1 << PageBits; - public const int PageMask = PageSize - 1; - public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry. public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set. @@ -39,8 +35,6 @@ private enum HostMappedPtBits : ulong private readonly InvalidAccessHandler _invalidAccessHandler; - private readonly ulong _addressSpaceSize; - private readonly HvAddressSpace _addressSpace; internal HvAddressSpace AddressSpace => _addressSpace; @@ -62,6 +56,8 @@ private enum HostMappedPtBits : ulong public event Action UnmapEvent; + protected override ulong AddressSpaceSize { get; } + /// /// Creates a new instance of the Hypervisor memory manager. /// @@ -73,7 +69,7 @@ public HvMemoryManager(MemoryBlock backingMemory, ulong addressSpaceSize, Invali _backingMemory = backingMemory; _pageTable = new PageTable(); _invalidAccessHandler = invalidAccessHandler; - _addressSpaceSize = addressSpaceSize; + AddressSpaceSize = addressSpaceSize; ulong asSize = PageSize; int asBits = PageBits; @@ -92,42 +88,6 @@ public HvMemoryManager(MemoryBlock backingMemory, ulong addressSpaceSize, Invali Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler); } - /// - /// Checks if the virtual address is part of the addressable space. - /// - /// Virtual address - /// True if the virtual address is part of the addressable space - private bool ValidateAddress(ulong va) - { - return va < _addressSpaceSize; - } - - /// - /// Checks if the combination of virtual address and size is part of the addressable space. - /// - /// Virtual address of the range - /// Size of the range in bytes - /// True if the combination of virtual address and size is part of the addressable space - private bool ValidateAddressAndSize(ulong va, ulong size) - { - ulong endVa = va + size; - return endVa >= va && endVa >= size && endVa <= _addressSpaceSize; - } - - /// - /// Ensures the combination of virtual address and size is part of the addressable space. - /// - /// Virtual address of the range - /// Size of the range in bytes - /// Throw when the memory region specified outside the addressable space - private void AssertValidAddressAndSize(ulong va, ulong size) - { - if (!ValidateAddressAndSize(va, size)) - { - throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}"); - } - } - /// public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags) { @@ -209,9 +169,19 @@ public T ReadTracked(ulong va) where T : unmanaged } /// - public void Read(ulong va, Span data) + public override void Read(ulong va, Span data) { - ReadImpl(va, data); + try + { + base.Read(va, data); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } } /// @@ -340,7 +310,7 @@ public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) { Span data = new byte[size]; - ReadImpl(va, data); + base.Read(va, data); return data; } @@ -367,7 +337,7 @@ public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false { Memory memory = new byte[size]; - ReadImpl(va, memory.Span); + base.Read(va, memory.Span); return new WritableRegion(this, va, memory); } @@ -576,48 +546,6 @@ private List GetPhysicalRegionsImpl(ulong va, ulong size) return regions; } - private void ReadImpl(ulong va, Span data) - { - if (data.Length == 0) - { - return; - } - - try - { - AssertValidAddressAndSize(va, (ulong)data.Length); - - int offset = 0, size; - - if ((va & PageMask) != 0) - { - ulong pa = GetPhysicalAddressChecked(va); - - size = Math.Min(data.Length, PageSize - (int)(va & PageMask)); - - _backingMemory.GetSpan(pa, size).CopyTo(data[..size]); - - offset += size; - } - - for (; offset < data.Length; offset += size) - { - ulong pa = GetPhysicalAddressChecked(va + (ulong)offset); - - size = Math.Min(data.Length - offset, PageSize); - - _backingMemory.GetSpan(pa, size).CopyTo(data.Slice(offset, size)); - } - } - catch (InvalidMemoryRegionException) - { - if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) - { - throw; - } - } - } - /// /// /// This function also validates that the given range is both valid and mapped, and will throw if it is not. @@ -936,6 +864,10 @@ protected override void Destroy() _addressSpace.Dispose(); } - private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message); + protected override Span GetPhysicalAddressSpan(ulong pa, int size) + => _backingMemory.GetSpan(pa, size); + + protected override ulong TranslateVirtualAddressForRead(ulong va) + => GetPhysicalAddressChecked(va); } } diff --git a/src/Ryujinx.Cpu/Jit/MemoryManager.cs b/src/Ryujinx.Cpu/Jit/MemoryManager.cs index b9a547025..bbfdf536e 100644 --- a/src/Ryujinx.Cpu/Jit/MemoryManager.cs +++ b/src/Ryujinx.Cpu/Jit/MemoryManager.cs @@ -14,12 +14,8 @@ namespace Ryujinx.Cpu.Jit /// /// Represents a CPU memory manager. /// - public sealed class MemoryManager : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock + public sealed class MemoryManager : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock { - public const int PageBits = 12; - public const int PageSize = 1 << PageBits; - public const int PageMask = PageSize - 1; - private const int PteSize = 8; private const int PointerTagBit = 62; @@ -35,8 +31,6 @@ public sealed class MemoryManager : MemoryManagerBase, IMemoryManager, IVirtualM ///
public int AddressSpaceBits { get; } - private readonly ulong _addressSpaceSize; - private readonly MemoryBlock _pageTable; /// @@ -50,6 +44,8 @@ public sealed class MemoryManager : MemoryManagerBase, IMemoryManager, IVirtualM public event Action UnmapEvent; + protected override ulong AddressSpaceSize { get; } + /// /// Creates a new instance of the memory manager. /// @@ -71,7 +67,7 @@ public MemoryManager(MemoryBlock backingMemory, ulong addressSpaceSize, InvalidA } AddressSpaceBits = asBits; - _addressSpaceSize = asSize; + AddressSpaceSize = asSize; _pageTable = new MemoryBlock((asSize / PageSize) * PteSize); Tracking = new MemoryTracking(this, PageSize); @@ -153,9 +149,19 @@ public T ReadTracked(ulong va) where T : unmanaged } /// - public void Read(ulong va, Span data) + public override void Read(ulong va, Span data) { - ReadImpl(va, data); + try + { + base.Read(va, data); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } } /// @@ -290,7 +296,7 @@ public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) { Span data = new byte[size]; - ReadImpl(va, data); + base.Read(va, data); return data; } @@ -462,48 +468,6 @@ private List GetPhysicalRegionsImpl(ulong va, ulong size) return regions; } - private void ReadImpl(ulong va, Span data) - { - if (data.Length == 0) - { - return; - } - - try - { - AssertValidAddressAndSize(va, (ulong)data.Length); - - int offset = 0, size; - - if ((va & PageMask) != 0) - { - ulong pa = GetPhysicalAddressInternal(va); - - size = Math.Min(data.Length, PageSize - (int)(va & PageMask)); - - _backingMemory.GetSpan(pa, size).CopyTo(data[..size]); - - offset += size; - } - - for (; offset < data.Length; offset += size) - { - ulong pa = GetPhysicalAddressInternal(va + (ulong)offset); - - size = Math.Min(data.Length - offset, PageSize); - - _backingMemory.GetSpan(pa, size).CopyTo(data.Slice(offset, size)); - } - } - catch (InvalidMemoryRegionException) - { - if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) - { - throw; - } - } - } - /// public bool IsRangeMapped(ulong va, ulong size) { @@ -544,37 +508,6 @@ public bool IsMapped(ulong va) return _pageTable.Read((va / PageSize) * PteSize) != 0; } - private bool ValidateAddress(ulong va) - { - return va < _addressSpaceSize; - } - - /// - /// Checks if the combination of virtual address and size is part of the addressable space. - /// - /// Virtual address of the range - /// Size of the range in bytes - /// True if the combination of virtual address and size is part of the addressable space - private bool ValidateAddressAndSize(ulong va, ulong size) - { - ulong endVa = va + size; - return endVa >= va && endVa >= size && endVa <= _addressSpaceSize; - } - - /// - /// Ensures the combination of virtual address and size is part of the addressable space. - /// - /// Virtual address of the range - /// Size of the range in bytes - /// Throw when the memory region specified outside the addressable space - private void AssertValidAddressAndSize(ulong va, ulong size) - { - if (!ValidateAddressAndSize(va, size)) - { - throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}"); - } - } - private ulong GetPhysicalAddressInternal(ulong va) { return PteToPa(_pageTable.Read((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask); @@ -691,5 +624,11 @@ private ulong PteToPa(ulong pte) /// Disposes of resources used by the memory manager. /// protected override void Destroy() => _pageTable.Dispose(); + + protected override Span GetPhysicalAddressSpan(ulong pa, int size) + => _backingMemory.GetSpan(pa, size); + + protected override ulong TranslateVirtualAddressForRead(ulong va) + => GetPhysicalAddressInternal(va); } } diff --git a/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs b/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs index 2b315e841..0b6ba260a 100644 --- a/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs +++ b/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs @@ -13,12 +13,8 @@ namespace Ryujinx.Cpu.Jit /// /// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region. /// - public sealed class MemoryManagerHostMapped : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock + public sealed class MemoryManagerHostMapped : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock { - public const int PageBits = 12; - public const int PageSize = 1 << PageBits; - public const int PageMask = PageSize - 1; - public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry. public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set. @@ -39,8 +35,6 @@ private enum HostMappedPtBits : ulong private readonly AddressSpace _addressSpace; - public ulong AddressSpaceSize { get; } - private readonly PageTable _pageTable; private readonly MemoryEhMeilleure _memoryEh; @@ -60,6 +54,8 @@ private enum HostMappedPtBits : ulong public event Action UnmapEvent; + protected override ulong AddressSpaceSize { get; } + /// /// Creates a new instance of the host mapped memory manager. /// @@ -91,42 +87,6 @@ public MemoryManagerHostMapped(AddressSpace addressSpace, bool unsafeMode, Inval _memoryEh = new MemoryEhMeilleure(_addressSpace.Base, _addressSpace.Mirror, Tracking); } - /// - /// Checks if the virtual address is part of the addressable space. - /// - /// Virtual address - /// True if the virtual address is part of the addressable space - private bool ValidateAddress(ulong va) - { - return va < AddressSpaceSize; - } - - /// - /// Checks if the combination of virtual address and size is part of the addressable space. - /// - /// Virtual address of the range - /// Size of the range in bytes - /// True if the combination of virtual address and size is part of the addressable space - private bool ValidateAddressAndSize(ulong va, ulong size) - { - ulong endVa = va + size; - return endVa >= va && endVa >= size && endVa <= AddressSpaceSize; - } - - /// - /// Ensures the combination of virtual address and size is part of the addressable space. - /// - /// Virtual address of the range - /// Size of the range in bytes - /// Throw when the memory region specified outside the addressable space - private void AssertValidAddressAndSize(ulong va, ulong size) - { - if (!ValidateAddressAndSize(va, size)) - { - throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}"); - } - } - /// /// Ensures the combination of virtual address and size is part of the addressable space and fully mapped. /// @@ -235,7 +195,7 @@ public T ReadTracked(ulong va) where T : unmanaged } /// - public void Read(ulong va, Span data) + public override void Read(ulong va, Span data) { try { @@ -816,6 +776,10 @@ protected override void Destroy() _memoryEh.Dispose(); } - private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message); + protected override Span GetPhysicalAddressSpan(ulong pa, int size) + => _addressSpace.Mirror.GetSpan(pa, size); + + protected override ulong TranslateVirtualAddressForRead(ulong va) + => va; } } diff --git a/src/Ryujinx.Cpu/MemoryManagerBase.cs b/src/Ryujinx.Cpu/VirtualMemoryManagerRefCountedBase.cs similarity index 70% rename from src/Ryujinx.Cpu/MemoryManagerBase.cs rename to src/Ryujinx.Cpu/VirtualMemoryManagerRefCountedBase.cs index 3288e3a49..c2d8cfb1a 100644 --- a/src/Ryujinx.Cpu/MemoryManagerBase.cs +++ b/src/Ryujinx.Cpu/VirtualMemoryManagerRefCountedBase.cs @@ -1,10 +1,13 @@ using Ryujinx.Memory; using System.Diagnostics; +using System.Numerics; using System.Threading; namespace Ryujinx.Cpu { - public abstract class MemoryManagerBase : IRefCounted + public abstract class VirtualMemoryManagerRefCountedBase : VirtualMemoryManagerBase, IRefCounted + where TVirtual : IBinaryInteger + where TPhysical : IBinaryInteger { private int _referenceCount; diff --git a/src/Ryujinx.Memory/AddressSpaceManager.cs b/src/Ryujinx.Memory/AddressSpaceManager.cs index 021d33663..b953eb306 100644 --- a/src/Ryujinx.Memory/AddressSpaceManager.cs +++ b/src/Ryujinx.Memory/AddressSpaceManager.cs @@ -11,12 +11,8 @@ namespace Ryujinx.Memory /// Represents a address space manager. /// Supports virtual memory region mapping, address translation and read/write access to mapped regions. ///
- public sealed class AddressSpaceManager : IVirtualMemoryManager, IWritableBlock + public sealed class AddressSpaceManager : VirtualMemoryManagerBase, IVirtualMemoryManager, IWritableBlock { - public const int PageBits = PageTable.PageBits; - public const int PageSize = PageTable.PageSize; - public const int PageMask = PageTable.PageMask; - /// public bool Supports4KBPages => true; @@ -25,11 +21,11 @@ public sealed class AddressSpaceManager : IVirtualMemoryManager, IWritableBlock /// public int AddressSpaceBits { get; } - private readonly ulong _addressSpaceSize; - private readonly MemoryBlock _backingMemory; private readonly PageTable _pageTable; + protected override ulong AddressSpaceSize { get; } + /// /// Creates a new instance of the memory manager. /// @@ -47,7 +43,7 @@ public AddressSpaceManager(MemoryBlock backingMemory, ulong addressSpaceSize) } AddressSpaceBits = asBits; - _addressSpaceSize = asSize; + AddressSpaceSize = asSize; _backingMemory = backingMemory; _pageTable = new PageTable(); } @@ -102,12 +98,6 @@ public T Read(ulong va) where T : unmanaged return MemoryMarshal.Cast(GetSpan(va, Unsafe.SizeOf()))[0]; } - /// - public void Read(ulong va, Span data) - { - ReadImpl(va, data); - } - /// public void Write(ulong va, T value) where T : unmanaged { @@ -174,7 +164,7 @@ public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) { Span data = new byte[size]; - ReadImpl(va, data); + Read(va, data); return data; } @@ -346,34 +336,6 @@ private List GetHostRegionsImpl(ulong va, ulong size) return regions; } - private void ReadImpl(ulong va, Span data) - { - if (data.Length == 0) - { - return; - } - - AssertValidAddressAndSize(va, (ulong)data.Length); - - int offset = 0, size; - - if ((va & PageMask) != 0) - { - size = Math.Min(data.Length, PageSize - (int)(va & PageMask)); - - GetHostSpanContiguous(va, size).CopyTo(data[..size]); - - offset += size; - } - - for (; offset < data.Length; offset += size) - { - size = Math.Min(data.Length - offset, PageSize); - - GetHostSpanContiguous(va + (ulong)offset, size).CopyTo(data.Slice(offset, size)); - } - } - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsMapped(ulong va) @@ -414,37 +376,6 @@ public bool IsRangeMapped(ulong va, ulong size) return true; } - private bool ValidateAddress(ulong va) - { - return va < _addressSpaceSize; - } - - /// - /// Checks if the combination of virtual address and size is part of the addressable space. - /// - /// Virtual address of the range - /// Size of the range in bytes - /// True if the combination of virtual address and size is part of the addressable space - private bool ValidateAddressAndSize(ulong va, ulong size) - { - ulong endVa = va + size; - return endVa >= va && endVa >= size && endVa <= _addressSpaceSize; - } - - /// - /// Ensures the combination of virtual address and size is part of the addressable space. - /// - /// Virtual address of the range - /// Size of the range in bytes - /// Throw when the memory region specified outside the addressable space - private void AssertValidAddressAndSize(ulong va, ulong size) - { - if (!ValidateAddressAndSize(va, size)) - { - throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}"); - } - } - private unsafe Span GetHostSpanContiguous(ulong va, int size) { return new Span((void*)GetHostAddress(va), size); @@ -471,5 +402,11 @@ public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise { // Only the ARM Memory Manager has tracking for now. } + + protected override unsafe Span GetPhysicalAddressSpan(nuint pa, int size) + => new((void*)pa, size); + + protected override nuint TranslateVirtualAddressForRead(ulong va) + => GetHostAddress(va); } } diff --git a/src/Ryujinx.Memory/VirtualMemoryManagerBase.cs b/src/Ryujinx.Memory/VirtualMemoryManagerBase.cs new file mode 100644 index 000000000..cbec88cc5 --- /dev/null +++ b/src/Ryujinx.Memory/VirtualMemoryManagerBase.cs @@ -0,0 +1,91 @@ +using System; +using System.Numerics; + +namespace Ryujinx.Memory +{ + public abstract class VirtualMemoryManagerBase + where TVirtual : IBinaryInteger + where TPhysical : IBinaryInteger + { + public const int PageBits = 12; + public const int PageSize = 1 << PageBits; + public const int PageMask = PageSize - 1; + + protected abstract TVirtual AddressSpaceSize { get; } + + public virtual void Read(TVirtual va, Span data) + { + if (data.Length == 0) + { + return; + } + + AssertValidAddressAndSize(va, TVirtual.CreateChecked(data.Length)); + + int offset = 0, size; + + if ((int.CreateTruncating(va) & PageMask) != 0) + { + TPhysical pa = TranslateVirtualAddressForRead(va); + + size = Math.Min(data.Length, PageSize - ((int.CreateTruncating(va) & PageMask))); + + GetPhysicalAddressSpan(pa, size).CopyTo(data[..size]); + + offset += size; + } + + for (; offset < data.Length; offset += size) + { + TPhysical pa = TranslateVirtualAddressForRead(va + TVirtual.CreateChecked(offset)); + + size = Math.Min(data.Length - offset, PageSize); + + GetPhysicalAddressSpan(pa, size).CopyTo(data.Slice(offset, size)); + } + } + + /// + /// Ensures the combination of virtual address and size is part of the addressable space. + /// + /// Virtual address of the range + /// Size of the range in bytes + /// Throw when the memory region specified outside the addressable space + protected void AssertValidAddressAndSize(TVirtual va, TVirtual size) + { + if (!ValidateAddressAndSize(va, size)) + { + throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}"); + } + } + + protected abstract Span GetPhysicalAddressSpan(TPhysical pa, int size); + + protected abstract TPhysical TranslateVirtualAddressForRead(TVirtual va); + + /// + /// Checks if the virtual address is part of the addressable space. + /// + /// Virtual address + /// True if the virtual address is part of the addressable space + protected bool ValidateAddress(TVirtual va) + { + return va < AddressSpaceSize; + } + + /// + /// Checks if the combination of virtual address and size is part of the addressable space. + /// + /// Virtual address of the range + /// Size of the range in bytes + /// True if the combination of virtual address and size is part of the addressable space + protected bool ValidateAddressAndSize(TVirtual va, TVirtual size) + { + TVirtual endVa = va + size; + return endVa >= va && endVa >= size && endVa <= AddressSpaceSize; + } + + protected static void ThrowInvalidMemoryRegionException(string message) + => throw new InvalidMemoryRegionException(message); + } +} From 5a900f38c52269ee1282695e5e62a05269d0a478 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sun, 10 Mar 2024 21:16:40 -0300 Subject: [PATCH 101/126] Fix lost copy and swap problem on shader SSA deconstruction (#6455) * Fix lost copy on shader SSA deconstruction * Shader cache version bump --- .../Shader/DiskCache/DiskCacheHostStorage.cs | 2 +- src/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index c5763b025..4a00d4d8e 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs @@ -22,7 +22,7 @@ class DiskCacheHostStorage private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMinor = 2; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; - private const uint CodeGenVersion = 6253; + private const uint CodeGenVersion = 6455; private const string SharedTocFileName = "shared.toc"; private const string SharedDataFileName = "shared.data"; diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs index 8b1cb9c56..90f1f2f6d 100644 --- a/src/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs @@ -24,17 +24,21 @@ public static void Remove(BasicBlock[] blocks) continue; } + Operand temp = OperandHelper.Local(); + for (int index = 0; index < phi.SourcesCount; index++) { Operand src = phi.GetSource(index); - BasicBlock srcBlock = phi.GetBlock(index); - Operation copyOp = new(Instruction.Copy, phi.Dest, src); + Operation copyOp = new(Instruction.Copy, temp, src); srcBlock.Append(copyOp); } + Operation copyOp2 = new(Instruction.Copy, phi.Dest, temp); + + nextNode = block.Operations.AddAfter(node, copyOp2).Next; block.Operations.Remove(node); node = nextNode; From 8354434a37abe28e587a3f515b1e2009d1b4e8c2 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 12 Mar 2024 00:21:39 +0000 Subject: [PATCH 102/126] Passthrough mouse for win32 (#6450) * passthrough mouse for win32 * remove unused enums --- src/Ryujinx/UI/Helpers/Win32NativeInterop.cs | 25 +++---- src/Ryujinx/UI/Renderer/EmbeddedWindow.cs | 78 ++------------------ 2 files changed, 14 insertions(+), 89 deletions(-) diff --git a/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs b/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs index 4834df802..fce2d518a 100644 --- a/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs +++ b/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs @@ -8,6 +8,8 @@ namespace Ryujinx.Ava.UI.Helpers [SupportedOSPlatform("windows")] internal partial class Win32NativeInterop { + internal const int GWLP_WNDPROC = -4; + [Flags] public enum ClassStyles : uint { @@ -29,22 +31,7 @@ public enum Cursors : uint [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] public enum WindowsMessages : uint { - Mousemove = 0x0200, - Lbuttondown = 0x0201, - Lbuttonup = 0x0202, - Lbuttondblclk = 0x0203, - Rbuttondown = 0x0204, - Rbuttonup = 0x0205, - Rbuttondblclk = 0x0206, - Mbuttondown = 0x0207, - Mbuttonup = 0x0208, - Mbuttondblclk = 0x0209, - Mousewheel = 0x020A, - Xbuttondown = 0x020B, - Xbuttonup = 0x020C, - Xbuttondblclk = 0x020D, - Mousehwheel = 0x020E, - Mouselast = 0x020E, + NcHitTest = 0x0084, } [UnmanagedFunctionPointer(CallingConvention.Winapi)] @@ -121,5 +108,11 @@ public static partial IntPtr CreateWindowEx( IntPtr hMenu, IntPtr hInstance, IntPtr lpParam); + + [LibraryImport("user32.dll", SetLastError = true)] + public static partial IntPtr SetWindowLongPtrW(IntPtr hWnd, int nIndex, IntPtr value); + + [LibraryImport("user32.dll", SetLastError = true)] + public static partial IntPtr SetWindowLongW(IntPtr hWnd, int nIndex, int value); } } diff --git a/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs b/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs index 3bf19b43e..8c5e31fff 100644 --- a/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs +++ b/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs @@ -141,80 +141,10 @@ IPlatformHandle CreateWin32(IPlatformHandle control) _wndProcDelegate = delegate (IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam) { - if (VisualRoot != null) + switch (msg) { - if (msg == WindowsMessages.Lbuttondown || - msg == WindowsMessages.Rbuttondown || - msg == WindowsMessages.Lbuttonup || - msg == WindowsMessages.Rbuttonup || - msg == WindowsMessages.Mousemove) - { - Point rootVisualPosition = this.TranslatePoint(new Point((long)lParam & 0xFFFF, (long)lParam >> 16 & 0xFFFF), this).Value; - Pointer pointer = new(0, PointerType.Mouse, true); - -#pragma warning disable CS0618 // Type or member is obsolete (As of Avalonia 11, the constructors for PointerPressedEventArgs & PointerEventArgs are marked as obsolete) - switch (msg) - { - case WindowsMessages.Lbuttondown: - case WindowsMessages.Rbuttondown: - { - bool isLeft = msg == WindowsMessages.Lbuttondown; - RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton; - PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonPressed : PointerUpdateKind.RightButtonPressed); - - var evnt = new PointerPressedEventArgs( - this, - pointer, - this, - rootVisualPosition, - (ulong)Environment.TickCount64, - properties, - KeyModifiers.None); - - RaiseEvent(evnt); - - break; - } - case WindowsMessages.Lbuttonup: - case WindowsMessages.Rbuttonup: - { - bool isLeft = msg == WindowsMessages.Lbuttonup; - RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton; - PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonReleased : PointerUpdateKind.RightButtonReleased); - - var evnt = new PointerReleasedEventArgs( - this, - pointer, - this, - rootVisualPosition, - (ulong)Environment.TickCount64, - properties, - KeyModifiers.None, - isLeft ? MouseButton.Left : MouseButton.Right); - - RaiseEvent(evnt); - - break; - } - case WindowsMessages.Mousemove: - { - var evnt = new PointerEventArgs( - PointerMovedEvent, - this, - pointer, - this, - rootVisualPosition, - (ulong)Environment.TickCount64, - new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.Other), - KeyModifiers.None); - - RaiseEvent(evnt); - - break; - } - } -#pragma warning restore CS0618 - } + case WindowsMessages.NcHitTest: + return -1; } return DefWindowProc(hWnd, msg, wParam, lParam); @@ -234,6 +164,8 @@ IPlatformHandle CreateWin32(IPlatformHandle control) WindowHandle = CreateWindowEx(0, _className, "NativeWindow", WindowStyles.WsChild, 0, 0, 640, 480, control.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + SetWindowLongPtrW(control.Handle, GWLP_WNDPROC, wndClassEx.lpfnWndProc); + Marshal.FreeHGlobal(wndClassEx.lpszClassName); return new PlatformHandle(WindowHandle, "HWND"); From d9a18919b051a3cfa4b3c9f5f2a2fb0e8eecfcd5 Mon Sep 17 00:00:00 2001 From: Nicolas Abram Date: Wed, 13 Mar 2024 17:26:19 -0300 Subject: [PATCH 103/126] Fix geometry shader passthrough issue (#6462) * Fix geometry shader passthrough issue (Diagnosed by gdkchan) * Fix whitespace formatting * Fix whitespace formatting * Bump shader cache version * Don't apply PassthroughNV decorations to output geometry shader variables --- .../Shader/DiskCache/DiskCacheHostStorage.cs | 2 +- .../CodeGen/Spirv/Declarations.cs | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index 4a00d4d8e..5036186ba 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs @@ -22,7 +22,7 @@ class DiskCacheHostStorage private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMinor = 2; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; - private const uint CodeGenVersion = 6455; + private const uint CodeGenVersion = 6462; private const string SharedTocFileName = "shared.toc"; private const string SharedDataFileName = "shared.data"; diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs index 4ff61d9f2..b74824255 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs @@ -356,6 +356,16 @@ private static void DeclarePerVertexBlock(CodeGenContext context) context.AddGlobalVariable(perVertexInputVariable); context.Inputs.Add(new IoDefinition(StorageKind.Input, IoVariable.Position), perVertexInputVariable); + + if (context.Definitions.Stage == ShaderStage.Geometry && + context.Definitions.GpPassthrough && + context.HostCapabilities.SupportsGeometryShaderPassthrough) + { + context.MemberDecorate(perVertexInputStructType, 0, Decoration.PassthroughNV); + context.MemberDecorate(perVertexInputStructType, 1, Decoration.PassthroughNV); + context.MemberDecorate(perVertexInputStructType, 2, Decoration.PassthroughNV); + context.MemberDecorate(perVertexInputStructType, 3, Decoration.PassthroughNV); + } } var perVertexOutputStructType = CreatePerVertexStructType(context); From e2a655f1a46252b67ded0c3280909fa81bbfe542 Mon Sep 17 00:00:00 2001 From: Keaton Date: Wed, 13 Mar 2024 15:39:39 -0500 Subject: [PATCH 104/126] Update AutoDeleteCache.cs (#6471) Increase the texture cache limit from 512 MB to 1 GB. --- src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs b/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs index 05782605b..732ec5d4c 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs @@ -46,7 +46,7 @@ class AutoDeleteCache : IEnumerable { private const int MinCountForDeletion = 32; private const int MaxCapacity = 2048; - private const ulong MaxTextureSizeCapacity = 512 * 1024 * 1024; // MB; + private const ulong MaxTextureSizeCapacity = 1024 * 1024 * 1024; // MB; private readonly LinkedList _textures; private ulong _totalSize; From 6b4ee82e5d4a4261de1e95d8d4c9df55928527f6 Mon Sep 17 00:00:00 2001 From: TSRBerry <20988865+TSRBerry@users.noreply.github.com> Date: Wed, 13 Mar 2024 23:26:35 +0100 Subject: [PATCH 105/126] infra: Fix updater for old Ava users (#6441) * Add binaries with both names to release archives * Add migration code for the new filename * Add Ryujinx.Ava to all win/linux releases for a while --- .github/workflows/release.yml | 19 ++++------ src/Ryujinx/Modules/Updater/Updater.cs | 48 ++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9843487a3..f2bebc77f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -71,7 +71,7 @@ jobs: - uses: actions/setup-dotnet@v4 with: global-json-file: global.json - + - name: Overwrite csc problem matcher run: echo "::add-matcher::.github/csc.json" @@ -104,37 +104,30 @@ jobs: if: matrix.platform.os == 'windows-latest' run: | pushd publish_ava + cp publish/Ryujinx.exe publish/Ryujinx.Ava.exe 7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish + 7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish popd pushd publish_sdl2_headless 7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish popd - - pushd publish_ava - mv publish/Ryujinx.exe publish/Ryujinx.Ava.exe - 7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish - popd shell: bash - name: Packing Linux builds if: matrix.platform.os == 'ubuntu-latest' run: | pushd publish_ava - chmod +x publish/Ryujinx.sh publish/Ryujinx + cp publish/Ryujinx publish/Ryujinx.Ava + chmod +x publish/Ryujinx.sh publish/Ryujinx.Ava publish/Ryujinx tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish + tar -czvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish popd pushd publish_sdl2_headless chmod +x publish/Ryujinx.sh publish/Ryujinx.Headless.SDL2 tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish popd - - pushd publish_ava - mv publish/Ryujinx publish/Ryujinx.Ava - chmod +x publish/Ryujinx.sh publish/Ryujinx.Ava - tar -czvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish - popd shell: bash - name: Pushing new release diff --git a/src/Ryujinx/Modules/Updater/Updater.cs b/src/Ryujinx/Modules/Updater/Updater.cs index d8346c8eb..9f186f2b3 100644 --- a/src/Ryujinx/Modules/Updater/Updater.cs +++ b/src/Ryujinx/Modules/Updater/Updater.cs @@ -298,7 +298,14 @@ private static async Task UpdateRyujinx(Window parent, string downloadUrl) else { // Find the process name. - string ryuName = Path.GetFileName(Environment.ProcessPath); + string ryuName = Path.GetFileName(Environment.ProcessPath) ?? string.Empty; + + // Migration: Start the updated binary. + // TODO: Remove this in a future update. + if (ryuName.StartsWith("Ryujinx.Ava")) + { + ryuName = ryuName.Replace(".Ava", ""); + } // Some operating systems can see the renamed executable, so strip off the .ryuold if found. if (ryuName.EndsWith(".ryuold")) @@ -307,7 +314,7 @@ private static async Task UpdateRyujinx(Window parent, string downloadUrl) } // Fallback if the executable could not be found. - if (!Path.Exists(Path.Combine(executableDirectory, ryuName))) + if (ryuName.Length == 0 || !Path.Exists(Path.Combine(executableDirectory, ryuName))) { ryuName = OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx"; } @@ -759,6 +766,43 @@ public static void CleanupUpdate() { File.Delete(file); } + + // Migration: Delete old Ryujinx binary. + // TODO: Remove this in a future update. + if (!OperatingSystem.IsMacOS()) + { + string[] oldRyuFiles = Directory.GetFiles(_homeDir, "Ryujinx.Ava*", SearchOption.TopDirectoryOnly); + // Assume we are running the new one if the process path is not available. + // This helps to prevent an infinite loop of restarts. + string currentRyuName = Path.GetFileName(Environment.ProcessPath) ?? (OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx"); + + string newRyuName = Path.Combine(_homeDir, currentRyuName.Replace(".Ava", "")); + if (!currentRyuName.Contains("Ryujinx.Ava")) + { + foreach (string oldRyuFile in oldRyuFiles) + { + File.Delete(oldRyuFile); + } + } + // Should we be running the old binary, start the new one if possible. + else if (File.Exists(newRyuName)) + { + ProcessStartInfo processStart = new(newRyuName) + { + UseShellExecute = true, + WorkingDirectory = _homeDir, + }; + + foreach (string argument in CommandLineState.Arguments) + { + processStart.ArgumentList.Add(argument); + } + + Process.Start(processStart); + + Environment.Exit(0); + } + } } } } From ce607db944beb352065107830769d8570f0c245e Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Thu, 14 Mar 2024 01:29:13 +0000 Subject: [PATCH 106/126] Ava UI: Update Ava (#6430) * Update Ava * Newline --- Directory.Packages.props | 15 +++++++-------- src/Ryujinx/Ryujinx.csproj | 3 --- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 00e6a25b0..c08e94357 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,13 +3,13 @@ true - - - - - - - + + + + + + + @@ -45,7 +45,6 @@ - diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj index b3d312f62..2a5a9fadd 100644 --- a/src/Ryujinx/Ryujinx.csproj +++ b/src/Ryujinx/Ryujinx.csproj @@ -55,9 +55,6 @@ - - - From fdd3263e31f8bf352a21e05703d0a6a82c800995 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Thu, 14 Mar 2024 22:38:27 +0000 Subject: [PATCH 107/126] Separate guest/host tracking + unaligned protection (#6486) * WIP: Separate guest/host tracking + unaligned protection Allow memory manager to define support for single byte guest tracking * Formatting * Improve docs * Properly handle cases where the address space bits are too low * Address feedback --- .../Instructions/NativeInterface.cs | 20 +- src/ARMeilleure/Memory/IMemoryManager.cs | 22 + src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs | 336 +-------------- .../IVirtualMemoryManagerTracked.cs | 6 +- src/Ryujinx.Cpu/Jit/MemoryManager.cs | 148 +++++-- .../Jit/MemoryManagerHostMapped.cs | 338 +-------------- src/Ryujinx.Cpu/ManagedPageFlags.cs | 389 ++++++++++++++++++ src/Ryujinx.Graphics.Gpu/Image/Pool.cs | 2 +- src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs | 4 +- .../Memory/PhysicalMemory.cs | 16 +- src/Ryujinx.Memory/AddressSpaceManager.cs | 2 +- src/Ryujinx.Memory/IVirtualMemoryManager.cs | 3 +- src/Ryujinx.Memory/Tracking/MemoryTracking.cs | 137 ++++-- .../Tracking/MultiRegionHandle.cs | 9 +- src/Ryujinx.Memory/Tracking/RegionFlags.cs | 21 + src/Ryujinx.Memory/Tracking/RegionHandle.cs | 68 ++- src/Ryujinx.Memory/Tracking/VirtualRegion.cs | 10 +- .../MockVirtualMemoryManager.cs | 2 +- 18 files changed, 772 insertions(+), 761 deletions(-) create mode 100644 src/Ryujinx.Cpu/ManagedPageFlags.cs create mode 100644 src/Ryujinx.Memory/Tracking/RegionFlags.cs diff --git a/src/ARMeilleure/Instructions/NativeInterface.cs b/src/ARMeilleure/Instructions/NativeInterface.cs index d1b2e353c..0cd3754f7 100644 --- a/src/ARMeilleure/Instructions/NativeInterface.cs +++ b/src/ARMeilleure/Instructions/NativeInterface.cs @@ -91,54 +91,54 @@ public static ulong GetCntvctEl0() #region "Read" public static byte ReadByte(ulong address) { - return GetMemoryManager().ReadTracked(address); + return GetMemoryManager().ReadGuest(address); } public static ushort ReadUInt16(ulong address) { - return GetMemoryManager().ReadTracked(address); + return GetMemoryManager().ReadGuest(address); } public static uint ReadUInt32(ulong address) { - return GetMemoryManager().ReadTracked(address); + return GetMemoryManager().ReadGuest(address); } public static ulong ReadUInt64(ulong address) { - return GetMemoryManager().ReadTracked(address); + return GetMemoryManager().ReadGuest(address); } public static V128 ReadVector128(ulong address) { - return GetMemoryManager().ReadTracked(address); + return GetMemoryManager().ReadGuest(address); } #endregion #region "Write" public static void WriteByte(ulong address, byte value) { - GetMemoryManager().Write(address, value); + GetMemoryManager().WriteGuest(address, value); } public static void WriteUInt16(ulong address, ushort value) { - GetMemoryManager().Write(address, value); + GetMemoryManager().WriteGuest(address, value); } public static void WriteUInt32(ulong address, uint value) { - GetMemoryManager().Write(address, value); + GetMemoryManager().WriteGuest(address, value); } public static void WriteUInt64(ulong address, ulong value) { - GetMemoryManager().Write(address, value); + GetMemoryManager().WriteGuest(address, value); } public static void WriteVector128(ulong address, V128 value) { - GetMemoryManager().Write(address, value); + GetMemoryManager().WriteGuest(address, value); } #endregion diff --git a/src/ARMeilleure/Memory/IMemoryManager.cs b/src/ARMeilleure/Memory/IMemoryManager.cs index 952cd2b4f..46d442655 100644 --- a/src/ARMeilleure/Memory/IMemoryManager.cs +++ b/src/ARMeilleure/Memory/IMemoryManager.cs @@ -28,6 +28,17 @@ public interface IMemoryManager /// The data T ReadTracked(ulong va) where T : unmanaged; + /// + /// Reads data from CPU mapped memory, from guest code. (with read tracking) + /// + /// Type of the data being read + /// Virtual address of the data in memory + /// The data + T ReadGuest(ulong va) where T : unmanaged + { + return ReadTracked(va); + } + /// /// Writes data to CPU mapped memory. /// @@ -36,6 +47,17 @@ public interface IMemoryManager /// Data to be written void Write(ulong va, T value) where T : unmanaged; + /// + /// Writes data to CPU mapped memory, from guest code. + /// + /// Type of the data being written + /// Virtual address to write the data into + /// Data to be written + void WriteGuest(ulong va, T value) where T : unmanaged + { + Write(va, value); + } + /// /// Gets a read-only span of data from CPU mapped memory. /// diff --git a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs index 6e864f4ca..80f7c8a1f 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs @@ -8,7 +8,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; -using System.Threading; namespace Ryujinx.Cpu.AppleHv { @@ -18,21 +17,6 @@ namespace Ryujinx.Cpu.AppleHv [SupportedOSPlatform("macos")] public class HvMemoryManager : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock { - public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry. - public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set. - - private enum HostMappedPtBits : ulong - { - Unmapped = 0, - Mapped, - WriteTracked, - ReadWriteTracked, - - MappedReplicated = 0x5555555555555555, - WriteTrackedReplicated = 0xaaaaaaaaaaaaaaaa, - ReadWriteTrackedReplicated = ulong.MaxValue, - } - private readonly InvalidAccessHandler _invalidAccessHandler; private readonly HvAddressSpace _addressSpace; @@ -42,7 +26,7 @@ private enum HostMappedPtBits : ulong private readonly MemoryBlock _backingMemory; private readonly PageTable _pageTable; - private readonly ulong[] _pageBitmap; + private readonly ManagedPageFlags _pages; public bool Supports4KBPages => true; @@ -84,7 +68,7 @@ public HvMemoryManager(MemoryBlock backingMemory, ulong addressSpaceSize, Invali AddressSpaceBits = asBits; - _pageBitmap = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))]; + _pages = new ManagedPageFlags(AddressSpaceBits); Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler); } @@ -95,7 +79,7 @@ public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags) PtMap(va, pa, size); _addressSpace.MapUser(va, pa, size, MemoryPermission.ReadWriteExecute); - AddMapping(va, size); + _pages.AddMapping(va, size); Tracking.Map(va, size); } @@ -126,7 +110,7 @@ public void Unmap(ulong va, ulong size) UnmapEvent?.Invoke(va, size); Tracking.Unmap(va, size); - RemoveMapping(va, size); + _pages.RemoveMapping(va, size); _addressSpace.UnmapUser(va, size); PtUnmap(va, size); } @@ -360,22 +344,7 @@ public ref T GetRef(ulong va) where T : unmanaged [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsMapped(ulong va) { - return ValidateAddress(va) && IsMappedImpl(va); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsMappedImpl(ulong va) - { - ulong page = va >> PageBits; - - int bit = (int)((page & 31) << 1); - - int pageIndex = (int)(page >> PageToPteShift); - ref ulong pageRef = ref _pageBitmap[pageIndex]; - - ulong pte = Volatile.Read(ref pageRef); - - return ((pte >> bit) & 3) != 0; + return ValidateAddress(va) && _pages.IsMapped(va); } /// @@ -383,58 +352,7 @@ public bool IsRangeMapped(ulong va, ulong size) { AssertValidAddressAndSize(va, size); - return IsRangeMappedImpl(va, size); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void GetPageBlockRange(ulong pageStart, ulong pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex) - { - startMask = ulong.MaxValue << ((int)(pageStart & 31) << 1); - endMask = ulong.MaxValue >> (64 - ((int)(pageEnd & 31) << 1)); - - pageIndex = (int)(pageStart >> PageToPteShift); - pageEndIndex = (int)((pageEnd - 1) >> PageToPteShift); - } - - private bool IsRangeMappedImpl(ulong va, ulong size) - { - int pages = GetPagesCount(va, size, out _); - - if (pages == 1) - { - return IsMappedImpl(va); - } - - ulong pageStart = va >> PageBits; - ulong pageEnd = pageStart + (ulong)pages; - - GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); - - // Check if either bit in each 2 bit page entry is set. - // OR the block with itself shifted down by 1, and check the first bit of each entry. - - ulong mask = BlockMappedMask & startMask; - - while (pageIndex <= pageEndIndex) - { - if (pageIndex == pageEndIndex) - { - mask &= endMask; - } - - ref ulong pageRef = ref _pageBitmap[pageIndex++]; - ulong pte = Volatile.Read(ref pageRef); - - pte |= pte >> 1; - if ((pte & mask) != mask) - { - return false; - } - - mask = BlockMappedMask; - } - - return true; + return _pages.IsRangeMapped(va, size); } private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException(); @@ -560,76 +478,7 @@ public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise return; } - // Software table, used for managed memory tracking. - - int pages = GetPagesCount(va, size, out _); - ulong pageStart = va >> PageBits; - - if (pages == 1) - { - ulong tag = (ulong)(write ? HostMappedPtBits.WriteTracked : HostMappedPtBits.ReadWriteTracked); - - int bit = (int)((pageStart & 31) << 1); - - int pageIndex = (int)(pageStart >> PageToPteShift); - ref ulong pageRef = ref _pageBitmap[pageIndex]; - - ulong pte = Volatile.Read(ref pageRef); - ulong state = ((pte >> bit) & 3); - - if (state >= tag) - { - Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId); - return; - } - else if (state == 0) - { - ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}"); - } - } - else - { - ulong pageEnd = pageStart + (ulong)pages; - - GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); - - ulong mask = startMask; - - ulong anyTrackingTag = (ulong)HostMappedPtBits.WriteTrackedReplicated; - - while (pageIndex <= pageEndIndex) - { - if (pageIndex == pageEndIndex) - { - mask &= endMask; - } - - ref ulong pageRef = ref _pageBitmap[pageIndex++]; - - ulong pte = Volatile.Read(ref pageRef); - ulong mappedMask = mask & BlockMappedMask; - - ulong mappedPte = pte | (pte >> 1); - if ((mappedPte & mappedMask) != mappedMask) - { - ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}"); - } - - pte &= mask; - if ((pte & anyTrackingTag) != 0) // Search for any tracking. - { - // Writes trigger any tracking. - // Only trigger tracking from reads if both bits are set on any page. - if (write || (pte & (pte >> 1) & BlockMappedMask) != 0) - { - Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId); - break; - } - } - - mask = ulong.MaxValue; - } - } + _pages.SignalMemoryTracking(Tracking, va, size, write, exemptId); } /// @@ -656,103 +505,28 @@ public void Reprotect(ulong va, ulong size, MemoryPermission protection) } /// - public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection) + public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest) { - // Protection is inverted on software pages, since the default value is 0. - protection = (~protection) & MemoryPermission.ReadAndWrite; - - int pages = GetPagesCount(va, size, out va); - ulong pageStart = va >> PageBits; - - if (pages == 1) + if (guest) { - ulong protTag = protection switch - { - MemoryPermission.None => (ulong)HostMappedPtBits.Mapped, - MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTracked, - _ => (ulong)HostMappedPtBits.ReadWriteTracked, - }; - - int bit = (int)((pageStart & 31) << 1); - - ulong tagMask = 3UL << bit; - ulong invTagMask = ~tagMask; - - ulong tag = protTag << bit; - - int pageIndex = (int)(pageStart >> PageToPteShift); - ref ulong pageRef = ref _pageBitmap[pageIndex]; - - ulong pte; - - do - { - pte = Volatile.Read(ref pageRef); - } - while ((pte & tagMask) != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte); + _addressSpace.ReprotectUser(va, size, protection); } else { - ulong pageEnd = pageStart + (ulong)pages; - - GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); - - ulong mask = startMask; - - ulong protTag = protection switch - { - MemoryPermission.None => (ulong)HostMappedPtBits.MappedReplicated, - MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTrackedReplicated, - _ => (ulong)HostMappedPtBits.ReadWriteTrackedReplicated, - }; - - while (pageIndex <= pageEndIndex) - { - if (pageIndex == pageEndIndex) - { - mask &= endMask; - } - - ref ulong pageRef = ref _pageBitmap[pageIndex++]; - - ulong pte; - ulong mappedMask; - - // Change the protection of all 2 bit entries that are mapped. - do - { - pte = Volatile.Read(ref pageRef); - - mappedMask = pte | (pte >> 1); - mappedMask |= (mappedMask & BlockMappedMask) << 1; - mappedMask &= mask; // Only update mapped pages within the given range. - } - while (Interlocked.CompareExchange(ref pageRef, (pte & (~mappedMask)) | (protTag & mappedMask), pte) != pte); - - mask = ulong.MaxValue; - } + _pages.TrackingReprotect(va, size, protection); } - - protection = protection switch - { - MemoryPermission.None => MemoryPermission.ReadAndWrite, - MemoryPermission.Write => MemoryPermission.Read, - _ => MemoryPermission.None, - }; - - _addressSpace.ReprotectUser(va, size, protection); } /// - public RegionHandle BeginTracking(ulong address, ulong size, int id) + public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None) { - return Tracking.BeginTracking(address, size, id); + return Tracking.BeginTracking(address, size, id, flags); } /// - public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id) + public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None) { - return Tracking.BeginGranularTracking(address, size, handles, granularity, id); + return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags); } /// @@ -761,86 +535,6 @@ public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong si return Tracking.BeginSmartGranularTracking(address, size, granularity, id); } - /// - /// Adds the given address mapping to the page table. - /// - /// Virtual memory address - /// Size to be mapped - private void AddMapping(ulong va, ulong size) - { - int pages = GetPagesCount(va, size, out _); - ulong pageStart = va >> PageBits; - ulong pageEnd = pageStart + (ulong)pages; - - GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); - - ulong mask = startMask; - - while (pageIndex <= pageEndIndex) - { - if (pageIndex == pageEndIndex) - { - mask &= endMask; - } - - ref ulong pageRef = ref _pageBitmap[pageIndex++]; - - ulong pte; - ulong mappedMask; - - // Map all 2-bit entries that are unmapped. - do - { - pte = Volatile.Read(ref pageRef); - - mappedMask = pte | (pte >> 1); - mappedMask |= (mappedMask & BlockMappedMask) << 1; - mappedMask |= ~mask; // Treat everything outside the range as mapped, thus unchanged. - } - while (Interlocked.CompareExchange(ref pageRef, (pte & mappedMask) | (BlockMappedMask & (~mappedMask)), pte) != pte); - - mask = ulong.MaxValue; - } - } - - /// - /// Removes the given address mapping from the page table. - /// - /// Virtual memory address - /// Size to be unmapped - private void RemoveMapping(ulong va, ulong size) - { - int pages = GetPagesCount(va, size, out _); - ulong pageStart = va >> PageBits; - ulong pageEnd = pageStart + (ulong)pages; - - GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); - - startMask = ~startMask; - endMask = ~endMask; - - ulong mask = startMask; - - while (pageIndex <= pageEndIndex) - { - if (pageIndex == pageEndIndex) - { - mask |= endMask; - } - - ref ulong pageRef = ref _pageBitmap[pageIndex++]; - ulong pte; - - do - { - pte = Volatile.Read(ref pageRef); - } - while (Interlocked.CompareExchange(ref pageRef, pte & mask, pte) != pte); - - mask = 0; - } - } - private ulong GetPhysicalAddressChecked(ulong va) { if (!IsMapped(va)) diff --git a/src/Ryujinx.Cpu/IVirtualMemoryManagerTracked.cs b/src/Ryujinx.Cpu/IVirtualMemoryManagerTracked.cs index 199bff240..e8d11ede5 100644 --- a/src/Ryujinx.Cpu/IVirtualMemoryManagerTracked.cs +++ b/src/Ryujinx.Cpu/IVirtualMemoryManagerTracked.cs @@ -28,8 +28,9 @@ public interface IVirtualMemoryManagerTracked : IVirtualMemoryManager /// CPU virtual address of the region /// Size of the region /// Handle ID + /// Region flags /// The memory tracking handle - RegionHandle BeginTracking(ulong address, ulong size, int id); + RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None); /// /// Obtains a memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with. @@ -39,8 +40,9 @@ public interface IVirtualMemoryManagerTracked : IVirtualMemoryManager /// Handles to inherit state from or reuse. When none are present, provide null /// Desired granularity of write tracking /// Handle ID + /// Region flags /// The memory tracking handle - MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id); + MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None); /// /// Obtains a smart memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with. diff --git a/src/Ryujinx.Cpu/Jit/MemoryManager.cs b/src/Ryujinx.Cpu/Jit/MemoryManager.cs index bbfdf536e..c87c8b8cc 100644 --- a/src/Ryujinx.Cpu/Jit/MemoryManager.cs +++ b/src/Ryujinx.Cpu/Jit/MemoryManager.cs @@ -33,6 +33,8 @@ public sealed class MemoryManager : VirtualMemoryManagerRefCountedBase /// Page table base pointer. /// @@ -70,6 +72,8 @@ public MemoryManager(MemoryBlock backingMemory, ulong addressSpaceSize, InvalidA AddressSpaceSize = asSize; _pageTable = new MemoryBlock((asSize / PageSize) * PteSize); + _pages = new ManagedPageFlags(AddressSpaceBits); + Tracking = new MemoryTracking(this, PageSize); } @@ -89,6 +93,7 @@ public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags) remainingSize -= PageSize; } + _pages.AddMapping(oVa, size); Tracking.Map(oVa, size); } @@ -111,6 +116,7 @@ public void Unmap(ulong va, ulong size) UnmapEvent?.Invoke(va, size); Tracking.Unmap(va, size); + _pages.RemoveMapping(va, size); ulong remainingSize = size; while (remainingSize != 0) @@ -148,6 +154,26 @@ public T ReadTracked(ulong va) where T : unmanaged } } + /// + public T ReadGuest(ulong va) where T : unmanaged + { + try + { + SignalMemoryTrackingImpl(va, (ulong)Unsafe.SizeOf(), false, true); + + return Read(va); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + + return default; + } + } + /// public override void Read(ulong va, Span data) { @@ -183,6 +209,16 @@ public void Write(ulong va, ReadOnlySpan data) WriteImpl(va, data); } + /// + public void WriteGuest(ulong va, T value) where T : unmanaged + { + Span data = MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1)); + + SignalMemoryTrackingImpl(va, (ulong)data.Length, true, true); + + WriteImpl(va, data); + } + /// public void WriteUntracked(ulong va, ReadOnlySpan data) { @@ -520,50 +556,57 @@ public void Reprotect(ulong va, ulong size, MemoryPermission protection) } /// - public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection) + public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest) { AssertValidAddressAndSize(va, size); - // Protection is inverted on software pages, since the default value is 0. - protection = (~protection) & MemoryPermission.ReadAndWrite; - - long tag = protection switch + if (guest) { - MemoryPermission.None => 0L, - MemoryPermission.Write => 2L << PointerTagBit, - _ => 3L << PointerTagBit, - }; + // Protection is inverted on software pages, since the default value is 0. + protection = (~protection) & MemoryPermission.ReadAndWrite; - int pages = GetPagesCount(va, (uint)size, out va); - ulong pageStart = va >> PageBits; - long invTagMask = ~(0xffffL << 48); - - for (int page = 0; page < pages; page++) - { - ref long pageRef = ref _pageTable.GetRef(pageStart * PteSize); + long tag = protection switch + { + MemoryPermission.None => 0L, + MemoryPermission.Write => 2L << PointerTagBit, + _ => 3L << PointerTagBit, + }; - long pte; + int pages = GetPagesCount(va, (uint)size, out va); + ulong pageStart = va >> PageBits; + long invTagMask = ~(0xffffL << 48); - do + for (int page = 0; page < pages; page++) { - pte = Volatile.Read(ref pageRef); - } - while (pte != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte); + ref long pageRef = ref _pageTable.GetRef(pageStart * PteSize); + + long pte; - pageStart++; + do + { + pte = Volatile.Read(ref pageRef); + } + while (pte != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte); + + pageStart++; + } + } + else + { + _pages.TrackingReprotect(va, size, protection); } } /// - public RegionHandle BeginTracking(ulong address, ulong size, int id) + public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None) { - return Tracking.BeginTracking(address, size, id); + return Tracking.BeginTracking(address, size, id, flags); } /// - public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id) + public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None) { - return Tracking.BeginGranularTracking(address, size, handles, granularity, id); + return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags); } /// @@ -572,8 +615,7 @@ public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong si return Tracking.BeginSmartGranularTracking(address, size, granularity, id); } - /// - public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) + private void SignalMemoryTrackingImpl(ulong va, ulong size, bool write, bool guest, bool precise = false, int? exemptId = null) { AssertValidAddressAndSize(va, size); @@ -583,31 +625,47 @@ public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise return; } - // We emulate guard pages for software memory access. This makes for an easy transition to - // tracking using host guard pages in future, but also supporting platforms where this is not possible. - - // Write tag includes read protection, since we don't have any read actions that aren't performed before write too. - long tag = (write ? 3L : 1L) << PointerTagBit; + // If the memory tracking is coming from the guest, use the tag bits in the page table entry. + // Otherwise, use the managed page flags. - int pages = GetPagesCount(va, (uint)size, out _); - ulong pageStart = va >> PageBits; - - for (int page = 0; page < pages; page++) + if (guest) { - ref long pageRef = ref _pageTable.GetRef(pageStart * PteSize); + // We emulate guard pages for software memory access. This makes for an easy transition to + // tracking using host guard pages in future, but also supporting platforms where this is not possible. - long pte; + // Write tag includes read protection, since we don't have any read actions that aren't performed before write too. + long tag = (write ? 3L : 1L) << PointerTagBit; - pte = Volatile.Read(ref pageRef); + int pages = GetPagesCount(va, (uint)size, out _); + ulong pageStart = va >> PageBits; - if ((pte & tag) != 0) + for (int page = 0; page < pages; page++) { - Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId); - break; - } + ref long pageRef = ref _pageTable.GetRef(pageStart * PteSize); + + long pte; + + pte = Volatile.Read(ref pageRef); - pageStart++; + if ((pte & tag) != 0) + { + Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId, true); + break; + } + + pageStart++; + } } + else + { + _pages.SignalMemoryTracking(Tracking, va, size, write, exemptId); + } + } + + /// + public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) + { + SignalMemoryTrackingImpl(va, size, write, false, precise, exemptId); } private ulong PaToPte(ulong pa) diff --git a/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs b/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs index 0b6ba260a..f410d02e9 100644 --- a/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs +++ b/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using System.Threading; namespace Ryujinx.Cpu.Jit { @@ -15,21 +14,6 @@ namespace Ryujinx.Cpu.Jit /// public sealed class MemoryManagerHostMapped : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock { - public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry. - public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set. - - private enum HostMappedPtBits : ulong - { - Unmapped = 0, - Mapped, - WriteTracked, - ReadWriteTracked, - - MappedReplicated = 0x5555555555555555, - WriteTrackedReplicated = 0xaaaaaaaaaaaaaaaa, - ReadWriteTrackedReplicated = ulong.MaxValue, - } - private readonly InvalidAccessHandler _invalidAccessHandler; private readonly bool _unsafeMode; @@ -39,7 +23,7 @@ private enum HostMappedPtBits : ulong private readonly MemoryEhMeilleure _memoryEh; - private readonly ulong[] _pageBitmap; + private readonly ManagedPageFlags _pages; /// public bool Supports4KBPages => MemoryBlock.GetPageSize() == PageSize; @@ -81,7 +65,7 @@ public MemoryManagerHostMapped(AddressSpace addressSpace, bool unsafeMode, Inval AddressSpaceBits = asBits; - _pageBitmap = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))]; + _pages = new ManagedPageFlags(AddressSpaceBits); Tracking = new MemoryTracking(this, (int)MemoryBlock.GetPageSize(), invalidAccessHandler); _memoryEh = new MemoryEhMeilleure(_addressSpace.Base, _addressSpace.Mirror, Tracking); @@ -94,7 +78,7 @@ public MemoryManagerHostMapped(AddressSpace addressSpace, bool unsafeMode, Inval /// Size of the range in bytes private void AssertMapped(ulong va, ulong size) { - if (!ValidateAddressAndSize(va, size) || !IsRangeMappedImpl(va, size)) + if (!ValidateAddressAndSize(va, size) || !_pages.IsRangeMapped(va, size)) { throw new InvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}"); } @@ -106,7 +90,7 @@ public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags) AssertValidAddressAndSize(va, size); _addressSpace.Map(va, pa, size, flags); - AddMapping(va, size); + _pages.AddMapping(va, size); PtMap(va, pa, size); Tracking.Map(va, size); @@ -126,7 +110,7 @@ public void Unmap(ulong va, ulong size) UnmapEvent?.Invoke(va, size); Tracking.Unmap(va, size); - RemoveMapping(va, size); + _pages.RemoveMapping(va, size); PtUnmap(va, size); _addressSpace.Unmap(va, size); } @@ -337,22 +321,7 @@ public ref T GetRef(ulong va) where T : unmanaged [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsMapped(ulong va) { - return ValidateAddress(va) && IsMappedImpl(va); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsMappedImpl(ulong va) - { - ulong page = va >> PageBits; - - int bit = (int)((page & 31) << 1); - - int pageIndex = (int)(page >> PageToPteShift); - ref ulong pageRef = ref _pageBitmap[pageIndex]; - - ulong pte = Volatile.Read(ref pageRef); - - return ((pte >> bit) & 3) != 0; + return ValidateAddress(va) && _pages.IsMapped(va); } /// @@ -360,58 +329,7 @@ public bool IsRangeMapped(ulong va, ulong size) { AssertValidAddressAndSize(va, size); - return IsRangeMappedImpl(va, size); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void GetPageBlockRange(ulong pageStart, ulong pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex) - { - startMask = ulong.MaxValue << ((int)(pageStart & 31) << 1); - endMask = ulong.MaxValue >> (64 - ((int)(pageEnd & 31) << 1)); - - pageIndex = (int)(pageStart >> PageToPteShift); - pageEndIndex = (int)((pageEnd - 1) >> PageToPteShift); - } - - private bool IsRangeMappedImpl(ulong va, ulong size) - { - int pages = GetPagesCount(va, size, out _); - - if (pages == 1) - { - return IsMappedImpl(va); - } - - ulong pageStart = va >> PageBits; - ulong pageEnd = pageStart + (ulong)pages; - - GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); - - // Check if either bit in each 2 bit page entry is set. - // OR the block with itself shifted down by 1, and check the first bit of each entry. - - ulong mask = BlockMappedMask & startMask; - - while (pageIndex <= pageEndIndex) - { - if (pageIndex == pageEndIndex) - { - mask &= endMask; - } - - ref ulong pageRef = ref _pageBitmap[pageIndex++]; - ulong pte = Volatile.Read(ref pageRef); - - pte |= pte >> 1; - if ((pte & mask) != mask) - { - return false; - } - - mask = BlockMappedMask; - } - - return true; + return _pages.IsRangeMapped(va, size); } /// @@ -486,76 +404,7 @@ public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise return; } - // Software table, used for managed memory tracking. - - int pages = GetPagesCount(va, size, out _); - ulong pageStart = va >> PageBits; - - if (pages == 1) - { - ulong tag = (ulong)(write ? HostMappedPtBits.WriteTracked : HostMappedPtBits.ReadWriteTracked); - - int bit = (int)((pageStart & 31) << 1); - - int pageIndex = (int)(pageStart >> PageToPteShift); - ref ulong pageRef = ref _pageBitmap[pageIndex]; - - ulong pte = Volatile.Read(ref pageRef); - ulong state = ((pte >> bit) & 3); - - if (state >= tag) - { - Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId); - return; - } - else if (state == 0) - { - ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}"); - } - } - else - { - ulong pageEnd = pageStart + (ulong)pages; - - GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); - - ulong mask = startMask; - - ulong anyTrackingTag = (ulong)HostMappedPtBits.WriteTrackedReplicated; - - while (pageIndex <= pageEndIndex) - { - if (pageIndex == pageEndIndex) - { - mask &= endMask; - } - - ref ulong pageRef = ref _pageBitmap[pageIndex++]; - - ulong pte = Volatile.Read(ref pageRef); - ulong mappedMask = mask & BlockMappedMask; - - ulong mappedPte = pte | (pte >> 1); - if ((mappedPte & mappedMask) != mappedMask) - { - ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}"); - } - - pte &= mask; - if ((pte & anyTrackingTag) != 0) // Search for any tracking. - { - // Writes trigger any tracking. - // Only trigger tracking from reads if both bits are set on any page. - if (write || (pte & (pte >> 1) & BlockMappedMask) != 0) - { - Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId); - break; - } - } - - mask = ulong.MaxValue; - } - } + _pages.SignalMemoryTracking(Tracking, va, size, write, exemptId); } /// @@ -582,103 +431,28 @@ public void Reprotect(ulong va, ulong size, MemoryPermission protection) } /// - public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection) + public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest) { - // Protection is inverted on software pages, since the default value is 0. - protection = (~protection) & MemoryPermission.ReadAndWrite; - - int pages = GetPagesCount(va, size, out va); - ulong pageStart = va >> PageBits; - - if (pages == 1) + if (guest) { - ulong protTag = protection switch - { - MemoryPermission.None => (ulong)HostMappedPtBits.Mapped, - MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTracked, - _ => (ulong)HostMappedPtBits.ReadWriteTracked, - }; - - int bit = (int)((pageStart & 31) << 1); - - ulong tagMask = 3UL << bit; - ulong invTagMask = ~tagMask; - - ulong tag = protTag << bit; - - int pageIndex = (int)(pageStart >> PageToPteShift); - ref ulong pageRef = ref _pageBitmap[pageIndex]; - - ulong pte; - - do - { - pte = Volatile.Read(ref pageRef); - } - while ((pte & tagMask) != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte); + _addressSpace.Base.Reprotect(va, size, protection, false); } else { - ulong pageEnd = pageStart + (ulong)pages; - - GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); - - ulong mask = startMask; - - ulong protTag = protection switch - { - MemoryPermission.None => (ulong)HostMappedPtBits.MappedReplicated, - MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTrackedReplicated, - _ => (ulong)HostMappedPtBits.ReadWriteTrackedReplicated, - }; - - while (pageIndex <= pageEndIndex) - { - if (pageIndex == pageEndIndex) - { - mask &= endMask; - } - - ref ulong pageRef = ref _pageBitmap[pageIndex++]; - - ulong pte; - ulong mappedMask; - - // Change the protection of all 2 bit entries that are mapped. - do - { - pte = Volatile.Read(ref pageRef); - - mappedMask = pte | (pte >> 1); - mappedMask |= (mappedMask & BlockMappedMask) << 1; - mappedMask &= mask; // Only update mapped pages within the given range. - } - while (Interlocked.CompareExchange(ref pageRef, (pte & (~mappedMask)) | (protTag & mappedMask), pte) != pte); - - mask = ulong.MaxValue; - } + _pages.TrackingReprotect(va, size, protection); } - - protection = protection switch - { - MemoryPermission.None => MemoryPermission.ReadAndWrite, - MemoryPermission.Write => MemoryPermission.Read, - _ => MemoryPermission.None, - }; - - _addressSpace.Base.Reprotect(va, size, protection, false); } /// - public RegionHandle BeginTracking(ulong address, ulong size, int id) + public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None) { - return Tracking.BeginTracking(address, size, id); + return Tracking.BeginTracking(address, size, id, flags); } /// - public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id) + public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None) { - return Tracking.BeginGranularTracking(address, size, handles, granularity, id); + return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags); } /// @@ -687,86 +461,6 @@ public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong si return Tracking.BeginSmartGranularTracking(address, size, granularity, id); } - /// - /// Adds the given address mapping to the page table. - /// - /// Virtual memory address - /// Size to be mapped - private void AddMapping(ulong va, ulong size) - { - int pages = GetPagesCount(va, size, out _); - ulong pageStart = va >> PageBits; - ulong pageEnd = pageStart + (ulong)pages; - - GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); - - ulong mask = startMask; - - while (pageIndex <= pageEndIndex) - { - if (pageIndex == pageEndIndex) - { - mask &= endMask; - } - - ref ulong pageRef = ref _pageBitmap[pageIndex++]; - - ulong pte; - ulong mappedMask; - - // Map all 2-bit entries that are unmapped. - do - { - pte = Volatile.Read(ref pageRef); - - mappedMask = pte | (pte >> 1); - mappedMask |= (mappedMask & BlockMappedMask) << 1; - mappedMask |= ~mask; // Treat everything outside the range as mapped, thus unchanged. - } - while (Interlocked.CompareExchange(ref pageRef, (pte & mappedMask) | (BlockMappedMask & (~mappedMask)), pte) != pte); - - mask = ulong.MaxValue; - } - } - - /// - /// Removes the given address mapping from the page table. - /// - /// Virtual memory address - /// Size to be unmapped - private void RemoveMapping(ulong va, ulong size) - { - int pages = GetPagesCount(va, size, out _); - ulong pageStart = va >> PageBits; - ulong pageEnd = pageStart + (ulong)pages; - - GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); - - startMask = ~startMask; - endMask = ~endMask; - - ulong mask = startMask; - - while (pageIndex <= pageEndIndex) - { - if (pageIndex == pageEndIndex) - { - mask |= endMask; - } - - ref ulong pageRef = ref _pageBitmap[pageIndex++]; - ulong pte; - - do - { - pte = Volatile.Read(ref pageRef); - } - while (Interlocked.CompareExchange(ref pageRef, pte & mask, pte) != pte); - - mask = 0; - } - } - /// /// Disposes of resources used by the memory manager. /// diff --git a/src/Ryujinx.Cpu/ManagedPageFlags.cs b/src/Ryujinx.Cpu/ManagedPageFlags.cs new file mode 100644 index 000000000..a839dae67 --- /dev/null +++ b/src/Ryujinx.Cpu/ManagedPageFlags.cs @@ -0,0 +1,389 @@ +using Ryujinx.Memory; +using Ryujinx.Memory.Tracking; +using System; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Ryujinx.Cpu +{ + /// + /// A page bitmap that keeps track of mapped state and tracking protection + /// for managed memory accesses (not using host page protection). + /// + internal readonly struct ManagedPageFlags + { + public const int PageBits = 12; + public const int PageSize = 1 << PageBits; + public const int PageMask = PageSize - 1; + + private readonly ulong[] _pageBitmap; + + public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry. + public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set. + + private enum ManagedPtBits : ulong + { + Unmapped = 0, + Mapped, + WriteTracked, + ReadWriteTracked, + + MappedReplicated = 0x5555555555555555, + WriteTrackedReplicated = 0xaaaaaaaaaaaaaaaa, + ReadWriteTrackedReplicated = ulong.MaxValue, + } + + public ManagedPageFlags(int addressSpaceBits) + { + int bits = Math.Max(0, addressSpaceBits - (PageBits + PageToPteShift)); + _pageBitmap = new ulong[1 << bits]; + } + + /// + /// Computes the number of pages in a virtual address range. + /// + /// Virtual address of the range + /// Size of the range + /// The virtual address of the beginning of the first page + /// This function does not differentiate between allocated and unallocated pages. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetPagesCount(ulong va, ulong size, out ulong startVa) + { + // WARNING: Always check if ulong does not overflow during the operations. + startVa = va & ~(ulong)PageMask; + ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask; + + return (int)(vaSpan / PageSize); + } + + /// + /// Checks if the page at a given CPU virtual address is mapped. + /// + /// Virtual address to check + /// True if the address is mapped, false otherwise + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool IsMapped(ulong va) + { + ulong page = va >> PageBits; + + int bit = (int)((page & 31) << 1); + + int pageIndex = (int)(page >> PageToPteShift); + ref ulong pageRef = ref _pageBitmap[pageIndex]; + + ulong pte = Volatile.Read(ref pageRef); + + return ((pte >> bit) & 3) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void GetPageBlockRange(ulong pageStart, ulong pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex) + { + startMask = ulong.MaxValue << ((int)(pageStart & 31) << 1); + endMask = ulong.MaxValue >> (64 - ((int)(pageEnd & 31) << 1)); + + pageIndex = (int)(pageStart >> PageToPteShift); + pageEndIndex = (int)((pageEnd - 1) >> PageToPteShift); + } + + /// + /// Checks if a memory range is mapped. + /// + /// Virtual address of the range + /// Size of the range in bytes + /// True if the entire range is mapped, false otherwise + public readonly bool IsRangeMapped(ulong va, ulong size) + { + int pages = GetPagesCount(va, size, out _); + + if (pages == 1) + { + return IsMapped(va); + } + + ulong pageStart = va >> PageBits; + ulong pageEnd = pageStart + (ulong)pages; + + GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); + + // Check if either bit in each 2 bit page entry is set. + // OR the block with itself shifted down by 1, and check the first bit of each entry. + + ulong mask = BlockMappedMask & startMask; + + while (pageIndex <= pageEndIndex) + { + if (pageIndex == pageEndIndex) + { + mask &= endMask; + } + + ref ulong pageRef = ref _pageBitmap[pageIndex++]; + ulong pte = Volatile.Read(ref pageRef); + + pte |= pte >> 1; + if ((pte & mask) != mask) + { + return false; + } + + mask = BlockMappedMask; + } + + return true; + } + + /// + /// Reprotect a region of virtual memory for tracking. + /// + /// Virtual address base + /// Size of the region to protect + /// Memory protection to set + public readonly void TrackingReprotect(ulong va, ulong size, MemoryPermission protection) + { + // Protection is inverted on software pages, since the default value is 0. + protection = (~protection) & MemoryPermission.ReadAndWrite; + + int pages = GetPagesCount(va, size, out va); + ulong pageStart = va >> PageBits; + + if (pages == 1) + { + ulong protTag = protection switch + { + MemoryPermission.None => (ulong)ManagedPtBits.Mapped, + MemoryPermission.Write => (ulong)ManagedPtBits.WriteTracked, + _ => (ulong)ManagedPtBits.ReadWriteTracked, + }; + + int bit = (int)((pageStart & 31) << 1); + + ulong tagMask = 3UL << bit; + ulong invTagMask = ~tagMask; + + ulong tag = protTag << bit; + + int pageIndex = (int)(pageStart >> PageToPteShift); + ref ulong pageRef = ref _pageBitmap[pageIndex]; + + ulong pte; + + do + { + pte = Volatile.Read(ref pageRef); + } + while ((pte & tagMask) != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte); + } + else + { + ulong pageEnd = pageStart + (ulong)pages; + + GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); + + ulong mask = startMask; + + ulong protTag = protection switch + { + MemoryPermission.None => (ulong)ManagedPtBits.MappedReplicated, + MemoryPermission.Write => (ulong)ManagedPtBits.WriteTrackedReplicated, + _ => (ulong)ManagedPtBits.ReadWriteTrackedReplicated, + }; + + while (pageIndex <= pageEndIndex) + { + if (pageIndex == pageEndIndex) + { + mask &= endMask; + } + + ref ulong pageRef = ref _pageBitmap[pageIndex++]; + + ulong pte; + ulong mappedMask; + + // Change the protection of all 2 bit entries that are mapped. + do + { + pte = Volatile.Read(ref pageRef); + + mappedMask = pte | (pte >> 1); + mappedMask |= (mappedMask & BlockMappedMask) << 1; + mappedMask &= mask; // Only update mapped pages within the given range. + } + while (Interlocked.CompareExchange(ref pageRef, (pte & (~mappedMask)) | (protTag & mappedMask), pte) != pte); + + mask = ulong.MaxValue; + } + } + } + + /// + /// Alerts the memory tracking that a given region has been read from or written to. + /// This should be called before read/write is performed. + /// + /// Memory tracking structure to call when pages are protected + /// Virtual address of the region + /// Size of the region + /// True if the region was written, false if read + /// Optional ID of the handles that should not be signalled + /// + /// This function also validates that the given range is both valid and mapped, and will throw if it is not. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly void SignalMemoryTracking(MemoryTracking tracking, ulong va, ulong size, bool write, int? exemptId = null) + { + // Software table, used for managed memory tracking. + + int pages = GetPagesCount(va, size, out _); + ulong pageStart = va >> PageBits; + + if (pages == 1) + { + ulong tag = (ulong)(write ? ManagedPtBits.WriteTracked : ManagedPtBits.ReadWriteTracked); + + int bit = (int)((pageStart & 31) << 1); + + int pageIndex = (int)(pageStart >> PageToPteShift); + ref ulong pageRef = ref _pageBitmap[pageIndex]; + + ulong pte = Volatile.Read(ref pageRef); + ulong state = ((pte >> bit) & 3); + + if (state >= tag) + { + tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId); + return; + } + else if (state == 0) + { + ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}"); + } + } + else + { + ulong pageEnd = pageStart + (ulong)pages; + + GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); + + ulong mask = startMask; + + ulong anyTrackingTag = (ulong)ManagedPtBits.WriteTrackedReplicated; + + while (pageIndex <= pageEndIndex) + { + if (pageIndex == pageEndIndex) + { + mask &= endMask; + } + + ref ulong pageRef = ref _pageBitmap[pageIndex++]; + + ulong pte = Volatile.Read(ref pageRef); + ulong mappedMask = mask & BlockMappedMask; + + ulong mappedPte = pte | (pte >> 1); + if ((mappedPte & mappedMask) != mappedMask) + { + ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}"); + } + + pte &= mask; + if ((pte & anyTrackingTag) != 0) // Search for any tracking. + { + // Writes trigger any tracking. + // Only trigger tracking from reads if both bits are set on any page. + if (write || (pte & (pte >> 1) & BlockMappedMask) != 0) + { + tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId); + break; + } + } + + mask = ulong.MaxValue; + } + } + } + + /// + /// Adds the given address mapping to the page table. + /// + /// Virtual memory address + /// Size to be mapped + public readonly void AddMapping(ulong va, ulong size) + { + int pages = GetPagesCount(va, size, out _); + ulong pageStart = va >> PageBits; + ulong pageEnd = pageStart + (ulong)pages; + + GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); + + ulong mask = startMask; + + while (pageIndex <= pageEndIndex) + { + if (pageIndex == pageEndIndex) + { + mask &= endMask; + } + + ref ulong pageRef = ref _pageBitmap[pageIndex++]; + + ulong pte; + ulong mappedMask; + + // Map all 2-bit entries that are unmapped. + do + { + pte = Volatile.Read(ref pageRef); + + mappedMask = pte | (pte >> 1); + mappedMask |= (mappedMask & BlockMappedMask) << 1; + mappedMask |= ~mask; // Treat everything outside the range as mapped, thus unchanged. + } + while (Interlocked.CompareExchange(ref pageRef, (pte & mappedMask) | (BlockMappedMask & (~mappedMask)), pte) != pte); + + mask = ulong.MaxValue; + } + } + + /// + /// Removes the given address mapping from the page table. + /// + /// Virtual memory address + /// Size to be unmapped + public readonly void RemoveMapping(ulong va, ulong size) + { + int pages = GetPagesCount(va, size, out _); + ulong pageStart = va >> PageBits; + ulong pageEnd = pageStart + (ulong)pages; + + GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); + + startMask = ~startMask; + endMask = ~endMask; + + ulong mask = startMask; + + while (pageIndex <= pageEndIndex) + { + if (pageIndex == pageEndIndex) + { + mask |= endMask; + } + + ref ulong pageRef = ref _pageBitmap[pageIndex++]; + ulong pte; + + do + { + pte = Volatile.Read(ref pageRef); + } + while (Interlocked.CompareExchange(ref pageRef, pte & mask, pte) != pte); + + mask = 0; + } + } + + private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message); + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/Pool.cs b/src/Ryujinx.Graphics.Gpu/Image/Pool.cs index 0c3a219de..6ede01971 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/Pool.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/Pool.cs @@ -69,7 +69,7 @@ public Pool(GpuContext context, PhysicalMemory physicalMemory, ulong address, in Address = address; Size = size; - _memoryTracking = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Pool); + _memoryTracking = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Pool, RegionFlags.None); _memoryTracking.RegisterPreciseAction(address, size, PreciseAction); _modifiedDelegate = RegionModified; } diff --git a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs index e01e5142c..d293060b5 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs @@ -128,13 +128,13 @@ public Buffer( if (_useGranular) { - _memoryTrackingGranular = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Buffer, baseHandles); + _memoryTrackingGranular = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Buffer, RegionFlags.UnalignedAccess, baseHandles); _memoryTrackingGranular.RegisterPreciseAction(address, size, PreciseAction); } else { - _memoryTracking = physicalMemory.BeginTracking(address, size, ResourceKind.Buffer); + _memoryTracking = physicalMemory.BeginTracking(address, size, ResourceKind.Buffer, RegionFlags.UnalignedAccess); if (baseHandles != null) { diff --git a/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs b/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs index 69a3054a4..cca02bb15 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs @@ -368,10 +368,11 @@ public void FillTrackedResource(ulong address, ulong size, uint value, ResourceK /// CPU virtual address of the region /// Size of the region /// Kind of the resource being tracked + /// Region flags /// The memory tracking handle - public RegionHandle BeginTracking(ulong address, ulong size, ResourceKind kind) + public RegionHandle BeginTracking(ulong address, ulong size, ResourceKind kind, RegionFlags flags = RegionFlags.None) { - return _cpuMemory.BeginTracking(address, size, (int)kind); + return _cpuMemory.BeginTracking(address, size, (int)kind, flags); } /// @@ -408,12 +409,19 @@ public GpuRegionHandle BeginTracking(MultiRange range, ResourceKind kind) /// CPU virtual address of the region /// Size of the region /// Kind of the resource being tracked + /// Region flags /// Handles to inherit state from or reuse /// Desired granularity of write tracking /// The memory tracking handle - public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, ResourceKind kind, IEnumerable handles = null, ulong granularity = 4096) + public MultiRegionHandle BeginGranularTracking( + ulong address, + ulong size, + ResourceKind kind, + RegionFlags flags = RegionFlags.None, + IEnumerable handles = null, + ulong granularity = 4096) { - return _cpuMemory.BeginGranularTracking(address, size, handles, granularity, (int)kind); + return _cpuMemory.BeginGranularTracking(address, size, handles, granularity, (int)kind, flags); } /// diff --git a/src/Ryujinx.Memory/AddressSpaceManager.cs b/src/Ryujinx.Memory/AddressSpaceManager.cs index b953eb306..0a4a95143 100644 --- a/src/Ryujinx.Memory/AddressSpaceManager.cs +++ b/src/Ryujinx.Memory/AddressSpaceManager.cs @@ -392,7 +392,7 @@ public void Reprotect(ulong va, ulong size, MemoryPermission protection) } /// - public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection) + public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest = false) { throw new NotImplementedException(); } diff --git a/src/Ryujinx.Memory/IVirtualMemoryManager.cs b/src/Ryujinx.Memory/IVirtualMemoryManager.cs index 9cf3663cf..557da2f26 100644 --- a/src/Ryujinx.Memory/IVirtualMemoryManager.cs +++ b/src/Ryujinx.Memory/IVirtualMemoryManager.cs @@ -214,6 +214,7 @@ void Fill(ulong va, ulong size, byte value) /// Virtual address base /// Size of the region to protect /// Memory protection to set - void TrackingReprotect(ulong va, ulong size, MemoryPermission protection); + /// True if the protection is for guest access, false otherwise + void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest); } } diff --git a/src/Ryujinx.Memory/Tracking/MemoryTracking.cs b/src/Ryujinx.Memory/Tracking/MemoryTracking.cs index 6febcbbb3..96cb2c5f5 100644 --- a/src/Ryujinx.Memory/Tracking/MemoryTracking.cs +++ b/src/Ryujinx.Memory/Tracking/MemoryTracking.cs @@ -14,9 +14,14 @@ public class MemoryTracking // Only use these from within the lock. private readonly NonOverlappingRangeList _virtualRegions; + // Guest virtual regions are a subset of the normal virtual regions, with potentially different protection + // and expanded area of effect on platforms that don't support misaligned page protection. + private readonly NonOverlappingRangeList _guestVirtualRegions; private readonly int _pageSize; + private readonly bool _singleByteGuestTracking; + /// /// This lock must be obtained when traversing or updating the region-handle hierarchy. /// It is not required when reading dirty flags. @@ -27,16 +32,27 @@ public class MemoryTracking /// Create a new tracking structure for the given "physical" memory block, /// with a given "virtual" memory manager that will provide mappings and virtual memory protection. /// + /// + /// If is true, the memory manager must also support protection on partially + /// unmapped regions without throwing exceptions or dropping protection on the mapped portion. + /// /// Virtual memory manager - /// Physical memory block /// Page size of the virtual memory space - public MemoryTracking(IVirtualMemoryManager memoryManager, int pageSize, InvalidAccessHandler invalidAccessHandler = null) + /// Method to call for invalid memory accesses + /// True if the guest only signals writes for the first byte + public MemoryTracking( + IVirtualMemoryManager memoryManager, + int pageSize, + InvalidAccessHandler invalidAccessHandler = null, + bool singleByteGuestTracking = false) { _memoryManager = memoryManager; _pageSize = pageSize; _invalidAccessHandler = invalidAccessHandler; + _singleByteGuestTracking = singleByteGuestTracking; _virtualRegions = new NonOverlappingRangeList(); + _guestVirtualRegions = new NonOverlappingRangeList(); } private (ulong address, ulong size) PageAlign(ulong address, ulong size) @@ -62,20 +78,25 @@ public void Map(ulong va, ulong size) { ref var overlaps = ref ThreadStaticArray.Get(); - int count = _virtualRegions.FindOverlapsNonOverlapping(va, size, ref overlaps); - - for (int i = 0; i < count; i++) + for (int type = 0; type < 2; type++) { - VirtualRegion region = overlaps[i]; + NonOverlappingRangeList regions = type == 0 ? _virtualRegions : _guestVirtualRegions; + + int count = regions.FindOverlapsNonOverlapping(va, size, ref overlaps); - // If the region has been fully remapped, signal that it has been mapped again. - bool remapped = _memoryManager.IsRangeMapped(region.Address, region.Size); - if (remapped) + for (int i = 0; i < count; i++) { - region.SignalMappingChanged(true); - } + VirtualRegion region = overlaps[i]; + + // If the region has been fully remapped, signal that it has been mapped again. + bool remapped = _memoryManager.IsRangeMapped(region.Address, region.Size); + if (remapped) + { + region.SignalMappingChanged(true); + } - region.UpdateProtection(); + region.UpdateProtection(); + } } } } @@ -95,27 +116,58 @@ public void Unmap(ulong va, ulong size) { ref var overlaps = ref ThreadStaticArray.Get(); - int count = _virtualRegions.FindOverlapsNonOverlapping(va, size, ref overlaps); - - for (int i = 0; i < count; i++) + for (int type = 0; type < 2; type++) { - VirtualRegion region = overlaps[i]; + NonOverlappingRangeList regions = type == 0 ? _virtualRegions : _guestVirtualRegions; + + int count = regions.FindOverlapsNonOverlapping(va, size, ref overlaps); - region.SignalMappingChanged(false); + for (int i = 0; i < count; i++) + { + VirtualRegion region = overlaps[i]; + + region.SignalMappingChanged(false); + } } } } + /// + /// Alter a tracked memory region to properly capture unaligned accesses. + /// For most memory manager modes, this does nothing. + /// + /// Original region address + /// Original region size + /// A new address and size for tracking unaligned accesses + internal (ulong newAddress, ulong newSize) GetUnalignedSafeRegion(ulong address, ulong size) + { + if (_singleByteGuestTracking) + { + // The guest only signals the first byte of each memory access with the current memory manager. + // To catch unaligned access properly, we need to also protect the page before the address. + + // Assume that the address and size are already aligned. + + return (address - (ulong)_pageSize, size + (ulong)_pageSize); + } + else + { + return (address, size); + } + } + /// /// Get a list of virtual regions that a handle covers. /// /// Starting virtual memory address of the handle /// Size of the handle's memory region + /// True if getting handles for guest protection, false otherwise /// A list of virtual regions within the given range - internal List GetVirtualRegionsForHandle(ulong va, ulong size) + internal List GetVirtualRegionsForHandle(ulong va, ulong size, bool guest) { List result = new(); - _virtualRegions.GetOrAddRegions(result, va, size, (va, size) => new VirtualRegion(this, va, size)); + NonOverlappingRangeList regions = guest ? _guestVirtualRegions : _virtualRegions; + regions.GetOrAddRegions(result, va, size, (va, size) => new VirtualRegion(this, va, size, guest)); return result; } @@ -126,7 +178,14 @@ internal List GetVirtualRegionsForHandle(ulong va, ulong size) /// Region to remove internal void RemoveVirtual(VirtualRegion region) { - _virtualRegions.Remove(region); + if (region.Guest) + { + _guestVirtualRegions.Remove(region); + } + else + { + _virtualRegions.Remove(region); + } } /// @@ -137,10 +196,11 @@ internal void RemoveVirtual(VirtualRegion region) /// Handles to inherit state from or reuse. When none are present, provide null /// Desired granularity of write tracking /// Handle ID + /// Region flags /// The memory tracking handle - public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id) + public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None) { - return new MultiRegionHandle(this, address, size, handles, granularity, id); + return new MultiRegionHandle(this, address, size, handles, granularity, id, flags); } /// @@ -164,15 +224,16 @@ public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong si /// CPU virtual address of the region /// Size of the region /// Handle ID + /// Region flags /// The memory tracking handle - public RegionHandle BeginTracking(ulong address, ulong size, int id) + public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None) { var (paAddress, paSize) = PageAlign(address, size); lock (TrackingLock) { bool mapped = _memoryManager.IsRangeMapped(address, size); - RegionHandle handle = new(this, paAddress, paSize, address, size, id, mapped); + RegionHandle handle = new(this, paAddress, paSize, address, size, id, flags, mapped); return handle; } @@ -186,15 +247,16 @@ public RegionHandle BeginTracking(ulong address, ulong size, int id) /// The bitmap owning the dirty flag for this handle /// The bit of this handle within the dirty flag /// Handle ID + /// Region flags /// The memory tracking handle - internal RegionHandle BeginTrackingBitmap(ulong address, ulong size, ConcurrentBitmap bitmap, int bit, int id) + internal RegionHandle BeginTrackingBitmap(ulong address, ulong size, ConcurrentBitmap bitmap, int bit, int id, RegionFlags flags = RegionFlags.None) { var (paAddress, paSize) = PageAlign(address, size); lock (TrackingLock) { bool mapped = _memoryManager.IsRangeMapped(address, size); - RegionHandle handle = new(this, paAddress, paSize, address, size, bitmap, bit, id, mapped); + RegionHandle handle = new(this, paAddress, paSize, address, size, bitmap, bit, id, flags, mapped); return handle; } @@ -202,6 +264,7 @@ internal RegionHandle BeginTrackingBitmap(ulong address, ulong size, ConcurrentB /// /// Signal that a virtual memory event happened at the given location. + /// The memory event is assumed to be triggered by guest code. /// /// Virtual address accessed /// Size of the region affected in bytes @@ -209,7 +272,7 @@ internal RegionHandle BeginTrackingBitmap(ulong address, ulong size, ConcurrentB /// True if the event triggered any tracking regions, false otherwise public bool VirtualMemoryEvent(ulong address, ulong size, bool write) { - return VirtualMemoryEvent(address, size, write, precise: false, null); + return VirtualMemoryEvent(address, size, write, precise: false, exemptId: null, guest: true); } /// @@ -222,8 +285,9 @@ public bool VirtualMemoryEvent(ulong address, ulong size, bool write) /// Whether the region was written to or read /// True if the access is precise, false otherwise /// Optional ID that of the handles that should not be signalled + /// True if the access is from the guest, false otherwise /// True if the event triggered any tracking regions, false otherwise - public bool VirtualMemoryEvent(ulong address, ulong size, bool write, bool precise, int? exemptId = null) + public bool VirtualMemoryEvent(ulong address, ulong size, bool write, bool precise, int? exemptId = null, bool guest = false) { // Look up the virtual region using the region list. // Signal up the chain to relevant handles. @@ -234,7 +298,9 @@ public bool VirtualMemoryEvent(ulong address, ulong size, bool write, bool preci { ref var overlaps = ref ThreadStaticArray.Get(); - int count = _virtualRegions.FindOverlapsNonOverlapping(address, size, ref overlaps); + NonOverlappingRangeList regions = guest ? _guestVirtualRegions : _virtualRegions; + + int count = regions.FindOverlapsNonOverlapping(address, size, ref overlaps); if (count == 0 && !precise) { @@ -242,7 +308,7 @@ public bool VirtualMemoryEvent(ulong address, ulong size, bool write, bool preci { // TODO: There is currently the possibility that a page can be protected after its virtual region is removed. // This code handles that case when it happens, but it would be better to find out how this happens. - _memoryManager.TrackingReprotect(address & ~(ulong)(_pageSize - 1), (ulong)_pageSize, MemoryPermission.ReadAndWrite); + _memoryManager.TrackingReprotect(address & ~(ulong)(_pageSize - 1), (ulong)_pageSize, MemoryPermission.ReadAndWrite, guest); return true; // This memory _should_ be mapped, so we need to try again. } else @@ -252,6 +318,12 @@ public bool VirtualMemoryEvent(ulong address, ulong size, bool write, bool preci } else { + if (guest && _singleByteGuestTracking) + { + // Increase the access size to trigger handles with misaligned accesses. + size += (ulong)_pageSize; + } + for (int i = 0; i < count; i++) { VirtualRegion region = overlaps[i]; @@ -285,9 +357,10 @@ public bool VirtualMemoryEvent(ulong address, ulong size, bool write, bool preci /// /// Region to reprotect /// Memory permission to protect with - internal void ProtectVirtualRegion(VirtualRegion region, MemoryPermission permission) + /// True if the protection is for guest access, false otherwise + internal void ProtectVirtualRegion(VirtualRegion region, MemoryPermission permission, bool guest) { - _memoryManager.TrackingReprotect(region.Address, region.Size, permission); + _memoryManager.TrackingReprotect(region.Address, region.Size, permission, guest); } /// diff --git a/src/Ryujinx.Memory/Tracking/MultiRegionHandle.cs b/src/Ryujinx.Memory/Tracking/MultiRegionHandle.cs index 1c1b48b3c..6fdca69f5 100644 --- a/src/Ryujinx.Memory/Tracking/MultiRegionHandle.cs +++ b/src/Ryujinx.Memory/Tracking/MultiRegionHandle.cs @@ -37,7 +37,8 @@ internal MultiRegionHandle( ulong size, IEnumerable handles, ulong granularity, - int id) + int id, + RegionFlags flags) { _handles = new RegionHandle[(size + granularity - 1) / granularity]; Granularity = granularity; @@ -62,7 +63,7 @@ internal MultiRegionHandle( // Fill any gap left before this handle. while (i < startIndex) { - RegionHandle fillHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id); + RegionHandle fillHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id, flags); fillHandle.Parent = this; _handles[i++] = fillHandle; } @@ -83,7 +84,7 @@ internal MultiRegionHandle( while (i < endIndex) { - RegionHandle splitHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id); + RegionHandle splitHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id, flags); splitHandle.Parent = this; splitHandle.Reprotect(handle.Dirty); @@ -106,7 +107,7 @@ internal MultiRegionHandle( // Fill any remaining space with new handles. while (i < _handles.Length) { - RegionHandle handle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id); + RegionHandle handle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id, flags); handle.Parent = this; _handles[i++] = handle; } diff --git a/src/Ryujinx.Memory/Tracking/RegionFlags.cs b/src/Ryujinx.Memory/Tracking/RegionFlags.cs new file mode 100644 index 000000000..ceb8e56a6 --- /dev/null +++ b/src/Ryujinx.Memory/Tracking/RegionFlags.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ryujinx.Memory.Tracking +{ + [Flags] + public enum RegionFlags + { + None = 0, + + /// + /// Access to the resource is expected to occasionally be unaligned. + /// With some memory managers, guest protection must extend into the previous page to cover unaligned access. + /// If this is not expected, protection is not altered, which can avoid unintended resource dirty/flush. + /// + UnalignedAccess = 1, + } +} diff --git a/src/Ryujinx.Memory/Tracking/RegionHandle.cs b/src/Ryujinx.Memory/Tracking/RegionHandle.cs index df3d9c311..a94ffa43c 100644 --- a/src/Ryujinx.Memory/Tracking/RegionHandle.cs +++ b/src/Ryujinx.Memory/Tracking/RegionHandle.cs @@ -55,6 +55,8 @@ protected set private RegionSignal _preAction; // Action to perform before a read or write. This will block the memory access. private PreciseRegionSignal _preciseAction; // Action to perform on a precise read or write. private readonly List _regions; + private readonly List _guestRegions; + private readonly List _allRegions; private readonly MemoryTracking _tracking; private bool _disposed; @@ -99,6 +101,7 @@ internal MemoryPermission RequiredPermission /// The bitmap the dirty flag for this handle is stored in /// The bit index representing the dirty flag for this handle /// Handle ID + /// Region flags /// True if the region handle starts mapped internal RegionHandle( MemoryTracking tracking, @@ -109,6 +112,7 @@ internal RegionHandle( ConcurrentBitmap bitmap, int bit, int id, + RegionFlags flags, bool mapped = true) { Bitmap = bitmap; @@ -128,11 +132,12 @@ internal RegionHandle( RealEndAddress = realAddress + realSize; _tracking = tracking; - _regions = tracking.GetVirtualRegionsForHandle(address, size); - foreach (var region in _regions) - { - region.Handles.Add(this); - } + + _regions = tracking.GetVirtualRegionsForHandle(address, size, false); + _guestRegions = GetGuestRegions(tracking, address, size, flags); + _allRegions = new List(_regions.Count + _guestRegions.Count); + + InitializeRegions(); } /// @@ -145,8 +150,9 @@ internal RegionHandle( /// The real, unaligned address of the handle /// The real, unaligned size of the handle /// Handle ID + /// Region flags /// True if the region handle starts mapped - internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, ulong realAddress, ulong realSize, int id, bool mapped = true) + internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, ulong realAddress, ulong realSize, int id, RegionFlags flags, bool mapped = true) { Bitmap = new ConcurrentBitmap(1, mapped); @@ -163,8 +169,37 @@ internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, ulong RealEndAddress = realAddress + realSize; _tracking = tracking; - _regions = tracking.GetVirtualRegionsForHandle(address, size); - foreach (var region in _regions) + + _regions = tracking.GetVirtualRegionsForHandle(address, size, false); + _guestRegions = GetGuestRegions(tracking, address, size, flags); + _allRegions = new List(_regions.Count + _guestRegions.Count); + + InitializeRegions(); + } + + private List GetGuestRegions(MemoryTracking tracking, ulong address, ulong size, RegionFlags flags) + { + ulong guestAddress; + ulong guestSize; + + if (flags.HasFlag(RegionFlags.UnalignedAccess)) + { + (guestAddress, guestSize) = tracking.GetUnalignedSafeRegion(address, size); + } + else + { + (guestAddress, guestSize) = (address, size); + } + + return tracking.GetVirtualRegionsForHandle(guestAddress, guestSize, true); + } + + private void InitializeRegions() + { + _allRegions.AddRange(_regions); + _allRegions.AddRange(_guestRegions); + + foreach (var region in _allRegions) { region.Handles.Add(this); } @@ -321,7 +356,7 @@ public void Reprotect(bool asDirty, bool consecutiveCheck = false) lock (_tracking.TrackingLock) { - foreach (VirtualRegion region in _regions) + foreach (VirtualRegion region in _allRegions) { protectionChanged |= region.UpdateProtection(); } @@ -379,7 +414,7 @@ public void RegisterAction(RegionSignal action) { lock (_tracking.TrackingLock) { - foreach (VirtualRegion region in _regions) + foreach (VirtualRegion region in _allRegions) { region.UpdateProtection(); } @@ -414,7 +449,16 @@ public void RegisterDirtyEvent(Action action) /// Virtual region to add as a child internal void AddChild(VirtualRegion region) { - _regions.Add(region); + if (region.Guest) + { + _guestRegions.Add(region); + } + else + { + _regions.Add(region); + } + + _allRegions.Add(region); } /// @@ -469,7 +513,7 @@ public void Dispose() lock (_tracking.TrackingLock) { - foreach (VirtualRegion region in _regions) + foreach (VirtualRegion region in _allRegions) { region.RemoveHandle(this); } diff --git a/src/Ryujinx.Memory/Tracking/VirtualRegion.cs b/src/Ryujinx.Memory/Tracking/VirtualRegion.cs index 538e94fef..bb087e9af 100644 --- a/src/Ryujinx.Memory/Tracking/VirtualRegion.cs +++ b/src/Ryujinx.Memory/Tracking/VirtualRegion.cs @@ -13,10 +13,14 @@ class VirtualRegion : AbstractRegion private readonly MemoryTracking _tracking; private MemoryPermission _lastPermission; - public VirtualRegion(MemoryTracking tracking, ulong address, ulong size, MemoryPermission lastPermission = MemoryPermission.Invalid) : base(address, size) + public bool Guest { get; } + + public VirtualRegion(MemoryTracking tracking, ulong address, ulong size, bool guest, MemoryPermission lastPermission = MemoryPermission.Invalid) : base(address, size) { _lastPermission = lastPermission; _tracking = tracking; + + Guest = guest; } /// @@ -103,7 +107,7 @@ public bool UpdateProtection() if (_lastPermission != permission) { - _tracking.ProtectVirtualRegion(this, permission); + _tracking.ProtectVirtualRegion(this, permission, Guest); _lastPermission = permission; return true; @@ -131,7 +135,7 @@ public void RemoveHandle(RegionHandle handle) public override INonOverlappingRange Split(ulong splitAddress) { - VirtualRegion newRegion = new(_tracking, splitAddress, EndAddress - splitAddress, _lastPermission); + VirtualRegion newRegion = new(_tracking, splitAddress, EndAddress - splitAddress, Guest, _lastPermission); Size = splitAddress - Address; // The new region inherits all of our parents. diff --git a/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs b/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs index 5c8396e6d..85a1ac02b 100644 --- a/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs +++ b/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs @@ -107,7 +107,7 @@ public void Reprotect(ulong va, ulong size, MemoryPermission protection) throw new NotImplementedException(); } - public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection) + public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest) { OnProtect?.Invoke(va, size, protection); } From 732db7581fdb11c87e7eea099fea8f9c153f98dd Mon Sep 17 00:00:00 2001 From: gdkchan Date: Thu, 14 Mar 2024 19:46:57 -0300 Subject: [PATCH 108/126] Consider Polygon as unsupported is triangle fans are unsupported on Vulkan (#6490) --- src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index 434545fe0..7d7c10952 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -781,7 +781,9 @@ internal PrimitiveTopology TopologyRemap(PrimitiveTopology topology) { PrimitiveTopology.Quads => PrimitiveTopology.Triangles, PrimitiveTopology.QuadStrip => PrimitiveTopology.TriangleStrip, - PrimitiveTopology.TriangleFan => Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.NoTriangleFans) ? PrimitiveTopology.Triangles : topology, + PrimitiveTopology.TriangleFan or PrimitiveTopology.Polygon => Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.NoTriangleFans) + ? PrimitiveTopology.Triangles + : topology, _ => topology, }; } @@ -791,7 +793,7 @@ internal bool TopologyUnsupported(PrimitiveTopology topology) return topology switch { PrimitiveTopology.Quads => true, - PrimitiveTopology.TriangleFan => Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.NoTriangleFans), + PrimitiveTopology.TriangleFan or PrimitiveTopology.Polygon => Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.NoTriangleFans), _ => false, }; } From 1217a8e69b9b4feadb34c2d38209d765c9542819 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Thu, 14 Mar 2024 22:59:09 +0000 Subject: [PATCH 109/126] GPU: Rebind RTs if scale changes when binding textures (#6493) This fixes a longstanding issue with resolution scale that could result in flickering graphics, typically the first frame something is drawn, or on camera cuts in cutscenes. The root cause of the issue is that texture scale can be changed when binding textures or images. This typically happens because a texture becomes a view of a larger texture, such as a 400x225 texture becoming a view of a 800x450 texture with two levels. If the 400x225 texture is bound as a render target and has state [1x Undesired], but the storage texture is [2x Scaled], the render target texture's scale is changed to [2x Scaled] to match its new storage. This means the scale changed after the render target state was processed... This can cause a number of issues. When render target state is processed, texture scales are examined and potentially changed so that they are all the same value. If one texture is scaled, all textures must be. If one texture is blacklisted from scaling, all of them must be. This results in a single resolution scale value being assigned to the TextureManager, which also scales the scissor and viewport values. If the scale is chosen as 1x, and a later texture binding changes one of the textures to be 2x, the scale in TextureManager no longer matches all of the bound textures. What's worse, the scales in these textures could mismatch entirely. This typically results in the support buffer scale, viewport and scissor being wrong for at least one of the bound render targets. This PR fixes the issue by re-evaluating render target state if any scale mismatches the expected scale after texture bindings happen. This can actually cause scale to change again, so it must loop back to perform texture bindings again. This can happen as many times as it needs to, but I don't expect it to happen more than once. Problematic bindings will just result in a blacklist, which will propagate to other bound targets. --- .../Engine/Threed/StateUpdater.cs | 17 +++++++++++--- .../Image/TextureManager.cs | 22 ++++++++++++++++--- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs index 6b4ea89f3..b3eb62185 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs @@ -343,11 +343,22 @@ private void CommitBindings() bool unalignedChanged = _currentSpecState.SetHasUnalignedStorageBuffer(_channel.BufferManager.HasUnalignedStorageBuffers); - if (!_channel.TextureManager.CommitGraphicsBindings(_shaderSpecState) || unalignedChanged) + bool scaleMismatch; + do { - // Shader must be reloaded. _vtgWritesRtLayer should not change. - UpdateShaderState(); + if (!_channel.TextureManager.CommitGraphicsBindings(_shaderSpecState, out scaleMismatch) || unalignedChanged) + { + // Shader must be reloaded. _vtgWritesRtLayer should not change. + UpdateShaderState(); + } + + if (scaleMismatch) + { + // Binding textures changed scale of the bound render targets, correct the render target scale and rebind. + UpdateRenderTargetState(); + } } + while (scaleMismatch); _channel.BufferManager.CommitGraphicsBindings(_drawState.DrawIndexed); } diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs index 3bf0beefd..8c2a88727 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs @@ -360,15 +360,16 @@ public bool CommitComputeBindings(ShaderSpecializationState specState) /// Commits bindings on the graphics pipeline. /// /// Specialization state for the bound shader + /// True if there is a scale mismatch in the render targets, indicating they must be re-evaluated /// True if all bound textures match the current shader specialization state, false otherwise - public bool CommitGraphicsBindings(ShaderSpecializationState specState) + public bool CommitGraphicsBindings(ShaderSpecializationState specState, out bool scaleMismatch) { _texturePoolCache.Tick(); _samplerPoolCache.Tick(); bool result = _gpBindingsManager.CommitBindings(specState); - UpdateRenderTargets(); + scaleMismatch = UpdateRenderTargets(); return result; } @@ -426,9 +427,12 @@ public TextureDescriptor GetGraphicsTextureDescriptor( /// /// Update host framebuffer attachments based on currently bound render target buffers. /// - public void UpdateRenderTargets() + /// True if there is a scale mismatch in the render targets, indicating they must be re-evaluated + public bool UpdateRenderTargets() { bool anyChanged = false; + float expectedScale = RenderTargetScale; + bool scaleMismatch = false; Texture dsTexture = _rtDepthStencil; ITexture hostDsTexture = null; @@ -448,6 +452,11 @@ public void UpdateRenderTargets() { _rtHostDs = hostDsTexture; anyChanged = true; + + if (dsTexture != null && dsTexture.ScaleFactor != expectedScale) + { + scaleMismatch = true; + } } for (int index = 0; index < _rtColors.Length; index++) @@ -470,6 +479,11 @@ public void UpdateRenderTargets() { _rtHostColors[index] = hostTexture; anyChanged = true; + + if (texture != null && texture.ScaleFactor != expectedScale) + { + scaleMismatch = true; + } } } @@ -477,6 +491,8 @@ public void UpdateRenderTargets() { _context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, _rtHostDs); } + + return scaleMismatch; } /// From 24068b023c0b8a1d8d9c74e9dcabcf48771231c3 Mon Sep 17 00:00:00 2001 From: TSRBerry <20988865+TSRBerry@users.noreply.github.com> Date: Sat, 16 Mar 2024 19:41:38 +0100 Subject: [PATCH 110/126] discord: Update ApplicationID (#6513) --- .../DiscordIntegrationModule.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs b/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs index 0b9439eaa..bbece1e1d 100644 --- a/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs +++ b/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs @@ -7,7 +7,7 @@ namespace Ryujinx.UI.Common public static class DiscordIntegrationModule { private const string Description = "A simple, experimental Nintendo Switch emulator."; - private const string CliendId = "568815339807309834"; + private const string ApplicationId = "1216775165866807456"; private static DiscordRpcClient _discordClient; private static RichPresence _discordPresenceMain; @@ -24,14 +24,14 @@ public static void Initialize() Details = "Main Menu", State = "Idling", Timestamps = Timestamps.Now, - Buttons = new[] - { + Buttons = + [ new Button { Label = "Website", - Url = "https://ryujinx.org/", + Url = "https://ryujinx.org/", }, - }, + ], }; ConfigurationState.Instance.EnableDiscordIntegration.Event += Update; @@ -52,7 +52,7 @@ private static void Update(object sender, ReactiveEventArgs evnt) // If we need to activate it and the client isn't active, initialize it if (evnt.NewValue && _discordClient == null) { - _discordClient = new DiscordRpcClient(CliendId); + _discordClient = new DiscordRpcClient(ApplicationId); _discordClient.Initialize(); _discordClient.SetPresence(_discordPresenceMain); @@ -74,14 +74,14 @@ public static void SwitchToPlayingState(string titleId, string titleName) Details = $"Playing {titleName}", State = (titleId == "0000000000000000") ? "Homebrew" : titleId.ToUpper(), Timestamps = Timestamps.Now, - Buttons = new[] - { + Buttons = + [ new Button { Label = "Website", Url = "https://ryujinx.org/", }, - }, + ], }); } From 26026d1357f29df6a938e1e13d5b84d4cf890891 Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Sat, 16 Mar 2024 18:46:03 +0000 Subject: [PATCH 111/126] Fix Title Update Manager not refreshing app list (#6507) --- src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs b/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs index f3ac69600..732f410ae 100644 --- a/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs @@ -1,4 +1,6 @@ +using Avalonia; using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Interactivity; using Avalonia.Styling; using FluentAvalonia.UI.Controls; @@ -59,9 +61,15 @@ public void Save(object sender, RoutedEventArgs e) { ViewModel.Save(); - if (VisualRoot is MainWindow window) + if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime al) { - window.LoadApplications(); + foreach (Window window in al.Windows) + { + if (window is MainWindow mainWindow) + { + mainWindow.LoadApplications(); + } + } } ((ContentDialog)Parent).Hide(); From e19e7622a34d7ef0dc39b36ad41297003fcb8be4 Mon Sep 17 00:00:00 2001 From: standstaff <163401255+standstaff@users.noreply.github.com> Date: Sun, 17 Mar 2024 02:49:54 +0800 Subject: [PATCH 112/126] chore: remove repetitive words (#6500) Signed-off-by: standstaff --- src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs | 2 +- .../Translation/Optimizations/Optimizer.cs | 2 +- src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs | 2 +- .../SoftwareKeyboard/SoftwareKeyboardRendererBase.cs | 4 ++-- src/Ryujinx.Memory/MemoryBlock.cs | 8 ++++---- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs b/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs index 889f5c9ca..964507a21 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs @@ -173,7 +173,7 @@ public bool TryGetOrAddRange(ulong gpuVa, ulong size, out MultiRange range) ShrinkOverlapsBufferIfNeeded(); - // If the the range is not properly aligned for sparse mapping, + // If the range is not properly aligned for sparse mapping, // let's just force it to a single range. // This might cause issues in some applications that uses sparse // mappings. diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs index 17427a5f9..ea06691ba 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs @@ -322,7 +322,7 @@ private static void EliminateMultiplyByFragmentCoordW(Operation operation) Operand lhs = operation.GetSource(0); Operand rhs = operation.GetSource(1); - // Check LHS of the the main multiplication operation. We expect an input being multiplied by gl_FragCoord.w. + // Check LHS of the main multiplication operation. We expect an input being multiplied by gl_FragCoord.w. if (lhs.AsgOp is not Operation attrMulOp || attrMulOp.Inst != (Instruction.FP32 | Instruction.Multiply)) { return; diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs index 3b1321c58..d59ca7e0e 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs @@ -136,7 +136,7 @@ internal static VulkanPhysicalDevice FindSuitablePhysicalDevice(Vk api, VulkanIn { instance.EnumeratePhysicalDevices(out var physicalDevices).ThrowOnError(); - // First we try to pick the the user preferred GPU. + // First we try to pick the user preferred GPU. for (int i = 0; i < physicalDevices.Length; i++) { if (IsPreferredAndSuitableDevice(api, physicalDevices[i], surface, preferredGpuId)) diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs index 0b87f87ad..9e48568e1 100644 --- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs @@ -466,7 +466,7 @@ private void DrawTextBox(IImageProcessingContext context, SoftwareKeyboardUIStat private void DrawPadButton(IImageProcessingContext context, PointF point, Image icon, string label, bool pressed, bool enabled) { - // Use relative positions so we can center the the entire drawing later. + // Use relative positions so we can center the entire drawing later. float iconX = 0; float iconY = 0; @@ -522,7 +522,7 @@ private void DrawControllerToggle(IImageProcessingContext context, PointF point) { var labelRectangle = MeasureString(ControllerToggleText, _labelsTextFont); - // Use relative positions so we can center the the entire drawing later. + // Use relative positions so we can center the entire drawing later. float keyWidth = _keyModeIcon.Width; float keyHeight = _keyModeIcon.Height; diff --git a/src/Ryujinx.Memory/MemoryBlock.cs b/src/Ryujinx.Memory/MemoryBlock.cs index 7fe7862a9..59ee269bb 100644 --- a/src/Ryujinx.Memory/MemoryBlock.cs +++ b/src/Ryujinx.Memory/MemoryBlock.cs @@ -174,7 +174,7 @@ public void Reprotect(ulong offset, ulong size, MemoryPermission permission, boo /// Starting offset of the range being read /// Span where the bytes being read will be copied to /// Throw when the memory block has already been disposed - /// Throw when the memory region specified for the the data is out of range + /// Throw when the memory region specified for the data is out of range [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Read(ulong offset, Span data) { @@ -188,7 +188,7 @@ public void Read(ulong offset, Span data) /// Offset where the data is located /// Data at the specified address /// Throw when the memory block has already been disposed - /// Throw when the memory region specified for the the data is out of range + /// Throw when the memory region specified for the data is out of range [MethodImpl(MethodImplOptions.AggressiveInlining)] public T Read(ulong offset) where T : unmanaged { @@ -201,7 +201,7 @@ public T Read(ulong offset) where T : unmanaged /// Starting offset of the range being written /// Span where the bytes being written will be copied from /// Throw when the memory block has already been disposed - /// Throw when the memory region specified for the the data is out of range + /// Throw when the memory region specified for the data is out of range [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Write(ulong offset, ReadOnlySpan data) { @@ -215,7 +215,7 @@ public void Write(ulong offset, ReadOnlySpan data) /// Offset to write the data into /// Data to be written /// Throw when the memory block has already been disposed - /// Throw when the memory region specified for the the data is out of range + /// Throw when the memory region specified for the data is out of range [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Write(ulong offset, T data) where T : unmanaged { From 18df25f66f32cf342398763f33fbd193dacc6372 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 16 Mar 2024 20:02:18 +0100 Subject: [PATCH 113/126] nuget: bump the avalonia group with 2 updates (#6505) Bumps the avalonia group with 2 updates: [Avalonia.Svg](https://github.com/wieslawsoltes/Svg.Skia) and [Avalonia.Svg.Skia](https://github.com/wieslawsoltes/Svg.Skia). Updates `Avalonia.Svg` from 11.0.0.14 to 11.0.0.16 - [Release notes](https://github.com/wieslawsoltes/Svg.Skia/releases) - [Changelog](https://github.com/wieslawsoltes/Svg.Skia/blob/master/CHANGELOG.md) - [Commits](https://github.com/wieslawsoltes/Svg.Skia/commits) Updates `Avalonia.Svg.Skia` from 11.0.0.14 to 11.0.0.16 - [Release notes](https://github.com/wieslawsoltes/Svg.Skia/releases) - [Changelog](https://github.com/wieslawsoltes/Svg.Skia/blob/master/CHANGELOG.md) - [Commits](https://github.com/wieslawsoltes/Svg.Skia/commits) --- updated-dependencies: - dependency-name: Avalonia.Svg dependency-type: direct:production update-type: version-update:semver-patch dependency-group: avalonia - dependency-name: Avalonia.Svg.Skia dependency-type: direct:production update-type: version-update:semver-patch dependency-group: avalonia ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index c08e94357..083eaf3d5 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -8,8 +8,8 @@ - - + + From 50bdda5baafcdca2773b38e5896c611b7bcc3112 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 16 Mar 2024 20:13:30 +0100 Subject: [PATCH 114/126] nuget: bump Microsoft.IdentityModel.JsonWebTokens from 7.3.0 to 7.4.0 (#6366) Bumps [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 7.3.0 to 7.4.0. - [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/dev/CHANGELOG.md) - [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/7.3.0...v7.4.0) --- updated-dependencies: - dependency-name: Microsoft.IdentityModel.JsonWebTokens dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 083eaf3d5..a0b694ce1 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -20,7 +20,7 @@ - + From bb8c5ebae1cc9666cccd83106f592148e7aa4293 Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Sat, 16 Mar 2024 19:34:26 +0000 Subject: [PATCH 115/126] Ava UI: Content Dialog Fixes (#6482) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Don’t use ContentDialogHelper when not necessary * Remove `ExtendClientAreaToDecorationsHint` --- src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml.cs | 1 - .../UI/Windows/DownloadableContentManagerWindow.axaml.cs | 2 +- src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml.cs b/src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml.cs index 2b12d72f1..0e2410247 100644 --- a/src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml.cs @@ -9,7 +9,6 @@ public ContentDialogOverlayWindow() { InitializeComponent(); - ExtendClientAreaToDecorationsHint = true; TransparencyLevelHint = new[] { WindowTransparencyLevel.Transparent }; WindowStartupLocation = WindowStartupLocation.Manual; SystemDecorations = SystemDecorations.None; diff --git a/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs b/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs index 0c02fa0f5..b9e2c7be3 100644 --- a/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs @@ -47,7 +47,7 @@ public static async Task Show(VirtualFileSystem virtualFileSystem, ulong titleId contentDialog.Styles.Add(bottomBorder); - await ContentDialogHelper.ShowAsync(contentDialog); + await contentDialog.ShowAsync(); } private void SaveAndClose(object sender, RoutedEventArgs routedEventArgs) diff --git a/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs b/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs index 732f410ae..f5e250323 100644 --- a/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs @@ -49,7 +49,7 @@ public static async Task Show(VirtualFileSystem virtualFileSystem, ulong titleId contentDialog.Styles.Add(bottomBorder); - await ContentDialogHelper.ShowAsync(contentDialog); + await contentDialog.ShowAsync(); } private void Close(object sender, RoutedEventArgs e) From a0552fd78ba90c843e8af6c7dd040f1fc83d72e9 Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Sun, 17 Mar 2024 01:27:14 +0000 Subject: [PATCH 116/126] Ava UI: Fix locale crash (#6385) * Fix locale crash * Apply suggestions from code review --------- Co-authored-by: Ac_K --- src/Ryujinx/Common/Locale/LocaleManager.cs | 54 +++++++++++++--------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/src/Ryujinx/Common/Locale/LocaleManager.cs b/src/Ryujinx/Common/Locale/LocaleManager.cs index e973fcc06..2d3deeaa3 100644 --- a/src/Ryujinx/Common/Locale/LocaleManager.cs +++ b/src/Ryujinx/Common/Locale/LocaleManager.cs @@ -30,28 +30,22 @@ public LocaleManager() Load(); } - public void Load() + private void Load() { - // Load the system Language Code. - string localeLanguageCode = CultureInfo.CurrentCulture.Name.Replace('-', '_'); + var localeLanguageCode = !string.IsNullOrEmpty(ConfigurationState.Instance.UI.LanguageCode.Value) ? + ConfigurationState.Instance.UI.LanguageCode.Value : CultureInfo.CurrentCulture.Name.Replace('-', '_'); - // If the view is loaded with the UI Previewer detached, then override it with the saved one or default. + // Load en_US as default, if the target language translation is missing or incomplete. + LoadDefaultLanguage(); + LoadLanguage(localeLanguageCode); + + // Save whatever we ended up with. if (Program.PreviewerDetached) { - if (!string.IsNullOrEmpty(ConfigurationState.Instance.UI.LanguageCode.Value)) - { - localeLanguageCode = ConfigurationState.Instance.UI.LanguageCode.Value; - } - else - { - localeLanguageCode = DefaultLanguageCode; - } - } + ConfigurationState.Instance.UI.LanguageCode.Value = _localeLanguageCode; - // Load en_US as default, if the target language translation is incomplete. - LoadDefaultLanguage(); - - LoadLanguage(localeLanguageCode); + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } } public string this[LocaleKeys key] @@ -126,24 +120,42 @@ public string UpdateAndGetDynamicValue(LocaleKeys key, params object[] values) private void LoadDefaultLanguage() { - _localeDefaultStrings = LoadJsonLanguage(); + _localeDefaultStrings = LoadJsonLanguage(DefaultLanguageCode); } public void LoadLanguage(string languageCode) { - foreach (var item in LoadJsonLanguage(languageCode)) + var locale = LoadJsonLanguage(languageCode); + + if (locale == null) + { + _localeLanguageCode = DefaultLanguageCode; + locale = _localeDefaultStrings; + } + else + { + _localeLanguageCode = languageCode; + } + + foreach (var item in locale) { this[item.Key] = item.Value; } - _localeLanguageCode = languageCode; LocaleChanged?.Invoke(); } - private static Dictionary LoadJsonLanguage(string languageCode = DefaultLanguageCode) + private static Dictionary LoadJsonLanguage(string languageCode) { var localeStrings = new Dictionary(); string languageJson = EmbeddedResources.ReadAllText($"Ryujinx/Assets/Locales/{languageCode}.json"); + + if (languageJson == null) + { + // We were unable to find file for that language code. + return null; + } + var strings = JsonHelper.Deserialize(languageJson, CommonJsonContext.Default.StringDictionary); foreach (var item in strings) From d26ef2eec309a7a7b30b103c245044d1cdc08add Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 17 Mar 2024 02:55:11 +0100 Subject: [PATCH 117/126] nuget: bump Microsoft.CodeAnalysis.CSharp from 4.8.0 to 4.9.2 (#6397) Bumps [Microsoft.CodeAnalysis.CSharp](https://github.com/dotnet/roslyn) from 4.8.0 to 4.9.2. - [Release notes](https://github.com/dotnet/roslyn/releases) - [Changelog](https://github.com/dotnet/roslyn/blob/main/docs/Breaking%20API%20Changes.md) - [Commits](https://github.com/dotnet/roslyn/commits) --- updated-dependencies: - dependency-name: Microsoft.CodeAnalysis.CSharp dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index a0b694ce1..455735fc4 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -19,7 +19,7 @@ - + From 75a4ea337008741896a88b8aed7919d368b46ea9 Mon Sep 17 00:00:00 2001 From: Ac_K Date: Thu, 21 Mar 2024 01:07:27 +0100 Subject: [PATCH 118/126] New Crowdin updates (#6541) * New translations en_us.json (French) * New translations en_us.json (Spanish) * New translations en_us.json (Arabic) * New translations en_us.json (German) * New translations en_us.json (Greek) * New translations en_us.json (Hebrew) * New translations en_us.json (Italian) * New translations en_us.json (Japanese) * New translations en_us.json (Korean) * New translations en_us.json (Polish) * New translations en_us.json (Russian) * New translations en_us.json (Turkish) * New translations en_us.json (Ukrainian) * New translations en_us.json (Chinese Simplified) * New translations en_us.json (Chinese Traditional) * New translations en_us.json (Portuguese, Brazilian) * New translations en_us.json (Thai) * Add missing Thai language name * Add new languages * Enable RTL for Arabic --------- Co-authored-by: gdkchan --- src/Ryujinx/Assets/Locales/ar_SA.json | 668 ++++++++++++++++ src/Ryujinx/Assets/Locales/de_DE.json | 100 +-- src/Ryujinx/Assets/Locales/el_GR.json | 116 +-- src/Ryujinx/Assets/Locales/es_ES.json | 102 +-- src/Ryujinx/Assets/Locales/fr_FR.json | 214 +++--- src/Ryujinx/Assets/Locales/he_IL.json | 88 ++- src/Ryujinx/Assets/Locales/it_IT.json | 82 +- src/Ryujinx/Assets/Locales/ja_JP.json | 84 ++- src/Ryujinx/Assets/Locales/ko_KR.json | 84 ++- src/Ryujinx/Assets/Locales/pl_PL.json | 350 ++++----- src/Ryujinx/Assets/Locales/pt_BR.json | 128 ++-- src/Ryujinx/Assets/Locales/ru_RU.json | 284 +++---- src/Ryujinx/Assets/Locales/th_TH.json | 668 ++++++++++++++++ src/Ryujinx/Assets/Locales/tr_TR.json | 96 +-- src/Ryujinx/Assets/Locales/uk_UA.json | 240 +++--- src/Ryujinx/Assets/Locales/zh_CN.json | 532 ++++++------- src/Ryujinx/Assets/Locales/zh_TW.json | 840 +++++++++++---------- src/Ryujinx/Common/Locale/LocaleManager.cs | 2 +- src/Ryujinx/Ryujinx.csproj | 6 +- 19 files changed, 3102 insertions(+), 1582 deletions(-) create mode 100644 src/Ryujinx/Assets/Locales/ar_SA.json create mode 100644 src/Ryujinx/Assets/Locales/th_TH.json diff --git a/src/Ryujinx/Assets/Locales/ar_SA.json b/src/Ryujinx/Assets/Locales/ar_SA.json new file mode 100644 index 000000000..1fac4850c --- /dev/null +++ b/src/Ryujinx/Assets/Locales/ar_SA.json @@ -0,0 +1,668 @@ +{ + "Language": "اَلْعَرَبِيَّةُ", + "MenuBarFileOpenApplet": "فتح التطبيق المُصغَّر", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "افتح تطبيق محرر الـMii المُصغَّر في الوضع المستقل", + "SettingsTabInputDirectMouseAccess": "الوصول المباشر للفأرة", + "SettingsTabSystemMemoryManagerMode": "وضع إدارة الذاكرة:", + "SettingsTabSystemMemoryManagerModeSoftware": "البرنامج", + "SettingsTabSystemMemoryManagerModeHost": "المُضيف (سريع)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "المضيف غير محدد (سريع، غير آمن)", + "SettingsTabSystemUseHypervisor": "استخدم الهايبرڤايزور", + "MenuBarFile": "_ملف", + "MenuBarFileOpenFromFile": "_تحميل تطبيق من ملف", + "MenuBarFileOpenUnpacked": "تحميل لعبه غير محزومه", + "MenuBarFileOpenEmuFolder": "فتح مجلّد Ryujinx", + "MenuBarFileOpenLogsFolder": "افتح مجلد السجلات", + "MenuBarFileExit": "_خروج", + "MenuBarOptions": "_خيارات", + "MenuBarOptionsToggleFullscreen": "وضع ملء الشاشة", + "MenuBarOptionsStartGamesInFullscreen": "ابدأ الألعاب في وضع ملء الشاشة", + "MenuBarOptionsStopEmulation": "إيقاف المحاكاة", + "MenuBarOptionsSettings": "الإعدادات", + "MenuBarOptionsManageUserProfiles": "إدارة الملفات الشخصية للمستخدم", + "MenuBarActions": "الإجراءات", + "MenuBarOptionsSimulateWakeUpMessage": "محاكاة رسالة الاستيقاظ", + "MenuBarActionsScanAmiibo": "فحص Amiibo", + "MenuBarTools": "الأدوات", + "MenuBarToolsInstallFirmware": "تثبيت البرامج الثابتة", + "MenuBarFileToolsInstallFirmwareFromFile": "تثبيت البرنامج الثابت من XCI أو ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "تثبيت برنامج ثابت من مجلد", + "MenuBarToolsManageFileTypes": "إدارة أنواع الملفات", + "MenuBarToolsInstallFileTypes": "تثبيت أنواع الملفات", + "MenuBarToolsUninstallFileTypes": "إزالة أنواع الملفات", + "MenuBarHelp": "_مساعدة", + "MenuBarHelpCheckForUpdates": "تحقق من التحديثات", + "MenuBarHelpAbout": "عن البرنامج", + "MenuSearch": "بحث...", + "GameListHeaderFavorite": "مفضلة", + "GameListHeaderIcon": "الأيقونة", + "GameListHeaderApplication": "الاسم", + "GameListHeaderDeveloper": "المطور", + "GameListHeaderVersion": "الإصدار", + "GameListHeaderTimePlayed": "وقت اللعب", + "GameListHeaderLastPlayed": "اخر تشغيل", + "GameListHeaderFileExtension": "امتداد الملف", + "GameListHeaderFileSize": "حجم الملف", + "GameListHeaderPath": "المسار", + "GameListContextMenuOpenUserSaveDirectory": "فتح مجلد حفظ المستخدم", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "يفتح المجلد الذي يحتوي على حفظ المستخدم للتطبيق", + "GameListContextMenuOpenDeviceSaveDirectory": "فتح مجلد حفظ الجهاز", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "يفتح المجلد الذي يحتوي على حفظ الجهاز للتطبيق", + "GameListContextMenuOpenBcatSaveDirectory": "فتح مجلد حفظ الـBCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "يفتح المجلد الذي يحتوي على حفظ الـBCAT للتطبيق", + "GameListContextMenuManageTitleUpdates": "إدارة تحديثات العنوان", + "GameListContextMenuManageTitleUpdatesToolTip": "يفتح نافذة إدارة تحديث العنوان", + "GameListContextMenuManageDlc": "إدارة المحتوي الإضافي", + "GameListContextMenuManageDlcToolTip": "يفتح نافذة إدارة المحتوي الإضافي", + "GameListContextMenuCacheManagement": "إدارة ذاكرة التخزين المؤقت", + "GameListContextMenuCacheManagementPurgePptc": "إعادة بناء PPTC في قائمة الانتظار", + "GameListContextMenuCacheManagementPurgePptcToolTip": "تنشيط PPTC لإعادة البناء في وقت التمهيد عند بدء تشغيل اللعبة التالية", + "GameListContextMenuCacheManagementPurgeShaderCache": "إزالة ذاكرة التشغيل المؤقتة للمظللات ", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "حذف الذاكرة المؤقتة للمظللات الخاصة بالتطبيق", + "GameListContextMenuCacheManagementOpenPptcDirectory": "فتح مجلد PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "يفتح المجلد الذي يحتوي على الـPPTC للتطبيق", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "فتح مجلد الذاكرة المؤقتة للمظللات ", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "يفتح المجلد الذي يحتوي على ذاكرة التشغيل المؤقتة للمظللات الخاصة بالتطبيق", + "GameListContextMenuExtractData": "إستخراج البيانات", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "إستخراج قسم ExeFS من التكوين الحالي للتطبيقات (بما في ذلك التحديثات)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "استخراج قسم RomFS من التكوين الحالي للتطبيقات (بما في ذلك التحديثات)", + "GameListContextMenuExtractDataLogo": "شعار", + "GameListContextMenuExtractDataLogoToolTip": "استخراج قسم الشعار من التكوين الحالي للتطبيقات (بما في ذلك التحديثات)", + "GameListContextMenuCreateShortcut": "إنشاء اختصار للتطبيق", + "GameListContextMenuCreateShortcutToolTip": "قم بإنشاء اختصار لسطح المكتب لتشغيل التطبيق المحدد", + "GameListContextMenuCreateShortcutToolTipMacOS": "قم بإنشاء اختصار في مجلد تطبيقات نظام التشغيل MacOS الذي يقوم بتشغيل التطبيق المحدد", + "GameListContextMenuOpenModsDirectory": "افتح مجلد التعديلات", + "GameListContextMenuOpenModsDirectoryToolTip": "يفتح المجلد الذي يحتوي على تعديلات التطبيق", + "GameListContextMenuOpenSdModsDirectory": "افتح مجلد تعديلات Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "يفتح دليل Atmosphere لبطاقة SD البديلة الذي يحتوي على تعديلات التطبيق. مفيد للتعديلات التي تم تعبئتها للأجهزة الحقيقية.", + "StatusBarGamesLoaded": "{0}/{1} الألعاب التي تم تحميلها", + "StatusBarSystemVersion": "إصدار النظام: {0}", + "LinuxVmMaxMapCountDialogTitle": "الحد الأدنى لتعيينات الذاكرة المكتشفة", + "LinuxVmMaxMapCountDialogTextPrimary": "هل ترغب في زيادة قيمة vm.max_map_count إلى {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "قد تحاول بعض الألعاب إنشاء المزيد من تعيينات الذاكرة أكثر مما هو مسموح به حاليا. سيتحطم Ryujinx بمجرد تجاوز هذا الحد.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "نعم، حتى إعادة التشغيل التالية", + "LinuxVmMaxMapCountDialogButtonPersistent": "نعم، بشكل دائم", + "LinuxVmMaxMapCountWarningTextPrimary": "الحد الأقصى لمقدار تعيينات الذاكرة أقل من الموصى به.", + "LinuxVmMaxMapCountWarningTextSecondary": "القيمة الحالية لـ vm.max_map_count ({0}) أقل من {1}. قد تحاول بعض الألعاب إنشاء المزيد من تعيينات الذاكرة أكثر مما هو مسموح به حاليا. سيتحطم Ryujinx بمجرد تجاوز هذا الحد.\n\nقد ترغب في زيادة الحد اليدوي أو تثبيت pkexec، مما يسمح لـ Ryujinx بالمساعدة في ذلك.", + "Settings": "إعدادات", + "SettingsTabGeneral": "واجهة المستخدم", + "SettingsTabGeneralGeneral": "العامة", + "SettingsTabGeneralEnableDiscordRichPresence": "تمكين وجود ديسكورد الغني", + "SettingsTabGeneralCheckUpdatesOnLaunch": "التحقق من وجود تحديثات عند التشغيل", + "SettingsTabGeneralShowConfirmExitDialog": "إظهار مربع حوار \"تأكيد الخروج\"", + "SettingsTabGeneralHideCursor": "إخفاء المؤشر:", + "SettingsTabGeneralHideCursorNever": "مطلقاً", + "SettingsTabGeneralHideCursorOnIdle": "عند الخمول", + "SettingsTabGeneralHideCursorAlways": "دائماً", + "SettingsTabGeneralGameDirectories": "مجلدات الألعاب", + "SettingsTabGeneralAdd": "إضافة", + "SettingsTabGeneralRemove": "إزالة", + "SettingsTabSystem": "النظام", + "SettingsTabSystemCore": "النواة", + "SettingsTabSystemSystemRegion": "منطقة النظام:", + "SettingsTabSystemSystemRegionJapan": "اليابان", + "SettingsTabSystemSystemRegionUSA": "الولايات المتحدة الأمريكية", + "SettingsTabSystemSystemRegionEurope": "أوروبا", + "SettingsTabSystemSystemRegionAustralia": "أستراليا", + "SettingsTabSystemSystemRegionChina": "الصين", + "SettingsTabSystemSystemRegionKorea": "كوريا", + "SettingsTabSystemSystemRegionTaiwan": "تايوان", + "SettingsTabSystemSystemLanguage": "لغة النظام:", + "SettingsTabSystemSystemLanguageJapanese": "اليابانية", + "SettingsTabSystemSystemLanguageAmericanEnglish": "الإنجليزية الأمريكية", + "SettingsTabSystemSystemLanguageFrench": "الفرنسية", + "SettingsTabSystemSystemLanguageGerman": "الألمانية", + "SettingsTabSystemSystemLanguageItalian": "الإيطالية", + "SettingsTabSystemSystemLanguageSpanish": "الإسبانية", + "SettingsTabSystemSystemLanguageChinese": "الصينية", + "SettingsTabSystemSystemLanguageKorean": "الكورية", + "SettingsTabSystemSystemLanguageDutch": "الهولندية", + "SettingsTabSystemSystemLanguagePortuguese": "البرتغالية", + "SettingsTabSystemSystemLanguageRussian": "الروسية", + "SettingsTabSystemSystemLanguageTaiwanese": "التايوانية", + "SettingsTabSystemSystemLanguageBritishEnglish": "الإنجليزية البريطانية", + "SettingsTabSystemSystemLanguageCanadianFrench": "الفرنسية الكندية", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "إسبانية أمريكا اللاتينية", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "الصينية المبسطة", + "SettingsTabSystemSystemLanguageTraditionalChinese": "الصينية التقليدية", + "SettingsTabSystemSystemTimeZone": "نظام التوقيت للنظام:", + "SettingsTabSystemSystemTime": "توقيت النظام:", + "SettingsTabSystemEnableVsync": "VSync", + "SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "FS Integrity Checks", + "SettingsTabSystemAudioBackend": "خلفية الصوت:", + "SettingsTabSystemAudioBackendDummy": "زائف", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "الاختراقات", + "SettingsTabSystemHacksNote": "قد يتسبب في عدم الاستقرار", + "SettingsTabSystemExpandDramSize": "استخدام تخطيط الذاكرة البديل (المطورين)", + "SettingsTabSystemIgnoreMissingServices": "تجاهل الخدمات المفقودة", + "SettingsTabGraphics": "الرسوميات", + "SettingsTabGraphicsAPI": "الرسومات API", + "SettingsTabGraphicsEnableShaderCache": "تفعيل ذاكرة المظللات المؤقتة", + "SettingsTabGraphicsAnisotropicFiltering": "تصفية متباين الخواص:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "تلقائي", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4×", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "مقياس الدقة", + "SettingsTabGraphicsResolutionScaleCustom": "مخصص (لا ينصح به)", + "SettingsTabGraphicsResolutionScaleNative": "الأصل (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (لا ينصح به)", + "SettingsTabGraphicsAspectRatio": "نسبة الارتفاع إلى العرض:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "تمدد لتلائم حجم النافذة", + "SettingsTabGraphicsDeveloperOptions": "خيارات المطور", + "SettingsTabGraphicsShaderDumpPath": "مسار تفريغ الرسومات:", + "SettingsTabLogging": "التسجيل", + "SettingsTabLoggingLogging": "التسجيل", + "SettingsTabLoggingEnableLoggingToFile": "تفعيل التسجيل إلى ملف", + "SettingsTabLoggingEnableStubLogs": "تفعيل سجلات الـStub", + "SettingsTabLoggingEnableInfoLogs": "تفعيل سجلات المعلومات", + "SettingsTabLoggingEnableWarningLogs": "تفعيل سجلات التحذير", + "SettingsTabLoggingEnableErrorLogs": "تفعيل سجلات الأخطاء", + "SettingsTabLoggingEnableTraceLogs": "تفعيل سجلات التتبع", + "SettingsTabLoggingEnableGuestLogs": "تفعيل سجلات الضيوف", + "SettingsTabLoggingEnableFsAccessLogs": "Enable Fs Access Logs", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs Global Access Log Mode:", + "SettingsTabLoggingDeveloperOptions": "خيارات المطور", + "SettingsTabLoggingDeveloperOptionsNote": "تحذير: سوف يقلل من الأداء", + "SettingsTabLoggingGraphicsBackendLogLevel": "مستوى سجل خلفية الرسومات:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "لا شيء", + "SettingsTabLoggingGraphicsBackendLogLevelError": "خطأ", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "تباطؤ", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "الكل", + "SettingsTabLoggingEnableDebugLogs": "تمكين سجلات التصحيح", + "SettingsTabInput": "الإدخال", + "SettingsTabInputEnableDockedMode": "مركب بالمنصة", + "SettingsTabInputDirectKeyboardAccess": "الوصول المباشر إلى لوحة المفاتيح", + "SettingsButtonSave": "حفظ", + "SettingsButtonClose": "إغلاق", + "SettingsButtonOk": "موافق", + "SettingsButtonCancel": "إلغاء", + "SettingsButtonApply": "تطبيق", + "ControllerSettingsPlayer": "اللاعب", + "ControllerSettingsPlayer1": "اللاعب ١", + "ControllerSettingsPlayer2": "اللاعب 2", + "ControllerSettingsPlayer3": "اللاعب 3", + "ControllerSettingsPlayer4": "اللاعب 4", + "ControllerSettingsPlayer5": "اللاعب 5", + "ControllerSettingsPlayer6": "اللاعب 6", + "ControllerSettingsPlayer7": "اللاعب 7", + "ControllerSettingsPlayer8": "اللاعب 8", + "ControllerSettingsHandheld": "محمول", + "ControllerSettingsInputDevice": "جهاز الإدخال", + "ControllerSettingsRefresh": "تحديث", + "ControllerSettingsDeviceDisabled": "معطل", + "ControllerSettingsControllerType": "نوع ذراع التحكم", + "ControllerSettingsControllerTypeHandheld": "محمول", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "اقتران جوي كون", + "ControllerSettingsControllerTypeJoyConLeft": "يسار جوي كون", + "ControllerSettingsControllerTypeJoyConRight": "يمين جوي كون", + "ControllerSettingsProfile": "الملف الشخصي", + "ControllerSettingsProfileDefault": "افتراضي", + "ControllerSettingsLoad": "تحميل", + "ControllerSettingsAdd": "إضافة", + "ControllerSettingsRemove": "إزالة", + "ControllerSettingsButtons": "الأزرار", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "لوحة الاتجاه", + "ControllerSettingsDPadUp": "اعلى", + "ControllerSettingsDPadDown": "أسفل", + "ControllerSettingsDPadLeft": "يسار", + "ControllerSettingsDPadRight": "يمين", + "ControllerSettingsStickButton": "زر", + "ControllerSettingsStickUp": "اعلى", + "ControllerSettingsStickDown": "أسفل", + "ControllerSettingsStickLeft": "يسار", + "ControllerSettingsStickRight": "يمين", + "ControllerSettingsStickStick": "عصا", + "ControllerSettingsStickInvertXAxis": "عكس عرض العصا", + "ControllerSettingsStickInvertYAxis": "عكس أفق العصا", + "ControllerSettingsStickDeadzone": "المنطقة الميتة:", + "ControllerSettingsLStick": "العصا اليسرى", + "ControllerSettingsRStick": "العصا اليمنى", + "ControllerSettingsTriggersLeft": "المحفزات اليسرى", + "ControllerSettingsTriggersRight": "المحفزات اليمني", + "ControllerSettingsTriggersButtonsLeft": "أزرار التحفيز اليسرى", + "ControllerSettingsTriggersButtonsRight": "أزرار التحفيز اليمنى", + "ControllerSettingsTriggers": "المحفزات", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "الأزرار اليسار", + "ControllerSettingsExtraButtonsRight": "الأزرار اليمين", + "ControllerSettingsMisc": "إعدادات إضافية", + "ControllerSettingsTriggerThreshold": "قوة التحفيز:", + "ControllerSettingsMotion": "الحركة", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "استخدام الحركة المتوافقة مع CemuHook", + "ControllerSettingsMotionControllerSlot": "خانة ذراع التحكم:", + "ControllerSettingsMotionMirrorInput": "إعادة الإدخال", + "ControllerSettingsMotionRightJoyConSlot": "خانة اليمين JoyCon :", + "ControllerSettingsMotionServerHost": "مضيف الخادم:", + "ControllerSettingsMotionGyroSensitivity": "حساسية الغيرو:", + "ControllerSettingsMotionGyroDeadzone": "منطقة الغيرو الميتة:", + "ControllerSettingsSave": "حفظ", + "ControllerSettingsClose": "إغلاق", + "UserProfilesSelectedUserProfile": "الملف الشخصي المحدد للمستخدم:", + "UserProfilesSaveProfileName": "حفظ اسم الملف الشخصي", + "UserProfilesChangeProfileImage": "تغيير صورة الملف الشخصي", + "UserProfilesAvailableUserProfiles": "الملفات الشخصية للمستخدم المتاحة:", + "UserProfilesAddNewProfile": "أنشئ ملف شخصي", + "UserProfilesDelete": "حذف", + "UserProfilesClose": "إغلاق", + "ProfileNameSelectionWatermark": "اختر اسم مستعار", + "ProfileImageSelectionTitle": "تحديد صورة الملف الشخصي", + "ProfileImageSelectionHeader": "اختر صورة الملف الشخصي", + "ProfileImageSelectionNote": "يمكنك استيراد صورة ملف تعريف مخصصة، أو تحديد صورة رمزية من البرامج الثابتة للنظام", + "ProfileImageSelectionImportImage": "استيراد ملف الصورة", + "ProfileImageSelectionSelectAvatar": "حدد الصورة الرمزية للبرنامج الثابت", + "InputDialogTitle": "حوار الإدخال", + "InputDialogOk": "موافق", + "InputDialogCancel": "إلغاء", + "InputDialogAddNewProfileTitle": "اختر اسم الملف الشخصي", + "InputDialogAddNewProfileHeader": "الرجاء إدخال اسم الملف الشخصي", + "InputDialogAddNewProfileSubtext": "(الطول الأقصى: {0})", + "AvatarChoose": "اختر الصورة الرمزية", + "AvatarSetBackgroundColor": "تعيين لون الخلفية", + "AvatarClose": "إغلاق", + "ControllerSettingsLoadProfileToolTip": "تحميل الملف الشخصي", + "ControllerSettingsAddProfileToolTip": "إضافة ملف تعريف", + "ControllerSettingsRemoveProfileToolTip": "إزالة ملف التعريف", + "ControllerSettingsSaveProfileToolTip": "حفظ ملف التعريف", + "MenuBarFileToolsTakeScreenshot": "أخذ لقطة للشاشة", + "MenuBarFileToolsHideUi": "إخفاء واجهة المستخدم", + "GameListContextMenuRunApplication": "تشغيل التطبيق", + "GameListContextMenuToggleFavorite": "تبديل المفضلة", + "GameListContextMenuToggleFavoriteToolTip": "تبديل الحالة المفضلة للعبة", + "SettingsTabGeneralTheme": "السمة:", + "SettingsTabGeneralThemeDark": "داكن", + "SettingsTabGeneralThemeLight": "فاتح", + "ControllerSettingsConfigureGeneral": "ضبط", + "ControllerSettingsRumble": "الاهتزاز", + "ControllerSettingsRumbleStrongMultiplier": "مضاعف اهتزاز قوي", + "ControllerSettingsRumbleWeakMultiplier": "مضاعف اهتزاز ضعيف", + "DialogMessageSaveNotAvailableMessage": "لا يوجد حفظ لـ {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "هل ترغب في إنشاء حفظ لهذه اللعبة؟", + "DialogConfirmationTitle": "Ryujinx - تأكيد", + "DialogUpdaterTitle": "Ryujinx - تحديث", + "DialogErrorTitle": "Ryujinx - خطأ", + "DialogWarningTitle": "Ryujinx - تحذير", + "DialogExitTitle": "Ryujinx - الخروج", + "DialogErrorMessage": "واجه Ryujinx خطأ", + "DialogExitMessage": "هل أنت متأكد من أنك تريد إغلاق Ryujinx؟", + "DialogExitSubMessage": "سيتم فقدان كافة البيانات غير المحفوظة!", + "DialogMessageCreateSaveErrorMessage": "حدث خطأ أثناء إنشاء المحفوظة المحددة: {0}", + "DialogMessageFindSaveErrorMessage": "حدث خطأ أثناء البحث عن البيانات المحفوظة المحددة: {0}", + "FolderDialogExtractTitle": "اختر المجلد الذي تريد الاستخراج إليه", + "DialogNcaExtractionMessage": "استخراج قسم {0} من {1}...", + "DialogNcaExtractionTitle": "Ryujinx - مستخرج قسم NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "فشل الاستخراج. لم يكن NCA الرئيسي موجودًا في الملف المحدد.", + "DialogNcaExtractionCheckLogErrorMessage": "فشل الاستخراج. اقرأ ملف السجل لمزيد من المعلومات.", + "DialogNcaExtractionSuccessMessage": "تم الاستخراج بنجاح.", + "DialogUpdaterConvertFailedMessage": "فشل تحويل إصدار Ryujinx الحالي.", + "DialogUpdaterCancelUpdateMessage": "إلغاء التحديث!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "أنت تستخدم بالفعل أحدث إصدار من Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "حدث خطأ أثناء محاولة الحصول على معلومات الإصدار من إصدار GitHub. يمكن أن يحدث هذا إذا تم تجميع إصدار جديد بواسطة GitHub Actions. جرب مجددا بعد دقائق.", + "DialogUpdaterConvertFailedGithubMessage": "فشل تحويل إصدار Ryujinx المستلم من إصدار Github.", + "DialogUpdaterDownloadingMessage": "تحميل التحديث...", + "DialogUpdaterExtractionMessage": "استخراج التحديث...", + "DialogUpdaterRenamingMessage": "إعادة تسمية التحديث...", + "DialogUpdaterAddingFilesMessage": "إضافة تحديث جديد...", + "DialogUpdaterCompleteMessage": "اكتمل التحديث", + "DialogUpdaterRestartMessage": "هل تريد إعادة تشغيل Ryujinx الآن؟", + "DialogUpdaterNoInternetMessage": "أنت غير متصل بالإنترنت.", + "DialogUpdaterNoInternetSubMessage": "يرجى التحقق من أن لديك اتصال إنترنت فعال!", + "DialogUpdaterDirtyBuildMessage": "لا يمكنك تحديث البناء القذر من Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "الرجاء تحميل Ryujinx على https://ryujinx.org/ إذا كنت تبحث عن إصدار مدعوم.", + "DialogRestartRequiredMessage": "يتطلب إعادة التشغيل", + "DialogThemeRestartMessage": "تم حفظ السمة. إعادة التشغيل مطلوبة لتطبيق السمة.", + "DialogThemeRestartSubMessage": "هل تريد إعادة التشغيل", + "DialogFirmwareInstallEmbeddedMessage": "هل ترغب في تثبيت البرنامج الثابت المدمج في هذه اللعبة؟ (البرنامج الثابت {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "لم يتم العثور على أي برنامج ثابت مثبت ولكن Ryujinx كان قادراً على تثبيت البرنامج الثابت {0} من اللعبة المقدمة.\\nسيبدأ المحاكي الآن.", + "DialogFirmwareNoFirmwareInstalledMessage": "لا يوجد برنامج ثابت مثبت", + "DialogFirmwareInstalledMessage": "تم تثبيت البرنامج الثابت {0}", + "DialogInstallFileTypesSuccessMessage": "تم تثبيت أنواع الملفات بنجاح!", + "DialogInstallFileTypesErrorMessage": "فشل تثبيت أنواع الملفات.", + "DialogUninstallFileTypesSuccessMessage": "تم إلغاء تثبيت أنواع الملفات بنجاح!", + "DialogUninstallFileTypesErrorMessage": "فشل إلغاء تثبيت أنواع الملفات.", + "DialogOpenSettingsWindowLabel": "فتح نافذة الإعدادات", + "DialogControllerAppletTitle": "برنامج التحكم", + "DialogMessageDialogErrorExceptionMessage": "خطأ في عرض مربع حوار الرسالة: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "خطأ في عرض لوحة مفاتيح البرامج: {0}", + "DialogErrorAppletErrorExceptionMessage": "Error displaying ErrorApplet Dialog: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "لمزيد من المعلومات حول كيفية إصلاح هذا الخطأ، اتبع دليل الإعداد الخاص بنا.", + "DialogUserErrorDialogTitle": "Ryujinx Error ({0})", + "DialogAmiiboApiTitle": "أميبو API", + "DialogAmiiboApiFailFetchMessage": "حدث خطأ أثناء جلب المعلومات من واجهة برمجة التطبيقات.", + "DialogAmiiboApiConnectErrorMessage": "غير قادر على الاتصال بخادم API Amiibo. قد تكون الخدمة متوقفة أو قد تحتاج إلى التحقق من اتصال الإنترنت الخاص بك على الإنترنت.", + "DialogProfileInvalidProfileErrorMessage": "الملف الشخصي {0} غير متوافق مع نظام تكوين الإدخال الحالي.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "لا يمكن الكتابة فوق الملف الشخصي الافتراضي", + "DialogProfileDeleteProfileTitle": "حذف ملف التعريف", + "DialogProfileDeleteProfileMessage": "هذا الإجراء لا رجعة فيه، هل أنت متأكد من أنك تريد المتابعة؟", + "DialogWarning": "تحذير", + "DialogPPTCDeletionMessage": "أنت على وشك الإنتظار لإعادة بناء PTC على التمهيد التالي :\n\n{0}\n\nهل أنت متأكد من أنك تريد المتابعة؟", + "DialogPPTCDeletionErrorMessage": "خطأ في إزالة ذاكرة التخزين المؤقت PPTC في {0}: {1}", + "DialogShaderDeletionMessage": "أنت على وشك حذف ذاكرة التخزين المؤقت لـ Shader من أجل:\n\n{0}\n\nهل انت متأكد انك تريد المتابعة؟", + "DialogShaderDeletionErrorMessage": "Error purging Shader cache at {0}: {1}", + "DialogRyujinxErrorMessage": "واجه Ryujinx خطأ", + "DialogInvalidTitleIdErrorMessage": "خطأ في واجهة المستخدم: اللعبة المحددة لم يكن لديها معرف عنوان صالح", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "لم يتم العثور على فريموير للنظام صالح في {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "تثبيت البرنامج الثابت {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "سيتم تثبيت إصدار النظام {0}.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nهذا سيحل محل إصدار النظام الحالي {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\nهل تريد المتابعة؟", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "تثبيت البرنامج الثابت...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "تم تثبيت إصدار النظام {0} بنجاح.", + "DialogUserProfileDeletionWarningMessage": "لن تكون هناك ملفات تعريف أخرى لفتحها إذا تم حذف الملف الشخصي المحدد", + "DialogUserProfileDeletionConfirmMessage": "هل تريد حذف الملف الشخصي المحدد", + "DialogUserProfileUnsavedChangesTitle": "تحذير - التغييرات غير المحفوظة", + "DialogUserProfileUnsavedChangesMessage": "لقد قمت بإجراء تغييرات على ملف تعريف المستخدم هذا ولم يتم حفظها.", + "DialogUserProfileUnsavedChangesSubMessage": "هل تريد تجاهل التغييرات؟", + "DialogControllerSettingsModifiedConfirmMessage": "تم تحديث إعدادات وحدة التحكم الحالية.", + "DialogControllerSettingsModifiedConfirmSubMessage": "هل تريد الحفظ ؟", + "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", + "DialogModAlreadyExistsMessage": "التعديل موجود بالفعل", + "DialogModInvalidMessage": "المجلد المحدد لا يحتوي على تعديل!", + "DialogModDeleteNoParentMessage": "فشل الحذف: لم يمكن العثور على المجلد الرئيسي للتعديل\"{0}\"!", + "DialogDlcNoDlcErrorMessage": "الملف المحدد لا يحتوي على DLC للعنوان المحدد!", + "DialogPerformanceCheckLoggingEnabledMessage": "لقد تم تمكين تسجيل التتبع، والذي تم تصميمه ليتم استخدامه من قبل المطورين فقط.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "للحصول على الأداء الأمثل، يوصى بتعطيل تسجيل التتبع. هل ترغب في تعطيل تسجيل التتبع الآن؟", + "DialogPerformanceCheckShaderDumpEnabledMessage": "لقد قمت بتمكين تفريغ التظليل، والذي تم تصميمه ليستخدمه المطورون فقط.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "For optimal performance, it's recommended to disable shader dumping. Would you like to disable shader dumping now?", + "DialogLoadAppGameAlreadyLoadedMessage": "تم تحميل لعبة بالفعل", + "DialogLoadAppGameAlreadyLoadedSubMessage": "الرجاء إيقاف المحاكاة أو إغلاق المحاكي قبل بدء لعبة أخرى.", + "DialogUpdateAddUpdateErrorMessage": "الملف المحدد لا يحتوي على تحديث للملف المحدد!", + "DialogSettingsBackendThreadingWarningTitle": "Warning - Backend Threading", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx must be restarted after changing this option for it to apply fully. Depending on your platform, you may need to manually disable your driver's own multithreading when using Ryujinx's.", + "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", + "SettingsTabGraphicsFeaturesOptions": "المميزات", + "SettingsTabGraphicsBackendMultithreading": "Graphics Backend Multithreading:", + "CommonAuto": "تلقائي", + "CommonOff": "إيقاف", + "CommonOn": "تشغيل", + "InputDialogYes": "نعم", + "InputDialogNo": "لا", + "DialogProfileInvalidProfileNameErrorMessage": "يحتوي اسم الملف على أحرف غير صالحة. يرجى المحاولة مرة أخرى.", + "MenuBarOptionsPauseEmulation": "إيقاف مؤقت", + "MenuBarOptionsResumeEmulation": "استئناف", + "AboutUrlTooltipMessage": "انقر لفتح موقع Ryujinx في متصفحك الافتراضي.", + "AboutDisclaimerMessage": "Ryujinx is not affiliated with Nintendo™,\nor any of its partners, in any way.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) is used\nin our Amiibo emulation.", + "AboutPatreonUrlTooltipMessage": "Click to open the Ryujinx Patreon page in your default browser.", + "AboutGithubUrlTooltipMessage": "Click to open the Ryujinx GitHub page in your default browser.", + "AboutDiscordUrlTooltipMessage": "Click to open an invite to the Ryujinx Discord server in your default browser.", + "AboutTwitterUrlTooltipMessage": "Click to open the Ryujinx Twitter page in your default browser.", + "AboutRyujinxAboutTitle": "حول:", + "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nPlease support us on Patreon.\nGet all the latest news on our Twitter or Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", + "AboutRyujinxMaintainersTitle": "تم إصلاحها بواسطة:", + "AboutRyujinxMaintainersContentTooltipMessage": "انقر لفتح صفحة المساهمين في متصفحك الافتراضي.", + "AboutRyujinxSupprtersTitle": "مدعوم على باتريون بواسطة:", + "AmiiboSeriesLabel": "مجموعة أميبو", + "AmiiboCharacterLabel": "Character", + "AmiiboScanButtonLabel": "فحصه", + "AmiiboOptionsShowAllLabel": "إظهار كل أميبو", + "AmiiboOptionsUsRandomTagLabel": "Hack: Use Random tag Uuid", + "DlcManagerTableHeadingEnabledLabel": "مفعل", + "DlcManagerTableHeadingTitleIdLabel": "معرف العنوان", + "DlcManagerTableHeadingContainerPathLabel": "مسار الحاوية", + "DlcManagerTableHeadingFullPathLabel": "المسار كاملاً", + "DlcManagerRemoveAllButton": "حذف الكل", + "DlcManagerEnableAllButton": "تشغيل الكل", + "DlcManagerDisableAllButton": "تعطيل الكل", + "ModManagerDeleteAllButton": "حذف الكل", + "MenuBarOptionsChangeLanguage": "تغيير اللغة", + "MenuBarShowFileTypes": "إظهار أنواع الملفات", + "CommonSort": "فرز", + "CommonShowNames": "عرض الأسماء", + "CommonFavorite": "المفضلة", + "OrderAscending": "ترتيب تصاعدي", + "OrderDescending": "ترتيب تنازلي", + "SettingsTabGraphicsFeatures": "الميزات والتحسينات", + "ErrorWindowTitle": "نافذة الخطأ", + "ToggleDiscordTooltip": "Choose whether or not to display Ryujinx on your \"currently playing\" Discord activity", + "AddGameDirBoxTooltip": "أدخل دليل اللعبة لإضافته إلى القائمة", + "AddGameDirTooltip": "إضافة دليل اللعبة إلى القائمة", + "RemoveGameDirTooltip": "إزالة دليل اللعبة المحدد", + "CustomThemeCheckTooltip": "Use a custom Avalonia theme for the GUI to change the appearance of the emulator menus", + "CustomThemePathTooltip": "مسار سمة واجهة المستخدم المخصصة", + "CustomThemeBrowseTooltip": "تصفح للحصول على سمة واجهة المستخدم المخصصة", + "DockModeToggleTooltip": "Docked mode makes the emulated system behave as a docked Nintendo Switch. This improves graphical fidelity in most games. Conversely, disabling this will make the emulated system behave as a handheld Nintendo Switch, reducing graphics quality.\n\nConfigure player 1 controls if planning to use docked mode; configure handheld controls if planning to use handheld mode.\n\nLeave ON if unsure.", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", + "RegionTooltip": "تغيير منطقة النظام", + "LanguageTooltip": "تغيير لغة النظام", + "TimezoneTooltip": "تغيير المنطقة الزمنية للنظام", + "TimeTooltip": "تغيير وقت النظام", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", + "PptcToggleTooltip": "Saves translated JIT functions so that they do not need to be translated every time the game loads.\n\nReduces stuttering and significantly speeds up boot times after the first boot of a game.\n\nLeave ON if unsure.", + "FsIntegrityToggleTooltip": "Checks for corrupt files when booting a game, and if corrupt files are detected, displays a hash error in the log.\n\nHas no impact on performance and is meant to help troubleshooting.\n\nLeave ON if unsure.", + "AudioBackendTooltip": "Changes the backend used to render audio.\n\nSDL2 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound.\n\nSet to SDL2 if unsure.", + "MemoryManagerTooltip": "Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance.\n\nSet to HOST UNCHECKED if unsure.", + "MemoryManagerSoftwareTooltip": "Use a software page table for address translation. Highest accuracy but slowest performance.", + "MemoryManagerHostTooltip": "Directly map memory in the host address space. Much faster JIT compilation and execution.", + "MemoryManagerUnsafeTooltip": "Directly map memory, but do not mask the address within the guest address space before access. Faster, but at the cost of safety. The guest application can access memory from anywhere in Ryujinx, so only run programs you trust with this mode.", + "UseHypervisorTooltip": "Use Hypervisor instead of JIT. Greatly improves performance when available, but can be unstable in its current state.", + "DRamTooltip": "Utilizes an alternative MemoryMode layout to mimic a Switch development model.\n\nThis is only useful for higher-resolution texture packs or 4k resolution mods. Does NOT improve performance.\n\nLeave OFF if unsure.", + "IgnoreMissingServicesTooltip": "Ignores unimplemented Horizon OS services. This may help in bypassing crashes when booting certain games.\n\nLeave OFF if unsure.", + "GraphicsBackendThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", + "GalThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", + "ShaderCacheToggleTooltip": "Saves a disk shader cache which reduces stuttering in subsequent runs.\n\nLeave ON if unsure.", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", + "ResolutionScaleEntryTooltip": "Floating point resolution scale, such as 1.5. Non-integral scales are more likely to cause issues or crash.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", + "ShaderDumpPathTooltip": "Graphics Shaders Dump Path", + "FileLogTooltip": "حفظ تسجيل وحدة التحكم إلى ملف سجل على القرص. لا يؤثر على الأداء.", + "StubLogTooltip": "Prints stub log messages in the console. Does not affect performance.", + "InfoLogTooltip": "Prints info log messages in the console. Does not affect performance.", + "WarnLogTooltip": "طباعة رسائل سجل التحذير في وحدة التحكم. لا يؤثر على الأداء.", + "ErrorLogTooltip": "طباعة رسائل سجل الأخطاء في وحدة التحكم. لا يؤثر على الأداء.", + "TraceLogTooltip": "طباعة رسائل سجل التتبع في وحدة التحكم. لا يؤثر على الأداء.", + "GuestLogTooltip": "طباعة رسائل سجل الضيف في وحدة التحكم. لا يؤثر على الأداء.", + "FileAccessLogTooltip": "طباعة رسائل سجل الوصول إلى الملفات في وحدة التحكم.", + "FSAccessLogModeTooltip": "Enables FS access log output to the console. Possible modes are 0-3", + "DeveloperOptionTooltip": "استخدمه بعناية", + "OpenGlLogLevel": "يتطلب تمكين مستويات السجل المناسبة", + "DebugLogTooltip": "Prints debug log messages in the console.\n\nOnly use this if specifically instructed by a staff member, as it will make logs difficult to read and worsen emulator performance.", + "LoadApplicationFileTooltip": "افتح مستكشف الملفات لاختيار ملف متوافق مع Switch لتحميله", + "LoadApplicationFolderTooltip": "افتح مستكشف الملفات لاختيار تطبيق متوافق مع Switch للتحميل", + "OpenRyujinxFolderTooltip": "فتح مجلد نظام ملفات Ryujinx", + "OpenRyujinxLogsTooltip": "Opens the folder where logs are written to", + "ExitTooltip": "الخروج من Ryujinx", + "OpenSettingsTooltip": "فتح نافذة الإعدادات", + "OpenProfileManagerTooltip": "افتح نافذة إدارة ملفات تعريف المستخدمين", + "StopEmulationTooltip": "إيقاف محاكاة اللعبة الحالية والعودة إلى اختيار اللعبة", + "CheckUpdatesTooltip": "التحقق من وجود تحديثات لـ Ryujinx", + "OpenAboutTooltip": "فتح حول النافذة", + "GridSize": "حجم الشبكة", + "GridSizeTooltip": "تغيير حجم عناصر الشبكة", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "البرتغالية البرازيلية", + "AboutRyujinxContributorsButtonHeader": "رؤية جميع المساهمين", + "SettingsTabSystemAudioVolume": "الحجم:", + "AudioVolumeTooltip": "تغيير مستوى الصوت", + "SettingsTabSystemEnableInternetAccess": "Guest Internet Access/LAN Mode", + "EnableInternetAccessTooltip": "للسماح للتطبيق المحاكي بالاتصال بالإنترنت.\n\nالألعاب ذات وضع الشبكة المحلية يمكن الاتصال ببعضها البعض عندما يتم تمكين هذا النظام وتكون الأنظمة متصلة بنفس نقطة الوصول. هذا يشمل وحدات التحكم الحقيقية أيضًا.\n\nلا يسمح بالاتصال بخوادم Nintendo. قد يسبب الانهيار في بعض الألعاب التي تحاول الاتصال بالإنترنت.\n\nاتركه متوقفا إن لم يكن مؤكدا.", + "GameListContextMenuManageCheatToolTip": "إدارة الغش", + "GameListContextMenuManageCheat": "إدارة الغش", + "GameListContextMenuManageModToolTip": "إدارة التعديلات", + "GameListContextMenuManageMod": "إدارة التعديلات", + "ControllerSettingsStickRange": "نطاق:", + "DialogStopEmulationTitle": "Ryujinx - إيقاف المحاكاة", + "DialogStopEmulationMessage": "هل أنت متأكد أنك تريد إيقاف المحاكاة؟", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "الصوت", + "SettingsTabNetwork": "الشبكة", + "SettingsTabNetworkConnection": "اتصال الشبكة", + "SettingsTabCpuCache": "ذاكرة المعالج المؤقت", + "SettingsTabCpuMemory": "وضع المعالج", + "DialogUpdaterFlatpakNotSupportedMessage": "الرجاء تحديث Ryujinx عبر FlatHub.", + "UpdaterDisabledWarningTitle": "التحديث معطل!", + "ControllerSettingsRotate90": "تدوير 90 درجة في اتجاه عقارب الساعة", + "IconSize": "حجم الأيقونة", + "IconSizeTooltip": "تغيير حجم أيقونات اللعبة", + "MenuBarOptionsShowConsole": "عرض وحدة التحكم", + "ShaderCachePurgeError": "Error purging shader cache at {0}: {1}", + "UserErrorNoKeys": "المفاتيح غير موجودة", + "UserErrorNoFirmware": "لم يتم العثور على البرنامج الثابت", + "UserErrorFirmwareParsingFailed": "خطأ في تحليل البرنامج الثابت", + "UserErrorApplicationNotFound": "التطبيق غير موجود", + "UserErrorUnknown": "خطأ غير معروف", + "UserErrorUndefined": "خطأ غير محدد", + "UserErrorNoKeysDescription": "Ryujinx was unable to find your 'prod.keys' file", + "UserErrorNoFirmwareDescription": "Ryujinx was unable to find any firmwares installed", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx was unable to parse the provided firmware. This is usually caused by outdated keys.", + "UserErrorApplicationNotFoundDescription": "Ryujinx couldn't find a valid application at the given path.", + "UserErrorUnknownDescription": "حدث خطأ غير معروف!", + "UserErrorUndefinedDescription": "حدث خطأ غير محدد! لا ينبغي أن يحدث هذا، يرجى الاتصال بمطور!", + "OpenSetupGuideMessage": "فتح دليل الإعداد", + "NoUpdate": "لا يوجد تحديث", + "TitleUpdateVersionLabel": "الإصدار: {0}", + "RyujinxInfo": "Ryujinx - معلومات", + "RyujinxConfirm": "Ryujinx - تأكيد", + "FileDialogAllTypes": "كل الأنواع", + "Never": "مطلقاً", + "SwkbdMinCharacters": "Must be at least {0} characters long", + "SwkbdMinRangeCharacters": "Must be {0}-{1} characters long", + "SoftwareKeyboard": "لوحة المفاتيح البرمجية", + "SoftwareKeyboardModeNumeric": "يجب أن يكون 0-9 أو '.' فقط", + "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", + "SoftwareKeyboardModeASCII": "Must be ASCII text only", + "ControllerAppletControllers": "ذراع التحكم المدعومة:", + "ControllerAppletPlayers": "اللاعبين:", + "ControllerAppletDescription": "الإعدادات الحالية غير صالحة. افتح الإعدادات وأعد تكوين المدخلات الخاصة بك.", + "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", + "UpdaterRenaming": "إعادة تسمية الملفات القديمة...", + "UpdaterRenameFailed": "التحديث غير قادر على إعادة تسمية الملف: {0}", + "UpdaterAddingFiles": "إضافة ملفات جديدة...", + "UpdaterExtracting": "استخراج التحديث...", + "UpdaterDownloading": "تحميل التحديث...", + "Game": "لعبة", + "Docked": "مركب بالمنصة", + "Handheld": "محمول", + "ConnectionError": "خطأ في الاتصال", + "AboutPageDeveloperListMore": "{0} والمزيد...", + "ApiError": "خطأ في API.", + "LoadingHeading": "جارٍ تحميل {0}", + "CompilingPPTC": "تجميع الـ PTC", + "CompilingShaders": "تجميع الظلال", + "AllKeyboards": "كل لوحات المفاتيح", + "OpenFileDialogTitle": "حدد ملف مدعوم لفتحه", + "OpenFolderDialogTitle": "حدد مجلدًا يحتوي على لعبة غير مضغوطة", + "AllSupportedFormats": "كل التنسيقات المدعومة", + "RyujinxUpdater": "تحديث Ryujinx", + "SettingsTabHotkeys": "مفاتيح الاختصار في لوحة المفاتيح", + "SettingsTabHotkeysHotkeys": "مفاتيح الاختصار في لوحة المفاتيح", + "SettingsTabHotkeysToggleVsyncHotkey": "تبديل VSync:", + "SettingsTabHotkeysScreenshotHotkey": "لقطة الشاشة:", + "SettingsTabHotkeysShowUiHotkey": "عرض واجهة المستخدم:", + "SettingsTabHotkeysPauseHotkey": "إيقاف مؤقت:", + "SettingsTabHotkeysToggleMuteHotkey": "كتم الصوت:", + "ControllerMotionTitle": "إعدادات التحكم بالحركة", + "ControllerRumbleTitle": "إعدادات الهزاز", + "SettingsSelectThemeFileDialogTitle": "حدد ملف السمة", + "SettingsXamlThemeFile": "Xaml Theme File", + "AvatarWindowTitle": "إدارة الحسابات - الصورة الرمزية", + "Amiibo": "أميبو", + "Unknown": "غير معروف", + "Usage": "الاستخدام", + "Writable": "قابل للكتابة", + "SelectDlcDialogTitle": "حدد ملفات DLC", + "SelectUpdateDialogTitle": "حدد ملفات التحديث", + "SelectModDialogTitle": "Select mod directory", + "UserProfileWindowTitle": "مدير ملفات تعريف المستخدمين", + "CheatWindowTitle": "مدير الغش", + "DlcWindowTitle": "إدارة المحتوى القابل للتنزيل لـ {0} ({1})", + "UpdateWindowTitle": "مدير تحديث العنوان", + "CheatWindowHeading": "الغش متوفر لـ {0} [{1}]", + "BuildId": "معرف البناء:", + "DlcWindowHeading": "المحتويات القابلة للتنزيل {0}", + "ModWindowHeading": "{0} تعديل", + "UserProfilesEditProfile": "تعديل المحددة", + "Cancel": "إلغاء", + "Save": "حفظ", + "Discard": "تجاهل", + "Paused": "متوقف مؤقتا", + "UserProfilesSetProfileImage": "تعيين صورة ملف التعريف", + "UserProfileEmptyNameError": "الاسم مطلوب", + "UserProfileNoImageError": "يجب تعيين صورة الملف الشخصي", + "GameUpdateWindowHeading": "إدارة التحديثات لـ {0} ({1})", + "SettingsTabHotkeysResScaleUpHotkey": "زيادة الدقة:", + "SettingsTabHotkeysResScaleDownHotkey": "تقليل الدقة:", + "UserProfilesName": "الاسم:", + "UserProfilesUserId": "معرف المستخدم:", + "SettingsTabGraphicsBackend": "خلفية الرسومات", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", + "SettingsEnableTextureRecompression": "تمكين إعادة ضغط التكستر", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", + "SettingsTabGraphicsPreferredGpu": "GPU المفضل", + "SettingsTabGraphicsPreferredGpuTooltip": "Select the graphics card that will be used with the Vulkan graphics backend.\n\nDoes not affect the GPU that OpenGL will use.\n\nSet to the GPU flagged as \"dGPU\" if unsure. If there isn't one, leave untouched.", + "SettingsAppRequiredRestartMessage": "مطلوب إعادة تشغيل Ryujinx", + "SettingsGpuBackendRestartMessage": "Graphics Backend or GPU settings have been modified. This will require a restart to be applied", + "SettingsGpuBackendRestartSubMessage": "\n\nهل تريد إعادة التشغيل الآن؟", + "RyujinxUpdaterMessage": "هل تريد تحديث Ryujinx إلى أحدث إصدار؟", + "SettingsTabHotkeysVolumeUpHotkey": "زيادة مستوى الصوت:", + "SettingsTabHotkeysVolumeDownHotkey": "تقليل الحجم:", + "SettingsEnableMacroHLE": "Enable Macro HLE", + "SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure.", + "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", + "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", + "VolumeShort": "الحجم", + "UserProfilesManageSaves": "إدارة الحفظ", + "DeleteUserSave": "هل تريد حذف حفظ المستخدم لهذه اللعبة؟", + "IrreversibleActionNote": "هذا الإجراء لا يمكن التراجع عنه.", + "SaveManagerHeading": "إدارة الحفظ لـ {0} ({1})", + "SaveManagerTitle": "مدير الحفظ", + "Name": "الاسم", + "Size": "الحجم", + "Search": "البحث", + "UserProfilesRecoverLostAccounts": "استعادة الحسابات المفقودة", + "Recover": "استعادة", + "UserProfilesRecoverHeading": "تم العثور على الحفظ للحسابات التالية", + "UserProfilesRecoverEmptyList": "لا توجد ملفات تعريف لاستردادها", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", + "GraphicsAALabel": "تنعيم الحواف:", + "GraphicsScalingFilterLabel": "فلتر التكبير:", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterLevelLabel": "المستوى", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", + "SmaaLow": "SMAA منخفض", + "SmaaMedium": "SMAA متوسط", + "SmaaHigh": "SMAA عالي", + "SmaaUltra": "SMAA فائق", + "UserEditorTitle": "تعديل المستخدم", + "UserEditorTitleCreate": "إنشاء مستخدم", + "SettingsTabNetworkInterface": "واجهة الشبكة:", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", + "NetworkInterfaceDefault": "افتراضي", + "PackagingShaders": "Packaging Shaders", + "AboutChangelogButton": "عرض سجل التغييرات على GitHub", + "AboutChangelogButtonTooltipMessage": "انقر لفتح سجل التغيير لهذا الإصدار في متصفحك الافتراضي.", + "SettingsTabNetworkMultiplayer": "لعب جماعي", + "MultiplayerMode": "النمط:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/de_DE.json b/src/Ryujinx/Assets/Locales/de_DE.json index 7cdcdf5a2..c70660532 100644 --- a/src/Ryujinx/Assets/Locales/de_DE.json +++ b/src/Ryujinx/Assets/Locales/de_DE.json @@ -9,12 +9,12 @@ "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host ungeprüft (am schnellsten, unsicher)", "SettingsTabSystemUseHypervisor": "Hypervisor verwenden", "MenuBarFile": "_Datei", - "MenuBarFileOpenFromFile": "_Datei öffnen", + "MenuBarFileOpenFromFile": "Datei _öffnen", "MenuBarFileOpenUnpacked": "_Entpacktes Spiel öffnen", "MenuBarFileOpenEmuFolder": "Ryujinx-Ordner öffnen", "MenuBarFileOpenLogsFolder": "Logs-Ordner öffnen", "MenuBarFileExit": "_Beenden", - "MenuBarOptions": "Optionen", + "MenuBarOptions": "_Optionen", "MenuBarOptionsToggleFullscreen": "Vollbild", "MenuBarOptionsStartGamesInFullscreen": "Spiele im Vollbildmodus starten", "MenuBarOptionsStopEmulation": "Emulation beenden", @@ -23,14 +23,14 @@ "MenuBarActions": "_Aktionen", "MenuBarOptionsSimulateWakeUpMessage": "Aufwachnachricht simulieren", "MenuBarActionsScanAmiibo": "Amiibo scannen", - "MenuBarTools": "_Werkzeuge", + "MenuBarTools": "_Tools", "MenuBarToolsInstallFirmware": "Firmware installieren", "MenuBarFileToolsInstallFirmwareFromFile": "Firmware von einer XCI- oder einer ZIP-Datei installieren", "MenuBarFileToolsInstallFirmwareFromDirectory": "Firmware aus einem Verzeichnis installieren", "MenuBarToolsManageFileTypes": "Dateitypen verwalten", "MenuBarToolsInstallFileTypes": "Dateitypen installieren", "MenuBarToolsUninstallFileTypes": "Dateitypen deinstallieren", - "MenuBarHelp": "Hilfe", + "MenuBarHelp": "_Hilfe", "MenuBarHelpCheckForUpdates": "Nach Updates suchen", "MenuBarHelpAbout": "Über Ryujinx", "MenuSearch": "Suchen...", @@ -54,9 +54,7 @@ "GameListContextMenuManageTitleUpdatesToolTip": "Öffnet den Spiel-Update-Manager", "GameListContextMenuManageDlc": "Verwalten von DLC", "GameListContextMenuManageDlcToolTip": "Öffnet den DLC-Manager", - "GameListContextMenuOpenModsDirectory": "Mod-Verzeichnis öffnen", - "GameListContextMenuOpenModsDirectoryToolTip": "Öffnet das Verzeichnis, welches Mods für die Spiele beinhaltet", - "GameListContextMenuCacheManagement": "Cache Verwaltung", + "GameListContextMenuCacheManagement": "Cache-Verwaltung", "GameListContextMenuCacheManagementPurgePptc": "PPTC als ungültig markieren", "GameListContextMenuCacheManagementPurgePptcToolTip": "Markiert den PPTC als ungültig, sodass dieser beim nächsten Spielstart neu erstellt wird", "GameListContextMenuCacheManagementPurgeShaderCache": "Shader Cache löschen", @@ -72,6 +70,13 @@ "GameListContextMenuExtractDataRomFSToolTip": "Extrahiert das RomFS aus der aktuellen Anwendungskonfiguration (einschließlich Updates)", "GameListContextMenuExtractDataLogo": "Logo", "GameListContextMenuExtractDataLogoToolTip": "Extrahiert das Logo aus der aktuellen Anwendungskonfiguration (einschließlich Updates)", + "GameListContextMenuCreateShortcut": "Erstelle Anwendungsverknüpfung", + "GameListContextMenuCreateShortcutToolTip": "Erstelle eine Desktop-Verknüpfung die die gewählte Anwendung startet", + "GameListContextMenuCreateShortcutToolTipMacOS": "Erstellen Sie eine Verknüpfung im MacOS-Programme-Ordner, die die ausgewählte Anwendung startet", + "GameListContextMenuOpenModsDirectory": "Open Mods Directory", + "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", + "GameListContextMenuOpenSdModsDirectory": "Atmosphere-Mod-Verzeichnis öffnen", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", "StatusBarGamesLoaded": "{0}/{1} Spiele geladen", "StatusBarSystemVersion": "Systemversion: {0}", "LinuxVmMaxMapCountDialogTitle": "Niedriges Limit für Speicherzuordnungen erkannt", @@ -150,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "Nativ (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Nicht empfohlen)", "SettingsTabGraphicsAspectRatio": "Bildseitenverhältnis:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -292,13 +297,9 @@ "GameListContextMenuRunApplication": "Anwendung ausführen", "GameListContextMenuToggleFavorite": "Als Favoriten hinzufügen/entfernen", "GameListContextMenuToggleFavoriteToolTip": "Aktiviert den Favoriten-Status des Spiels", - "SettingsTabGeneralTheme": "Design", - "SettingsTabGeneralThemeCustomTheme": "Pfad für das benutzerdefinierte Design", - "SettingsTabGeneralThemeBaseStyle": "Farbschema", - "SettingsTabGeneralThemeBaseStyleDark": "Dunkel", - "SettingsTabGeneralThemeBaseStyleLight": "Hell", - "SettingsTabGeneralThemeEnableCustomTheme": "Design für die Emulator-Benutzeroberfläche", - "ButtonBrowse": "Durchsuchen", + "SettingsTabGeneralTheme": "Design:", + "SettingsTabGeneralThemeDark": "Dunkel", + "SettingsTabGeneralThemeLight": "Hell", "ControllerSettingsConfigureGeneral": "Konfigurieren", "ControllerSettingsRumble": "Vibration", "ControllerSettingsRumbleStrongMultiplier": "Starker Vibrations-Multiplikator", @@ -322,7 +323,7 @@ "DialogNcaExtractionCheckLogErrorMessage": "Extraktion fehlgeschlagen. Überprüfe die Logs für weitere Informationen.", "DialogNcaExtractionSuccessMessage": "Extraktion erfolgreich abgeschlossen.", "DialogUpdaterConvertFailedMessage": "Die Konvertierung der aktuellen Ryujinx-Version ist fehlgeschlagen.", - "DialogUpdaterCancelUpdateMessage": "Download wird abgebrochen!", + "DialogUpdaterCancelUpdateMessage": "Update wird abgebrochen!", "DialogUpdaterAlreadyOnLatestVersionMessage": "Es wird bereits die aktuellste Version von Ryujinx benutzt", "DialogUpdaterFailedToGetVersionMessage": "Beim Versuch, Veröffentlichungs-Info von GitHub Release zu erhalten, ist ein Fehler aufgetreten. Dies kann aufgrund einer neuen Veröffentlichung, die gerade von GitHub Actions kompiliert wird, verursacht werden.", "DialogUpdaterConvertFailedGithubMessage": "Fehler beim Konvertieren der erhaltenen Ryujinx-Version von GitHub Release.", @@ -332,8 +333,6 @@ "DialogUpdaterAddingFilesMessage": "Update wird hinzugefügt...", "DialogUpdaterCompleteMessage": "Update abgeschlossen!", "DialogUpdaterRestartMessage": "Ryujinx jetzt neu starten?", - "DialogUpdaterArchNotSupportedMessage": "Eine nicht unterstützte Systemarchitektur wird benutzt!", - "DialogUpdaterArchNotSupportedSubMessage": "Nur 64-Bit-Systeme werden unterstützt!", "DialogUpdaterNoInternetMessage": "Es besteht keine Verbindung mit dem Internet!", "DialogUpdaterNoInternetSubMessage": "Bitte vergewissern, dass eine funktionierende Internetverbindung existiert!", "DialogUpdaterDirtyBuildMessage": "Inoffizielle Versionen von Ryujinx können nicht aktualisiert werden", @@ -385,7 +384,10 @@ "DialogUserProfileUnsavedChangesSubMessage": "Möchten Sie Ihre Änderungen wirklich verwerfen?", "DialogControllerSettingsModifiedConfirmMessage": "Die aktuellen Controller-Einstellungen wurden aktualisiert.", "DialogControllerSettingsModifiedConfirmSubMessage": "Controller-Einstellungen speichern?", - "DialogLoadNcaErrorMessage": "{0}. Fehlerhafte Datei: {1}", + "DialogLoadFileErrorMessage": "{0}. Fehlerhafte Datei: {1}", + "DialogModAlreadyExistsMessage": "Mod ist bereits vorhanden", + "DialogModInvalidMessage": "Das angegebene Verzeichnis enthält keine Mods!", + "DialogModDeleteNoParentMessage": "Löschen fehlgeschlagen: Das übergeordnete Verzeichnis für den Mod \"{0}\" konnte nicht gefunden werden!", "DialogDlcNoDlcErrorMessage": "Die angegebene Datei enthält keinen DLC für den ausgewählten Titel!", "DialogPerformanceCheckLoggingEnabledMessage": "Es wurde die Debug Protokollierung aktiviert", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Um eine optimale Leistung zu erzielen, wird empfohlen, die Debug Protokollierung zu deaktivieren. Debug Protokollierung jetzt deaktivieren?", @@ -396,6 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "Die angegebene Datei enthält keine Updates für den ausgewählten Titel!", "DialogSettingsBackendThreadingWarningTitle": "Warnung - Render Threading", "DialogSettingsBackendThreadingWarningMessage": "Ryujinx muss muss neu gestartet werden, damit die Änderungen wirksam werden. Abhängig von dem Betriebssystem muss möglicherweise das Multithreading des Treibers manuell deaktiviert werden, wenn Ryujinx verwendet wird.", + "DialogModManagerDeletionWarningMessage": "Du bist dabei, diesen Mod zu lösche. {0}\n\nMöchtest du wirklich fortfahren?", + "DialogModManagerDeletionAllWarningMessage": "Du bist dabei, alle Mods für diesen Titel zu löschen.\n\nMöchtest du wirklich fortfahren?", "SettingsTabGraphicsFeaturesOptions": "Erweiterungen", "SettingsTabGraphicsBackendMultithreading": "Grafik-Backend Multithreading:", "CommonAuto": "Auto", @@ -418,7 +422,7 @@ "AboutRyujinxMaintainersTitle": "Entwickelt von:", "AboutRyujinxMaintainersContentTooltipMessage": "Klicke hier, um die Liste der Mitwirkenden im Standardbrowser zu öffnen.", "AboutRyujinxSupprtersTitle": "Unterstützt auf Patreon von:", - "AmiiboSeriesLabel": "Amiibo Serie", + "AmiiboSeriesLabel": "Amiibo-Serie", "AmiiboCharacterLabel": "Charakter", "AmiiboScanButtonLabel": "Einscannen", "AmiiboOptionsShowAllLabel": "Zeige alle Amiibos", @@ -430,6 +434,7 @@ "DlcManagerRemoveAllButton": "Entferne alle", "DlcManagerEnableAllButton": "Alle aktivieren", "DlcManagerDisableAllButton": "Alle deaktivieren", + "ModManagerDeleteAllButton": "Alle löschen", "MenuBarOptionsChangeLanguage": "Sprache ändern", "MenuBarShowFileTypes": "Dateitypen anzeigen", "CommonSort": "Sortieren", @@ -447,13 +452,13 @@ "CustomThemePathTooltip": "Gibt den Pfad zum Design für die Emulator-Benutzeroberfläche an", "CustomThemeBrowseTooltip": "Ermöglicht die Suche nach einem benutzerdefinierten Design für die Emulator-Benutzeroberfläche", "DockModeToggleTooltip": "Im gedockten Modus verhält sich das emulierte System wie eine Nintendo Switch im TV Modus. Dies verbessert die grafische Qualität der meisten Spiele. Umgekehrt führt die Deaktivierung dazu, dass sich das emulierte System wie eine Nintendo Switch im Handheld Modus verhält, was die Grafikqualität beeinträchtigt.\n\nKonfiguriere das Eingabegerät für Spieler 1, um im Docked Modus zu spielen; konfiguriere das Controllerprofil via der Handheld Option, wenn geplant wird den Handheld Modus zu nutzen.\n\nIm Zweifelsfall AN lassen.", - "DirectKeyboardTooltip": "Aktiviert/Deaktiviert den \"Direkter Tastaturzugriff (HID) Unterstützung\" (Ermöglicht die Benutzung der Tastaur als Eingabegerät in Spielen)", - "DirectMouseTooltip": "Aktiviert/Deaktiviert den \"Direkten Mauszugriff (HID) Unterstützung\" (Ermöglicht die Benutzung der Maus als Eingabegerät in Spielen)", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", "RegionTooltip": "Ändert die Systemregion", "LanguageTooltip": "Ändert die Systemsprache", "TimezoneTooltip": "Ändert die Systemzeitzone", "TimeTooltip": "Ändert die Systemzeit", - "VSyncToggleTooltip": "Vertikale Synchronisierung der emulierten Konsole. Diese Option ist ein Frame-Limiter für die meisten Spiele; die Deaktivierung kann dazu führen, dass Spiele mit höherer Geschwindigkeit laufen, Ladebildschirme länger benötigen oder hängen bleiben.\n\nKann beim Spielen mit einem frei wählbaren Hotkey ein- und ausgeschaltet werden.\n\nIm Zweifelsfall AN lassen.", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", "PptcToggleTooltip": "Speichert übersetzte JIT-Funktionen, sodass jene nicht jedes Mal übersetzt werden müssen, wenn das Spiel geladen wird.\n\nVerringert Stottern und die Zeit beim zweiten und den darauffolgenden Startvorgängen eines Spiels erheblich.\n\nIm Zweifelsfall AN lassen.", "FsIntegrityToggleTooltip": "Prüft beim Startvorgang auf beschädigte Dateien und zeigt bei beschädigten Dateien einen Hash-Fehler (Hash Error) im Log an.\n\nDiese Einstellung hat keinen Einfluss auf die Leistung und hilft bei der Fehlersuche.\n\nIm Zweifelsfall AN lassen.", "AudioBackendTooltip": "Ändert das Backend, das zum Rendern von Audio verwendet wird.\n\nSDL2 ist das bevorzugte Audio-Backend, OpenAL und SoundIO sind als Alternativen vorhanden. Dummy wird keinen Audio-Output haben.\n\nIm Zweifelsfall SDL2 auswählen.", @@ -467,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "Führt Grafik-Backend Befehle auf einem zweiten Thread aus.\n\nDies beschleunigt die Shader-Kompilierung, reduziert Stottern und verbessert die Leistung auf GPU-Treibern ohne eigene Multithreading-Unterstützung. Geringfügig bessere Leistung bei Treibern mit Multithreading.\n\nIm Zweifelsfall auf AUTO stellen.", "GalThreadingTooltip": "Führt Grafik-Backend Befehle auf einem zweiten Thread aus.\n\nDies Beschleunigt die Shader-Kompilierung, reduziert Stottern und verbessert die Leistung auf GPU-Treibern ohne eigene Multithreading-Unterstützung. Geringfügig bessere Leistung bei Treibern mit Multithreading.\n\nIm Zweifelsfall auf auf AUTO stellen.", "ShaderCacheToggleTooltip": "Speichert einen persistenten Shader Cache, der das Stottern bei nachfolgenden Durchläufen reduziert.\n\nIm Zweifelsfall AN lassen.", - "ResolutionScaleTooltip": "Wendet die Auflösungsskalierung auf anwendbare Render Ziele", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", "ResolutionScaleEntryTooltip": "Fließkomma Auflösungsskalierung, wie 1,5.\n Bei nicht ganzzahligen Werten ist die Wahrscheinlichkeit größer, dass Probleme entstehen, die auch zum Absturz führen können.", - "AnisotropyTooltip": "Stufe der Anisotropen Filterung (Auf Auto setzen, um den vom Spiel geforderten Wert zu verwenden)", - "AspectRatioTooltip": "Auf das Renderer-Fenster angewandtes Seitenverhältnis.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", "ShaderDumpPathTooltip": "Grafik-Shader-Dump-Pfad", "FileLogTooltip": "Speichert die Konsolenausgabe in einer Log-Datei auf der Festplatte. Hat keinen Einfluss auf die Leistung.", "StubLogTooltip": "Ausgabe von Stub-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", @@ -504,6 +509,8 @@ "EnableInternetAccessTooltip": "Erlaubt es der emulierten Anwendung sich mit dem Internet zu verbinden.\n\nSpiele die den LAN-Modus unterstützen, ermöglichen es Ryujinx sich sowohl mit anderen Ryujinx-Systemen, als auch mit offiziellen Nintendo Switch Konsolen zu verbinden. Allerdings nur, wenn diese Option aktiviert ist und die Systeme mit demselben lokalen Netzwerk verbunden sind.\n\nDies erlaubt KEINE Verbindung zu Nintendo-Servern. Kann bei bestimmten Spielen die versuchen sich mit dem Internet zu verbinden zum Absturz führen.\n\nIm Zweifelsfall AUS lassen", "GameListContextMenuManageCheatToolTip": "Öffnet den Cheat-Manager", "GameListContextMenuManageCheat": "Cheats verwalten", + "GameListContextMenuManageModToolTip": "Mods verwalten", + "GameListContextMenuManageMod": "Mods verwalten", "ControllerSettingsStickRange": "Bereich:", "DialogStopEmulationTitle": "Ryujinx - Beende Emulation", "DialogStopEmulationMessage": "Emulation wirklich beenden?", @@ -513,10 +520,8 @@ "SettingsTabNetworkConnection": "Netwerkverbindung", "SettingsTabCpuCache": "CPU-Cache", "SettingsTabCpuMemory": "CPU-Speicher", - "DialogUpdaterFlatpakNotSupportedMessage": "Bitte aktualisiere Ryujinx mit FlatHub", + "DialogUpdaterFlatpakNotSupportedMessage": "Bitte aktualisiere Ryujinx über FlatHub", "UpdaterDisabledWarningTitle": "Updater deaktiviert!", - "GameListContextMenuOpenSdModsDirectory": "Atmosphere-Mod-Verzeichnis öffnen", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Öffnet das alternative SD-Karten-Atmosphere-Verzeichnis, das die Mods der Anwendung enthält. Dieser Ordner ist nützlich für Mods, die für einen gemoddete Switch erstellt worden sind.", "ControllerSettingsRotate90": "Um 90° rotieren", "IconSize": "Cover Größe", "IconSizeTooltip": "Ändert die Größe der Spiel-Cover", @@ -544,12 +549,13 @@ "SwkbdMinCharacters": "Muss mindestens {0} Zeichen lang sein", "SwkbdMinRangeCharacters": "Muss {0}-{1} Zeichen lang sein", "SoftwareKeyboard": "Software-Tastatur", - "SoftwareKeyboardModeNumbersOnly": "Nur Zahlen", + "SoftwareKeyboardModeNumeric": "Darf nur 0-9 oder \".\" sein", "SoftwareKeyboardModeAlphabet": "Keine CJK-Zeichen", "SoftwareKeyboardModeASCII": "Nur ASCII-Text", - "DialogControllerAppletMessagePlayerRange": "Die Anwendung benötigt {0} Spieler mit:\n\nTYPEN: {1}\n\nSPIELER: {2}\n\n{3}Bitte öffne die Einstellungen und rekonfiguriere die Controller Einstellungen oder drücke auf schließen.", - "DialogControllerAppletMessage": "Die Anwendung benötigt genau {0} Speieler mit:\n\nTYPEN: {1}\n\nSPIELER: {2}\n\n{3}Bitte öffne die Einstellungen und rekonfiguriere die Controller Einstellungen oder drücke auf schließen.", - "DialogControllerAppletDockModeSet": "Der 'Docked Modus' ist ausgewählt. Handheld ist ebenfalls ungültig.\n\n", + "ControllerAppletControllers": "Unterstützte Controller:", + "ControllerAppletPlayers": "Spieler:", + "ControllerAppletDescription": "Ihre aktuelle Konfiguration ist ungültig. Öffnen Sie die Einstellungen und konfigurieren Sie Ihre Eingaben neu.", + "ControllerAppletDocked": "Andockmodus gesetzt. Handheld-Steuerung sollte deaktiviert worden sein.", "UpdaterRenaming": "Alte Dateien umbenennen...", "UpdaterRenameFailed": "Der Updater konnte die folgende Datei nicht umbenennen: {0}", "UpdaterAddingFiles": "Neue Dateien hinzufügen...", @@ -569,8 +575,8 @@ "OpenFolderDialogTitle": "Wähle einen Ordner mit einem entpackten Spiel", "AllSupportedFormats": "Alle unterstützten Formate", "RyujinxUpdater": "Ryujinx - Updater", - "SettingsTabHotkeys": "Tastatur Hotkeys", - "SettingsTabHotkeysHotkeys": "Tastatur Hotkeys", + "SettingsTabHotkeys": "Tastatur-Hotkeys", + "SettingsTabHotkeysHotkeys": "Tastatur-Hotkeys", "SettingsTabHotkeysToggleVsyncHotkey": "VSync:", "SettingsTabHotkeysScreenshotHotkey": "Screenshot:", "SettingsTabHotkeysShowUiHotkey": "Zeige UI:", @@ -587,6 +593,7 @@ "Writable": "Beschreibbar", "SelectDlcDialogTitle": "DLC-Dateien auswählen", "SelectUpdateDialogTitle": "Update-Datei auswählen", + "SelectModDialogTitle": "Mod-Ordner auswählen", "UserProfileWindowTitle": "Benutzerprofile verwalten", "CheatWindowTitle": "Spiel-Cheats verwalten", "DlcWindowTitle": "Spiel-DLC verwalten", @@ -594,10 +601,12 @@ "CheatWindowHeading": "Cheats verfügbar für {0} [{1}]", "BuildId": "BuildId:", "DlcWindowHeading": "DLC verfügbar für {0} [{1}]", + "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "Profil bearbeiten", "Cancel": "Abbrechen", "Save": "Speichern", "Discard": "Verwerfen", + "Paused": "Pausiert", "UserProfilesSetProfileImage": "Profilbild einrichten", "UserProfileEmptyNameError": "Name ist erforderlich", "UserProfileNoImageError": "Bitte ein Profilbild auswählen", @@ -605,11 +614,11 @@ "SettingsTabHotkeysResScaleUpHotkey": "Auflösung erhöhen:", "SettingsTabHotkeysResScaleDownHotkey": "Auflösung verringern:", "UserProfilesName": "Name:", - "UserProfilesUserId": "Benutzer Id:", + "UserProfilesUserId": "Benutzer-ID:", "SettingsTabGraphicsBackend": "Grafik-Backend:", - "SettingsTabGraphicsBackendTooltip": "Verwendendetes Grafik-Backend", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", "SettingsEnableTextureRecompression": "Textur-Rekompression", - "SettingsEnableTextureRecompressionTooltip": "Komprimiert bestimmte Texturen, um den VRAM-Verbrauch zu reduzieren.\n\nEmpfohlen für die Verwendung von GPUs, die weniger als 4 GiB VRAM haben.\n\nIm Zweifelsfall AUS lassen", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", "SettingsTabGraphicsPreferredGpu": "Bevorzugte GPU:", "SettingsTabGraphicsPreferredGpuTooltip": "Wähle die Grafikkarte aus, die mit dem Vulkan Grafik-Backend verwendet werden soll.\n\nDies hat keinen Einfluss auf die GPU die OpenGL verwendet.\n\nIm Zweifelsfall die als \"dGPU\" gekennzeichnete GPU auswählen. Diese Einstellung unberührt lassen, wenn keine zur Auswahl steht.", "SettingsAppRequiredRestartMessage": "Ein Neustart von Ryujinx ist erforderlich", @@ -635,12 +644,12 @@ "Recover": "Wiederherstellen", "UserProfilesRecoverHeading": "Speicherstände wurden für die folgenden Konten gefunden", "UserProfilesRecoverEmptyList": "Keine Profile zum Wiederherstellen", - "GraphicsAATooltip": "Wendet Anti-Aliasing auf das Spiel-Rendering an", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", "GraphicsAALabel": "Antialiasing:", "GraphicsScalingFilterLabel": "Skalierungsfilter:", - "GraphicsScalingFilterTooltip": "Ermöglicht Framebuffer-Skalierung", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", "GraphicsScalingFilterLevelLabel": "Stufe", - "GraphicsScalingFilterLevelTooltip": "Skalierungsfilter-Stufe festlegen", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA Niedrig", "SmaaMedium": "SMAA Mittel", "SmaaHigh": "SMAA Hoch", @@ -648,9 +657,12 @@ "UserEditorTitle": "Nutzer bearbeiten", "UserEditorTitleCreate": "Nutzer erstellen", "SettingsTabNetworkInterface": "Netzwerkschnittstelle:", - "NetworkInterfaceTooltip": "Die Netzwerkschnittstelle, die für LAN-Funktionen verwendet wird", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", "NetworkInterfaceDefault": "Standard", "PackagingShaders": "Verpackt Shader", "AboutChangelogButton": "Changelog in GitHub öffnen", - "AboutChangelogButtonTooltipMessage": "Klicke hier, um das Changelog für diese Version in Ihrem Standardbrowser zu öffnen." -} \ No newline at end of file + "AboutChangelogButtonTooltipMessage": "Klicke hier, um das Changelog für diese Version in Ihrem Standardbrowser zu öffnen.", + "SettingsTabNetworkMultiplayer": "Mehrspieler", + "MultiplayerMode": "Modus:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/el_GR.json b/src/Ryujinx/Assets/Locales/el_GR.json index 59f50ad92..5b7e3b97d 100644 --- a/src/Ryujinx/Assets/Locales/el_GR.json +++ b/src/Ryujinx/Assets/Locales/el_GR.json @@ -14,7 +14,7 @@ "MenuBarFileOpenEmuFolder": "Άνοιγμα Φακέλου Ryujinx", "MenuBarFileOpenLogsFolder": "Άνοιγμα Φακέλου Καταγραφής", "MenuBarFileExit": "_Έξοδος", - "MenuBarOptions": "Επιλογές", + "MenuBarOptions": "_Επιλογές", "MenuBarOptionsToggleFullscreen": "Λειτουργία Πλήρους Οθόνης", "MenuBarOptionsStartGamesInFullscreen": "Εκκίνηση Παιχνιδιών σε Πλήρη Οθόνη", "MenuBarOptionsStopEmulation": "Διακοπή Εξομοίωσης", @@ -30,7 +30,7 @@ "MenuBarToolsManageFileTypes": "Διαχείριση τύπων αρχείων", "MenuBarToolsInstallFileTypes": "Εγκαταστήσετε τύπους αρχείων.", "MenuBarToolsUninstallFileTypes": "Απεγκαταστήσετε τύπους αρχείων", - "MenuBarHelp": "Βοήθεια", + "MenuBarHelp": "_Βοήθεια", "MenuBarHelpCheckForUpdates": "Έλεγχος για Ενημερώσεις", "MenuBarHelpAbout": "Σχετικά με", "MenuSearch": "Αναζήτηση...", @@ -54,8 +54,6 @@ "GameListContextMenuManageTitleUpdatesToolTip": "Ανοίγει το παράθυρο διαχείρισης Ενημερώσεων Παιχνιδιού", "GameListContextMenuManageDlc": "Διαχείριση DLC", "GameListContextMenuManageDlcToolTip": "Ανοίγει το παράθυρο διαχείρισης DLC", - "GameListContextMenuOpenModsDirectory": "Άνοιγμα Τοποθεσίας Τροποποιήσεων", - "GameListContextMenuOpenModsDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει τις Τροποποιήσεις της εφαρμογής", "GameListContextMenuCacheManagement": "Διαχείριση Προσωρινής Μνήμης", "GameListContextMenuCacheManagementPurgePptc": "Εκκαθάριση Προσωρινής Μνήμης PPTC", "GameListContextMenuCacheManagementPurgePptcToolTip": "Διαγράφει την προσωρινή μνήμη PPTC της εφαρμογής", @@ -72,15 +70,22 @@ "GameListContextMenuExtractDataRomFSToolTip": "Εξαγωγή της ενότητας RomFS από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)", "GameListContextMenuExtractDataLogo": "Λογότυπο", "GameListContextMenuExtractDataLogoToolTip": "Εξαγωγή της ενότητας Logo από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)", + "GameListContextMenuCreateShortcut": "Δημιουργία Συντόμευσης Εφαρμογής", + "GameListContextMenuCreateShortcutToolTip": "Δημιουργία συντόμευσης επιφάνειας εργασίας που ανοίγει την επιλεγμένη εφαρμογή", + "GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application", + "GameListContextMenuOpenModsDirectory": "Open Mods Directory", + "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", + "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", "StatusBarGamesLoaded": "{0}/{1} Φορτωμένα Παιχνίδια", "StatusBarSystemVersion": "Έκδοση Συστήματος: {0}", - "LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected", - "LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}", - "LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "Yes, until the next restart", - "LinuxVmMaxMapCountDialogButtonPersistent": "Yes, permanently", - "LinuxVmMaxMapCountWarningTextPrimary": "Max amount of memory mappings is lower than recommended.", - "LinuxVmMaxMapCountWarningTextSecondary": "The current value of vm.max_map_count ({0}) is lower than {1}. Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.\n\nYou might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.", + "LinuxVmMaxMapCountDialogTitle": "Εντοπίστηκε χαμηλό όριο για αντιστοιχίσεις μνήμης", + "LinuxVmMaxMapCountDialogTextPrimary": "Θα θέλατε να αυξήσετε την τιμή του vm.max_map_count σε {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Μερικά παιχνίδια μπορεί να προσπαθήσουν να δημιουργήσουν περισσότερες αντιστοιχίσεις μνήμης από αυτές που επιτρέπονται τώρα. Ο Ryujinx θα καταρρεύσει μόλις ξεπεραστεί αυτό το όριο.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Ναι, μέχρι την επόμενη επανεκκίνηση", + "LinuxVmMaxMapCountDialogButtonPersistent": "Ναι, μόνιμα", + "LinuxVmMaxMapCountWarningTextPrimary": "Ο μέγιστος αριθμός αντιστοιχίσεων μνήμης είναι μικρότερος από τον συνιστώμενο.", + "LinuxVmMaxMapCountWarningTextSecondary": "Η τρέχουσα τιμή του vm.max_map_count ({0}) είναι χαμηλότερη από {1}. Ορισμένα παιχνίδια μπορεί να προσπαθήσουν να δημιουργήσουν περισσότερες αντιστοιχίσεις μνήμης από αυτές που επιτρέπονται τώρα. Ο Ryujinx θα συντριβεί μόλις ξεπεραστεί το όριο.\n\nΜπορεί να θέλετε είτε να αυξήσετε χειροκίνητα το όριο ή να εγκαταστήσετε το pkexec, το οποίο επιτρέπει Ryujinx να βοηθήσει με αυτό.", "Settings": "Ρυθμίσεις", "SettingsTabGeneral": "Εμφάνιση", "SettingsTabGeneralGeneral": "Γενικά", @@ -150,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "Εγγενής (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", "SettingsTabGraphicsAspectRatio": "Αναλογία Απεικόνισης:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -228,10 +233,10 @@ "ControllerSettingsStickDown": "Κάτω", "ControllerSettingsStickLeft": "Αριστερά", "ControllerSettingsStickRight": "Δεξιά", - "ControllerSettingsStickStick": "Stick", - "ControllerSettingsStickInvertXAxis": "Invert Stick X", - "ControllerSettingsStickInvertYAxis": "Invert Stick Y", - "ControllerSettingsStickDeadzone": "Deadzone:", + "ControllerSettingsStickStick": "Μοχλός", + "ControllerSettingsStickInvertXAxis": "Αντιστροφή Μοχλού X", + "ControllerSettingsStickInvertYAxis": "Αντιστροφή Μοχλού Y", + "ControllerSettingsStickDeadzone": "Νεκρή Ζώνη:", "ControllerSettingsLStick": "Αριστερός Μοχλός", "ControllerSettingsRStick": "Δεξιός Μοχλός", "ControllerSettingsTriggersLeft": "Αριστερή Σκανδάλη", @@ -289,16 +294,12 @@ "ControllerSettingsSaveProfileToolTip": "Αποθήκευση Προφίλ", "MenuBarFileToolsTakeScreenshot": "Λήψη Στιγμιότυπου", "MenuBarFileToolsHideUi": "Απόκρυψη UI", - "GameListContextMenuRunApplication": "Run Application", + "GameListContextMenuRunApplication": "Εκτέλεση Εφαρμογής", "GameListContextMenuToggleFavorite": "Εναλλαγή Αγαπημένου", "GameListContextMenuToggleFavoriteToolTip": "Εναλλαγή της Κατάστασης Αγαπημένο του Παιχνιδιού", - "SettingsTabGeneralTheme": "Θέμα", - "SettingsTabGeneralThemeCustomTheme": "Προσαρμοσμένη Τοποθεσία Θέματος", - "SettingsTabGeneralThemeBaseStyle": "Βασικό Στυλ", - "SettingsTabGeneralThemeBaseStyleDark": "Σκούρο", - "SettingsTabGeneralThemeBaseStyleLight": "Ανοιχτό", - "SettingsTabGeneralThemeEnableCustomTheme": "Ενεργοποίηση Προσαρμοσμένου Θέματος", - "ButtonBrowse": "Αναζήτηση", + "SettingsTabGeneralTheme": "Theme:", + "SettingsTabGeneralThemeDark": "Dark", + "SettingsTabGeneralThemeLight": "Light", "ControllerSettingsConfigureGeneral": "Παραμέτρων", "ControllerSettingsRumble": "Δόνηση", "ControllerSettingsRumbleStrongMultiplier": "Ισχυρός Πολλαπλασιαστής Δόνησης", @@ -332,8 +333,6 @@ "DialogUpdaterAddingFilesMessage": "Προσθήκη Νέας Ενημέρωσης...", "DialogUpdaterCompleteMessage": "Η Ενημέρωση Ολοκληρώθηκε!", "DialogUpdaterRestartMessage": "Θέλετε να επανεκκινήσετε το Ryujinx τώρα;", - "DialogUpdaterArchNotSupportedMessage": "Δεν υπάρχει υποστηριζόμενη αρχιτεκτονική συστήματος!", - "DialogUpdaterArchNotSupportedSubMessage": "(Υποστηρίζονται μόνο συστήματα x64!)", "DialogUpdaterNoInternetMessage": "Δεν είστε συνδεδεμένοι στο Διαδίκτυο!", "DialogUpdaterNoInternetSubMessage": "Επαληθεύστε ότι έχετε σύνδεση στο Διαδίκτυο που λειτουργεί!", "DialogUpdaterDirtyBuildMessage": "Δεν μπορείτε να ενημερώσετε μία Πρόχειρη Έκδοση του Ryujinx!", @@ -385,7 +384,10 @@ "DialogUserProfileUnsavedChangesSubMessage": "Θέλετε να απορρίψετε τις αλλαγές σας;", "DialogControllerSettingsModifiedConfirmMessage": "Οι τρέχουσες ρυθμίσεις χειρισμού έχουν ενημερωθεί.", "DialogControllerSettingsModifiedConfirmSubMessage": "Θέλετε να αποθηκεύσετε;", - "DialogLoadNcaErrorMessage": "{0}. Σφάλμα Αρχείου: {1}", + "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", + "DialogModAlreadyExistsMessage": "Mod already exists", + "DialogModInvalidMessage": "The specified directory does not contain a mod!", + "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", "DialogDlcNoDlcErrorMessage": "Το αρχείο δεν περιέχει DLC για τον επιλεγμένο τίτλο!", "DialogPerformanceCheckLoggingEnabledMessage": "Έχετε ενεργοποιημένη την καταγραφή εντοπισμού σφαλμάτων, η οποία έχει σχεδιαστεί για χρήση μόνο από προγραμματιστές.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Για βέλτιστη απόδοση, συνιστάται η απενεργοποίηση καταγραφής εντοπισμού σφαλμάτων. Θέλετε να απενεργοποιήσετε την καταγραφή τώρα;", @@ -396,6 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "Το αρχείο δεν περιέχει ενημέρωση για τον επιλεγμένο τίτλο!", "DialogSettingsBackendThreadingWarningTitle": "Προειδοποίηση - Backend Threading", "DialogSettingsBackendThreadingWarningMessage": "Το Ryujinx πρέπει να επανεκκινηθεί αφού αλλάξει αυτή η επιλογή για να εφαρμοστεί πλήρως. Ανάλογα με την πλατφόρμα σας, μπορεί να χρειαστεί να απενεργοποιήσετε με μη αυτόματο τρόπο το multithreading του ίδιου του προγράμματος οδήγησης όταν χρησιμοποιείτε το Ryujinx.", + "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", "SettingsTabGraphicsFeaturesOptions": "Χαρακτηριστικά", "SettingsTabGraphicsBackendMultithreading": "Πολυνηματική Επεξεργασία Γραφικών:", "CommonAuto": "Αυτόματο", @@ -430,6 +434,7 @@ "DlcManagerRemoveAllButton": "Αφαίρεση όλων", "DlcManagerEnableAllButton": "Ενεργοποίηση Όλων", "DlcManagerDisableAllButton": "Απενεργοποίηση Όλων", + "ModManagerDeleteAllButton": "Delete All", "MenuBarOptionsChangeLanguage": "Αλλαξε γλώσσα", "MenuBarShowFileTypes": "Εμφάνιση Τύπων Αρχείων", "CommonSort": "Κατάταξη", @@ -447,13 +452,13 @@ "CustomThemePathTooltip": "Διαδρομή προς το προσαρμοσμένο θέμα GUI", "CustomThemeBrowseTooltip": "Αναζητήστε ένα προσαρμοσμένο θέμα GUI", "DockModeToggleTooltip": "Ενεργοποιήστε ή απενεργοποιήστε τη λειτουργία σύνδεσης", - "DirectKeyboardTooltip": "Ενεργοποίηση ή απενεργοποίηση της \"υποστήριξης άμεσης πρόσβασης πληκτρολογίου (HID)\" (Παρέχει πρόσβαση στα παιχνίδια στο πληκτρολόγιό σας ως συσκευή εισαγωγής κειμένου)", - "DirectMouseTooltip": "Ενεργοποίηση ή απενεργοποίηση της \"υποστήριξης άμεσης πρόσβασης ποντικιού (HID)\" (Παρέχει πρόσβαση στα παιχνίδια στο ποντίκι σας ως συσκευή κατάδειξης)", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", "RegionTooltip": "Αλλαγή Περιοχής Συστήματος", "LanguageTooltip": "Αλλαγή Γλώσσας Συστήματος", "TimezoneTooltip": "Αλλαγή Ζώνης Ώρας Συστήματος", "TimeTooltip": "Αλλαγή Ώρας Συστήματος", - "VSyncToggleTooltip": "Ενεργοποιεί ή απενεργοποιεί τον κατακόρυφο συγχρονισμό", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", "PptcToggleTooltip": "Ενεργοποιεί ή απενεργοποιεί το PPTC", "FsIntegrityToggleTooltip": "Ενεργοποιεί τους ελέγχους ακεραιότητας σε αρχεία περιεχομένου παιχνιδιού", "AudioBackendTooltip": "Αλλαγή ήχου υποστήριξης", @@ -467,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "Ενεργοποίηση Πολυνηματικής Επεξεργασίας Γραφικών", "GalThreadingTooltip": "Εκτελεί εντολές γραφικών σε ένα δεύτερο νήμα. Επιτρέπει την πολυνηματική μεταγλώττιση Shader σε χρόνο εκτέλεσης, μειώνει το τρεμόπαιγμα και βελτιώνει την απόδοση των προγραμμάτων οδήγησης χωρίς τη δική τους υποστήριξη πολλαπλών νημάτων. Ποικίλες κορυφαίες επιδόσεις σε προγράμματα οδήγησης με multithreading. Μπορεί να χρειαστεί επανεκκίνηση του Ryujinx για να απενεργοποιήσετε σωστά την ενσωματωμένη λειτουργία πολλαπλών νημάτων του προγράμματος οδήγησης ή ίσως χρειαστεί να το κάνετε χειροκίνητα για να έχετε την καλύτερη απόδοση.", "ShaderCacheToggleTooltip": "Ενεργοποιεί ή απενεργοποιεί την Προσωρινή Μνήμη Shader", - "ResolutionScaleTooltip": "Κλίμακα ανάλυσης που εφαρμόστηκε σε ισχύοντες στόχους απόδοσης", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", "ResolutionScaleEntryTooltip": "Κλίμακα ανάλυσης κινητής υποδιαστολής, όπως 1,5. Οι μη αναπόσπαστες τιμές είναι πιθανό να προκαλέσουν προβλήματα ή σφάλματα.", - "AnisotropyTooltip": "Επίπεδο Ανισότροπου Φιλτραρίσματος (ρυθμίστε το στο Αυτόματο για να χρησιμοποιηθεί η τιμή που ζητήθηκε από το παιχνίδι)", - "AspectRatioTooltip": "Λόγος διαστάσεων που εφαρμόστηκε στο παράθυρο απόδοσης.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", "ShaderDumpPathTooltip": "Τοποθεσία Εναπόθεσης Προσωρινής Μνήμης Shaders", "FileLogTooltip": "Ενεργοποιεί ή απενεργοποιεί την καταγραφή σε ένα αρχείο στο δίσκο", "StubLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων καταγραφής ατελειών", @@ -504,6 +509,8 @@ "EnableInternetAccessTooltip": "Επιτρέπει την πρόσβαση επισκέπτη στο Διαδίκτυο. Εάν ενεργοποιηθεί, η εξομοιωμένη κονσόλα Switch θα συμπεριφέρεται σαν να είναι συνδεδεμένη στο Διαδίκτυο. Λάβετε υπόψη ότι σε ορισμένες περιπτώσεις, οι εφαρμογές ενδέχεται να εξακολουθούν να έχουν πρόσβαση στο Διαδίκτυο, ακόμη και όταν αυτή η επιλογή είναι απενεργοποιημένη", "GameListContextMenuManageCheatToolTip": "Διαχείριση Κόλπων", "GameListContextMenuManageCheat": "Διαχείριση Κόλπων", + "GameListContextMenuManageModToolTip": "Manage Mods", + "GameListContextMenuManageMod": "Manage Mods", "ControllerSettingsStickRange": "Εύρος:", "DialogStopEmulationTitle": "Ryujinx - Διακοπή εξομοίωσης", "DialogStopEmulationMessage": "Είστε βέβαιοι ότι θέλετε να σταματήσετε την εξομοίωση;", @@ -515,8 +522,6 @@ "SettingsTabCpuMemory": "Μνήμη CPU", "DialogUpdaterFlatpakNotSupportedMessage": "Παρακαλούμε ενημερώστε το Ryujinx μέσω FlatHub.", "UpdaterDisabledWarningTitle": "Ο Διαχειριστής Ενημερώσεων Είναι Απενεργοποιημένος!", - "GameListContextMenuOpenSdModsDirectory": "Άνοιγμα Της Τοποθεσίας Των Atmosphere Mods", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Ανοίγει τον εναλλακτικό SD card Atmosphere κατάλογο που περιέχει Mods για το Application. Χρήσιμο για mods τα οποία συσκευάστηκαν για την πραγματική κονσόλα.", "ControllerSettingsRotate90": "Περιστροφή 90° Δεξιόστροφα", "IconSize": "Μέγεθος Εικονιδίου", "IconSizeTooltip": "Αλλάξτε μέγεθος εικονιδίων των παιχνιδιών", @@ -544,12 +549,13 @@ "SwkbdMinCharacters": "Πρέπει να έχει μήκος τουλάχιστον {0} χαρακτήρες", "SwkbdMinRangeCharacters": "Πρέπει να έχει μήκος {0}-{1} χαρακτήρες", "SoftwareKeyboard": "Εικονικό Πληκτρολόγιο", - "SoftwareKeyboardModeNumbersOnly": "Must be numbers only", - "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", - "SoftwareKeyboardModeASCII": "Must be ASCII text only", - "DialogControllerAppletMessagePlayerRange": "Η εφαρμογή ζητά {0} παίκτη(ες) με:\n\nΤΥΠΟΥΣ: {1}\n\nΠΑΙΚΤΕΣ: {2}\n\n{3}Παρακαλώ ανοίξτε τις ρυθμίσεις και αλλάξτε τις ρυθμίσεις εισαγωγής τώρα ή πατήστε Κλείσιμο.", - "DialogControllerAppletMessage": "Η εφαρμογή ζητά ακριβώς {0} παίκτη(ες) με:\n\nΤΥΠΟΥΣ: {1}\n\nΠΑΙΚΤΕΣ: {2}\n\n{3}Παρακαλώ ανοίξτε τις ρυθμίσεις και αλλάξτε τις Ρυθμίσεις εισαγωγής τώρα ή πατήστε Κλείσιμο.", - "DialogControllerAppletDockModeSet": "Η κατάσταση Docked ενεργοποιήθηκε. Το συσκευή παλάμης είναι επίσης μην αποδεκτό.", + "SoftwareKeyboardModeNumeric": "Πρέπει να είναι 0-9 ή '.' μόνο", + "SoftwareKeyboardModeAlphabet": "Πρέπει να μην είναι μόνο χαρακτήρες CJK", + "SoftwareKeyboardModeASCII": "Πρέπει να είναι μόνο κείμενο ASCII", + "ControllerAppletControllers": "Supported Controllers:", + "ControllerAppletPlayers": "Players:", + "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", + "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", "UpdaterRenaming": "Μετονομασία Παλαιών Αρχείων...", "UpdaterRenameFailed": "Δεν ήταν δυνατή η μετονομασία του αρχείου: {0}", "UpdaterAddingFiles": "Προσθήκη Νέων Αρχείων...", @@ -587,6 +593,7 @@ "Writable": "Εγγράψιμο", "SelectDlcDialogTitle": "Επιλογή αρχείων DLC", "SelectUpdateDialogTitle": "Επιλογή αρχείων ενημέρωσης", + "SelectModDialogTitle": "Select mod directory", "UserProfileWindowTitle": "Διαχειριστής Προφίλ Χρήστη", "CheatWindowTitle": "Διαχειριστής των Cheats", "DlcWindowTitle": "Downloadable Content Manager", @@ -594,10 +601,12 @@ "CheatWindowHeading": "Διαθέσιμα Cheats για {0} [{1}]", "BuildId": "BuildId:", "DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})", + "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "Επεξεργασία Επιλεγμένων", "Cancel": "Ακύρωση", "Save": "Αποθήκευση", "Discard": "Απόρριψη", + "Paused": "Σε παύση", "UserProfilesSetProfileImage": "Ορισμός Εικόνας Προφίλ", "UserProfileEmptyNameError": "Απαιτείται όνομα", "UserProfileNoImageError": "Η εικόνα προφίλ πρέπει να οριστεί", @@ -607,9 +616,9 @@ "UserProfilesName": "Όνομα:", "UserProfilesUserId": "User Id:", "SettingsTabGraphicsBackend": "Σύστημα Υποστήριξης Γραφικών", - "SettingsTabGraphicsBackendTooltip": "Backend Γραφικών που θα χρησιμοποιηθεί", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", "SettingsEnableTextureRecompression": "Ενεργοποίηση Επανασυμπίεσης Των Texture", - "SettingsEnableTextureRecompressionTooltip": "Συμπιέζει συγκεκριμένα texture για να μειωθεί η χρήση της VRAM.\nΣυνίσταται για χρήση σε κάρτες γραφικών με λιγότερο από 4 GiB VRAM.\nΑφήστε το Απενεργοποιημένο αν είστε αβέβαιοι.", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", "SettingsTabGraphicsPreferredGpu": "Προτιμώμενη GPU", "SettingsTabGraphicsPreferredGpuTooltip": "Επιλέξτε την κάρτα γραφικών η οποία θα χρησιμοποιηθεί από το Vulkan.\n\nΔεν επηρεάζει το OpenGL.\n\nΔιαλέξτε την GPU που διαθέτει την υπόδειξη \"dGPU\" αν δεν είστε βέβαιοι. Αν δεν υπάρχει κάποιαν, το πειράξετε", "SettingsAppRequiredRestartMessage": "Απαιτείται Επανεκκίνηση Του Ryujinx", @@ -620,8 +629,8 @@ "SettingsTabHotkeysVolumeDownHotkey": "Μείωση Έντασης:", "SettingsEnableMacroHLE": "Ενεργοποίηση του Macro HLE", "SettingsEnableMacroHLETooltip": "Προσομοίωση του κώδικα GPU Macro .\n\nΒελτιώνει την απόδοση, αλλά μπορεί να προκαλέσει γραφικά προβλήματα σε μερικά παιχνίδια.\n\nΑφήστε ΕΝΕΡΓΟ αν δεν είστε σίγουροι.", - "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", - "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", + "SettingsEnableColorSpacePassthrough": "Διέλευση Χρωματικού Χώρου", + "SettingsEnableColorSpacePassthroughTooltip": "Σκηνοθετεί το σύστημα υποστήριξης του Vulkan για να περάσει από πληροφορίες χρώματος χωρίς να καθορίσει έναν χρωματικό χώρο. Για χρήστες με ευρείες οθόνες γκάμας, αυτό μπορεί να οδηγήσει σε πιο ζωηρά χρώματα, με κόστος την ορθότητα του χρώματος.", "VolumeShort": "Έντ.", "UserProfilesManageSaves": "Διαχείριση Των Save", "DeleteUserSave": "Επιθυμείτε να διαγράψετε το save χρήστη για το συγκεκριμένο παιχνίδι;", @@ -635,12 +644,12 @@ "Recover": "Ανάκτηση", "UserProfilesRecoverHeading": "Βρέθηκαν save για τους ακόλουθους λογαριασμούς", "UserProfilesRecoverEmptyList": "Δεν υπάρχουν προφίλ για ανάκτηση", - "GraphicsAATooltip": "Εφαρμόζει anti-aliasing στην απόδοση του παιχνιδιού", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", "GraphicsAALabel": "Anti-Aliasing", "GraphicsScalingFilterLabel": "Φίλτρο Κλιμάκωσης:", - "GraphicsScalingFilterTooltip": "Ενεργοποιεί Κλίμακα Framebuffer", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", "GraphicsScalingFilterLevelLabel": "Επίπεδο", - "GraphicsScalingFilterLevelTooltip": "Ορισμός Επιπέδου Φίλτρου Κλιμάκωσης", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "Χαμηλό SMAA", "SmaaMedium": " Μεσαίο SMAA", "SmaaHigh": "Υψηλό SMAA", @@ -648,9 +657,12 @@ "UserEditorTitle": "Επεξεργασία Χρήστη", "UserEditorTitleCreate": "Δημιουργία Χρήστη", "SettingsTabNetworkInterface": "Διεπαφή Δικτύου", - "NetworkInterfaceTooltip": "Η διεπαφή δικτύου που χρησιμοποιείται για τα χαρακτηριστικά LAN", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", "NetworkInterfaceDefault": "Προεπιλογή", "PackagingShaders": "Shaders Συσκευασίας", - "AboutChangelogButton": "View Changelog on GitHub", - "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser." -} \ No newline at end of file + "AboutChangelogButton": "Προβολή αρχείου αλλαγών στο GitHub", + "AboutChangelogButtonTooltipMessage": "Κάντε κλικ για να ανοίξετε το αρχείο αλλαγών για αυτήν την έκδοση στο προεπιλεγμένο πρόγραμμα περιήγησης σας.", + "SettingsTabNetworkMultiplayer": "Πολλαπλοί παίκτες", + "MultiplayerMode": "Λειτουργία:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/es_ES.json b/src/Ryujinx/Assets/Locales/es_ES.json index 91bcd8f11..ff41c855f 100644 --- a/src/Ryujinx/Assets/Locales/es_ES.json +++ b/src/Ryujinx/Assets/Locales/es_ES.json @@ -30,7 +30,7 @@ "MenuBarToolsManageFileTypes": "Administrar tipos de archivo", "MenuBarToolsInstallFileTypes": "Instalar tipos de archivo", "MenuBarToolsUninstallFileTypes": "Desinstalar tipos de archivo", - "MenuBarHelp": "Ayuda", + "MenuBarHelp": "_Ayuda", "MenuBarHelpCheckForUpdates": "Buscar actualizaciones", "MenuBarHelpAbout": "Acerca de", "MenuSearch": "Buscar...", @@ -54,17 +54,15 @@ "GameListContextMenuManageTitleUpdatesToolTip": "Abrir la ventana de gestión de actualizaciones de esta aplicación", "GameListContextMenuManageDlc": "Gestionar DLC", "GameListContextMenuManageDlcToolTip": "Abrir la ventana de gestión del DLC", - "GameListContextMenuOpenModsDirectory": "Abrir carpeta de mods", - "GameListContextMenuOpenModsDirectoryToolTip": "Abrir la carpeta que contiene los mods (archivos modificantes) de esta aplicación", "GameListContextMenuCacheManagement": "Gestión de caché ", "GameListContextMenuCacheManagementPurgePptc": "Reconstruir PPTC en cola", "GameListContextMenuCacheManagementPurgePptcToolTip": "Elimina la caché de PPTC de esta aplicación", - "GameListContextMenuCacheManagementPurgeShaderCache": "Limpiar caché de sombras", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Eliminar la caché de sombras de esta aplicación", + "GameListContextMenuCacheManagementPurgeShaderCache": "Limpiar caché de sombreadores", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Eliminar la caché de sombreadores de esta aplicación", "GameListContextMenuCacheManagementOpenPptcDirectory": "Abrir carpeta de PPTC", "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Abrir la carpeta que contiene la caché de PPTC de esta aplicación", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Abrir carpeta de caché de sombras", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Abrir la carpeta que contiene la caché de sombras de esta aplicación", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Abrir carpeta de caché de sombreadores", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Abrir la carpeta que contiene la caché de sombreadores de esta aplicación", "GameListContextMenuExtractData": "Extraer datos", "GameListContextMenuExtractDataExeFS": "ExeFS", "GameListContextMenuExtractDataExeFSToolTip": "Extraer la sección ExeFS de la configuración actual de la aplicación (incluyendo actualizaciones)", @@ -72,6 +70,13 @@ "GameListContextMenuExtractDataRomFSToolTip": "Extraer la sección RomFS de la configuración actual de la aplicación (incluyendo actualizaciones)", "GameListContextMenuExtractDataLogo": "Logotipo", "GameListContextMenuExtractDataLogoToolTip": "Extraer la sección Logo de la configuración actual de la aplicación (incluyendo actualizaciones)", + "GameListContextMenuCreateShortcut": "Crear acceso directo de aplicación", + "GameListContextMenuCreateShortcutToolTip": "Crear un acceso directo en el escritorio que lance la aplicación seleccionada", + "GameListContextMenuCreateShortcutToolTipMacOS": "Crea un acceso directo en la carpeta de Aplicaciones de macOS que inicie la Aplicación seleccionada", + "GameListContextMenuOpenModsDirectory": "Abrir Directorio de Mods", + "GameListContextMenuOpenModsDirectoryToolTip": "Abre el directorio que contiene los Mods de la Aplicación.", + "GameListContextMenuOpenSdModsDirectory": "Abrir Directorio de Mods de Atmosphere\n\n\n\n\n\n", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Abre el directorio alternativo de la tarjeta SD de Atmosphere que contiene los Mods de la Aplicación. Útil para los mods que están empaquetados para el hardware real.", "StatusBarGamesLoaded": "{0}/{1} juegos cargados", "StatusBarSystemVersion": "Versión del sistema: {0}", "LinuxVmMaxMapCountDialogTitle": "Límite inferior para mapeos de memoria detectado", @@ -138,7 +143,7 @@ "SettingsTabSystemIgnoreMissingServices": "Ignorar servicios no implementados", "SettingsTabGraphics": "Gráficos", "SettingsTabGraphicsAPI": "API de gráficos", - "SettingsTabGraphicsEnableShaderCache": "Habilitar caché de sombras", + "SettingsTabGraphicsEnableShaderCache": "Habilitar caché de sombreadores", "SettingsTabGraphicsAnisotropicFiltering": "Filtro anisotrópico:", "SettingsTabGraphicsAnisotropicFilteringAuto": "Automático", "SettingsTabGraphicsAnisotropicFiltering2x": "x2", @@ -150,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "x2 (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "x3 (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "x4 (2880p/4320p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", "SettingsTabGraphicsAspectRatio": "Relación de aspecto:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -159,7 +164,7 @@ "SettingsTabGraphicsAspectRatio32x9": "32:9", "SettingsTabGraphicsAspectRatioStretch": "Estirar a la ventana", "SettingsTabGraphicsDeveloperOptions": "Opciones de desarrollador", - "SettingsTabGraphicsShaderDumpPath": "Directorio de volcado de sombras:", + "SettingsTabGraphicsShaderDumpPath": "Directorio de volcado de sombreadores:", "SettingsTabLogging": "Registros", "SettingsTabLoggingLogging": "Registros", "SettingsTabLoggingEnableLoggingToFile": "Habilitar registro a archivo", @@ -292,13 +297,9 @@ "GameListContextMenuRunApplication": "Ejecutar aplicación", "GameListContextMenuToggleFavorite": "Marcar favorito", "GameListContextMenuToggleFavoriteToolTip": "Marca o desmarca el juego como favorito", - "SettingsTabGeneralTheme": "Tema", - "SettingsTabGeneralThemeCustomTheme": "Directorio de tema personalizado", - "SettingsTabGeneralThemeBaseStyle": "Estilo base", - "SettingsTabGeneralThemeBaseStyleDark": "Oscuro", - "SettingsTabGeneralThemeBaseStyleLight": "Claro", - "SettingsTabGeneralThemeEnableCustomTheme": "Habilitar tema personalizado", - "ButtonBrowse": "Buscar", + "SettingsTabGeneralTheme": "Tema:", + "SettingsTabGeneralThemeDark": "Oscuro", + "SettingsTabGeneralThemeLight": "Claro", "ControllerSettingsConfigureGeneral": "Configurar", "ControllerSettingsRumble": "Vibración", "ControllerSettingsRumbleStrongMultiplier": "Multiplicador de vibraciones fuertes", @@ -332,8 +333,6 @@ "DialogUpdaterAddingFilesMessage": "Aplicando actualización...", "DialogUpdaterCompleteMessage": "¡Actualización completa!", "DialogUpdaterRestartMessage": "¿Quieres reiniciar Ryujinx?", - "DialogUpdaterArchNotSupportedMessage": "¡Tu arquitectura de sistema no es compatible!", - "DialogUpdaterArchNotSupportedSubMessage": "(¡Solo son compatibles los sistemas x64!)", "DialogUpdaterNoInternetMessage": "¡No estás conectado a internet!", "DialogUpdaterNoInternetSubMessage": "¡Por favor, verifica que tu conexión a Internet funciona!", "DialogUpdaterDirtyBuildMessage": "¡No puedes actualizar una versión \"dirty\" de Ryujinx!", @@ -385,17 +384,22 @@ "DialogUserProfileUnsavedChangesSubMessage": "¿Quieres descartar los cambios realizados?", "DialogControllerSettingsModifiedConfirmMessage": "Se ha actualizado la configuración del mando actual.", "DialogControllerSettingsModifiedConfirmSubMessage": "¿Guardar cambios?", - "DialogLoadNcaErrorMessage": "{0}. Archivo con error: {1}", + "DialogLoadFileErrorMessage": "{0}. Archivo con error: {1}", + "DialogModAlreadyExistsMessage": "El mod ya existe", + "DialogModInvalidMessage": "¡El directorio especificado no contiene un mod!", + "DialogModDeleteNoParentMessage": "Error al eliminar: ¡No se pudo encontrar el directorio principal para el mod \"{0}\"!", "DialogDlcNoDlcErrorMessage": "¡Ese archivo no contiene contenido descargable para el título seleccionado!", "DialogPerformanceCheckLoggingEnabledMessage": "Has habilitado los registros debug, diseñados solo para uso de los desarrolladores.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Para un rendimiento óptimo, se recomienda deshabilitar los registros debug. ¿Quieres deshabilitarlos ahora?", - "DialogPerformanceCheckShaderDumpEnabledMessage": "Has habilitado el volcado de sombras, diseñado solo para uso de los desarrolladores.", - "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Para un rendimiento óptimo, se recomienda deshabilitar el volcado de sombraa. ¿Quieres deshabilitarlo ahora?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Has habilitado el volcado de sombreadores, diseñado solo para uso de los desarrolladores.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Para un rendimiento óptimo, se recomienda deshabilitar el volcado de sombreadores. ¿Quieres deshabilitarlo ahora?", "DialogLoadAppGameAlreadyLoadedMessage": "Ya has cargado un juego", "DialogLoadAppGameAlreadyLoadedSubMessage": "Por favor, detén la emulación o cierra el emulador antes de iniciar otro juego.", "DialogUpdateAddUpdateErrorMessage": "¡Ese archivo no contiene una actualización para el título seleccionado!", "DialogSettingsBackendThreadingWarningTitle": "Advertencia - multihilado de gráficos", "DialogSettingsBackendThreadingWarningMessage": "Ryujinx debe reiniciarse para aplicar este cambio. Dependiendo de tu plataforma, puede que tengas que desactivar manualmente la optimización enlazada de tus controladores gráficos para usar el multihilo de Ryujinx.", + "DialogModManagerDeletionWarningMessage": "Estás a punto de eliminar el mod: {0}\n\n¿Estás seguro de que quieres continuar?", + "DialogModManagerDeletionAllWarningMessage": "Estás a punto de eliminar todos los Mods para este título.\n\n¿Estás seguro de que quieres continuar?", "SettingsTabGraphicsFeaturesOptions": "Funcionalidades", "SettingsTabGraphicsBackendMultithreading": "Multihilado del motor gráfico:", "CommonAuto": "Automático", @@ -430,6 +434,7 @@ "DlcManagerRemoveAllButton": "Quitar todo", "DlcManagerEnableAllButton": "Activar todas", "DlcManagerDisableAllButton": "Desactivar todos", + "ModManagerDeleteAllButton": "Eliminar Todo", "MenuBarOptionsChangeLanguage": "Cambiar idioma", "MenuBarShowFileTypes": "Mostrar tipos de archivo", "CommonSort": "Orden", @@ -447,13 +452,13 @@ "CustomThemePathTooltip": "Carpeta que contiene los temas personalizados para la interfaz", "CustomThemeBrowseTooltip": "Busca un tema personalizado para la interfaz", "DockModeToggleTooltip": "El modo dock o modo TV hace que la consola emulada se comporte como una Nintendo Switch en su dock. Esto mejora la calidad gráfica en la mayoría de los juegos. Del mismo modo, si lo desactivas, el sistema emulado se comportará como una Nintendo Switch en modo portátil, reduciendo la cálidad de los gráficos.\n\nConfigura los controles de \"Jugador\" 1 si planeas jugar en modo dock/TV; configura los controles de \"Portátil\" si planeas jugar en modo portátil.\n\nActívalo si no sabes qué hacer.", - "DirectKeyboardTooltip": "Activa o desactiva \"soporte para acceso directo al teclado (HID)\" (Permite a los juegos utilizar tu teclado como entrada de texto)", - "DirectMouseTooltip": "Activa o desactiva \"soporte para acceso directo al ratón (HID)\" (Permite a los juegos utilizar tu ratón como cursor)", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", "RegionTooltip": "Cambia la región del sistema", "LanguageTooltip": "Cambia el idioma del sistema", "TimezoneTooltip": "Cambia la zona horaria del sistema", "TimeTooltip": "Cambia la hora del sistema", - "VSyncToggleTooltip": "Sincronización vertical del sistema emulado. A efectos prácticos es un límite de fotogramas para la mayoría de juegos; deshabilitarlo puede hacer que los juegos se aceleren o que las pantallas de carga tarden más o se atasquen.\n\nPuedes activar y desactivar esto mientras el emulador está funcionando con un atajo de teclado a tu elección. Recomendamos hacer esto en vez de deshabilitarlo.\n\nActívalo si no sabes qué hacer.", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", "PptcToggleTooltip": "Guarda funciones de JIT traducidas para que no sea necesario traducirlas cada vez que el juego carga.\n\nReduce los tirones y acelera significativamente el tiempo de inicio de los juegos después de haberlos ejecutado al menos una vez.\n\nActívalo si no sabes qué hacer.", "FsIntegrityToggleTooltip": "Comprueba si hay archivos corruptos en los juegos que ejecutes al abrirlos, y si detecta archivos corruptos, muestra un error de Hash en los registros.\n\nEsto no tiene impacto alguno en el rendimiento y está pensado para ayudar a resolver problemas.\n\nActívalo si no sabes qué hacer.", "AudioBackendTooltip": "Cambia el motor usado para renderizar audio.\n\nSDL2 es el preferido, mientras que OpenAL y SoundIO se usan si hay problemas con este. Dummy no produce audio.\n\nSelecciona SDL2 si no sabes qué hacer.", @@ -464,13 +469,13 @@ "UseHypervisorTooltip": "Usar Hypervisor en lugar de JIT. Mejora enormemente el rendimiento cuando está disponible, pero puede ser inestable en su estado actual.", "DRamTooltip": "Expande la memoria DRAM del sistema emulado de 4GiB a 6GiB.\n\nUtilizar solo con packs de texturas HD o mods de resolución 4K. NO mejora el rendimiento.\n\nDesactívalo si no sabes qué hacer.", "IgnoreMissingServicesTooltip": "Hack para ignorar servicios no implementados del Horizon OS. Esto puede ayudar a sobrepasar crasheos cuando inicies ciertos juegos.\n\nDesactívalo si no sabes qué hacer.", - "GraphicsBackendThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Acelera la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos que no realicen su propio multihilado. Rendimiento máximo ligeramente superior en controladores gráficos que soporten multihilado.\n\nSelecciona \"Auto\" si no sabes qué hacer.", - "GalThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Acelera la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos que no realicen su propio multihilado. Rendimiento máximo ligeramente superior en controladores gráficos que soporten multihilado.\n\nSelecciona \"Auto\" si no sabes qué hacer.", + "GraphicsBackendThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Acelera la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos que no realicen su propio procesamiento con múltiples hilos. Rendimiento ligeramente superior en controladores gráficos que soporten múltiples hilos.\n\nSelecciona \"Auto\" si no sabes qué hacer.", + "GalThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Acelera la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos que no realicen su propio procesamiento con múltiples hilos. Rendimiento ligeramente superior en controladores gráficos que soporten múltiples hilos.\n\nSelecciona \"Auto\" si no sabes qué hacer.", "ShaderCacheToggleTooltip": "Guarda una caché de sombreadores en disco, la cual reduce los tirones a medida que vas jugando.\n\nActívalo si no sabes qué hacer.", - "ResolutionScaleTooltip": "Escala de resolución aplicada a objetivos aplicables en el renderizado", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", "ResolutionScaleEntryTooltip": "Escalado de resolución de coma flotante, como por ejemplo 1,5. Los valores no íntegros pueden causar errores gráficos o crashes.", - "AnisotropyTooltip": "Nivel de filtrado anisotrópico (selecciona Auto para utilizar el valor solicitado por el juego)", - "AspectRatioTooltip": "Relación de aspecto aplicada a la ventana de renderizado.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", "ShaderDumpPathTooltip": "Directorio en el cual se volcarán los sombreadores de los gráficos", "FileLogTooltip": "Guarda los registros de la consola en archivos en disco. No afectan al rendimiento.", "StubLogTooltip": "Escribe mensajes de Stub en la consola. No afectan al rendimiento.", @@ -504,6 +509,8 @@ "EnableInternetAccessTooltip": "Permite a la aplicación emulada conectarse a Internet.\n\nLos juegos que tengan modo LAN podrán conectarse entre sí habilitando esta opción y estando conectados al mismo módem. Asimismo, esto permite conexiones con consolas reales.\n\nNO permite conectar con los servidores de Nintendo Online. Puede causar que ciertos juegos crasheen al intentar conectarse a sus servidores.\n\nDesactívalo si no estás seguro.", "GameListContextMenuManageCheatToolTip": "Activa o desactiva los cheats", "GameListContextMenuManageCheat": "Administrar cheats", + "GameListContextMenuManageModToolTip": "Gestionar Mods", + "GameListContextMenuManageMod": "Gestionar Mods", "ControllerSettingsStickRange": "Alcance:", "DialogStopEmulationTitle": "Ryujinx - Detener emulación", "DialogStopEmulationMessage": "¿Seguro que quieres detener la emulación actual?", @@ -515,13 +522,11 @@ "SettingsTabCpuMemory": "Memoria de CPU", "DialogUpdaterFlatpakNotSupportedMessage": "Por favor, actualiza Ryujinx a través de FlatHub.", "UpdaterDisabledWarningTitle": "¡Actualizador deshabilitado!", - "GameListContextMenuOpenSdModsDirectory": "Abrir carpeta de mods Atmosphere", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Abre la carpeta alternativa de mods en la que puedes insertar mods de Atmosphere. Útil para mods que vengan organizados para uso en consola.", "ControllerSettingsRotate90": "Rotar 90° en el sentido de las agujas del reloj", "IconSize": "Tamaño de iconos", "IconSizeTooltip": "Cambia el tamaño de los iconos de juegos", "MenuBarOptionsShowConsole": "Mostrar consola", - "ShaderCachePurgeError": "Error al eliminar la caché en {0}: {1}", + "ShaderCachePurgeError": "Error al eliminar la caché de sombreadores en {0}: {1}", "UserErrorNoKeys": "No se encontraron keys", "UserErrorNoFirmware": "No se encontró firmware", "UserErrorFirmwareParsingFailed": "Error al analizar el firmware", @@ -544,12 +549,13 @@ "SwkbdMinCharacters": "Debe tener al menos {0} caracteres", "SwkbdMinRangeCharacters": "Debe tener {0}-{1} caracteres", "SoftwareKeyboard": "Teclado de software", - "SoftwareKeyboardModeNumbersOnly": "Solo deben ser números", + "SoftwareKeyboardModeNumeric": "Debe ser sólo 0-9 o '.'", "SoftwareKeyboardModeAlphabet": "Solo deben ser caracteres no CJK", "SoftwareKeyboardModeASCII": "Solo deben ser texto ASCII", - "DialogControllerAppletMessagePlayerRange": "La aplicación require {0} jugador(es) con:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Por favor abre las opciones y reconfigura los dispositivos de entrada o presiona 'Cerrar'.", - "DialogControllerAppletMessage": "La aplicación require exactamente {0} jugador(es) con:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Por favor abre las opciones y reconfigura los dispositivos de entrada o presiona 'Cerrar'.", - "DialogControllerAppletDockModeSet": "Modo dock/TV activo. El control portátil también es inválido.\n\n", + "ControllerAppletControllers": "Controladores Compatibles:", + "ControllerAppletPlayers": "Jugadores:", + "ControllerAppletDescription": "Tu configuración actual no es válida. Abre la configuración y vuelve a configurar tus entradas", + "ControllerAppletDocked": "Modo acoplado activado. El modo portátil debería estar desactivado.", "UpdaterRenaming": "Renombrando archivos viejos...", "UpdaterRenameFailed": "El actualizador no pudo renombrar el archivo: {0}", "UpdaterAddingFiles": "Añadiendo nuevos archivos...", @@ -587,6 +593,7 @@ "Writable": "Escribible", "SelectDlcDialogTitle": "Selecciona archivo(s) de DLC", "SelectUpdateDialogTitle": "Selecciona archivo(s) de actualización", + "SelectModDialogTitle": "Seleccionar un directorio de Mods", "UserProfileWindowTitle": "Administrar perfiles de usuario", "CheatWindowTitle": "Administrar cheats", "DlcWindowTitle": "Administrar contenido descargable", @@ -594,10 +601,12 @@ "CheatWindowHeading": "Cheats disponibles para {0} [{1}]", "BuildId": "Id de compilación:", "DlcWindowHeading": "Contenido descargable disponible para {0} [{1}]", + "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "Editar selección", "Cancel": "Cancelar", "Save": "Guardar", "Discard": "Descartar", + "Paused": "Pausado", "UserProfilesSetProfileImage": "Elegir Imagen de Perfil ", "UserProfileEmptyNameError": "El nombre es obligatorio", "UserProfileNoImageError": "Debe establecerse la imagen de perfil", @@ -607,9 +616,9 @@ "UserProfilesName": "Nombre:", "UserProfilesUserId": "Id de Usuario:", "SettingsTabGraphicsBackend": "Fondo de gráficos", - "SettingsTabGraphicsBackendTooltip": "Back-end de los gráficos a utilizar", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", "SettingsEnableTextureRecompression": "Activar recompresión de texturas", - "SettingsEnableTextureRecompressionTooltip": "Comprime ciertas texturas para reducir el uso de la VRAM.\n\nRecomendado para GPUs que tienen menos de 4 GB de VRAM.\n\nMantén esta opción desactivada si no estás seguro.", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", "SettingsTabGraphicsPreferredGpu": "GPU preferida", "SettingsTabGraphicsPreferredGpuTooltip": "Selecciona la tarjeta gráfica que se utilizará con los back-end de gráficos Vulkan.\n\nNo afecta la GPU que utilizará OpenGL.\n\nFije a la GPU marcada como \"dGUP\" ante dudas. Si no hay una, no haga modificaciones.", "SettingsAppRequiredRestartMessage": "Reinicio de Ryujinx requerido.", @@ -635,12 +644,12 @@ "Recover": "Recuperar", "UserProfilesRecoverHeading": "Datos de guardado fueron encontrados para las siguientes cuentas", "UserProfilesRecoverEmptyList": "No hay perfiles a recuperar", - "GraphicsAATooltip": "Aplica el suavizado de bordes al procesamiento del juego", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", "GraphicsAALabel": "Suavizado de bordes:", "GraphicsScalingFilterLabel": "Filtro de escalado:", - "GraphicsScalingFilterTooltip": "Activa el escalado de búfer de fotogramas", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", "GraphicsScalingFilterLevelLabel": "Nivel", - "GraphicsScalingFilterLevelTooltip": "Establecer nivel del filtro de escalado", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA Bajo", "SmaaMedium": "SMAA Medio", "SmaaHigh": "SMAA Alto", @@ -648,9 +657,12 @@ "UserEditorTitle": "Editar usuario", "UserEditorTitleCreate": "Crear Usuario", "SettingsTabNetworkInterface": "Interfaz de Red", - "NetworkInterfaceTooltip": "Interfaz de red usada para las características LAN", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", "NetworkInterfaceDefault": "Predeterminado", "PackagingShaders": "Empaquetando sombreadores", "AboutChangelogButton": "Ver registro de cambios en GitHub", - "AboutChangelogButtonTooltipMessage": "Haga clic para abrir el registro de cambios para esta versión en su navegador predeterminado." -} \ No newline at end of file + "AboutChangelogButtonTooltipMessage": "Haga clic para abrir el registro de cambios para esta versión en su navegador predeterminado.", + "SettingsTabNetworkMultiplayer": "Multijugador", + "MultiplayerMode": "Modo:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/fr_FR.json b/src/Ryujinx/Assets/Locales/fr_FR.json index 5bab6f7b2..1c84573c2 100644 --- a/src/Ryujinx/Assets/Locales/fr_FR.json +++ b/src/Ryujinx/Assets/Locales/fr_FR.json @@ -1,7 +1,7 @@ { "Language": "Français", - "MenuBarFileOpenApplet": "Ouvrir Applet", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Ouvrir l'Éditeur Applet Mii en mode autonome", + "MenuBarFileOpenApplet": "Ouvrir un applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Ouvrir l'Applet Mii Editor en mode Standalone", "SettingsTabInputDirectMouseAccess": "Accès direct à la souris", "SettingsTabSystemMemoryManagerMode": "Mode de gestion de la mémoire :", "SettingsTabSystemMemoryManagerModeSoftware": "Logiciel", @@ -14,9 +14,9 @@ "MenuBarFileOpenEmuFolder": "Ouvrir le dossier Ryujinx", "MenuBarFileOpenLogsFolder": "Ouvrir le dossier des journaux", "MenuBarFileExit": "_Quitter", - "MenuBarOptions": "Options", + "MenuBarOptions": "_Options", "MenuBarOptionsToggleFullscreen": "Basculer en plein écran", - "MenuBarOptionsStartGamesInFullscreen": "Démarrer jeux en plein écran", + "MenuBarOptionsStartGamesInFullscreen": "Démarrer le jeu en plein écran", "MenuBarOptionsStopEmulation": "Arrêter l'émulation", "MenuBarOptionsSettings": "_Paramètres", "MenuBarOptionsManageUserProfiles": "_Gérer les profils d'utilisateurs", @@ -30,9 +30,9 @@ "MenuBarToolsManageFileTypes": "Gérer les types de fichiers", "MenuBarToolsInstallFileTypes": "Installer les types de fichiers", "MenuBarToolsUninstallFileTypes": "Désinstaller les types de fichiers", - "MenuBarHelp": "Aide", + "MenuBarHelp": "_Aide", "MenuBarHelpCheckForUpdates": "Vérifier les mises à jour", - "MenuBarHelpAbout": "Á propos", + "MenuBarHelpAbout": "À propos", "MenuSearch": "Rechercher...", "GameListHeaderFavorite": "Favoris", "GameListHeaderIcon": "Icône", @@ -40,8 +40,8 @@ "GameListHeaderDeveloper": "Développeur", "GameListHeaderVersion": "Version", "GameListHeaderTimePlayed": "Temps de jeu", - "GameListHeaderLastPlayed": "jouer la dernière fois", - "GameListHeaderFileExtension": "Fichier externe", + "GameListHeaderLastPlayed": "Dernière partie jouée", + "GameListHeaderFileExtension": "Extension du Fichier", "GameListHeaderFileSize": "Taille du Fichier", "GameListHeaderPath": "Chemin", "GameListContextMenuOpenUserSaveDirectory": "Ouvrir le dossier de sauvegarde utilisateur", @@ -51,15 +51,13 @@ "GameListContextMenuOpenBcatSaveDirectory": "Ouvrir le dossier de sauvegarde BCAT", "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde BCAT du jeu", "GameListContextMenuManageTitleUpdates": "Gérer la mise à jour des titres", - "GameListContextMenuManageTitleUpdatesToolTip": "Ouvre la fenêtre de gestion de la mise à jour des titres", + "GameListContextMenuManageTitleUpdatesToolTip": "Ouvre la fenêtre de gestion des mises à jour du jeu", "GameListContextMenuManageDlc": "Gérer les DLC", "GameListContextMenuManageDlcToolTip": "Ouvre la fenêtre de gestion des DLC", - "GameListContextMenuOpenModsDirectory": "Ouvrir le dossier des Mods", - "GameListContextMenuOpenModsDirectoryToolTip": "Ouvre le dossier contenant les mods du jeu", "GameListContextMenuCacheManagement": "Gestion des caches", - "GameListContextMenuCacheManagementPurgePptc": "Purger le PPTC", - "GameListContextMenuCacheManagementPurgePptcToolTip": "Supprime le PPTC du jeu", - "GameListContextMenuCacheManagementPurgeShaderCache": "Purger le cache des Shaders", + "GameListContextMenuCacheManagementPurgePptc": "Reconstruction du PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Effectuer une reconstruction du PPTC au prochain démarrage du jeu", + "GameListContextMenuCacheManagementPurgeShaderCache": "Purger le cache des shaders", "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Supprime le cache des shaders du jeu", "GameListContextMenuCacheManagementOpenPptcDirectory": "Ouvrir le dossier du PPTC", "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Ouvre le dossier contenant le PPTC du jeu", @@ -72,15 +70,22 @@ "GameListContextMenuExtractDataRomFSToolTip": "Extrait la section RomFS du jeu (mise à jour incluse)", "GameListContextMenuExtractDataLogo": "Logo", "GameListContextMenuExtractDataLogoToolTip": "Extrait la section Logo du jeu (mise à jour incluse)", + "GameListContextMenuCreateShortcut": "Créer un raccourci d'application", + "GameListContextMenuCreateShortcutToolTip": "Créer un raccourci sur le bureau qui lance l'application sélectionnée", + "GameListContextMenuCreateShortcutToolTipMacOS": "Créer un raccourci dans le dossier Applications de macOS qui lance l'application sélectionnée", + "GameListContextMenuOpenModsDirectory": "Ouvrir le dossier des mods", + "GameListContextMenuOpenModsDirectoryToolTip": "Ouvre le dossier contenant les mods de l'application", + "GameListContextMenuOpenSdModsDirectory": "Ouvrir le dossier des mods Atmosphère", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Ouvre le dossier alternatif de la carte SD Atmosphère qui contient les mods de l'application. Utile pour les mods conçus pour du matériel réel.", "StatusBarGamesLoaded": "{0}/{1} Jeux chargés", "StatusBarSystemVersion": "Version du Firmware: {0}", - "LinuxVmMaxMapCountDialogTitle": "Limite basse pour les mappages de mémoire détectés", + "LinuxVmMaxMapCountDialogTitle": "Limite basse pour les mappings mémoire détectée", "LinuxVmMaxMapCountDialogTextPrimary": "Voulez-vous augmenter la valeur de vm.max_map_count à {0}", - "LinuxVmMaxMapCountDialogTextSecondary": "Certains jeux peuvent essayer de créer plus de mappages de mémoire que ce qui est actuellement autorisé. Ryujinx plantera dès que cette limite sera dépassée.", + "LinuxVmMaxMapCountDialogTextSecondary": "Certains jeux peuvent essayer de créer plus de mappings mémoire que ce qui est actuellement autorisé. Ryujinx plantera dès que cette limite sera dépassée.", "LinuxVmMaxMapCountDialogButtonUntilRestart": "Oui, jusqu'au prochain redémarrage", "LinuxVmMaxMapCountDialogButtonPersistent": "Oui, en permanence", "LinuxVmMaxMapCountWarningTextPrimary": "La quantité maximale de mappings mémoire est inférieure à la valeur recommandée.", - "LinuxVmMaxMapCountWarningTextSecondary": "La valeur actuelle de vm.max_map_count ({0}) est inférieure à {1}. Certains jeux peuvent essayer de créer plus de mappings mémoire que ceux actuellement autorisés. Ryujinx s'écrasera dès que cette limite sera dépassée.\n\nVous pouvez soit augmenter manuellement la limite, soit installer pkexec, ce qui permet à Ryujinx de l'aider.", + "LinuxVmMaxMapCountWarningTextSecondary": "La valeur actuelle de vm.max_map_count ({0}) est inférieure à {1}. Certains jeux peuvent essayer de créer plus de mappings mémoire que ceux actuellement autorisés. Ryujinx plantera dès que cette limite sera dépassée.\n\nVous pouvez soit augmenter manuellement la limite, soit installer pkexec, ce qui permet à Ryujinx de l'aider.", "Settings": "Paramètres", "SettingsTabGeneral": "Interface Utilisateur", "SettingsTabGeneralGeneral": "Général", @@ -91,9 +96,9 @@ "SettingsTabGeneralHideCursorNever": "Jamais", "SettingsTabGeneralHideCursorOnIdle": "Masquer le curseur si inactif", "SettingsTabGeneralHideCursorAlways": "Toujours", - "SettingsTabGeneralGameDirectories": "Dossiers de Jeux", + "SettingsTabGeneralGameDirectories": "Dossiers des jeux", "SettingsTabGeneralAdd": "Ajouter", - "SettingsTabGeneralRemove": "Supprimer", + "SettingsTabGeneralRemove": "Retirer", "SettingsTabSystem": "Système", "SettingsTabSystemCore": "Cœur", "SettingsTabSystemSystemRegion": "Région du système:", @@ -124,19 +129,19 @@ "SettingsTabSystemSystemLanguageTraditionalChinese": "Chinois traditionnel", "SettingsTabSystemSystemTimeZone": "Fuseau horaire du système :", "SettingsTabSystemSystemTime": "Heure du système:", - "SettingsTabSystemEnableVsync": "Activer le VSync", + "SettingsTabSystemEnableVsync": "Synchronisation verticale (VSync)", "SettingsTabSystemEnablePptc": "Activer le PPTC (Profiled Persistent Translation Cache)", "SettingsTabSystemEnableFsIntegrityChecks": "Activer la vérification de l'intégrité du système de fichiers", - "SettingsTabSystemAudioBackend": "Back-end audio", + "SettingsTabSystemAudioBackend": "Bibliothèque Audio :", "SettingsTabSystemAudioBackendDummy": "Factice", "SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemHacks": "Hacks", - "SettingsTabSystemHacksNote": " (Cela peut causer des instabilités)", + "SettingsTabSystemHacksNote": "Cela peut causer des instabilités", "SettingsTabSystemExpandDramSize": "Utiliser disposition alternative de la mémoire (développeur)", "SettingsTabSystemIgnoreMissingServices": "Ignorer les services manquants", - "SettingsTabGraphics": "Graphique", + "SettingsTabGraphics": "Graphismes", "SettingsTabGraphicsAPI": "API Graphique", "SettingsTabGraphicsEnableShaderCache": "Activer le cache des shaders", "SettingsTabGraphicsAnisotropicFiltering": "Filtrage anisotrope:", @@ -150,8 +155,8 @@ "SettingsTabGraphicsResolutionScaleNative": "Natif (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "x2 (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "x3 (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "x4 (2880p/4320p)", - "SettingsTabGraphicsAspectRatio": "Format :", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Non recommandé)", + "SettingsTabGraphicsAspectRatio": "Format d'affichage :", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", "SettingsTabGraphicsAspectRatio16x10": "16:10", @@ -159,7 +164,7 @@ "SettingsTabGraphicsAspectRatio32x9": "32:9", "SettingsTabGraphicsAspectRatioStretch": "Écran étiré", "SettingsTabGraphicsDeveloperOptions": "Options développeur", - "SettingsTabGraphicsShaderDumpPath": "Chemin du dossier de dump des shaders:", + "SettingsTabGraphicsShaderDumpPath": "Chemin du dossier de copie des shaders:", "SettingsTabLogging": "Journaux", "SettingsTabLoggingLogging": "Journaux", "SettingsTabLoggingEnableLoggingToFile": "Activer la sauvegarde des journaux vers un fichier", @@ -169,9 +174,9 @@ "SettingsTabLoggingEnableErrorLogs": "Activer les journaux d'erreurs", "SettingsTabLoggingEnableTraceLogs": "Activer journaux d'erreurs Trace", "SettingsTabLoggingEnableGuestLogs": "Activer les journaux du programme simulé", - "SettingsTabLoggingEnableFsAccessLogs": "Activer les journaux des accès au système de fichiers", - "SettingsTabLoggingFsGlobalAccessLogMode": "Niveau des journaux des accès au système de fichiers:", - "SettingsTabLoggingDeveloperOptions": "Options développeur (ATTENTION: Cela peut réduire les performances)", + "SettingsTabLoggingEnableFsAccessLogs": "Activer les journaux d'accès au système de fichiers", + "SettingsTabLoggingFsGlobalAccessLogMode": "Niveau des journaux d'accès au système de fichiers:", + "SettingsTabLoggingDeveloperOptions": "Options développeur", "SettingsTabLoggingDeveloperOptionsNote": "ATTENTION : Réduira les performances", "SettingsTabLoggingGraphicsBackendLogLevel": "Niveau du journal du backend graphique :", "SettingsTabLoggingGraphicsBackendLogLevelNone": "Aucun", @@ -200,7 +205,7 @@ "ControllerSettingsInputDevice": "Périphériques", "ControllerSettingsRefresh": "Actualiser", "ControllerSettingsDeviceDisabled": "Désactivé", - "ControllerSettingsControllerType": "Type de Controleur", + "ControllerSettingsControllerType": "Type de contrôleur", "ControllerSettingsControllerTypeHandheld": "Portable", "ControllerSettingsControllerTypeProController": "Pro Controller", "ControllerSettingsControllerTypeJoyConPair": "JoyCon Joints", @@ -218,7 +223,7 @@ "ControllerSettingsButtonY": "Y", "ControllerSettingsButtonPlus": "+", "ControllerSettingsButtonMinus": "-", - "ControllerSettingsDPad": "Croix Directionnelle", + "ControllerSettingsDPad": "Croix directionnelle", "ControllerSettingsDPadUp": "Haut", "ControllerSettingsDPadDown": "Bas", "ControllerSettingsDPadLeft": "Gauche", @@ -228,7 +233,7 @@ "ControllerSettingsStickDown": "Bas", "ControllerSettingsStickLeft": "Gauche", "ControllerSettingsStickRight": "Droite", - "ControllerSettingsStickStick": "Stick", + "ControllerSettingsStickStick": "Joystick", "ControllerSettingsStickInvertXAxis": "Inverser l'axe X", "ControllerSettingsStickInvertYAxis": "Inverser l'axe Y", "ControllerSettingsStickDeadzone": "Zone morte :", @@ -256,16 +261,16 @@ "ControllerSettingsMotionControllerSlot": "Contrôleur ID:", "ControllerSettingsMotionMirrorInput": "Inverser les contrôles", "ControllerSettingsMotionRightJoyConSlot": "JoyCon Droit ID:", - "ControllerSettingsMotionServerHost": "Addresse du Server:", + "ControllerSettingsMotionServerHost": "Serveur d'hébergement :", "ControllerSettingsMotionGyroSensitivity": "Sensibilitée du gyroscope:", "ControllerSettingsMotionGyroDeadzone": "Zone morte du gyroscope:", "ControllerSettingsSave": "Enregistrer", "ControllerSettingsClose": "Fermer", - "UserProfilesSelectedUserProfile": "Choisir un profil utilisateur:", + "UserProfilesSelectedUserProfile": "Profil utilisateur sélectionné :", "UserProfilesSaveProfileName": "Enregistrer le nom du profil", "UserProfilesChangeProfileImage": "Changer l'image du profil", - "UserProfilesAvailableUserProfiles": "Profils utilisateurs disponible:", - "UserProfilesAddNewProfile": "Ajouter un nouveau profil", + "UserProfilesAvailableUserProfiles": "Profils utilisateurs disponibles:", + "UserProfilesAddNewProfile": "Créer un profil", "UserProfilesDelete": "Supprimer", "UserProfilesClose": "Fermer", "ProfileNameSelectionWatermark": "Choisir un pseudo", @@ -280,25 +285,21 @@ "InputDialogAddNewProfileTitle": "Choisir un nom de profil", "InputDialogAddNewProfileHeader": "Merci d'entrer un nom de profil", "InputDialogAddNewProfileSubtext": "(Longueur max.: {0})", - "AvatarChoose": "Choisir", + "AvatarChoose": "Choisir un avatar", "AvatarSetBackgroundColor": "Choisir une couleur de fond", "AvatarClose": "Fermer", "ControllerSettingsLoadProfileToolTip": "Charger un profil", "ControllerSettingsAddProfileToolTip": "Ajouter un profil", "ControllerSettingsRemoveProfileToolTip": "Supprimer un profil", "ControllerSettingsSaveProfileToolTip": "Enregistrer un profil", - "MenuBarFileToolsTakeScreenshot": "Prendre une Capture d'Écran", + "MenuBarFileToolsTakeScreenshot": "Prendre une capture d'écran", "MenuBarFileToolsHideUi": "Masquer l'interface utilisateur", "GameListContextMenuRunApplication": "Démarrer l'application", "GameListContextMenuToggleFavorite": "Ajouter/Retirer des favoris", "GameListContextMenuToggleFavoriteToolTip": "Activer/désactiver le statut favori du jeu", - "SettingsTabGeneralTheme": "Thème", - "SettingsTabGeneralThemeCustomTheme": "Chemin du thème personnalisé", - "SettingsTabGeneralThemeBaseStyle": "Style par défaut", - "SettingsTabGeneralThemeBaseStyleDark": "Sombre", - "SettingsTabGeneralThemeBaseStyleLight": "Lumière", - "SettingsTabGeneralThemeEnableCustomTheme": "Activer un Thème Personnalisé", - "ButtonBrowse": "Parcourir", + "SettingsTabGeneralTheme": "Thème :", + "SettingsTabGeneralThemeDark": "Sombre", + "SettingsTabGeneralThemeLight": "Clair", "ControllerSettingsConfigureGeneral": "Configurer", "ControllerSettingsRumble": "Vibreur", "ControllerSettingsRumbleStrongMultiplier": "Multiplicateur de vibrations fortes", @@ -312,7 +313,7 @@ "DialogExitTitle": "Ryujinx - Quitter", "DialogErrorMessage": "Ryujinx a rencontré une erreur", "DialogExitMessage": "Êtes-vous sûr de vouloir fermer Ryujinx ?", - "DialogExitSubMessage": "Toute progression non sauvegardée sera perdue.", + "DialogExitSubMessage": "Toutes les données non enregistrées seront perdues !", "DialogMessageCreateSaveErrorMessage": "Une erreur s'est produite lors de la création de la sauvegarde spécifiée : {0}", "DialogMessageFindSaveErrorMessage": "Une erreur s'est produite lors de la recherche de la sauvegarde spécifiée : {0}", "FolderDialogExtractTitle": "Choisissez le dossier dans lequel extraire", @@ -322,8 +323,8 @@ "DialogNcaExtractionCheckLogErrorMessage": "Échec de l'extraction. Lisez le fichier journal pour plus d'informations.", "DialogNcaExtractionSuccessMessage": "Extraction terminée avec succès.", "DialogUpdaterConvertFailedMessage": "Échec de la conversion de la version actuelle de Ryujinx.", - "DialogUpdaterCancelUpdateMessage": "Annuler la mise à jour!", - "DialogUpdaterAlreadyOnLatestVersionMessage": "Vous utilisez déjà la version la plus mise à jour de Ryujinx!", + "DialogUpdaterCancelUpdateMessage": "Annuler la mise à jour !", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Vous utilisez déjà la version la plus récente de Ryujinx !", "DialogUpdaterFailedToGetVersionMessage": "Une erreur s'est produite lors de la tentative d'obtention des informations de publication de la version GitHub. Cela peut survenir lorsqu'une nouvelle version est en cours de compilation par GitHub Actions. Réessayez dans quelques minutes.", "DialogUpdaterConvertFailedGithubMessage": "Impossible de convertir la version reçue de Ryujinx depuis Github Release.", "DialogUpdaterDownloadingMessage": "Téléchargement de la mise à jour...", @@ -332,13 +333,11 @@ "DialogUpdaterAddingFilesMessage": "Ajout d'une nouvelle mise à jour...", "DialogUpdaterCompleteMessage": "Mise à jour terminée !", "DialogUpdaterRestartMessage": "Voulez-vous redémarrer Ryujinx maintenant ?", - "DialogUpdaterArchNotSupportedMessage": "Vous n'utilisez pas d'architecture système prise en charge !", - "DialogUpdaterArchNotSupportedSubMessage": "(Seuls les systèmes x64 sont pris en charge !)", "DialogUpdaterNoInternetMessage": "Vous n'êtes pas connecté à Internet !", - "DialogUpdaterNoInternetSubMessage": "Veuillez vérifier que vous avez une connexion Internet fonctionnelle!", - "DialogUpdaterDirtyBuildMessage": "Vous ne pouvez pas mettre à jour une version Dirty de Ryujinx!", + "DialogUpdaterNoInternetSubMessage": "Veuillez vérifier que vous disposez d'une connexion Internet fonctionnelle !", + "DialogUpdaterDirtyBuildMessage": "Vous ne pouvez pas mettre à jour une version Dirty de Ryujinx !", "DialogUpdaterDirtyBuildSubMessage": "Veuillez télécharger Ryujinx sur https://ryujinx.org/ si vous recherchez une version prise en charge.", - "DialogRestartRequiredMessage": "Redémarrage Requis", + "DialogRestartRequiredMessage": "Redémarrage requis", "DialogThemeRestartMessage": "Le thème a été enregistré. Un redémarrage est requis pour appliquer le thème.", "DialogThemeRestartSubMessage": "Voulez-vous redémarrer", "DialogFirmwareInstallEmbeddedMessage": "Voulez-vous installer le firmware intégré dans ce jeu ? (Firmware {0})", @@ -384,8 +383,11 @@ "DialogUserProfileUnsavedChangesMessage": "Vous avez effectué des modifications sur ce profil d'utilisateur qui n'ont pas été enregistrées.", "DialogUserProfileUnsavedChangesSubMessage": "Voulez-vous annuler les modifications ?", "DialogControllerSettingsModifiedConfirmMessage": "Les paramètres actuels du contrôleur ont été mis à jour.", - "DialogControllerSettingsModifiedConfirmSubMessage": "Voulez-vous sauvegarder?", - "DialogLoadNcaErrorMessage": "{0}. Fichier erroné : {1}", + "DialogControllerSettingsModifiedConfirmSubMessage": "Voulez-vous sauvegarder ?", + "DialogLoadFileErrorMessage": "{0}. Fichier erroné : {1}", + "DialogModAlreadyExistsMessage": "Le mod existe déjà", + "DialogModInvalidMessage": "Le répertoire spécifié ne contient pas de mod !", + "DialogModDeleteNoParentMessage": "Impossible de supprimer : impossible de trouver le répertoire parent pour le mod \"{0} \" !", "DialogDlcNoDlcErrorMessage": "Le fichier spécifié ne contient pas de DLC pour le titre sélectionné !", "DialogPerformanceCheckLoggingEnabledMessage": "Vous avez activé la journalisation des traces, conçue pour être utilisée uniquement par les développeurs.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Pour des performances optimales, il est recommandé de désactiver la journalisation des traces. Souhaitez-vous désactiver la journalisation des traces maintenant ?", @@ -396,6 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "Le fichier spécifié ne contient pas de mise à jour pour le titre sélectionné !", "DialogSettingsBackendThreadingWarningTitle": "Avertissement - Backend Threading ", "DialogSettingsBackendThreadingWarningMessage": "Ryujinx doit être redémarré après avoir changé cette option pour qu'elle s'applique complètement. Selon votre plate-forme, vous devrez peut-être désactiver manuellement le multithreading de votre pilote lorsque vous utilisez Ryujinx.", + "DialogModManagerDeletionWarningMessage": "Vous êtes sur le point de supprimer le mod : {0}\n\nÊtes-vous sûr de vouloir continuer ?", + "DialogModManagerDeletionAllWarningMessage": "Vous êtes sur le point de supprimer tous les mods pour ce titre.\n\nÊtes-vous sûr de vouloir continuer ?", "SettingsTabGraphicsFeaturesOptions": "Fonctionnalités", "SettingsTabGraphicsBackendMultithreading": "Interface graphique multithread", "CommonAuto": "Auto", @@ -413,7 +417,7 @@ "AboutGithubUrlTooltipMessage": "Cliquez pour ouvrir la page GitHub de Ryujinx dans votre navigateur par défaut.", "AboutDiscordUrlTooltipMessage": "Cliquez pour ouvrir une invitation au serveur Discord de Ryujinx dans votre navigateur par défaut.", "AboutTwitterUrlTooltipMessage": "Cliquez pour ouvrir la page Twitter de Ryujinx dans votre navigateur par défaut.", - "AboutRyujinxAboutTitle": "Á propos:", + "AboutRyujinxAboutTitle": "À propos :", "AboutRyujinxAboutContent": "Ryujinx est un émulateur pour la Nintendo Switch™.\nMerci de nous soutenir sur Patreon.\nObtenez toutes les dernières actualités sur notre Twitter ou notre Discord.\nLes développeurs intéressés à contribuer peuvent en savoir plus sur notre GitHub ou notre Discord.", "AboutRyujinxMaintainersTitle": "Maintenu par :", "AboutRyujinxMaintainersContentTooltipMessage": "Cliquez pour ouvrir la page Contributeurs dans votre navigateur par défaut.", @@ -428,9 +432,10 @@ "DlcManagerTableHeadingContainerPathLabel": "Chemin du conteneur", "DlcManagerTableHeadingFullPathLabel": "Chemin complet", "DlcManagerRemoveAllButton": "Tout supprimer", - "DlcManagerEnableAllButton": "Activer Tout", - "DlcManagerDisableAllButton": "Désactiver Tout", - "MenuBarOptionsChangeLanguage": "Changer la Langue", + "DlcManagerEnableAllButton": "Tout activer", + "DlcManagerDisableAllButton": "Tout désactiver", + "ModManagerDeleteAllButton": "Tout supprimer", + "MenuBarOptionsChangeLanguage": "Changer la langue", "MenuBarShowFileTypes": "Afficher les types de fichiers", "CommonSort": "Trier", "CommonShowNames": "Afficher les noms", @@ -442,18 +447,18 @@ "ToggleDiscordTooltip": "Choisissez d'afficher ou non Ryujinx sur votre activité « en cours de jeu » Discord", "AddGameDirBoxTooltip": "Entrez un répertoire de jeux à ajouter à la liste", "AddGameDirTooltip": "Ajouter un répertoire de jeux à la liste", - "RemoveGameDirTooltip": "Supprimer le dossier sélectionné", + "RemoveGameDirTooltip": "Supprimer le répertoire de jeu sélectionné", "CustomThemeCheckTooltip": "Utilisez un thème personnalisé Avalonia pour modifier l'apparence des menus de l'émulateur", "CustomThemePathTooltip": "Chemin vers le thème personnalisé de l'interface utilisateur", "CustomThemeBrowseTooltip": "Parcourir vers un thème personnalisé pour l'interface utilisateur", "DockModeToggleTooltip": "Le mode station d'accueil permet à la console émulée de se comporter comme une Nintendo Switch en mode station d'accueil, ce qui améliore la fidélité graphique dans la plupart des jeux. Inversement, la désactivation de cette option rendra la console émulée comme une console Nintendo Switch portable, réduisant la qualité graphique.\n\nConfigurer les controles du joueur 1 si vous prévoyez d'utiliser le mode station d'accueil; configurez les commandes portable si vous prévoyez d'utiliser le mode portable.\n\nLaissez ACTIVER si vous n'êtes pas sûr.", - "DirectKeyboardTooltip": "Prise en charge de l'accès direct au clavier (HID). Fournit aux jeux l'accès à votre clavier en tant que périphérique de saisie de texte.", - "DirectMouseTooltip": "Prise en charge de l'accès à la souris (HID). Permet aux jeux d'accéder a votre souris en tant que périphérique de pointage.", + "DirectKeyboardTooltip": "Prise en charge de l'accès direct au clavier (HID). Permet aux jeux d'accéder à votre clavier comme périphérique de saisie de texte.\n\nFonctionne uniquement avec les jeux prenant en charge nativement l'utilisation du clavier sur le matériel Switch.\n\nLaissez OFF si vous n'êtes pas sûr.", + "DirectMouseTooltip": "Prise en charge de l'accès direct à la souris (HID). Permet aux jeux d'accéder à votre souris en tant que dispositif de pointage.\n\nFonctionne uniquement avec les jeux qui prennent en charge nativement les contrôles de souris sur le matériel Switch, ce qui est rare.\n\nLorsqu'il est activé, la fonctionnalité de l'écran tactile peut ne pas fonctionner.\n\nLaissez sur OFF si vous n'êtes pas sûr.", "RegionTooltip": "Changer la région du système", "LanguageTooltip": "Changer la langue du système", "TimezoneTooltip": "Changer le fuseau horaire du système", "TimeTooltip": "Changer l'heure du système", - "VSyncToggleTooltip": "Synchronisation verticale de l'émulateur. Généralement un limiteur d'FPS pour la majorité des jeux, le désactiver peut accélérer les jeux pour rendre les écrans de chargement plus court, mais augemente le risque de crash lors des chargements.\n\nPeut être activer ou desactiver en jeu avec un raccourci de votre choix. Nous vous recommandons de le laisser.\n\nLaissez activer, si vous n'êtes pas sûr.", + "VSyncToggleTooltip": "La synchronisation verticale de la console émulée. Essentiellement un limiteur de trame pour la majorité des jeux ; le désactiver peut entraîner un fonctionnement plus rapide des jeux ou prolonger ou bloquer les écrans de chargement.\n\nPeut être activé ou désactivé en jeu avec un raccourci clavier de votre choix (F1 par défaut). Nous recommandons de le faire si vous envisagez de le désactiver.\n\nLaissez activé si vous n'êtes pas sûr.", "PptcToggleTooltip": "Sauvegarde les fonctions JIT afin qu'elles n'aient pas besoin d'être à chaque fois recompiler lorsque le jeu se charge.\n\nRéduit les lags et accélère considérablement le temps de chargement après le premier lancement d'un jeu.\n\nLaissez par défaut si vous n'êtes pas sûr.", "FsIntegrityToggleTooltip": "Vérifie si des fichiers sont corrompus lors du lancement d'un jeu, et si des fichiers corrompus sont détectés, affiche une erreur de hachage dans la console.\n\nN'a aucun impact sur les performances et est destiné à aider le dépannage.\n\nLaissez activer en cas d'incertitude.", "AudioBackendTooltip": "Modifie le backend utilisé pour donnée un rendu audio.\n\nSDL2 est préféré, tandis que OpenAL et SoundIO sont utilisés comme backend secondaire. Le backend Dummy (Factice) ne rends aucun son.\n\nLaissez sur SDL2 si vous n'êtes pas sûr.", @@ -467,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "Exécute des commandes du backend graphiques sur un second thread.\n\nAccélère la compilation des shaders, réduit les crashs et les lags, améliore les performances sur les pilotes GPU sans support de multithreading. Légère augementation des performances sur les pilotes avec multithreading intégrer.\n\nRéglez sur Auto en cas d'incertitude.", "GalThreadingTooltip": "Exécute des commandes du backend graphiques sur un second thread.\n\nAccélère la compilation des shaders, réduit les crashs et les lags, améliore les performances sur les pilotes GPU sans support de multithreading. Légère augementation des performances sur les pilotes avec multithreading intégrer.\n\nRéglez sur Auto en cas d'incertitude.", "ShaderCacheToggleTooltip": "Enregistre un cache de shaders sur le disque dur, réduit le lag lors de multiples exécutions.\n\nLaissez Activer si vous n'êtes pas sûr.", - "ResolutionScaleTooltip": "Échelle de résolution appliquer au rendu du jeu", + "ResolutionScaleTooltip": "Multiplie la résolution de rendu du jeu.\n\nQuelques jeux peuvent ne pas fonctionner avec cette fonctionnalité et sembler pixelisés même lorsque la résolution est augmentée ; pour ces jeux, vous devrez peut-être trouver des mods qui suppriment l'anti-aliasing ou qui augmentent leur résolution de rendu interne. Pour utiliser cette dernière option, vous voudrez probablement sélectionner \"Native\".\n\nCette option peut être modifiée pendant qu'un jeu est en cours d'exécution en cliquant sur \"Appliquer\" ci-dessous ; vous pouvez simplement déplacer la fenêtre des paramètres de côté et expérimenter jusqu'à ce que vous trouviez l'apparence souhaitée pour un jeu.\n\nGardez à l'esprit que 4x est excessif pour pratiquement n'importe quelle configuration.", "ResolutionScaleEntryTooltip": "Échelle de résolution à virgule flottante, telle que : 1.5. Les échelles non intégrales sont plus susceptibles de causer des problèmes ou des crashs.", - "AnisotropyTooltip": "Niveau de Filtrage Anisotropique (mettre sur Auto pour utiliser la valeur demandée par le jeu)", - "AspectRatioTooltip": "Ratio d'aspect appliqué à la fenêtre de rendu", + "AnisotropyTooltip": "Niveau de filtrage anisotrope. Réglez sur Auto pour utiliser la valeur demandée par le jeu.", + "AspectRatioTooltip": "Rapport d'aspect appliqué à la fenêtre du moteur de rendu.\n\nChangez cela uniquement si vous utilisez un mod de rapport d'aspect pour votre jeu, sinon les graphismes seront étirés.\n\nLaissez sur 16:9 si vous n'êtes pas sûr.", "ShaderDumpPathTooltip": "Chemin de copie des Shaders Graphiques", "FileLogTooltip": "Sauver le journal de la console dans un fichier journal sur le disque. Cela n'affecte pas les performances.", "StubLogTooltip": "Affiche les messages de log dans la console. N'affecte pas les performances.", @@ -504,7 +509,9 @@ "EnableInternetAccessTooltip": "Permet à l'application émulée de se connecter à Internet.\n\nLes jeux avec un mode LAN peuvent se connecter les uns aux autres lorsque cette option est cochée et que les systèmes sont connectés au même point d'accès. Cela inclut également les vrais consoles.\n\nCette option n'autorise PAS la connexion aux serveurs Nintendo. Elle peut faire planter certains jeux qui essaient de se connecter à l'Internet.\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.", "GameListContextMenuManageCheatToolTip": "Gérer la triche", "GameListContextMenuManageCheat": "Gérer la triche", - "ControllerSettingsStickRange": "Intervalle:", + "GameListContextMenuManageModToolTip": "Gérer les mods", + "GameListContextMenuManageMod": "Gérer les mods", + "ControllerSettingsStickRange": "Intervalle :", "DialogStopEmulationTitle": "Ryujinx - Arrêt de l'émulation", "DialogStopEmulationMessage": "Êtes-vous sûr de vouloir arrêter l'émulation ?", "SettingsTabCpu": "CPU", @@ -515,9 +522,7 @@ "SettingsTabCpuMemory": "Mémoire CPU", "DialogUpdaterFlatpakNotSupportedMessage": "Merci de mettre à jour Ryujinx via FlatHub.", "UpdaterDisabledWarningTitle": "Mise à jour désactivée !", - "GameListContextMenuOpenSdModsDirectory": "Ouvrir le dossier Mods d'Atmosphère", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Ouvre le répertoire alternatif de carte SD Atmosphère qui contient les Mods d'Application. Utile pour les mods qui sont conçu pour le vrai matériel.", - "ControllerSettingsRotate90": "Rotation 90° horaire", + "ControllerSettingsRotate90": "Faire pivoter de 90° à droite", "IconSize": "Taille d'icône", "IconSizeTooltip": "Changer la taille des icônes de jeu", "MenuBarOptionsShowConsole": "Afficher la console", @@ -532,38 +537,39 @@ "UserErrorNoFirmwareDescription": "Ryujinx n'a pas trouvé de firmwares installés", "UserErrorFirmwareParsingFailedDescription": "Ryujinx n'a pas pu analyser le firmware fourni. Cela est généralement dû à des clés obsolètes.", "UserErrorApplicationNotFoundDescription": "Ryujinx n'a pas pu trouver une application valide dans le chemin indiqué.", - "UserErrorUnknownDescription": "Une erreur inconnue est survenue!", + "UserErrorUnknownDescription": "Une erreur inconnue est survenue !", "UserErrorUndefinedDescription": "Une erreur inconnue est survenue ! Cela ne devrait pas se produire, merci de contacter un développeur !", "OpenSetupGuideMessage": "Ouvrir le guide d'installation", "NoUpdate": "Aucune mise à jour", - "TitleUpdateVersionLabel": "Version {0} - {1}", + "TitleUpdateVersionLabel": "Version {0}", "RyujinxInfo": "Ryujinx - Info", "RyujinxConfirm": "Ryujinx - Confirmation", "FileDialogAllTypes": "Tous les types", "Never": "Jamais", "SwkbdMinCharacters": "Doit comporter au moins {0} caractères", - "SwkbdMinRangeCharacters": "Doit contenir {0}-{1} caractères en longueur", + "SwkbdMinRangeCharacters": "Doit comporter entre {0} et {1} caractères", "SoftwareKeyboard": "Clavier logiciel", - "SoftwareKeyboardModeNumbersOnly": "Doit être uniquement des chiffres", + "SoftwareKeyboardModeNumeric": "Doit être 0-9 ou '.' uniquement", "SoftwareKeyboardModeAlphabet": "Doit être uniquement des caractères non CJK", "SoftwareKeyboardModeASCII": "Doit être uniquement du texte ASCII", - "DialogControllerAppletMessagePlayerRange": "L'application demande {0} joueur(s) avec :\n\nTYPES : {1}\n\nJOUEURS : {2}\n\n{3}Merci d'ouvrir les Paramètres et de reconfigurer les Périphériques maintenant ou appuyez sur Fermer.", - "DialogControllerAppletMessage": "L'application demande exactement {0} joueur(s) avec :\n\nTYPES : {1}\n\nJOUEURS : {2}\n\n{3}Merci d'ouvrir les Paramètres et de reconfigurer les Périphériques maintenant ou appuyez sur Fermer.", - "DialogControllerAppletDockModeSet": "Mode station d'accueil défini. Le portable est également invalide.\n\n", + "ControllerAppletControllers": "Contrôleurs pris en charge :", + "ControllerAppletPlayers": "Joueurs :", + "ControllerAppletDescription": "Votre configuration actuelle n'est pas valide. Ouvrez les paramètres et reconfigurez vos contrôles.", + "ControllerAppletDocked": "Mode station d'accueil défini. Le mode contrôle portable doit être désactivé.", "UpdaterRenaming": "Renommage des anciens fichiers...", "UpdaterRenameFailed": "Impossible de renommer le fichier : {0}", "UpdaterAddingFiles": "Ajout des nouveaux fichiers...", "UpdaterExtracting": "Extraction de la mise à jour…", "UpdaterDownloading": "Téléchargement de la mise à jour...", "Game": "Jeu", - "Docked": "Attaché", - "Handheld": "Portable", + "Docked": "Mode station d'accueil", + "Handheld": "Mode Portable", "ConnectionError": "Erreur de connexion.", "AboutPageDeveloperListMore": "{0} et plus...", "ApiError": "Erreur API.", "LoadingHeading": "Chargement {0}", "CompilingPPTC": "Compilation PTC", - "CompilingShaders": "Compilation des Shaders", + "CompilingShaders": "Compilation des shaders", "AllKeyboards": "Tous les claviers", "OpenFileDialogTitle": "Sélectionnez un fichier supporté à ouvrir", "OpenFolderDialogTitle": "Sélectionnez un dossier avec un jeu décompressé", @@ -572,13 +578,13 @@ "SettingsTabHotkeys": "Raccourcis clavier", "SettingsTabHotkeysHotkeys": "Raccourcis clavier", "SettingsTabHotkeysToggleVsyncHotkey": "Activer/désactiver la VSync :", - "SettingsTabHotkeysScreenshotHotkey": "Captures d'écran :", + "SettingsTabHotkeysScreenshotHotkey": "Capture d'écran :", "SettingsTabHotkeysShowUiHotkey": "Afficher UI :", "SettingsTabHotkeysPauseHotkey": "Suspendre :", "SettingsTabHotkeysToggleMuteHotkey": "Muet : ", "ControllerMotionTitle": "Réglages du contrôle par mouvement", - "ControllerRumbleTitle": "Paramètres de Vibration", - "SettingsSelectThemeFileDialogTitle": "Sélectionnez un Fichier de Thème", + "ControllerRumbleTitle": "Paramètres de vibration", + "SettingsSelectThemeFileDialogTitle": "Sélectionner un fichier de thème", "SettingsXamlThemeFile": "Fichier thème Xaml", "AvatarWindowTitle": "Gérer les Comptes - Avatar", "Amiibo": "Amiibo", @@ -587,46 +593,49 @@ "Writable": "Ecriture possible", "SelectDlcDialogTitle": "Sélectionner les fichiers DLC", "SelectUpdateDialogTitle": "Sélectionner les fichiers de mise à jour", + "SelectModDialogTitle": "Sélectionner le répertoire du mod", "UserProfileWindowTitle": "Gestionnaire de profils utilisateur", "CheatWindowTitle": "Gestionnaire de triches", - "DlcWindowTitle": "Gestionnaire de contenus téléchargeables", + "DlcWindowTitle": "Gérer le contenu téléchargeable pour {0} ({1})", "UpdateWindowTitle": "Gestionnaire de mises à jour", "CheatWindowHeading": "Cheats disponibles pour {0} [{1}]", "BuildId": "BuildId:", - "DlcWindowHeading": "{0} Contenu(s) téléchargeable(s) disponible pour {1} ({2})", + "DlcWindowHeading": "{0} Contenu(s) téléchargeable(s)", + "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "Éditer la sélection", "Cancel": "Annuler", "Save": "Enregistrer", "Discard": "Abandonner", - "UserProfilesSetProfileImage": "Modifier l'image du profil", + "Paused": "Suspendu", + "UserProfilesSetProfileImage": "Définir l'image de profil", "UserProfileEmptyNameError": "Le nom est requis", "UserProfileNoImageError": "L'image du profil doit être définie", - "GameUpdateWindowHeading": "{0} mise(s) à jour disponible pour {1} ({2})", - "SettingsTabHotkeysResScaleUpHotkey": "Augmenter la résolution:", + "GameUpdateWindowHeading": "Gérer les mises à jour pour {0} ({1})", + "SettingsTabHotkeysResScaleUpHotkey": "Augmenter la résolution :", "SettingsTabHotkeysResScaleDownHotkey": "Diminuer la résolution :", "UserProfilesName": "Nom :", "UserProfilesUserId": "Identifiant de l'utilisateur :", "SettingsTabGraphicsBackend": "API de Rendu", - "SettingsTabGraphicsBackendTooltip": "Interface Graphique à utiliser", + "SettingsTabGraphicsBackendTooltip": "Sélectionnez le moteur graphique qui sera utilisé dans l'émulateur.\n\nVulkan est globalement meilleur pour toutes les cartes graphiques modernes, tant que leurs pilotes sont à jour. Vulkan offre également une compilation de shaders plus rapide (moins de saccades) sur tous les fournisseurs de GPU.\n\nOpenGL peut obtenir de meilleurs résultats sur d'anciennes cartes graphiques Nvidia, sur d'anciennes cartes graphiques AMD sous Linux, ou sur des GPU avec moins de VRAM, bien que les saccades dues à la compilation des shaders soient plus importantes.\n\nRéglez sur Vulkan si vous n'êtes pas sûr. Réglez sur OpenGL si votre GPU ne prend pas en charge Vulkan même avec les derniers pilotes graphiques.", "SettingsEnableTextureRecompression": "Activer la recompression des textures", - "SettingsEnableTextureRecompressionTooltip": "Compresse certaines textures afin de réduire l'utilisation de la VRAM.\n\nRecommandé pour une utilisation avec des GPU qui ont moins de 4 Go de VRAM.\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.", + "SettingsEnableTextureRecompressionTooltip": "Les jeux utilisant ce format de texture incluent Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder et The Legend of Zelda: Tears of the Kingdom.\n\nLes cartes graphiques avec 4 Go ou moins de VRAM risquent probablement de planter à un moment donné lors de l'exécution de ces jeux.\n\nActivez uniquement si vous manquez de VRAM sur les jeux mentionnés ci-dessus. Laissez DÉSACTIVÉ si vous n'êtes pas sûr.", "SettingsTabGraphicsPreferredGpu": "GPU préféré", "SettingsTabGraphicsPreferredGpuTooltip": "Sélectionnez la carte graphique qui sera utilisée avec l'interface graphique Vulkan.\n\nCela ne change pas le GPU qu'OpenGL utilisera.\n\nChoisissez le GPU noté \"dGPU\" si vous n'êtes pas sûr. S'il n'y en a pas, ne pas modifier.", "SettingsAppRequiredRestartMessage": "Redémarrage de Ryujinx requis", "SettingsGpuBackendRestartMessage": "Les paramètres de l'interface graphique ou du GPU ont été modifiés. Cela nécessitera un redémarrage pour être appliqué", - "SettingsGpuBackendRestartSubMessage": "\n\nVoulez-vous redémarrer maintenant?", + "SettingsGpuBackendRestartSubMessage": "\n\nVoulez-vous redémarrer maintenant ?", "RyujinxUpdaterMessage": "Voulez-vous mettre à jour Ryujinx vers la dernière version ?", "SettingsTabHotkeysVolumeUpHotkey": "Augmenter le volume :", "SettingsTabHotkeysVolumeDownHotkey": "Diminuer le volume :", "SettingsEnableMacroHLE": "Activer les macros HLE", - "SettingsEnableMacroHLETooltip": "Émulation de haut niveau du code de Macro GPU.\n\nAméliore les performances, mais peut causer des bugs graphiques dans certains jeux.\n\nLaissez ACTIVER si vous n'êtes pas sûr.", + "SettingsEnableMacroHLETooltip": "Émulation de haut niveau du code de Macro GPU.\n\nAméliore les performances, mais peut causer des artefacts graphiques dans certains jeux.\n\nLaissez ACTIVER si vous n'êtes pas sûr.", "SettingsEnableColorSpacePassthrough": "Traversée de l'espace colorimétrique", - "SettingsEnableColorSpacePassthroughTooltip": "Dirige l'interface graphique Vulkan pour qu'il transmette les informations de couleur sans spécifier d'espace colorimétrique. Pour les utilisateurs possédant des écrans haut de gamme, cela peut entraîner des couleurs plus vives, au détriment de l'exactitude des couleurs.", + "SettingsEnableColorSpacePassthroughTooltip": "Dirige l'interface graphique Vulkan pour qu'il transmette les informations de couleur sans spécifier d'espace colorimétrique. Pour les utilisateurs possédant des écrans Wide Color Gamut, cela peut entraîner des couleurs plus vives, au détriment de l'exactitude des couleurs.", "VolumeShort": "Vol", "UserProfilesManageSaves": "Gérer les sauvegardes", - "DeleteUserSave": "Voulez-vous supprimer la sauvegarde de l'utilisateur pour ce jeu?", + "DeleteUserSave": "Voulez-vous supprimer la sauvegarde de l'utilisateur pour ce jeu ?", "IrreversibleActionNote": "Cette action n'est pas réversible.", - "SaveManagerHeading": "Gérer les sauvegardes pour {0}", + "SaveManagerHeading": "Gérer les sauvegardes pour {0} ({1})", "SaveManagerTitle": "Gestionnaire de sauvegarde", "Name": "Nom ", "Size": "Taille", @@ -635,12 +644,12 @@ "Recover": "Récupérer", "UserProfilesRecoverHeading": "Des sauvegardes ont été trouvées pour les comptes suivants", "UserProfilesRecoverEmptyList": "Aucun profil à restaurer", - "GraphicsAATooltip": "Applique l'anticrénelage au rendu du jeu", + "GraphicsAATooltip": "FXAA floute la plupart de l'image, tandis que SMAA tente de détecter les contours dentelés et de les lisser.\n\nIl n'est pas recommandé de l'utiliser en conjonction avec le filtre de mise à l'échelle FSR.\n\nCette option peut être modifiée pendant qu'un jeu est en cours d'exécution en cliquant sur \"Appliquer\" ci-dessous ; vous pouvez simplement déplacer la fenêtre des paramètres de côté et expérimenter jusqu'à ce que vous trouviez l'apparence souhaitée pour un jeu.\n\nLaissez sur NONE si vous n'êtes pas sûr.", "GraphicsAALabel": "Anticrénelage :", "GraphicsScalingFilterLabel": "Filtre de mise à l'échelle :", - "GraphicsScalingFilterTooltip": "Active la mise à l'échelle du tampon d'image", + "GraphicsScalingFilterTooltip": "Choisissez le filtre de mise à l'échelle qui sera appliqué lors de l'utilisation de la mise à l'échelle de la résolution.\n\nLe filtre bilinéaire fonctionne bien pour les jeux en 3D et constitue une option par défaut sûre.\n\nLe filtre le plus proche est recommandé pour les jeux de pixel art.\n\nFSR 1.0 est simplement un filtre de netteté, non recommandé pour une utilisation avec FXAA ou SMAA.\n\nCette option peut être modifiée pendant qu'un jeu est en cours d'exécution en cliquant sur \"Appliquer\" ci-dessous ; vous pouvez simplement déplacer la fenêtre des paramètres de côté et expérimenter jusqu'à ce que vous trouviez l'aspect souhaité pour un jeu.\n\nLaissez sur BILINEAR si vous n'êtes pas sûr.", "GraphicsScalingFilterLevelLabel": "Niveau ", - "GraphicsScalingFilterLevelTooltip": "Définir le niveau du filtre de mise à l'échelle", + "GraphicsScalingFilterLevelTooltip": "Définissez le niveau de netteté FSR 1.0. Plus élevé signifie plus net.", "SmaaLow": "SMAA Faible", "SmaaMedium": "SMAA moyen", "SmaaHigh": "SMAA Élevé", @@ -648,9 +657,12 @@ "UserEditorTitle": "Modifier Utilisateur", "UserEditorTitleCreate": "Créer Utilisateur", "SettingsTabNetworkInterface": "Interface Réseau :", - "NetworkInterfaceTooltip": "Interface réseau pour les fonctionnalités LAN", + "NetworkInterfaceTooltip": "L'interface réseau utilisée pour les fonctionnalités LAN/LDN.\n\nEn conjonction avec un VPN ou XLink Kai et un jeu prenant en charge le LAN, peut être utilisée pour simuler une connexion sur le même réseau via Internet.\n\nLaissez sur DEFAULT si vous n'êtes pas sûr.", "NetworkInterfaceDefault": "Par défaut", "PackagingShaders": "Empaquetage des Shaders", "AboutChangelogButton": "Voir le Changelog sur GitHub", - "AboutChangelogButtonTooltipMessage": "Cliquez pour ouvrir le changelog de cette version dans votre navigateur par défaut." -} \ No newline at end of file + "AboutChangelogButtonTooltipMessage": "Cliquez pour ouvrir le changelog de cette version dans votre navigateur par défaut.", + "SettingsTabNetworkMultiplayer": "Multijoueur", + "MultiplayerMode": "Mode :", + "MultiplayerModeTooltip": "Changer le mode multijoueur LDN.\n\nLdnMitm modifiera la fonctionnalité de jeu sans fil local/jeu local dans les jeux pour fonctionner comme s'il s'agissait d'un LAN, permettant des connexions locales sur le même réseau avec d'autres instances de Ryujinx et des consoles Nintendo Switch piratées ayant le module ldn_mitm installé.\n\nLe multijoueur nécessite que tous les joueurs soient sur la même version du jeu (par exemple, Super Smash Bros. Ultimate v13.0.1 ne peut pas se connecter à v13.0.0).\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr." +} diff --git a/src/Ryujinx/Assets/Locales/he_IL.json b/src/Ryujinx/Assets/Locales/he_IL.json index e5caf445a..5ba56c269 100644 --- a/src/Ryujinx/Assets/Locales/he_IL.json +++ b/src/Ryujinx/Assets/Locales/he_IL.json @@ -1,5 +1,5 @@ { - "Language": "אנגלית (ארה\"ב)", + "Language": "עִברִית", "MenuBarFileOpenApplet": "פתח יישומון", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "פתח את יישומון עורך ה- Mii במצב עצמאי", "SettingsTabInputDirectMouseAccess": "גישה ישירה לעכבר", @@ -14,7 +14,7 @@ "MenuBarFileOpenEmuFolder": "פתח את תיקיית ריוג'ינקס", "MenuBarFileOpenLogsFolder": "פתח את תיקיית קבצי הלוג", "MenuBarFileExit": "_יציאה", - "MenuBarOptions": "הגדרות", + "MenuBarOptions": "_אפשרויות", "MenuBarOptionsToggleFullscreen": "שנה מצב- מסך מלא", "MenuBarOptionsStartGamesInFullscreen": "התחל משחקים במסך מלא", "MenuBarOptionsStopEmulation": "עצור אמולציה", @@ -30,7 +30,7 @@ "MenuBarToolsManageFileTypes": "ניהול סוגי קבצים", "MenuBarToolsInstallFileTypes": "סוגי קבצי התקנה", "MenuBarToolsUninstallFileTypes": "סוגי קבצי הסרה", - "MenuBarHelp": "עזרה", + "MenuBarHelp": "_עזרה", "MenuBarHelpCheckForUpdates": "חפש עדכונים", "MenuBarHelpAbout": "אודות", "MenuSearch": "חפש...", @@ -54,8 +54,6 @@ "GameListContextMenuManageTitleUpdatesToolTip": "פותח את חלון מנהל עדכוני המשחקים", "GameListContextMenuManageDlc": "מנהל הרחבות", "GameListContextMenuManageDlcToolTip": "פותח את חלון מנהל הרחבות המשחקים", - "GameListContextMenuOpenModsDirectory": "פתח את תקיית המודים", - "GameListContextMenuOpenModsDirectoryToolTip": "פותח את התקייה המכילה את המודים של היישום", "GameListContextMenuCacheManagement": "ניהול מטמון", "GameListContextMenuCacheManagementPurgePptc": "הוסף PPTC לתור בנייה מחדש", "GameListContextMenuCacheManagementPurgePptcToolTip": "גרום ל-PPTC להבנות מחדש בפתיחה הבאה של המשחק", @@ -72,6 +70,13 @@ "GameListContextMenuExtractDataRomFSToolTip": "חלץ את קטע ה-RomFS מתצורת היישום הנוכחית (כולל עדכונים)", "GameListContextMenuExtractDataLogo": "Logo", "GameListContextMenuExtractDataLogoToolTip": "חלץ את קטע ה-Logo מתצורת היישום הנוכחית (כולל עדכונים)", + "GameListContextMenuCreateShortcut": "ליצור קיצור דרך לאפליקציה", + "GameListContextMenuCreateShortcutToolTip": "ליצור קיצור דרך בשולחן העבודה שיפתח את אפליקציה זו", + "GameListContextMenuCreateShortcutToolTipMacOS": "ליצור קיצור דרך בתיקיית האפליקציות של macOS שיפתח את אפליקציה זו", + "GameListContextMenuOpenModsDirectory": "פתח תיקיית מודים", + "GameListContextMenuOpenModsDirectoryToolTip": "פותח את התיקייה שמכילה מודים של האפליקציה", + "GameListContextMenuOpenSdModsDirectory": "פתח תיקיית מודים של Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "פותח את תיקיית כרטיס ה-SD החלופית של Atmosphere המכילה את המודים של האפליקציה. שימושי עבור מודים שארוזים עבור חומרה אמיתית.", "StatusBarGamesLoaded": "{1}/{0} משחקים נטענו", "StatusBarSystemVersion": "גרסת מערכת: {0}", "LinuxVmMaxMapCountDialogTitle": "זוהתה מגבלה נמוכה עבור מיפויי זיכרון", @@ -150,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "מקורי (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", "SettingsTabGraphicsAspectRatio": "יחס גובה-רוחב:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -292,13 +297,9 @@ "GameListContextMenuRunApplication": "הרץ יישום", "GameListContextMenuToggleFavorite": "למתג העדפה", "GameListContextMenuToggleFavoriteToolTip": "למתג סטטוס העדפה של משחק", - "SettingsTabGeneralTheme": "ערכת נושא", - "SettingsTabGeneralThemeCustomTheme": "נתיב ערכת נושא מותאמת אישית", - "SettingsTabGeneralThemeBaseStyle": "סגנון בסיס", - "SettingsTabGeneralThemeBaseStyleDark": "כהה", - "SettingsTabGeneralThemeBaseStyleLight": "בהיר", - "SettingsTabGeneralThemeEnableCustomTheme": "אפשר ערכת נושא מותאמת אישית", - "ButtonBrowse": "עיין", + "SettingsTabGeneralTheme": "ערכת נושא:", + "SettingsTabGeneralThemeDark": "כהה", + "SettingsTabGeneralThemeLight": "בהיר", "ControllerSettingsConfigureGeneral": "הגדר", "ControllerSettingsRumble": "רטט", "ControllerSettingsRumbleStrongMultiplier": "העצמת רטט חזק", @@ -332,8 +333,6 @@ "DialogUpdaterAddingFilesMessage": "מוסיף עדכון חדש...", "DialogUpdaterCompleteMessage": "העדכון הושלם!", "DialogUpdaterRestartMessage": "האם אתם רוצים להפעיל מחדש את ריוג'ינקס עכשיו?", - "DialogUpdaterArchNotSupportedMessage": "אינך מריץ ארכיטקטורת מערכת נתמכת!", - "DialogUpdaterArchNotSupportedSubMessage": "(רק מערכות x64 נתמכות!)", "DialogUpdaterNoInternetMessage": "אתם לא מחוברים לאינטרנט!", "DialogUpdaterNoInternetSubMessage": "אנא ודא שיש לך חיבור אינטרנט תקין!", "DialogUpdaterDirtyBuildMessage": "אתם לא יכולים לעדכן מבנה מלוכלך של ריוג'ינקס!", @@ -385,7 +384,10 @@ "DialogUserProfileUnsavedChangesSubMessage": "האם ברצונך למחוק את השינויים האחרונים?", "DialogControllerSettingsModifiedConfirmMessage": "הגדרות השלט הנוכחי עודכנו.", "DialogControllerSettingsModifiedConfirmSubMessage": "האם ברצונך לשמור?", - "DialogLoadNcaErrorMessage": "{0}. קובץ שגוי: {1}", + "DialogLoadFileErrorMessage": "{0}. קובץ שגוי: {1}", + "DialogModAlreadyExistsMessage": "מוד כבר קיים", + "DialogModInvalidMessage": "התיקייה שצוינה אינה מכילה מוד", + "DialogModDeleteNoParentMessage": "נכשל למחוק: לא היה ניתן למצוא את תיקיית האב למוד \"{0}\"!\n", "DialogDlcNoDlcErrorMessage": "הקובץ שצוין אינו מכיל DLC עבור המשחק שנבחר!", "DialogPerformanceCheckLoggingEnabledMessage": "הפעלת רישום מעקב, אשר נועד לשמש מפתחים בלבד.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "לביצועים מיטביים, מומלץ להשבית את רישום המעקב. האם ברצונך להשבית את רישום המעקב כעת?", @@ -396,6 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "הקובץ שצוין אינו מכיל עדכון עבור המשחק שנבחר!", "DialogSettingsBackendThreadingWarningTitle": "אזהרה - ריבוי תהליכי רקע", "DialogSettingsBackendThreadingWarningMessage": "יש להפעיל מחדש את ריוג'ינקס לאחר שינוי אפשרות זו כדי שהיא תחול במלואה. בהתאם לפלטפורמה שלך, ייתכן שיהיה עליך להשבית ידנית את ריבוי ההליכים של ההתקן שלך בעת השימוש ב-ריוג'ינקס.", + "DialogModManagerDeletionWarningMessage": "אתה עומד למחוק את המוד: {0}\nהאם אתה בטוח שאתה רוצה להמשיך?", + "DialogModManagerDeletionAllWarningMessage": "אתה עומד למחוק את כל המודים בשביל משחק זה.\n\nהאם אתה בטוח שאתה רוצה להמשיך?", "SettingsTabGraphicsFeaturesOptions": "אפשרויות", "SettingsTabGraphicsBackendMultithreading": "אחראי גרפיקה רב-תהליכי:", "CommonAuto": "אוטומטי", @@ -430,6 +434,7 @@ "DlcManagerRemoveAllButton": "מחק הכל", "DlcManagerEnableAllButton": "אפשר הכל", "DlcManagerDisableAllButton": "השבת הכל", + "ModManagerDeleteAllButton": "מחק הכל", "MenuBarOptionsChangeLanguage": "החלף שפה", "MenuBarShowFileTypes": "הצג מזהה סוג קובץ", "CommonSort": "מיין", @@ -447,13 +452,13 @@ "CustomThemePathTooltip": "נתיב לערכת נושא לממשק גראפי מותאם אישית", "CustomThemeBrowseTooltip": "חפש עיצוב ממשק גראפי מותאם אישית", "DockModeToggleTooltip": "מצב עגינה גורם למערכת המדומה להתנהג כ-נינטנדו סוויץ' בתחנת עגינתו. זה משפר את הנאמנות הגרפית ברוב המשחקים.\n לעומת זאת, השבתה של תכונה זו תגרום למערכת המדומה להתנהג כ- נינטנדו סוויץ' נייד, ולהפחית את איכות הגרפיקה.\n\nהגדירו את שלט שחקן 1 אם אתם מתכננים להשתמש במצב עגינה; הגדירו את פקדי כף היד אם אתם מתכננים להשתמש במצב נייד.\n\nמוטב להשאיר דלוק אם אתם לא בטוחים.", - "DirectKeyboardTooltip": "תמיכה ישירה למקלדת (HID). מספק גישה בשביל משחקים למקלדת שלך כמכשיר להזנת טקסט.", - "DirectMouseTooltip": "תמיכה ישירה לעכבר (HID). מספק גישה בשביל משחקים לעכבר שלך כהתקן הצבעה.", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", "RegionTooltip": "שנה אזור מערכת", "LanguageTooltip": "שנה שפת מערכת", "TimezoneTooltip": "שנה את אזור הזמן של המערכת", "TimeTooltip": "שנה זמן מערכת", - "VSyncToggleTooltip": "מדמה סנכרון אנכי של קונסולה. כלומר חסם הפריימים לרוב המשחקים; השבתה שלו עלולה לגרום למשחקים לרוץ מהר יותר או לגרום למסכי טעינה לקחת יותר זמן או להתקע.\n\nניתן לשנות מצב של תפריט זה בזמן משחק עם המקש לבחירתך. אנו ממליצים לעשות זאת אם אתם מתכננים להשבית אותו.\n\nמוטב להשאיר דלוק אם לא בטוחים.", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", "PptcToggleTooltip": "שומר את פונקציות ה-JIT המתורגמות כך שלא יצטרכו לעבור תרגום שוב כאשר משחק עולה.\n\nמפחית תקיעות ומשפר מהירות עלייה של המערכת אחרי הפתיחה הראשונה של המשחק.\n\nמוטב להשאיר דלוק אם לא בטוחים.", "FsIntegrityToggleTooltip": "בודק לקבצים שגויים כאשר משחק עולה, ואם מתגלים כאלו, מציג את מזהה השגיאה שלהם לקובץ הלוג.\n\nאין לכך השפעה על הביצועים ונועד לעזור לבדיקה וניפוי שגיאות של האמולטור.\n\nמוטב להשאיר דלוק אם לא בטוחים.", "AudioBackendTooltip": "משנה את אחראי השמע.\n\nSDL2 הוא הנבחר, למראת שOpenAL וגם SoundIO משומשים כאפשרויות חלופיות. אפשרות הDummy לא תשמיע קול כלל.\n\nמוטב להשאיר על SDL2 אם לא בטוחים.", @@ -467,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "מריץ פקודות גראפיקה בתהליך שני נפרד.\n\nמאיץ עיבוד הצללות, מפחית תקיעות ומשפר ביצועים של דרייבר כרטיסי מסך אשר לא תומכים בהרצה רב-תהליכית.\n\nמוטב להשאיר על אוטומטי אם לא בטוחים.", "GalThreadingTooltip": "מריץ פקודות גראפיקה בתהליך שני נפרד.\n\nמאיץ עיבוד הצללות, מפחית תקיעות ומשפר ביצועים של דרייבר כרטיסי מסך אשר לא תומכים בהרצה רב-תהליכית.\n\nמוטב להשאיר על אוטומטי אם לא בטוחים.", "ShaderCacheToggleTooltip": "שומר זכרון מטמון של הצללות, דבר שמפחית תקיעות בריצות מסוימות.\n\nמוטב להשאיר דלוק אם לא בטוחים.", - "ResolutionScaleTooltip": "שיפור רזולוצייה המאופשרת לעיבוד מטרות.", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", "ResolutionScaleEntryTooltip": "שיפור רזולוציית נקודה צפה, כגון 1.5. הוא שיפור לא אינטגרלי הנוטה לגרום יותר בעיות או להקריס.", - "AnisotropyTooltip": "רמת סינון אניסוטרופי (מוגדר לאוטומטי כדי להשתמש בערך המבוקש על ידי המשחק)", - "AspectRatioTooltip": "יחס גובה-רוחב הוחל על חלון המעבד.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", "ShaderDumpPathTooltip": "נתיב השלכת הצללות גראפיות", "FileLogTooltip": "שומר את רישומי שורת הפקודות לזיכרון, לא משפיע על ביצועי היישום.", "StubLogTooltip": "מדפיס רישומים כושלים לשורת הפקודות. לא משפיע על ביצועי היישום.", @@ -504,6 +509,8 @@ "EnableInternetAccessTooltip": "מאפשר ליישומים באמולצייה להתחבר לאינטרנט.\n\nמשחקים עם חיבור לאן יכולים להתחבר אחד לשני כשאופצייה זו מופעלת והמערכות מתחברות לאותה נקודת גישה. כמו כן זה כולל שורות פקודות אמיתיות גם.\n\nאופצייה זו לא מאפשרת חיבור לשרתי נינטנדו. כשהאופצייה דלוקה היא עלולה לגרום לקריסת היישום במשחקים מסויימים שמנסים להתחבר לאינטרנט.\n\nמוטב להשאיר כבוי אם לא בטוחים.", "GameListContextMenuManageCheatToolTip": "נהל צ'יטים", "GameListContextMenuManageCheat": "נהל צ'יטים", + "GameListContextMenuManageModToolTip": "נהל מודים", + "GameListContextMenuManageMod": "נהל מודים", "ControllerSettingsStickRange": "טווח:", "DialogStopEmulationTitle": "ריוג'ינקס - עצור אמולציה", "DialogStopEmulationMessage": "האם אתם בטוחים שאתם רוצים לסגור את האמולצייה?", @@ -515,8 +522,6 @@ "SettingsTabCpuMemory": "מצב מעבד", "DialogUpdaterFlatpakNotSupportedMessage": "בבקשה עדכן את ריוג'ינקס דרך פלאטהב.", "UpdaterDisabledWarningTitle": "מעדכן מושבת!", - "GameListContextMenuOpenSdModsDirectory": "פתח את תקיית המודים של אטמוספרה", - "GameListContextMenuOpenSdModsDirectoryToolTip": "פותח את תקיית כרטיס ה-SD החלופית של אטמוספרה המכילה את המודים של האפליקציה. שימושי עבור מודים ארוזים עבור חומרה אמיתית.", "ControllerSettingsRotate90": "סובב 90° עם בכיוון השעון", "IconSize": "גודל הסמל", "IconSizeTooltip": "שנה את גודל הסמלים של משחקים", @@ -544,12 +549,13 @@ "SwkbdMinCharacters": "לפחות {0} תווים", "SwkbdMinRangeCharacters": "באורך {0}-{1} תווים", "SoftwareKeyboard": "מקלדת וירטואלית", - "SoftwareKeyboardModeNumbersOnly": "מחויב להיות מספרי בלבד", + "SoftwareKeyboardModeNumeric": "חייב להיות בין 0-9 או '.' בלבד", "SoftwareKeyboardModeAlphabet": "מחויב להיות ללא אותיות CJK", "SoftwareKeyboardModeASCII": "מחויב להיות טקסט אסקיי", - "DialogControllerAppletMessagePlayerRange": "האפליקציה מבקשת {0} שחקנים עם:\n\nסוגים: {1}\n\nשחקנים: {2}\n\n{3}אנא פתח את ההגדרות והגדר מחדש את הקלט כעת או סגור.", - "DialogControllerAppletMessage": "האפליקציה מבקשת בדיוק {0} שחקנים עם:\n\nסוגים: {1}\n\nשחקנים: {2}\n\n{3}אנא פתח את ההגדרות והגדר מחדש את הקלט כעת או סגור.", - "DialogControllerAppletDockModeSet": "במצב עגינה. בנוסף מצב נייד לא אפשרי.\n\n", + "ControllerAppletControllers": "בקרים נתמכים:", + "ControllerAppletPlayers": "שחקנים:", + "ControllerAppletDescription": "התצורה הנוכחית אינה תקינה. פתח הגדרות והגדר מחדש את הקלטים שלך.", + "ControllerAppletDocked": "מצב עגינה מוגדר. כדאי ששליטה ניידת תהיה מושבתת.", "UpdaterRenaming": "משנה שמות של קבצים ישנים...", "UpdaterRenameFailed": "המעדכן לא הצליח לשנות את שם הקובץ: {0}", "UpdaterAddingFiles": "מוסיף קבצים חדשים...", @@ -587,6 +593,7 @@ "Writable": "ניתן לכתיבה", "SelectDlcDialogTitle": "בחרו קבצי הרחבות משחק", "SelectUpdateDialogTitle": "בחרו קבצי עדכון", + "SelectModDialogTitle": "בחר תיקיית מודים", "UserProfileWindowTitle": "ניהול פרופילי משתמש", "CheatWindowTitle": "נהל צ'יטים למשחק", "DlcWindowTitle": "נהל הרחבות משחק עבור {0} ({1})", @@ -594,10 +601,12 @@ "CheatWindowHeading": "צ'יטים זמינים עבור {0} [{1}]", "BuildId": "מזהה בניה:", "DlcWindowHeading": "{0} הרחבות משחק", + "ModWindowHeading": "{0} מוד(ים)", "UserProfilesEditProfile": "ערוך נבחר/ים", "Cancel": "בטל", "Save": "שמור", "Discard": "השלך", + "Paused": "מושהה", "UserProfilesSetProfileImage": "הגדר תמונת פרופיל", "UserProfileEmptyNameError": "נדרש שם", "UserProfileNoImageError": "נדרשת תמונת פרופיל", @@ -607,9 +616,9 @@ "UserProfilesName": "שם:", "UserProfilesUserId": "מזהה משתמש:", "SettingsTabGraphicsBackend": "אחראי גראפיקה", - "SettingsTabGraphicsBackendTooltip": "אחראי גראפיקה לשימוש", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", "SettingsEnableTextureRecompression": "אפשר דחיסה מחדש של המרקם", - "SettingsEnableTextureRecompressionTooltip": " דוחס מרקמים מסויימים להפחתת השימוש בראם הוירטואלי.\n\nמומלץ לשימוש עם כרטיס גראפי בעל פחות מ-4GiB בראם הוירטואלי.\n\nמוטב להשאיר כבוי אם אינכם בטוחים.", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", "SettingsTabGraphicsPreferredGpu": "כרטיס גראפי מועדף", "SettingsTabGraphicsPreferredGpuTooltip": "בחר את הכרטיס הגראפי שישומש עם הגראפיקה של וולקאן.\n\nדבר זה לא משפיע על הכרטיס הגראפי שישומש עם OpenGL.\n\nמוטב לבחור את ה-GPU המסומן כ-\"dGPU\" אם אינכם בטוחים, אם זו לא אופצייה, אל תשנו דבר.", "SettingsAppRequiredRestartMessage": "ריוג'ינקס דורש אתחול מחדש", @@ -620,8 +629,8 @@ "SettingsTabHotkeysVolumeDownHotkey": "הנמך את עוצמת הקול:", "SettingsEnableMacroHLE": "Enable Macro HLE", "SettingsEnableMacroHLETooltip": "אמולצייה ברמה גבוהה של כרטיס גראפי עם קוד מקרו.\n\nמשפר את ביצועי היישום אך עלול לגרום לגליצ'ים חזותיים במשחקים מסויימים.\n\nמוטב להשאיר דלוק אם אינך בטוח.", - "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", - "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", + "SettingsEnableColorSpacePassthrough": "שקיפות מרחב צבע", + "SettingsEnableColorSpacePassthroughTooltip": "מנחה את המנוע Vulkan להעביר שקיפות בצבעים מבלי לציין מרחב צבע. עבור משתמשים עם מסכים רחבים, הדבר עשוי לגרום לצבעים מרהיבים יותר, בחוסר דיוק בצבעים האמתיים.", "VolumeShort": "שמע", "UserProfilesManageSaves": "נהל שמורים", "DeleteUserSave": "האם ברצונך למחוק את תקיית השמור למשחק זה?", @@ -635,12 +644,12 @@ "Recover": "שחזר", "UserProfilesRecoverHeading": "שמורים נמצאו לחשבונות הבאים", "UserProfilesRecoverEmptyList": "אין פרופילים לשחזור", - "GraphicsAATooltip": "מחיל החלקת-עקומות על עיבוד המשחק", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", "GraphicsAALabel": "החלקת-עקומות:", "GraphicsScalingFilterLabel": "מסנן מידת איכות:", - "GraphicsScalingFilterTooltip": "אפשר מידת איכות מסוג איגור-תמונה", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", "GraphicsScalingFilterLevelLabel": "רמה", - "GraphicsScalingFilterLevelTooltip": "קבע מידת איכות תמונה לפי רמת סינון", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA נמוך", "SmaaMedium": "SMAA בינוני", "SmaaHigh": "SMAA גבוהה", @@ -648,9 +657,12 @@ "UserEditorTitle": "ערוך משתמש", "UserEditorTitleCreate": "צור משתמש", "SettingsTabNetworkInterface": "ממשק רשת", - "NetworkInterfaceTooltip": "ממשק הרשת המשומש עבור יכולות לאן", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", "NetworkInterfaceDefault": "ברירת המחדל", "PackagingShaders": "אורז הצללות", "AboutChangelogButton": "צפה במידע אודות שינויים בגיטהב", - "AboutChangelogButtonTooltipMessage": "לחץ כדי לפתוח את יומן השינויים עבור גרסה זו בדפדפן ברירת המחדל שלך." -} \ No newline at end of file + "AboutChangelogButtonTooltipMessage": "לחץ כדי לפתוח את יומן השינויים עבור גרסה זו בדפדפן ברירת המחדל שלך.", + "SettingsTabNetworkMultiplayer": "רב משתתפים", + "MultiplayerMode": "מצב:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/it_IT.json b/src/Ryujinx/Assets/Locales/it_IT.json index 5aff7a7ec..7db45b001 100644 --- a/src/Ryujinx/Assets/Locales/it_IT.json +++ b/src/Ryujinx/Assets/Locales/it_IT.json @@ -14,7 +14,7 @@ "MenuBarFileOpenEmuFolder": "Apri cartella di Ryujinx", "MenuBarFileOpenLogsFolder": "Apri cartella dei Log", "MenuBarFileExit": "_Esci", - "MenuBarOptions": "Opzioni", + "MenuBarOptions": "_Opzioni", "MenuBarOptionsToggleFullscreen": "Schermo intero", "MenuBarOptionsStartGamesInFullscreen": "Avvia i giochi a schermo intero", "MenuBarOptionsStopEmulation": "Ferma emulazione", @@ -30,7 +30,7 @@ "MenuBarToolsManageFileTypes": "Gestisci i tipi di file", "MenuBarToolsInstallFileTypes": "Installa i tipi di file", "MenuBarToolsUninstallFileTypes": "Disinstalla i tipi di file", - "MenuBarHelp": "Aiuto", + "MenuBarHelp": "_Aiuto", "MenuBarHelpCheckForUpdates": "Controlla aggiornamenti", "MenuBarHelpAbout": "Informazioni", "MenuSearch": "Cerca...", @@ -54,8 +54,6 @@ "GameListContextMenuManageTitleUpdatesToolTip": "Apre la finestra di gestione aggiornamenti del gioco", "GameListContextMenuManageDlc": "Gestici DLC", "GameListContextMenuManageDlcToolTip": "Apre la finestra di gestione DLC", - "GameListContextMenuOpenModsDirectory": "Apri cartella delle mod", - "GameListContextMenuOpenModsDirectoryToolTip": "Apre la cartella che contiene le mod dell'applicazione", "GameListContextMenuCacheManagement": "Gestione della cache", "GameListContextMenuCacheManagementPurgePptc": "Pulisci PPTC cache", "GameListContextMenuCacheManagementPurgePptcToolTip": "Elimina la PPTC cache dell'applicazione", @@ -72,6 +70,13 @@ "GameListContextMenuExtractDataRomFSToolTip": "Estrae la sezione RomFS dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", "GameListContextMenuExtractDataLogo": "Logo", "GameListContextMenuExtractDataLogoToolTip": "Estrae la sezione Logo dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", + "GameListContextMenuCreateShortcut": "Crea Collegamento", + "GameListContextMenuCreateShortcutToolTip": "Crea un collegamento del gioco sul Desktop", + "GameListContextMenuCreateShortcutToolTipMacOS": "Crea un collegamento del gioco sulla Scrivania", + "GameListContextMenuOpenModsDirectory": "Apri la cartella delle Mods", + "GameListContextMenuOpenModsDirectoryToolTip": "Apre la cartella delle Mods del gioco", + "GameListContextMenuOpenSdModsDirectory": "Apri la cartella delle Mods Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Apre la cartella delle Mods Atmosphere (Utilizzare solo per le Mods eseguite su Hardware reale)", "StatusBarGamesLoaded": "{0}/{1} Giochi caricati", "StatusBarSystemVersion": "Versione di sistema: {0}", "LinuxVmMaxMapCountDialogTitle": "Limite basso per le mappature di memoria rilevate", @@ -150,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Non consigliato)", "SettingsTabGraphicsAspectRatio": "Rapporto d'aspetto:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -292,13 +297,9 @@ "GameListContextMenuRunApplication": "Esegui applicazione", "GameListContextMenuToggleFavorite": "Preferito", "GameListContextMenuToggleFavoriteToolTip": "Segna il gioco come preferito", - "SettingsTabGeneralTheme": "Tema", - "SettingsTabGeneralThemeCustomTheme": "Percorso del tema personalizzato", - "SettingsTabGeneralThemeBaseStyle": "Modalità", - "SettingsTabGeneralThemeBaseStyleDark": "Scura", - "SettingsTabGeneralThemeBaseStyleLight": "Chiara", - "SettingsTabGeneralThemeEnableCustomTheme": "Attiva tema personalizzato", - "ButtonBrowse": "Sfoglia", + "SettingsTabGeneralTheme": "Tema:", + "SettingsTabGeneralThemeDark": "Scuro", + "SettingsTabGeneralThemeLight": "Chiaro", "ControllerSettingsConfigureGeneral": "Configura", "ControllerSettingsRumble": "Vibrazione", "ControllerSettingsRumbleStrongMultiplier": "Moltiplicatore vibrazione forte", @@ -332,8 +333,6 @@ "DialogUpdaterAddingFilesMessage": "Aggiunta del nuovo aggiornamento...", "DialogUpdaterCompleteMessage": "Aggiornamento completato!", "DialogUpdaterRestartMessage": "Vuoi riavviare Ryujinx adesso?", - "DialogUpdaterArchNotSupportedMessage": "Non stai usando un'architettura di sistema supportata!", - "DialogUpdaterArchNotSupportedSubMessage": "(Solo sistemi x64 sono supportati!)", "DialogUpdaterNoInternetMessage": "Non sei connesso ad Internet!", "DialogUpdaterNoInternetSubMessage": "Verifica di avere una connessione ad Internet funzionante!", "DialogUpdaterDirtyBuildMessage": "Non puoi aggiornare una Dirty build di Ryujinx!", @@ -385,7 +384,10 @@ "DialogUserProfileUnsavedChangesSubMessage": "Vuoi scartare le modifiche?", "DialogControllerSettingsModifiedConfirmMessage": "Le attuali impostazioni del controller sono state aggiornate.", "DialogControllerSettingsModifiedConfirmSubMessage": "Vuoi salvare?", - "DialogLoadNcaErrorMessage": "{0}. File errato: {1}", + "DialogLoadFileErrorMessage": "{0}. Errore File: {1}", + "DialogModAlreadyExistsMessage": "La Mod risulta già installata", + "DialogModInvalidMessage": "Questa cartella non contiene nessuna Mods!", + "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", "DialogDlcNoDlcErrorMessage": "Il file specificato non contiene un DLC per il titolo selezionato!", "DialogPerformanceCheckLoggingEnabledMessage": "Hai abilitato il trace logging, che è progettato per essere usato solo dagli sviluppatori.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Per prestazioni ottimali, si raccomanda di disabilitare il trace logging. Vuoi disabilitare il debug logging adesso?", @@ -396,6 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "Il file specificato non contiene un aggiornamento per il titolo selezionato!", "DialogSettingsBackendThreadingWarningTitle": "Avviso - Backend Threading", "DialogSettingsBackendThreadingWarningMessage": "Ryujinx deve essere riavviato dopo aver cambiato questa opzione per applicarla completamente. A seconda della tua piattaforma, potrebbe essere necessario disabilitare manualmente il multithreading del driver quando usi quello di Ryujinx.", + "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", "SettingsTabGraphicsFeaturesOptions": "Funzionalità", "SettingsTabGraphicsBackendMultithreading": "Graphics Backend Multithreading", "CommonAuto": "Auto", @@ -430,6 +434,7 @@ "DlcManagerRemoveAllButton": "Rimuovi tutti", "DlcManagerEnableAllButton": "Abilita tutto", "DlcManagerDisableAllButton": "Disabilita tutto", + "ModManagerDeleteAllButton": "Delete All", "MenuBarOptionsChangeLanguage": "Cambia lingua", "MenuBarShowFileTypes": "Mostra tipi di file", "CommonSort": "Ordina", @@ -447,13 +452,13 @@ "CustomThemePathTooltip": "Percorso al tema GUI personalizzato", "CustomThemeBrowseTooltip": "Sfoglia per cercare un tema GUI personalizzato", "DockModeToggleTooltip": "Attiva o disabilta modalità TV", - "DirectKeyboardTooltip": "Attiva o disattiva \"il supporto all'accesso diretto alla tastiera (HID)\" (Fornisce l'accesso ai giochi alla tua tastiera come dispositivo di immissione del testo)", - "DirectMouseTooltip": "Attiva o disattiva \"il supporto all'accesso diretto al mouse (HID)\" (Fornisce l'accesso ai giochi al tuo mouse come dispositivo di puntamento)", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", "RegionTooltip": "Cambia regione di sistema", "LanguageTooltip": "Cambia lingua di sistema", "TimezoneTooltip": "Cambia fuso orario di sistema", "TimeTooltip": "Cambia data e ora di sistema", - "VSyncToggleTooltip": "Attiva o disattiva sincronizzazione verticale", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", "PptcToggleTooltip": "Attiva o disattiva PPTC", "FsIntegrityToggleTooltip": "Attiva controlli d'integrità sui file dei contenuti di gioco", "AudioBackendTooltip": "Cambia backend audio", @@ -467,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "Attiva il Graphics Backend Multithreading", "GalThreadingTooltip": "Esegue i comandi del backend grafico su un secondo thread. Permette il multithreading runtime della compilazione degli shader, riduce lo stuttering e migliora le prestazioni sui driver senza supporto multithreading proprio. Varia leggermente le prestazioni di picco sui driver con multithreading. Ryujinx potrebbe aver bisogno di essere riavviato per disabilitare correttamente il multithreading integrato nel driver, o potrebbe essere necessario farlo manualmente per ottenere le migliori prestazioni.", "ShaderCacheToggleTooltip": "Attiva o disattiva la Shader Cache", - "ResolutionScaleTooltip": "Scala della risoluzione applicata ai render targets applicabili", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", "ResolutionScaleEntryTooltip": "Scala della risoluzione in virgola mobile, come 1,5. Le scale non integrali hanno maggiori probabilità di causare problemi o crash.", - "AnisotropyTooltip": "Livello del filtro anisotropico (imposta su Auto per usare il valore richiesto dal gioco)", - "AspectRatioTooltip": "Rapporto d'aspetto applicato alla finestra del renderer.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", "ShaderDumpPathTooltip": "Percorso di dump Graphics Shaders", "FileLogTooltip": "Attiva o disattiva il logging su file", "StubLogTooltip": "Attiva messaggi stub log", @@ -504,6 +509,8 @@ "EnableInternetAccessTooltip": "Attiva il guest Internet access. Se abilitato, l'applicazione si comporterà come se la console Switch emulata fosse collegata a Internet. Si noti che in alcuni casi, le applicazioni possono comunque accedere a Internet anche con questa opzione disabilitata", "GameListContextMenuManageCheatToolTip": "Gestisci Cheats", "GameListContextMenuManageCheat": "Gestisci Cheats", + "GameListContextMenuManageModToolTip": "Manage Mods", + "GameListContextMenuManageMod": "Manage Mods", "ControllerSettingsStickRange": "Raggio:", "DialogStopEmulationTitle": "Ryujinx - Ferma emulazione", "DialogStopEmulationMessage": "Sei sicuro di voler fermare l'emulazione?", @@ -515,8 +522,6 @@ "SettingsTabCpuMemory": "Memoria CPU", "DialogUpdaterFlatpakNotSupportedMessage": "Per favore aggiorna Ryujinx via FlatHub.", "UpdaterDisabledWarningTitle": "Updater disabilitato!", - "GameListContextMenuOpenSdModsDirectory": "Apri cartella delle mods Atmosphere", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Apre la cartella Atmosphere della scheda SD alternativa che contiene le Mod dell'applicazione. Utile per mod confezionate per hardware reale", "ControllerSettingsRotate90": "Ruota in senso orario di 90°", "IconSize": "Dimensioni icona", "IconSizeTooltip": "Cambia le dimensioni dell'icona di un gioco", @@ -544,12 +549,13 @@ "SwkbdMinCharacters": "Non può avere meno di {0} caratteri", "SwkbdMinRangeCharacters": "Può avere da {0} a {1} caratteri", "SoftwareKeyboard": "Tastiera software", - "SoftwareKeyboardModeNumbersOnly": "Deve essere solo numeri", + "SoftwareKeyboardModeNumeric": "Must be 0-9 or '.' only", "SoftwareKeyboardModeAlphabet": "Deve essere solo caratteri non CJK", "SoftwareKeyboardModeASCII": "Deve essere solo testo ASCII", - "DialogControllerAppletMessagePlayerRange": "L'applicazione richiede {0} giocatori con:\n\nTIPI: {1}\n\nGIOCATORI: {2}\n\n{3}Apri le impostazioni e riconfigura l'input adesso o premi Chiudi.", - "DialogControllerAppletMessage": "L'applicazione richiede esattamente {0} giocatori con:\n\nTIPI: {1}\n\nGIOCATORI: {2}\n\n{3}Apri le impostazioni e riconfigura l'input adesso o premi Chiudi.", - "DialogControllerAppletDockModeSet": "Modalità TV attivata. Neanche portatile è valida.\n\n", + "ControllerAppletControllers": "Supported Controllers:", + "ControllerAppletPlayers": "Players:", + "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", + "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", "UpdaterRenaming": "Rinominazione dei vecchi files...", "UpdaterRenameFailed": "L'updater non è riuscito a rinominare il file: {0}", "UpdaterAddingFiles": "Aggiunta nuovi files...", @@ -587,6 +593,7 @@ "Writable": "Scrivibile", "SelectDlcDialogTitle": "Seleziona file dei DLC", "SelectUpdateDialogTitle": "Seleziona file di aggiornamento", + "SelectModDialogTitle": "Select mod directory", "UserProfileWindowTitle": "Gestisci profili degli utenti", "CheatWindowTitle": "Gestisci cheat dei giochi", "DlcWindowTitle": "Gestisci DLC dei giochi", @@ -594,10 +601,12 @@ "CheatWindowHeading": "Cheat disponibiili per {0} [{1}]", "BuildId": "ID Build", "DlcWindowHeading": "DLC disponibili per {0} [{1}]", + "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "Modifica selezionati", "Cancel": "Annulla", "Save": "Salva", "Discard": "Scarta", + "Paused": "Paused", "UserProfilesSetProfileImage": "Imposta immagine profilo", "UserProfileEmptyNameError": "È richiesto un nome", "UserProfileNoImageError": "Dev'essere impostata un'immagine profilo", @@ -607,9 +616,9 @@ "UserProfilesName": "Nome:", "UserProfilesUserId": "ID utente:", "SettingsTabGraphicsBackend": "Backend grafica", - "SettingsTabGraphicsBackendTooltip": "Backend grafica da usare", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", "SettingsEnableTextureRecompression": "Abilita Ricompressione Texture", - "SettingsEnableTextureRecompressionTooltip": "Comprime alcune texture per ridurre l'utilizzo della VRAM.\n\nL'utilizzo è consigliato con GPU con meno di 4GB di VRAM.\n\nLascia su OFF se non sei sicuro.", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", "SettingsTabGraphicsPreferredGpu": "GPU preferita", "SettingsTabGraphicsPreferredGpuTooltip": "Seleziona la scheda grafica che verrà usata con la backend grafica Vulkan.\n\nNon influenza la GPU che userà OpenGL.\n\nImposta la GPU contrassegnata come \"dGPU\" se non sei sicuro. Se non ce n'è una, lascia intatta quest'impostazione.", "SettingsAppRequiredRestartMessage": "È richiesto un riavvio di Ryujinx", @@ -635,12 +644,12 @@ "Recover": "Recupera", "UserProfilesRecoverHeading": "Sono stati trovati dei salvataggi per i seguenti account", "UserProfilesRecoverEmptyList": "Nessun profilo da recuperare", - "GraphicsAATooltip": "Applica anti-aliasing al rendering del gioco", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", "GraphicsAALabel": "Anti-Aliasing:", "GraphicsScalingFilterLabel": "Filtro di scala:", - "GraphicsScalingFilterTooltip": "Abilita scalatura Framebuffer", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", "GraphicsScalingFilterLevelLabel": "Livello", - "GraphicsScalingFilterLevelTooltip": "Imposta livello del filtro di scala", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA Basso", "SmaaMedium": "SMAA Medio", "SmaaHigh": "SMAA Alto", @@ -648,9 +657,12 @@ "UserEditorTitle": "Modificare L'Utente", "UserEditorTitleCreate": "Crea Un Utente", "SettingsTabNetworkInterface": "Interfaccia di rete:", - "NetworkInterfaceTooltip": "L'interfaccia di rete utilizzata per le funzionalità LAN", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", "NetworkInterfaceDefault": "Predefinito", "PackagingShaders": "Comprimendo shader", "AboutChangelogButton": "Visualizza changelog su GitHub", - "AboutChangelogButtonTooltipMessage": "Clicca per aprire il changelog per questa versione nel tuo browser predefinito." -} \ No newline at end of file + "AboutChangelogButtonTooltipMessage": "Clicca per aprire il changelog per questa versione nel tuo browser predefinito.", + "SettingsTabNetworkMultiplayer": "Multiplayer", + "MultiplayerMode": "Mode:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/ja_JP.json b/src/Ryujinx/Assets/Locales/ja_JP.json index 5b31c5f20..ce4f07c41 100644 --- a/src/Ryujinx/Assets/Locales/ja_JP.json +++ b/src/Ryujinx/Assets/Locales/ja_JP.json @@ -1,5 +1,5 @@ { - "Language": "英語 (アメリカ)", + "Language": "日本語", "MenuBarFileOpenApplet": "アプレットを開く", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "スタンドアロンモードで Mii エディタアプレットを開きます", "SettingsTabInputDirectMouseAccess": "マウス直接アクセス", @@ -14,7 +14,7 @@ "MenuBarFileOpenEmuFolder": "Ryujinx フォルダを開く", "MenuBarFileOpenLogsFolder": "ログフォルダを開く", "MenuBarFileExit": "終了(_E)", - "MenuBarOptions": "オプション", + "MenuBarOptions": "オプション(_O)", "MenuBarOptionsToggleFullscreen": "全画面切り替え", "MenuBarOptionsStartGamesInFullscreen": "全画面モードでゲームを開始", "MenuBarOptionsStopEmulation": "エミュレーションを停止", @@ -30,7 +30,7 @@ "MenuBarToolsManageFileTypes": "ファイル形式を管理", "MenuBarToolsInstallFileTypes": "ファイル形式をインストール", "MenuBarToolsUninstallFileTypes": "ファイル形式をアンインストール", - "MenuBarHelp": "ヘルプ", + "MenuBarHelp": "ヘルプ(_H)", "MenuBarHelpCheckForUpdates": "アップデートを確認", "MenuBarHelpAbout": "Ryujinx について", "MenuSearch": "検索...", @@ -54,8 +54,6 @@ "GameListContextMenuManageTitleUpdatesToolTip": "タイトルのアップデート管理ウインドウを開きます", "GameListContextMenuManageDlc": "DLCを管理", "GameListContextMenuManageDlcToolTip": "DLC管理ウインドウを開きます", - "GameListContextMenuOpenModsDirectory": "Modディレクトリを開く", - "GameListContextMenuOpenModsDirectoryToolTip": "アプリケーションの Mod データを格納するディレクトリを開きます", "GameListContextMenuCacheManagement": "キャッシュ管理", "GameListContextMenuCacheManagementPurgePptc": "PPTC を再構築", "GameListContextMenuCacheManagementPurgePptcToolTip": "次回のゲーム起動時に PPTC を再構築します", @@ -72,6 +70,13 @@ "GameListContextMenuExtractDataRomFSToolTip": "現在のアプリケーション設定(アップデート含む)から RomFS セクションを展開します", "GameListContextMenuExtractDataLogo": "ロゴ", "GameListContextMenuExtractDataLogoToolTip": "現在のアプリケーション設定(アップデート含む)からロゴセクションを展開します", + "GameListContextMenuCreateShortcut": "アプリケーションのショートカットを作成", + "GameListContextMenuCreateShortcutToolTip": "選択したアプリケーションを起動するデスクトップショートカットを作成します", + "GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application", + "GameListContextMenuOpenModsDirectory": "Modディレクトリを開く", + "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", + "GameListContextMenuOpenSdModsDirectory": "Atmosphere Mods ディレクトリを開く", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", "StatusBarGamesLoaded": "{0}/{1} ゲーム", "StatusBarSystemVersion": "システムバージョン: {0}", "LinuxVmMaxMapCountDialogTitle": "メモリマッピング上限値が小さすぎます", @@ -150,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "ネイティブ (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (非推奨)", "SettingsTabGraphicsAspectRatio": "アスペクト比:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -292,13 +297,9 @@ "GameListContextMenuRunApplication": "アプリケーションを実行", "GameListContextMenuToggleFavorite": "お気に入りを切り替え", "GameListContextMenuToggleFavoriteToolTip": "ゲームをお気に入りに含めるかどうかを切り替えます", - "SettingsTabGeneralTheme": "テーマ", - "SettingsTabGeneralThemeCustomTheme": "カスタムテーマパス", - "SettingsTabGeneralThemeBaseStyle": "基本スタイル", - "SettingsTabGeneralThemeBaseStyleDark": "ダーク", - "SettingsTabGeneralThemeBaseStyleLight": "ライト", - "SettingsTabGeneralThemeEnableCustomTheme": "カスタムテーマを有効にする", - "ButtonBrowse": "参照", + "SettingsTabGeneralTheme": "テーマ:", + "SettingsTabGeneralThemeDark": "ダーク", + "SettingsTabGeneralThemeLight": "ライト", "ControllerSettingsConfigureGeneral": "設定", "ControllerSettingsRumble": "振動", "ControllerSettingsRumbleStrongMultiplier": "強振動の補正値", @@ -332,8 +333,6 @@ "DialogUpdaterAddingFilesMessage": "新規アップデートを追加中...", "DialogUpdaterCompleteMessage": "アップデート完了!", "DialogUpdaterRestartMessage": "すぐに Ryujinx を再起動しますか?", - "DialogUpdaterArchNotSupportedMessage": "サポート外のアーキテクチャです!", - "DialogUpdaterArchNotSupportedSubMessage": "(x64 システムのみサポートしています!)", "DialogUpdaterNoInternetMessage": "インターネットに接続されていません!", "DialogUpdaterNoInternetSubMessage": "インターネット接続が正常動作しているか確認してください!", "DialogUpdaterDirtyBuildMessage": "Dirty ビルドの Ryujinx はアップデートできません!", @@ -385,7 +384,10 @@ "DialogUserProfileUnsavedChangesSubMessage": "変更を破棄しますか?", "DialogControllerSettingsModifiedConfirmMessage": "現在のコントローラ設定が更新されました.", "DialogControllerSettingsModifiedConfirmSubMessage": "セーブしますか?", - "DialogLoadNcaErrorMessage": "{0}. エラー発生ファイル: {1}", + "DialogLoadFileErrorMessage": "{0}. エラー発生ファイル: {1}", + "DialogModAlreadyExistsMessage": "Mod already exists", + "DialogModInvalidMessage": "The specified directory does not contain a mod!", + "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", "DialogDlcNoDlcErrorMessage": "選択されたファイルはこのタイトル用の DLC ではありません!", "DialogPerformanceCheckLoggingEnabledMessage": "トレースロギングを有効にします. これは開発者のみに有用な機能です.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "パフォーマンス最適化のためには,トレースロギングを無効にすることを推奨します. トレースロギングを無効にしてよろしいですか?", @@ -396,6 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "選択されたファイルはこのタイトル用のアップデートではありません!", "DialogSettingsBackendThreadingWarningTitle": "警告 - バックエンドスレッディング", "DialogSettingsBackendThreadingWarningMessage": "このオプションの変更を完全に適用するには Ryujinx の再起動が必要です. プラットフォームによっては, Ryujinx のものを使用する前に手動でドライバ自身のマルチスレッディングを無効にする必要があるかもしれません.", + "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", "SettingsTabGraphicsFeaturesOptions": "機能", "SettingsTabGraphicsBackendMultithreading": "グラフィックスバックエンドのマルチスレッド実行:", "CommonAuto": "自動", @@ -430,6 +434,7 @@ "DlcManagerRemoveAllButton": "すべて削除", "DlcManagerEnableAllButton": "すべて有効", "DlcManagerDisableAllButton": "すべて無効", + "ModManagerDeleteAllButton": "すべて削除", "MenuBarOptionsChangeLanguage": "言語を変更", "MenuBarShowFileTypes": "ファイル形式を表示", "CommonSort": "並べ替え", @@ -447,13 +452,13 @@ "CustomThemePathTooltip": "カスタム GUI テーマのパスです", "CustomThemeBrowseTooltip": "カスタム GUI テーマを参照します", "DockModeToggleTooltip": "有効にすると,ドッキングされた Nintendo Switch をエミュレートします.多くのゲームではグラフィックス品質が向上します.\n無効にすると,携帯モードの Nintendo Switch をエミュレートします.グラフィックスの品質は低下します.\n\nドッキングモード有効ならプレイヤー1の,無効なら携帯の入力を設定してください.\n\nよくわからない場合はオンのままにしてください.", - "DirectKeyboardTooltip": "キーボード直接アクセス (HID) に対応します. キーボードをテキスト入力デバイスとして使用できます.", - "DirectMouseTooltip": "マウス直接アクセス (HID) に対応します. マウスをポインティングデバイスとして使用できます.", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", "RegionTooltip": "システムの地域を変更します", "LanguageTooltip": "システムの言語を変更します", "TimezoneTooltip": "システムのタイムゾーンを変更します", "TimeTooltip": "システムの時刻を変更します", - "VSyncToggleTooltip": "エミュレートされたゲーム機の垂直同期です. 多くのゲームにおいて, フレームリミッタとして機能します. 無効にすると, ゲームが高速で実行されたり, ロード中に時間がかかったり, 止まったりすることがあります.\n\n設定したホットキーで, ゲーム内で切り替え可能です. 無効にする場合は, この操作を行うことをおすすめします.\n\nよくわからない場合はオンのままにしてください.", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", "PptcToggleTooltip": "翻訳されたJIT関数をセーブすることで, ゲームをロードするたびに毎回翻訳する処理を不要とします.\n\n一度ゲームを起動すれば,二度目以降の起動時遅延を大きく軽減できます.\n\nよくわからない場合はオンのままにしてください.", "FsIntegrityToggleTooltip": "ゲーム起動時にファイル破損をチェックし,破損が検出されたらログにハッシュエラーを表示します..\n\nパフォーマンスには影響なく, トラブルシューティングに役立ちます.\n\nよくわからない場合はオンのままにしてください.", "AudioBackendTooltip": "音声レンダリングに使用するバックエンドを変更します.\n\nSDL2 が優先され, OpenAL と SoundIO はフォールバックとして使用されます. ダミーは音声出力しません.\n\nよくわからない場合は SDL2 を設定してください.", @@ -467,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "グラフィックスバックエンドのコマンドを別スレッドで実行します.\n\nシェーダのコンパイルを高速化し, 遅延を軽減し, マルチスレッド非対応の GPU ドライバにおいてパフォーマンスを改善します. マルチスレッド対応のドライバでも若干パフォーマンス改善が見られます.\n\nよくわからない場合は自動に設定してください.", "GalThreadingTooltip": "グラフィックスバックエンドのコマンドを別スレッドで実行します.\n\nシェーダのコンパイルを高速化し, 遅延を軽減し, マルチスレッド非対応の GPU ドライバにおいてパフォーマンスを改善します. マルチスレッド対応のドライバでも若干パフォーマンス改善が見られます.\n\nよくわからない場合は自動に設定してください.", "ShaderCacheToggleTooltip": "ディスクシェーダーキャッシュをセーブし,次回以降の実行時遅延を軽減します.\n\nよくわからない場合はオンのままにしてください.", - "ResolutionScaleTooltip": "レンダリングに適用される解像度の倍率です", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", "ResolutionScaleEntryTooltip": "1.5 のような整数でない倍率を指定すると,問題が発生したりクラッシュしたりする場合があります.", - "AnisotropyTooltip": "異方性フィルタリングのレベルです (ゲームが要求する値を使用する場合は「自動」を設定してください)", - "AspectRatioTooltip": "レンダリングに適用されるアスペクト比です.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", "ShaderDumpPathTooltip": "グラフィックス シェーダー ダンプのパスです", "FileLogTooltip": "コンソール出力されるログをディスク上のログファイルにセーブします. パフォーマンスには影響を与えません.", "StubLogTooltip": "stub ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", @@ -504,6 +509,8 @@ "EnableInternetAccessTooltip": "エミュレートしたアプリケーションをインターネットに接続できるようにします.\n\nLAN モードを持つゲーム同士は,この機能を有効にして同じアクセスポイントに接続すると接続できます. 実機も含まれます.\n\n任天堂のサーバーには接続できません. インターネットに接続しようとすると,特定のゲームでクラッシュすることがあります.\n\nよくわからない場合はオフのままにしてください.", "GameListContextMenuManageCheatToolTip": "チートを管理します", "GameListContextMenuManageCheat": "チートを管理", + "GameListContextMenuManageModToolTip": "Manage Mods", + "GameListContextMenuManageMod": "Manage Mods", "ControllerSettingsStickRange": "範囲:", "DialogStopEmulationTitle": "Ryujinx - エミュレーションを停止", "DialogStopEmulationMessage": "エミュレーションを停止してよろしいですか?", @@ -515,8 +522,6 @@ "SettingsTabCpuMemory": "CPU メモリ", "DialogUpdaterFlatpakNotSupportedMessage": "FlatHub を使用して Ryujinx をアップデートしてください.", "UpdaterDisabledWarningTitle": "アップデータは無効です!", - "GameListContextMenuOpenSdModsDirectory": "Atmosphere Mods ディレクトリを開く", - "GameListContextMenuOpenSdModsDirectoryToolTip": "アプリケーションの Mod データを格納する SD カードの Atmosphere ディレクトリを開きます. 実際のハードウェア用にパッケージされた Mod データに有用です.", "ControllerSettingsRotate90": "時計回りに 90° 回転", "IconSize": "アイコンサイズ", "IconSizeTooltip": "ゲームアイコンのサイズを変更します", @@ -544,12 +549,13 @@ "SwkbdMinCharacters": "最低 {0} 文字必要です", "SwkbdMinRangeCharacters": "{0}-{1} 文字にしてください", "SoftwareKeyboard": "ソフトウェアキーボード", - "SoftwareKeyboardModeNumbersOnly": "数字のみ", + "SoftwareKeyboardModeNumeric": "0-9 または '.' のみでなければなりません", "SoftwareKeyboardModeAlphabet": "CJK文字以外のみ", "SoftwareKeyboardModeASCII": "ASCII文字列のみ", - "DialogControllerAppletMessagePlayerRange": "アプリケーションは {0} 名のプレイヤーを要求しています:\n\n種別: {1}\n\nプレイヤー: {2}\n\n{3}設定を開き各プレイヤーの入力設定を行ってから閉じるを押してください.", - "DialogControllerAppletMessage": "アプリケーションは {0} 名のプレイヤーを要求しています:\n\n種別: {1}\n\nプレイヤー: {2}\n\n{3}設定を開き各プレイヤーの入力設定を行ってから閉じるを押してください.", - "DialogControllerAppletDockModeSet": "ドッキングモードに設定されました. 携帯モードは無効になります.\n\n", + "ControllerAppletControllers": "サポートされているコントローラ:", + "ControllerAppletPlayers": "プレイヤー:", + "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", + "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", "UpdaterRenaming": "古いファイルをリネーム中...", "UpdaterRenameFailed": "ファイルをリネームできませんでした: {0}", "UpdaterAddingFiles": "新規ファイルを追加中...", @@ -587,6 +593,7 @@ "Writable": "書き込み可能", "SelectDlcDialogTitle": "DLC ファイルを選択", "SelectUpdateDialogTitle": "アップデートファイルを選択", + "SelectModDialogTitle": "Select mod directory", "UserProfileWindowTitle": "ユーザプロファイルを管理", "CheatWindowTitle": "チート管理", "DlcWindowTitle": "DLC 管理", @@ -594,10 +601,12 @@ "CheatWindowHeading": "利用可能なチート {0} [{1}]", "BuildId": "ビルドID:", "DlcWindowHeading": "利用可能な DLC {0} [{1}]", + "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "編集", "Cancel": "キャンセル", "Save": "セーブ", "Discard": "破棄", + "Paused": "Paused", "UserProfilesSetProfileImage": "プロファイル画像を設定", "UserProfileEmptyNameError": "名称が必要です", "UserProfileNoImageError": "プロファイル画像が必要です", @@ -607,9 +616,9 @@ "UserProfilesName": "名称:", "UserProfilesUserId": "ユーザID:", "SettingsTabGraphicsBackend": "グラフィックスバックエンド", - "SettingsTabGraphicsBackendTooltip": "使用するグラフィックスバックエンドです", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", "SettingsEnableTextureRecompression": "テクスチャの再圧縮を有効にする", - "SettingsEnableTextureRecompressionTooltip": "VRAMの使用量を削減するためテクスチャを圧縮します.\n\nGPUのVRAMが4GiB未満の場合は使用を推奨します.\n\nよくわからない場合はオフのままにしてください.", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", "SettingsTabGraphicsPreferredGpu": "優先使用するGPU", "SettingsTabGraphicsPreferredGpuTooltip": "Vulkanグラフィックスバックエンドで使用されるグラフィックスカードを選択します.\n\nOpenGLが使用するGPUには影響しません.\n\n不明な場合は, \"dGPU\" としてフラグが立っているGPUに設定します. ない場合はそのままにします.", "SettingsAppRequiredRestartMessage": "Ryujinx の再起動が必要です", @@ -635,12 +644,12 @@ "Recover": "復旧", "UserProfilesRecoverHeading": "以下のアカウントのセーブデータが見つかりました", "UserProfilesRecoverEmptyList": "復元するプロファイルはありません", - "GraphicsAATooltip": "ゲームのレンダリングにアンチエイリアスを適用します", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", "GraphicsAALabel": "アンチエイリアス:", "GraphicsScalingFilterLabel": "スケーリングフィルタ:", - "GraphicsScalingFilterTooltip": "フレームバッファスケーリングを有効にします", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", "GraphicsScalingFilterLevelLabel": "レベル", - "GraphicsScalingFilterLevelTooltip": "スケーリングフィルタのレベルを設定", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA Low", "SmaaMedium": "SMAA Medium", "SmaaHigh": "SMAA High", @@ -648,9 +657,12 @@ "UserEditorTitle": "ユーザを編集", "UserEditorTitleCreate": "ユーザを作成", "SettingsTabNetworkInterface": "ネットワークインタフェース:", - "NetworkInterfaceTooltip": "LAN機能に使用されるネットワークインタフェース", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", "NetworkInterfaceDefault": "デフォルト", "PackagingShaders": "シェーダーを構築中", "AboutChangelogButton": "GitHub で更新履歴を表示", - "AboutChangelogButtonTooltipMessage": "クリックして, このバージョンの更新履歴をデフォルトのブラウザで開きます." -} \ No newline at end of file + "AboutChangelogButtonTooltipMessage": "クリックして, このバージョンの更新履歴をデフォルトのブラウザで開きます.", + "SettingsTabNetworkMultiplayer": "マルチプレイヤー", + "MultiplayerMode": "モード:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/ko_KR.json b/src/Ryujinx/Assets/Locales/ko_KR.json index cdc617222..1db5215e5 100644 --- a/src/Ryujinx/Assets/Locales/ko_KR.json +++ b/src/Ryujinx/Assets/Locales/ko_KR.json @@ -14,7 +14,7 @@ "MenuBarFileOpenEmuFolder": "Ryujinx 폴더 열기", "MenuBarFileOpenLogsFolder": "로그 폴더 열기", "MenuBarFileExit": "_종료", - "MenuBarOptions": "옵션", + "MenuBarOptions": "옵션(_O)", "MenuBarOptionsToggleFullscreen": "전체화면 전환", "MenuBarOptionsStartGamesInFullscreen": "전체 화면 모드에서 게임 시작", "MenuBarOptionsStopEmulation": "에뮬레이션 중지", @@ -30,7 +30,7 @@ "MenuBarToolsManageFileTypes": "파일 형식 관리", "MenuBarToolsInstallFileTypes": "파일 형식 설치", "MenuBarToolsUninstallFileTypes": "파일 형식 설치 제거", - "MenuBarHelp": "도움말", + "MenuBarHelp": "도움말(_H)", "MenuBarHelpCheckForUpdates": "업데이트 확인", "MenuBarHelpAbout": "정보", "MenuSearch": "검색...", @@ -46,7 +46,7 @@ "GameListHeaderPath": "경로", "GameListContextMenuOpenUserSaveDirectory": "사용자 저장 디렉터리 열기", "GameListContextMenuOpenUserSaveDirectoryToolTip": "응용프로그램의 사용자 저장이 포함된 디렉터리 열기", - "GameListContextMenuOpenDeviceSaveDirectory": "사용자 장치 디렉터리 열기", + "GameListContextMenuOpenDeviceSaveDirectory": "사용자 장치 디렉토리 열기", "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "응용프로그램의 장치 저장이 포함된 디렉터리 열기", "GameListContextMenuOpenBcatSaveDirectory": "BCAT 저장 디렉터리 열기", "GameListContextMenuOpenBcatSaveDirectoryToolTip": "응용프로그램의 BCAT 저장이 포함된 디렉터리 열기", @@ -54,8 +54,6 @@ "GameListContextMenuManageTitleUpdatesToolTip": "타이틀 업데이트 관리 창 열기", "GameListContextMenuManageDlc": "DLC 관리", "GameListContextMenuManageDlcToolTip": "DLC 관리 창 열기", - "GameListContextMenuOpenModsDirectory": "Mod 디렉터리 열기", - "GameListContextMenuOpenModsDirectoryToolTip": "응용프로그램의 Mod가 포함된 디렉터리 열기", "GameListContextMenuCacheManagement": "캐시 관리", "GameListContextMenuCacheManagementPurgePptc": "대기열 PPTC 재구성", "GameListContextMenuCacheManagementPurgePptcToolTip": "다음 게임 시작에서 부팅 시 PPTC가 다시 빌드하도록 트리거", @@ -72,6 +70,13 @@ "GameListContextMenuExtractDataRomFSToolTip": "응용 프로그램의 현재 구성에서 RomFS 추출 (업데이트 포함)", "GameListContextMenuExtractDataLogo": "로고", "GameListContextMenuExtractDataLogoToolTip": "응용프로그램의 현재 구성에서 로고 섹션 추출 (업데이트 포함)", + "GameListContextMenuCreateShortcut": "애플리케이션 바로 가기 만들기", + "GameListContextMenuCreateShortcutToolTip": "선택한 애플리케이션을 실행하는 바탕 화면 바로 가기를 만듭니다.", + "GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application", + "GameListContextMenuOpenModsDirectory": "Open Mods Directory", + "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", + "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", "StatusBarGamesLoaded": "{0}/{1}개의 게임 불러옴", "StatusBarSystemVersion": "시스템 버전 : {0}", "LinuxVmMaxMapCountDialogTitle": "감지된 메모리 매핑의 하한선", @@ -150,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "원본(720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2배(1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3배(2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4배(2880p/4320p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", "SettingsTabGraphicsAspectRatio": "종횡비 :", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -292,13 +297,9 @@ "GameListContextMenuRunApplication": "응용프로그램 실행", "GameListContextMenuToggleFavorite": "즐겨찾기 전환", "GameListContextMenuToggleFavoriteToolTip": "게임 즐겨찾기 상태 전환", - "SettingsTabGeneralTheme": "테마", - "SettingsTabGeneralThemeCustomTheme": "커스텀 테마 경로", - "SettingsTabGeneralThemeBaseStyle": "기본 스타일", - "SettingsTabGeneralThemeBaseStyleDark": "어두움", - "SettingsTabGeneralThemeBaseStyleLight": "밝음", - "SettingsTabGeneralThemeEnableCustomTheme": "사용자 정의 테마 활성화", - "ButtonBrowse": "찾아보기", + "SettingsTabGeneralTheme": "Theme:", + "SettingsTabGeneralThemeDark": "Dark", + "SettingsTabGeneralThemeLight": "Light", "ControllerSettingsConfigureGeneral": "구성", "ControllerSettingsRumble": "진동", "ControllerSettingsRumbleStrongMultiplier": "강력한 진동 증폭기", @@ -332,8 +333,6 @@ "DialogUpdaterAddingFilesMessage": "새 업데이트 추가 중...", "DialogUpdaterCompleteMessage": "업데이트를 완료했습니다!", "DialogUpdaterRestartMessage": "지금 Ryujinx를 다시 시작하겠습니까?", - "DialogUpdaterArchNotSupportedMessage": "지원되는 시스템 아키텍처를 실행하고 있지 않습니다!", - "DialogUpdaterArchNotSupportedSubMessage": "(64비트 시스템만 지원됩니다!)", "DialogUpdaterNoInternetMessage": "인터넷에 연결되어 있지 않습니다!", "DialogUpdaterNoInternetSubMessage": "인터넷 연결이 작동하는지 확인하세요!", "DialogUpdaterDirtyBuildMessage": "Ryujinx의 나쁜 빌드는 업데이트할 수 없습니다!\n", @@ -385,7 +384,10 @@ "DialogUserProfileUnsavedChangesSubMessage": "변경사항을 저장하지 않으시겠습니까?", "DialogControllerSettingsModifiedConfirmMessage": "현재 컨트롤러 설정이 업데이트되었습니다.", "DialogControllerSettingsModifiedConfirmSubMessage": "저장하겠습니까?", - "DialogLoadNcaErrorMessage": "{0}. 오류 발생 파일 : {1}", + "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", + "DialogModAlreadyExistsMessage": "Mod already exists", + "DialogModInvalidMessage": "The specified directory does not contain a mod!", + "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", "DialogDlcNoDlcErrorMessage": "지정된 파일에 선택한 타이틀에 대한 DLC가 포함되어 있지 않습니다!", "DialogPerformanceCheckLoggingEnabledMessage": "개발자만 사용하도록 설계된 추적 로그 기록이 활성화되어 있습니다.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "최적의 성능을 위해 추적 로그 생성을 비활성화하는 것이 좋습니다. 지금 추적 로그 기록을 비활성화하겠습니까?", @@ -396,6 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "지정된 파일에 선택한 제목에 대한 업데이트가 포함되어 있지 않습니다!", "DialogSettingsBackendThreadingWarningTitle": "경고 - 후단부 스레딩", "DialogSettingsBackendThreadingWarningMessage": "변경 사항을 완전히 적용하려면 이 옵션을 변경한 후, Ryujinx를 다시 시작해야 합니다. 플랫폼에 따라 Ryujinx를 사용할 때 드라이버 자체의 멀티스레딩을 수동으로 비활성화해야 할 수도 있습니다.", + "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", "SettingsTabGraphicsFeaturesOptions": "기능", "SettingsTabGraphicsBackendMultithreading": "그래픽 후단부 멀티스레딩 :", "CommonAuto": "자동", @@ -430,6 +434,7 @@ "DlcManagerRemoveAllButton": "모두 제거", "DlcManagerEnableAllButton": "모두 활성화", "DlcManagerDisableAllButton": "모두 비활성화", + "ModManagerDeleteAllButton": "Delete All", "MenuBarOptionsChangeLanguage": "언어 변경", "MenuBarShowFileTypes": "파일 유형 표시", "CommonSort": "정렬", @@ -447,13 +452,13 @@ "CustomThemePathTooltip": "사용자 정의 GUI 테마 경로", "CustomThemeBrowseTooltip": "사용자 정의 GUI 테마 찾아보기", "DockModeToggleTooltip": "독 모드에서는 에뮬레이트된 시스템이 도킹된 닌텐도 스위치처럼 작동합니다. 이것은 대부분의 게임에서 그래픽 품질을 향상시킵니다. 반대로 이 기능을 비활성화하면 에뮬레이트된 시스템이 휴대용 닌텐도 스위치처럼 작동하여 그래픽 품질이 저하됩니다.\n\n독 모드를 사용하려는 경우 플레이어 1의 컨트롤을 구성하세요. 휴대 모드를 사용하려는 경우 휴대용 컨트롤을 구성하세요.\n\n확실하지 않으면 켜 두세요.", - "DirectKeyboardTooltip": "직접 키보드 접속 (HID) 지원합니다. 텍스트 입력 장치로 키보드에 대한 게임 접속을 제공합니다.", - "DirectMouseTooltip": "직접 마우스 접속 (HID) 지원합니다. 포인팅 장치로 마우스에 대한 게임 접속을 제공합니다.", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", "RegionTooltip": "시스템 지역 변경", "LanguageTooltip": "시스템 언어 변경", "TimezoneTooltip": "시스템 시간대 변경", "TimeTooltip": "시스템 시간 변경", - "VSyncToggleTooltip": "에뮬레이트된 콘솔의 수직 동기화입니다. 기본적으로 대부분의 게임에 대한 프레임 제한 장치입니다. 비활성화하면 게임이 더 빠른 속도로 실행되거나 로딩 화면이 더 오래 걸리거나 멈출 수 있습니다.\n\n게임 내에서 선호하는 핫키로 전환할 수 있습니다. 비활성화할 계획이라면 이 작업을 수행하는 것이 좋습니다.\n\n확실하지 않으면 켜 두세요.", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", "PptcToggleTooltip": "게임이 불러올 때마다 번역할 필요가 없도록 번역된 JIT 기능을 저장합니다.\n\n게임을 처음 부팅한 후 끊김 현상을 줄이고 부팅 시간을 크게 단축합니다.\n\n확실하지 않으면 켜 두세요.", "FsIntegrityToggleTooltip": "게임을 부팅할 때 손상된 파일을 확인하고 손상된 파일이 감지되면 로그에 해시 오류를 표시합니다.\n\n성능에 영향을 미치지 않으며 문제 해결에 도움이 됩니다.\n\n확실하지 않으면 켜 두세요.", "AudioBackendTooltip": "오디오를 렌더링하는 데 사용되는 백엔드를 변경합니다.\n\nSDL2가 선호되는 반면 OpenAL 및 사운드IO는 폴백으로 사용됩니다. 더미는 소리가 나지 않습니다.\n\n확실하지 않으면 SDL2로 설정하세요.", @@ -467,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "두 번째 스레드에서 그래픽 백엔드 명령을 실행합니다.\n\n세이더 컴파일 속도를 높이고 끊김 현상을 줄이며 자체 멀티스레딩 지원 없이 GPU 드라이버의 성능을 향상시킵니다. 멀티스레딩이 있는 드라이버에서 성능이 약간 향상되었습니다.\n\n잘 모르겠으면 자동으로 설정하세요.", "GalThreadingTooltip": "두 번째 스레드에서 그래픽 백엔드 명령을 실행합니다.\n\n세이더 컴파일 속도를 높이고 끊김 현상을 줄이며 자체 멀티스레딩 지원 없이 GPU 드라이버의 성능을 향상시킵니다. 멀티스레딩이 있는 드라이버에서 성능이 약간 향상되었습니다.\n\n잘 모르겠으면 자동으로 설정하세요.", "ShaderCacheToggleTooltip": "후속 실행에서 끊김 현상을 줄이는 디스크 세이더 캐시를 저장합니다.\n\n확실하지 않으면 켜 두세요.", - "ResolutionScaleTooltip": "적용 가능한 렌더 타겟에 적용된 해상도 스케일", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", "ResolutionScaleEntryTooltip": "1.5와 같은 부동 소수점 분해능 스케일입니다. 비통합 척도는 문제나 충돌을 일으킬 가능성이 더 큽니다.", - "AnisotropyTooltip": "비등방성 필터링 수준 (게임에서 요청한 값을 사용하려면 자동으로 설정)", - "AspectRatioTooltip": "렌더러 창에 적용된 화면비입니다.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", "ShaderDumpPathTooltip": "그래픽 셰이더 덤프 경로", "FileLogTooltip": "디스크의 로그 파일에 콘솔 로깅을 저장합니다. 성능에 영향을 미치지 않습니다.", "StubLogTooltip": "콘솔에 스텁 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", @@ -504,6 +509,8 @@ "EnableInternetAccessTooltip": "에뮬레이션된 응용프로그램이 인터넷에 연결되도록 허용합니다.\n\nLAN 모드가 있는 게임은 이 모드가 활성화되고 시스템이 동일한 접속 포인트에 연결된 경우 서로 연결할 수 있습니다. 여기에는 실제 콘솔도 포함됩니다.\n\n닌텐도 서버에 연결할 수 없습니다. 인터넷에 연결을 시도하는 특정 게임에서 충돌이 발생할 수 있습니다.\n\n확실하지 않으면 꺼두세요.", "GameListContextMenuManageCheatToolTip": "치트 관리", "GameListContextMenuManageCheat": "치트 관리", + "GameListContextMenuManageModToolTip": "Manage Mods", + "GameListContextMenuManageMod": "Manage Mods", "ControllerSettingsStickRange": "범위 :", "DialogStopEmulationTitle": "Ryujinx - 에뮬레이션 중지", "DialogStopEmulationMessage": "에뮬레이션을 중지하겠습니까?", @@ -515,8 +522,6 @@ "SettingsTabCpuMemory": "CPU 모드", "DialogUpdaterFlatpakNotSupportedMessage": "FlatHub를 통해 Ryujinx를 업데이트하세요.", "UpdaterDisabledWarningTitle": "업데이터 비활성화입니다!", - "GameListContextMenuOpenSdModsDirectory": "분위기 모드 디렉터리 열기", - "GameListContextMenuOpenSdModsDirectoryToolTip": "응용프로그램의 모드가 포함된 대체 SD 카드 분위기 디렉터리를 엽니다. 실제 하드웨어용으로 패키징된 모드에 유용합니다.", "ControllerSettingsRotate90": "시계 방향으로 90° 회전", "IconSize": "아이콘 크기", "IconSizeTooltip": "게임 아이콘 크기 변경", @@ -544,12 +549,13 @@ "SwkbdMinCharacters": "{0}자 이상이어야 함", "SwkbdMinRangeCharacters": "{0}-{1}자여야 함", "SoftwareKeyboard": "소프트웨어 키보드", - "SoftwareKeyboardModeNumbersOnly": "숫자만 가능", + "SoftwareKeyboardModeNumeric": "'0~9' 또는 '.'만 가능", "SoftwareKeyboardModeAlphabet": "한중일 문자가 아닌 문자만 가능", "SoftwareKeyboardModeASCII": "ASCII 텍스트만 가능", - "DialogControllerAppletMessagePlayerRange": "응용 프로그램은 다음을 사용하는 {0} 명의 플레이어를 요청합니다:\n\n유형: {1}\n\n플레이어: {2}\n\n{3} 지금 설정을 열고 입력을 재구성하거나 닫기를 누르세요.\n", - "DialogControllerAppletMessage": "응용 프로그램은 다음을 사용하는 정확히 {0}명의 플레이어를 요청합니다:\n\n유형: {1}\n\n플레이어: {2}\n\n{3} 지금 설정을 열고 입력을 재구성하거나 닫기를 누르세요.\n", - "DialogControllerAppletDockModeSet": "독 모드가 설정되었습니다. 휴대 모드도 유효하지 않습니다.", + "ControllerAppletControllers": "Supported Controllers:", + "ControllerAppletPlayers": "Players:", + "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", + "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", "UpdaterRenaming": "이전 파일 이름 바꾸는 중...", "UpdaterRenameFailed": "업데이터가 파일 이름을 바꿀 수 없음: {0}", "UpdaterAddingFiles": "새로운 파일을 추가하는 중...", @@ -587,6 +593,7 @@ "Writable": "쓰기 가능", "SelectDlcDialogTitle": "DLC 파일 선택", "SelectUpdateDialogTitle": "업데이트 파일 선택", + "SelectModDialogTitle": "Select mod directory", "UserProfileWindowTitle": "사용자 프로파일 관리자", "CheatWindowTitle": "치트 관리자", "DlcWindowTitle": "{0} ({1})의 다운로드 가능한 콘텐츠 관리", @@ -594,10 +601,12 @@ "CheatWindowHeading": "{0} [{1}]에 사용할 수 있는 치트", "BuildId": "빌드ID :", "DlcWindowHeading": "{0} 내려받기 가능한 콘텐츠", + "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "선택된 항목 편집", "Cancel": "취소", "Save": "저장", "Discard": "삭제", + "Paused": "일시 중지", "UserProfilesSetProfileImage": "프로파일 이미지 설정", "UserProfileEmptyNameError": "이름 필요", "UserProfileNoImageError": "프로파일 이미지를 설정해야 함", @@ -607,9 +616,9 @@ "UserProfilesName": "이름 :", "UserProfilesUserId": "사용자 ID :", "SettingsTabGraphicsBackend": "그래픽 후단부", - "SettingsTabGraphicsBackendTooltip": "사용할 그래픽 후단부", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", "SettingsEnableTextureRecompression": "텍스처 재압축 활성화", - "SettingsEnableTextureRecompressionTooltip": "VRAM 사용량을 줄이기 위해 특정 텍스처를 압축합니다.\n\n4GiB VRAM 미만의 GPU와 함께 사용하는 것이 좋습니다.\n\n확실하지 않으면 꺼 두세요.", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", "SettingsTabGraphicsPreferredGpu": "선호하는 GPU", "SettingsTabGraphicsPreferredGpuTooltip": "Vulkan 그래픽 후단부와 함께 사용할 그래픽 카드를 선택하세요.\n\nOpenGL이 사용할 GPU에는 영향을 미치지 않습니다.\n\n확실하지 않은 경우 \"dGPU\" 플래그가 지정된 GPU로 설정하세요. 없는 경우, 그대로 두세요.", "SettingsAppRequiredRestartMessage": "Ryujinx 다시 시작 필요", @@ -635,12 +644,12 @@ "Recover": "복구", "UserProfilesRecoverHeading": "다음 계정에 대한 저장 발견", "UserProfilesRecoverEmptyList": "복구할 프로파일이 없습니다", - "GraphicsAATooltip": "게임 렌더링에 안티 앨리어싱을 적용", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", "GraphicsAALabel": "안티 앨리어싱:", "GraphicsScalingFilterLabel": "스케일링 필터:", - "GraphicsScalingFilterTooltip": "프레임버퍼 스케일링 활성화", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", "GraphicsScalingFilterLevelLabel": "수준", - "GraphicsScalingFilterLevelTooltip": "스케일링 필터 수준 설정", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA 낮음", "SmaaMedium": "SMAA 중간", "SmaaHigh": "SMAA 높음", @@ -648,9 +657,12 @@ "UserEditorTitle": "사용자 수정", "UserEditorTitleCreate": "사용자 생성", "SettingsTabNetworkInterface": "네트워크 인터페이스:", - "NetworkInterfaceTooltip": "LAN 기능에 사용되는 네트워크 인터페이스", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", "NetworkInterfaceDefault": "기본", "PackagingShaders": "셰이더 패키징 중", "AboutChangelogButton": "GitHub에서 변경 로그 보기", - "AboutChangelogButtonTooltipMessage": "기본 브라우저에서 이 버전의 변경 로그를 열려면 클릭합니다." -} \ No newline at end of file + "AboutChangelogButtonTooltipMessage": "기본 브라우저에서 이 버전의 변경 로그를 열려면 클릭합니다.", + "SettingsTabNetworkMultiplayer": "멀티 플레이어", + "MultiplayerMode": "모드 :", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/pl_PL.json b/src/Ryujinx/Assets/Locales/pl_PL.json index 7edf41e84..4647f4308 100644 --- a/src/Ryujinx/Assets/Locales/pl_PL.json +++ b/src/Ryujinx/Assets/Locales/pl_PL.json @@ -1,112 +1,117 @@ { - "Language": "Angielski (USA)", + "Language": "Polski", "MenuBarFileOpenApplet": "Otwórz Aplet", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Otwórz aplet Mii Editor w trybie Indywidualnym", - "SettingsTabInputDirectMouseAccess": "Bezpośredni Dostęp do Myszy", - "SettingsTabSystemMemoryManagerMode": "Tryb Menedżera Pamięci:", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Otwórz aplet Mii Editor w trybie indywidualnym", + "SettingsTabInputDirectMouseAccess": "Bezpośredni dostęp do myszy", + "SettingsTabSystemMemoryManagerMode": "Tryb menedżera pamięci:", "SettingsTabSystemMemoryManagerModeSoftware": "Oprogramowanie", - "SettingsTabSystemMemoryManagerModeHost": "Host (szybko)", - "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Niesprawdzony (najszybciej, niebezpiecznie)", - "SettingsTabSystemUseHypervisor": "Użyj Hiperwizora", + "SettingsTabSystemMemoryManagerModeHost": "Gospodarz (szybki)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Gospodarza (NIESPRAWDZONY, najszybszy, niebezpieczne)", + "SettingsTabSystemUseHypervisor": "Użyj Hipernadzorcy", "MenuBarFile": "_Plik", - "MenuBarFileOpenFromFile": "_Załaduj Aplikację z Pliku", - "MenuBarFileOpenUnpacked": "Załaduj _Rozpakowaną Grę", - "MenuBarFileOpenEmuFolder": "Otwórz Folder Ryujinx", - "MenuBarFileOpenLogsFolder": "Otwórz Folder Logów", + "MenuBarFileOpenFromFile": "_Załaduj aplikację z pliku", + "MenuBarFileOpenUnpacked": "Załaduj _rozpakowaną grę", + "MenuBarFileOpenEmuFolder": "Otwórz folder Ryujinx", + "MenuBarFileOpenLogsFolder": "Otwórz folder plików dziennika zdarzeń", "MenuBarFileExit": "_Wyjdź", - "MenuBarOptions": "Opcje", - "MenuBarOptionsToggleFullscreen": "Przełącz Tryb Pełnoekranowy", - "MenuBarOptionsStartGamesInFullscreen": "Uruchamiaj Gry w Trybie Pełnoekranowym", - "MenuBarOptionsStopEmulation": "Zatrzymaj Emulację", + "MenuBarOptions": "_Opcje", + "MenuBarOptionsToggleFullscreen": "Przełącz na tryb pełnoekranowy", + "MenuBarOptionsStartGamesInFullscreen": "Uruchamiaj gry w trybie pełnoekranowym", + "MenuBarOptionsStopEmulation": "Zatrzymaj emulację", "MenuBarOptionsSettings": "_Ustawienia", - "MenuBarOptionsManageUserProfiles": "_Zarządzaj Profilami Użytkowników", + "MenuBarOptionsManageUserProfiles": "_Zarządzaj profilami użytkowników", "MenuBarActions": "_Akcje", - "MenuBarOptionsSimulateWakeUpMessage": "Symuluj Wiadomość Budzenia", + "MenuBarOptionsSimulateWakeUpMessage": "Symuluj wiadomość wybudzania", "MenuBarActionsScanAmiibo": "Skanuj Amiibo", "MenuBarTools": "_Narzędzia", - "MenuBarToolsInstallFirmware": "Zainstaluj Firmware", - "MenuBarFileToolsInstallFirmwareFromFile": "Zainstaluj Firmware z XCI lub ZIP", - "MenuBarFileToolsInstallFirmwareFromDirectory": "Zainstaluj Firmware z Katalogu", - "MenuBarToolsManageFileTypes": "Zarządzaj rodzajem plików", - "MenuBarToolsInstallFileTypes": "Instalacja typów plików", - "MenuBarToolsUninstallFileTypes": "Odinstaluj rodzaje plików", - "MenuBarHelp": "Pomoc", - "MenuBarHelpCheckForUpdates": "Sprawdź Aktualizacje", - "MenuBarHelpAbout": "O Aplikacji", + "MenuBarToolsInstallFirmware": "Zainstaluj oprogramowanie", + "MenuBarFileToolsInstallFirmwareFromFile": "Zainstaluj oprogramowanie z XCI lub ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Zainstaluj oprogramowanie z katalogu", + "MenuBarToolsManageFileTypes": "Zarządzaj rodzajami plików", + "MenuBarToolsInstallFileTypes": "Typy plików instalacyjnych", + "MenuBarToolsUninstallFileTypes": "Typy plików dezinstalacyjnych", + "MenuBarHelp": "_Pomoc", + "MenuBarHelpCheckForUpdates": "Sprawdź aktualizacje", + "MenuBarHelpAbout": "O programie", "MenuSearch": "Wyszukaj...", - "GameListHeaderFavorite": "Ulub", + "GameListHeaderFavorite": "Ulubione", "GameListHeaderIcon": "Ikona", "GameListHeaderApplication": "Nazwa", - "GameListHeaderDeveloper": "Deweloper", + "GameListHeaderDeveloper": "Twórca", "GameListHeaderVersion": "Wersja", - "GameListHeaderTimePlayed": "Czas Gry", - "GameListHeaderLastPlayed": "Ostatnio Grane", - "GameListHeaderFileExtension": "Rozsz. Pliku", - "GameListHeaderFileSize": "Rozm. Pliku", + "GameListHeaderTimePlayed": "Czas w grze:", + "GameListHeaderLastPlayed": "Ostatnio grane", + "GameListHeaderFileExtension": "Rozszerzenie pliku", + "GameListHeaderFileSize": "Rozmiar pliku", "GameListHeaderPath": "Ścieżka", - "GameListContextMenuOpenUserSaveDirectory": "Otwórz Katalog Zapisów Użytkownika", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "Otwiera katalog, który zawiera Zapis Użytkownika Aplikacji", - "GameListContextMenuOpenDeviceSaveDirectory": "Otwórz Katalog Urządzeń Użytkownika", - "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Otwiera katalog, który zawiera Zapis Urządzenia Aplikacji", - "GameListContextMenuOpenBcatSaveDirectory": "Otwórz Katalog BCAT Użytkownika", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Otwiera katalog, który zawiera Zapis BCAT Aplikacji", - "GameListContextMenuManageTitleUpdates": "Zarządzaj Aktualizacjami Tytułów", - "GameListContextMenuManageTitleUpdatesToolTip": "Otwiera okno zarządzania Aktualizacjami Tytułu", - "GameListContextMenuManageDlc": "Zarządzaj DLC", - "GameListContextMenuManageDlcToolTip": "Otwiera okno zarządzania DLC", - "GameListContextMenuOpenModsDirectory": "Otwórz Katalog Modów", - "GameListContextMenuOpenModsDirectoryToolTip": "Otwiera katalog zawierający Mody Aplikacji", + "GameListContextMenuOpenUserSaveDirectory": "Otwórz katalog zapisów użytkownika", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Otwiera katalog, który zawiera zapis użytkownika dla tej aplikacji", + "GameListContextMenuOpenDeviceSaveDirectory": "Otwórz katalog zapisów urządzenia", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Otwiera katalog, który zawiera zapis urządzenia dla tej aplikacji", + "GameListContextMenuOpenBcatSaveDirectory": "Otwórz katalog zapisu BCAT obecnego użytkownika", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Otwiera katalog, który zawiera zapis BCAT dla tej aplikacji", + "GameListContextMenuManageTitleUpdates": "Zarządzaj aktualizacjami", + "GameListContextMenuManageTitleUpdatesToolTip": "Otwiera okno zarządzania aktualizacjami danej aplikacji", + "GameListContextMenuManageDlc": "Zarządzaj dodatkową zawartością (DLC)", + "GameListContextMenuManageDlcToolTip": "Otwiera okno zarządzania dodatkową zawartością", "GameListContextMenuCacheManagement": "Zarządzanie Cache", - "GameListContextMenuCacheManagementPurgePptc": "Dodaj Rekompilację PPTC do Kolejki", + "GameListContextMenuCacheManagementPurgePptc": "Zakolejkuj rekompilację PPTC", "GameListContextMenuCacheManagementPurgePptcToolTip": "Zainicjuj Rekompilację PPTC przy następnym uruchomieniu gry", - "GameListContextMenuCacheManagementPurgeShaderCache": "Wyczyść Cache Shaderów", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Usuwa cache shaderów aplikacji", - "GameListContextMenuCacheManagementOpenPptcDirectory": "Otwórz Katalog PPTC", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Otwiera katalog, który zawiera cache PPTC aplikacji", - "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Otwórz Katalog Cache Shaderów", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Otwiera katalog, który zawiera cache shaderów aplikacji", - "GameListContextMenuExtractData": "Wyodrębnij Dane", + "GameListContextMenuCacheManagementPurgeShaderCache": "Wyczyść pamięć podręczną cieni", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Usuwa pamięć podręczną cieni danej aplikacji", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Otwórz katalog PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Otwiera katalog, który zawiera pamięć podręczną PPTC aplikacji", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Otwórz katalog pamięci podręcznej cieni", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Otwiera katalog, który zawiera pamięć podręczną cieni aplikacji", + "GameListContextMenuExtractData": "Wypakuj dane", "GameListContextMenuExtractDataExeFS": "ExeFS", "GameListContextMenuExtractDataExeFSToolTip": "Wyodrębnij sekcję ExeFS z bieżącej konfiguracji aplikacji (w tym aktualizacje)", "GameListContextMenuExtractDataRomFS": "RomFS", "GameListContextMenuExtractDataRomFSToolTip": "Wyodrębnij sekcję RomFS z bieżącej konfiguracji aplikacji (w tym aktualizacje)", "GameListContextMenuExtractDataLogo": "Logo", - "GameListContextMenuExtractDataLogoToolTip": "Wyodrębnij sekcję Logo z bieżącej konfiguracji aplikacji (w tym aktualizacje)", - "StatusBarGamesLoaded": "{0}/{1} Załadowane Gry", - "StatusBarSystemVersion": "Wersja Systemu: {0}", - "LinuxVmMaxMapCountDialogTitle": "Wykryto niski limit dla mapowania pamięci", - "LinuxVmMaxMapCountDialogTextPrimary": "Czy chciałbyś zwiększyć wartość vm.max_map_count do {0}", - "LinuxVmMaxMapCountDialogTextSecondary": "Niektóre gry mogą próbować stworzyć więcej mapowań pamięci niż obecnie, jest to dozwolone. Ryujinx napotka crash, gdy dojdzie do takiej sytuacji.", + "GameListContextMenuExtractDataLogoToolTip": "Wyodrębnij sekcję z logiem z bieżącej konfiguracji aplikacji (w tym aktualizacje)", + "GameListContextMenuCreateShortcut": "Utwórz skrót aplikacji", + "GameListContextMenuCreateShortcutToolTip": "Utwórz skrót na pulpicie, który uruchamia wybraną aplikację", + "GameListContextMenuCreateShortcutToolTipMacOS": "Utwórz skrót w folderze 'Aplikacje' w systemie macOS, który uruchamia wybraną aplikację", + "GameListContextMenuOpenModsDirectory": "Otwórz katalog modów", + "GameListContextMenuOpenModsDirectoryToolTip": "Otwiera katalog zawierający mody dla danej aplikacji", + "GameListContextMenuOpenSdModsDirectory": "Otwórz katalog modów Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Otwiera alternatywny katalog Atmosphere na karcie SD, który zawiera mody danej aplikacji. Przydatne dla modów przygotowanych pod prawdziwy sprzęt.", + "StatusBarGamesLoaded": "{0}/{1} Załadowane gry", + "StatusBarSystemVersion": "Wersja systemu: {0}", + "LinuxVmMaxMapCountDialogTitle": "Wykryto niski limit dla przypisań pamięci", + "LinuxVmMaxMapCountDialogTextPrimary": "Czy chcesz zwiększyć wartość vm.max_map_count do {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Niektóre gry mogą próbować przypisać sobie więcej pamięci niż obecnie, jest to dozwolone. Ryujinx ulegnie awarii, gdy limit zostanie przekroczony.", "LinuxVmMaxMapCountDialogButtonUntilRestart": "Tak, do następnego ponownego uruchomienia", "LinuxVmMaxMapCountDialogButtonPersistent": "Tak, permanentnie ", - "LinuxVmMaxMapCountWarningTextPrimary": "Maksymalna ilość mapowania pamięci jest mniejsza niż zalecana.", + "LinuxVmMaxMapCountWarningTextPrimary": "Maksymalna ilość przypisanej pamięci jest mniejsza niż zalecana.", "LinuxVmMaxMapCountWarningTextSecondary": "Obecna wartość vm.max_map_count ({0}) jest mniejsza niż {1}. Niektóre gry mogą próbować stworzyć więcej mapowań pamięci niż obecnie jest to dozwolone. Ryujinx napotka crash, gdy dojdzie do takiej sytuacji.\n\nMożesz chcieć ręcznie zwiększyć limit lub zainstalować pkexec, co pozwala Ryujinx na pomoc w tym zakresie.", "Settings": "Ustawienia", - "SettingsTabGeneral": "Interfejs Użytkownika", + "SettingsTabGeneral": "Interfejs użytkownika", "SettingsTabGeneralGeneral": "Ogólne", "SettingsTabGeneralEnableDiscordRichPresence": "Włącz Bogatą Obecność Discord", - "SettingsTabGeneralCheckUpdatesOnLaunch": "Sprawdź Aktualizacje przy Uruchomieniu", - "SettingsTabGeneralShowConfirmExitDialog": "Pokaż Okno Dialogowe \"Potwierdzenia Wyjścia\"", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Sprawdzaj aktualizacje przy uruchomieniu", + "SettingsTabGeneralShowConfirmExitDialog": "Pokazuj okno dialogowe \"Potwierdź wyjście\"", "SettingsTabGeneralHideCursor": "Ukryj kursor:", "SettingsTabGeneralHideCursorNever": "Nigdy", - "SettingsTabGeneralHideCursorOnIdle": "Ukryj Kursor Podczas Bezczynności", + "SettingsTabGeneralHideCursorOnIdle": "Gdy bezczynny", "SettingsTabGeneralHideCursorAlways": "Zawsze", - "SettingsTabGeneralGameDirectories": "Katalogi Gier", + "SettingsTabGeneralGameDirectories": "Katalogi gier", "SettingsTabGeneralAdd": "Dodaj", "SettingsTabGeneralRemove": "Usuń", "SettingsTabSystem": "System", "SettingsTabSystemCore": "Główne", - "SettingsTabSystemSystemRegion": "Region Systemu:", + "SettingsTabSystemSystemRegion": "Region systemu:", "SettingsTabSystemSystemRegionJapan": "Japonia", - "SettingsTabSystemSystemRegionUSA": "USA", + "SettingsTabSystemSystemRegionUSA": "Stany Zjednoczone", "SettingsTabSystemSystemRegionEurope": "Europa", "SettingsTabSystemSystemRegionAustralia": "Australia", "SettingsTabSystemSystemRegionChina": "Chiny", "SettingsTabSystemSystemRegionKorea": "Korea", "SettingsTabSystemSystemRegionTaiwan": "Tajwan", - "SettingsTabSystemSystemLanguage": "Język Systemu:", + "SettingsTabSystemSystemLanguage": "Język systemu:", "SettingsTabSystemSystemLanguageJapanese": "Japoński", - "SettingsTabSystemSystemLanguageAmericanEnglish": "Amerykański Angielski", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Angielski (Stany Zjednoczone)", "SettingsTabSystemSystemLanguageFrench": "Francuski", "SettingsTabSystemSystemLanguageGerman": "Niemiecki", "SettingsTabSystemSystemLanguageItalian": "Włoski", @@ -117,16 +122,16 @@ "SettingsTabSystemSystemLanguagePortuguese": "Portugalski", "SettingsTabSystemSystemLanguageRussian": "Rosyjski", "SettingsTabSystemSystemLanguageTaiwanese": "Tajwański", - "SettingsTabSystemSystemLanguageBritishEnglish": "Brytyjski Angielski", + "SettingsTabSystemSystemLanguageBritishEnglish": "Angielski (Wielka Brytania)", "SettingsTabSystemSystemLanguageCanadianFrench": "Kanadyjski Francuski", - "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Hiszpański Latynoamerykański", - "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chiński Uproszczony", - "SettingsTabSystemSystemLanguageTraditionalChinese": "Chiński Tradycyjny", - "SettingsTabSystemSystemTimeZone": "Strefa Czasowa Systemu:", - "SettingsTabSystemSystemTime": "Czas Systemu:", - "SettingsTabSystemEnableVsync": "Włącz synchronizację pionową", - "SettingsTabSystemEnablePptc": "PPTC (Profilowany Cache Trwałych Tłumaczeń)", - "SettingsTabSystemEnableFsIntegrityChecks": "Kontrole Integralności Systemu Plików", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Hiszpański (Ameryka Łacińska)", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chiński (Uproszczony)", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Chiński (Tradycyjny)", + "SettingsTabSystemSystemTimeZone": "Strefa czasowa systemu:", + "SettingsTabSystemSystemTime": "Czas systemu:", + "SettingsTabSystemEnableVsync": "Synchronizacja pionowa", + "SettingsTabSystemEnablePptc": "PPTC (Profilowana pamięć podręczna trwałych łłumaczeń)", + "SettingsTabSystemEnableFsIntegrityChecks": "Sprawdzanie integralności systemu plików", "SettingsTabSystemAudioBackend": "Backend Dżwięku:", "SettingsTabSystemAudioBackendDummy": "Atrapa", "SettingsTabSystemAudioBackendOpenAL": "OpenAL", @@ -138,31 +143,31 @@ "SettingsTabSystemIgnoreMissingServices": "Ignoruj Brakujące Usługi", "SettingsTabGraphics": "Grafika", "SettingsTabGraphicsAPI": "Graficzne API", - "SettingsTabGraphicsEnableShaderCache": "Włącz Cache Shaderów", - "SettingsTabGraphicsAnisotropicFiltering": "Filtrowanie Anizotropowe:", - "SettingsTabGraphicsAnisotropicFilteringAuto": "Automatyczny", + "SettingsTabGraphicsEnableShaderCache": "Włącz pamięć podręczną cieni", + "SettingsTabGraphicsAnisotropicFiltering": "Filtrowanie anizotropowe:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Automatyczne", "SettingsTabGraphicsAnisotropicFiltering2x": "2x", "SettingsTabGraphicsAnisotropicFiltering4x": "4x", "SettingsTabGraphicsAnisotropicFiltering8x": "8x", "SettingsTabGraphicsAnisotropicFiltering16x": "16x", - "SettingsTabGraphicsResolutionScale": "Skala Rozdzielczości:", + "SettingsTabGraphicsResolutionScale": "Skalowanie rozdzielczości:", "SettingsTabGraphicsResolutionScaleCustom": "Niestandardowa (Niezalecane)", "SettingsTabGraphicsResolutionScaleNative": "Natywna (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", - "SettingsTabGraphicsAspectRatio": "Współczynnik Proporcji:", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (niezalecane)", + "SettingsTabGraphicsAspectRatio": "Format obrazu:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", "SettingsTabGraphicsAspectRatio16x10": "16:10", "SettingsTabGraphicsAspectRatio21x9": "21:9", "SettingsTabGraphicsAspectRatio32x9": "32:9", "SettingsTabGraphicsAspectRatioStretch": "Rozciągnij do Okna", - "SettingsTabGraphicsDeveloperOptions": "Opcje Programistyczne", - "SettingsTabGraphicsShaderDumpPath": "Ścieżka Zrzutu Shaderów Grafiki:", - "SettingsTabLogging": "Logowanie", - "SettingsTabLoggingLogging": "Logowanie", - "SettingsTabLoggingEnableLoggingToFile": "Włącz Logowanie do Pliku", + "SettingsTabGraphicsDeveloperOptions": "Opcje programisty", + "SettingsTabGraphicsShaderDumpPath": "Ścieżka do zgranych cieni graficznych:", + "SettingsTabLogging": "Dziennik zdarzeń", + "SettingsTabLoggingLogging": "Dziennik zdarzeń", + "SettingsTabLoggingEnableLoggingToFile": "Włącz rejestrowanie zdarzeń do pliku", "SettingsTabLoggingEnableStubLogs": "Wlącz Skróty Logów", "SettingsTabLoggingEnableInfoLogs": "Włącz Logi Informacyjne", "SettingsTabLoggingEnableWarningLogs": "Włącz Logi Ostrzeżeń", @@ -170,18 +175,18 @@ "SettingsTabLoggingEnableTraceLogs": "Włącz Logi Śledzenia", "SettingsTabLoggingEnableGuestLogs": "Włącz Logi Gości", "SettingsTabLoggingEnableFsAccessLogs": "Włącz Logi Dostępu do Systemu Plików", - "SettingsTabLoggingFsGlobalAccessLogMode": "Tryb Globalnych Logów Systemu Plików:", - "SettingsTabLoggingDeveloperOptions": "Opcje programistyczne (OSTRZEŻENIE: Zmniejszą wydajność)", - "SettingsTabLoggingDeveloperOptionsNote": "UWAGA: Zredukuje wydajność", - "SettingsTabLoggingGraphicsBackendLogLevel": "Ilość Logów Backendu Graficznego:", - "SettingsTabLoggingGraphicsBackendLogLevelNone": "Żadne", + "SettingsTabLoggingFsGlobalAccessLogMode": "Tryb globalnego dziennika zdarzeń systemu plików:", + "SettingsTabLoggingDeveloperOptions": "Opcje programisty (UWAGA: wpływa na wydajność)", + "SettingsTabLoggingDeveloperOptionsNote": "UWAGA: Pogrorszy wydajność", + "SettingsTabLoggingGraphicsBackendLogLevel": "Poziom rejestrowania do dziennika zdarzeń Backendu Graficznego:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Nic", "SettingsTabLoggingGraphicsBackendLogLevelError": "Błędy", "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Spowolnienia", - "SettingsTabLoggingGraphicsBackendLogLevelAll": "Wszystkie", - "SettingsTabLoggingEnableDebugLogs": "Włącz Logi Debugowania", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Wszystko", + "SettingsTabLoggingEnableDebugLogs": "Włącz dzienniki zdarzeń do debugowania", "SettingsTabInput": "Sterowanie", - "SettingsTabInputEnableDockedMode": "Tryb Zadokowany", - "SettingsTabInputDirectKeyboardAccess": "Bezpośredni Dostęp do Klawiatury", + "SettingsTabInputEnableDockedMode": "Tryb zadokowany", + "SettingsTabInputDirectKeyboardAccess": "Bezpośredni dostęp do klawiatury", "SettingsButtonSave": "Zapisz", "SettingsButtonClose": "Zamknij", "SettingsButtonOk": "OK", @@ -197,10 +202,10 @@ "ControllerSettingsPlayer7": "Gracz 7", "ControllerSettingsPlayer8": "Gracz 8", "ControllerSettingsHandheld": "Przenośny", - "ControllerSettingsInputDevice": "Urządzenie Wejściowe", + "ControllerSettingsInputDevice": "Urządzenie wejściowe", "ControllerSettingsRefresh": "Odśwież", "ControllerSettingsDeviceDisabled": "Wyłączone", - "ControllerSettingsControllerType": "Typ Kontrolera", + "ControllerSettingsControllerType": "Typ kontrolera", "ControllerSettingsControllerTypeHandheld": "Przenośny", "ControllerSettingsControllerTypeProController": "Pro Kontroler", "ControllerSettingsControllerTypeJoyConPair": "Para JoyCon-ów", @@ -218,7 +223,7 @@ "ControllerSettingsButtonY": "Y", "ControllerSettingsButtonPlus": "+", "ControllerSettingsButtonMinus": "-", - "ControllerSettingsDPad": "Pad Kierunkowy", + "ControllerSettingsDPad": "Krzyżak (D-Pad)", "ControllerSettingsDPadUp": "Góra", "ControllerSettingsDPadDown": "Dół", "ControllerSettingsDPadLeft": "Lewo", @@ -261,79 +266,73 @@ "ControllerSettingsMotionGyroDeadzone": "Deadzone Żyroskopu:", "ControllerSettingsSave": "Zapisz", "ControllerSettingsClose": "Zamknij", - "UserProfilesSelectedUserProfile": "Wybrany Profil Użytkownika:", - "UserProfilesSaveProfileName": "Zapisz Nazwę Profilu", - "UserProfilesChangeProfileImage": "Zmień Obraz Profilu", - "UserProfilesAvailableUserProfiles": "Dostępne Profile Użytkowników:", - "UserProfilesAddNewProfile": "Utwórz Profil", - "UserProfilesDelete": "Usuwać", + "UserProfilesSelectedUserProfile": "Wybrany profil użytkownika:", + "UserProfilesSaveProfileName": "Zapisz nazwę profilu", + "UserProfilesChangeProfileImage": "Zmień obrazek profilu", + "UserProfilesAvailableUserProfiles": "Dostępne profile użytkownika:", + "UserProfilesAddNewProfile": "Utwórz profil", + "UserProfilesDelete": "Usuń", "UserProfilesClose": "Zamknij", "ProfileNameSelectionWatermark": "Wybierz pseudonim", "ProfileImageSelectionTitle": "Wybór Obrazu Profilu", "ProfileImageSelectionHeader": "Wybierz zdjęcie profilowe", "ProfileImageSelectionNote": "Możesz zaimportować niestandardowy obraz profilu lub wybrać awatar z firmware'u systemowego", "ProfileImageSelectionImportImage": "Importuj Plik Obrazu", - "ProfileImageSelectionSelectAvatar": "Wybierz Awatar z Firmware'u", + "ProfileImageSelectionSelectAvatar": "Wybierz domyślny awatar z oprogramowania konsoli", "InputDialogTitle": "Okno Dialogowe Wprowadzania", "InputDialogOk": "OK", "InputDialogCancel": "Anuluj", - "InputDialogAddNewProfileTitle": "Wybierz Nazwę Profilu", - "InputDialogAddNewProfileHeader": "Wprowadź Nazwę Profilu", - "InputDialogAddNewProfileSubtext": "(Maksymalna Długość: {0})", - "AvatarChoose": "Wybierz", - "AvatarSetBackgroundColor": "Ustaw Kolor Tła", + "InputDialogAddNewProfileTitle": "Wybierz nazwę profilu", + "InputDialogAddNewProfileHeader": "Wprowadź nazwę profilu", + "InputDialogAddNewProfileSubtext": "(Maksymalna długość: {0})", + "AvatarChoose": "Wybierz awatar", + "AvatarSetBackgroundColor": "Ustaw kolor tła", "AvatarClose": "Zamknij", - "ControllerSettingsLoadProfileToolTip": "Załaduj Profil", - "ControllerSettingsAddProfileToolTip": "Dodaj Profil", - "ControllerSettingsRemoveProfileToolTip": "Usuń Profil", - "ControllerSettingsSaveProfileToolTip": "Zapisz Profil", - "MenuBarFileToolsTakeScreenshot": "Zrób Zrzut Ekranu", - "MenuBarFileToolsHideUi": "Ukryj UI", + "ControllerSettingsLoadProfileToolTip": "Wczytaj profil", + "ControllerSettingsAddProfileToolTip": "Dodaj profil", + "ControllerSettingsRemoveProfileToolTip": "Usuń profil", + "ControllerSettingsSaveProfileToolTip": "Zapisz profil", + "MenuBarFileToolsTakeScreenshot": "Zrób zrzut ekranu", + "MenuBarFileToolsHideUi": "Ukryj interfejs użytkownika", "GameListContextMenuRunApplication": "Uruchom aplikację ", - "GameListContextMenuToggleFavorite": "Przełącz Ulubione", + "GameListContextMenuToggleFavorite": "Przełącz na ulubione", "GameListContextMenuToggleFavoriteToolTip": "Przełącz status Ulubionej Gry", - "SettingsTabGeneralTheme": "Motyw", - "SettingsTabGeneralThemeCustomTheme": "Ścieżka Niestandardowych Motywów", - "SettingsTabGeneralThemeBaseStyle": "Styl Podstawowy", - "SettingsTabGeneralThemeBaseStyleDark": "Ciemny", - "SettingsTabGeneralThemeBaseStyleLight": "Jasny", - "SettingsTabGeneralThemeEnableCustomTheme": "Włącz Niestandardowy Motyw", - "ButtonBrowse": "Przeglądaj", + "SettingsTabGeneralTheme": "Motyw:", + "SettingsTabGeneralThemeDark": "Ciemny", + "SettingsTabGeneralThemeLight": "Jasny", "ControllerSettingsConfigureGeneral": "Konfiguruj", "ControllerSettingsRumble": "Wibracje", - "ControllerSettingsRumbleStrongMultiplier": "Mocny Mnożnik Wibracji", - "ControllerSettingsRumbleWeakMultiplier": "Słaby Mnożnik Wibracji", - "DialogMessageSaveNotAvailableMessage": "Nie ma danych zapisu dla {0} [{1:x16}]", - "DialogMessageSaveNotAvailableCreateSaveMessage": "Czy chcesz utworzyć dane zapisu dla tej gry?", + "ControllerSettingsRumbleStrongMultiplier": "Mnożnik mocnych wibracji", + "ControllerSettingsRumbleWeakMultiplier": "Mnożnik słabych wibracji", + "DialogMessageSaveNotAvailableMessage": "Nie ma zapisanych danych dla {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Czy chcesz utworzyć zapis danych dla tej gry?", "DialogConfirmationTitle": "Ryujinx - Potwierdzenie", - "DialogUpdaterTitle": "Ryujinx - Aktualizator", + "DialogUpdaterTitle": "Ryujinx - Asystent aktualizacji", "DialogErrorTitle": "Ryujinx - Błąd", - "DialogWarningTitle": "Ryujinx - Uwaga", + "DialogWarningTitle": "Ryujinx - Ostrzeżenie", "DialogExitTitle": "Ryujinx - Wyjdź", "DialogErrorMessage": "Ryujinx napotkał błąd", "DialogExitMessage": "Czy na pewno chcesz zamknąć Ryujinx?", "DialogExitSubMessage": "Wszystkie niezapisane dane zostaną utracone!", - "DialogMessageCreateSaveErrorMessage": "Wystąpił błąd podczas tworzenia określonych danych zapisu: {0}", - "DialogMessageFindSaveErrorMessage": "Wystąpił błąd podczas znajdowania określonych danych zapisu: {0}", - "FolderDialogExtractTitle": "Wybierz folder do rozpakowania", - "DialogNcaExtractionMessage": "Wyodrębnianie sekcji {0} z {1}...", - "DialogNcaExtractionTitle": "Ryujinx - Ekstraktor Sekcji NCA", - "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Niepowodzenie ekstrakcji. W wybranym pliku nie było głównego NCA.", - "DialogNcaExtractionCheckLogErrorMessage": "Niepowodzenie ekstrakcji. Przeczytaj plik dziennika, aby uzyskać więcej informacji.", - "DialogNcaExtractionSuccessMessage": "Ekstrakcja zakończona pomyślnie.", + "DialogMessageCreateSaveErrorMessage": "Wystąpił błąd podczas tworzenia określonych zapisanych danych: {0}", + "DialogMessageFindSaveErrorMessage": "Wystąpił błąd podczas próby znalezienia określonych zapisanych danych: {0}", + "FolderDialogExtractTitle": "Wybierz folder, do którego chcesz rozpakować", + "DialogNcaExtractionMessage": "Wypakowywanie sekcji {0} z {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Asystent wypakowania sekcji NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Niepowodzenie podczas wypakowywania. W wybranym pliku nie było głównego NCA.", + "DialogNcaExtractionCheckLogErrorMessage": "Niepowodzenie podczas wypakowywania. Przeczytaj plik dziennika, aby uzyskać więcej informacji.", + "DialogNcaExtractionSuccessMessage": "Wypakowywanie zakończone pomyślnie.", "DialogUpdaterConvertFailedMessage": "Nie udało się przekonwertować obecnej wersji Ryujinx.", - "DialogUpdaterCancelUpdateMessage": "Anulowanie Aktualizacji!", + "DialogUpdaterCancelUpdateMessage": "Anulowanie aktualizacji!", "DialogUpdaterAlreadyOnLatestVersionMessage": "Używasz już najnowszej wersji Ryujinx!", - "DialogUpdaterFailedToGetVersionMessage": "Wystąpił błąd podczas próby uzyskania informacji o wydaniu z GitHub Release. Może to być spowodowane nową wersją kompilowaną przez GitHub Actions. Spróbuj ponownie za kilka minut.", + "DialogUpdaterFailedToGetVersionMessage": "Wystąpił błąd podczas próby uzyskania informacji o obecnej wersji z GitHub Release. Może to być spowodowane nową wersją kompilowaną przez GitHub Actions. Spróbuj ponownie za kilka minut.", "DialogUpdaterConvertFailedGithubMessage": "Nie udało się przekonwertować otrzymanej wersji Ryujinx z Github Release.", - "DialogUpdaterDownloadingMessage": "Pobieranie Aktualizacji...", + "DialogUpdaterDownloadingMessage": "Pobieranie aktualizacji...", "DialogUpdaterExtractionMessage": "Wypakowywanie Aktualizacji...", "DialogUpdaterRenamingMessage": "Zmiana Nazwy Aktualizacji...", "DialogUpdaterAddingFilesMessage": "Dodawanie Nowej Aktualizacji...", "DialogUpdaterCompleteMessage": "Aktualizacja Zakończona!", "DialogUpdaterRestartMessage": "Czy chcesz teraz zrestartować Ryujinx?", - "DialogUpdaterArchNotSupportedMessage": "Nie używasz obsługiwanej architektury systemu!", - "DialogUpdaterArchNotSupportedSubMessage": "(Obsługiwane są tylko systemy x64!)", "DialogUpdaterNoInternetMessage": "Nie masz połączenia z Internetem!", "DialogUpdaterNoInternetSubMessage": "Sprawdź, czy masz działające połączenie internetowe!", "DialogUpdaterDirtyBuildMessage": "Nie możesz zaktualizować Dirty wersji Ryujinx!", @@ -385,7 +384,10 @@ "DialogUserProfileUnsavedChangesSubMessage": "Czy chcesz odrzucić zmiany?", "DialogControllerSettingsModifiedConfirmMessage": "Aktualne ustawienia kontrolera zostały zaktualizowane.", "DialogControllerSettingsModifiedConfirmSubMessage": "Czy chcesz zapisać?", - "DialogLoadNcaErrorMessage": "{0}. Błędny Plik: {1}", + "DialogLoadFileErrorMessage": "{0}. Błędny plik: {1}", + "DialogModAlreadyExistsMessage": "Modyfikacja już istnieje", + "DialogModInvalidMessage": "Podany katalog nie zawiera modyfikacji!", + "DialogModDeleteNoParentMessage": "Nie udało się usunąć: Nie można odnaleźć katalogu nadrzędnego dla modyfikacji \"{0}\"!", "DialogDlcNoDlcErrorMessage": "Określony plik nie zawiera DLC dla wybranego tytułu!", "DialogPerformanceCheckLoggingEnabledMessage": "Masz włączone rejestrowanie śledzenia, które jest przeznaczone tylko dla programistów.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Aby uzyskać optymalną wydajność, zaleca się wyłączenie rejestrowania śledzenia. Czy chcesz teraz wyłączyć rejestrowanie śledzenia?", @@ -396,6 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "Określony plik nie zawiera aktualizacji dla wybranego tytułu!", "DialogSettingsBackendThreadingWarningTitle": "Ostrzeżenie — Wątki Backend", "DialogSettingsBackendThreadingWarningMessage": "Ryujinx musi zostać ponownie uruchomiony po zmianie tej opcji, aby działał w pełni. W zależności od platformy może być konieczne ręczne wyłączenie sterownika wielowątkowości podczas korzystania z Ryujinx.", + "DialogModManagerDeletionWarningMessage": "Zamierzasz usunąć modyfikacje: {0}\n\nCzy na pewno chcesz kontynuować?", + "DialogModManagerDeletionAllWarningMessage": "Zamierzasz usunąć wszystkie modyfikacje dla wybranego tytułu: {0}\n\nCzy na pewno chcesz kontynuować?", "SettingsTabGraphicsFeaturesOptions": "Funkcje", "SettingsTabGraphicsBackendMultithreading": "Wielowątkowość Backendu Graficznego:", "CommonAuto": "Auto", @@ -430,6 +434,7 @@ "DlcManagerRemoveAllButton": "Usuń Wszystkie", "DlcManagerEnableAllButton": "Włącz Wszystkie", "DlcManagerDisableAllButton": "Wyłącz Wszystkie", + "ModManagerDeleteAllButton": "Usuń wszystko", "MenuBarOptionsChangeLanguage": "Zmień Język", "MenuBarShowFileTypes": "Pokaż typy plików", "CommonSort": "Sortuj", @@ -447,13 +452,13 @@ "CustomThemePathTooltip": "Ścieżka do niestandardowego motywu GUI", "CustomThemeBrowseTooltip": "Wyszukaj niestandardowy motyw GUI", "DockModeToggleTooltip": "Tryb Zadokowany sprawia, że emulowany system zachowuje się jak zadokowany Nintendo Switch. Poprawia to jakość grafiki w większości gier. I odwrotnie, wyłączenie tej opcji sprawi, że emulowany system będzie zachowywał się jak przenośny Nintendo Switch, zmniejszając jakość grafiki.\n\nSkonfiguruj sterowanie gracza 1, jeśli planujesz używać trybu Zadokowanego; Skonfiguruj sterowanie przenośne, jeśli planujesz używać trybu przenośnego.\n\nPozostaw WŁĄCZONY, jeśli nie masz pewności.", - "DirectKeyboardTooltip": "Obsługa bezpośredniego dostępu klawiatury (HID). Zapewnia dostęp gier do klawiatury jako urządzenia do wprowadzania tekstu.", - "DirectMouseTooltip": "Obsługa bezpośredniego dostępu myszy (HID). Zapewnia grom dostęp do myszy jako urządzenia wskazującego.", + "DirectKeyboardTooltip": "Obsługa bezpośredniego dostępu do klawiatury (HID). Zapewnia dostęp gier do klawiatury jako urządzenia do wprowadzania tekstu.\n\nDziała tylko z grami, które natywnie wspierają użycie klawiatury w urządzeniu Switch hardware.\n\nPozostaw wyłączone w razie braku pewności.", + "DirectMouseTooltip": "Obsługa bezpośredniego dostępu do myszy (HID). Zapewnia dostęp gier do myszy jako urządzenia wskazującego.\n\nDziała tylko z grami, które natywnie obsługują przyciski myszy w urządzeniu Switch, które są nieliczne i daleko między nimi.\n\nPo włączeniu funkcja ekranu dotykowego może nie działać.\n\nPozostaw wyłączone w razie wątpliwości.", "RegionTooltip": "Zmień Region Systemu", "LanguageTooltip": "Zmień Język Systemu", "TimezoneTooltip": "Zmień Strefę Czasową Systemu", - "TimeTooltip": "Zmień Czas Systemu", - "VSyncToggleTooltip": "Pionowa synchronizacja emulowanej konsoli. Zasadniczo ogranicznik klatek dla większości gier; wyłączenie jej może spowodować, że gry będą działać z większą szybkością, ekrany wczytywania wydłużą się lub nawet utkną.\n\nMoże być przełączana w grze za pomocą preferowanego skrótu klawiszowego. Zalecamy to zrobić, jeśli planujesz ją wyłączyć.\n\nW razie wątpliwości pozostaw WŁĄCZONĄ", + "TimeTooltip": "Zmień czas systemowy", + "VSyncToggleTooltip": "Synchronizacja pionowa emulowanej konsoli. Zasadniczo ogranicznik klatek dla większości gier; wyłączenie jej może spowodować, że gry będą działać z większą szybkością, ekrany wczytywania wydłużą się lub nawet utkną.\n\nMoże być przełączana w grze za pomocą preferowanego skrótu klawiszowego. Zalecamy to zrobić, jeśli planujesz ją wyłączyć.\n\nW razie wątpliwości pozostaw WŁĄCZONĄ.", "PptcToggleTooltip": "Zapisuje przetłumaczone funkcje JIT, dzięki czemu nie muszą być tłumaczone za każdym razem, gdy gra się ładuje.\n\nZmniejsza zacinanie się i znacznie przyspiesza uruchamianie po pierwszym uruchomieniu gry.\n\nJeśli nie masz pewności, pozostaw WŁĄCZONE", "FsIntegrityToggleTooltip": "Sprawdza pliki podczas uruchamiania gry i jeśli zostaną wykryte uszkodzone pliki, wyświetla w dzienniku błąd hash.\n\nNie ma wpływu na wydajność i ma pomóc w rozwiązywaniu problemów.\n\nPozostaw WŁĄCZONE, jeśli nie masz pewności.", "AudioBackendTooltip": "Zmienia backend używany do renderowania dźwięku.\n\nSDL2 jest preferowany, podczas gdy OpenAL i SoundIO są używane jako rezerwy. Dummy nie będzie odtwarzać dźwięku.\n\nW razie wątpliwości ustaw SDL2.", @@ -467,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "Wykonuje polecenia backend'u graficznego w drugim wątku.\n\nPrzyspiesza kompilację shaderów, zmniejsza zacinanie się i poprawia wydajność sterowników GPU bez własnej obsługi wielowątkowości. Nieco lepsza wydajność w sterownikach z wielowątkowością.\n\nUstaw na AUTO, jeśli nie masz pewności.", "GalThreadingTooltip": "Wykonuje polecenia backend'u graficznego w drugim wątku.\n\nPrzyspiesza kompilację shaderów, zmniejsza zacinanie się i poprawia wydajność sterowników GPU bez własnej obsługi wielowątkowości. Nieco lepsza wydajność w sterownikach z wielowątkowością.\n\nUstaw na AUTO, jeśli nie masz pewności.", "ShaderCacheToggleTooltip": "Zapisuje pamięć podręczną shaderów na dysku, co zmniejsza zacinanie się w kolejnych uruchomieniach.\n\nPozostaw WŁĄCZONE, jeśli nie masz pewności.", - "ResolutionScaleTooltip": "Skala Rozdzielczości zastosowana do odpowiednich celów renderowania", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", "ResolutionScaleEntryTooltip": "Skala rozdzielczości zmiennoprzecinkowej, np. 1,5. Skale niecałkowite częściej powodują problemy lub awarie.", - "AnisotropyTooltip": "Poziom filtrowania anizotropowego (ustaw na Auto, aby użyć wartości wymaganej przez grę)", - "AspectRatioTooltip": "Współczynnik proporcji zastosowany do okna renderowania.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", "ShaderDumpPathTooltip": "Ścieżka Zrzutu Shaderów Grafiki", "FileLogTooltip": "Zapisuje logowanie konsoli w pliku dziennika na dysku. Nie wpływa na wydajność.", "StubLogTooltip": "Wyświetla w konsoli skrótowe komunikaty dziennika. Nie wpływa na wydajność.", @@ -504,6 +509,8 @@ "EnableInternetAccessTooltip": "Pozwala emulowanej aplikacji na łączenie się z Internetem.\n\nGry w trybie LAN mogą łączyć się ze sobą, gdy ta opcja jest włączona, a systemy są połączone z tym samym punktem dostępu. Dotyczy to również prawdziwych konsol.\n\nNie pozwala na łączenie się z serwerami Nintendo. Może powodować awarie niektórych gier, które próbują połączyć się z Internetem.\n\nPozostaw WYŁĄCZONE, jeśli nie masz pewności.", "GameListContextMenuManageCheatToolTip": "Zarządzaj Kodami", "GameListContextMenuManageCheat": "Zarządzaj Kodami", + "GameListContextMenuManageModToolTip": "Zarządzaj modyfikacjami", + "GameListContextMenuManageMod": "Zarządzaj modyfikacjami", "ControllerSettingsStickRange": "Zasięg:", "DialogStopEmulationTitle": "Ryujinx - Zatrzymaj Emulację", "DialogStopEmulationMessage": "Czy na pewno chcesz zatrzymać emulację?", @@ -515,8 +522,6 @@ "SettingsTabCpuMemory": "Pamięć CPU", "DialogUpdaterFlatpakNotSupportedMessage": "Zaktualizuj Ryujinx przez FlatHub.", "UpdaterDisabledWarningTitle": "Aktualizator Wyłączony!", - "GameListContextMenuOpenSdModsDirectory": "Otwórz Katalog Modów Atmosphere", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Otwiera alternatywny katalog Atmosphere na karcie SD, który zawiera modyfikacje aplikacji. Przydatne dla modów, które są pakowane dla prawdziwego sprzętu.", "ControllerSettingsRotate90": "Obróć o 90° w Prawo", "IconSize": "Rozmiar Ikon", "IconSizeTooltip": "Zmień rozmiar ikon gry", @@ -544,12 +549,13 @@ "SwkbdMinCharacters": "Musi mieć co najmniej {0} znaków", "SwkbdMinRangeCharacters": "Musi mieć długość od {0}-{1} znaków", "SoftwareKeyboard": "Klawiatura Oprogramowania", - "SoftwareKeyboardModeNumbersOnly": "Mogą być tylko liczby ", + "SoftwareKeyboardModeNumeric": "Może składać się jedynie z 0-9 lub '.'", "SoftwareKeyboardModeAlphabet": "Nie może zawierać znaków CJK", "SoftwareKeyboardModeASCII": "Musi zawierać tylko tekst ASCII", - "DialogControllerAppletMessagePlayerRange": "Aplikacja żąda {0} graczy z:\n\nTYPY: {1}\n\nGRACZE: {2}\n\n{3}Otwórz Ustawienia i ponownie skonfiguruj Sterowanie lub naciśnij Zamknij.", - "DialogControllerAppletMessage": "Aplikacja żąda dokładnie {0} graczy z:\n\nTYPY: {1}\n\nGRACZE: {2}\n\n{3}Otwórz teraz Ustawienia i ponownie skonfiguruj Sterowanie lub naciśnij Zamknij.", - "DialogControllerAppletDockModeSet": "Ustawiono tryb Zadokowane. Przenośny też jest nieprawidłowy.\n\n", + "ControllerAppletControllers": "Obsługiwane Kontrolery:", + "ControllerAppletPlayers": "Gracze:", + "ControllerAppletDescription": "Twoja aktualna konfiguracja jest nieprawidłowa. Otwórz ustawienia i skonfiguruj swoje wejścia.", + "ControllerAppletDocked": "Ustawiony tryb zadokowany. Sterowanie przenośne powinno być wyłączone.", "UpdaterRenaming": "Zmienianie Nazw Starych Plików...", "UpdaterRenameFailed": "Aktualizator nie mógł zmienić nazwy pliku: {0}", "UpdaterAddingFiles": "Dodawanie Nowych Plików...", @@ -587,6 +593,7 @@ "Writable": "Zapisywalne", "SelectDlcDialogTitle": "Wybierz pliki DLC", "SelectUpdateDialogTitle": "Wybierz pliki aktualizacji", + "SelectModDialogTitle": "Wybierz katalog modów", "UserProfileWindowTitle": "Menedżer Profili Użytkowników", "CheatWindowTitle": "Menedżer Kodów", "DlcWindowTitle": "Menedżer Zawartości do Pobrania", @@ -594,10 +601,12 @@ "CheatWindowHeading": "Kody Dostępne dla {0} [{1}]", "BuildId": "Identyfikator wersji:", "DlcWindowHeading": "{0} Zawartości do Pobrania dostępna dla {1} ({2})", + "ModWindowHeading": "{0} Mod(y/ów)", "UserProfilesEditProfile": "Edytuj Zaznaczone", "Cancel": "Anuluj", "Save": "Zapisz", "Discard": "Odrzuć", + "Paused": "Wstrzymano", "UserProfilesSetProfileImage": "Ustaw Obraz Profilu", "UserProfileEmptyNameError": "Nazwa jest wymagana", "UserProfileNoImageError": "Należy ustawić obraz profilowy", @@ -607,9 +616,9 @@ "UserProfilesName": "Nazwa:", "UserProfilesUserId": "ID Użytkownika:", "SettingsTabGraphicsBackend": "Backend Graficzny", - "SettingsTabGraphicsBackendTooltip": "Używalne Backendy Graficzne", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", "SettingsEnableTextureRecompression": "Włącz Rekompresję Tekstur", - "SettingsEnableTextureRecompressionTooltip": "Kompresuje niektóre tekstury w celu zmniejszenia zużycia pamięci VRAM.\n\nZalecane do użytku z GPU, które mają mniej niż 4 GiB pamięci VRAM.\n\nW razie wątpliwości pozostaw WYŁĄCZONE.", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", "SettingsTabGraphicsPreferredGpu": "Preferowane GPU", "SettingsTabGraphicsPreferredGpuTooltip": "Wybierz kartę graficzną, która będzie używana z backendem graficznym Vulkan.\n\nNie wpływa na GPU używane przez OpenGL.\n\nW razie wątpliwości ustaw flagę GPU jako \"dGPU\". Jeśli żadnej nie ma, pozostaw nietknięte.", "SettingsAppRequiredRestartMessage": "Wymagane Zrestartowanie Ryujinx", @@ -620,8 +629,8 @@ "SettingsTabHotkeysVolumeDownHotkey": "Zmniejsz Głośność:", "SettingsEnableMacroHLE": "Włącz Macro HLE", "SettingsEnableMacroHLETooltip": "Wysokopoziomowa emulacja kodu GPU Macro.\n\nPoprawia wydajność, ale może powodować błędy graficzne w niektórych grach.\n\nW razie wątpliwości pozostaw WŁĄCZONE.", - "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", - "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", + "SettingsEnableColorSpacePassthrough": "Przekazywanie przestrzeni kolorów", + "SettingsEnableColorSpacePassthroughTooltip": "Nakazuje API Vulkan przekazywać informacje o kolorze bez określania przestrzeni kolorów. Dla użytkowników z wyświetlaczami o szerokim zakresie kolorów może to skutkować bardziej żywymi kolorami, kosztem ich poprawności.", "VolumeShort": "Głoś", "UserProfilesManageSaves": "Zarządzaj Zapisami", "DeleteUserSave": "Czy chcesz usunąć zapis użytkownika dla tej gry?", @@ -635,12 +644,12 @@ "Recover": "Odzyskaj", "UserProfilesRecoverHeading": "Znaleziono zapisy dla następujących kont", "UserProfilesRecoverEmptyList": "Brak profili do odzyskania", - "GraphicsAATooltip": "Stosuje antyaliasing do renderowania gry", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", "GraphicsAALabel": "Antyaliasing:", "GraphicsScalingFilterLabel": "Filtr skalowania:", - "GraphicsScalingFilterTooltip": "Włącza skalowanie bufora ramki", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", "GraphicsScalingFilterLevelLabel": "Poziom", - "GraphicsScalingFilterLevelTooltip": "Ustaw poziom filtra skalowania", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA Niskie", "SmaaMedium": "SMAA Średnie", "SmaaHigh": "SMAA Wysokie", @@ -648,9 +657,12 @@ "UserEditorTitle": "Edytuj użytkownika", "UserEditorTitleCreate": "Utwórz użytkownika", "SettingsTabNetworkInterface": "Interfejs sieci:", - "NetworkInterfaceTooltip": "Interfejs sieciowy używany do funkcji LAN", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", "NetworkInterfaceDefault": "Domyślny", "PackagingShaders": "Pakuje Shadery ", "AboutChangelogButton": "Zobacz listę zmian na GitHubie", - "AboutChangelogButtonTooltipMessage": "Kliknij, aby otworzyć listę zmian dla tej wersji w domyślnej przeglądarce." -} \ No newline at end of file + "AboutChangelogButtonTooltipMessage": "Kliknij, aby otworzyć listę zmian dla tej wersji w domyślnej przeglądarce.", + "SettingsTabNetworkMultiplayer": "Gra Wieloosobowa", + "MultiplayerMode": "Tryb:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/pt_BR.json b/src/Ryujinx/Assets/Locales/pt_BR.json index 8909a84fe..cd6a2fd1c 100644 --- a/src/Ryujinx/Assets/Locales/pt_BR.json +++ b/src/Ryujinx/Assets/Locales/pt_BR.json @@ -30,7 +30,7 @@ "MenuBarToolsManageFileTypes": "Gerenciar tipos de arquivo", "MenuBarToolsInstallFileTypes": "Instalar tipos de arquivo", "MenuBarToolsUninstallFileTypes": "Desinstalar tipos de arquivos", - "MenuBarHelp": "A_juda", + "MenuBarHelp": "_Ajuda", "MenuBarHelpCheckForUpdates": "_Verificar se há atualizações", "MenuBarHelpAbout": "_Sobre", "MenuSearch": "Buscar...", @@ -54,8 +54,6 @@ "GameListContextMenuManageTitleUpdatesToolTip": "Abre a janela de gerenciamento de atualizações", "GameListContextMenuManageDlc": "Gerenciar DLCs", "GameListContextMenuManageDlcToolTip": "Abre a janela de gerenciamento de DLCs", - "GameListContextMenuOpenModsDirectory": "Abrir diretório de mods", - "GameListContextMenuOpenModsDirectoryToolTip": "Abre o diretório que contém modificações (mods) do jogo", "GameListContextMenuCacheManagement": "Gerenciamento de cache", "GameListContextMenuCacheManagementPurgePptc": "Limpar cache PPTC", "GameListContextMenuCacheManagementPurgePptcToolTip": "Deleta o cache PPTC armazenado em disco do jogo", @@ -72,15 +70,22 @@ "GameListContextMenuExtractDataRomFSToolTip": "Extrai a seção RomFS do jogo (incluindo atualizações)", "GameListContextMenuExtractDataLogo": "Logo", "GameListContextMenuExtractDataLogoToolTip": "Extrai a seção Logo do jogo (incluindo atualizações)", + "GameListContextMenuCreateShortcut": "Criar atalho da aplicação", + "GameListContextMenuCreateShortcutToolTip": "Criar um atalho de área de trabalho que inicia o aplicativo selecionado", + "GameListContextMenuCreateShortcutToolTipMacOS": "Crie um atalho na pasta Aplicativos do macOS que abre o Aplicativo selecionado", + "GameListContextMenuOpenModsDirectory": "Abrir pasta de Mods", + "GameListContextMenuOpenModsDirectoryToolTip": "Abre a pasta que contém os mods da aplicação ", + "GameListContextMenuOpenSdModsDirectory": "Abrir diretório de mods Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", "StatusBarGamesLoaded": "{0}/{1} jogos carregados", "StatusBarSystemVersion": "Versão do firmware: {0}", - "LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected", - "LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}", - "LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "Yes, until the next restart", - "LinuxVmMaxMapCountDialogButtonPersistent": "Yes, permanently", - "LinuxVmMaxMapCountWarningTextPrimary": "Max amount of memory mappings is lower than recommended.", - "LinuxVmMaxMapCountWarningTextSecondary": "The current value of vm.max_map_count ({0}) is lower than {1}. Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.\n\nYou might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.", + "LinuxVmMaxMapCountDialogTitle": "Limite baixo para mapeamentos de memória detectado", + "LinuxVmMaxMapCountDialogTextPrimary": "Você gostaria de aumentar o valor de vm.max_map_count para {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Alguns jogos podem tentar criar mais mapeamentos de memória do que o atualmente permitido. Ryujinx irá falhar assim que este limite for excedido.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Sim, até a próxima reinicialização", + "LinuxVmMaxMapCountDialogButtonPersistent": "Sim, permanentemente", + "LinuxVmMaxMapCountWarningTextPrimary": "A quantidade máxima de mapeamentos de memória é menor que a recomendada.", + "LinuxVmMaxMapCountWarningTextSecondary": "O valor atual de vm.max_map_count ({0}) é menor que {1}. Alguns jogos podem tentar criar mais mapeamentos de memória do que o permitido no momento. Ryujinx vai falhar assim que este limite for excedido.\n\nTalvez você queira aumentar o limite manualmente ou instalar pkexec, o que permite que Ryujinx ajude com isso.", "Settings": "Configurações", "SettingsTabGeneral": "Geral", "SettingsTabGeneralGeneral": "Geral", @@ -150,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (não recomendado)", "SettingsTabGraphicsAspectRatio": "Proporção:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -223,15 +228,15 @@ "ControllerSettingsDPadDown": "Baixo", "ControllerSettingsDPadLeft": "Esquerda", "ControllerSettingsDPadRight": "Direita", - "ControllerSettingsStickButton": "Button", - "ControllerSettingsStickUp": "Up", - "ControllerSettingsStickDown": "Down", - "ControllerSettingsStickLeft": "Left", - "ControllerSettingsStickRight": "Right", - "ControllerSettingsStickStick": "Stick", - "ControllerSettingsStickInvertXAxis": "Invert Stick X", - "ControllerSettingsStickInvertYAxis": "Invert Stick Y", - "ControllerSettingsStickDeadzone": "Deadzone:", + "ControllerSettingsStickButton": "Botão", + "ControllerSettingsStickUp": "Cima", + "ControllerSettingsStickDown": "Baixo", + "ControllerSettingsStickLeft": "Esquerda", + "ControllerSettingsStickRight": "Direita", + "ControllerSettingsStickStick": "Analógico", + "ControllerSettingsStickInvertXAxis": "Inverter eixo X", + "ControllerSettingsStickInvertYAxis": "Inverter eixo Y", + "ControllerSettingsStickDeadzone": "Zona morta:", "ControllerSettingsLStick": "Analógico esquerdo", "ControllerSettingsRStick": "Analógico direito", "ControllerSettingsTriggersLeft": "Gatilhos esquerda", @@ -289,16 +294,12 @@ "ControllerSettingsSaveProfileToolTip": "Salvar perfil", "MenuBarFileToolsTakeScreenshot": "Salvar captura de tela", "MenuBarFileToolsHideUi": "Esconder Interface", - "GameListContextMenuRunApplication": "Run Application", + "GameListContextMenuRunApplication": "Executar Aplicativo", "GameListContextMenuToggleFavorite": "Alternar favorito", "GameListContextMenuToggleFavoriteToolTip": "Marca ou desmarca jogo como favorito", - "SettingsTabGeneralTheme": "Tema", - "SettingsTabGeneralThemeCustomTheme": "Diretório de tema customizado", - "SettingsTabGeneralThemeBaseStyle": "Estilo base", - "SettingsTabGeneralThemeBaseStyleDark": "Escuro", - "SettingsTabGeneralThemeBaseStyleLight": "Claro", - "SettingsTabGeneralThemeEnableCustomTheme": "Habilitar tema customizado", - "ButtonBrowse": "Procurar", + "SettingsTabGeneralTheme": "Tema:", + "SettingsTabGeneralThemeDark": "Escuro", + "SettingsTabGeneralThemeLight": "Claro", "ControllerSettingsConfigureGeneral": "Configurar", "ControllerSettingsRumble": "Vibração", "ControllerSettingsRumbleStrongMultiplier": "Multiplicador de vibração forte", @@ -332,8 +333,6 @@ "DialogUpdaterAddingFilesMessage": "Adicionando nova atualização...", "DialogUpdaterCompleteMessage": "Atualização concluída!", "DialogUpdaterRestartMessage": "Deseja reiniciar o Ryujinx agora?", - "DialogUpdaterArchNotSupportedMessage": "Você não está rodando uma arquitetura de sistema suportada!", - "DialogUpdaterArchNotSupportedSubMessage": "(Apenas sistemas x64 são suportados!)", "DialogUpdaterNoInternetMessage": "Você não está conectado à Internet!", "DialogUpdaterNoInternetSubMessage": "Por favor, certifique-se de que você tem uma conexão funcional à Internet!", "DialogUpdaterDirtyBuildMessage": "Você não pode atualizar uma compilação Dirty do Ryujinx!", @@ -385,7 +384,10 @@ "DialogUserProfileUnsavedChangesSubMessage": "Deseja descartar as alterações?", "DialogControllerSettingsModifiedConfirmMessage": "As configurações de controle atuais foram atualizadas.", "DialogControllerSettingsModifiedConfirmSubMessage": "Deseja salvar?", - "DialogLoadNcaErrorMessage": "{0}. Arquivo com erro: {1}", + "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", + "DialogModAlreadyExistsMessage": "Mod already exists", + "DialogModInvalidMessage": "The specified directory does not contain a mod!", + "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", "DialogDlcNoDlcErrorMessage": "O arquivo especificado não contém DLCs para o título selecionado!", "DialogPerformanceCheckLoggingEnabledMessage": "Os logs de depuração estão ativos, esse recurso é feito para ser usado apenas por desenvolvedores.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Para melhor performance, é recomendável desabilitar os logs de depuração. Gostaria de desabilitar os logs de depuração agora?", @@ -396,6 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "O arquivo especificado não contém atualizações para o título selecionado!", "DialogSettingsBackendThreadingWarningTitle": "Alerta - Threading da API gráfica", "DialogSettingsBackendThreadingWarningMessage": "Ryujinx precisa ser reiniciado após mudar essa opção para que ela tenha efeito. Dependendo da sua plataforma, pode ser preciso desabilitar o multithreading do driver de vídeo quando usar o Ryujinx.", + "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", "SettingsTabGraphicsFeaturesOptions": "Recursos", "SettingsTabGraphicsBackendMultithreading": "Multithreading da API gráfica:", "CommonAuto": "Automático", @@ -430,6 +434,7 @@ "DlcManagerRemoveAllButton": "Remover todos", "DlcManagerEnableAllButton": "Habilitar todos", "DlcManagerDisableAllButton": "Desabilitar todos", + "ModManagerDeleteAllButton": "Delete All", "MenuBarOptionsChangeLanguage": "Mudar idioma", "MenuBarShowFileTypes": "Mostrar tipos de arquivo", "CommonSort": "Ordenar", @@ -447,13 +452,13 @@ "CustomThemePathTooltip": "Diretório do tema customizado", "CustomThemeBrowseTooltip": "Navegar até um tema customizado", "DockModeToggleTooltip": "Habilita ou desabilita modo TV", - "DirectKeyboardTooltip": "Habilita ou desabilita \"acesso direto ao teclado (HID)\" (Permite que o jogo acesse o seu teclado como dispositivo de entrada de texto)", - "DirectMouseTooltip": "Habilita ou desabilita \"acesso direto ao mouse (HID)\" (Permite que o jogo acesse o seu mouse como dispositivo apontador)", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", "RegionTooltip": "Mudar a região do sistema", "LanguageTooltip": "Mudar o idioma do sistema", "TimezoneTooltip": "Mudar o fuso-horário do sistema", "TimeTooltip": "Mudar a hora do sistema", - "VSyncToggleTooltip": "Habilita ou desabilita a sincronia vertical", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", "PptcToggleTooltip": "Habilita ou desabilita PPTC", "FsIntegrityToggleTooltip": "Habilita ou desabilita verificação de integridade dos arquivos do jogo", "AudioBackendTooltip": "Mudar biblioteca de áudio", @@ -467,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "Habilita multithreading do backend gráfico", "GalThreadingTooltip": "Executa comandos do backend gráfico em uma segunda thread. Permite multithreading em tempo de execução da compilação de shader, diminui os travamentos, e melhora performance em drivers sem suporte embutido a multithreading. Pequena variação na performance máxima em drivers com suporte a multithreading. Ryujinx pode precisar ser reiniciado para desabilitar adequadamente o multithreading embutido do driver, ou você pode precisar fazer isso manualmente para ter a melhor performance.", "ShaderCacheToggleTooltip": "Habilita ou desabilita o cache de shader", - "ResolutionScaleTooltip": "Escala de resolução aplicada às texturas de renderização", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", "ResolutionScaleEntryTooltip": "Escala de resolução de ponto flutuante, como 1.5. Valores não inteiros tem probabilidade maior de causar problemas ou quebras.", - "AnisotropyTooltip": "Nível de filtragem anisotrópica (deixe em Auto para usar o valor solicitado pelo jogo)", - "AspectRatioTooltip": "Taxa de proporção aplicada à janela do renderizador.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", "ShaderDumpPathTooltip": "Diretòrio de despejo de shaders", "FileLogTooltip": "Habilita ou desabilita log para um arquivo no disco", "StubLogTooltip": "Habilita ou desabilita exibição de mensagens de stub", @@ -504,6 +509,8 @@ "EnableInternetAccessTooltip": "Habilita acesso à internet do programa convidado. Se habilitado, o aplicativo vai se comportar como se o sistema Switch emulado estivesse conectado a Internet. Note que em alguns casos, aplicativos podem acessar a Internet mesmo com essa opção desabilitada", "GameListContextMenuManageCheatToolTip": "Gerenciar Cheats", "GameListContextMenuManageCheat": "Gerenciar Cheats", + "GameListContextMenuManageModToolTip": "Manage Mods", + "GameListContextMenuManageMod": "Manage Mods", "ControllerSettingsStickRange": "Intervalo:", "DialogStopEmulationTitle": "Ryujinx - Parar emulação", "DialogStopEmulationMessage": "Tem certeza que deseja parar a emulação?", @@ -515,8 +522,6 @@ "SettingsTabCpuMemory": "Memória da CPU", "DialogUpdaterFlatpakNotSupportedMessage": "Por favor, atualize o Ryujinx pelo FlatHub.", "UpdaterDisabledWarningTitle": "Atualizador desabilitado!", - "GameListContextMenuOpenSdModsDirectory": "Abrir diretório de mods Atmosphere", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Abre o diretório alternativo Atmosphere no cartão SD que contém mods para o aplicativo", "ControllerSettingsRotate90": "Rodar 90° sentido horário", "IconSize": "Tamanho do ícone", "IconSizeTooltip": "Muda o tamanho do ícone do jogo", @@ -544,12 +549,13 @@ "SwkbdMinCharacters": "Deve ter pelo menos {0} caracteres", "SwkbdMinRangeCharacters": "Deve ter entre {0}-{1} caracteres", "SoftwareKeyboard": "Teclado por Software", - "SoftwareKeyboardModeNumbersOnly": "Must be numbers only", - "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", - "SoftwareKeyboardModeASCII": "Must be ASCII text only", - "DialogControllerAppletMessagePlayerRange": "O aplicativo requer {0} jogador(es) com:\n\nTIPOS: {1}\n\nJOGADORES: {2}\n\n{3}Por favor, abra as configurações e reconfigure os controles agora ou clique em Fechar.", - "DialogControllerAppletMessage": "O aplicativo requer exatamente {0} jogador(es) com:\n\nTIPOS: {1}\n\nJOGADORES: {2}\n\n{3}Por favor, abra as configurações e reconfigure os controles agora ou clique em Fechar.", - "DialogControllerAppletDockModeSet": "Modo TV ativado. Portátil também não é válido.\n\n", + "SoftwareKeyboardModeNumeric": "Deve ser somente 0-9 ou '.'", + "SoftwareKeyboardModeAlphabet": "Apenas devem ser caracteres não CJK.", + "SoftwareKeyboardModeASCII": "Deve ser apenas texto ASCII", + "ControllerAppletControllers": "Supported Controllers:", + "ControllerAppletPlayers": "Players:", + "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", + "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", "UpdaterRenaming": "Renomeando arquivos antigos...", "UpdaterRenameFailed": "O atualizador não conseguiu renomear o arquivo: {0}", "UpdaterAddingFiles": "Adicionando novos arquivos...", @@ -587,17 +593,20 @@ "Writable": "Gravável", "SelectDlcDialogTitle": "Selecionar arquivos de DLC", "SelectUpdateDialogTitle": "Selecionar arquivos de atualização", + "SelectModDialogTitle": "Select mod directory", "UserProfileWindowTitle": "Gerenciador de perfis de usuário", "CheatWindowTitle": "Gerenciador de Cheats", "DlcWindowTitle": "Gerenciador de DLC", "UpdateWindowTitle": "Gerenciador de atualizações", "CheatWindowHeading": "Cheats disponíveis para {0} [{1}]", - "BuildId": "BuildId:", + "BuildId": "ID da Build", "DlcWindowHeading": "{0} DLCs disponíveis para {1} ({2})", + "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "Editar selecionado", "Cancel": "Cancelar", "Save": "Salvar", "Discard": "Descartar", + "Paused": "Paused", "UserProfilesSetProfileImage": "Definir imagem de perfil", "UserProfileEmptyNameError": "É necessário um nome", "UserProfileNoImageError": "A imagem de perfil deve ser definida", @@ -607,9 +616,9 @@ "UserProfilesName": "Nome:", "UserProfilesUserId": "ID de usuário:", "SettingsTabGraphicsBackend": "Backend gráfico", - "SettingsTabGraphicsBackendTooltip": "Backend gráfico a ser usado", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", "SettingsEnableTextureRecompression": "Habilitar recompressão de texturas", - "SettingsEnableTextureRecompressionTooltip": "Comprime certas texturas para reduzir o uso da VRAM.\n\nRecomendado para uso com GPUs com menos de 4GB VRAM.\n\nEm caso de dúvida, deixe DESLIGADO.", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", "SettingsTabGraphicsPreferredGpu": "GPU preferencial", "SettingsTabGraphicsPreferredGpuTooltip": "Selecione a placa de vídeo que será usada com o backend gráfico Vulkan.\n\nNão afeta a GPU que OpenGL usará.\n\nSelecione \"dGPU\" em caso de dúvida. Se não houver nenhuma, não mexa.", "SettingsAppRequiredRestartMessage": "Reinicialização do Ryujinx necessária", @@ -620,8 +629,8 @@ "SettingsTabHotkeysVolumeDownHotkey": "Diminuir volume:", "SettingsEnableMacroHLE": "Habilitar emulação de alto nível para Macros", "SettingsEnableMacroHLETooltip": "Habilita emulação de alto nível de códigos Macro da GPU.\n\nMelhora a performance, mas pode causar problemas gráficos em alguns jogos.\n\nEm caso de dúvida, deixe ATIVADO.", - "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", - "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", + "SettingsEnableColorSpacePassthrough": "Passagem de Espaço Cor", + "SettingsEnableColorSpacePassthroughTooltip": "Direciona o backend Vulkan para passar informações de cores sem especificar um espaço de cores. Para usuários com telas de ampla gama, isso pode resultar em cores mais vibrantes, ao custo da correção de cores.", "VolumeShort": "Vol", "UserProfilesManageSaves": "Gerenciar jogos salvos", "DeleteUserSave": "Deseja apagar o jogo salvo do usuário para este jogo?", @@ -635,12 +644,12 @@ "Recover": "Recuperar", "UserProfilesRecoverHeading": "Jogos salvos foram encontrados para as seguintes contas", "UserProfilesRecoverEmptyList": "Nenhum perfil para recuperar", - "GraphicsAATooltip": "Aplica anti-serrilhamento à renderização do jogo", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", "GraphicsAALabel": "Anti-serrilhado:", "GraphicsScalingFilterLabel": "Filtro de escala:", - "GraphicsScalingFilterTooltip": "Habilita escala do Framebuffer", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", "GraphicsScalingFilterLevelLabel": "Nível", - "GraphicsScalingFilterLevelTooltip": "Define o nível do filtro de escala", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA Baixo", "SmaaMedium": "SMAA Médio", "SmaaHigh": "SMAA Alto", @@ -648,9 +657,12 @@ "UserEditorTitle": "Editar usuário", "UserEditorTitleCreate": "Criar usuário", "SettingsTabNetworkInterface": "Interface de rede:", - "NetworkInterfaceTooltip": "A interface de rede usada para recursos LAN (rede local)", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", "NetworkInterfaceDefault": "Padrão", - "PackagingShaders": "Packaging Shaders", - "AboutChangelogButton": "View Changelog on GitHub", - "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser." -} \ No newline at end of file + "PackagingShaders": "Empacotamento de Shaders", + "AboutChangelogButton": "Ver mudanças no GitHub", + "AboutChangelogButtonTooltipMessage": "Clique para abrir o relatório de alterações para esta versão no seu navegador padrão.", + "SettingsTabNetworkMultiplayer": "Multiplayer", + "MultiplayerMode": "Modo:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/ru_RU.json b/src/Ryujinx/Assets/Locales/ru_RU.json index a2128e52e..e7c36f6ca 100644 --- a/src/Ryujinx/Assets/Locales/ru_RU.json +++ b/src/Ryujinx/Assets/Locales/ru_RU.json @@ -1,37 +1,37 @@ { - "Language": "Русский", + "Language": "Русский (RU)", "MenuBarFileOpenApplet": "Открыть апплет", "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Открыть апплет Mii Editor в автономном режиме", - "SettingsTabInputDirectMouseAccess": "Прямой доступ к мыши", + "SettingsTabInputDirectMouseAccess": "Прямой ввод мыши", "SettingsTabSystemMemoryManagerMode": "Режим диспетчера памяти:", "SettingsTabSystemMemoryManagerModeSoftware": "Программное обеспечение", "SettingsTabSystemMemoryManagerModeHost": "Хост (быстро)", "SettingsTabSystemMemoryManagerModeHostUnchecked": "Хост не установлен (самый быстрый, небезопасный)", - "SettingsTabSystemUseHypervisor": "Использовать Гипервизор", + "SettingsTabSystemUseHypervisor": "Использовать Hypervisor", "MenuBarFile": "_Файл", - "MenuBarFileOpenFromFile": "_Загрузить приложение из файла", - "MenuBarFileOpenUnpacked": "Загрузить _Распакованную игру", + "MenuBarFileOpenFromFile": "_Добавить приложение из файла", + "MenuBarFileOpenUnpacked": "Добавить _распакованную игру", "MenuBarFileOpenEmuFolder": "Открыть папку Ryujinx", - "MenuBarFileOpenLogsFolder": "Открыть папку журналов", + "MenuBarFileOpenLogsFolder": "Открыть папку с логами", "MenuBarFileExit": "_Выход", - "MenuBarOptions": "Опции", + "MenuBarOptions": "_Настройки", "MenuBarOptionsToggleFullscreen": "Включить полноэкранный режим", - "MenuBarOptionsStartGamesInFullscreen": "Запустить игру в полноэкранном режиме", + "MenuBarOptionsStartGamesInFullscreen": "Запускать игры в полноэкранном режиме", "MenuBarOptionsStopEmulation": "Остановить эмуляцию", "MenuBarOptionsSettings": "_Параметры", - "MenuBarOptionsManageUserProfiles": "_Управление профилями пользователей", + "MenuBarOptionsManageUserProfiles": "_Менеджер учетных записей", "MenuBarActions": "_Действия", "MenuBarOptionsSimulateWakeUpMessage": "Имитировать сообщение пробуждения", "MenuBarActionsScanAmiibo": "Сканировать Amiibo", "MenuBarTools": "_Инструменты", - "MenuBarToolsInstallFirmware": "Установить прошивку", + "MenuBarToolsInstallFirmware": "Установка прошивки", "MenuBarFileToolsInstallFirmwareFromFile": "Установить прошивку из XCI или ZIP", "MenuBarFileToolsInstallFirmwareFromDirectory": "Установить прошивку из папки", "MenuBarToolsManageFileTypes": "Управление типами файлов", "MenuBarToolsInstallFileTypes": "Установить типы файлов", "MenuBarToolsUninstallFileTypes": "Удалить типы файлов", - "MenuBarHelp": "Помощь", - "MenuBarHelpCheckForUpdates": "Проверка обновления", + "MenuBarHelp": "_Помощь", + "MenuBarHelpCheckForUpdates": "Проверка обновлений", "MenuBarHelpAbout": "О программе", "MenuSearch": "Поиск...", "GameListHeaderFavorite": "Избранные", @@ -39,23 +39,21 @@ "GameListHeaderApplication": "Название", "GameListHeaderDeveloper": "Разработчик", "GameListHeaderVersion": "Версия", - "GameListHeaderTimePlayed": "Время воспроизведения", + "GameListHeaderTimePlayed": "Время в игре", "GameListHeaderLastPlayed": "Последняя игра", "GameListHeaderFileExtension": "Расширение файла", "GameListHeaderFileSize": "Размер файла", "GameListHeaderPath": "Путь", - "GameListContextMenuOpenUserSaveDirectory": "Открыть папку сохранений пользователя", + "GameListContextMenuOpenUserSaveDirectory": "Открыть папку с сохранениями", "GameListContextMenuOpenUserSaveDirectoryToolTip": "Открывает папку, содержащую пользовательские сохранения", "GameListContextMenuOpenDeviceSaveDirectory": "Открыть папку сохраненных устройств", "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Открывает папку, содержащую сохраненные устройства", "GameListContextMenuOpenBcatSaveDirectory": "Открыть папку сохраненных BCAT", "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Открывает папку, содержащую сохраненные BCAT.", - "GameListContextMenuManageTitleUpdates": "Управление обновлениями названий", + "GameListContextMenuManageTitleUpdates": "Управление обновлениями", "GameListContextMenuManageTitleUpdatesToolTip": "Открывает окно управления обновлением заголовков", "GameListContextMenuManageDlc": "Управление DLC", "GameListContextMenuManageDlcToolTip": "Открывает окно управления DLC", - "GameListContextMenuOpenModsDirectory": "Открыть папку с модами", - "GameListContextMenuOpenModsDirectoryToolTip": "Открывает папку, содержащую моды приложений и игр", "GameListContextMenuCacheManagement": "Управление кэшем", "GameListContextMenuCacheManagementPurgePptc": "Перестройка очереди PPTC", "GameListContextMenuCacheManagementPurgePptcToolTip": "Запускает перестройку PPTC во время запуска следующей игры.", @@ -72,8 +70,15 @@ "GameListContextMenuExtractDataRomFSToolTip": "Извлечение раздела RomFS из текущих настроек приложения (включая обновления)", "GameListContextMenuExtractDataLogo": "Логотип", "GameListContextMenuExtractDataLogoToolTip": "Извлечение раздела с логотипом из текущих настроек приложения (включая обновления)", + "GameListContextMenuCreateShortcut": "Создать ярлык приложения", + "GameListContextMenuCreateShortcutToolTip": "Создать ярлык на рабочем столе, с помощью которого можно запустить игру или приложение", + "GameListContextMenuCreateShortcutToolTipMacOS": "Создать ярлык игры или приложения в папке Программы macOS", + "GameListContextMenuOpenModsDirectory": "Открыть папку с модами", + "GameListContextMenuOpenModsDirectoryToolTip": "Открывает папку, содержащую моды для приложений и игр", + "GameListContextMenuOpenSdModsDirectory": "Открыть папку с модами Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Открывает альтернативную папку Atmosphere SD-карты, которая содержит моды для приложений и игр. Полезно для модов, сделанных для реальной консоли.", "StatusBarGamesLoaded": "{0}/{1} Игр загружено", - "StatusBarSystemVersion": "Версия системы: {0}", + "StatusBarSystemVersion": "Версия прошивки: {0}", "LinuxVmMaxMapCountDialogTitle": "Обнаружен низкий лимит разметки памяти", "LinuxVmMaxMapCountDialogTextPrimary": "Вы хотите увеличить значение vm.max_map_count до {0}", "LinuxVmMaxMapCountDialogTextSecondary": "Некоторые игры могут создавать большую разметку памяти, чем разрешено на данный момент по умолчанию. Ryujinx вылетит при превышении этого лимита.", @@ -82,21 +87,21 @@ "LinuxVmMaxMapCountWarningTextPrimary": "Максимальная разметка памяти меньше, чем рекомендуется.", "LinuxVmMaxMapCountWarningTextSecondary": "Текущее значение vm.max_map_count ({0}) меньше, чем {1}. Некоторые игры могут попытаться создать большую разметку памяти, чем разрешено в данный момент. Ryujinx вылетит как только этот лимит будет превышен.\n\nВозможно, вы захотите вручную увеличить лимит или установить pkexec, что позволит Ryujinx помочь справиться с превышением лимита.", "Settings": "Параметры", - "SettingsTabGeneral": "Пользовательский интерфейс", + "SettingsTabGeneral": "Интерфейс", "SettingsTabGeneralGeneral": "Общее", - "SettingsTabGeneralEnableDiscordRichPresence": "Включить Discord Rich Presence", + "SettingsTabGeneralEnableDiscordRichPresence": "Включить статус активности в Discord", "SettingsTabGeneralCheckUpdatesOnLaunch": "Проверять наличие обновлений при запуске", - "SettingsTabGeneralShowConfirmExitDialog": "Показать диалоговое окно \"Подтвердить выход\"", - "SettingsTabGeneralHideCursor": "Скрыть курсор", + "SettingsTabGeneralShowConfirmExitDialog": "Подтверждать выход из приложения", + "SettingsTabGeneralHideCursor": "Скрывать курсор", "SettingsTabGeneralHideCursorNever": "Никогда", - "SettingsTabGeneralHideCursorOnIdle": "Скрыть курсор в режиме ожидания", + "SettingsTabGeneralHideCursorOnIdle": "В режиме ожидания", "SettingsTabGeneralHideCursorAlways": "Всегда", "SettingsTabGeneralGameDirectories": "Папки с играми", "SettingsTabGeneralAdd": "Добавить", "SettingsTabGeneralRemove": "Удалить", "SettingsTabSystem": "Система", "SettingsTabSystemCore": "Основные настройки", - "SettingsTabSystemSystemRegion": "Регион Системы:", + "SettingsTabSystemSystemRegion": "Регион прошивки:", "SettingsTabSystemSystemRegionJapan": "Япония", "SettingsTabSystemSystemRegionUSA": "США", "SettingsTabSystemSystemRegionEurope": "Европа", @@ -104,7 +109,7 @@ "SettingsTabSystemSystemRegionChina": "Китай", "SettingsTabSystemSystemRegionKorea": "Корея", "SettingsTabSystemSystemRegionTaiwan": "Тайвань", - "SettingsTabSystemSystemLanguage": "Язык системы:", + "SettingsTabSystemSystemLanguage": "Язык прошивки:", "SettingsTabSystemSystemLanguageJapanese": "Японский", "SettingsTabSystemSystemLanguageAmericanEnglish": "Английский (США)", "SettingsTabSystemSystemLanguageFrench": "Французский", @@ -122,19 +127,19 @@ "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Испанский (Латиноамериканский)", "SettingsTabSystemSystemLanguageSimplifiedChinese": "Китайский упрощённый", "SettingsTabSystemSystemLanguageTraditionalChinese": "Китайский традиционный", - "SettingsTabSystemSystemTimeZone": "Часовой пояс системы:", - "SettingsTabSystemSystemTime": "Время системы:", + "SettingsTabSystemSystemTimeZone": "Часовой пояс прошивки:", + "SettingsTabSystemSystemTime": "Время в прошивке:", "SettingsTabSystemEnableVsync": "Включить вертикальную синхронизацию", "SettingsTabSystemEnablePptc": "Включить PPTC (Profiled Persistent Translation Cache)", - "SettingsTabSystemEnableFsIntegrityChecks": "Включить проверку целостности FS", - "SettingsTabSystemAudioBackend": "Аудио бэкэнд:", - "SettingsTabSystemAudioBackendDummy": "Муляж", + "SettingsTabSystemEnableFsIntegrityChecks": "Включить проверку целостности файловой системы", + "SettingsTabSystemAudioBackend": "Аудио бэкенд:", + "SettingsTabSystemAudioBackendDummy": "Без звука", "SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemHacks": "Хаки", - "SettingsTabSystemHacksNote": " (Эти многие настройки вызывают нестабильность)", - "SettingsTabSystemExpandDramSize": "Увеличение размера DRAM до 6GiB", + "SettingsTabSystemHacksNote": "Возможна нестабильная работа", + "SettingsTabSystemExpandDramSize": "Использовать альтернативный макет памяти (для разработчиков)", "SettingsTabSystemIgnoreMissingServices": "Игнорировать отсутствующие службы", "SettingsTabGraphics": "Графика", "SettingsTabGraphicsAPI": "Графические API", @@ -145,12 +150,12 @@ "SettingsTabGraphicsAnisotropicFiltering4x": "4x", "SettingsTabGraphicsAnisotropicFiltering8x": "8x", "SettingsTabGraphicsAnisotropicFiltering16x": "16x", - "SettingsTabGraphicsResolutionScale": "Масштаб:", + "SettingsTabGraphicsResolutionScale": "Масштабирование:", "SettingsTabGraphicsResolutionScaleCustom": "Пользовательский (не рекомендуется)", - "SettingsTabGraphicsResolutionScaleNative": "Родной (720p/1080p)", + "SettingsTabGraphicsResolutionScaleNative": "Нативное (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (не рекомендуется)", "SettingsTabGraphicsAspectRatio": "Соотношение сторон:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -172,16 +177,16 @@ "SettingsTabLoggingEnableFsAccessLogs": "Включить журналы доступа файловой системы", "SettingsTabLoggingFsGlobalAccessLogMode": "Режим журнала глобального доступа файловой системы:", "SettingsTabLoggingDeveloperOptions": "Параметры разработчика", - "SettingsTabLoggingDeveloperOptionsNote": "ВНИМАНИЕ: Снижает производительность", + "SettingsTabLoggingDeveloperOptionsNote": "ВНИМАНИЕ: эти настройки снижают производительность", "SettingsTabLoggingGraphicsBackendLogLevel": "Уровень журнала бэкенда графики:", - "SettingsTabLoggingGraphicsBackendLogLevelNone": "Ничего", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Нет", "SettingsTabLoggingGraphicsBackendLogLevelError": "Ошибка", "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Замедления", "SettingsTabLoggingGraphicsBackendLogLevelAll": "Всё", "SettingsTabLoggingEnableDebugLogs": "Включить журнал отладки", "SettingsTabInput": "Управление", - "SettingsTabInputEnableDockedMode": "Включить режим закрепления", - "SettingsTabInputDirectKeyboardAccess": "Прямой доступ с клавиатуры", + "SettingsTabInputEnableDockedMode": "Стационарный режим", + "SettingsTabInputDirectKeyboardAccess": "Прямой ввод клавиатуры", "SettingsButtonSave": "Сохранить", "SettingsButtonClose": "Закрыть", "SettingsButtonOk": "Ок", @@ -203,9 +208,9 @@ "ControllerSettingsControllerType": "Тип контроллера", "ControllerSettingsControllerTypeHandheld": "Портативный", "ControllerSettingsControllerTypeProController": "Pro Контроллер", - "ControllerSettingsControllerTypeJoyConPair": "JoyCon Пара", - "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Левый", - "ControllerSettingsControllerTypeJoyConRight": "JoyCon Правый", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon (пара)", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon (левый)", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon (правый)", "ControllerSettingsProfile": "Профиль", "ControllerSettingsProfileDefault": "По умолчанию", "ControllerSettingsLoad": "Загрузить", @@ -218,19 +223,19 @@ "ControllerSettingsButtonY": "Y", "ControllerSettingsButtonPlus": "+", "ControllerSettingsButtonMinus": "-", - "ControllerSettingsDPad": "Направляющая панель", + "ControllerSettingsDPad": "Кнопки направления", "ControllerSettingsDPadUp": "Вверх", "ControllerSettingsDPadDown": "Вниз", "ControllerSettingsDPadLeft": "Влево", "ControllerSettingsDPadRight": "Вправо", - "ControllerSettingsStickButton": "Кнопка", + "ControllerSettingsStickButton": "Нажатие на стик", "ControllerSettingsStickUp": "Вверх", "ControllerSettingsStickDown": "Вниз", "ControllerSettingsStickLeft": "Влево", "ControllerSettingsStickRight": "Вправо", "ControllerSettingsStickStick": "Стик", - "ControllerSettingsStickInvertXAxis": "Инвертировать X ось", - "ControllerSettingsStickInvertYAxis": "Инвертировать Y ось", + "ControllerSettingsStickInvertXAxis": "Инвертировать ось X", + "ControllerSettingsStickInvertYAxis": "Инвертировать ось Y", "ControllerSettingsStickDeadzone": "Мёртвая зона:", "ControllerSettingsLStick": "Левый стик", "ControllerSettingsRStick": "Правый стик", @@ -252,10 +257,10 @@ "ControllerSettingsMisc": "Разное", "ControllerSettingsTriggerThreshold": "Порог срабатывания:", "ControllerSettingsMotion": "Движение", - "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Используйте движение, совместимое с CemuHook", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Включить совместимость с CemuHook", "ControllerSettingsMotionControllerSlot": "Слот контроллера:", "ControllerSettingsMotionMirrorInput": "Зеркальный ввод", - "ControllerSettingsMotionRightJoyConSlot": "Правый JoyCon слот:", + "ControllerSettingsMotionRightJoyConSlot": "Слот правого JoyCon:", "ControllerSettingsMotionServerHost": "Хост сервера:", "ControllerSettingsMotionGyroSensitivity": "Чувствительность гироскопа:", "ControllerSettingsMotionGyroDeadzone": "Мертвая зона гироскопа:", @@ -268,12 +273,12 @@ "UserProfilesAddNewProfile": "Добавить новый профиль", "UserProfilesDelete": "Удалить", "UserProfilesClose": "Закрыть", - "ProfileNameSelectionWatermark": "Выберите псевдоним", + "ProfileNameSelectionWatermark": "Выберите никнейм", "ProfileImageSelectionTitle": "Выбор изображения профиля", "ProfileImageSelectionHeader": "Выберите изображение профиля", "ProfileImageSelectionNote": "Вы можете импортировать собственное изображение профиля или выбрать аватар из системной прошивки.", - "ProfileImageSelectionImportImage": "Импорт файла изображения", - "ProfileImageSelectionSelectAvatar": "Выберите аватар прошивки", + "ProfileImageSelectionImportImage": "Импорт изображения", + "ProfileImageSelectionSelectAvatar": "Выстроенные аватары", "InputDialogTitle": "Диалоговое окно ввода", "InputDialogOk": "ОК", "InputDialogCancel": "Отмена", @@ -290,15 +295,11 @@ "MenuBarFileToolsTakeScreenshot": "Сделать снимок экрана", "MenuBarFileToolsHideUi": "Скрыть UI", "GameListContextMenuRunApplication": "Запуск приложения", - "GameListContextMenuToggleFavorite": "Переключить Избранное", - "GameListContextMenuToggleFavoriteToolTip": "Переключить любимый статус игры", - "SettingsTabGeneralTheme": "Тема", - "SettingsTabGeneralThemeCustomTheme": "Пользовательский путь к теме", - "SettingsTabGeneralThemeBaseStyle": "Базовый стиль", - "SettingsTabGeneralThemeBaseStyleDark": "Тёмная", - "SettingsTabGeneralThemeBaseStyleLight": "Светлая", - "SettingsTabGeneralThemeEnableCustomTheme": "Включить пользовательскую тему", - "ButtonBrowse": "Обзор", + "GameListContextMenuToggleFavorite": "Добавить в избранное", + "GameListContextMenuToggleFavoriteToolTip": "Помечает игру звездочкой как избранную", + "SettingsTabGeneralTheme": "Тема:", + "SettingsTabGeneralThemeDark": "Темная", + "SettingsTabGeneralThemeLight": "Светлая", "ControllerSettingsConfigureGeneral": "Настройка", "ControllerSettingsRumble": "Вибрация", "ControllerSettingsRumbleStrongMultiplier": "Множитель сильной вибрации", @@ -332,8 +333,6 @@ "DialogUpdaterAddingFilesMessage": "Добавление нового обновления...", "DialogUpdaterCompleteMessage": "Обновление завершено!", "DialogUpdaterRestartMessage": "Вы хотите перезапустить Ryujinx сейчас?", - "DialogUpdaterArchNotSupportedMessage": "Вы используете не поддерживаемую системную архитектуру!", - "DialogUpdaterArchNotSupportedSubMessage": "(Поддерживаются только x64 системы)", "DialogUpdaterNoInternetMessage": "Вы не подключены к интернету!", "DialogUpdaterNoInternetSubMessage": "Убедитесь, что у вас есть работающее подключение к интернету!", "DialogUpdaterDirtyBuildMessage": "Вы не можете обновить Dirty Build!", @@ -349,7 +348,7 @@ "DialogInstallFileTypesErrorMessage": "Не удалось установить типы файлов.", "DialogUninstallFileTypesSuccessMessage": "Успешно удалены типы файлов!", "DialogUninstallFileTypesErrorMessage": "Не удалось удалить типы файлов.", - "DialogOpenSettingsWindowLabel": "Открыть окно настроек", + "DialogOpenSettingsWindowLabel": "Открыть окно параметров", "DialogControllerAppletTitle": "Апплет контроллера", "DialogMessageDialogErrorExceptionMessage": "Ошибка отображения диалогового окна сообщений: {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "Ошибка отображения программной клавиатуры: {0}", @@ -373,11 +372,11 @@ "DialogInvalidTitleIdErrorMessage": "Ошибка пользовательского интерфейса: выбранная игра не имеет действительного идентификатора названия.", "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Действительная системная прошивка не найдена в {0}.", "DialogFirmwareInstallerFirmwareInstallTitle": "Установить прошивку {0}", - "DialogFirmwareInstallerFirmwareInstallMessage": "Будет установлена версия системы {0}.", - "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nЭто заменит текущую версию системы {0}.", + "DialogFirmwareInstallerFirmwareInstallMessage": "Будет установлена версия прошивки {0}.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nЭто заменит текущую версию прошивки {0}.", "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nПродолжить?", "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Установка прошивки...", - "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Версия системы {0} успешно установлена.", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Прошивка версии {0} успешно установлена.", "DialogUserProfileDeletionWarningMessage": "Если выбранный профиль будет удален, другие профили не будут открываться.", "DialogUserProfileDeletionConfirmMessage": "Вы хотите удалить выбранный профиль", "DialogUserProfileUnsavedChangesTitle": "Внимание - Несохраненные изменения", @@ -385,7 +384,10 @@ "DialogUserProfileUnsavedChangesSubMessage": "Вы хотите отменить изменения?", "DialogControllerSettingsModifiedConfirmMessage": "Текущие настройки контроллера обновлены.", "DialogControllerSettingsModifiedConfirmSubMessage": "Вы хотите сохранить?", - "DialogLoadNcaErrorMessage": "{0}. Файл с ошибкой: {1}", + "DialogLoadFileErrorMessage": "{0}. Файл с ошибкой: {1}", + "DialogModAlreadyExistsMessage": "Мод уже существует", + "DialogModInvalidMessage": "Выбранная папка не содержит модов", + "DialogModDeleteNoParentMessage": "Невозможно удалить: не удалось найти папку мода \"{0}\"!", "DialogDlcNoDlcErrorMessage": "Указанный файл не содержит DLC для выбранной игры!", "DialogPerformanceCheckLoggingEnabledMessage": "У вас включено ведение журнала отладки, предназначенное только для разработчиков.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить ведение журнала отладки. Вы хотите отключить ведение журнала отладки сейчас?", @@ -396,6 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "Указанный файл не содержит обновления для выбранного заголовка!", "DialogSettingsBackendThreadingWarningTitle": "Предупреждение: многопоточность в бэкенде", "DialogSettingsBackendThreadingWarningMessage": "Ryujinx необходимо перезапустить после изменения этой опции, чтобы она полностью применилась. В зависимости от вашей платформы вам может потребоваться вручную отключить собственную многопоточность вашего драйвера при использовании Ryujinx.", + "DialogModManagerDeletionWarningMessage": "Вы сейчас удалите мод: {0}\n\nВы уверены, что хотите продолжить?", + "DialogModManagerDeletionAllWarningMessage": "Вы сейчас удалите все выбранные файлы для этой игры.\n\nВы уверены, что хотите продолжить?", "SettingsTabGraphicsFeaturesOptions": "Функции & Улучшения", "SettingsTabGraphicsBackendMultithreading": "Многопоточность графического бэкенда:", "CommonAuto": "Автоматически", @@ -406,18 +410,18 @@ "DialogProfileInvalidProfileNameErrorMessage": "Имя файла содержит недопустимые символы. Пожалуйста, попробуйте еще раз.", "MenuBarOptionsPauseEmulation": "Пауза", "MenuBarOptionsResumeEmulation": "Продолжить", - "AboutUrlTooltipMessage": "Нажмите, чтобы открыть веб-сайт Ryujinx в браузере по умолчанию.", + "AboutUrlTooltipMessage": "Нажмите, чтобы открыть веб-сайт Ryujinx", "AboutDisclaimerMessage": "Ryujinx никоим образом не связан ни с Nintendo™, ни с кем-либо из ее партнеров.", - "AboutAmiiboDisclaimerMessage": "Amiibo API (www.amiibo api.com) используется\n нашей эмуляции Amiibo.", - "AboutPatreonUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx Patreon в браузере по умолчанию.", - "AboutGithubUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx GitHub в браузере по умолчанию.", - "AboutDiscordUrlTooltipMessage": "Нажмите, чтобы открыть приглашение на сервер Ryujinx Discord в браузере по умолчанию.", - "AboutTwitterUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx в Twitter в браузере по умолчанию.", + "AboutAmiiboDisclaimerMessage": "Amiibo API (www.amiiboapi.com) используется для эмуляции Amiibo.", + "AboutPatreonUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx на Patreon", + "AboutGithubUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx на GitHub", + "AboutDiscordUrlTooltipMessage": "Нажмите, чтобы открыть приглашение на сервер Ryujinx в Discord", + "AboutTwitterUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx в X (бывший Twitter)", "AboutRyujinxAboutTitle": "О программе:", - "AboutRyujinxAboutContent": "Ryujinx — это эмулятор Nintendo Switch™.\nПожалуйста, поддержите нас на Patreon.\nУзнавайте все последние новости в нашем Twitter или Discord.\nРазработчики, заинтересованные в участии, могут узнать больше на нашем GitHub или в Discord.", - "AboutRyujinxMaintainersTitle": "Поддерживается:", - "AboutRyujinxMaintainersContentTooltipMessage": "Нажмите, чтобы открыть страницу Contributors в браузере по умолчанию.", - "AboutRyujinxSupprtersTitle": "Поддерживается на Patreon:", + "AboutRyujinxAboutContent": "Ryujinx — это эмулятор Nintendo Switch™.\nПожалуйста, поддержите нас на Patreon.\nЧитайте последние новости в наших X (Twitter) или Discord.\nРазработчики, заинтересованные в участии, могут ознакомиться с проектом на GitHub или в Discord.", + "AboutRyujinxMaintainersTitle": "Разработка:", + "AboutRyujinxMaintainersContentTooltipMessage": "Нажмите, чтобы открыть страницу с участниками", + "AboutRyujinxSupprtersTitle": "Поддержка на Patreon:", "AmiiboSeriesLabel": "Серия Amiibo", "AmiiboCharacterLabel": "Персонаж", "AmiiboScanButtonLabel": "Сканировать", @@ -427,13 +431,14 @@ "DlcManagerTableHeadingTitleIdLabel": "Идентификатор заголовка", "DlcManagerTableHeadingContainerPathLabel": "Путь к контейнеру", "DlcManagerTableHeadingFullPathLabel": "Полный путь", - "DlcManagerRemoveAllButton": "Убрать все", + "DlcManagerRemoveAllButton": "Удалить все", "DlcManagerEnableAllButton": "Включить все", "DlcManagerDisableAllButton": "Отключить все", - "MenuBarOptionsChangeLanguage": "Изменить язык", - "MenuBarShowFileTypes": "Показать типы файлов", - "CommonSort": "Сортировать", - "CommonShowNames": "Показать названия", + "ModManagerDeleteAllButton": "Удалить все", + "MenuBarOptionsChangeLanguage": "Сменить язык", + "MenuBarShowFileTypes": "Показывать форматы файлов", + "CommonSort": "Сортировка", + "CommonShowNames": "Показывать названия", "CommonFavorite": "Избранные", "OrderAscending": "По возрастанию", "OrderDescending": "По убыванию", @@ -447,39 +452,39 @@ "CustomThemePathTooltip": "Путь к пользовательской теме интерфейса", "CustomThemeBrowseTooltip": "Просмотр пользовательской темы интерфейса", "DockModeToggleTooltip": "\"Стационарный\" режим запускает эмулятор, как если бы Nintendo Switch находилась в доке, что улучшает графику и разрешение в большинстве игр. И наоборот, при отключении этого режима эмулятор будет запускать игры в \"Портативном\" режиме, снижая качество графики.\n\nНастройте управление для Игрока 1 если планируете использовать в \"Стационарном\" режиме; настройте портативное управление если планируете использовать эмулятор в \"Портативном\" режиме.\n\nОставьте включенным если не уверены.", - "DirectKeyboardTooltip": "Включить или отключить «поддержку прямого доступа к клавиатуре (HID)» (предоставляет играм доступ к клавиатуре как к устройству ввода текста)", - "DirectMouseTooltip": "Включить или отключить «поддержку прямого доступа к мыши (HID)» (предоставляет играм доступ к вашей мыши как указывающему устройству)", - "RegionTooltip": "Изменение региона системы", - "LanguageTooltip": "Изменение языка системы", - "TimezoneTooltip": "Изменение часового пояса системы", + "DirectKeyboardTooltip": "Поддержка прямого ввода с клавиатуры (HID). Предоставляет игре прямой доступ к клавиатуре в качестве устройства ввода текста.\nРаботает только с играми, которые изначально поддерживают использование клавиатуры с Switch.\nРекомендуется оставить выключенным.", + "DirectMouseTooltip": "Поддержка прямого ввода мыши (HID). Предоставляет игре прямой доступ к мыши в качестве указывающего устройства.\nРаботает только с играми, которые изначально поддерживают использование мыши совместно с железом Switch.\nРекомендуется оставить выключенным.", + "RegionTooltip": "Изменение региона прошивки", + "LanguageTooltip": "Изменение языка прошивки", + "TimezoneTooltip": "Изменение часового пояса прошивки", "TimeTooltip": "Изменение системного времени", - "VSyncToggleTooltip": "Эмуляция вертикальной синхронизации консоли, которая ограничивает количество кадров в секунду в большинстве игр; отключение может привести к тому, что игры будут запущены с более высокой частотой кадров, но загрузка игры может занять больше времени, либо игра не запустится вообще.\n\nМожно включать и выключать эту настройку непосредственно в игре с помощью горячих клавиш. Если планируете отключить вериткальную синхронизацию, мы рекомендуем настроить горячие клавиши.\n\nРекомендуется оставить включенным.", + "VSyncToggleTooltip": "Эмуляция вертикальной синхронизации консоли, которая ограничивает количество кадров в секунду в большинстве игр; отключение может привести к тому, что игры будут запущены с более высокой частотой кадров, но загрузка игры может занять больше времени, либо игра не запустится вообще.\n\nМожно включать и выключать эту настройку непосредственно в игре с помощью горячих клавиш (F1 по умолчанию). Если планируете отключить вертикальную синхронизацию, рекомендуем настроить горячие клавиши.\n\nРекомендуется оставить включенным.", "PptcToggleTooltip": "Сохранение преобразованных JIT-функций таким образом, чтобы их не нужно преобразовывать по новой каждый раз при загрузке игры.\n\nУменьшает статтеры и значительно ускоряет последующую загрузку игр.\n\nРекомендуется оставить включенным.", - "FsIntegrityToggleTooltip": "Проверяет поврежденные файлы при загрузке игры и если поврежденные файлы обнаружены, отображает ошибку о поврежденном хэше в журнале.\n\nНе влияет на производительность и необходим для содействия в устранении неполадок.\n\nРекомендуется оставить включенным.", + "FsIntegrityToggleTooltip": "Проверяет поврежденные файлы при загрузке игры и если поврежденные файлы обнаружены, отображает ошибку о поврежденном хэше в журнале.\n\nНе влияет на производительность и необходим для помощи в устранении неполадок.\n\nРекомендуется оставить включенным.", "AudioBackendTooltip": "Изменяет используемый аудио-бэкенд для рендера звука.\n\nSDL2 является предпочтительным, в то время как OpenAL и SoundIO используются в качестве резервных. При выборе \"Заглушки\" звук будет отсутствовать.\n\nРекомендуется использование SDL2.", "MemoryManagerTooltip": "Изменение разметки и доступа к гостевой памяти. Значительно влияет на производительность процессора.\n\nРекомендуется оставить \"Хост не установлен\"", "MemoryManagerSoftwareTooltip": "Использует таблицу страниц для преобразования адресов. Самая высокая точность, но самая низкая производительность.", "MemoryManagerHostTooltip": "Прямая разметка памяти в адресном пространстве хоста. Значительно более быстрая JIT-компиляция и запуск.", - "MemoryManagerUnsafeTooltip": "Производит прямую разметку памяти, но не маскирует адрес в гостевом адресном пространстве перед получением доступа. Быстрее, но менее безопасно. Гостевое приложение может получить доступ к памяти из любой точки Ryujinx, поэтому в этом режиме рекомендуется запускать только те программы, которым вы доверяете.", - "UseHypervisorTooltip": "Использует Гипервизор вместо JIT. Значительно увеличивает производительность, но может быть работать нестабильно.", - "DRamTooltip": "Использует альтернативный макет MemoryMode для имитации использования Nintendo Switch для разработчика.\n\nПолезно только для пакетов текстур с высоким разрешением или модов добавляющих разрешение 4К. Не улучшает производительность.\n\nРекомендуется оставить выключенным.", - "IgnoreMissingServicesTooltip": "Игнорирует нереализованные сервисы Horizon OS. Это поможет избежать вылеты при загрузке определенных игр.\n\nРекомендуется оставить выключенным.", - "GraphicsBackendThreadingTooltip": "Выполняет команды графического бэкенда на втором потоке.\n\nУскоряет компиляцию шейдеров, уменьшает статтеры и повышает производительность на драйверах GPU без поддержки многопоточности. Производительность на драйверах с многопоточностью немного выше.\n\nРекомендуется оставить в Авто.", - "GalThreadingTooltip": "Выполняет команды графического бэкенда на во втором потоке.\n\nУскоряет компиляцию шейдеров, уменьшает статтеры и повышает производительность на драйверах GPU без поддержки многопоточности. Производительность на драйверах с многопоточностью немного выше.\n\nРекомендуется оставить в Авто.", + "MemoryManagerUnsafeTooltip": "Производит прямую разметку памяти, но не маскирует адрес в гостевом адресном пространстве перед получением доступа. Быстро, но небезопасно. Гостевое приложение может получить доступ к памяти из Ryujinx, поэтому в этом режиме рекомендуется запускать только те программы, которым вы доверяете.", + "UseHypervisorTooltip": "Использует Hypervisor вместо JIT. Значительно увеличивает производительность, но может работать нестабильно.", + "DRamTooltip": "Использует альтернативный макет MemoryMode для имитации использования Nintendo Switch в режиме разработчика.\n\nПолезно только для пакетов текстур с высоким разрешением или модов добавляющих разрешение 4К. Не улучшает производительность.\n\nРекомендуется оставить выключенным.", + "IgnoreMissingServicesTooltip": "Игнорирует нереализованные сервисы Horizon OS. Эта настройка поможет избежать вылеты при запуске определенных игр.\n\nРекомендуется оставить выключенным.", + "GraphicsBackendThreadingTooltip": "Выполняет команды графического бэкенда на втором потоке.\n\nУскоряет компиляцию шейдеров, уменьшает статтеры и повышает производительность на драйверах GPU без поддержки многопоточности. Производительность на драйверах с многопоточностью немного выше.\n\nРекомендуется оставить Автоматически.", + "GalThreadingTooltip": "Выполняет команды графического бэкенда на втором потоке.\n\nУскоряет компиляцию шейдеров, уменьшает статтеры и повышает производительность на драйверах GPU без поддержки многопоточности. Производительность на драйверах с многопоточностью немного выше.\n\nРекомендуется оставить в Авто.", "ShaderCacheToggleTooltip": "Сохраняет кэш шейдеров на диске, который уменьшает статтеры при последующих запусках.\n\nРекомендуется оставить включенным.", - "ResolutionScaleTooltip": "Масштабирование разрешения", + "ResolutionScaleTooltip": "Увеличивает разрешение рендера игры.\n\nНекоторые игры могут не работать с этой настройкой и выглядеть смазано даже когда разрешение увеличено; Для этих игр вам может потребоваться найти моды, которые убирают сглаживание или увеличивают разрешение рендеринга этих игр. Для использования последнего, вам нужно будет выбрать опцию \"Нативное\".\n\nЭта опция может быть изменена во время игры по нажатию кнопки \"Применить\" ниже; Вы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не подберете подходящие настройки для конкретной игры.\n\nИмейте в виду, что \"4x\" является излишеством.", "ResolutionScaleEntryTooltip": "Масштабирование разрешения с плавающей запятой, например 1,5. Неинтегральное масштабирование с большой вероятностью вызовет сбои в работе.", - "AnisotropyTooltip": "Уровень анизотропной фильтрации (установите значение «Авто», чтобы использовать значение в игре по умолчанию)", - "AspectRatioTooltip": "Соотношение сторон, применяемое к окну.", + "AnisotropyTooltip": "Уровень анизотропной фильтрации. Установите значение «Авто», чтобы использовать значение в игре по умолчанию.", + "AspectRatioTooltip": "Соотношение сторон применимое к окну рендерера.\n\nИзмените эту настройку только если вы используете мод для соотношения сторон, иначе изображение будет растянуто.\n\nРекомендуется настройка 16:9.", "ShaderDumpPathTooltip": "Путь дампа графических шейдеров", "FileLogTooltip": "Включает или отключает ведение журнала в файл на диске. Не влияет на производительность.", "StubLogTooltip": "Включает ведение журнала-заглушки. Не влияет на производительность.", - "InfoLogTooltip": "Включает печать сообщений информационного журнала. Не влияет на производительность.", - "WarnLogTooltip": "Включает печать сообщений журнала предупреждений. Не влияет на производительность.", - "ErrorLogTooltip": "Включает печать сообщений журнала ошибок. Не влияет на производительность.", + "InfoLogTooltip": "Включает вывод сообщений информационного журнала в консоль. Не влияет на производительность.", + "WarnLogTooltip": "Включает вывод сообщений журнала предупреждений в консоль. Не влияет на производительность.", + "ErrorLogTooltip": "Включает вывод сообщений журнала ошибок. Не влияет на производительность.", "TraceLogTooltip": "Выводит сообщения журнала трассировки в консоли. Не влияет на производительность.", - "GuestLogTooltip": "Включает печать сообщений гостевого журнала. Не влияет на производительность.", - "FileAccessLogTooltip": "Включает печать сообщений журнала доступа к файлам", + "GuestLogTooltip": "Включает вывод сообщений гостевого журнала. Не влияет на производительность.", + "FileAccessLogTooltip": "Включает вывод сообщений журнала доступа к файлам", "FSAccessLogModeTooltip": "Включает вывод журнала доступа к файловой системе. Возможные режимы: 0-3", "DeveloperOptionTooltip": "Используйте с осторожностью", "OpenGlLogLevel": "Требует включения соответствующих уровней ведения журнала", @@ -487,10 +492,10 @@ "LoadApplicationFileTooltip": "Открыть файловый менеджер для выбора файла, совместимого с Nintendo Switch.", "LoadApplicationFolderTooltip": "Открыть файловый менеджер для выбора распакованного приложения, совместимого с Nintendo Switch.", "OpenRyujinxFolderTooltip": "Открывает папку файловой системы Ryujinx. ", - "OpenRyujinxLogsTooltip": "Открывает папку, в которую записываются журналы", + "OpenRyujinxLogsTooltip": "Открывает папку, в которую записываются логи", "ExitTooltip": "Выйти из Ryujinx", - "OpenSettingsTooltip": "Открыть окно настроек", - "OpenProfileManagerTooltip": "Открыть диспетчер профилей", + "OpenSettingsTooltip": "Открыть окно параметров", + "OpenProfileManagerTooltip": "Открыть менеджер учетных записей", "StopEmulationTooltip": "Остановка эмуляции текущей игры и возврат к списку игр", "CheckUpdatesTooltip": "Проверка наличия обновления Ryujinx", "OpenAboutTooltip": "Открыть окно «О программе»", @@ -504,6 +509,8 @@ "EnableInternetAccessTooltip": "Позволяет эмулированному приложению подключаться к Интернету.\n\nПри включении этой функции игры с возможностью сетевой игры могут подключаться друг к другу, если все эмуляторы (или реальные консоли) подключены к одной и той же точке доступа.\n\nНЕ разрешает подключение к серверам Nintendo. Может вызвать сбой в некоторых играх, которые пытаются подключиться к Интернету.\n\nРекомендутеся оставить выключенным.", "GameListContextMenuManageCheatToolTip": "Управление читами", "GameListContextMenuManageCheat": "Управление читами", + "GameListContextMenuManageModToolTip": "Управление модами", + "GameListContextMenuManageMod": "Управление модами", "ControllerSettingsStickRange": "Диапазон:", "DialogStopEmulationTitle": "Ryujinx - Остановить эмуляцию", "DialogStopEmulationMessage": "Вы уверены, что хотите остановить эмуляцию?", @@ -515,10 +522,8 @@ "SettingsTabCpuMemory": "Память ЦП", "DialogUpdaterFlatpakNotSupportedMessage": "Пожалуйста, обновите Ryujinx через FlatHub.", "UpdaterDisabledWarningTitle": "Обновление выключено!", - "GameListContextMenuOpenSdModsDirectory": "Открыть папку с модами Atmosphere", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Открывает альтернативную папку SD-карты Atmosphere, которая содержит моды для приложений и игр. Полезно для модов, сделанных для реальной консоли.", "ControllerSettingsRotate90": "Повернуть на 90° по часовой стрелке", - "IconSize": "Размер иконки", + "IconSize": "Размер обложек", "IconSizeTooltip": "Изменить размер игровых иконок", "MenuBarOptionsShowConsole": "Показать консоль", "ShaderCachePurgeError": "Ошибка очистки кэша шейдеров в {0}: {1}", @@ -535,7 +540,7 @@ "UserErrorUnknownDescription": "Произошла неизвестная ошибка!", "UserErrorUndefinedDescription": "Произошла неизвестная ошибка! Такого не должно происходить. Пожалуйста, свяжитесь с разработчиками!", "OpenSetupGuideMessage": "Открыть руководство по установке", - "NoUpdate": "Нет обновлений", + "NoUpdate": "Без обновлений", "TitleUpdateVersionLabel": "Version {0} - {1}", "RyujinxInfo": "Ryujinx - Информация", "RyujinxConfirm": "Ryujinx - Подтверждение", @@ -544,12 +549,13 @@ "SwkbdMinCharacters": "Должно быть не менее {0} символов.", "SwkbdMinRangeCharacters": "Должно быть {0}-{1} символов", "SoftwareKeyboard": "Программная клавиатура", - "SoftwareKeyboardModeNumbersOnly": "Должны быть только цифры", + "SoftwareKeyboardModeNumeric": "Должно быть в диапазоне 0-9 или '.'", "SoftwareKeyboardModeAlphabet": "Не должно быть CJK-символов", "SoftwareKeyboardModeASCII": "Текст должен быть только в ASCII кодировке", - "DialogControllerAppletMessagePlayerRange": "Приложение запрашивает {0} игроков с:\n\nТИПЫ: {1}\n\nИГРОКИ: {2}\n\n{3}Пожалуйста, откройте \"Настройки\" и перенастройте сейчас или нажмите \"Закрыть\".", - "DialogControllerAppletMessage": "Приложение запрашивает ровно {0} игроков с:\n\nТИПЫ: {1}\n\nИГРОКИ: {2}\n\n{3}Пожалуйста, откройте \"Настройки\" и перенастройте Ввод или нажмите \"Закрыть\".", - "DialogControllerAppletDockModeSet": "Установлен стационарный режим. Портативный режим недоступен.", + "ControllerAppletControllers": "Поддерживаемые геймпады:", + "ControllerAppletPlayers": "Игроки:", + "ControllerAppletDescription": "Текущая конфигурация некорректна. Откройте параметры и перенастройте управление.", + "ControllerAppletDocked": "Используется стационарный режим. Управление в портативном режиме должно быть отключено.", "UpdaterRenaming": "Переименование старых файлов...", "UpdaterRenameFailed": "Программе обновления не удалось переименовать файл: {0}", "UpdaterAddingFiles": "Добавление новых файлов...", @@ -559,7 +565,7 @@ "Docked": "Стационарный режим", "Handheld": "Портативный режим", "ConnectionError": "Ошибка соединения!", - "AboutPageDeveloperListMore": "{0} и больше...", + "AboutPageDeveloperListMore": "{0} и другие...", "ApiError": "Ошибка API.", "LoadingHeading": "Загрузка {0}", "CompilingPPTC": "Компиляция PTC", @@ -571,11 +577,11 @@ "RyujinxUpdater": "Ryujinx - Обновление", "SettingsTabHotkeys": "Горячие клавиши", "SettingsTabHotkeysHotkeys": "Горячие клавиши", - "SettingsTabHotkeysToggleVsyncHotkey": "Переключить VSync:", + "SettingsTabHotkeysToggleVsyncHotkey": "Вкл./выкл. VSync:", "SettingsTabHotkeysScreenshotHotkey": "Скриншот:", "SettingsTabHotkeysShowUiHotkey": "Показать UI:", "SettingsTabHotkeysPauseHotkey": "Пауза:", - "SettingsTabHotkeysToggleMuteHotkey": "Приглушить:", + "SettingsTabHotkeysToggleMuteHotkey": "Выключить звук:", "ControllerMotionTitle": "Настройки управления движением", "ControllerRumbleTitle": "Настройки вибрации", "SettingsSelectThemeFileDialogTitle": "Выбрать файл темы", @@ -587,17 +593,20 @@ "Writable": "Доступно для записи", "SelectDlcDialogTitle": "Выберите файлы DLC", "SelectUpdateDialogTitle": "Выберите файлы обновления", - "UserProfileWindowTitle": "Менеджер профилей пользователей", + "SelectModDialogTitle": "Выбрать папку с модами", + "UserProfileWindowTitle": "Менеджер учетных записей", "CheatWindowTitle": "Менеджер читов", - "DlcWindowTitle": "Управление загружаемым контентом для {0} ({1})", - "UpdateWindowTitle": "Менеджер обновления названий", - "CheatWindowHeading": "Читы доступны для {0} [{1}]", + "DlcWindowTitle": "Управление DLC для {0} ({1})", + "UpdateWindowTitle": "Менеджер обновлений игр", + "CheatWindowHeading": "Доступные читы для {0} [{1}]", "BuildId": "ID версии:", - "DlcWindowHeading": "{0} Загружаемый контент", + "DlcWindowHeading": "{0} DLC", + "ModWindowHeading": "Мод(ы) {0} ", "UserProfilesEditProfile": "Изменить выбранные", "Cancel": "Отмена", "Save": "Сохранить", "Discard": "Отменить", + "Paused": "Приостановлено", "UserProfilesSetProfileImage": "Установить изображение профиля", "UserProfileEmptyNameError": "Имя обязательно", "UserProfileNoImageError": "Изображение профиля должно быть установлено", @@ -606,12 +615,12 @@ "SettingsTabHotkeysResScaleDownHotkey": "Уменьшить разрешение:", "UserProfilesName": "Имя:", "UserProfilesUserId": "ID пользователя:", - "SettingsTabGraphicsBackend": "Бэкенд графики", - "SettingsTabGraphicsBackendTooltip": "Использовать графический бэкенд", + "SettingsTabGraphicsBackend": "Графический бэкенд", + "SettingsTabGraphicsBackendTooltip": "Выберите бэкенд, который будет использован в эмуляторе.\n\nVulkan является лучшим выбором для всех современных графических карт с актуальными драйверами. В Vulkan также включена более быстрая компиляция шейдеров (меньше статтеров) для всех GPU.\n\nOpenGL может достичь лучших результатов на старых GPU Nvidia, на старых GPU AMD на Linux или на GPU с небольшим количеством VRAM, хотя статтеров при компиляции шейдеров будут больше.\n\nРекомендуется использовать Vulkan. Используйте OpenGL, если ваш GPU не поддерживает Vulkan даже с актуальными драйверами.", "SettingsEnableTextureRecompression": "Включить пережатие текстур", - "SettingsEnableTextureRecompressionTooltip": "Сжимает некоторые текстуры для уменьшения использования видеопамяти.\n\nРекомендуется для GPU с 4 гб видеопамяти и менее.\n\nРекомендуется оставить выключенным.", + "SettingsEnableTextureRecompressionTooltip": "Сжатие ASTC текстур для уменьшения использования VRAM. \n\nИгры, использующие этот формат текстур, включая Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder и The Legend of Zelda: Tears of the Kingdom. \n\nНа GPU с 4GiB VRAM или менее возможны вылеты при запуске этих игр. \n\nВключите, только если у вас заканчивается VRAM в вышеупомянутых играх. Рекомендуется оставить выключенным.", "SettingsTabGraphicsPreferredGpu": "Предпочтительный GPU", - "SettingsTabGraphicsPreferredGpuTooltip": "Выберите видеокарту, которая будет использоваться с графическим бэкендом Vulkan.\n\nНе влияет на GPU, который будет использовать OpenGL.\n\nУстановите графический процессор, помеченный как \"dGPU\", если вы не уверены. Если его нет, оставьте нетронутым.", + "SettingsTabGraphicsPreferredGpuTooltip": "Выберите графический процессор, которая будет использоваться с графическим бэкендом Vulkan.\n\nЭта настройка не влияет на графический процессор, который будет использовать OpenGL.\n\nЕсли вы не уверены что нужно выбрать, используйте графический процессор, помеченный как \"dGPU\". Если его нет, оставьте выбор по умолчанию.", "SettingsAppRequiredRestartMessage": "Требуется перезапуск Ryujinx", "SettingsGpuBackendRestartMessage": "Графический бэкенд или настройки графического процессора были изменены. Требуется перезапуск для вступления в силу изменений.", "SettingsGpuBackendRestartSubMessage": "Перезапустить сейчас?", @@ -621,7 +630,7 @@ "SettingsEnableMacroHLE": "Включить Macro HLE", "SettingsEnableMacroHLETooltip": "Высокоуровневая эмуляции макроса GPU.\n\nПовышает производительность, но может вызывать графические сбои в некоторых играх.\n\nРекомендуется оставить включенным.", "SettingsEnableColorSpacePassthrough": "Пропуск цветового пространства", - "SettingsEnableColorSpacePassthroughTooltip": "Направляет бэкэнд Vulkan на передачу информации о цвете без указания цветового пространства. Для пользователей с экранами с расширенной гаммой данная настройка приводит к получению более ярких цветов за счет снижения корректности цветопередачи.", + "SettingsEnableColorSpacePassthroughTooltip": "Направляет бэкенд Vulkan на передачу информации о цвете без указания цветового пространства. Для пользователей с экранами с расширенной гаммой данная настройка приводит к получению более ярких цветов за счет снижения корректности цветопередачи.", "VolumeShort": "Громкость", "UserProfilesManageSaves": "Управление сохранениями", "DeleteUserSave": "Вы хотите удалить сохранение пользователя для этой игры?", @@ -631,16 +640,16 @@ "Name": "Название", "Size": "Размер", "Search": "Поиск", - "UserProfilesRecoverLostAccounts": "Восстановить утерянные аккаунты", + "UserProfilesRecoverLostAccounts": "Восстановить учетные записи", "Recover": "Восстановление", "UserProfilesRecoverHeading": "Были найдены сохранения для следующих аккаунтов", - "UserProfilesRecoverEmptyList": "Нет профилей для восстановления", - "GraphicsAATooltip": "Применяет сглаживание к рейдеру игры.", + "UserProfilesRecoverEmptyList": "Нет учетных записей для восстановления", + "GraphicsAATooltip": "Применимое сглаживание для рендера.\n\nFXAA размывает большую часть изображения, SMAA попытается найти \"зазубренные\" края и сгладить их.\n\nНе рекомендуется использовать вместе с масштабирующим фильтром FSR.\n\nЭта опция может быть изменена во время игры по нажатию \"Применить\" ниже; Вы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не найдёте подходящую настройку игры.\n\nРекомендуется использовать \"Нет\".", "GraphicsAALabel": "Сглаживание:", - "GraphicsScalingFilterLabel": "Масштабирующий фильтр:", - "GraphicsScalingFilterTooltip": "Включает масштабирование кадрового буфера", + "GraphicsScalingFilterLabel": "Интерполяция:", + "GraphicsScalingFilterTooltip": "Выберите фильтрацию, которая будет применяться при масштабировании.\n\n\"Билинейная\" хорошо работает для 3D-игр и является настройкой по умолчанию.\n\n\"Точный\" рекомендуется для пиксельных .\n\n\"FSR\" это фильтр резкости, который не рекомендуется использовать с FXAA или SMAA.\n\nЭта опция может быть изменена во время игры по нажатию кнопки \"Применить\" ниже; Вы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не подберете подходящие настройки для конкретной игры.\n\nРекомендуется использовать \"Билинейная\".", "GraphicsScalingFilterLevelLabel": "Уровень", - "GraphicsScalingFilterLevelTooltip": "Установить уровень фильтра масштабирования", + "GraphicsScalingFilterLevelTooltip": "Выбор режима работы FSR 1.0. Выше - четче.", "SmaaLow": "SMAA Низкое", "SmaaMedium": "SMAA Среднее", "SmaaHigh": "SMAA Высокое", @@ -648,9 +657,12 @@ "UserEditorTitle": "Редактирование пользователя", "UserEditorTitleCreate": "Создание пользователя", "SettingsTabNetworkInterface": "Сетевой Интерфейс", - "NetworkInterfaceTooltip": "Сетевой интерфейс, используемый для функций LAN", + "NetworkInterfaceTooltip": "Сетевой интерфейс, используемый для функций LAN/LDN.\n\nВ сочетании с VPN или XLink Kai и игрой с поддержкой LAN, может использоваться для создания локальной сети через интернет.\n\nРекомендуется использовать \"По умолчанию\".", "NetworkInterfaceDefault": "По умолчанию", "PackagingShaders": "Упаковка шейдеров", "AboutChangelogButton": "Список изменений на GitHub", - "AboutChangelogButtonTooltipMessage": "Нажмите, чтобы открыть список изменений для этой версии в браузере." -} \ No newline at end of file + "AboutChangelogButtonTooltipMessage": "Нажмите, чтобы открыть список изменений для этой версии", + "SettingsTabNetworkMultiplayer": "Мультиплеер", + "MultiplayerMode": "Режим:", + "MultiplayerModeTooltip": "Изменение многопользовательского режима LDN.\n\nLdnMitm модифицирует функциональность локальной беспроводной/игры на одном устройстве в играх, позволяя играть с другими пользователями Ryujinx или взломанными консолями Nintendo Switch с установленным модулем ldn_mitm, находящимися в одной локальной сети друг с другом.\n\nМногопользовательская игра требует наличия у всех игроков одной и той же версии игры (т.е. Super Smash Bros. Ultimate v13.0.1 не может подключиться к v13.0.0).\n\nРекомендуется оставить отключенным." +} diff --git a/src/Ryujinx/Assets/Locales/th_TH.json b/src/Ryujinx/Assets/Locales/th_TH.json new file mode 100644 index 000000000..61f50c7c2 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/th_TH.json @@ -0,0 +1,668 @@ +{ + "Language": "ภาษาไทย", + "MenuBarFileOpenApplet": "เปิด Applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "เปิด Mii Editor Applet ในโหมดสแตนด์อโลน", + "SettingsTabInputDirectMouseAccess": "เข้าถึงเมาส์ได้โดยตรง", + "SettingsTabSystemMemoryManagerMode": "โหมดจัดการหน่วยความจำ:", + "SettingsTabSystemMemoryManagerModeSoftware": "ซอฟต์แวร์", + "SettingsTabSystemMemoryManagerModeHost": "โฮสต์ (เร็ว)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "ไม่ได้ตรวจสอบโฮสต์ (เร็วที่สุด, แต่ไม่ปลอดภัย)", + "SettingsTabSystemUseHypervisor": "ใช้งาน Hypervisor", + "MenuBarFile": "ไฟล์", + "MenuBarFileOpenFromFile": "โหลดแอปพลิเคชั่นจากไฟล์", + "MenuBarFileOpenUnpacked": "โหลดเกมที่คลายแพ็กแล้ว", + "MenuBarFileOpenEmuFolder": "เปิดโฟลเดอร์ รียูจินซ์", + "MenuBarFileOpenLogsFolder": "เปิดโฟลเดอร์ Logs", + "MenuBarFileExit": "ออก", + "MenuBarOptions": "_ตัวเลือก", + "MenuBarOptionsToggleFullscreen": "สลับการแสดงผลแบบเต็มหน้าจอ", + "MenuBarOptionsStartGamesInFullscreen": "เริ่มเกมในโหมดเต็มหน้าจอ", + "MenuBarOptionsStopEmulation": "หยุดการจำลอง", + "MenuBarOptionsSettings": "การตั้งค่า", + "MenuBarOptionsManageUserProfiles": "จัดการโปรไฟล์ผู้ใช้งาน", + "MenuBarActions": "การดำเนินการ", + "MenuBarOptionsSimulateWakeUpMessage": "จำลองข้อความปลุก", + "MenuBarActionsScanAmiibo": "สแกนหา อะมิโบ", + "MenuBarTools": "_เครื่องมือ", + "MenuBarToolsInstallFirmware": "ติดตั้งเฟิร์มแวร์", + "MenuBarFileToolsInstallFirmwareFromFile": "ติดตั้งเฟิร์มแวร์จาก ไฟล์ XCI หรือ ไฟล์ ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "ติดตั้งเฟิร์มแวร์จากไดเร็กทอรี", + "MenuBarToolsManageFileTypes": "จัดการประเภทไฟล์", + "MenuBarToolsInstallFileTypes": "ติดตั้งประเภทไฟล์", + "MenuBarToolsUninstallFileTypes": "ถอนการติดตั้งประเภทไฟล์", + "MenuBarHelp": "_ช่วยเหลือ", + "MenuBarHelpCheckForUpdates": "ตรวจหาการอัพเดต", + "MenuBarHelpAbout": "เกี่ยวกับ", + "MenuSearch": "กำลังค้นหา...", + "GameListHeaderFavorite": "ชื่นชอบ", + "GameListHeaderIcon": "ไอคอน", + "GameListHeaderApplication": "ชื่อ", + "GameListHeaderDeveloper": "ผู้พัฒนา", + "GameListHeaderVersion": "เวอร์ชั่น", + "GameListHeaderTimePlayed": "เวลาที่เล่นไปแล้ว", + "GameListHeaderLastPlayed": "เล่นล่าสุด", + "GameListHeaderFileExtension": "นามสกุลไฟล์", + "GameListHeaderFileSize": "ขนาดไฟล์", + "GameListHeaderPath": "ที่เก็บไฟล์", + "GameListContextMenuOpenUserSaveDirectory": "เปิดไดเร็กทอรี่บันทึกของผู้ใช้", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "เปิดไดเร็กทอรี่ซึ่งมีการบันทึกผู้ใช้ของแอปพลิเคชัน", + "GameListContextMenuOpenDeviceSaveDirectory": "เปิดไดเร็กทอรีบันทึกของอุปกรณ์", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "เปิดไดเรกทอรี่ซึ่งมีบันทึกอุปกรณ์ของแอปพลิเคชัน", + "GameListContextMenuOpenBcatSaveDirectory": "เปิดไดเรกทอรีบันทึก BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "เปิดไดเรกทอรี่ซึ่งมีการบันทึก BCAT ของแอปพลิเคชัน", + "GameListContextMenuManageTitleUpdates": "จัดการ การอัปเดตหัวข้อ", + "GameListContextMenuManageTitleUpdatesToolTip": "เปิดหน้าต่างการจัดการการอัพเดตหัวข้อ", + "GameListContextMenuManageDlc": "จัดการ DLC", + "GameListContextMenuManageDlcToolTip": "เปิดหน้าต่างการจัดการ DLC", + "GameListContextMenuCacheManagement": "การบริหารจัดการแคช", + "GameListContextMenuCacheManagementPurgePptc": "เพิ่มเข้าคิวงาน PPTC ที่สร้างใหม่", + "GameListContextMenuCacheManagementPurgePptcToolTip": "ทริกเกอร์ PPTC ให้สร้างใหม่ในเวลาบูตเมื่อเปิดตัวเกมครั้งถัดไป", + "GameListContextMenuCacheManagementPurgeShaderCache": "ล้าง เชเดอร์แคช", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "ลบ เชเดอร์แคช ของแอปพลิเคชัน", + "GameListContextMenuCacheManagementOpenPptcDirectory": "เปิดไดเรกทอรี่่ PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "เปิดไดเร็กทอรีที่มี PPTC แคช ของแอปพลิเคชัน", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "เปิดไดเรกทอรี่ เชเดอร์แคช", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "เปิดไดเรกทอรี่ที่มี แคชเชเดอร์ ของแอปพลิเคชัน", + "GameListContextMenuExtractData": "แยกข้อมูล", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "แยกส่วน ExeFS ออกจากการกำหนดค่าปัจจุบันของแอปพลิเคชัน (รวมถึงการอัปเดต)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "แยกส่วน RomFS ออกจากการกำหนดค่าปัจจุบันของแอปพลิเคชัน (รวมถึงการอัพเดต)", + "GameListContextMenuExtractDataLogo": "โลโก้", + "GameListContextMenuExtractDataLogoToolTip": "แยกส่วน โลโก้ ออกจากการกำหนดค่าปัจจุบันของแอปพลิเคชัน (รวมถึงการอัปเดต)", + "GameListContextMenuCreateShortcut": "สร้างทางลัดของแอปพลิเคชัน", + "GameListContextMenuCreateShortcutToolTip": "สร้างทางลัดบนเดสก์ท็อปที่เรียกใช้แอปพลิเคชันที่เลือก", + "GameListContextMenuCreateShortcutToolTipMacOS": "สร้างทางลัดในโฟลเดอร์ Applications ของ macOS ที่เรียกใช้ Application ที่เลือก", + "GameListContextMenuOpenModsDirectory": "เปิดไดเรกทอรี่ Mods", + "GameListContextMenuOpenModsDirectoryToolTip": "เปิดไดเร็กทอรีซึ่งมี Mods ของแอปพลิเคชัน", + "GameListContextMenuOpenSdModsDirectory": "เปิดไดเร็กทอรี Mods Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "เปิดไดเร็กทอรี Atmosphere ของการ์ด SD สำรองซึ่งมี Mods ของแอปพลิเคชัน มีประโยชน์สำหรับ mods ที่บรรจุมากับฮาร์ดแวร์จริง", + "StatusBarGamesLoaded": "เกมส์โหลดแล้ว {0}/{1}", + "StatusBarSystemVersion": "เวอร์ชั่นของระบบ: {0}", + "LinuxVmMaxMapCountDialogTitle": "ตรวจพบขีดจำกัดต่ำสุดสำหรับการแมปหน่วยความจำ", + "LinuxVmMaxMapCountDialogTextPrimary": "คุณต้องการที่จะเพิ่มค่า vm.max_map_count ไปยัง {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "บางเกมอาจพยายามสร้างการแมปหน่วยความจำมากกว่าที่ได้รับอนุญาตในปัจจุบัน รียูจินซ์ จะปิดตัวลงเมื่อเกินขีดจำกัดนี้", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "ใช่, จนกว่าจะรีสตาร์ทครั้งถัดไป", + "LinuxVmMaxMapCountDialogButtonPersistent": "ใช่, อย่างถาวร", + "LinuxVmMaxMapCountWarningTextPrimary": "จำนวนสูงสุดของการแม็ปหน่วยความจำ ต่ำกว่าที่แนะนำ", + "LinuxVmMaxMapCountWarningTextSecondary": "ค่าปัจจุบันของ vm.max_map_count ({0}) มีค่าต่ำกว่า {1} บางเกมอาจพยายามสร้างการแมปหน่วยความจำมากกว่าที่ได้รับอนุญาตในปัจจุบัน รียูจินซ์ จะปิดตัวลงเมื่อเกินขีดจำกัดนี้\n\nคุณอาจต้องการเพิ่มขีดจำกัดด้วยตนเองหรือติดตั้ง pkexec ซึ่งอนุญาตให้ ริวจินซ์ เพื่อช่วยเหลือคุณได้", + "Settings": "การตั้งค่า", + "SettingsTabGeneral": "หน้าจอผู้ใช้", + "SettingsTabGeneralGeneral": "ทั่วไป", + "SettingsTabGeneralEnableDiscordRichPresence": "เปิดใช้งาน Discord Rich Presence", + "SettingsTabGeneralCheckUpdatesOnLaunch": "ตรวจหาการอัปเดตเมื่อเปิดโปรแกรม", + "SettingsTabGeneralShowConfirmExitDialog": "แสดง \"ยืนยันการออก\" กล่องข้อความโต้ตอบ", + "SettingsTabGeneralHideCursor": "ซ่อน เคอร์เซอร์:", + "SettingsTabGeneralHideCursorNever": "ไม่มี", + "SettingsTabGeneralHideCursorOnIdle": "ซ่อนอัตโนมัติ", + "SettingsTabGeneralHideCursorAlways": "ตลอดเวลา", + "SettingsTabGeneralGameDirectories": "ไดเรกทอรี่ของเกม", + "SettingsTabGeneralAdd": "เพิ่ม", + "SettingsTabGeneralRemove": "เอาออก", + "SettingsTabSystem": "ระบบ", + "SettingsTabSystemCore": "แกนกลาง", + "SettingsTabSystemSystemRegion": "ภูมิภาคของระบบ:", + "SettingsTabSystemSystemRegionJapan": "ญี่ปุ่น", + "SettingsTabSystemSystemRegionUSA": "สหรัฐอเมริกา", + "SettingsTabSystemSystemRegionEurope": "ยุโรป", + "SettingsTabSystemSystemRegionAustralia": "ออสเตรเลีย", + "SettingsTabSystemSystemRegionChina": "จีน", + "SettingsTabSystemSystemRegionKorea": "เกาหลี", + "SettingsTabSystemSystemRegionTaiwan": "ไต้หวัน", + "SettingsTabSystemSystemLanguage": "ภาษาของระบบ:", + "SettingsTabSystemSystemLanguageJapanese": "ญี่ปุ่น", + "SettingsTabSystemSystemLanguageAmericanEnglish": "อังกฤษ (อเมริกัน)", + "SettingsTabSystemSystemLanguageFrench": "ฝรั่งเศส", + "SettingsTabSystemSystemLanguageGerman": "เยอรมัน", + "SettingsTabSystemSystemLanguageItalian": "อิตาลี", + "SettingsTabSystemSystemLanguageSpanish": "สเปน", + "SettingsTabSystemSystemLanguageChinese": "จีน", + "SettingsTabSystemSystemLanguageKorean": "เกาหลี", + "SettingsTabSystemSystemLanguageDutch": "ดัตช์", + "SettingsTabSystemSystemLanguagePortuguese": "โปรตุเกส", + "SettingsTabSystemSystemLanguageRussian": "รัสเซีย", + "SettingsTabSystemSystemLanguageTaiwanese": "จีนตัวเต็ม (ไต้หวัน)", + "SettingsTabSystemSystemLanguageBritishEnglish": "อังกฤษ (บริติช)", + "SettingsTabSystemSystemLanguageCanadianFrench": "ฝรั่งเศส (แคนาดา)", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "สเปน (ลาตินอเมริกา)", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "จีน (ตัวย่อ)", + "SettingsTabSystemSystemLanguageTraditionalChinese": "จีน (ดั้งเดิม)", + "SettingsTabSystemSystemTimeZone": "โซนเวลาของระบบ:", + "SettingsTabSystemSystemTime": "เวลาของระบบ:", + "SettingsTabSystemEnableVsync": "VSync", + "SettingsTabSystemEnablePptc": "PPTC (แคชการแปลแบบถาวรที่มีโปรไฟล์)", + "SettingsTabSystemEnableFsIntegrityChecks": "การตรวจสอบความถูกต้องของ FS", + "SettingsTabSystemAudioBackend": "แบ็กเอนด์เสียง:", + "SettingsTabSystemAudioBackendDummy": "ดัมมี่", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "แฮ็ก", + "SettingsTabSystemHacksNote": "อาจทำให้เกิดข้อผิดพลาดได้", + "SettingsTabSystemExpandDramSize": "ใช้รูปแบบหน่วยความจำสำรอง (โหมดนักพัฒนา)", + "SettingsTabSystemIgnoreMissingServices": "ไม่สนใจบริการที่ขาดหายไป", + "SettingsTabGraphics": "กราฟิก", + "SettingsTabGraphicsAPI": "เอพีไอของกราฟิก", + "SettingsTabGraphicsEnableShaderCache": "เปิดใช้งาน เชเดอร์ แคช", + "SettingsTabGraphicsAnisotropicFiltering": "ตัวกรองแบบ แอนไอโซทรอปิก:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "อัตโนมัติ", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "อัตราส่วนความละเอียด:", + "SettingsTabGraphicsResolutionScaleCustom": "กำหนดเอง (ไม่แนะนำ)", + "SettingsTabGraphicsResolutionScaleNative": "พื้นฐานของระบบ (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (ไม่แนะนำ)", + "SettingsTabGraphicsAspectRatio": "อัตราส่วนภาพ:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "ยืดภาพเพื่อให้พอดีกับหน้าต่าง", + "SettingsTabGraphicsDeveloperOptions": "ตัวเลือกนักพัฒนา", + "SettingsTabGraphicsShaderDumpPath": "ที่เก็บไฟล์ดัมพ์ของ เชเดอร์กราฟิก:", + "SettingsTabLogging": "การบันทึก", + "SettingsTabLoggingLogging": "การบันทึก", + "SettingsTabLoggingEnableLoggingToFile": "เปิดใช้งาน การบันทึกไปยังไฟล์", + "SettingsTabLoggingEnableStubLogs": "เปิดใช้งาน บันทึกของต้นขั้ว", + "SettingsTabLoggingEnableInfoLogs": "เปิดใช้งาน บันทึกของข้อมูล", + "SettingsTabLoggingEnableWarningLogs": "เปิดใช้งาน บันทึกคำเตือน", + "SettingsTabLoggingEnableErrorLogs": "เปิดใช้งาน บันทึกข้อผิดพลาด", + "SettingsTabLoggingEnableTraceLogs": "เปิดใช้งาน บันทึกการติดตาม", + "SettingsTabLoggingEnableGuestLogs": "เปิดใช้งาน บันทึกของผู้เยี่ยมชม", + "SettingsTabLoggingEnableFsAccessLogs": "เปิดใช้งาน บันทึกการเข้าถึง Fs", + "SettingsTabLoggingFsGlobalAccessLogMode": "โหมดบันทึกการเข้าถึงส่วนกลาง:", + "SettingsTabLoggingDeveloperOptions": "ตัวเลือกนักพัฒนา", + "SettingsTabLoggingDeveloperOptionsNote": "คำเตือน: จะทำให้ประสิทธิภาพลดลง", + "SettingsTabLoggingGraphicsBackendLogLevel": "ระดับการบันทึก แบ็กเอนด์กราฟิก:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "ไม่มี", + "SettingsTabLoggingGraphicsBackendLogLevelError": "ผิดพลาด", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "ชะลอตัว", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "ทั้งหมด", + "SettingsTabLoggingEnableDebugLogs": "เปิดใช้งานบันทึกการแก้ไขข้อบกพร่อง", + "SettingsTabInput": "ป้อนข้อมูล", + "SettingsTabInputEnableDockedMode": "ด็อกโหมด", + "SettingsTabInputDirectKeyboardAccess": "เข้าถึงคีย์บอร์ดโดยตรง", + "SettingsButtonSave": "บันทึก", + "SettingsButtonClose": "ปิด", + "SettingsButtonOk": "ตกลง", + "SettingsButtonCancel": "ยกเลิก", + "SettingsButtonApply": "นำไปใช้", + "ControllerSettingsPlayer": "ผู้เล่น", + "ControllerSettingsPlayer1": "ผู้เล่นคนที่ 1", + "ControllerSettingsPlayer2": "ผู้เล่นคนที่ 2", + "ControllerSettingsPlayer3": "ผู้เล่นคนที่ 3", + "ControllerSettingsPlayer4": "ผู้เล่นคนที่ 4", + "ControllerSettingsPlayer5": "ผู้เล่นคนที่ 5", + "ControllerSettingsPlayer6": "ผู้เล่นคนที่ 6", + "ControllerSettingsPlayer7": "ผู้เล่นคนที่ 7", + "ControllerSettingsPlayer8": "ผู้เล่นคนที่ 8", + "ControllerSettingsHandheld": "แฮนด์เฮลด์โหมด", + "ControllerSettingsInputDevice": "อุปกรณ์ป้อนข้อมูล", + "ControllerSettingsRefresh": "รีเฟรช", + "ControllerSettingsDeviceDisabled": "ปิดการใช้งาน", + "ControllerSettingsControllerType": "ประเภทของคอนโทรลเลอร์", + "ControllerSettingsControllerTypeHandheld": "แฮนด์เฮลด์", + "ControllerSettingsControllerTypeProController": "โปรคอนโทรลเลอร์", + "ControllerSettingsControllerTypeJoyConPair": "จับคู่ จอยคอน", + "ControllerSettingsControllerTypeJoyConLeft": "จอยคอน ด้านซ้าย", + "ControllerSettingsControllerTypeJoyConRight": "จอยคอน ด้านขวา", + "ControllerSettingsProfile": "โปรไฟล์", + "ControllerSettingsProfileDefault": "ค่าเริ่มต้น", + "ControllerSettingsLoad": "โหลด", + "ControllerSettingsAdd": "เพิ่ม", + "ControllerSettingsRemove": "เอาออก", + "ControllerSettingsButtons": "ปุ่มกด", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "ปุ่มลูกศร", + "ControllerSettingsDPadUp": "ขึ้น", + "ControllerSettingsDPadDown": "ลง", + "ControllerSettingsDPadLeft": "ซ้าย", + "ControllerSettingsDPadRight": "ขวา", + "ControllerSettingsStickButton": "ปุ่ม", + "ControllerSettingsStickUp": "ขึ้น", + "ControllerSettingsStickDown": "ลง", + "ControllerSettingsStickLeft": "ซ้าย", + "ControllerSettingsStickRight": "ขวา", + "ControllerSettingsStickStick": "จอยสติ๊ก", + "ControllerSettingsStickInvertXAxis": "กลับทิศทางของแกน X", + "ControllerSettingsStickInvertYAxis": "กลับทิศทางของแกน Y", + "ControllerSettingsStickDeadzone": "โซนที่ไม่ทำงานของ จอยสติ๊ก:", + "ControllerSettingsLStick": "จอยสติ๊ก ด้านซ้าย", + "ControllerSettingsRStick": "จอยสติ๊ก ด้านขวา", + "ControllerSettingsTriggersLeft": "ทริกเกอร์ ด้านซ้าย", + "ControllerSettingsTriggersRight": "ทริกเกอร์ ด้านขวา", + "ControllerSettingsTriggersButtonsLeft": "ปุ่มทริกเกอร์ ด้านซ้าย", + "ControllerSettingsTriggersButtonsRight": "ปุ่มทริกเกอร์ ด้านขวา", + "ControllerSettingsTriggers": "ทริกเกอร์", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "ปุ่มกดเสริม ด้านซ้าย", + "ControllerSettingsExtraButtonsRight": "ปุ่มกดเสริม ด้านขวา", + "ControllerSettingsMisc": "การควบคุมเพิ่มเติม", + "ControllerSettingsTriggerThreshold": "ตั้งค่าขีดจำกัดของ ทริกเกอร์:", + "ControllerSettingsMotion": "การเคลื่อนไหว", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "ใช้การเคลื่อนไหวที่เข้ากันได้กับ CemuHook", + "ControllerSettingsMotionControllerSlot": "ช่องเสียบ คอนโทรลเลอร์:", + "ControllerSettingsMotionMirrorInput": "นำเข้าการสะท้อน การควบคุม", + "ControllerSettingsMotionRightJoyConSlot": "ช่องเสียบ จอยคอน ด้านขวา:", + "ControllerSettingsMotionServerHost": "เซิร์ฟเวอร์โฮสต์:", + "ControllerSettingsMotionGyroSensitivity": "ความไวของไจโร:", + "ControllerSettingsMotionGyroDeadzone": "โซนที่ไม่ทำงานของไจโร:", + "ControllerSettingsSave": "บันทึก", + "ControllerSettingsClose": "ปิด", + "UserProfilesSelectedUserProfile": "โปรไฟล์ผู้ใช้งานที่เลือก:", + "UserProfilesSaveProfileName": "บันทึกชื่อโปรไฟล์", + "UserProfilesChangeProfileImage": "เปลี่ยนรูปโปรไฟล์", + "UserProfilesAvailableUserProfiles": "โปรไฟล์ผู้ใช้ที่ใช้งานได้:", + "UserProfilesAddNewProfile": "สร้างโปรไฟล์ใหม่", + "UserProfilesDelete": "ลบ", + "UserProfilesClose": "ปิด", + "ProfileNameSelectionWatermark": "เลือกชื่อเล่น", + "ProfileImageSelectionTitle": "เลือกรูปโปรไฟล์ของคุณ", + "ProfileImageSelectionHeader": "เลือกรูปโปรไฟล์", + "ProfileImageSelectionNote": "คุณสามารถนำเข้ารูปโปรไฟล์ที่กำหนดเอง หรือเลือกอวาต้าจากเฟิร์มแวร์ระบบได้", + "ProfileImageSelectionImportImage": "นำเข้าไฟล์รูปภาพ", + "ProfileImageSelectionSelectAvatar": "เลือกรูปอวาต้าเฟิร์มแวร์", + "InputDialogTitle": "กล่องโต้ตอบการป้อนข้อมูล", + "InputDialogOk": "ตกลง", + "InputDialogCancel": "ยกเลิก", + "InputDialogAddNewProfileTitle": "เลือกชื่อโปรไฟล์", + "InputDialogAddNewProfileHeader": "กรุณาใส่ชื่อโปรไฟล์", + "InputDialogAddNewProfileSubtext": "(ความยาวสูงสุด: {0})", + "AvatarChoose": "เลือกรูปอวาต้าของคุณ", + "AvatarSetBackgroundColor": "ตั้งค่าสีพื้นหลัง", + "AvatarClose": "ปิด", + "ControllerSettingsLoadProfileToolTip": "โหลดโปรไฟล์", + "ControllerSettingsAddProfileToolTip": "เพิ่มโปรไฟล์", + "ControllerSettingsRemoveProfileToolTip": "ลบโปรไฟล์", + "ControllerSettingsSaveProfileToolTip": "บันทึกโปรไฟล์", + "MenuBarFileToolsTakeScreenshot": "ถ่ายภาพหน้าจอ", + "MenuBarFileToolsHideUi": "ซ่อน UI", + "GameListContextMenuRunApplication": "เรียกใช้แอปพลิเคชัน", + "GameListContextMenuToggleFavorite": "สลับรายการโปรด", + "GameListContextMenuToggleFavoriteToolTip": "สลับสถานะเกมที่ชื่นชอบ", + "SettingsTabGeneralTheme": "ธีม:", + "SettingsTabGeneralThemeDark": "มืด", + "SettingsTabGeneralThemeLight": "สว่าง", + "ControllerSettingsConfigureGeneral": "กำหนดค่า", + "ControllerSettingsRumble": "การสั่นไหว", + "ControllerSettingsRumbleStrongMultiplier": "เพิ่มความแรงการสั่นไหว", + "ControllerSettingsRumbleWeakMultiplier": "ลดความแรงการสั่นไหว", + "DialogMessageSaveNotAvailableMessage": "ไม่มีข้อมูลบันทึกไว้สำหรับ {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "คุณต้องการสร้างข้อมูลบันทึกสำหรับเกมนี้หรือไม่?", + "DialogConfirmationTitle": "ริวจินซ์ - ยืนยัน", + "DialogUpdaterTitle": "รียูจินซ์ - อัพเดต", + "DialogErrorTitle": "รียูจินซ์ - ผิดพลาด", + "DialogWarningTitle": "รียูจินซ์ - คำเตือน", + "DialogExitTitle": "รียูจินซ์ - ออก", + "DialogErrorMessage": "รียูจินซ์ พบข้อผิดพลาด", + "DialogExitMessage": "คุณแน่ใจหรือไม่ว่าต้องการปิด ริวจินซ์ หรือไม่?", + "DialogExitSubMessage": "ข้อมูลที่ไม่ได้บันทึกทั้งหมดจะสูญหาย!", + "DialogMessageCreateSaveErrorMessage": "มีข้อผิดพลาดในการสร้างข้อมูลการบันทึกที่ระบุ: {0}", + "DialogMessageFindSaveErrorMessage": "มีข้อผิดพลาดในการค้นหาข้อมูลที่บันทึกไว้ที่ระบุ: {0}", + "FolderDialogExtractTitle": "เลือกโฟลเดอร์ที่จะแตกไฟล์เข้าไป", + "DialogNcaExtractionMessage": "กำลังแตกไฟล์ {0} จากส่วน {1}...", + "DialogNcaExtractionTitle": "รียูจินซ์ - เครื่องมือแตกไฟล์ของ NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "เกิดความล้มเหลวในการแตกไฟล์เนื่องจากไม่พบ NCA หลักในไฟล์ที่เลือก", + "DialogNcaExtractionCheckLogErrorMessage": "เกิดความล้มเหลวในการแตกไฟล์ โปรดอ่านไฟล์บันทึกเพื่อดูข้อมูลเพิ่มเติม", + "DialogNcaExtractionSuccessMessage": "การแตกไฟล์เสร็จสมบูรณ์แล้ว", + "DialogUpdaterConvertFailedMessage": "ไม่สามารถแปลงเวอร์ชั่น รียูจินซ์ ปัจจุบันได้", + "DialogUpdaterCancelUpdateMessage": "ยกเลิกการอัพเดต!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "คุณกำลังใช้ รียูจินซ์ เวอร์ชั่นที่อัปเดตล่าสุด!", + "DialogUpdaterFailedToGetVersionMessage": "เกิดข้อผิดพลาดขณะพยายามรับข้อมูลเวอร์ชั่นจาก GitHub Release ปัญหานี้อาจเกิดขึ้นได้หากมีการรวบรวมเวอร์ชั่นใหม่โดย GitHub Actions โปรดลองอีกครั้งในอีกไม่กี่นาทีข้างหน้า", + "DialogUpdaterConvertFailedGithubMessage": "ไม่สามารถแปลงเวอร์ชั่น รียูจินซ์ ที่ได้รับจาก Github Release", + "DialogUpdaterDownloadingMessage": "กำลังดาวน์โหลดอัปเดต...", + "DialogUpdaterExtractionMessage": "กำลังแตกไฟล์อัปเดต...", + "DialogUpdaterRenamingMessage": "กำลังลบไฟล์เก่า...", + "DialogUpdaterAddingFilesMessage": "กำลังเพิ่มไฟล์อัปเดตใหม่...", + "DialogUpdaterCompleteMessage": "อัปเดตเสร็จสมบูรณ์แล้ว!", + "DialogUpdaterRestartMessage": "คุณต้องการรีสตาร์ท รียูจินซ์ ตอนนี้หรือไม่?", + "DialogUpdaterNoInternetMessage": "คุณไม่ได้เชื่อมต่อกับอินเทอร์เน็ต!", + "DialogUpdaterNoInternetSubMessage": "โปรดตรวจสอบว่าคุณมีการเชื่อมต่ออินเทอร์เน็ตว่ามีการใช้งานได้หรือไม่!", + "DialogUpdaterDirtyBuildMessage": "คุณไม่สามารถอัปเดต Dirty build ของ รียูจินซ์ ได้!", + "DialogUpdaterDirtyBuildSubMessage": "โปรดดาวน์โหลด รียูจินซ์ ได้ที่ https://ryujinx.org/ หากคุณกำลังมองหาเวอร์ชั่นที่รองรับ", + "DialogRestartRequiredMessage": "จำเป็นต้องรีสตาร์ทเพื่อให้การอัพเดตสามารถให้งานได้", + "DialogThemeRestartMessage": "บันทึกธีมแล้ว จำเป็นต้องรีสตาร์ทเพื่อใช้ธีม", + "DialogThemeRestartSubMessage": "คุณต้องการรีสตาร์ทหรือไม่?", + "DialogFirmwareInstallEmbeddedMessage": "คุณต้องการติดตั้งเฟิร์มแวร์ที่ฝังอยู่ในเกมนี้หรือไม่? (เฟิร์มแวร์ {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "ไม่พบเฟิร์มแวร์ที่ติดตั้งไว้ แต่ รียูจินซ์ สามารถติดตั้งเฟิร์มแวร์ได้ {0} จากเกมที่ให้มา\\nตอนนี้โปรแกรมจำลองจะเริ่มทำงาน", + "DialogFirmwareNoFirmwareInstalledMessage": "ไม่มีการติดตั้งเฟิร์มแวร์", + "DialogFirmwareInstalledMessage": "เฟิร์มแวร์ทำการติดตั้งแล้ว {0}", + "DialogInstallFileTypesSuccessMessage": "ติดตั้งประเภทไฟล์สำเร็จแล้ว!", + "DialogInstallFileTypesErrorMessage": "ติดตั้งประเภทไฟล์ไม่สำเร็จ", + "DialogUninstallFileTypesSuccessMessage": "ถอนการติดตั้งประเภทไฟล์สำเร็จแล้ว!", + "DialogUninstallFileTypesErrorMessage": "ไม่สามารถถอนการติดตั้งประเภทไฟล์ได้", + "DialogOpenSettingsWindowLabel": "เปิดหน้าต่างการตั้งค่า", + "DialogControllerAppletTitle": "แอพเพล็ตคอนโทรลเลอร์", + "DialogMessageDialogErrorExceptionMessage": "เกิดข้อผิดพลาดในการแสดงกล่องโต้ตอบข้อความ: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "เกิดข้อผิดพลาดในการแสดงซอฟต์แวร์แป้นพิมพ์: {0}", + "DialogErrorAppletErrorExceptionMessage": "เกิดข้อผิดพลาดในการแสดงกล่องโต้ตอบ ข้อผิดพลาด แอปเพล็ต: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nสำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีแก้ไขข้อผิดพลาดนี้ โปรดทำตามคำแนะนำในการตั้งค่าของเรา", + "DialogUserErrorDialogTitle": "ข้อผิดพลาด รียูจินซ์ ({0})", + "DialogAmiiboApiTitle": "อะมิโบ API", + "DialogAmiiboApiFailFetchMessage": "เกิดข้อผิดพลาดขณะเรียกข้อมูลจาก API", + "DialogAmiiboApiConnectErrorMessage": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ อะมิโบ API บ้างบริการอาจหยุดทำงาน หรือไม่คุณต้องทำการตรวจสอบว่าการเชื่อมต่ออินเทอร์เน็ตของคุณอยู่ในสถานะเชื่อมต่ออยู่หรือไม่", + "DialogProfileInvalidProfileErrorMessage": "โปรไฟล์ {0} เข้ากันไม่ได้กับระบบการกำหนดค่าอินพุตปัจจุบัน", + "DialogProfileDefaultProfileOverwriteErrorMessage": "ไม่สามารถเขียนทับโปรไฟล์เริ่มต้นได้", + "DialogProfileDeleteProfileTitle": "กำลังลบโปรไฟล์", + "DialogProfileDeleteProfileMessage": "การดำเนินการนี้ไม่สามารถย้อนกลับได้ คุณแน่ใจหรือไม่ว่าต้องการดำเนินการต่อหรือไม่?", + "DialogWarning": "คำเตือน", + "DialogPPTCDeletionMessage": "คุณกำลังจะจัดคิวการสร้าง PPTC ใหม่ในการบูตครั้งถัดไป:\n\n{0}\n\nคุณแน่ใจหรือไม่ว่าต้องการดำเนินการต่อหรือไม่?", + "DialogPPTCDeletionErrorMessage": "มีข้อผิดพลาดในการล้างแคช PPTC {0}: {1}", + "DialogShaderDeletionMessage": "คุณกำลังจะลบ เชเดอร์แคช:\n\n{0}\n\nคุณแน่ใจหรือไม่ว่าต้องการดำเนินการต่อหรือไม่?", + "DialogShaderDeletionErrorMessage": "เกิดข้อผิดพลาดในการล้าง เชเดอร์แคช {0}: {1}", + "DialogRyujinxErrorMessage": "รียูจินซ์ พบข้อผิดพลาด", + "DialogInvalidTitleIdErrorMessage": "ข้อผิดพลาดของ ยูไอ: เกมที่เลือกไม่มีชื่อ ID ที่ถูกต้อง", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "ไม่พบเฟิร์มแวร์ของระบบที่ถูกต้อง {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "ติดตั้งเฟิร์มแวร์ {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "นี่คื่อเวอร์ชั่นของระบบ {0} ที่ได้รับการติดตั้งเมื่อเร็วๆ นี้", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nสิ่งนี้จะแทนที่เวอร์ชั่นของระบบปัจจุบัน {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nคุณต้องการดำเนินการต่อหรือไม่?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "กำลังติดตั้งเฟิร์มแวร์...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "การติดตั้งเวอร์ชั่นระบบ {0} เรียบร้อยแล้ว", + "DialogUserProfileDeletionWarningMessage": "จะไม่มีโปรไฟล์อื่นให้เปิดหากโปรไฟล์ที่เลือกถูกลบ", + "DialogUserProfileDeletionConfirmMessage": "คุณต้องการลบโปรไฟล์ที่เลือกหรือไม่?", + "DialogUserProfileUnsavedChangesTitle": "คำเตือน - มีการเปลี่ยนแปลงที่ไม่ได้บันทึก", + "DialogUserProfileUnsavedChangesMessage": "คุณได้ทำการเปลี่ยนแปลงโปรไฟล์ผู้ใช้นี้โดยไม่ได้รับการบันทึก", + "DialogUserProfileUnsavedChangesSubMessage": "คุณต้องการยกเลิกการเปลี่ยนแปลงของคุณหรือไม่?", + "DialogControllerSettingsModifiedConfirmMessage": "การตั้งค่าคอนโทรลเลอร์ปัจจุบันได้รับการอัปเดตแล้ว", + "DialogControllerSettingsModifiedConfirmSubMessage": "คุณต้องการบันทึกหรือไม่?", + "DialogLoadFileErrorMessage": "{0} ไฟล์เกิดข้อผิดพลาด: {1}", + "DialogModAlreadyExistsMessage": "มีม็อดอยู่แล้ว", + "DialogModInvalidMessage": "ไดเร็กทอรีที่ระบุไม่มี ม็อดอยู่!", + "DialogModDeleteNoParentMessage": "ไม่สามารถลบ: ไม่พบไดเร็กทอรีหลักสำหรับ ม็อด \"{0}\"!", + "DialogDlcNoDlcErrorMessage": "ไฟล์ที่ระบุไม่มี DLC สำหรับชื่อที่เลือก!", + "DialogPerformanceCheckLoggingEnabledMessage": "คุณได้เปิดใช้งานการบันทึกการติดตาม ซึ่งออกแบบมาเพื่อให้นักพัฒนาใช้เท่านั้น", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "เพื่อประสิทธิภาพสูงสุด ขอแนะนำให้ปิดใช้งานการบันทึกการติดตาม คุณต้องการปิดใช้การบันทึกการติดตามตอนนี้หรือไม่?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "คุณได้เปิดใช้งาน การดัมพ์เชเดอร์ ซึ่งออกแบบมาเพื่อให้นักพัฒนาใช้งานเท่านั้น", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "เพื่อประสิทธิภาพสูงสุด ขอแนะนำให้ปิดใช้การดัมพ์เชเดอร์ คุณต้องการปิดการใช้งานการ ดัมพ์เชเดอร์ ตอนนี้หรือไม่?", + "DialogLoadAppGameAlreadyLoadedMessage": "ทำการโหลดเกมเรียบร้อยแล้ว", + "DialogLoadAppGameAlreadyLoadedSubMessage": "โปรดหยุดการจำลอง หรือปิดโปรแกรมจำลองก่อนที่จะเปิดเกมอื่น", + "DialogUpdateAddUpdateErrorMessage": "ไฟล์ที่ระบุไม่มีการอัพเดตสำหรับชื่อเรื่องที่เลือก!", + "DialogSettingsBackendThreadingWarningTitle": "คำเตือน - การทำเธรดแบ็กเอนด์", + "DialogSettingsBackendThreadingWarningMessage": "รียูจินซ์ ต้องรีสตาร์ทหลังจากเปลี่ยนตัวเลือกนี้จึงจะใช้งานได้อย่างสมบูรณ์ คุณอาจต้องปิดการใช้งาน มัลติเธรด ของไดรเวอร์ของคุณด้วยตนเองเมื่อใช้ รียูจินซ์ ทั้งนี้ขึ้นอยู่กับแพลตฟอร์มของคุณ", + "DialogModManagerDeletionWarningMessage": "คุณกำลังจะลบ ม็อด: {0}\n\nคุณแน่ใจหรือไม่ว่าต้องการดำเนินการต่อ?", + "DialogModManagerDeletionAllWarningMessage": "คุณกำลังจะลบม็อดทั้งหมดสำหรับชื่อนี้\n\nคุณแน่ใจหรือไม่ว่าต้องการดำเนินการต่อ?", + "SettingsTabGraphicsFeaturesOptions": "คุณสมบัติ", + "SettingsTabGraphicsBackendMultithreading": "มัลติเธรด แบ็กเอนด์กราฟิก:", + "CommonAuto": "อัตโนมัติ", + "CommonOff": "ปิดการใช้งาน", + "CommonOn": "เปิดใช้งาน", + "InputDialogYes": "ใช่", + "InputDialogNo": "ไม่ใช่", + "DialogProfileInvalidProfileNameErrorMessage": "ชื่อไฟล์ประกอบด้วยอักขระที่ไม่ถูกต้อง กรุณาลองอีกครั้ง", + "MenuBarOptionsPauseEmulation": "หยุดชั่วคราว", + "MenuBarOptionsResumeEmulation": "ดำเนินการต่อ", + "AboutUrlTooltipMessage": "คลิกเพื่อเปิดเว็บไซต์ รียูจินซ์ บนเบราว์เซอร์เริ่มต้นของคุณ", + "AboutDisclaimerMessage": "ทางผู้พัฒนาโปรแกรม รียูจินซ์ ไม่มีส่วนเกี่ยวข้องกับทางบริษัท Nintendo™\nหรือพันธมิตรใดๆ ทั้งสิ้น!", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) ถูกใช้\nในการจำลอง อะมิโบ ของเรา", + "AboutPatreonUrlTooltipMessage": "คลิกเพื่อเปิดหน้า เพทรีออน ของ รียูจินซ์ บนเบราว์เซอร์เริ่มต้นของคุณ", + "AboutGithubUrlTooltipMessage": "คลิกเพื่อเปิดหน้า กิตฮับ ของ ริวจินซ์ บนเบราว์เซอร์เริ่มต้นของคุณ", + "AboutDiscordUrlTooltipMessage": "คลิกเพื่อเปิดคำเชิญเข้าสู่เซิร์ฟเวอร์ ดิสคอร์ด ของ รียูจินซ์ บนเบราว์เซอร์เริ่มต้นของคุณ", + "AboutTwitterUrlTooltipMessage": "คลิกเพื่อเปิดหน้าเพจ ทวิตเตอร์ ของ รียูจินซ์ บนเบราว์เซอร์เริ่มต้นของคุณ", + "AboutRyujinxAboutTitle": "เกี่ยวกับ:", + "AboutRyujinxAboutContent": "รียูจินซ์ เป็นอีมูเลเตอร์สำหรับ Nintendo Switch™\nโปรดสนับสนุนเราบน เพทรีออน\nรับข่าวสารล่าสุดทั้งหมดบน ทวิตเตอร์ หรือ ดิสคอร์ด ของเรา\nนักพัฒนาที่สนใจจะมีส่วนร่วมสามารถดูข้อมูลเพิ่มเติมได้ที่ กิตฮับ หรือ ดิสคอร์ด ของเรา", + "AboutRyujinxMaintainersTitle": "ได้รับการดูแลรักษาโดย:", + "AboutRyujinxMaintainersContentTooltipMessage": "คลิกเพื่อเปิดหน้าผู้ร่วมให้ข้อมูลในเบราว์เซอร์เริ่มต้นของคุณ", + "AboutRyujinxSupprtersTitle": "สนับสนุนบน เพทรีออน โดย:", + "AmiiboSeriesLabel": "อะมิโบซีรีส์", + "AmiiboCharacterLabel": "ตัวละคร", + "AmiiboScanButtonLabel": "สแกนเลย", + "AmiiboOptionsShowAllLabel": "แสดง อะมิโบ ทั้งหมด", + "AmiiboOptionsUsRandomTagLabel": "แฮ็ค: ใช้แท็กสุ่ม Uuid", + "DlcManagerTableHeadingEnabledLabel": "เปิดใช้งานแล้ว", + "DlcManagerTableHeadingTitleIdLabel": "ชื่อไอดี", + "DlcManagerTableHeadingContainerPathLabel": "ที่เก็บไฟล์ คอนเทนเนอร์", + "DlcManagerTableHeadingFullPathLabel": "ที่เก็บไฟล์แบบเต็ม", + "DlcManagerRemoveAllButton": "ลบทั้งหมด", + "DlcManagerEnableAllButton": "เปิดใช้งานทั้งหมด", + "DlcManagerDisableAllButton": "ปิดใช้งานทั้งหมด", + "ModManagerDeleteAllButton": "ลบทั้งหมด", + "MenuBarOptionsChangeLanguage": "เปลี่ยนภาษา", + "MenuBarShowFileTypes": "แสดงประเภทของไฟล์", + "CommonSort": "เรียงลำดับ", + "CommonShowNames": "แสดงชื่อ", + "CommonFavorite": "สิ่งที่ชื่นชอบ", + "OrderAscending": "จากน้อยไปมาก", + "OrderDescending": "จากมากไปน้อย", + "SettingsTabGraphicsFeatures": "คุณสมบัติ และ การเพิ่มประสิทธิภาพ", + "ErrorWindowTitle": "หน้าต่างแสดงข้อผิดพลาด", + "ToggleDiscordTooltip": "เลือกว่าจะแสดง รียูจินซ์ ในกิจกรรม ดิสคอร์ด \"ที่กำลังเล่นอยู่\" ของคุณหรือไม่?", + "AddGameDirBoxTooltip": "ป้อนไดเรกทอรี่เกมที่จะทำการเพิ่มลงในรายการ", + "AddGameDirTooltip": "เพิ่มไดเรกทอรี่เกมลงในรายการ", + "RemoveGameDirTooltip": "ลบไดเรกทอรี่เกมที่เลือก", + "CustomThemeCheckTooltip": "ใช้ธีม Avalonia แบบกำหนดเองสำหรับ GUI เพื่อเปลี่ยนรูปลักษณ์ของเมนูโปรแกรมจำลอง", + "CustomThemePathTooltip": "ไปยังที่เก็บไฟล์ธีม GUI แบบกำหนดเอง", + "CustomThemeBrowseTooltip": "เรียกดูธีม GUI ที่กำหนดเอง", + "DockModeToggleTooltip": "ด็อกโหมดทำให้ระบบจำลองการทำงานเสมือน Nintendo ที่กำลังเชื่อมต่ออยู่ด็อก สิ่งนี้จะปรับปรุงความเสถียรภาพของกราฟิกในเกมส่วนใหญ่ ในทางกลับกัน การปิดใช้จะทำให้ระบบจำลองทำงานเหมือนกับ Nintendo Switch แบบพกพา ส่งผลให้คุณภาพกราฟิกลดลง\n\nกำหนดค่าส่วนควบคุมของผู้เล่น 1 หากวางแผนที่จะใช้ด็อกโหมด กำหนดค่าการควบคุมแบบ แฮนด์เฮลด์ หากวางแผนที่จะใช้โหมดแฮนด์เฮลด์\n\nเปิดทิ้งไว้หากคุณไม่แน่ใจ", + "DirectKeyboardTooltip": "รองรับการเข้าถึงแป้นพิมพ์โดยตรง (HID) ให้เกมเข้าถึงคีย์บอร์ดของคุณเป็นอุปกรณ์ป้อนข้อความ\n\nใช้งานได้กับเกมที่รองรับการใช้งานคีย์บอร์ดบนฮาร์ดแวร์ของ Switch เท่านั้น\n\nหากคุณไม่แน่ใจปล่อยให้ปิดอย่างนั้น", + "DirectMouseTooltip": "รองรับการเข้าถึงเมาส์โดยตรง (HID) ให้เกมเข้าถึงเมาส์ของคุณเป็นอุปกรณ์ชี้ตำแหน่ง\n\nใช้งานได้เฉพาะกับเกมที่รองรับการควบคุมเมาส์บนฮาร์ดแวร์ของ Switch เท่านั้น ซึ่งมีอยู่ไม่มากนัก\n\nเมื่อเปิดใช้งาน ฟังก์ชั่นหน้าจอสัมผัสอาจไม่ทำงาน\n\nหากคุณไม่แน่ใจปล่อยให้ปิดอย่างนั้น", + "RegionTooltip": "เปลี่ยนภูมิภาคของระบบ", + "LanguageTooltip": "เปลี่ยนภาษาของระบบ", + "TimezoneTooltip": "เปลี่ยนโซนเวลาของระบบ", + "TimeTooltip": "เปลี่ยนเวลาของระบบ", + "VSyncToggleTooltip": "Vertical Sync ของคอนโซลจำลอง โดยพื้นฐานแล้วเป็นตัวจำกัดเฟรมสำหรับเกมส่วนใหญ่ การปิดใช้งานอาจทำให้เกมทำงานด้วยความเร็วสูงขึ้น หรือทำให้หน้าจอการโหลดใช้เวลานานขึ้นหรือค้าง\n\nสามารถสลับได้ในเกมด้วยปุ่มลัดตามที่คุณต้องการ (F1 เป็นค่าเริ่มต้น) เราขอแนะนำให้ทำเช่นนี้หากคุณวางแผนที่จะปิดการใช้งาน\n\nหากคุณไม่แน่ใจให้ปล่อยไว้อย่างนั้น", + "PptcToggleTooltip": "บันทึกฟังก์ชั่น JIT ที่แปลแล้ว ดังนั้นจึงไม่จำเป็นต้องแปลทุกครั้งที่โหลดเกม\n\nลดอาการกระตุกและเร่งความเร็วการบูตได้อย่างมากหลังจากการบูตครั้งแรกของเกม\n\nปล่อยไว้หากคุณไม่แน่ใจ", + "FsIntegrityToggleTooltip": "ตรวจสอบไฟล์ที่เสียหายเมื่อบูตเกม และหากตรวจพบไฟล์ที่เสียหาย จะแสดงข้อผิดพลาดของแฮชในบันทึก\n\nไม่มีผลกระทบต่อประสิทธิภาพการทำงานและมีไว้เพื่อช่วยในการแก้ไขปัญหา\n\nปล่อยไว้หากคุณไม่แน่ใจ", + "AudioBackendTooltip": "เปลี่ยนแบ็กเอนด์ที่ใช้ในการเรนเดอร์เสียง\n\nSDL2 เป็นที่ต้องการ ในขณะที่ OpenAL และ SoundIO ถูกใช้เป็นทางเลือกสำรอง ดัมมี่จะไม่มีเสียง\n\nปล่อยไว้หากคุณไม่แน่ใจ", + "MemoryManagerTooltip": "เปลี่ยนวิธีการแมปและเข้าถึงหน่วยความจำของผู้เยี่ยมชม ส่งผลอย่างมากต่อประสิทธิภาพการทำงานของ CPU ที่จำลอง\n\nตั้งค่าเป็น ไม่ทำการตรวจสอบ โฮสต์ หากคุณไม่แน่ใจ", + "MemoryManagerSoftwareTooltip": "ใช้ตารางหน้าซอฟต์แวร์สำหรับการแปลที่อยู่ ความแม่นยำสูงสุดแต่ประสิทธิภาพช้าที่สุด", + "MemoryManagerHostTooltip": "แมปหน่วยความจำในพื้นที่ที่อยู่โฮสต์โดยตรง การคอมไพล์และดำเนินการ JIT เร็วขึ้นมาก", + "MemoryManagerUnsafeTooltip": "แมปหน่วยความจำโดยตรง แต่อย่าปิดบังที่อยู่ภายในพื้นที่ที่อยู่ของผู้เยี่ยมชมก่อนที่จะเข้าถึง เร็วกว่า แต่ต้องแลกกับความปลอดภัย แอปพลิเคชั่นผู้เยี่ยมชมสามารถเข้าถึงหน่วยความจำได้จากทุกที่ใน รียูจินซ์ ดังนั้นให้รันเฉพาะโปรแกรมที่คุณเชื่อถือในโหมดนี้", + "UseHypervisorTooltip": "ใช้ Hypervisor แทน JIT ปรับปรุงประสิทธิภาพอย่างมากเมื่อพร้อมใช้งาน แต่อาจไม่เสถียรในสถานะปัจจุบัน", + "DRamTooltip": "ใช้เค้าโครง MemoryMode ทางเลือกเพื่อเลียนแบบโมเดลการพัฒนาสวิตช์\n\nสิ่งนี้มีประโยชน์สำหรับแพ็กพื้นผิวที่มีความละเอียดสูงกว่าหรือม็อดที่มีความละเอียด 4k เท่านั้น ไม่ปรับปรุงประสิทธิภาพ\n\nปล่อยให้ปิดหากคุณไม่แน่ใจ", + "IgnoreMissingServicesTooltip": "ละเว้นบริการ Horizon OS ที่ยังไม่ได้ใช้งาน วิธีนี้อาจช่วยในการหลีกเลี่ยงข้อผิดพลาดเมื่อบู๊ตเกมบางเกม\n\nปล่อยให้ปิดหากคุณไม่แน่ใจ", + "GraphicsBackendThreadingTooltip": "ดำเนินการคำสั่งแบ็กเอนด์กราฟิกบนเธรดที่สอง\n\nเร่งความเร็วการคอมไพล์เชเดอร์ ลดการกระตุก และปรับปรุงประสิทธิภาพการทำงานของไดรเวอร์ GPU โดยไม่ต้องรองรับมัลติเธรดในตัว ประสิทธิภาพที่ดีขึ้นเล็กน้อยสำหรับไดรเวอร์ที่มีมัลติเธรด\n\nตั้งเป็น อัตโนมัติ หากคุณไม่แน่ใจ", + "GalThreadingTooltip": "ดำเนินการคำสั่งแบ็กเอนด์กราฟิกบนเธรดที่สอง\n\nเร่งความเร็วการคอมไพล์เชเดอร์ ลดการกระตุก และปรับปรุงประสิทธิภาพการทำงานของไดรเวอร์ GPU โดยไม่ต้องรองรับมัลติเธรดในตัว ประสิทธิภาพที่ดีขึ้นเล็กน้อยสำหรับไดรเวอร์ที่มีมัลติเธรด\n\nตั้งเป็น อัตโนมัติ หากคุณไม่แน่ใจ", + "ShaderCacheToggleTooltip": "บันทึกแคชเชเดอร์ของดิสก์ซึ่งช่วยลดการกระตุกในการรันครั้งต่อๆ ไป\n\nปล่อยไว้หากคุณไม่แน่ใจ", + "ResolutionScaleTooltip": "คูณความละเอียดการเรนเดอร์ของเกม\n\nเกมบางเกมอาจไม่สามารถใช้งานได้และดูเป็นพิกเซลแม้ว่าความละเอียดจะเพิ่มขึ้นก็ตาม สำหรับเกมเหล่านั้น คุณอาจต้องค้นหาม็อดที่ลบรอยหยักของภาพหรือเพิ่มความละเอียดในการเรนเดอร์ภายใน หากต้องการใช้อย่างหลัง คุณอาจต้องเลือก Native\n\nตัวเลือกนี้สามารถเปลี่ยนแปลงได้ในขณะที่เกมกำลังทำงานอยู่โดยคลิก \"นำมาใช้\" ด้านล่าง คุณสามารถย้ายหน้าต่างการตั้งค่าไปด้านข้างและทดลองจนกว่าคุณจะพบรูปลักษณ์ที่คุณต้องการสำหรับเกม\n\nโปรดทราบว่า 4x นั้นเกินความจำเป็นสำหรับการตั้งค่าแทบทุกประเภท", + "ResolutionScaleEntryTooltip": "สเกลความละเอียดจุดทศนิยม เช่น 1.5 ไม่ใช่จำนวนเต็มของสเกล มีแนวโน้มที่จะก่อให้เกิดปัญหาหรือความผิดพลาดได้", + "AnisotropyTooltip": "ระดับของการกรองแบบ Anisotropic ตั้งค่าเป็นอัตโนมัติเพื่อใช้ค่าที่เกมร้องขอ", + "AspectRatioTooltip": "อัตราส่วนภาพที่ใช้กับหน้าต่างตัวแสดงภาพ\n\nเปลี่ยนสิ่งนี้หากคุณใช้ตัวดัดแปลงอัตราส่วนกว้างยาวสำหรับเกมของคุณ ไม่เช่นนั้นกราฟิกจะถูกยืดออก\n\nทิ้งไว้ที่ 16:9 หากไม่แน่ใจ", + "ShaderDumpPathTooltip": "ที่เก็บ ดัมพ์ไฟล์ของ เชเดอร์กราฟิก", + "FileLogTooltip": "บันทึกการบันทึกคอนโซลลงในไฟล์บันทึกบนดิสก์ จะไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "StubLogTooltip": "พิมพ์ข้อความบันทึกต้นขั้วในคอนโซล จะไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "InfoLogTooltip": "พิมพ์ข้อความบันทึกข้อมูลในคอนโซล จะไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "WarnLogTooltip": "พิมพ์ข้อความบันทึกแจ้งตือนในคอนโซล จะไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "ErrorLogTooltip": "พิมพ์ข้อความบันทึกข้อผิดพลาดในคอนโซล จะไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "TraceLogTooltip": "พิมพ์ข้อความบันทึกการติดตามในคอนโซล ไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "GuestLogTooltip": "พิมพ์ข้อความบันทึกของผู้เยี่ยมชมในคอนโซล ไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "FileAccessLogTooltip": "พิมพ์ข้อความบันทึกการเข้าถึงไฟล์ในคอนโซล", + "FSAccessLogModeTooltip": "เปิดใช้งานเอาต์พุตบันทึกการเข้าถึง FS ไปยังคอนโซล โหมดที่เป็นไปได้คือ 0-3", + "DeveloperOptionTooltip": "โปรดใช้ด้วยความระมัดระวัง", + "OpenGlLogLevel": "จำเป็นต้องเปิดใช้งานระดับบันทึกที่เหมาะสม", + "DebugLogTooltip": "พิมพ์ข้อความบันทึกการแก้ไขข้อบกพร่องในคอนโซล\n\nใช้สิ่งนี้เฉพาะเมื่อได้รับคำแนะนำจากเจ้าหน้าที่โดยเฉพาะเท่านั้น เนื่องจากจะทำให้บันทึกอ่านยากและทำให้ประสิทธิภาพของโปรแกรมจำลองแย่ลง", + "LoadApplicationFileTooltip": "เปิด File Explorer เพื่อเลือกไฟล์ที่เข้ากันได้กับ Switch ที่จะโหลด", + "LoadApplicationFolderTooltip": "เปิดตัวสำรวจไฟล์เพื่อเลือกไฟล์ที่เข้ากันได้กับ Switch ที่จะโหลด", + "OpenRyujinxFolderTooltip": "เปิดโฟลเดอร์ระบบไฟล์ รียูจินซ์", + "OpenRyujinxLogsTooltip": "เปิดโฟลเดอร์ที่มีการเขียนบันทึก", + "ExitTooltip": "ออกจากโปรแกรม รียูจินซ์", + "OpenSettingsTooltip": "เปิดหน้าต่างการตั้งค่า", + "OpenProfileManagerTooltip": "เปิดหน้าต่างตัวจัดการโปรไฟล์ผู้ใช้", + "StopEmulationTooltip": "หยุดการจำลองเกมปัจจุบันและกลับไปยังการเลือกเกม", + "CheckUpdatesTooltip": "ตรวจสอบการอัปเดตของ รียูจินซ์", + "OpenAboutTooltip": "เปิดหน้าต่าง เกี่ยวกับ", + "GridSize": "ขนาดตาราง", + "GridSizeTooltip": "เปลี่ยนขนาด ของตาราง", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "บราซิล โปรตุเกส", + "AboutRyujinxContributorsButtonHeader": "ดูผู้มีส่วนร่วมทั้งหมด", + "SettingsTabSystemAudioVolume": "ระดับเสียง: ", + "AudioVolumeTooltip": "ปรับระดับเสียง", + "SettingsTabSystemEnableInternetAccess": "การเข้าถึงอินเทอร์เน็ตของผู้เยี่ยมชม/โหมด LAN", + "EnableInternetAccessTooltip": "อนุญาตให้แอปพลิเคชันจำลองเชื่อมต่ออินเทอร์เน็ต\n\nเกมที่มีโหมด LAN สามารถเชื่อมต่อระหว่างกันได้เมื่อเปิดใช้งานและระบบเชื่อมต่อกับจุดเชื่อมต่อเดียวกัน รวมถึงคอนโซลจริงด้วย\n\nไม่อนุญาตให้มีการเชื่อมต่อกับเซิร์ฟเวอร์ Nintendo อาจทำให้เกิดการหยุดทำงานในบางเกมที่พยายามเชื่อมต่ออินเทอร์เน็ต\n\nปล่อยให้ปิดหากคุณไม่แน่ใจ", + "GameListContextMenuManageCheatToolTip": "ฟังชั่นจัดการสูตรโกง", + "GameListContextMenuManageCheat": "จัดการสูตรโกง", + "GameListContextMenuManageModToolTip": "จัดการ ม็อด", + "GameListContextMenuManageMod": "จัดการ ม็อด", + "ControllerSettingsStickRange": "ขอบเขต:", + "DialogStopEmulationTitle": "รียูจินซ์ - หยุดการจำลอง", + "DialogStopEmulationMessage": "คุณแน่ใจหรือไม่ว่าต้องการหยุดการจำลองหรือไม่?", + "SettingsTabCpu": "หน่วยประมวลผลกลาง", + "SettingsTabAudio": "เสียง", + "SettingsTabNetwork": "เครือข่าย", + "SettingsTabNetworkConnection": "การเชื่อมต่อเครือข่าย", + "SettingsTabCpuCache": "ซีพียู แคช", + "SettingsTabCpuMemory": "ซีพียูเมมโมรี่ แคช", + "DialogUpdaterFlatpakNotSupportedMessage": "โปรดอัปเดต รียูจินซ์ ผ่านช่องทาง FlatHub", + "UpdaterDisabledWarningTitle": "ปิดใช้งานการอัปเดตแล้ว!", + "ControllerSettingsRotate90": "หมุน 90 องศา ตามเข็มนาฬิกา", + "IconSize": "ขนาดไอคอน", + "IconSizeTooltip": "เปลี่ยนขนาดของไอคอนเกม", + "MenuBarOptionsShowConsole": "แสดง คอนโซล", + "ShaderCachePurgeError": "เกิดข้อผิดพลาดในการล้างแคชเชเดอร์ {0}: {1}", + "UserErrorNoKeys": "ไม่พบคีย์", + "UserErrorNoFirmware": "ไม่พบเฟิร์มแวร์", + "UserErrorFirmwareParsingFailed": "เกิดข้อผิดพลาดในการแยกวิเคราะห์เฟิร์มแวร์", + "UserErrorApplicationNotFound": "ไม่พบแอปพลิเคชัน", + "UserErrorUnknown": "ข้อผิดพลาดที่ไม่รู้จัก", + "UserErrorUndefined": "ข้อผิดพลาดที่ไม่ได้ระบุ", + "UserErrorNoKeysDescription": "รียูจินซ์ ไม่พบไฟล์ 'prod.keys' ของคุณ", + "UserErrorNoFirmwareDescription": "รียูจินซ์ ไม่พบเฟิร์มแวร์ที่ติดตั้งไว้", + "UserErrorFirmwareParsingFailedDescription": "รียูจินซ์ ไม่สามารถแยกวิเคราะห์เฟิร์มแวร์ที่ให้มาได้ ซึ่งมักมีสาเหตุมาจากคีย์ที่ล้าสมัย", + "UserErrorApplicationNotFoundDescription": "รียูจินซ์ ไม่พบแอปพลิเคชันที่ถูกต้องในที่เก็บไฟล์ที่กำหนด", + "UserErrorUnknownDescription": "เกิดข้อผิดพลาดที่ไม่รู้จักเกิดขึ้น!", + "UserErrorUndefinedDescription": "เกิดข้อผิดพลาดที่ไม่สามารถระบุได้! สิ่งนี้ไม่ควรเกิดขึ้น โปรดติดต่อผู้พัฒนา!", + "OpenSetupGuideMessage": "เปิดคู่มือการตั้งค่า", + "NoUpdate": "ไม่มีการอัพเดต", + "TitleUpdateVersionLabel": "เวอร์ชั่น {0}", + "RyujinxInfo": "รียูจินซ์ – ข้อมูล", + "RyujinxConfirm": "รียูจินซ์ - ยืนยัน", + "FileDialogAllTypes": "ทุกประเภท", + "Never": "ไม่มี", + "SwkbdMinCharacters": "ต้องมีความยาวของตัวอักษรอย่างน้อย {0} ตัว", + "SwkbdMinRangeCharacters": "ต้องมีความยาวของตัวอักษร {0}-{1} ตัว", + "SoftwareKeyboard": "ซอฟต์แวร์ ของคีย์บอร์ด", + "SoftwareKeyboardModeNumeric": "ต้องเป็น 0-9 หรือ '.' เท่านั้น", + "SoftwareKeyboardModeAlphabet": "ต้องเป็นตัวอักษรที่ไม่ใช่ CJK เท่านั้น", + "SoftwareKeyboardModeASCII": "ต้องเป็นตัวอักษร ASCII เท่านั้น", + "ControllerAppletControllers": "คอนโทรลเลอร์ที่รองรับ:", + "ControllerAppletPlayers": "ผู้เล่น:", + "ControllerAppletDescription": "การกำหนดค่าปัจจุบันของคุณไม่ถูกต้อง เปิดการตั้งค่าและกำหนดค่าอินพุตของคุณใหม่", + "ControllerAppletDocked": "ตั้งค่าด็อกโหมด ควรปิดใช้งานการควบคุมแบบแฮนด์เฮลด์", + "UpdaterRenaming": "กำลังเปลี่ยนชื่อไฟล์เก่า...", + "UpdaterRenameFailed": "โปรแกรมอัปเดตไม่สามารถเปลี่ยนชื่อไฟล์ได้: {0}", + "UpdaterAddingFiles": "กำลังเพิ่มไฟล์ใหม่...", + "UpdaterExtracting": "กำลังแยกการอัปเดต...", + "UpdaterDownloading": "กำลังดาวน์โหลดอัปเดต...", + "Game": "เกมส์", + "Docked": "ด็อก", + "Handheld": "แฮนด์เฮลด์", + "ConnectionError": "การเชื่อมต่อล้มเหลว", + "AboutPageDeveloperListMore": "{0} และอื่น ๆ...", + "ApiError": "ข้อผิดพลาดของ API", + "LoadingHeading": "กำลังโหลด {0}", + "CompilingPPTC": "กำลังคอมไพล์ PTC", + "CompilingShaders": "กำลังคอมไพล์ เชเดอร์", + "AllKeyboards": "คีย์บอร์ดทั้งหมด", + "OpenFileDialogTitle": "เลือกไฟล์ที่สนับสนุนเพื่อเปิด", + "OpenFolderDialogTitle": "เลือกโฟลเดอร์ที่มีเกมที่แตกไฟล์แล้ว", + "AllSupportedFormats": "รูปแบบที่รองรับทั้งหมด", + "RyujinxUpdater": "อัพเดต รียูจินซ์", + "SettingsTabHotkeys": "ปุ่มลัดของคีย์บอร์ด", + "SettingsTabHotkeysHotkeys": "ปุ่มลัดของคีย์บอร์ด", + "SettingsTabHotkeysToggleVsyncHotkey": "สลับเป็น VSync:", + "SettingsTabHotkeysScreenshotHotkey": "ภาพหน้าจอ:", + "SettingsTabHotkeysShowUiHotkey": "แสดง UI:", + "SettingsTabHotkeysPauseHotkey": "หยุดชั่วคราว:", + "SettingsTabHotkeysToggleMuteHotkey": "ปิดเสียง:", + "ControllerMotionTitle": "การตั้งค่าการควบคุมการเคลื่อนไหว", + "ControllerRumbleTitle": "ตั้งค่าการสั่นไหว", + "SettingsSelectThemeFileDialogTitle": "เลือกไฟล์ธีม", + "SettingsXamlThemeFile": "ไฟล์ธีมรูปแบบ XAML", + "AvatarWindowTitle": "จัดการบัญชี - อวาต้า", + "Amiibo": "อะมิโบ", + "Unknown": "ไม่รู้จัก", + "Usage": "การใช้งาน", + "Writable": "สามารถเขียนได้", + "SelectDlcDialogTitle": "เลือกไฟล์ DLC", + "SelectUpdateDialogTitle": "เลือกไฟล์อัพเดต", + "SelectModDialogTitle": "เลือกไดเรกทอรี ม็อด", + "UserProfileWindowTitle": "จัดการโปรไฟล์ผู้ใช้", + "CheatWindowTitle": "จัดการสูตรโกง", + "DlcWindowTitle": "จัดการเนื้อหาที่ดาวน์โหลดได้สำหรับ {0} ({1})", + "UpdateWindowTitle": "จัดการการอัพเดตชื่อเรื่อง", + "CheatWindowHeading": "สูตรโกงมีให้สำหรับ {0} [{1}]", + "BuildId": "รหัสบิวด์:", + "DlcWindowHeading": "{0} เนื้อหาที่สามารถดาวน์โหลดได้", + "ModWindowHeading": "{0} ม็อด", + "UserProfilesEditProfile": "แก้ไขที่เลือกแล้ว", + "Cancel": "ยกเลิก", + "Save": "บันทึก", + "Discard": "ละทิ้ง", + "Paused": "หยุดชั่วคราว", + "UserProfilesSetProfileImage": "ตั้งค่ารูปโปรไฟล์", + "UserProfileEmptyNameError": "จำเป็นต้องมีการระบุชื่อ", + "UserProfileNoImageError": "จำเป็นต้องตั้งค่ารูปโปรไฟล์", + "GameUpdateWindowHeading": "จัดการอัพเดตสำหรับ {0} ({1})", + "SettingsTabHotkeysResScaleUpHotkey": "เพิ่มความละเอียด:", + "SettingsTabHotkeysResScaleDownHotkey": "ลดความละเอียด:", + "UserProfilesName": "ชื่อ:", + "UserProfilesUserId": "รหัสผู้ใช้:", + "SettingsTabGraphicsBackend": "แบ็กเอนด์กราฟิก", + "SettingsTabGraphicsBackendTooltip": "เลือกแบ็กเอนด์กราฟิกที่จะใช้ในโปรแกรมจำลอง\n\nโดยรวมแล้ว Vulkan นั้นดีกว่าสำหรับกราฟิกการ์ดรุ่นใหม่ทั้งหมด ตราบใดที่ไดรเวอร์ยังอัพเดทอยู่เสมอ Vulkan ยังมีคุณสมบัติการคอมไพล์เชเดอร์ที่เร็วขึ้น (ลดอาการกระตุก) ของผู้จำหน่าย GPU ทุกรายอยู่แล้ว\n\nOpenGL อาจได้รับผลลัพธ์ที่ดีกว่าบน Nvidia GPU รุ่นเก่า, AMD GPU รุ่นเก่าบน Linux หรือบน GPU ที่มี VRAM ต่ำกว่า แม้ว่าการคอมไพล์เชเดอร์จะสะดุดมากขึ้นก็ตาม\n\nตั้งค่าเป็น Vulkan หากไม่แน่ใจ ตั้งค่าเป็น OpenGL หาก GPU ของคุณไม่รองรับ Vulkan แม้จะมีไดรเวอร์กราฟิกล่าสุดก็ตาม", + "SettingsEnableTextureRecompression": "เปิดใช้งานการบีบอัดพื้นผิวอีกครั้ง", + "SettingsEnableTextureRecompressionTooltip": "บีบอัดพื้นผิว ASTC เพื่อลดการใช้งาน VRAM\n\nเกมที่ใช้รูปแบบพื้นผิวนี้ ได้แก่ Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder และ The Legend of Zelda: Tears of the Kingdom\n\nกราฟิกการ์ดที่มี 4 กิกะไบต์ VRAM หรือน้อยกว่ามีแนวโน้มที่จะให้แคชในบางจุดขณะเล่นเกมเหล่านี้\n\nเปิดใช้งานเฉพาะในกรณีที่ VRAM ของคุณใกล้หมดในเกมที่กล่าวมาข้างต้น ปล่อยให้ปิดหากไม่แน่ใจ", + "SettingsTabGraphicsPreferredGpu": "GPU ที่ต้องการ", + "SettingsTabGraphicsPreferredGpuTooltip": "เลือกกราฟิกการ์ดที่จะใช้กับแบ็กเอนด์กราฟิก Vulkan\n\nไม่ส่งผลต่อ GPU ที่ OpenGL จะใช้\n\nตั้งค่าเป็น GPU ที่ถูกตั้งค่าสถานะเป็น \"dGPU\" หากคุณไม่แน่ใจ หากไม่มีก็ปล่อยทิ้งไว้โดยไม่มีใครแตะต้องมัน", + "SettingsAppRequiredRestartMessage": "จำเป็นต้องรีสตาร์ท รียูจินซ์", + "SettingsGpuBackendRestartMessage": "การตั้งค่ากราฟิกแบ็กเอนด์หรือ GPU ได้รับการแก้ไขแล้ว สิ่งนี้จะต้องมีการรีสตาร์ทจึงจะสามารถใช้งานได้", + "SettingsGpuBackendRestartSubMessage": "คุณต้องการรีสตาร์ทตอนนี้หรือไม่?", + "RyujinxUpdaterMessage": "คุณต้องการอัพเดต รียูจินซ์ เป็นเวอร์ชั่นล่าสุดหรือไม่?", + "SettingsTabHotkeysVolumeUpHotkey": "เพิ่มระดับเสียง:", + "SettingsTabHotkeysVolumeDownHotkey": "ลดระดับเสียง:", + "SettingsEnableMacroHLE": "เปิดใช้งาน มาโคร HLE", + "SettingsEnableMacroHLETooltip": "การจำลองระดับสูงของโค้ดมาโคร GPU\n\nปรับปรุงประสิทธิภาพ แต่อาจทำให้เกิดข้อผิดพลาดด้านกราฟิกในบางเกม\n\nปล่อยไว้หากคุณไม่แน่ใจ", + "SettingsEnableColorSpacePassthrough": "ทะลุผ่านพื้นที่สี", + "SettingsEnableColorSpacePassthroughTooltip": "สั่งให้แบ็กเอนด์ Vulkan ส่งผ่านข้อมูลสีโดยไม่ต้องระบุค่าของสี สำหรับผู้ใช้ที่มีการแสดงกระจายตัวของสี อาจส่งผลให้สีสดใสมากขึ้น โดยต้องแลกกับความถูกต้องของสี", + "VolumeShort": "ระดับเสียง", + "UserProfilesManageSaves": "จัดการบันทึก", + "DeleteUserSave": "คุณต้องการลบบันทึกผู้ใช้สำหรับเกมนี้หรือไม่?", + "IrreversibleActionNote": "การดำเนินการนี้ไม่สามารถย้อนกลับได้", + "SaveManagerHeading": "จัดการบันทึกสำหรับ {0} ({1})", + "SaveManagerTitle": "จัดการบันทึก", + "Name": "ชื่อ", + "Size": "ขนาด", + "Search": "ค้นหา", + "UserProfilesRecoverLostAccounts": "กู้คืนบัญชีที่สูญหาย", + "Recover": "กู้คืน", + "UserProfilesRecoverHeading": "พบบันทึกสำหรับบัญชีดังต่อไปนี้", + "UserProfilesRecoverEmptyList": "ไม่มีโปรไฟล์ที่สามารถกู้คืนได้", + "GraphicsAATooltip": "ใช้การลดรอยหยักกับการเรนเดอร์เกม\n\nFXAA จะเบลอภาพส่วนใหญ่ ในขณะที่ SMAA จะพยายามค้นหาขอบหยักและปรับให้เรียบ\n\nไม่แนะนำให้ใช้ร่วมกับตัวกรองสเกล FSR\n\nตัวเลือกนี้สามารถเปลี่ยนแปลงได้ในขณะที่เกมกำลังทำงานอยู่โดยคลิก \"นำไปใช้\" ด้านล่าง คุณสามารถย้ายหน้าต่างการตั้งค่าไปด้านข้างและทดลองจนกว่าคุณจะพบรูปลักษณ์ที่คุณต้องการสำหรับเกม\n\nปล่อยไว้ที่ NONE หากไม่แน่ใจ", + "GraphicsAALabel": "ลดการฉีกขาดของภาพ:", + "GraphicsScalingFilterLabel": "ปรับขนาดตัวกรอง:", + "GraphicsScalingFilterTooltip": "เลือกตัวกรองสเกลที่จะใช้เมื่อใช้สเกลความละเอียด\n\nBilinear ทำงานได้ดีกับเกม 3D และเป็นตัวเลือกเริ่มต้นที่ปลอดภัย\n\nแนะนำให้ใช้เกมภาพพิกเซลที่ใกล้เคียงที่สุด\n\nFSR 1.0 เป็นเพียงตัวกรองความคมชัด ไม่แนะนำให้ใช้กับ FXAA หรือ SMAA\n\nตัวเลือกนี้สามารถเปลี่ยนแปลงได้ในขณะที่เกมกำลังทำงานอยู่โดยคลิก \"นำไปใช้\" ด้านล่าง คุณสามารถย้ายหน้าต่างการตั้งค่าไปด้านข้างและทดลองจนกว่าคุณจะพบรูปลักษณ์ที่คุณต้องการสำหรับเกม", + "GraphicsScalingFilterLevelLabel": "ระดับ", + "GraphicsScalingFilterLevelTooltip": "ตั้งค่าระดับความคมชัด FSR 1.0 สูงกว่าจะคมชัดกว่า", + "SmaaLow": "SMAA ต่ำ", + "SmaaMedium": "SMAA ปานกลาง", + "SmaaHigh": "SMAA สูง", + "SmaaUltra": "SMAA สูงมาก", + "UserEditorTitle": "แก้ไขผู้ใช้", + "UserEditorTitleCreate": "สร้างผู้ใช้", + "SettingsTabNetworkInterface": "เชื่อมต่อเครือข่าย:", + "NetworkInterfaceTooltip": "อินเทอร์เฟซเครือข่ายที่ใช้สำหรับคุณสมบัติ LAN/LDN\n\nเมื่อใช้ร่วมกับ VPN หรือ XLink Kai และเกมที่รองรับ LAN สามารถใช้เพื่อปลอมการเชื่อมต่อเครือข่ายเดียวกันผ่านทางอินเทอร์เน็ต\n\nปล่อยให้เป็น ค่าเริ่มต้น หากคุณไม่แน่ใจ", + "NetworkInterfaceDefault": "ค่าเริ่มต้น", + "PackagingShaders": "แพ็คเชเดอร์ไฟล์", + "AboutChangelogButton": "ดูบันทึกการเปลี่ยนแปลงบน GitHub", + "AboutChangelogButtonTooltipMessage": "คลิกเพื่อเปิดบันทึกการเปลี่ยนแปลงสำหรับเวอร์ชั่นนี้ บนเบราว์เซอร์เริ่มต้นของคุณ", + "SettingsTabNetworkMultiplayer": "ผู้เล่นหลายคน", + "MultiplayerMode": "โหมด:", + "MultiplayerModeTooltip": "เปลี่ยนโหมดผู้เล่นหลายคนของ LDN\n\nLdnMitm จะปรับเปลี่ยนฟังก์ชันการเล่นแบบไร้สาย/ภายใน จะให้เกมทำงานเหมือนกับว่าเป็น LAN ช่วยให้สามารถเชื่อมต่อภายในเครือข่ายเดียวกันกับอินสแตนซ์ Ryujinx อื่น ๆ และคอนโซล Nintendo Switch ที่ถูกแฮ็กซึ่งมีโมดูล ldn_mitm ติดตั้งอยู่\n\nผู้เล่นหลายคนต้องการให้ผู้เล่นทุกคนอยู่ในเกมเวอร์ชันเดียวกัน (เช่น Super Smash Bros. Ultimate v13.0.1 ไม่สามารถเชื่อมต่อกับ v13.0.0)\n\nปล่อยให้ปิดการใช้งานหากไม่แน่ใจ" +} diff --git a/src/Ryujinx/Assets/Locales/tr_TR.json b/src/Ryujinx/Assets/Locales/tr_TR.json index 3f70a781d..569ed28b1 100644 --- a/src/Ryujinx/Assets/Locales/tr_TR.json +++ b/src/Ryujinx/Assets/Locales/tr_TR.json @@ -14,7 +14,7 @@ "MenuBarFileOpenEmuFolder": "Ryujinx Klasörünü aç", "MenuBarFileOpenLogsFolder": "Logs Klasörünü aç", "MenuBarFileExit": "_Çıkış", - "MenuBarOptions": "Seçenekler", + "MenuBarOptions": "_Seçenekler", "MenuBarOptionsToggleFullscreen": "Tam Ekran Modunu Aç", "MenuBarOptionsStartGamesInFullscreen": "Oyunları Tam Ekran Modunda Başlat", "MenuBarOptionsStopEmulation": "Emülasyonu Durdur", @@ -30,7 +30,7 @@ "MenuBarToolsManageFileTypes": "Dosya uzantılarını yönet", "MenuBarToolsInstallFileTypes": "Dosya uzantılarını yükle", "MenuBarToolsUninstallFileTypes": "Dosya uzantılarını kaldır", - "MenuBarHelp": "Yardım", + "MenuBarHelp": "_Yardım", "MenuBarHelpCheckForUpdates": "Güncellemeleri Denetle", "MenuBarHelpAbout": "Hakkında", "MenuSearch": "Ara...", @@ -54,8 +54,6 @@ "GameListContextMenuManageTitleUpdatesToolTip": "Oyun Güncelleme Yönetim Penceresini Açar", "GameListContextMenuManageDlc": "DLC'leri Yönet", "GameListContextMenuManageDlcToolTip": "DLC yönetim penceresini açar", - "GameListContextMenuOpenModsDirectory": "Mod Dizinini Aç", - "GameListContextMenuOpenModsDirectoryToolTip": "Uygulamanın modlarının bulunduğu dizini açar", "GameListContextMenuCacheManagement": "Önbellek Yönetimi", "GameListContextMenuCacheManagementPurgePptc": "PPTC Yeniden Yapılandırmasını Başlat", "GameListContextMenuCacheManagementPurgePptcToolTip": "Oyunun bir sonraki açılışında PPTC'yi yeniden yapılandır", @@ -72,6 +70,13 @@ "GameListContextMenuExtractDataRomFSToolTip": "Uygulamanın geçerli yapılandırmasından RomFS kısmını ayıkla (Güncellemeler dahil)", "GameListContextMenuExtractDataLogo": "Simge", "GameListContextMenuExtractDataLogoToolTip": "Uygulamanın geçerli yapılandırmasından Logo kısmını ayıkla (Güncellemeler dahil)", + "GameListContextMenuCreateShortcut": "Uygulama Kısayolu Oluştur", + "GameListContextMenuCreateShortcutToolTip": "Seçilmiş uygulamayı çalıştıracak bir masaüstü kısayolu oluştur", + "GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application", + "GameListContextMenuOpenModsDirectory": "Open Mods Directory", + "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", + "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", "StatusBarGamesLoaded": "{0}/{1} Oyun Yüklendi", "StatusBarSystemVersion": "Sistem Sürümü: {0}", "LinuxVmMaxMapCountDialogTitle": "Bellek Haritaları İçin Düşük Limit Tespit Edildi ", @@ -150,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "Yerel (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", "SettingsTabGraphicsAspectRatio": "En-Boy Oranı:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -200,9 +205,9 @@ "ControllerSettingsInputDevice": "Giriş Cihazı", "ControllerSettingsRefresh": "Yenile", "ControllerSettingsDeviceDisabled": "Devre Dışı", - "ControllerSettingsControllerType": "Kontrolcü Tipi", + "ControllerSettingsControllerType": "Kumanda Tipi", "ControllerSettingsControllerTypeHandheld": "Portatif Mod", - "ControllerSettingsControllerTypeProController": "Profesyonel Denetleyici", + "ControllerSettingsControllerTypeProController": "Profesyonel Kumanda", "ControllerSettingsControllerTypeJoyConPair": "JoyCon Çifti", "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Sol", "ControllerSettingsControllerTypeJoyConRight": "JoyCon Sağ", @@ -253,7 +258,7 @@ "ControllerSettingsTriggerThreshold": "Tetik Eşiği:", "ControllerSettingsMotion": "Hareket", "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook uyumlu hareket kullan", - "ControllerSettingsMotionControllerSlot": "Kontrolcü Yuvası:", + "ControllerSettingsMotionControllerSlot": "Kumanda Yuvası:", "ControllerSettingsMotionMirrorInput": "Girişi Aynala", "ControllerSettingsMotionRightJoyConSlot": "Sağ JoyCon Yuvası:", "ControllerSettingsMotionServerHost": "Sunucu Sahibi:", @@ -292,13 +297,9 @@ "GameListContextMenuRunApplication": "Uygulamayı Çalıştır", "GameListContextMenuToggleFavorite": "Favori Ayarla", "GameListContextMenuToggleFavoriteToolTip": "Oyunu Favorilere Ekle/Çıkar", - "SettingsTabGeneralTheme": "Tema", - "SettingsTabGeneralThemeCustomTheme": "Özel Tema Yolu", - "SettingsTabGeneralThemeBaseStyle": "Temel Stil", - "SettingsTabGeneralThemeBaseStyleDark": "Karanlık", - "SettingsTabGeneralThemeBaseStyleLight": "Aydınlık", - "SettingsTabGeneralThemeEnableCustomTheme": "Özel Tema Etkinleştir", - "ButtonBrowse": "Göz At", + "SettingsTabGeneralTheme": "Theme:", + "SettingsTabGeneralThemeDark": "Dark", + "SettingsTabGeneralThemeLight": "Light", "ControllerSettingsConfigureGeneral": "Ayarla", "ControllerSettingsRumble": "Titreşim", "ControllerSettingsRumbleStrongMultiplier": "Güçlü Titreşim Çoklayıcı", @@ -332,8 +333,6 @@ "DialogUpdaterAddingFilesMessage": "Yeni Güncelleme Ekleniyor...", "DialogUpdaterCompleteMessage": "Güncelleme Tamamlandı!", "DialogUpdaterRestartMessage": "Ryujinx'i şimdi yeniden başlatmak istiyor musunuz?", - "DialogUpdaterArchNotSupportedMessage": "Sistem mimariniz desteklenmemektedir!", - "DialogUpdaterArchNotSupportedSubMessage": "(Sadece x64 sistemleri desteklenmektedir!)", "DialogUpdaterNoInternetMessage": "İnternete bağlı değilsiniz!", "DialogUpdaterNoInternetSubMessage": "Lütfen aktif bir internet bağlantınız olduğunu kontrol edin!", "DialogUpdaterDirtyBuildMessage": "Ryujinx'in Dirty build'lerini güncelleyemezsiniz!", @@ -350,7 +349,7 @@ "DialogUninstallFileTypesSuccessMessage": "Dosya uzantıları başarıyla kaldırıldı!", "DialogUninstallFileTypesErrorMessage": "Dosya uzantıları kaldırma işlemi başarısız oldu.", "DialogOpenSettingsWindowLabel": "Seçenekler Penceresini Aç", - "DialogControllerAppletTitle": "Kontrolcü Applet'i", + "DialogControllerAppletTitle": "Kumanda Applet'i", "DialogMessageDialogErrorExceptionMessage": "Mesaj diyaloğu gösterilirken hata: {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "Mesaj diyaloğu gösterilirken hata: {0}", "DialogErrorAppletErrorExceptionMessage": "Applet diyaloğu gösterilirken hata: {0}", @@ -383,9 +382,12 @@ "DialogUserProfileUnsavedChangesTitle": "Uyarı - Kaydedilmemiş Değişiklikler", "DialogUserProfileUnsavedChangesMessage": "Kullanıcı profilinizde kaydedilmemiş değişiklikler var.", "DialogUserProfileUnsavedChangesSubMessage": "Yaptığınız değişiklikleri iptal etmek istediğinize emin misiniz?", - "DialogControllerSettingsModifiedConfirmMessage": "Güncel kontrolcü seçenekleri güncellendi.", + "DialogControllerSettingsModifiedConfirmMessage": "Geçerli kumanda seçenekleri güncellendi.", "DialogControllerSettingsModifiedConfirmSubMessage": "Kaydetmek istiyor musunuz?", - "DialogLoadNcaErrorMessage": "{0}. Hatalı Dosya: {1}", + "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", + "DialogModAlreadyExistsMessage": "Mod already exists", + "DialogModInvalidMessage": "The specified directory does not contain a mod!", + "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", "DialogDlcNoDlcErrorMessage": "Belirtilen dosya seçilen oyun için DLC içermiyor!", "DialogPerformanceCheckLoggingEnabledMessage": "Sadece geliştiriler için dizayn edilen Trace Loglama seçeneği etkin.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "En iyi performans için trace loglama'nın devre dışı bırakılması tavsiye edilir. Trace loglama seçeneğini şimdi devre dışı bırakmak ister misiniz?", @@ -396,6 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "Belirtilen dosya seçilen oyun için güncelleme içermiyor!", "DialogSettingsBackendThreadingWarningTitle": "Uyarı - Backend Threading", "DialogSettingsBackendThreadingWarningMessage": "Bu seçeneğin tamamen uygulanması için Ryujinx'in kapatıp açılması gerekir. Kullandığınız işletim sistemine bağlı olarak, Ryujinx'in multithreading'ini kullanırken driver'ınızın multithreading seçeneğini kapatmanız gerekebilir.", + "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", "SettingsTabGraphicsFeaturesOptions": "Özellikler", "SettingsTabGraphicsBackendMultithreading": "Grafik Backend Multithreading:", "CommonAuto": "Otomatik", @@ -430,6 +434,7 @@ "DlcManagerRemoveAllButton": "Tümünü kaldır", "DlcManagerEnableAllButton": "Tümünü Aktif Et", "DlcManagerDisableAllButton": "Tümünü Devre Dışı Bırak", + "ModManagerDeleteAllButton": "Delete All", "MenuBarOptionsChangeLanguage": "Dili Değiştir", "MenuBarShowFileTypes": "Dosya Uzantılarını Göster", "CommonSort": "Sırala", @@ -446,14 +451,14 @@ "CustomThemeCheckTooltip": "Emülatör pencerelerinin görünümünü değiştirmek için özel bir Avalonia teması kullan", "CustomThemePathTooltip": "Özel arayüz temasının yolu", "CustomThemeBrowseTooltip": "Özel arayüz teması için göz at", - "DockModeToggleTooltip": "Docked modu emüle edilen sistemin yerleşik Nintendo Switch gibi davranmasını sağlar. Bu çoğu oyunda grafik kalitesini arttırır. Diğer yandan, bu seçeneği devre dışı bırakmak emüle edilen sistemin elde Ninendo Switch gibi davranmasını sağlayıp grafik kalitesini düşürür.\n\nDocked modu kullanmayı düşünüyorsanız 1. Oyuncu kontrollerini; Handheld modunu kullanmak istiyorsanız Handheld kontrollerini konfigüre edin.\n\nEmin değilseniz aktif halde bırakın.", - "DirectKeyboardTooltip": "Doğrudan Klavye Erişimi (HID) desteği. Oyunların klavyenizi metin giriş cihazı olarak kullanmasını sağlar.", - "DirectMouseTooltip": "Doğrudan Fare Erişimi (HID) desteği. Oyunların farenizi işaret aygıtı olarak kullanmasını sağlar.", + "DockModeToggleTooltip": "Docked modu emüle edilen sistemin yerleşik Nintendo Switch gibi davranmasını sağlar. Bu çoğu oyunda grafik kalitesini arttırır. Diğer yandan, bu seçeneği devre dışı bırakmak emüle edilen sistemin portatif Ninendo Switch gibi davranmasını sağlayıp grafik kalitesini düşürür.\n\nDocked modu kullanmayı düşünüyorsanız 1. Oyuncu kontrollerini; Handheld modunu kullanmak istiyorsanız portatif kontrollerini konfigüre edin.\n\nEmin değilseniz aktif halde bırakın.", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", "RegionTooltip": "Sistem Bölgesini Değiştir", "LanguageTooltip": "Sistem Dilini Değiştir", "TimezoneTooltip": "Sistem Saat Dilimini Değiştir", "TimeTooltip": "Sistem Saatini Değiştir", - "VSyncToggleTooltip": "Emüle edilen konsolun Dikey Senkronizasyonu. Çoğu oyun için kare sınırlayıcı işlevi görür, bu seçeneği devre dışı bırakmak bazı oyunların normalden yüksek hızda çalışmasını ve yükleme ekranlarının daha uzun sürmesini veya sıkışıp kalmasını sağlar.\n\nTercih ettiğiniz bir kısayol ile oyun içindeyken etkinleştirilip devre dışı bırakılabilir. Bu seçeneği devre dışı bırakmayı düşünüyorsanız bir kısayol atamanızı öneririz.\n\nEmin değilseniz aktif halde bırakın.", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", "PptcToggleTooltip": "Çevrilen JIT fonksiyonlarını oyun her açıldığında çevrilmek zorunda kalmaması için kaydeder.\n\nTeklemeyi azaltır ve ilk açılıştan sonra oyunların ilk açılış süresini ciddi biçimde hızlandırır.\n\nEmin değilseniz aktif halde bırakın.", "FsIntegrityToggleTooltip": "Oyun açarken hatalı dosyaların olup olmadığını kontrol eder, ve hatalı dosya bulursa log dosyasında hash hatası görüntüler.\n\nPerformansa herhangi bir etkisi yoktur ve sorun gidermeye yardımcı olur.\n\nEmin değilseniz aktif halde bırakın.", "AudioBackendTooltip": "Ses çıkış motorunu değiştirir.\n\nSDL2 tercih edilen seçenektir, OpenAL ve SoundIO ise alternatif olarak kullanılabilir. Dummy seçeneğinde ses çıkışı olmayacaktır.\n\nEmin değilseniz SDL2 seçeneğine ayarlayın.", @@ -467,10 +472,10 @@ "GraphicsBackendThreadingTooltip": "Grafik arka uç komutlarını ikinci bir iş parçacığında işletir.\n\nKendi multithreading desteği olmayan sürücülerde shader derlemeyi hızlandırıp performansı artırır. Multithreading desteği olan sürücülerde çok az daha iyi performans sağlar.\n\nEmin değilseniz Otomatik seçeneğine ayarlayın.", "GalThreadingTooltip": "Grafik arka uç komutlarını ikinci bir iş parçacığında işletir.\n\nKendi multithreading desteği olmayan sürücülerde shader derlemeyi hızlandırıp performansı artırır. Multithreading desteği olan sürücülerde çok az daha iyi performans sağlar.\n\nEmin değilseniz Otomatik seçeneğine ayarlayın.", "ShaderCacheToggleTooltip": "Sonraki çalışmalarda takılmaları engelleyen bir gölgelendirici disk önbelleğine kaydeder.", - "ResolutionScaleTooltip": "Uygulanabilir grafik hedeflerine uygulanan çözünürlük ölçeği", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", "ResolutionScaleEntryTooltip": "Küsüratlı çözünürlük ölçeği, 1.5 gibi. Küsüratlı ölçekler hata oluşturmaya ve çökmeye daha yatkındır.", - "AnisotropyTooltip": "Eşyönsüz doku süzmesi seviyesi (Oyun tarafından istenen değeri kullanmak için Otomatik seçeneğine ayarlayın)", - "AspectRatioTooltip": "Grafik penceresine uygulanan en-boy oranı.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", "ShaderDumpPathTooltip": "Grafik Shader Döküm Yolu", "FileLogTooltip": "Konsol loglarını diskte bir log dosyasına kaydeder. Performansı etkilemez.", "StubLogTooltip": "Stub log mesajlarını konsola yazdırır. Performansı etkilemez.", @@ -504,6 +509,8 @@ "EnableInternetAccessTooltip": "Emüle edilen uygulamanın internete bağlanmasını sağlar.\n\nLAN modu bulunan oyunlar bu seçenek ile birbirine bağlanabilir ve sistemler aynı access point'e bağlanır. Bu gerçek konsolları da kapsar.\n\nNintendo sunucularına bağlanmayı sağlaMAZ. Internete bağlanmaya çalışan baz oyunların çökmesine sebep olabilr.\n\nEmin değilseniz devre dışı bırakın.", "GameListContextMenuManageCheatToolTip": "Hileleri yönetmeyi sağlar", "GameListContextMenuManageCheat": "Hileleri Yönet", + "GameListContextMenuManageModToolTip": "Manage Mods", + "GameListContextMenuManageMod": "Manage Mods", "ControllerSettingsStickRange": "Menzil:", "DialogStopEmulationTitle": "Ryujinx - Emülasyonu Durdur", "DialogStopEmulationMessage": "Emülasyonu durdurmak istediğinizden emin misiniz?", @@ -515,8 +522,6 @@ "SettingsTabCpuMemory": "CPU Hafızası", "DialogUpdaterFlatpakNotSupportedMessage": "Lütfen Ryujinx'i FlatHub aracılığıyla güncelleyin.", "UpdaterDisabledWarningTitle": "Güncelleyici Devre Dışı!", - "GameListContextMenuOpenSdModsDirectory": "Atmosphere Mod Dizini", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Uygulama Modlarını içeren alternatif SD kart Atmosfer dizinini açar. Gerçek donanım için paketlenmiş modlar için kullanışlıdır.", "ControllerSettingsRotate90": "Saat yönünde 90° Döndür", "IconSize": "Ikon Boyutu", "IconSizeTooltip": "Oyun ikonlarının boyutunu değiştirmeyi sağlar", @@ -544,19 +549,20 @@ "SwkbdMinCharacters": "En az {0} karakter uzunluğunda olmalı", "SwkbdMinRangeCharacters": "{0}-{1} karakter uzunluğunda olmalı", "SoftwareKeyboard": "Yazılım Klavyesi", - "SoftwareKeyboardModeNumbersOnly": "Sadece Numara Olabilir", + "SoftwareKeyboardModeNumeric": "Sadece 0-9 veya '.' olabilir", "SoftwareKeyboardModeAlphabet": "Sadece CJK-characters olmayan karakterler olabilir", "SoftwareKeyboardModeASCII": "Sadece ASCII karakterler olabilir", - "DialogControllerAppletMessagePlayerRange": "Uygulama belirtilen türde {0} oyuncu istiyor:\n\nTÜRLER: {1}\n\nOYUNCULAR: {2}\n\n{3}Lütfen şimdi seçeneklerden giriş aygıtlarını ayarlayın veya Kapat'a basın.", - "DialogControllerAppletMessage": "Uygulama belirtilen türde tam olarak {0} oyuncu istiyor:\n\nTÜRLER: {1}\n\nOYUNCULAR: {2}\n\n{3}Lütfen şimdi seçeneklerden giriş aygıtlarını ayarlayın veya Kapat'a basın.", - "DialogControllerAppletDockModeSet": "Docked mode etkin. Handheld geçersiz.\n\n", + "ControllerAppletControllers": "Desteklenen Kumandalar:", + "ControllerAppletPlayers": "Oyuncular:", + "ControllerAppletDescription": "Halihazırdaki konfigürasyonunuz geçersiz. Ayarları açın ve girişlerinizi yeniden konfigüre edin.", + "ControllerAppletDocked": "Docked mod ayarlandı. Portatif denetim devre dışı bırakılmalı.", "UpdaterRenaming": "Eski dosyalar yeniden adlandırılıyor...", "UpdaterRenameFailed": "Güncelleyici belirtilen dosyayı yeniden adlandıramadı: {0}", "UpdaterAddingFiles": "Yeni Dosyalar Ekleniyor...", "UpdaterExtracting": "Güncelleme Ayrıştırılıyor...", "UpdaterDownloading": "Güncelleme İndiriliyor...", "Game": "Oyun", - "Docked": "Yerleştirildi", + "Docked": "Docked", "Handheld": "El tipi", "ConnectionError": "Bağlantı Hatası.", "AboutPageDeveloperListMore": "{0} ve daha fazla...", @@ -587,6 +593,7 @@ "Writable": "Yazılabilir", "SelectDlcDialogTitle": "DLC dosyalarını seç", "SelectUpdateDialogTitle": "Güncelleme dosyalarını seç", + "SelectModDialogTitle": "Select mod directory", "UserProfileWindowTitle": "Kullanıcı Profillerini Yönet", "CheatWindowTitle": "Oyun Hilelerini Yönet", "DlcWindowTitle": "Oyun DLC'lerini Yönet", @@ -594,10 +601,12 @@ "CheatWindowHeading": "{0} için Hile mevcut [{1}]", "BuildId": "BuildId:", "DlcWindowHeading": "{0} için DLC mevcut [{1}]", + "ModWindowHeading": "{0} Mod(s)", "UserProfilesEditProfile": "Seçiliyi Düzenle", "Cancel": "İptal", "Save": "Kaydet", "Discard": "Iskarta", + "Paused": "Durduruldu", "UserProfilesSetProfileImage": "Profil Resmi Ayarla", "UserProfileEmptyNameError": "İsim gerekli", "UserProfileNoImageError": "Profil resmi ayarlanmalıdır", @@ -607,9 +616,9 @@ "UserProfilesName": "İsim:", "UserProfilesUserId": "Kullanıcı Adı:", "SettingsTabGraphicsBackend": "Grafik Arka Ucu", - "SettingsTabGraphicsBackendTooltip": "Kullanılacak Grafik Arka Uç", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", "SettingsEnableTextureRecompression": "Yeniden Doku Sıkıştırılmasını Aktif Et", - "SettingsEnableTextureRecompressionTooltip": "4GB VRAM'in Altında Sistemler için önerilir.\n\nEmin değilseniz kapalı bırakın", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", "SettingsTabGraphicsPreferredGpu": "Kullanılan GPU", "SettingsTabGraphicsPreferredGpuTooltip": "Vulkan Grafik Arka Ucu ile kullanılacak Ekran Kartını Seçin.\n\nOpenGL'nin kullanacağı GPU'yu etkilemez.\n\n Emin değilseniz \"dGPU\" olarak işaretlenmiş GPU'ya ayarlayın. Eğer yoksa, dokunmadan bırakın.\n", "SettingsAppRequiredRestartMessage": "Ryujinx'i Yeniden Başlatma Gerekli", @@ -635,12 +644,12 @@ "Recover": "Kurtar", "UserProfilesRecoverHeading": "Aşağıdaki hesaplar için kayıtlar bulundu", "UserProfilesRecoverEmptyList": "Kurtarılacak profil bulunamadı", - "GraphicsAATooltip": "Oyuna Kenar Yumuşatma Ekler", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", "GraphicsAALabel": "Kenar Yumuşatma:", "GraphicsScalingFilterLabel": "Ölçekleme Filtresi:", - "GraphicsScalingFilterTooltip": "Çerçeve Arabellek Filtresini Açar", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", "GraphicsScalingFilterLevelLabel": "Seviye", - "GraphicsScalingFilterLevelTooltip": "Ölçekleme Filtre Seviyesini Belirle", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "Düşük SMAA", "SmaaMedium": "Orta SMAA", "SmaaHigh": "Yüksek SMAA", @@ -648,9 +657,12 @@ "UserEditorTitle": "Kullanıcıyı Düzenle", "UserEditorTitleCreate": "Kullanıcı Oluştur", "SettingsTabNetworkInterface": "Ağ Bağlantısı:", - "NetworkInterfaceTooltip": "LAN özellikleri için kullanılan ağ bağlantısı", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", "NetworkInterfaceDefault": "Varsayılan", "PackagingShaders": "Gölgeler Paketleniyor", "AboutChangelogButton": "GitHub'da Değişiklikleri Görüntüle", - "AboutChangelogButtonTooltipMessage": "Kullandığınız versiyon için olan değişiklikleri varsayılan tarayıcınızda görmek için tıklayın" -} \ No newline at end of file + "AboutChangelogButtonTooltipMessage": "Kullandığınız versiyon için olan değişiklikleri varsayılan tarayıcınızda görmek için tıklayın", + "SettingsTabNetworkMultiplayer": "Çok Oyunculu", + "MultiplayerMode": "Mod:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/uk_UA.json b/src/Ryujinx/Assets/Locales/uk_UA.json index a119fb4b7..61af5c32a 100644 --- a/src/Ryujinx/Assets/Locales/uk_UA.json +++ b/src/Ryujinx/Assets/Locales/uk_UA.json @@ -1,46 +1,46 @@ { - "Language": "Yкраїнська", + "Language": "Українська", "MenuBarFileOpenApplet": "Відкрити аплет", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Відкрийте аплет Mii Editor в автономному режимі", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Відкрити аплет Mii Editor в автономному режимі", "SettingsTabInputDirectMouseAccess": "Прямий доступ мишею", - "SettingsTabSystemMemoryManagerMode": "Режим диспетчера пам'яті:", + "SettingsTabSystemMemoryManagerMode": "Режим диспетчера пам’яті:", "SettingsTabSystemMemoryManagerModeSoftware": "Програмне забезпечення", "SettingsTabSystemMemoryManagerModeHost": "Хост (швидко)", "SettingsTabSystemMemoryManagerModeHostUnchecked": "Неперевірений хост (найшвидший, небезпечний)", - "SettingsTabSystemUseHypervisor": "Use Hypervisor", + "SettingsTabSystemUseHypervisor": "Використовувати гіпервізор", "MenuBarFile": "_Файл", "MenuBarFileOpenFromFile": "_Завантажити програму з файлу", "MenuBarFileOpenUnpacked": "Завантажити _розпаковану гру", "MenuBarFileOpenEmuFolder": "Відкрити теку Ryujinx", "MenuBarFileOpenLogsFolder": "Відкрити теку журналів змін", "MenuBarFileExit": "_Вихід", - "MenuBarOptions": "Опції", - "MenuBarOptionsToggleFullscreen": "Перемкнути на весь екран", + "MenuBarOptions": "_Options", + "MenuBarOptionsToggleFullscreen": "На весь екран", "MenuBarOptionsStartGamesInFullscreen": "Запускати ігри на весь екран", "MenuBarOptionsStopEmulation": "Зупинити емуляцію", "MenuBarOptionsSettings": "_Налаштування", - "MenuBarOptionsManageUserProfiles": "_Керування профілями користувачів", + "MenuBarOptionsManageUserProfiles": "_Керувати профілями користувачів", "MenuBarActions": "_Дії", "MenuBarOptionsSimulateWakeUpMessage": "Симулювати повідомлення про пробудження", "MenuBarActionsScanAmiibo": "Сканувати Amiibo", "MenuBarTools": "_Інструменти", - "MenuBarToolsInstallFirmware": "Встановити прошивку", - "MenuBarFileToolsInstallFirmwareFromFile": "Встановити прошивку з XCI або ZIP", - "MenuBarFileToolsInstallFirmwareFromDirectory": "Встановити прошивку з теки", - "MenuBarToolsManageFileTypes": "Manage file types", - "MenuBarToolsInstallFileTypes": "Install file types", - "MenuBarToolsUninstallFileTypes": "Uninstall file types", - "MenuBarHelp": "Довідка", + "MenuBarToolsInstallFirmware": "Установити прошивку", + "MenuBarFileToolsInstallFirmwareFromFile": "Установити прошивку з XCI або ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Установити прошивку з теки", + "MenuBarToolsManageFileTypes": "Керувати типами файлів", + "MenuBarToolsInstallFileTypes": "Установити типи файлів", + "MenuBarToolsUninstallFileTypes": "Видалити типи файлів", + "MenuBarHelp": "_Help", "MenuBarHelpCheckForUpdates": "Перевірити оновлення", - "MenuBarHelpAbout": "Про програму", + "MenuBarHelpAbout": "Про застосунок", "MenuSearch": "Пошук...", - "GameListHeaderFavorite": "Вибране", + "GameListHeaderFavorite": "Обране", "GameListHeaderIcon": "Значок", "GameListHeaderApplication": "Назва", "GameListHeaderDeveloper": "Розробник", "GameListHeaderVersion": "Версія", "GameListHeaderTimePlayed": "Зіграно часу", - "GameListHeaderLastPlayed": "Остання гра", + "GameListHeaderLastPlayed": "Востаннє зіграно", "GameListHeaderFileExtension": "Розширення файлу", "GameListHeaderFileSize": "Розмір файлу", "GameListHeaderPath": "Шлях", @@ -54,8 +54,6 @@ "GameListContextMenuManageTitleUpdatesToolTip": "Відкриває вікно керування оновленням заголовка", "GameListContextMenuManageDlc": "Керування DLC", "GameListContextMenuManageDlcToolTip": "Відкриває вікно керування DLC", - "GameListContextMenuOpenModsDirectory": "Відкрити каталог модифікацій", - "GameListContextMenuOpenModsDirectoryToolTip": "Відкриває каталог, який містить модифікації програм", "GameListContextMenuCacheManagement": "Керування кешем", "GameListContextMenuCacheManagementPurgePptc": "Очистити кеш PPTC", "GameListContextMenuCacheManagementPurgePptcToolTip": "Видаляє кеш PPTC програми", @@ -72,26 +70,33 @@ "GameListContextMenuExtractDataRomFSToolTip": "Видобуває розділ RomFS із поточної конфігурації програми (включаючи оновлення)", "GameListContextMenuExtractDataLogo": "Логотип", "GameListContextMenuExtractDataLogoToolTip": "Видобуває розділ логотипу з поточної конфігурації програми (включаючи оновлення)", - "StatusBarGamesLoaded": "{0}/{1} Ігор завантажено", + "GameListContextMenuCreateShortcut": "Створити ярлик застосунку", + "GameListContextMenuCreateShortcutToolTip": "Створити ярлик на робочому столі, який запускає вибраний застосунок", + "GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application", + "GameListContextMenuOpenModsDirectory": "Відкрити теку з модами", + "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", + "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", + "StatusBarGamesLoaded": "{0}/{1} ігор завантажено", "StatusBarSystemVersion": "Версія системи: {0}", - "LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected", - "LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}", - "LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "Yes, until the next restart", - "LinuxVmMaxMapCountDialogButtonPersistent": "Yes, permanently", - "LinuxVmMaxMapCountWarningTextPrimary": "Max amount of memory mappings is lower than recommended.", - "LinuxVmMaxMapCountWarningTextSecondary": "The current value of vm.max_map_count ({0}) is lower than {1}. Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.\n\nYou might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.", + "LinuxVmMaxMapCountDialogTitle": "Виявлено низьку межу для відображення памʼяті", + "LinuxVmMaxMapCountDialogTextPrimary": "Бажаєте збільшити значення vm.max_map_count на {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Деякі ігри можуть спробувати створити більше відображень памʼяті, ніж дозволено наразі. Ryujinx завершить роботу, щойно цей ліміт буде перевищено.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Так, до наст. перезапуску", + "LinuxVmMaxMapCountDialogButtonPersistent": "Так, назавжди", + "LinuxVmMaxMapCountWarningTextPrimary": "Максимальна кількість відображення памʼяті менша, ніж рекомендовано.", + "LinuxVmMaxMapCountWarningTextSecondary": "Поточне значення vm.max_map_count ({0}) менше за {1}. Деякі ігри можуть спробувати створити більше відображень пам’яті, ніж дозволено наразі. Ryujinx завершить роботу, щойно цей ліміт буде перевищено.\n\nВи можете збільшити ліміт вручну або встановити pkexec, який дозволяє Ryujinx допомогти з цим.", "Settings": "Налаштування", "SettingsTabGeneral": "Інтерфейс користувача", "SettingsTabGeneralGeneral": "Загальні", "SettingsTabGeneralEnableDiscordRichPresence": "Увімкнути розширену присутність Discord", "SettingsTabGeneralCheckUpdatesOnLaunch": "Перевіряти наявність оновлень під час запуску", "SettingsTabGeneralShowConfirmExitDialog": "Показати діалогове вікно «Підтвердити вихід».", - "SettingsTabGeneralHideCursor": "Hide Cursor:", - "SettingsTabGeneralHideCursorNever": "Never", - "SettingsTabGeneralHideCursorOnIdle": "Приховати курсор у режимі очікування", - "SettingsTabGeneralHideCursorAlways": "Always", - "SettingsTabGeneralGameDirectories": "Каталоги ігор", + "SettingsTabGeneralHideCursor": "Сховати вказівник:", + "SettingsTabGeneralHideCursorNever": "Ніколи", + "SettingsTabGeneralHideCursorOnIdle": "Сховати у режимі очікування", + "SettingsTabGeneralHideCursorAlways": "Завжди", + "SettingsTabGeneralGameDirectories": "Тека ігор", "SettingsTabGeneralAdd": "Додати", "SettingsTabGeneralRemove": "Видалити", "SettingsTabSystem": "Система", @@ -150,7 +155,7 @@ "SettingsTabGraphicsResolutionScaleNative": "Стандартний (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", "SettingsTabGraphicsAspectRatio": "Співвідношення сторін:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -158,7 +163,7 @@ "SettingsTabGraphicsAspectRatio21x9": "21:9", "SettingsTabGraphicsAspectRatio32x9": "32:9", "SettingsTabGraphicsAspectRatioStretch": "Розтягнути до розміру вікна", - "SettingsTabGraphicsDeveloperOptions": "Налаштування виробника", + "SettingsTabGraphicsDeveloperOptions": "Параметри розробника", "SettingsTabGraphicsShaderDumpPath": "Шлях скидання графічного шейдера:", "SettingsTabLogging": "Налагодження", "SettingsTabLoggingLogging": "Налагодження", @@ -172,9 +177,9 @@ "SettingsTabLoggingEnableFsAccessLogs": "Увімкнути журнали доступу Fs", "SettingsTabLoggingFsGlobalAccessLogMode": "Режим журналу глобального доступу Fs:", "SettingsTabLoggingDeveloperOptions": "Параметри розробника (УВАГА: знизиться продуктивність)", - "SettingsTabLoggingDeveloperOptionsNote": "WARNING: Will reduce performance", + "SettingsTabLoggingDeveloperOptionsNote": "УВАГА: Знижує продуктивність", "SettingsTabLoggingGraphicsBackendLogLevel": "Рівень журналу графічного сервера:", - "SettingsTabLoggingGraphicsBackendLogLevelNone": "Ні", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Немає", "SettingsTabLoggingGraphicsBackendLogLevelError": "Помилка", "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Уповільнення", "SettingsTabLoggingGraphicsBackendLogLevelAll": "Все", @@ -223,15 +228,15 @@ "ControllerSettingsDPadDown": "Вниз", "ControllerSettingsDPadLeft": "Вліво", "ControllerSettingsDPadRight": "Вправо", - "ControllerSettingsStickButton": "Button", - "ControllerSettingsStickUp": "Up", - "ControllerSettingsStickDown": "Down", - "ControllerSettingsStickLeft": "Left", - "ControllerSettingsStickRight": "Right", - "ControllerSettingsStickStick": "Stick", - "ControllerSettingsStickInvertXAxis": "Invert Stick X", - "ControllerSettingsStickInvertYAxis": "Invert Stick Y", - "ControllerSettingsStickDeadzone": "Deadzone:", + "ControllerSettingsStickButton": "Кнопка", + "ControllerSettingsStickUp": "Уверх", + "ControllerSettingsStickDown": "Униз", + "ControllerSettingsStickLeft": "Ліворуч", + "ControllerSettingsStickRight": "Праворуч", + "ControllerSettingsStickStick": "Стик", + "ControllerSettingsStickInvertXAxis": "Обернути вісь стику X", + "ControllerSettingsStickInvertYAxis": "Обернути вісь стику Y", + "ControllerSettingsStickDeadzone": "Мертва зона:", "ControllerSettingsLStick": "Лівий джойстик", "ControllerSettingsRStick": "Правий джойстик", "ControllerSettingsTriggersLeft": "Тригери ліворуч", @@ -266,9 +271,9 @@ "UserProfilesChangeProfileImage": "Змінити зображення профілю", "UserProfilesAvailableUserProfiles": "Доступні профілі користувачів:", "UserProfilesAddNewProfile": "Створити профіль", - "UserProfilesDelete": "Delete", + "UserProfilesDelete": "Видалити", "UserProfilesClose": "Закрити", - "ProfileNameSelectionWatermark": "Choose a nickname", + "ProfileNameSelectionWatermark": "Оберіть псевдонім", "ProfileImageSelectionTitle": "Вибір зображення профілю", "ProfileImageSelectionHeader": "Виберіть зображення профілю", "ProfileImageSelectionNote": "Ви можете імпортувати власне зображення профілю або вибрати аватар із мікропрограми системи", @@ -289,16 +294,12 @@ "ControllerSettingsSaveProfileToolTip": "Зберегти профіль", "MenuBarFileToolsTakeScreenshot": "Зробити знімок екрана", "MenuBarFileToolsHideUi": "Сховати інтерфейс", - "GameListContextMenuRunApplication": "Run Application", + "GameListContextMenuRunApplication": "Запустити додаток", "GameListContextMenuToggleFavorite": "Перемкнути вибране", "GameListContextMenuToggleFavoriteToolTip": "Перемкнути улюблений статус гри", - "SettingsTabGeneralTheme": "Тема", - "SettingsTabGeneralThemeCustomTheme": "Користувацький шлях до теми", - "SettingsTabGeneralThemeBaseStyle": "Базовий стиль", - "SettingsTabGeneralThemeBaseStyleDark": "Темна", - "SettingsTabGeneralThemeBaseStyleLight": "Світла", - "SettingsTabGeneralThemeEnableCustomTheme": "Увімкнути користуваьку тему", - "ButtonBrowse": "Огляд", + "SettingsTabGeneralTheme": "Тема:", + "SettingsTabGeneralThemeDark": "Темна", + "SettingsTabGeneralThemeLight": "Світла", "ControllerSettingsConfigureGeneral": "Налаштування", "ControllerSettingsRumble": "Вібрація", "ControllerSettingsRumbleStrongMultiplier": "Множник сильної вібрації", @@ -332,8 +333,6 @@ "DialogUpdaterAddingFilesMessage": "Додавання нового оновлення...", "DialogUpdaterCompleteMessage": "Оновлення завершено!", "DialogUpdaterRestartMessage": "Перезапустити Ryujinx зараз?", - "DialogUpdaterArchNotSupportedMessage": "Ви використовуєте не підтримувану архітектуру системи!", - "DialogUpdaterArchNotSupportedSubMessage": "(Підтримуються лише системи x64!)", "DialogUpdaterNoInternetMessage": "Ви не підключені до Інтернету!", "DialogUpdaterNoInternetSubMessage": "Будь ласка, переконайтеся, що у вас є робоче підключення до Інтернету!", "DialogUpdaterDirtyBuildMessage": "Ви не можете оновити брудну збірку Ryujinx!", @@ -345,10 +344,10 @@ "DialogFirmwareInstallEmbeddedSuccessMessage": "Встановлену прошивку не знайдено, але Ryujinx вдалося встановити прошивку {0} з наданої гри.\\nТепер запуститься емулятор.", "DialogFirmwareNoFirmwareInstalledMessage": "Прошивка не встановлена", "DialogFirmwareInstalledMessage": "Встановлено прошивку {0}", - "DialogInstallFileTypesSuccessMessage": "Successfully installed file types!", - "DialogInstallFileTypesErrorMessage": "Failed to install file types.", - "DialogUninstallFileTypesSuccessMessage": "Successfully uninstalled file types!", - "DialogUninstallFileTypesErrorMessage": "Failed to uninstall file types.", + "DialogInstallFileTypesSuccessMessage": "Успішно встановлено типи файлів!", + "DialogInstallFileTypesErrorMessage": "Не вдалося встановити типи файлів.", + "DialogUninstallFileTypesSuccessMessage": "Успішно видалено типи файлів!", + "DialogUninstallFileTypesErrorMessage": "Не вдалося видалити типи файлів.", "DialogOpenSettingsWindowLabel": "Відкрити вікно налаштувань", "DialogControllerAppletTitle": "Аплет контролера", "DialogMessageDialogErrorExceptionMessage": "Помилка показу діалогового вікна повідомлення: {0}", @@ -380,12 +379,15 @@ "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Версію системи {0} успішно встановлено.", "DialogUserProfileDeletionWarningMessage": "Якщо вибраний профіль буде видалено, інші профілі не відкриватимуться", "DialogUserProfileDeletionConfirmMessage": "Ви хочете видалити вибраний профіль", - "DialogUserProfileUnsavedChangesTitle": "Warning - Unsaved Changes", - "DialogUserProfileUnsavedChangesMessage": "You have made changes to this user profile that have not been saved.", - "DialogUserProfileUnsavedChangesSubMessage": "Do you want to discard your changes?", + "DialogUserProfileUnsavedChangesTitle": "Увага — Незбережені зміни", + "DialogUserProfileUnsavedChangesMessage": "Ви зробили зміни у цьому профілю користувача які не було збережено.", + "DialogUserProfileUnsavedChangesSubMessage": "Бажаєте скасувати зміни?", "DialogControllerSettingsModifiedConfirmMessage": "Поточні налаштування контролера оновлено.", "DialogControllerSettingsModifiedConfirmSubMessage": "Ви хочете зберегти?", - "DialogLoadNcaErrorMessage": "{0}. Файл з помилкою: {1}", + "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", + "DialogModAlreadyExistsMessage": "Mod already exists", + "DialogModInvalidMessage": "The specified directory does not contain a mod!", + "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", "DialogDlcNoDlcErrorMessage": "Зазначений файл не містить DLC для вибраного заголовку!", "DialogPerformanceCheckLoggingEnabledMessage": "Ви увімкнули журнал налагодження, призначений лише для розробників.", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальної продуктивності рекомендується вимкнути ведення журналу налагодження. Ви хочете вимкнути ведення журналу налагодження зараз?", @@ -396,6 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "Зазначений файл не містить оновлення для вибраного заголовка!", "DialogSettingsBackendThreadingWarningTitle": "Попередження - потокове керування сервером", "DialogSettingsBackendThreadingWarningMessage": "Ryujinx потрібно перезапустити після зміни цього параметра, щоб він застосовувався повністю. Залежно від вашої платформи вам може знадобитися вручну вимкнути власну багатопотоковість драйвера під час використання Ryujinx.", + "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", "SettingsTabGraphicsFeaturesOptions": "Особливості", "SettingsTabGraphicsBackendMultithreading": "Багатопотоковість графічного сервера:", "CommonAuto": "Авто", @@ -430,8 +434,9 @@ "DlcManagerRemoveAllButton": "Видалити все", "DlcManagerEnableAllButton": "Увімкнути всі", "DlcManagerDisableAllButton": "Вимкнути всі", + "ModManagerDeleteAllButton": "Видалити все", "MenuBarOptionsChangeLanguage": "Змінити мову", - "MenuBarShowFileTypes": "Show File Types", + "MenuBarShowFileTypes": "Показати типи файлів", "CommonSort": "Сортувати", "CommonShowNames": "Показати назви", "CommonFavorite": "Вибрані", @@ -447,13 +452,13 @@ "CustomThemePathTooltip": "Шлях до користувацької теми графічного інтерфейсу", "CustomThemeBrowseTooltip": "Огляд користувацької теми графічного інтерфейсу", "DockModeToggleTooltip": "У режимі док-станції емульована система веде себе як приєднаний Nintendo Switch. Це покращує точність графіки в більшості ігор. І навпаки, вимкнення цього призведе до того, що емульована система поводитиметься як портативний комутатор Nintendo, погіршуючи якість графіки.\n\nНалаштуйте елементи керування для гравця 1, якщо плануєте використовувати режим док-станції; налаштуйте ручні елементи керування, якщо плануєте використовувати портативний режим.\n\nЗалиште увімкненим, якщо не впевнені.", - "DirectKeyboardTooltip": "Підтримка прямого доступу з клавіатури (HID). Надає іграм доступ до клавіатури як пристрою для введення тексту.", - "DirectMouseTooltip": "Підтримка прямого доступу миші (HID). Надає іграм доступ до миші як вказівного пристрою.", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", "RegionTooltip": "Змінити регіон системи", "LanguageTooltip": "Змінити мову системи", "TimezoneTooltip": "Змінити часовий пояс системи", "TimeTooltip": "Змінити час системи", - "VSyncToggleTooltip": "Емульована вертикальна синхронізація консолі. По суті, обмежувач кадрів для більшості ігор; його вимкнення може призвести до того, що ігри працюватимуть на вищій швидкості, екрани завантаження триватимуть довше чи зупинятимуться.\n\nМожна перемикати в грі гарячою клавішею за вашим бажанням. Ми рекомендуємо зробити це, якщо ви плануєте вимкнути його.\n\nЗалиште увімкненим, якщо не впевнені.", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", "PptcToggleTooltip": "Зберігає перекладені функції JIT, щоб їх не потрібно було перекладати кожного разу, коли гра завантажується.\n\nЗменшує заїкання та значно прискорює час завантаження після першого завантаження гри.\n\nЗалиште увімкненим, якщо не впевнені.", "FsIntegrityToggleTooltip": "Перевіряє наявність пошкоджених файлів під час завантаження гри, і якщо виявлено пошкоджені файли, показує помилку хешу в журналі.\n\nНе впливає на продуктивність і призначений для усунення несправностей.\n\nЗалиште увімкненим, якщо не впевнені.", "AudioBackendTooltip": "Змінює серверну частину, яка використовується для відтворення аудіо.\n\nSDL2 є кращим, тоді як OpenAL і SoundIO використовуються як резервні варіанти. Dummy не матиме звуку.\n\nВстановіть SDL2, якщо не впевнені.", @@ -461,16 +466,16 @@ "MemoryManagerSoftwareTooltip": "Використовує програмну таблицю сторінок для перекладу адрес. Найвища точність, але найповільніша продуктивність.", "MemoryManagerHostTooltip": "Пряме відображення пам'яті в адресному просторі хосту. Набагато швидша компіляція та виконання JIT.", "MemoryManagerUnsafeTooltip": "Пряме відображення пам’яті, але не маскує адресу в гостьовому адресному просторі перед доступом. Швидше, але ціною безпеки. Гостьова програма може отримати доступ до пам’яті з будь-якого місця в Ryujinx, тому запускайте в цьому режимі лише програми, яким ви довіряєте.", - "UseHypervisorTooltip": "Use Hypervisor instead of JIT. Greatly improves performance when available, but can be unstable in its current state.", + "UseHypervisorTooltip": "Використання гіпервізор замість JIT. Значно покращує продуктивність, коли доступний, але може бути нестабільним у поточному стані.", "DRamTooltip": "Використовує альтернативний макет MemoryMode для імітації моделі розробки Switch.\n\nЦе корисно лише для пакетів текстур з вищою роздільною здатністю або модифікацій із роздільною здатністю 4K. НЕ покращує продуктивність.\n\nЗалиште вимкненим, якщо не впевнені.", "IgnoreMissingServicesTooltip": "Ігнорує нереалізовані служби Horizon OS. Це може допомогти в обході збоїв під час завантаження певних ігор.\n\nЗалиште вимкненим, якщо не впевнені.", "GraphicsBackendThreadingTooltip": "Виконує команди графічного сервера в другому потоці.\n\nПрискорює компіляцію шейдерів, зменшує затримки та покращує продуктивність драйверів GPU без власної підтримки багатопоточності. Трохи краща продуктивність на драйверах з багатопотоковістю.\nВстановіть значення «Авто», якщо не впевнені", "GalThreadingTooltip": "Виконує команди графічного сервера в другому потоці.\n\nПрискорює компіляцію шейдерів, зменшує затримки та покращує продуктивність драйверів GPU без власної підтримки багатопоточності. Трохи краща продуктивність на драйверах з багатопотоковістю.\n\nВстановіть значення «Авто», якщо не впевнені.", "ShaderCacheToggleTooltip": "Зберігає кеш дискового шейдера, що зменшує затримки під час наступних запусків.\n\nЗалиште увімкненим, якщо не впевнені.", - "ResolutionScaleTooltip": "Масштаб роздільної здатності, застосована до відповідних цілей візуалізації", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", "ResolutionScaleEntryTooltip": "Масштаб роздільної здатності з плаваючою комою, наприклад 1,5. Не інтегральні масштаби, швидше за все, спричинять проблеми або збій.", - "AnisotropyTooltip": "Рівень анізотропної фільтрації (встановіть на «Авто», щоб використовувати значення, яке вимагає гра)", - "AspectRatioTooltip": "Співвідношення сторін, застосоване до вікна візуалізації.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", "ShaderDumpPathTooltip": "Шлях скидання графічних шейдерів", "FileLogTooltip": "Зберігає журнал консолі у файл журналу на диску. Не впливає на продуктивність.", "StubLogTooltip": "Друкує повідомлення журналу-заглушки на консолі. Не впливає на продуктивність.", @@ -504,6 +509,8 @@ "EnableInternetAccessTooltip": "Дозволяє емульованій програмі підключатися до Інтернету.\n\nІгри з режимом локальної мережі можуть підключатися одна до одної, якщо це увімкнено, і системи підключені до однієї точки доступу. Сюди входять і справжні консолі.\n\nНЕ дозволяє підключатися до серверів Nintendo. Може призвести до збою в деяких іграх, які намагаються підключитися до Інтернету.\n\nЗалиште вимкненим, якщо не впевнені.", "GameListContextMenuManageCheatToolTip": "Керування читами", "GameListContextMenuManageCheat": "Керування читами", + "GameListContextMenuManageModToolTip": "Керування модами", + "GameListContextMenuManageMod": "Керування модами", "ControllerSettingsStickRange": "Діапазон:", "DialogStopEmulationTitle": "Ryujinx - Зупинити емуляцію", "DialogStopEmulationMessage": "Ви впевнені, що хочете зупинити емуляцію?", @@ -515,8 +522,6 @@ "SettingsTabCpuMemory": "Пам'ять ЦП", "DialogUpdaterFlatpakNotSupportedMessage": "Будь ласка, оновіть Ryujinx через FlatHub.", "UpdaterDisabledWarningTitle": "Програму оновлення вимкнено!", - "GameListContextMenuOpenSdModsDirectory": "Відкрити каталог модифікацій Atmosphere", - "GameListContextMenuOpenSdModsDirectoryToolTip": "Відкриває альтернативний каталог SD-карти Atmosphere, який містить модифікації програми. Корисно для модифікацій, упакованих для реального обладнання.", "ControllerSettingsRotate90": "Повернути на 90° за годинниковою стрілкою", "IconSize": "Розмір значка", "IconSizeTooltip": "Змінити розмір значків гри", @@ -544,12 +549,13 @@ "SwkbdMinCharacters": "Мінімальна кількість символів: {0}", "SwkbdMinRangeCharacters": "Має бути {0}-{1} символів", "SoftwareKeyboard": "Програмна клавіатура", - "SoftwareKeyboardModeNumbersOnly": "Must be numbers only", - "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", - "SoftwareKeyboardModeASCII": "Must be ASCII text only", - "DialogControllerAppletMessagePlayerRange": "Програма запитує {0} гравця(ів) з:\n\nТИПИ: {1}\n\nГРАВЦІ: {2}\n\n{3}Будь ласка, відкрийте «Налаштування» та повторно налаштуйте «Введення» або натисніть «Закрити».", - "DialogControllerAppletMessage": "Програма запитує рівно стільки гравців: {0} з:\n\nТИПАМИ: {1}\n\nГРАВЦІВ: {2}\n\n{3}Будь ласка, відкрийте «Налаштування» та повторно налаштуйте «Введення» або натисніть «Закрити».", - "DialogControllerAppletDockModeSet": "Встановлено режим док-станції. Ручний також недійсний.\n", + "SoftwareKeyboardModeNumeric": "Повинно бути лише 0-9 або “.”", + "SoftwareKeyboardModeAlphabet": "Повинно бути лише не CJK-символи", + "SoftwareKeyboardModeASCII": "Повинно бути лише ASCII текст", + "ControllerAppletControllers": "Підтримувані контролери:", + "ControllerAppletPlayers": "Players:", + "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", + "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", "UpdaterRenaming": "Перейменування старих файлів...", "UpdaterRenameFailed": "Програмі оновлення не вдалося перейменувати файл: {0}", "UpdaterAddingFiles": "Додавання нових файлів...", @@ -587,42 +593,45 @@ "Writable": "Можливість запису", "SelectDlcDialogTitle": "Виберіть файли DLC", "SelectUpdateDialogTitle": "Виберіть файли оновлення", + "SelectModDialogTitle": "Виберіть теку з модами", "UserProfileWindowTitle": "Менеджер профілів користувачів", "CheatWindowTitle": "Менеджер читів", "DlcWindowTitle": "Менеджер вмісту для завантаження", "UpdateWindowTitle": "Менеджер оновлення назв", "CheatWindowHeading": "Коди доступні для {0} [{1}]", - "BuildId": "BuildId:", + "BuildId": "ID збірки:", "DlcWindowHeading": "Вміст для завантаження, доступний для {1} ({2}): {0}", + "ModWindowHeading": "{0} мод(ів)", "UserProfilesEditProfile": "Редагувати вибране", "Cancel": "Скасувати", "Save": "Зберегти", "Discard": "Скасувати", + "Paused": "Призупинено", "UserProfilesSetProfileImage": "Встановити зображення профілю", - "UserProfileEmptyNameError": "Назва обов'язкова", - "UserProfileNoImageError": "Зображення профілю обов'язкове", + "UserProfileEmptyNameError": "Імʼя обовʼязкове", + "UserProfileNoImageError": "Зображення профілю обовʼязкове", "GameUpdateWindowHeading": "{0} Доступні оновлення для {1} ({2})", - "SettingsTabHotkeysResScaleUpHotkey": "Збільшити роздільну здатність:", - "SettingsTabHotkeysResScaleDownHotkey": "Зменшити роздільну здатність:", - "UserProfilesName": "Ім'я", + "SettingsTabHotkeysResScaleUpHotkey": "Збільшити роздільність:", + "SettingsTabHotkeysResScaleDownHotkey": "Зменшити роздільність:", + "UserProfilesName": "Імʼя", "UserProfilesUserId": "ID користувача:", "SettingsTabGraphicsBackend": "Графічний сервер", - "SettingsTabGraphicsBackendTooltip": "Графічний сервер для використання", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", "SettingsEnableTextureRecompression": "Увімкнути рекомпресію текстури", - "SettingsEnableTextureRecompressionTooltip": "Стискає певні текстури, щоб зменшити використання VRAM.\n\nРекомендовано для використання з графічними процесорами, які мають менш ніж 4 ГБ відеопам’яті.\n\nЗалиште вимкненим, якщо не впевнені.", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", "SettingsTabGraphicsPreferredGpu": "Бажаний GPU", "SettingsTabGraphicsPreferredGpuTooltip": "Виберіть відеокарту, яка використовуватиметься з графічним сервером Vulkan.\n\nНе впливає на графічний процесор, який використовуватиме OpenGL.\n\nЯкщо не впевнені, встановіть графічний процесор, позначений як «dGPU». Якщо такого немає, залиште це.", "SettingsAppRequiredRestartMessage": "Необхідно перезапустити Ryujinx", "SettingsGpuBackendRestartMessage": "Налаштування графічного сервера або GPU було змінено. Для цього знадобиться перезапуск", - "SettingsGpuBackendRestartSubMessage": "Ви хочете перезапустити зараз?", - "RyujinxUpdaterMessage": "Хочете оновити Ryujinx до останньої версії?", + "SettingsGpuBackendRestartSubMessage": "Бажаєте перезапустити зараз?", + "RyujinxUpdaterMessage": "Бажаєте оновити Ryujinx до останньої версії?", "SettingsTabHotkeysVolumeUpHotkey": "Збільшити гучність:", "SettingsTabHotkeysVolumeDownHotkey": "Зменшити гучність:", "SettingsEnableMacroHLE": "Увімкнути макрос HLE", "SettingsEnableMacroHLETooltip": "Високорівнева емуляція коду макросу GPU.\n\nПокращує продуктивність, але може викликати графічні збої в деяких іграх.\n\nЗалиште увімкненим, якщо не впевнені.", - "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", - "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", - "VolumeShort": "Гуч", + "SettingsEnableColorSpacePassthrough": "Наскрізний колірний простір", + "SettingsEnableColorSpacePassthroughTooltip": "Дозволяє серверу Vulkan передавати інформацію про колір без вказівки колірного простору. Для користувачів з екранами з широкою гамою це може призвести до більш яскравих кольорів, але шляхом втрати коректності передачі кольору.", + "VolumeShort": "Гуч.", "UserProfilesManageSaves": "Керувати збереженнями", "DeleteUserSave": "Ви хочете видалити збереження користувача для цієї гри?", "IrreversibleActionNote": "Цю дію не можна скасувати.", @@ -634,23 +643,26 @@ "UserProfilesRecoverLostAccounts": "Відновлення втрачених облікових записів", "Recover": "Відновити", "UserProfilesRecoverHeading": "Знайдено збереження для наступних облікових записів", - "UserProfilesRecoverEmptyList": "No profiles to recover", - "GraphicsAATooltip": "Applies anti-aliasing to the game render", - "GraphicsAALabel": "Anti-Aliasing:", - "GraphicsScalingFilterLabel": "Scaling Filter:", - "GraphicsScalingFilterTooltip": "Enables Framebuffer Scaling", - "GraphicsScalingFilterLevelLabel": "Level", - "GraphicsScalingFilterLevelTooltip": "Set Scaling Filter Level", - "SmaaLow": "SMAA Low", - "SmaaMedium": "SMAA Medium", - "SmaaHigh": "SMAA High", - "SmaaUltra": "SMAA Ultra", - "UserEditorTitle": "Edit User", - "UserEditorTitleCreate": "Create User", - "SettingsTabNetworkInterface": "Network Interface:", - "NetworkInterfaceTooltip": "The network interface used for LAN features", - "NetworkInterfaceDefault": "Default", - "PackagingShaders": "Packaging Shaders", - "AboutChangelogButton": "View Changelog on GitHub", - "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser." -} \ No newline at end of file + "UserProfilesRecoverEmptyList": "Немає профілів для відновлення", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", + "GraphicsAALabel": "Згладжування:", + "GraphicsScalingFilterLabel": "Фільтр масштабування:", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterLevelLabel": "Рівень", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", + "SmaaLow": "SMAA Низький", + "SmaaMedium": "SMAA Середній", + "SmaaHigh": "SMAA Високий", + "SmaaUltra": "SMAA Ультра", + "UserEditorTitle": "Редагувати користувача", + "UserEditorTitleCreate": "Створити користувача", + "SettingsTabNetworkInterface": "Мережевий інтерфейс:", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", + "NetworkInterfaceDefault": "Стандартний", + "PackagingShaders": "Пакування шейдерів", + "AboutChangelogButton": "Переглянути журнал змін на GitHub", + "AboutChangelogButtonTooltipMessage": "Клацніть, щоб відкрити журнал змін для цієї версії у стандартному браузері.", + "SettingsTabNetworkMultiplayer": "Мережева гра", + "MultiplayerMode": "Режим:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." +} diff --git a/src/Ryujinx/Assets/Locales/zh_CN.json b/src/Ryujinx/Assets/Locales/zh_CN.json index d09a80ec6..ce07385c1 100644 --- a/src/Ryujinx/Assets/Locales/zh_CN.json +++ b/src/Ryujinx/Assets/Locales/zh_CN.json @@ -4,14 +4,14 @@ "MenuBarFileOpenAppletOpenMiiAppletToolTip": "打开独立的 Mii 小程序", "SettingsTabInputDirectMouseAccess": "直通鼠标操作", "SettingsTabSystemMemoryManagerMode": "内存管理模式:", - "SettingsTabSystemMemoryManagerModeSoftware": "软件", - "SettingsTabSystemMemoryManagerModeHost": "本机 (快速)", - "SettingsTabSystemMemoryManagerModeHostUnchecked": "跳过检查的本机 (最快)", + "SettingsTabSystemMemoryManagerModeSoftware": "软件管理", + "SettingsTabSystemMemoryManagerModeHost": "本机映射 (较快)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "跳过检查的本机映射 (最快,但不安全)", "SettingsTabSystemUseHypervisor": "使用 Hypervisor 虚拟化", "MenuBarFile": "文件", - "MenuBarFileOpenFromFile": "加载文件", + "MenuBarFileOpenFromFile": "加载游戏文件", "MenuBarFileOpenUnpacked": "加载解包后的游戏", - "MenuBarFileOpenEmuFolder": "打开 Ryujinx 文件夹", + "MenuBarFileOpenEmuFolder": "打开 Ryujinx 系统文件夹", "MenuBarFileOpenLogsFolder": "打开日志文件夹", "MenuBarFileExit": "退出", "MenuBarOptions": "选项", @@ -51,14 +51,12 @@ "GameListContextMenuOpenBcatSaveDirectory": "打开 BCAT 目录", "GameListContextMenuOpenBcatSaveDirectoryToolTip": "打开包含游戏 BCAT 数据的目录", "GameListContextMenuManageTitleUpdates": "管理游戏更新", - "GameListContextMenuManageTitleUpdatesToolTip": "打开更新管理器", + "GameListContextMenuManageTitleUpdatesToolTip": "打开游戏更新管理窗口", "GameListContextMenuManageDlc": "管理 DLC", "GameListContextMenuManageDlcToolTip": "打开 DLC 管理窗口", - "GameListContextMenuOpenModsDirectory": "打开 MOD 目录", - "GameListContextMenuOpenModsDirectoryToolTip": "打开存放游戏 MOD 的目录", "GameListContextMenuCacheManagement": "缓存管理", "GameListContextMenuCacheManagementPurgePptc": "清除已编译的 PPTC 文件", - "GameListContextMenuCacheManagementPurgePptcToolTip": "仅删除 PPTC 转换后的文件,下次打开游戏时将根据 .info 文件重新生成 PPTC 文件。\n如想彻底清除缓存,请进入目录把 .info 文件一并删除", + "GameListContextMenuCacheManagementPurgePptcToolTip": "仅删除 PPTC 转换后的文件,下次打开游戏时将根据 .info 文件重新生成 PPTC 文件。\n如想彻底清除缓存,请进入CPU目录把 .info 文件一并删除", "GameListContextMenuCacheManagementPurgeShaderCache": "清除着色器缓存", "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "删除游戏的着色器缓存", "GameListContextMenuCacheManagementOpenPptcDirectory": "打开 PPTC 目录", @@ -72,25 +70,32 @@ "GameListContextMenuExtractDataRomFSToolTip": "从游戏的当前状态中提取 RomFS 分区 (包括更新)", "GameListContextMenuExtractDataLogo": "图标", "GameListContextMenuExtractDataLogoToolTip": "从游戏的当前状态中提取图标 (包括更新)", + "GameListContextMenuCreateShortcut": "创建游戏快捷方式", + "GameListContextMenuCreateShortcutToolTip": "创建一个直接启动此游戏的桌面快捷方式", + "GameListContextMenuCreateShortcutToolTipMacOS": "在 macOS 的应用程序文件夹中创建一个直接启动此游戏的快捷方式", + "GameListContextMenuOpenModsDirectory": "打开 MOD 目录", + "GameListContextMenuOpenModsDirectoryToolTip": "打开存放游戏 MOD 的目录", + "GameListContextMenuOpenSdModsDirectory": "打开大气层系统 MOD 目录", + "GameListContextMenuOpenSdModsDirectoryToolTip": "打开适用于大气层系统的游戏 MOD 目录,对于为真实硬件打包的 MOD 非常有用", "StatusBarGamesLoaded": "{0}/{1} 游戏加载完成", "StatusBarSystemVersion": "系统版本:{0}", - "LinuxVmMaxMapCountDialogTitle": "检测到内存映射的限制过低", - "LinuxVmMaxMapCountDialogTextPrimary": "你想要将 vm.max_map_count 的值增加到 {0} 吗", - "LinuxVmMaxMapCountDialogTextSecondary": "有些游戏可能会试图创建超过当前允许的内存映射数量。当超过此限制时,Ryujinx会立即崩溃。", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "确定,关闭后重置", + "LinuxVmMaxMapCountDialogTitle": "检测到操作系统内存映射最大数量被设置的过低", + "LinuxVmMaxMapCountDialogTextPrimary": "你想要将操作系统 vm.max_map_count 的值增加到 {0} 吗", + "LinuxVmMaxMapCountDialogTextSecondary": "有些游戏可能会尝试创建超过当前系统允许的内存映射最大数量,若超过当前最大数量,Ryujinx 模拟器将会闪退。", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "确定,临时保存(重启后失效)", "LinuxVmMaxMapCountDialogButtonPersistent": "确定,永久保存", "LinuxVmMaxMapCountWarningTextPrimary": "内存映射的最大数量低于推荐值。", - "LinuxVmMaxMapCountWarningTextSecondary": "vm.max_map_count ({0}) 的当前值小于 {1}。 有些游戏可能会试图创建超过当前允许的内存映射量。当大于此限制时,Ryujinx 会立即崩溃。\n\n你可以手动增加内存映射限制或者安装 pkexec,它可以辅助Ryujinx解决该问题。", + "LinuxVmMaxMapCountWarningTextSecondary": "vm.max_map_count ({0}) 的当前值小于 {1}。 有些游戏可能会尝试创建超过当前系统允许的内存映射最大数量,若超过当前最大数量,Ryujinx 模拟器将会闪退。\n\n你可以手动增加内存映射最大数量,或者安装 pkexec,它可以辅助 Ryujinx 完成内存映射最大数量的修改操作。", "Settings": "设置", "SettingsTabGeneral": "用户界面", "SettingsTabGeneralGeneral": "常规", "SettingsTabGeneralEnableDiscordRichPresence": "启用 Discord 在线状态展示", - "SettingsTabGeneralCheckUpdatesOnLaunch": "自动检查更新", - "SettingsTabGeneralShowConfirmExitDialog": "显示 \"确认退出\" 对话框", + "SettingsTabGeneralCheckUpdatesOnLaunch": "启动时检查更新", + "SettingsTabGeneralShowConfirmExitDialog": "退出游戏时需要确认", "SettingsTabGeneralHideCursor": "隐藏鼠标指针:", - "SettingsTabGeneralHideCursorNever": "从不", + "SettingsTabGeneralHideCursorNever": "从不隐藏", "SettingsTabGeneralHideCursorOnIdle": "自动隐藏", - "SettingsTabGeneralHideCursorAlways": "始终", + "SettingsTabGeneralHideCursorAlways": "始终隐藏", "SettingsTabGeneralGameDirectories": "游戏目录", "SettingsTabGeneralAdd": "添加", "SettingsTabGeneralRemove": "删除", @@ -106,18 +111,18 @@ "SettingsTabSystemSystemRegionTaiwan": "台湾地区", "SettingsTabSystemSystemLanguage": "系统语言:", "SettingsTabSystemSystemLanguageJapanese": "日语", - "SettingsTabSystemSystemLanguageAmericanEnglish": "美式英语", + "SettingsTabSystemSystemLanguageAmericanEnglish": "英语(美国)", "SettingsTabSystemSystemLanguageFrench": "法语", "SettingsTabSystemSystemLanguageGerman": "德语", "SettingsTabSystemSystemLanguageItalian": "意大利语", "SettingsTabSystemSystemLanguageSpanish": "西班牙语", - "SettingsTabSystemSystemLanguageChinese": "简体中文", + "SettingsTabSystemSystemLanguageChinese": "中文(简体)——无效", "SettingsTabSystemSystemLanguageKorean": "韩语", "SettingsTabSystemSystemLanguageDutch": "荷兰语", "SettingsTabSystemSystemLanguagePortuguese": "葡萄牙语", "SettingsTabSystemSystemLanguageRussian": "俄语", - "SettingsTabSystemSystemLanguageTaiwanese": "繁体中文(台湾)", - "SettingsTabSystemSystemLanguageBritishEnglish": "英式英语", + "SettingsTabSystemSystemLanguageTaiwanese": "中文(繁体)——无效", + "SettingsTabSystemSystemLanguageBritishEnglish": "英语(英国)", "SettingsTabSystemSystemLanguageCanadianFrench": "加拿大法语", "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "拉美西班牙语", "SettingsTabSystemSystemLanguageSimplifiedChinese": "简体中文(推荐)", @@ -126,15 +131,15 @@ "SettingsTabSystemSystemTime": "系统时钟:", "SettingsTabSystemEnableVsync": "启用垂直同步", "SettingsTabSystemEnablePptc": "开启 PPTC 缓存", - "SettingsTabSystemEnableFsIntegrityChecks": "文件系统完整性检查", - "SettingsTabSystemAudioBackend": "音频后端:", + "SettingsTabSystemEnableFsIntegrityChecks": "启用文件系统完整性检查", + "SettingsTabSystemAudioBackend": "音频处理引擎:", "SettingsTabSystemAudioBackendDummy": "无", "SettingsTabSystemAudioBackendOpenAL": "OpenAL", - "SettingsTabSystemAudioBackendSoundIO": "音频输入/输出", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSDL2": "SDL2", - "SettingsTabSystemHacks": "修正", - "SettingsTabSystemHacksNote": "(会引起模拟器不稳定)", - "SettingsTabSystemExpandDramSize": "使用开发机的内存布局", + "SettingsTabSystemHacks": "修改", + "SettingsTabSystemHacksNote": "会导致模拟器不稳定", + "SettingsTabSystemExpandDramSize": "使用开发机的内存布局(开发人员使用)", "SettingsTabSystemIgnoreMissingServices": "忽略缺失的服务", "SettingsTabGraphics": "图形", "SettingsTabGraphicsAPI": "图形 API", @@ -148,9 +153,9 @@ "SettingsTabGraphicsResolutionScale": "分辨率缩放:", "SettingsTabGraphicsResolutionScaleCustom": "自定义(不推荐)", "SettingsTabGraphicsResolutionScaleNative": "原生 (720p/1080p)", - "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", - "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsResolutionScale2x": "2 倍 (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3 倍 (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4 倍 (2880p/4320p) (不推荐)", "SettingsTabGraphicsAspectRatio": "宽高比:", "SettingsTabGraphicsAspectRatio4x3": "4:3", "SettingsTabGraphicsAspectRatio16x9": "16:9", @@ -162,18 +167,18 @@ "SettingsTabGraphicsShaderDumpPath": "图形着色器转储路径:", "SettingsTabLogging": "日志", "SettingsTabLoggingLogging": "日志", - "SettingsTabLoggingEnableLoggingToFile": "保存日志为文件", - "SettingsTabLoggingEnableStubLogs": "启用 Stub 日志", + "SettingsTabLoggingEnableLoggingToFile": "将日志写入文件", + "SettingsTabLoggingEnableStubLogs": "启用存根日志", "SettingsTabLoggingEnableInfoLogs": "启用信息日志", "SettingsTabLoggingEnableWarningLogs": "启用警告日志", "SettingsTabLoggingEnableErrorLogs": "启用错误日志", "SettingsTabLoggingEnableTraceLogs": "启用跟踪日志", - "SettingsTabLoggingEnableGuestLogs": "启用来宾日志", - "SettingsTabLoggingEnableFsAccessLogs": "启用访问日志", - "SettingsTabLoggingFsGlobalAccessLogMode": "全局访问日志模式:", + "SettingsTabLoggingEnableGuestLogs": "启用访客日志", + "SettingsTabLoggingEnableFsAccessLogs": "启用文件访问日志", + "SettingsTabLoggingFsGlobalAccessLogMode": "文件系统全局访问日志模式:", "SettingsTabLoggingDeveloperOptions": "开发者选项", - "SettingsTabLoggingDeveloperOptionsNote": "警告:会降低性能", - "SettingsTabLoggingGraphicsBackendLogLevel": "图形后端日志级别:", + "SettingsTabLoggingDeveloperOptionsNote": "警告:会降低模拟器性能", + "SettingsTabLoggingGraphicsBackendLogLevel": "图形引擎日志级别:", "SettingsTabLoggingGraphicsBackendLogLevelNone": "无", "SettingsTabLoggingGraphicsBackendLogLevelError": "错误", "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "减速", @@ -183,7 +188,7 @@ "SettingsTabInputEnableDockedMode": "主机模式", "SettingsTabInputDirectKeyboardAccess": "直通键盘控制", "SettingsButtonSave": "保存", - "SettingsButtonClose": "取消", + "SettingsButtonClose": "关闭", "SettingsButtonOk": "确定", "SettingsButtonCancel": "取消", "SettingsButtonApply": "应用", @@ -199,19 +204,19 @@ "ControllerSettingsHandheld": "掌机模式", "ControllerSettingsInputDevice": "输入设备", "ControllerSettingsRefresh": "刷新", - "ControllerSettingsDeviceDisabled": "关闭", + "ControllerSettingsDeviceDisabled": "禁用", "ControllerSettingsControllerType": "手柄类型", "ControllerSettingsControllerTypeHandheld": "掌机", "ControllerSettingsControllerTypeProController": "Pro 手柄", - "ControllerSettingsControllerTypeJoyConPair": "JoyCon 组合", - "ControllerSettingsControllerTypeJoyConLeft": "左 JoyCon", - "ControllerSettingsControllerTypeJoyConRight": "右 JoyCon", - "ControllerSettingsProfile": "预设", - "ControllerSettingsProfileDefault": "默认布局", + "ControllerSettingsControllerTypeJoyConPair": "双 JoyCon 手柄", + "ControllerSettingsControllerTypeJoyConLeft": "左 JoyCon 手柄", + "ControllerSettingsControllerTypeJoyConRight": "右 JoyCon 手柄", + "ControllerSettingsProfile": "配置文件", + "ControllerSettingsProfileDefault": "默认设置", "ControllerSettingsLoad": "加载", "ControllerSettingsAdd": "新建", "ControllerSettingsRemove": "删除", - "ControllerSettingsButtons": "按键", + "ControllerSettingsButtons": "基础按键", "ControllerSettingsButtonA": "A", "ControllerSettingsButtonB": "B", "ControllerSettingsButtonX": "X", @@ -229,8 +234,8 @@ "ControllerSettingsStickLeft": "左", "ControllerSettingsStickRight": "右", "ControllerSettingsStickStick": "摇杆", - "ControllerSettingsStickInvertXAxis": "反转 X 轴方向", - "ControllerSettingsStickInvertYAxis": "反转 Y 轴方向", + "ControllerSettingsStickInvertXAxis": "摇杆左右反转", + "ControllerSettingsStickInvertYAxis": "摇杆上下反转", "ControllerSettingsStickDeadzone": "死区:", "ControllerSettingsLStick": "左摇杆", "ControllerSettingsRStick": "右摇杆", @@ -252,28 +257,28 @@ "ControllerSettingsMisc": "其他", "ControllerSettingsTriggerThreshold": "扳机阈值:", "ControllerSettingsMotion": "体感", - "ControllerSettingsMotionUseCemuhookCompatibleMotion": "使用 CemuHook 体感协议", - "ControllerSettingsMotionControllerSlot": "手柄:", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "使用 CemuHook 兼容的体感协议", + "ControllerSettingsMotionControllerSlot": "手柄槽位:", "ControllerSettingsMotionMirrorInput": "镜像操作", - "ControllerSettingsMotionRightJoyConSlot": "右JoyCon:", - "ControllerSettingsMotionServerHost": "服务器Host:", + "ControllerSettingsMotionRightJoyConSlot": "右 JoyCon 槽位:", + "ControllerSettingsMotionServerHost": "服务器地址:", "ControllerSettingsMotionGyroSensitivity": "陀螺仪敏感度:", "ControllerSettingsMotionGyroDeadzone": "陀螺仪死区:", "ControllerSettingsSave": "保存", "ControllerSettingsClose": "关闭", - "UserProfilesSelectedUserProfile": "选择的用户账户:", + "UserProfilesSelectedUserProfile": "选定的用户账户:", "UserProfilesSaveProfileName": "保存名称", "UserProfilesChangeProfileImage": "更换头像", - "UserProfilesAvailableUserProfiles": "现有账户:", + "UserProfilesAvailableUserProfiles": "现有用户账户:", "UserProfilesAddNewProfile": "新建账户", "UserProfilesDelete": "删除", "UserProfilesClose": "关闭", - "ProfileNameSelectionWatermark": "选择昵称", + "ProfileNameSelectionWatermark": "输入昵称", "ProfileImageSelectionTitle": "选择头像", "ProfileImageSelectionHeader": "选择合适的头像图片", - "ProfileImageSelectionNote": "您可以导入自定义头像,或从系统中选择头像", + "ProfileImageSelectionNote": "您可以导入自定义头像,或从模拟器系统中选择预设头像", "ProfileImageSelectionImportImage": "导入图像文件", - "ProfileImageSelectionSelectAvatar": "选择系统头像", + "ProfileImageSelectionSelectAvatar": "选择预设头像", "InputDialogTitle": "输入对话框", "InputDialogOk": "完成", "InputDialogCancel": "取消", @@ -283,121 +288,120 @@ "AvatarChoose": "选择头像", "AvatarSetBackgroundColor": "设置背景色", "AvatarClose": "关闭", - "ControllerSettingsLoadProfileToolTip": "加载预设", - "ControllerSettingsAddProfileToolTip": "新增预设", - "ControllerSettingsRemoveProfileToolTip": "删除预设", - "ControllerSettingsSaveProfileToolTip": "保存预设", - "MenuBarFileToolsTakeScreenshot": "保存截图", - "MenuBarFileToolsHideUi": "隐藏界面", - "GameListContextMenuRunApplication": "运行应用", + "ControllerSettingsLoadProfileToolTip": "加载配置文件", + "ControllerSettingsAddProfileToolTip": "新增配置文件", + "ControllerSettingsRemoveProfileToolTip": "删除配置文件", + "ControllerSettingsSaveProfileToolTip": "保存配置文件", + "MenuBarFileToolsTakeScreenshot": "保存截屏", + "MenuBarFileToolsHideUi": "隐藏菜单栏和状态栏", + "GameListContextMenuRunApplication": "启动游戏", "GameListContextMenuToggleFavorite": "收藏", - "GameListContextMenuToggleFavoriteToolTip": "标记喜爱的游戏", - "SettingsTabGeneralTheme": "主题", - "SettingsTabGeneralThemeCustomTheme": "自选主题路径", - "SettingsTabGeneralThemeBaseStyle": "主题色调", - "SettingsTabGeneralThemeBaseStyleDark": "暗黑", - "SettingsTabGeneralThemeBaseStyleLight": "浅色", - "SettingsTabGeneralThemeEnableCustomTheme": "使用自选主题界面", - "ButtonBrowse": "浏览", + "GameListContextMenuToggleFavoriteToolTip": "切换游戏的收藏状态", + "SettingsTabGeneralTheme": "主题︰", + "SettingsTabGeneralThemeDark": "深色(暗黑)", + "SettingsTabGeneralThemeLight": "浅色(亮色)", "ControllerSettingsConfigureGeneral": "配置", "ControllerSettingsRumble": "震动", "ControllerSettingsRumbleStrongMultiplier": "强震动幅度", "ControllerSettingsRumbleWeakMultiplier": "弱震动幅度", "DialogMessageSaveNotAvailableMessage": "没有{0} [{1:x16}]的游戏存档", "DialogMessageSaveNotAvailableCreateSaveMessage": "是否创建该游戏的存档文件夹?", - "DialogConfirmationTitle": "Ryujinx - 设置", + "DialogConfirmationTitle": "Ryujinx - 确认", "DialogUpdaterTitle": "Ryujinx - 更新", "DialogErrorTitle": "Ryujinx - 错误", "DialogWarningTitle": "Ryujinx - 警告", - "DialogExitTitle": "Ryujinx - 关闭", - "DialogErrorMessage": "Ryujinx 发生错误", - "DialogExitMessage": "是否关闭 Ryujinx?", + "DialogExitTitle": "Ryujinx - 退出", + "DialogErrorMessage": "Ryujinx 模拟器发生错误", + "DialogExitMessage": "是否关闭 Ryujinx 模拟器?", "DialogExitSubMessage": "未保存的进度将会丢失!", - "DialogMessageCreateSaveErrorMessage": "创建特定的存档时出错:{0}", - "DialogMessageFindSaveErrorMessage": "查找特定的存档时出错:{0}", - "FolderDialogExtractTitle": "选择要解压到的文件夹", + "DialogMessageCreateSaveErrorMessage": "创建指定存档时出错:{0}", + "DialogMessageFindSaveErrorMessage": "查找指定存档时出错:{0}", + "FolderDialogExtractTitle": "选择要提取到的文件夹", "DialogNcaExtractionMessage": "提取 {1} 的 {0} 分区...", - "DialogNcaExtractionTitle": "Ryujinx - NCA分区提取", - "DialogNcaExtractionMainNcaNotFoundErrorMessage": "提取失败。所选文件中不含主NCA文件", - "DialogNcaExtractionCheckLogErrorMessage": "提取失败。请查看日志文件获取详情。", - "DialogNcaExtractionSuccessMessage": "提取成功。", - "DialogUpdaterConvertFailedMessage": "无法转换当前 Ryujinx 版本。", - "DialogUpdaterCancelUpdateMessage": "更新取消!", - "DialogUpdaterAlreadyOnLatestVersionMessage": "您使用的 Ryujinx 是最新版本。", - "DialogUpdaterFailedToGetVersionMessage": "尝试从 Github 获取版本信息时无效。\n可能由于 GitHub Actions 正在编译新版本。请过一会再试。", - "DialogUpdaterConvertFailedGithubMessage": "无法转换从 Github 接收到的 Ryujinx 版本。", - "DialogUpdaterDownloadingMessage": "下载新版本中...", + "DialogNcaExtractionTitle": "Ryujinx - NCA 分区提取", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "提取失败,所选文件中没有 NCA 文件", + "DialogNcaExtractionCheckLogErrorMessage": "提取失败,请查看日志文件获取详情", + "DialogNcaExtractionSuccessMessage": "提取成功!", + "DialogUpdaterConvertFailedMessage": "无法切换当前 Ryujinx 版本。", + "DialogUpdaterCancelUpdateMessage": "取消更新!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "您使用的 Ryujinx 模拟器是最新版本。", + "DialogUpdaterFailedToGetVersionMessage": "尝试从 Github 获取版本信息时无效,可能由于 GitHub Actions 正在编译新版本。\n请过一会再试。", + "DialogUpdaterConvertFailedGithubMessage": "无法切换至从 Github 接收到的新版 Ryujinx 模拟器。", + "DialogUpdaterDownloadingMessage": "下载更新中...", "DialogUpdaterExtractionMessage": "正在提取更新...", - "DialogUpdaterRenamingMessage": "正在删除旧文件...", + "DialogUpdaterRenamingMessage": "正在重命名更新...", "DialogUpdaterAddingFilesMessage": "安装更新中...", "DialogUpdaterCompleteMessage": "更新成功!", - "DialogUpdaterRestartMessage": "立即重启 Ryujinx 完成更新?", - "DialogUpdaterArchNotSupportedMessage": "您运行的系统架构不受支持!", - "DialogUpdaterArchNotSupportedSubMessage": "(仅支持 x64 系统)", - "DialogUpdaterNoInternetMessage": "没有连接到互联网", + "DialogUpdaterRestartMessage": "是否立即重启 Ryujinx 模拟器?", + "DialogUpdaterNoInternetMessage": "没有连接到网络", "DialogUpdaterNoInternetSubMessage": "请确保互联网连接正常。", - "DialogUpdaterDirtyBuildMessage": "不能更新非官方版本的 Ryujinx!", - "DialogUpdaterDirtyBuildSubMessage": "如果希望使用受支持的版本,请您在 https://ryujinx.org/ 下载。", + "DialogUpdaterDirtyBuildMessage": "无法更新非官方版本的 Ryujinx 模拟器!", + "DialogUpdaterDirtyBuildSubMessage": "如果想使用受支持的版本,请您在 https://ryujinx.org/ 下载官方版本。", "DialogRestartRequiredMessage": "需要重启模拟器", - "DialogThemeRestartMessage": "主题设置已保存。需要重新启动才能生效。", - "DialogThemeRestartSubMessage": "您是否要重启?", - "DialogFirmwareInstallEmbeddedMessage": "要安装游戏内置的固件吗?(固件 {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "未找到已安装的固件,但 Ryujinx 可以从现有的游戏安装固件{0}.\n模拟器现在可以运行。", + "DialogThemeRestartMessage": "主题设置已保存,需要重启模拟器才能生效。", + "DialogThemeRestartSubMessage": "是否要重启模拟器?", + "DialogFirmwareInstallEmbeddedMessage": "要安装游戏内置的系统固件吗?(固件 {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "未找到已安装的固件,Ryujinx 已经从当前游戏中安装了系统固件 {0} 。\n模拟器现在可以运行。", "DialogFirmwareNoFirmwareInstalledMessage": "未安装固件", "DialogFirmwareInstalledMessage": "已安装固件 {0}", "DialogInstallFileTypesSuccessMessage": "关联文件类型成功!", - "DialogInstallFileTypesErrorMessage": "关联文件类型失败。", + "DialogInstallFileTypesErrorMessage": "关联文件类型失败!", "DialogUninstallFileTypesSuccessMessage": "成功解除文件类型关联!", - "DialogUninstallFileTypesErrorMessage": "解除文件类型关联失败。", + "DialogUninstallFileTypesErrorMessage": "解除文件类型关联失败!", "DialogOpenSettingsWindowLabel": "打开设置窗口", "DialogControllerAppletTitle": "控制器小窗口", "DialogMessageDialogErrorExceptionMessage": "显示消息对话框时出错:{0}", "DialogSoftwareKeyboardErrorExceptionMessage": "显示软件键盘时出错:{0}", "DialogErrorAppletErrorExceptionMessage": "显示错误对话框时出错:{0}", "DialogUserErrorDialogMessage": "{0}: {1}", - "DialogUserErrorDialogInfoMessage": "\n有关修复此错误的更多信息,可以遵循我们的设置指南。", + "DialogUserErrorDialogInfoMessage": "\n有关修复此错误的更多信息,可以遵循我们的安装指南。", "DialogUserErrorDialogTitle": "Ryujinx 错误 ({0})", "DialogAmiiboApiTitle": "Amiibo API", "DialogAmiiboApiFailFetchMessage": "从 API 获取信息时出错。", - "DialogAmiiboApiConnectErrorMessage": "无法连接到 Amiibo API 服务器。服务器可能已关闭,或者您没有连接网络。", - "DialogProfileInvalidProfileErrorMessage": "预设 {0} 与当前输入配置系统不兼容。", - "DialogProfileDefaultProfileOverwriteErrorMessage": "默认预设不能被覆盖", - "DialogProfileDeleteProfileTitle": "删除预设", + "DialogAmiiboApiConnectErrorMessage": "无法连接到 Amiibo API 服务器,服务可能已关闭,或者没有连接互联网。", + "DialogProfileInvalidProfileErrorMessage": "配置文件 {0} 与当前输入配置系统不兼容。", + "DialogProfileDefaultProfileOverwriteErrorMessage": "不允许覆盖默认配置文件", + "DialogProfileDeleteProfileTitle": "删除配置文件", "DialogProfileDeleteProfileMessage": "删除后不可恢复,确认删除吗?", "DialogWarning": "警告", "DialogPPTCDeletionMessage": "您即将删除:\n\n{0} 的 PPTC 缓存\n\n确定吗?", - "DialogPPTCDeletionErrorMessage": "清除位于 {0} 的 PPTC 缓存时出错:{1}", + "DialogPPTCDeletionErrorMessage": "清除 {0} 的 PPTC 缓存时出错:{1}", "DialogShaderDeletionMessage": "您即将删除:\n\n{0} 的着色器缓存\n\n确定吗?", - "DialogShaderDeletionErrorMessage": "清除位于 {0} 的着色器缓存时出错:{1}", - "DialogRyujinxErrorMessage": "Ryujinx 遇到错误", - "DialogInvalidTitleIdErrorMessage": "UI错误:所选游戏没有有效的标题ID", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "路径 {0} 找不到有效的系统固件。", - "DialogFirmwareInstallerFirmwareInstallTitle": "固件 {0}", + "DialogShaderDeletionErrorMessage": "清除 {0} 的着色器缓存时出错:{1}", + "DialogRyujinxErrorMessage": "Ryujinx 模拟器发生错误", + "DialogInvalidTitleIdErrorMessage": "用户界面错误:所选游戏没有有效的游戏 ID", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "在路径 {0} 中找不到有效的系统固件。", + "DialogFirmwareInstallerFirmwareInstallTitle": "安装固件 {0}", "DialogFirmwareInstallerFirmwareInstallMessage": "即将安装系统版本 {0} 。", - "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n会替换当前系统版本 {0} 。", - "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n是否确认继续?", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n替换当前系统版本 {0} 。", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n是否继续?", "DialogFirmwareInstallerFirmwareInstallWaitMessage": "安装固件中...", "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "成功安装系统版本 {0} 。", - "DialogUserProfileDeletionWarningMessage": "删除后将没有可选择的用户账户", - "DialogUserProfileDeletionConfirmMessage": "是否删除选择的账户", - "DialogUserProfileUnsavedChangesTitle": "警告 - 未保存的更改", - "DialogUserProfileUnsavedChangesMessage": "您为该用户做出的部分改动尚未保存。", - "DialogUserProfileUnsavedChangesSubMessage": "是否舍弃这些改动?", - "DialogControllerSettingsModifiedConfirmMessage": "目前的输入预设已更新", - "DialogControllerSettingsModifiedConfirmSubMessage": "要保存吗?", - "DialogLoadNcaErrorMessage": "{0}. 错误的文件:{1}", - "DialogDlcNoDlcErrorMessage": "选择的文件不包含所选游戏的 DLC!", - "DialogPerformanceCheckLoggingEnabledMessage": "您启用了跟踪日志,仅供开发人员使用。", + "DialogUserProfileDeletionWarningMessage": "删除后将没有可用的账户", + "DialogUserProfileDeletionConfirmMessage": "是否删除所选账户", + "DialogUserProfileUnsavedChangesTitle": "警告 - 有未保存的更改", + "DialogUserProfileUnsavedChangesMessage": "您对该账户的更改尚未保存。", + "DialogUserProfileUnsavedChangesSubMessage": "确定要放弃更改吗?", + "DialogControllerSettingsModifiedConfirmMessage": "当前的输入设置已更新", + "DialogControllerSettingsModifiedConfirmSubMessage": "是否保存?", + "DialogLoadFileErrorMessage": "{0}. 错误的文件:{1}", + "DialogModAlreadyExistsMessage": "MOD 已存在", + "DialogModInvalidMessage": "指定的目录找不到 MOD!", + "DialogModDeleteNoParentMessage": "删除失败:找不到 MOD 的父目录“{0}”!", + "DialogDlcNoDlcErrorMessage": "选择的文件不是当前游戏的 DLC!", + "DialogPerformanceCheckLoggingEnabledMessage": "您启用了跟踪日志,该功能仅供开发人员使用。", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "为了获得最佳性能,建议禁用跟踪日志记录。您是否要立即禁用?", - "DialogPerformanceCheckShaderDumpEnabledMessage": "您启用了着色器转储,仅供开发人员使用。", + "DialogPerformanceCheckShaderDumpEnabledMessage": "您启用了着色器转储,该功能仅供开发人员使用。", "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "为了获得最佳性能,建议禁用着色器转储。您是否要立即禁用?", - "DialogLoadAppGameAlreadyLoadedMessage": "已有游戏正在运行", - "DialogLoadAppGameAlreadyLoadedSubMessage": "请停止模拟或关闭程序,再启动另一个游戏。", - "DialogUpdateAddUpdateErrorMessage": "选择的文件不包含所选游戏的更新!", - "DialogSettingsBackendThreadingWarningTitle": "警告 - 后端多线程", - "DialogSettingsBackendThreadingWarningMessage": "改变此选项后必须重启 Ryujinx 才能生效。\n\n取决于您的硬件,可能需要手动禁用驱动面板中的线程优化。", + "DialogLoadAppGameAlreadyLoadedMessage": "游戏已经启动", + "DialogLoadAppGameAlreadyLoadedSubMessage": "请停止模拟或关闭模拟器,再启动另一个游戏。", + "DialogUpdateAddUpdateErrorMessage": "选择的文件不是当前游戏的更新!", + "DialogSettingsBackendThreadingWarningTitle": "警告 - 图形引擎多线程", + "DialogSettingsBackendThreadingWarningMessage": "更改此选项后,必须重启 Ryujinx 模拟器才能生效。\n\n当启用图形引擎多线程时,根据显卡不同,您可能需要手动禁用显卡驱动程序自身的多线程(线程优化)。", + "DialogModManagerDeletionWarningMessage": "您即将删除 MOD:{0} \n\n确定吗?", + "DialogModManagerDeletionAllWarningMessage": "您即将删除该游戏的所有 MOD,\n\n确定吗?", "SettingsTabGraphicsFeaturesOptions": "功能", - "SettingsTabGraphicsBackendMultithreading": "多线程图形后端:", + "SettingsTabGraphicsBackendMultithreading": "图形引擎多线程:", "CommonAuto": "自动(推荐)", "CommonOff": "关闭", "CommonOn": "打开", @@ -406,8 +410,8 @@ "DialogProfileInvalidProfileNameErrorMessage": "文件名包含无效字符,请重试。", "MenuBarOptionsPauseEmulation": "暂停", "MenuBarOptionsResumeEmulation": "继续", - "AboutUrlTooltipMessage": "在浏览器中打开 Ryujinx 官网。", - "AboutDisclaimerMessage": "Ryujinx 以任何方式与 Nintendo™ 及其任何商业伙伴都没有关联", + "AboutUrlTooltipMessage": "在浏览器中打开 Ryujinx 模拟器官网。", + "AboutDisclaimerMessage": "Ryujinx 与 Nintendo™ 以及其合作伙伴没有任何关联。", "AboutAmiiboDisclaimerMessage": "我们的 Amiibo 模拟使用了\nAmiiboAPI (www.amiiboapi.com) ", "AboutPatreonUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Patreon 赞助页。", "AboutGithubUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 GitHub 代码库。", @@ -415,216 +419,221 @@ "AboutTwitterUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Twitter 主页。", "AboutRyujinxAboutTitle": "关于:", "AboutRyujinxAboutContent": "Ryujinx 是一款 Nintendo Switch™ 模拟器。\n您可以在 Patreon 上赞助 Ryujinx。\n关注 Twitter 或 Discord 可以获取模拟器最新动态。\n如果您对开发感兴趣,欢迎来 GitHub 或 Discord 加入我们!", - "AboutRyujinxMaintainersTitle": "由以下作者维护:", + "AboutRyujinxMaintainersTitle": "开发维护人员名单:", "AboutRyujinxMaintainersContentTooltipMessage": "在浏览器中打开贡献者页面", - "AboutRyujinxSupprtersTitle": "感谢 Patreon 的赞助者:", + "AboutRyujinxSupprtersTitle": "感谢 Patreon 上的赞助者:", "AmiiboSeriesLabel": "Amiibo 系列", "AmiiboCharacterLabel": "角色", "AmiiboScanButtonLabel": "扫描", - "AmiiboOptionsShowAllLabel": "显示所有 Amiibo 系列", - "AmiiboOptionsUsRandomTagLabel": "修复:使用随机标记的 UUID", - "DlcManagerTableHeadingEnabledLabel": "启用", - "DlcManagerTableHeadingTitleIdLabel": "游戏ID", + "AmiiboOptionsShowAllLabel": "显示所有 Amiibo", + "AmiiboOptionsUsRandomTagLabel": "修改:使用随机生成的Amiibo ID", + "DlcManagerTableHeadingEnabledLabel": "已启用", + "DlcManagerTableHeadingTitleIdLabel": "游戏 ID", "DlcManagerTableHeadingContainerPathLabel": "文件夹路径", "DlcManagerTableHeadingFullPathLabel": "完整路径", "DlcManagerRemoveAllButton": "全部删除", "DlcManagerEnableAllButton": "全部启用", - "DlcManagerDisableAllButton": "全部禁用", - "MenuBarOptionsChangeLanguage": "更改语言", + "DlcManagerDisableAllButton": "全部停用", + "ModManagerDeleteAllButton": "全部刪除", + "MenuBarOptionsChangeLanguage": "更改界面语言", "MenuBarShowFileTypes": "主页显示的文件类型", "CommonSort": "排序", "CommonShowNames": "显示名称", "CommonFavorite": "收藏", "OrderAscending": "从小到大", "OrderDescending": "从大到小", - "SettingsTabGraphicsFeatures": "功能与增强", + "SettingsTabGraphicsFeatures": "功能与优化", "ErrorWindowTitle": "错误窗口", - "ToggleDiscordTooltip": "控制是否在 Discord 中显示您的游玩状态", + "ToggleDiscordTooltip": "选择是否在 Discord 中显示您的游玩状态", "AddGameDirBoxTooltip": "输入要添加的游戏目录", "AddGameDirTooltip": "添加游戏目录到列表中", "RemoveGameDirTooltip": "移除选中的目录", - "CustomThemeCheckTooltip": "使用自定义UI主题来更改模拟器的外观样式", + "CustomThemeCheckTooltip": "使用自定义的 Avalonia 主题作为模拟器菜单的外观", "CustomThemePathTooltip": "自定义主题的目录", "CustomThemeBrowseTooltip": "查找自定义主题", - "DockModeToggleTooltip": "启用 Switch 的主机模式。\n绝大多数游戏画质会提高,略微增加性能消耗。\n在掌机和主机模式切换的过程中,您可能需要重新设置手柄类型。", - "DirectKeyboardTooltip": "开启 \"直连键盘访问(HID)支持\"\n(部分游戏可以使用您的键盘输入文字)", - "DirectMouseTooltip": "开启 \"直连鼠标访问(HID)支持\"\n(部分游戏可以使用您的鼠标导航)", + "DockModeToggleTooltip": "启用 Switch 的主机模式,可以模拟 Switch 连接底座的情况,此时绝大多数游戏画质会提高,略微增加性能消耗。\n若禁用主机模式,则使用 Switch 的掌机模式,可以模拟手持 Switch 运行游戏的情况,游戏画质会降低,性能消耗也会降低。\n\n如果使用主机模式,请选择“玩家 1”的手柄设置;如果使用掌机模式,请选择“掌机模式”的手柄设置。\n\n如果不确定,请保持开启状态。", + "DirectKeyboardTooltip": "直接键盘访问(HID)支持,游戏可以直接访问键盘作为文本输入设备。\n\n仅适用于在 Switch 硬件上原生支持键盘的游戏。\n\n如果不确定,请保持关闭状态。", + "DirectMouseTooltip": "直接鼠标访问(HID)支持,游戏可以直接访问鼠标作为指针输入设备。\n\n只适用于在 Switch 硬件上原生支持鼠标控制的游戏,这种游戏很少。\n\n启用后,触屏功能可能无法正常工作。\n\n如果不确定,请保持关闭状态。", "RegionTooltip": "更改系统区域", "LanguageTooltip": "更改系统语言", "TimezoneTooltip": "更改系统时区", - "TimeTooltip": "更改系统时钟", - "VSyncToggleTooltip": "关闭后,小部分游戏可以超过60FPS帧率,以获得高帧率体验。\n但是可能出现软锁或读盘时间增加。\n如不确定,就请保持开启状态。", - "PptcToggleTooltip": "缓存编译完成的游戏CPU指令。减少启动时间和卡顿,提高游戏响应速度。\n如不确定,就请保持开启状态。", - "FsIntegrityToggleTooltip": "检查游戏文件内容的完整性。\n遇到损坏的文件则记录到日志文件,有助于排查错误。\n对性能没有影响。\n如不确定,就请保持开启状态。", - "AudioBackendTooltip": "默认推荐SDL2,但每种音频后端对各类游戏兼容性不同,遇到音频问题可以尝试切换后端。", - "MemoryManagerTooltip": "改变 Switch 内存映射到电脑内存的方式,会影响CPU性能消耗。", - "MemoryManagerSoftwareTooltip": "使用软件内存页管理,最精确但是速度最慢。", - "MemoryManagerHostTooltip": "直接映射内存页到电脑内存,使得即时编译效率更高。", - "MemoryManagerUnsafeTooltip": "直接映射内存页,但不检查内存溢出,使得即时编译效率更高。\nRyujinx 可以访问任何位置的内存,因而相对不安全。\n此模式下只应运行您信任的游戏或软件(即官方游戏)。", - "UseHypervisorTooltip": "使用 Hypervisor 虚拟机代替即时编译。在可用的情况下能大幅提高性能。但目前可能不稳定。", - "DRamTooltip": "使用Switch开发机的内存布局。\n不会提高任何性能,某些高清纹理包或 4k 分辨率 MOD 可能需要此选项。\n如果不确定,请始终关闭该选项。", - "IgnoreMissingServicesTooltip": "开启后,游戏会忽略未实现的系统服务,从而继续运行。\n少部分新发布的游戏由于使用新的未知系统服务,可能需要此选项来避免闪退。\n模拟器更新完善系统服务之后,则无需开启选项。\n如您的游戏已经正常运行,请保持此选项关闭。", - "GraphicsBackendThreadingTooltip": "在第二个线程上执行图形后端命令。\n\n加速着色器编译,减少卡顿,提高 GPU 的性能。\n\n如果不确定,请设置为自动。", - "GalThreadingTooltip": "在第二个线程上执行图形后端命令。\n\n加速着色器编译,减少卡顿,提高 GPU 的性能。\n\n如果不确定,请设置为自动。", - "ShaderCacheToggleTooltip": "开启后,模拟器会保存编译完成的着色器到磁盘,减少游戏渲染新特效和场景时的卡顿。", - "ResolutionScaleTooltip": "缩放渲染的分辨率", - "ResolutionScaleEntryTooltip": "尽可能使用例如1.5的浮点倍数。非整数的倍率易引起 BUG。", - "AnisotropyTooltip": "各向异性过滤等级。提高倾斜视角纹理的清晰度\n('自动'使用游戏默认的等级)", - "AspectRatioTooltip": "渲染窗口的宽高比。", + "TimeTooltip": "更改系统时间", + "VSyncToggleTooltip": "模拟控制台的垂直同步,开启后会降低大部分游戏的帧率。关闭后,可以获得更高的帧率,但也可能导致游戏画面加载耗时更长或卡住。\n\n在游戏中可以使用热键进行切换(默认为 F1 键)。\n\n如果不确定,请保持开启状态。", + "PptcToggleTooltip": "缓存已编译的游戏指令,这样每次游戏加载时就无需重新编译。\n\n减少卡顿和启动时间,提高游戏响应速度。\n\n如果不确定,请保持开启状态。", + "FsIntegrityToggleTooltip": "启动游戏时检查游戏文件的完整性,并在日志中记录损坏的文件。\n\n对性能没有影响,用于排查故障。\n\n如果不确定,请保持开启状态。", + "AudioBackendTooltip": "更改音频处理引擎。\n\n推荐选择“SDL2”,另外“OpenAL”和“SoundIO”可以作为备选,选择“无”将没有声音。\n\n如果不确定,请设置为“SDL2”。", + "MemoryManagerTooltip": "更改模拟器内存映射和访问的方式,对模拟器CPU的性能影响很大。\n\n如果不确定,请设置为“跳过检查的本机映射”。", + "MemoryManagerSoftwareTooltip": "使用软件内存页管理,最准确但是速度最慢。", + "MemoryManagerHostTooltip": "直接映射内存页到电脑内存,使得即时编译和执行的效率更高。", + "MemoryManagerUnsafeTooltip": "直接映射内存页到电脑内存,并且不检查内存溢出,使得效率更高,但牺牲了安全。\n游戏程序可以访问模拟器内存的任意地址,所以不安全。\n建议此模式下只运行您信任的游戏程序。", + "UseHypervisorTooltip": "使用 Hypervisor 虚拟机代替即时编译,在可用的情况下能大幅提高性能,但目前可能还不稳定。", + "DRamTooltip": "模拟 Switch 开发机的内存布局。\n\n不会提高性能,某些高清纹理包或 4k 分辨率 MOD 可能需要使用此选项。\n\n如果不确定,请保持关闭状态。", + "IgnoreMissingServicesTooltip": "开启后,游戏会忽略未实现的系统服务,从而继续运行。\n少部分新发布的游戏由于使用了新的未知系统服务,可能需要此选项来避免闪退。\n模拟器更新完善系统服务之后,则无需开启此选项。\n\n如果不确定,请保持关闭状态。", + "GraphicsBackendThreadingTooltip": "在第二个线程上执行图形引擎指令。\n\n加速着色器编译,减少卡顿,可以提高 GPU 的性能。\n\n如果不确定,请设置为“自动”。", + "GalThreadingTooltip": "在第二个线程上执行图形引擎指令。\n\n加速着色器编译,减少卡顿,可以提高 GPU 的性能。\n\n如果不确定,请设置为“自动”。", + "ShaderCacheToggleTooltip": "模拟器将已编译的着色器保存到硬盘,减少游戏再次渲染相同图形导致的卡顿。\n\n如果不确定,请保持开启状态。", + "ResolutionScaleTooltip": "将游戏的渲染分辨率乘以一个倍数。\n\n有些游戏可能不适用这项设置,而且即使提高了分辨率仍然看起来像素化;对于这些游戏,您可能需要找到移除抗锯齿或提高内部渲染分辨率的 MOD。当使用这些 MOD 时,建议设置为“原生”。\n\n在游戏运行时,通过点击下面的“应用”按钮可以使设置生效;你可以将设置窗口移开,并试验找到您喜欢的游戏画面效果。\n\n请记住,对于几乎所有人而言,4倍分辨率都是过度的。", + "ResolutionScaleEntryTooltip": "建议设置为整数倍,带小数的分辨率缩放倍数(例如1.5),非整数倍的缩放容易导致问题或闪退。", + "AnisotropyTooltip": "各向异性过滤等级,可以提高倾斜视角纹理的清晰度。\n当设置为“自动”时,使用游戏自身设定的等级。", + "AspectRatioTooltip": "游戏渲染窗口的宽高比。\n\n只有当游戏使用了修改宽高比的 MOD 时才需要修改这个设置,否则图像会被拉伸。\n\n如果不确定,请保持为“16:9”。", "ShaderDumpPathTooltip": "转储图形着色器的路径", - "FileLogTooltip": "保存日志文件到硬盘。不会影响性能。", - "StubLogTooltip": "在控制台中打印 stub 日志消息。不影响性能。", - "InfoLogTooltip": "在控制台中打印信息日志消息。不影响性能。", - "WarnLogTooltip": "在控制台中打印警告日志消息。不影响性能。", - "ErrorLogTooltip": "打印控制台中的错误日志消息。不影响性能。", - "TraceLogTooltip": "在控制台中打印跟踪日志消息。不影响性能。", - "GuestLogTooltip": "在控制台中打印访客日志消息。不影响性能。", - "FileAccessLogTooltip": "在控制台中打印文件访问日志信息。", - "FSAccessLogModeTooltip": "启用访问日志输出到控制台。可能的模式为 0-3", - "DeveloperOptionTooltip": "谨慎使用", - "OpenGlLogLevel": "需要打开适当的日志等级", - "DebugLogTooltip": "记录Debug消息", - "LoadApplicationFileTooltip": "选择 Switch 支持的游戏格式并加载", - "LoadApplicationFolderTooltip": "选择解包后的 Switch 游戏并加载", - "OpenRyujinxFolderTooltip": "打开 Ryujinx 系统目录", + "FileLogTooltip": "将控制台日志保存到硬盘文件,不影响性能。", + "StubLogTooltip": "在控制台中显示存根日志,不影响性能。", + "InfoLogTooltip": "在控制台中显示信息日志,不影响性能。", + "WarnLogTooltip": "在控制台中显示警告日志,不影响性能。", + "ErrorLogTooltip": "在控制台中显示错误日志,不影响性能。", + "TraceLogTooltip": "在控制台中显示跟踪日志。", + "GuestLogTooltip": "在控制台中显示访客日志,不影响性能。", + "FileAccessLogTooltip": "在控制台中显示文件访问日志。", + "FSAccessLogModeTooltip": "在控制台中显示文件系统访问日志,可选模式为 0-3。", + "DeveloperOptionTooltip": "请谨慎使用", + "OpenGlLogLevel": "需要启用适当的日志级别", + "DebugLogTooltip": "在控制台中显示调试日志。\n\n仅在特别需要时使用此功能,因为它会导致日志信息难以阅读,并降低模拟器性能。", + "LoadApplicationFileTooltip": "选择 Switch 游戏文件并加载", + "LoadApplicationFolderTooltip": "选择解包后的 Switch 游戏目录并加载", + "OpenRyujinxFolderTooltip": "打开 Ryujinx 模拟器系统目录", "OpenRyujinxLogsTooltip": "打开日志存放的目录", - "ExitTooltip": "关闭 Ryujinx", + "ExitTooltip": "退出 Ryujinx 模拟器", "OpenSettingsTooltip": "打开设置窗口", - "OpenProfileManagerTooltip": "打开用户账户管理界面", - "StopEmulationTooltip": "停止运行当前游戏并回到主界面", + "OpenProfileManagerTooltip": "打开用户账户管理窗口", + "StopEmulationTooltip": "停止运行当前游戏,并回到主界面", "CheckUpdatesTooltip": "检查 Ryujinx 新版本", - "OpenAboutTooltip": "打开“关于”窗口", + "OpenAboutTooltip": "打开关于窗口", "GridSize": "网格尺寸", - "GridSizeTooltip": "调整网格模式的大小", + "GridSizeTooltip": "调整网格项目的大小", "SettingsTabSystemSystemLanguageBrazilianPortuguese": "巴西葡萄牙语", - "AboutRyujinxContributorsButtonHeader": "查看所有参与者", + "AboutRyujinxContributorsButtonHeader": "查看所有贡献者", "SettingsTabSystemAudioVolume": "音量:", "AudioVolumeTooltip": "调节音量", - "SettingsTabSystemEnableInternetAccess": "允许网络访问/局域网模式", - "EnableInternetAccessTooltip": "允许模拟的游戏进程访问互联网。\n当多个模拟器/真实的 Switch 连接到同一个局域网时,带有 LAN 模式的游戏可以相互通信。\n即使开启选项也无法访问 Nintendo 服务器。此外可能导致某些尝试联网的游戏崩溃。\n如果您不确定,请关闭该选项。", - "GameListContextMenuManageCheatToolTip": "管理金手指", + "SettingsTabSystemEnableInternetAccess": "启用网络连接(局域网)", + "EnableInternetAccessTooltip": "允许模拟的游戏程序访问网络。\n\n当多个模拟器或实体 Switch 连接到同一个网络时,带有局域网模式的游戏便可以相互通信。\n\n即使开启此选项也无法访问 Nintendo 服务器,有可能导致某些尝试联网的游戏闪退。\n\n如果不确定,请保持关闭状态。", + "GameListContextMenuManageCheatToolTip": "管理当前游戏的金手指", "GameListContextMenuManageCheat": "管理金手指", + "GameListContextMenuManageModToolTip": "管理当前游戏的 MOD", + "GameListContextMenuManageMod": "管理 MOD", "ControllerSettingsStickRange": "范围:", "DialogStopEmulationTitle": "Ryujinx - 停止模拟", - "DialogStopEmulationMessage": "是否确定停止模拟?", + "DialogStopEmulationMessage": "确定要停止模拟?", "SettingsTabCpu": "CPU", "SettingsTabAudio": "音频", "SettingsTabNetwork": "网络", "SettingsTabNetworkConnection": "网络连接", "SettingsTabCpuCache": "CPU 缓存", - "SettingsTabCpuMemory": "CPU 内存", - "DialogUpdaterFlatpakNotSupportedMessage": "请通过 FlatHub 更新 Ryujinx。", - "UpdaterDisabledWarningTitle": "更新已禁用!", - "GameListContextMenuOpenSdModsDirectory": "打开 Atmosphere MOD 目录", - "GameListContextMenuOpenSdModsDirectoryToolTip": "打开适用于 Atmosphere 自制系统的 MOD 目录", + "SettingsTabCpuMemory": "CPU 模式", + "DialogUpdaterFlatpakNotSupportedMessage": "请通过 FlatHub 更新 Ryujinx 模拟器。", + "UpdaterDisabledWarningTitle": "已禁用更新!", "ControllerSettingsRotate90": "顺时针旋转 90°", "IconSize": "图标尺寸", - "IconSizeTooltip": "更改游戏图标大小", + "IconSizeTooltip": "更改游戏图标的显示尺寸", "MenuBarOptionsShowConsole": "显示控制台", - "ShaderCachePurgeError": "清除着色器缓存时出错:{0}: {1}", - "UserErrorNoKeys": "找不到密钥", + "ShaderCachePurgeError": "清除 {0} 的着色器缓存时出错:{1}", + "UserErrorNoKeys": "找不到密钥Keys", "UserErrorNoFirmware": "找不到固件", - "UserErrorFirmwareParsingFailed": "固件解析错误", - "UserErrorApplicationNotFound": "找不到应用程序", + "UserErrorFirmwareParsingFailed": "固件解析出错", + "UserErrorApplicationNotFound": "找不到游戏程序", "UserErrorUnknown": "未知错误", "UserErrorUndefined": "未定义错误", - "UserErrorNoKeysDescription": "Ryujinx 找不到 'prod.keys' 文件", - "UserErrorNoFirmwareDescription": "Ryujinx 找不到任何已安装的固件", - "UserErrorFirmwareParsingFailedDescription": "Ryujinx 无法解密选择的固件。这通常是由于使用了过旧的密钥。", - "UserErrorApplicationNotFoundDescription": "Ryujinx 在选中路径找不到有效的应用程序。", - "UserErrorUnknownDescription": "发生未知错误!", - "UserErrorUndefinedDescription": "发生了未定义错误!此类错误不应出现,请联系开发人员!", - "OpenSetupGuideMessage": "打开设置教程", - "NoUpdate": "无更新", + "UserErrorNoKeysDescription": "Ryujinx 模拟器找不到“prod.keys”密钥文件", + "UserErrorNoFirmwareDescription": "Ryujinx 模拟器找不到任何已安装的固件", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx 模拟器无法解密当前固件,一般是由于使用了旧版的密钥导致的。", + "UserErrorApplicationNotFoundDescription": "Ryujinx 模拟器在所选路径中找不到有效的游戏程序。", + "UserErrorUnknownDescription": "出现未知错误!", + "UserErrorUndefinedDescription": "出现未定义错误!此类错误不应出现,请联系开发者!", + "OpenSetupGuideMessage": "打开安装指南", + "NoUpdate": "没有可用更新", "TitleUpdateVersionLabel": "版本 {0}", "RyujinxInfo": "Ryujinx - 信息", "RyujinxConfirm": "Ryujinx - 确认", "FileDialogAllTypes": "全部类型", "Never": "从不", - "SwkbdMinCharacters": "至少应为 {0} 个字长", - "SwkbdMinRangeCharacters": "必须为 {0}-{1} 个字长", - "SoftwareKeyboard": "软件键盘", - "SoftwareKeyboardModeNumbersOnly": "只接受数字", - "SoftwareKeyboardModeAlphabet": "只接受非中日韩文字", - "SoftwareKeyboardModeASCII": "只接受 ASCII 符号", - "DialogControllerAppletMessagePlayerRange": "游戏需要 {0} 个玩家并满足以下要求:\n\n手柄类型:{1}\n\n玩家类型:{2}\n\n{3} 请打开设置窗口,重新配置手柄输入;或者关闭返回。", - "DialogControllerAppletMessage": "游戏需要刚好 {0} 个玩家并满足以下要求:\n\n手柄类型:{1}\n\n玩家类型:{2}\n\n{3} 请打开设置窗口,重新配置手柄输入;或者关闭返回。", - "DialogControllerAppletDockModeSet": "目前处于主机模式,无法使用掌机操作方式", - "UpdaterRenaming": "正在删除旧文件...", + "SwkbdMinCharacters": "不少于 {0} 个字符", + "SwkbdMinRangeCharacters": "必须为 {0}-{1} 个字符", + "SoftwareKeyboard": "软键盘", + "SoftwareKeyboardModeNumeric": "只能输入 0-9 或 \".\"", + "SoftwareKeyboardModeAlphabet": "仅支持非中文字符", + "SoftwareKeyboardModeASCII": "仅支持 ASCII 字符", + "ControllerAppletControllers": "支持的手柄:", + "ControllerAppletPlayers": "玩家:", + "ControllerAppletDescription": "您当前的输入配置无效。打开设置并重新设置您的输入选项。", + "ControllerAppletDocked": "已经设置为主机模式,掌机手柄操控已经被禁用", + "UpdaterRenaming": "正在重命名旧文件...", "UpdaterRenameFailed": "更新过程中无法重命名文件:{0}", "UpdaterAddingFiles": "安装更新中...", "UpdaterExtracting": "正在提取更新...", - "UpdaterDownloading": "下载新版本中...", + "UpdaterDownloading": "下载更新中...", "Game": "游戏", "Docked": "主机模式", "Handheld": "掌机模式", "ConnectionError": "连接错误。", "AboutPageDeveloperListMore": "{0} 等开发者...", - "ApiError": "API错误。", + "ApiError": "API 错误。", "LoadingHeading": "正在启动 {0}", - "CompilingPPTC": "编译PPTC缓存中", + "CompilingPPTC": "编译 PPTC 缓存中", "CompilingShaders": "编译着色器中", "AllKeyboards": "所有键盘", - "OpenFileDialogTitle": "选择一个支持的文件以打开", - "OpenFolderDialogTitle": "选择一个包含解包游戏的文件夹", + "OpenFileDialogTitle": "选择支持的游戏文件并加载", + "OpenFolderDialogTitle": "选择包含解包游戏的目录并加载", "AllSupportedFormats": "所有支持的格式", - "RyujinxUpdater": "Ryujinx 更新程序", + "RyujinxUpdater": "Ryujinx 更新", "SettingsTabHotkeys": "快捷键", "SettingsTabHotkeysHotkeys": "键盘快捷键", - "SettingsTabHotkeysToggleVsyncHotkey": "切换垂直同步:", - "SettingsTabHotkeysScreenshotHotkey": "截屏:", - "SettingsTabHotkeysShowUiHotkey": "隐藏 界面:", + "SettingsTabHotkeysToggleVsyncHotkey": "开启或关闭垂直同步:", + "SettingsTabHotkeysScreenshotHotkey": "保存截屏:", + "SettingsTabHotkeysShowUiHotkey": "隐藏菜单栏和状态栏:", "SettingsTabHotkeysPauseHotkey": "暂停:", "SettingsTabHotkeysToggleMuteHotkey": "静音:", - "ControllerMotionTitle": "体感操作设置", + "ControllerMotionTitle": "体感设置", "ControllerRumbleTitle": "震动设置", "SettingsSelectThemeFileDialogTitle": "选择主题文件", "SettingsXamlThemeFile": "Xaml 主题文件", "AvatarWindowTitle": "管理账户 - 头像", "Amiibo": "Amiibo", "Unknown": "未知", - "Usage": "扫描可获得", + "Usage": "用法", "Writable": "可写入", "SelectDlcDialogTitle": "选择 DLC 文件", "SelectUpdateDialogTitle": "选择更新文件", + "SelectModDialogTitle": "选择 MOD 目录", "UserProfileWindowTitle": "管理用户账户", "CheatWindowTitle": "金手指管理器", "DlcWindowTitle": "管理 {0} ({1}) 的 DLC", "UpdateWindowTitle": "游戏更新管理器", "CheatWindowHeading": "适用于 {0} [{1}] 的金手指", - "BuildId": "游戏版本ID:", - "DlcWindowHeading": "{0} 个适用于 {1} ({2}) 的 DLC", - "UserProfilesEditProfile": "编辑选中账户", + "BuildId": "游戏版本 ID:", + "DlcWindowHeading": "{0} 个 DLC", + "ModWindowHeading": "{0} 个 MOD", + "UserProfilesEditProfile": "编辑所选", "Cancel": "取消", "Save": "保存", - "Discard": "返回", + "Discard": "放弃", + "Paused": "已暂停", "UserProfilesSetProfileImage": "选择头像", "UserProfileEmptyNameError": "必须输入名称", - "UserProfileNoImageError": "请选择您的头像", + "UserProfileNoImageError": "必须设置头像", "GameUpdateWindowHeading": "管理 {0} ({1}) 的更新", "SettingsTabHotkeysResScaleUpHotkey": "提高分辨率:", "SettingsTabHotkeysResScaleDownHotkey": "降低分辨率:", "UserProfilesName": "名称:", - "UserProfilesUserId": "用户ID:", - "SettingsTabGraphicsBackend": "图形后端", - "SettingsTabGraphicsBackendTooltip": "显卡使用的图形后端", - "SettingsEnableTextureRecompression": "启用纹理重压缩", - "SettingsEnableTextureRecompressionTooltip": "压缩某些纹理以减少显存的使用。\n适合显存小于 4GiB 的 GPU 开启。\n如果您不确定,请保持此项关闭。", - "SettingsTabGraphicsPreferredGpu": "首选 GPU", - "SettingsTabGraphicsPreferredGpuTooltip": "选择 Vulkan API 使用的显卡。\n此选项不会影响 OpenGL API。\n如果您不确定,建议选择\"dGPU(独立显卡)\"。如果没有独立显卡,则无需改动此选项。", - "SettingsAppRequiredRestartMessage": "Ryujinx 需要重启", - "SettingsGpuBackendRestartMessage": "您修改了图形 API 或显卡设置。需要重新启动才能生效", - "SettingsGpuBackendRestartSubMessage": "是否重启模拟器?", + "UserProfilesUserId": "用户 ID:", + "SettingsTabGraphicsBackend": "图形渲染引擎:", + "SettingsTabGraphicsBackendTooltip": "选择模拟器中使用的图像渲染引擎。\n\n安装了最新显卡驱动程序的所有现代显卡基本都支持 Vulkan,Vulkan 能够提供更快的着色器编译(较少的卡顿)。\n\n在旧版 Nvidia 显卡上、Linux 上的旧版 AMD 显卡,或者显存较低的显卡上,OpenGL 可能会取得更好的效果,但着色器编译更慢(更多的卡顿)。\n\n如果不确定,请设置为“Vulkan”。如果您的 GPU 已安装了最新显卡驱动程序也不支持 Vulkan,那请设置为“OpenGL”。", + "SettingsEnableTextureRecompression": "启用纹理压缩", + "SettingsEnableTextureRecompressionTooltip": "压缩 ASTC 纹理以减少 VRAM (显存)的占用。\n\n使用此纹理格式的游戏包括:异界锁链(Astral Chain),蓓优妮塔3(Bayonetta 3),火焰纹章Engage(Fire Emblem Engage),密特罗德 究极(Metroid Prime Remased),超级马力欧兄弟 惊奇(Super Mario Bros. Wonder)以及塞尔达传说 王国之泪(The Legend of Zelda: Tears of the Kingdom)。\n\n显存小于4GB的显卡在运行这些游戏时可能会偶发闪退。\n\n只有当您在上述游戏中的显存不足时才需要启用此选项。\n\n如果不确定,请保持关闭状态。", + "SettingsTabGraphicsPreferredGpu": "首选 GPU:", + "SettingsTabGraphicsPreferredGpuTooltip": "选择 Vulkan 图形引擎使用的 GPU。\n\n此选项不会影响 OpenGL 使用的 GPU。\n\n如果不确定,建议选择\"独立显卡(dGPU)\"。如果没有独立显卡,则无需改动此选项。", + "SettingsAppRequiredRestartMessage": "Ryujinx 模拟器需要重启", + "SettingsGpuBackendRestartMessage": "您修改了图形引擎或 GPU 设置,需要重启模拟器才能生效", + "SettingsGpuBackendRestartSubMessage": "是否要立即重启模拟器?", "RyujinxUpdaterMessage": "是否更新 Ryujinx 到最新的版本?", "SettingsTabHotkeysVolumeUpHotkey": "音量加:", "SettingsTabHotkeysVolumeDownHotkey": "音量减:", - "SettingsEnableMacroHLE": "启用 HLE 宏", - "SettingsEnableMacroHLETooltip": "GPU 宏代码的高级模拟。\n提高性能表现,但可能在某些游戏中引起图形错误。\n如果您不确定,请保持此项开启。", - "SettingsEnableColorSpacePassthrough": "颜色空间穿透", - "SettingsEnableColorSpacePassthroughTooltip": "指示 Vulkan 后端在不指定颜色空间的情况下传递颜色信息。对于具有宽色域显示器的用户来说,这可能会以颜色正确性为代价,产生更鲜艳的颜色。", + "SettingsEnableMacroHLE": "启用 HLE 宏加速", + "SettingsEnableMacroHLETooltip": "GPU 宏指令的高级模拟。\n\n提高性能表现,但一些游戏可能会出现图形错误。\n\n如果不确定,请保持开启状态。", + "SettingsEnableColorSpacePassthrough": "色彩空间直通", + "SettingsEnableColorSpacePassthroughTooltip": "使 Vulkan 图形引擎直接传输原始色彩信息。对于宽色域 (例如 DCI-P3) 显示器的用户来说,可以产生更鲜艳的颜色,代价是会损失部分色彩准确度。", "VolumeShort": "音量", "UserProfilesManageSaves": "管理存档", - "DeleteUserSave": "确定删除这个游戏的存档吗?", + "DeleteUserSave": "确定删除此游戏的用户存档吗?", "IrreversibleActionNote": "删除后不可恢复。", "SaveManagerHeading": "管理 {0} ({1}) 的存档", "SaveManagerTitle": "存档管理器", @@ -634,23 +643,26 @@ "UserProfilesRecoverLostAccounts": "恢复丢失的账户", "Recover": "恢复", "UserProfilesRecoverHeading": "找到了这些用户的存档数据", - "UserProfilesRecoverEmptyList": "没有可以恢复的配置文件", - "GraphicsAATooltip": "将抗锯齿使用到游戏渲染中", + "UserProfilesRecoverEmptyList": "没有可以恢复的用户数据", + "GraphicsAATooltip": "抗锯齿是一种图形处理技术,用于减少图像边缘的锯齿状现象,使图像更加平滑。\n\nFXAA(快速近似抗锯齿)是一种性能开销相对较小的抗锯齿方法,但可能会使得整体图像看起来有些模糊。\n\nSMAA(增强型子像素抗锯齿)则更加精细,它会尝试找到锯齿边缘并平滑它们,相比 FXAA 有更好的图像质量,但性能开销可能会稍大一些。\n\n如果开启了 FSR(FidelityFX Super Resolution,一种图像缩放过滤器)来提高性能或图像质量,不建议再启用抗锯齿,因为它们会产生不必要的图形处理开销,或者相互之间效果不协调。\n\n在游戏运行时,通过点击下面的“应用”按钮可以使设置生效;你可以将设置窗口移开,并试验找到您喜欢的游戏画面效果。\n\n如果不确定,请保持为“无”。", "GraphicsAALabel": "抗锯齿:", "GraphicsScalingFilterLabel": "缩放过滤:", - "GraphicsScalingFilterTooltip": "对帧缓冲区进行缩放", + "GraphicsScalingFilterTooltip": "选择在分辨率缩放时将使用的缩放过滤器。\n\nBilinear(双线性过滤)对于3D游戏效果较好,是一个安全的默认选项。\n\nNearest(最近邻过滤)推荐用于像素艺术游戏。\n\nFSR 1.0 只是一个锐化过滤器,不推荐与 FXAA 或 SMAA 抗锯齿一起使用。\n\n在游戏运行时,通过点击下面的“应用”按钮可以使设置生效;你可以将设置窗口移开,并试验找到您喜欢的游戏画面效果。\n\n如果不确定,请保持为“Bilinear”。", "GraphicsScalingFilterLevelLabel": "等级", - "GraphicsScalingFilterLevelTooltip": "设置缩放过滤级别", + "GraphicsScalingFilterLevelTooltip": "设置 FSR 1.0 的锐化等级,数值越高,图像越锐利。", "SmaaLow": "SMAA 低质量", "SmaaMedium": "SMAA 中质量", "SmaaHigh": "SMAA 高质量", - "SmaaUltra": "SMAA 极致质量", + "SmaaUltra": "SMAA 超高质量", "UserEditorTitle": "编辑用户", "UserEditorTitleCreate": "创建用户", "SettingsTabNetworkInterface": "网络接口:", - "NetworkInterfaceTooltip": "用于局域网功能的网络接口", + "NetworkInterfaceTooltip": "用于局域网(LAN)/本地网络发现(LDN)功能的网络接口。\n\n结合 VPN 或 XLink Kai 以及支持局域网功能的游戏,可以在互联网上伪造为同一网络连接。\n\n如果不确定,请保持为“默认”。", "NetworkInterfaceDefault": "默认", "PackagingShaders": "整合着色器中", - "AboutChangelogButton": "在Github上查看更新日志", - "AboutChangelogButtonTooltipMessage": "点击这里在您的默认浏览器中打开此版本的更新日志。" -} \ No newline at end of file + "AboutChangelogButton": "在 Github 上查看更新日志", + "AboutChangelogButtonTooltipMessage": "点击这里在浏览器中打开此版本的更新日志。", + "SettingsTabNetworkMultiplayer": "多人联机游玩", + "MultiplayerMode": "联机模式:", + "MultiplayerModeTooltip": "修改 LDN 多人联机游玩模式。\n\nLdnMitm 将修改游戏中的本地无线和本地游玩功能,使其表现得像局域网一样,允许和其他安装了 ldn_mitm 模块的 Ryujinx 模拟器和破解的任天堂 Switch 主机在同一网络下进行本地连接,实现多人联机游玩。\n\n多人联机游玩要求所有玩家必须运行相同的游戏版本(例如,任天堂明星大乱斗特别版 v13.0.1 无法与 v13.0.0 版本联机)。\n\n如果不确定,请保持为“禁用”。" +} diff --git a/src/Ryujinx/Assets/Locales/zh_TW.json b/src/Ryujinx/Assets/Locales/zh_TW.json index a2f59f60d..d9df134f7 100644 --- a/src/Ryujinx/Assets/Locales/zh_TW.json +++ b/src/Ryujinx/Assets/Locales/zh_TW.json @@ -1,43 +1,43 @@ { - "Language": "英文 (美國)", - "MenuBarFileOpenApplet": "開啟 Applet 應用程序", - "MenuBarFileOpenAppletOpenMiiAppletToolTip": "開啟獨立的Mii修改器應用程序", - "SettingsTabInputDirectMouseAccess": "滑鼠直接操作", - "SettingsTabSystemMemoryManagerMode": "記憶體管理模式:", - "SettingsTabSystemMemoryManagerModeSoftware": "軟體", - "SettingsTabSystemMemoryManagerModeHost": "主機模式 (快速)", - "SettingsTabSystemMemoryManagerModeHostUnchecked": "主機略過檢查模式 (最快, 但不安全)", + "Language": "繁體中文 (台灣)", + "MenuBarFileOpenApplet": "開啟小程式", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "在獨立模式下開啟 Mii 編輯器小程式", + "SettingsTabInputDirectMouseAccess": "滑鼠直接存取", + "SettingsTabSystemMemoryManagerMode": "記憶體管理員模式:", + "SettingsTabSystemMemoryManagerModeSoftware": "軟體模式", + "SettingsTabSystemMemoryManagerModeHost": "主體模式 (快速)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "主體略過檢查模式 (最快,不安全)", "SettingsTabSystemUseHypervisor": "使用 Hypervisor", - "MenuBarFile": "_檔案", - "MenuBarFileOpenFromFile": "_載入檔案", - "MenuBarFileOpenUnpacked": "載入_已解開封裝的遊戲", + "MenuBarFile": "檔案(_F)", + "MenuBarFileOpenFromFile": "從檔案載入應用程式(_L)", + "MenuBarFileOpenUnpacked": "載入解開封裝的遊戲(_U)", "MenuBarFileOpenEmuFolder": "開啟 Ryujinx 資料夾", "MenuBarFileOpenLogsFolder": "開啟日誌資料夾", - "MenuBarFileExit": "_退出", - "MenuBarOptions": "選項", + "MenuBarFileExit": "結束(_E)", + "MenuBarOptions": "選項(_O)", "MenuBarOptionsToggleFullscreen": "切換全螢幕模式", "MenuBarOptionsStartGamesInFullscreen": "使用全螢幕模式啟動遊戲", "MenuBarOptionsStopEmulation": "停止模擬", - "MenuBarOptionsSettings": "_設定", - "MenuBarOptionsManageUserProfiles": "_管理使用者帳戶", - "MenuBarActions": "_動作", + "MenuBarOptionsSettings": "設定(_S)", + "MenuBarOptionsManageUserProfiles": "管理使用者設定檔(_M)", + "MenuBarActions": "動作(_A)", "MenuBarOptionsSimulateWakeUpMessage": "模擬喚醒訊息", "MenuBarActionsScanAmiibo": "掃描 Amiibo", - "MenuBarTools": "_工具", + "MenuBarTools": "工具(_T)", "MenuBarToolsInstallFirmware": "安裝韌體", "MenuBarFileToolsInstallFirmwareFromFile": "從 XCI 或 ZIP 安裝韌體", "MenuBarFileToolsInstallFirmwareFromDirectory": "從資料夾安裝韌體", "MenuBarToolsManageFileTypes": "管理檔案類型", - "MenuBarToolsInstallFileTypes": "註冊檔案類型", - "MenuBarToolsUninstallFileTypes": "取消註冊檔案類型", - "MenuBarHelp": "幫助", + "MenuBarToolsInstallFileTypes": "安裝檔案類型", + "MenuBarToolsUninstallFileTypes": "移除檔案類型", + "MenuBarHelp": "說明(_H)", "MenuBarHelpCheckForUpdates": "檢查更新", "MenuBarHelpAbout": "關於", "MenuSearch": "搜尋...", - "GameListHeaderFavorite": "收藏", + "GameListHeaderFavorite": "我的最愛", "GameListHeaderIcon": "圖示", "GameListHeaderApplication": "名稱", - "GameListHeaderDeveloper": "開發人員", + "GameListHeaderDeveloper": "開發者", "GameListHeaderVersion": "版本", "GameListHeaderTimePlayed": "遊玩時數", "GameListHeaderLastPlayed": "最近遊玩", @@ -45,66 +45,71 @@ "GameListHeaderFileSize": "檔案大小", "GameListHeaderPath": "路徑", "GameListContextMenuOpenUserSaveDirectory": "開啟使用者存檔資料夾", - "GameListContextMenuOpenUserSaveDirectoryToolTip": "開啟此遊戲的存檔資料夾", - "GameListContextMenuOpenDeviceSaveDirectory": "開啟系統資料夾", - "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "開啟此遊戲的系統設定資料夾", - "GameListContextMenuOpenBcatSaveDirectory": "開啟 BCAT 資料夾", - "GameListContextMenuOpenBcatSaveDirectoryToolTip": "開啟此遊戲的 BCAT 資料夾\n", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "開啟此應用程式的使用者存檔資料夾", + "GameListContextMenuOpenDeviceSaveDirectory": "開啟裝置存檔資料夾", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "開啟此應用程式的裝置存檔資料夾", + "GameListContextMenuOpenBcatSaveDirectory": "開啟 BCAT 存檔資料夾", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "開啟此應用程式的 BCAT 存檔資料夾", "GameListContextMenuManageTitleUpdates": "管理遊戲更新", "GameListContextMenuManageTitleUpdatesToolTip": "開啟遊戲更新管理視窗", "GameListContextMenuManageDlc": "管理 DLC", "GameListContextMenuManageDlcToolTip": "開啟 DLC 管理視窗", - "GameListContextMenuOpenModsDirectory": "開啟模組資料夾", - "GameListContextMenuOpenModsDirectoryToolTip": "開啟此遊戲的模組資料夾", "GameListContextMenuCacheManagement": "快取管理", - "GameListContextMenuCacheManagementPurgePptc": "清除 PPTC 快取", - "GameListContextMenuCacheManagementPurgePptcToolTip": "刪除遊戲的 PPTC 快取", + "GameListContextMenuCacheManagementPurgePptc": "佇列 PPTC 重建", + "GameListContextMenuCacheManagementPurgePptcToolTip": "下一次啟動遊戲時,觸發 PPTC 進行重建", "GameListContextMenuCacheManagementPurgeShaderCache": "清除著色器快取", - "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "刪除遊戲的著色器快取", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "刪除應用程式的著色器快取", "GameListContextMenuCacheManagementOpenPptcDirectory": "開啟 PPTC 資料夾", - "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "開啟此遊戲的 PPTC 快取資料夾", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "開啟此應用程式的 PPTC 快取資料夾", "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "開啟著色器快取資料夾", - "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "開啟此遊戲的著色器快取資料夾", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "開啟此應用程式的著色器快取資料夾", "GameListContextMenuExtractData": "提取資料", "GameListContextMenuExtractDataExeFS": "ExeFS", - "GameListContextMenuExtractDataExeFSToolTip": "從遊戲的目前狀態中提取 ExeFS 分區(包含更新)", + "GameListContextMenuExtractDataExeFSToolTip": "從應用程式的目前配置中提取 ExeFS 分區 (包含更新)", "GameListContextMenuExtractDataRomFS": "RomFS", - "GameListContextMenuExtractDataRomFSToolTip": "從遊戲的目前狀態中提取 RomFS 分區(包含更新)", - "GameListContextMenuExtractDataLogo": "圖示", - "GameListContextMenuExtractDataLogoToolTip": "從遊戲的目前狀態中提取圖示(包含更新)", - "StatusBarGamesLoaded": "{0}/{1} 遊戲載入完成", - "StatusBarSystemVersion": "系統版本: {0}", - "LinuxVmMaxMapCountDialogTitle": "檢測到映射的記憶體上限過低", - "LinuxVmMaxMapCountDialogTextPrimary": "你願意增加 vm.max_map_count to {0} 的數值嗎?", - "LinuxVmMaxMapCountDialogTextSecondary": "遊戲佔用的記憶體超出了映射的上限. Ryujinx的處理程序即將面臨崩潰.", - "LinuxVmMaxMapCountDialogButtonUntilRestart": "碓定 (直至下一次重新啟動)", - "LinuxVmMaxMapCountDialogButtonPersistent": "碓定 (永遠設定)", - "LinuxVmMaxMapCountWarningTextPrimary": "映射記憶體的最大值少於目前建議的下限.", - "LinuxVmMaxMapCountWarningTextSecondary": "目前 vm.max_map_count ({0}) 的數值少於 {1}. 遊戲佔用的記憶體超出了映射的上限. Ryujinx的處理程序即將面臨崩潰.\n\n你可能需要手動增加上限或安裝 pkexec, 這些都能協助Ryujinx完成此操作.", + "GameListContextMenuExtractDataRomFSToolTip": "從應用程式的目前配置中提取 RomFS 分區 (包含更新)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "從應用程式的目前配置中提取 Logo 分區 (包含更新)", + "GameListContextMenuCreateShortcut": "建立應用程式捷徑", + "GameListContextMenuCreateShortcutToolTip": "建立桌面捷徑,啟動選取的應用程式", + "GameListContextMenuCreateShortcutToolTipMacOS": "在 macOS 的應用程式資料夾中建立捷徑,啟動選取的應用程式", + "GameListContextMenuOpenModsDirectory": "開啟模組資料夾", + "GameListContextMenuOpenModsDirectoryToolTip": "開啟此應用程式模組的資料夾", + "GameListContextMenuOpenSdModsDirectory": "開啟 Atmosphere 模組資料夾", + "GameListContextMenuOpenSdModsDirectoryToolTip": "開啟此應用程式模組的另一個 SD 卡 Atmosphere 資料夾。適用於為真實硬體封裝的模組。", + "StatusBarGamesLoaded": "{0}/{1} 遊戲已載入", + "StatusBarSystemVersion": "系統版本: {0}", + "LinuxVmMaxMapCountDialogTitle": "檢測到記憶體映射的低限值", + "LinuxVmMaxMapCountDialogTextPrimary": "您是否要將 vm.max_map_count 的數值增至 {0}?", + "LinuxVmMaxMapCountDialogTextSecondary": "某些遊戲可能會嘗試建立超過目前允許的記憶體映射。一旦超過此限制,Ryujinx 就會崩潰。", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "是的,直到下次重新啟動", + "LinuxVmMaxMapCountDialogButtonPersistent": "是的,永久設定", + "LinuxVmMaxMapCountWarningTextPrimary": "記憶體映射的最大值低於建議值。", + "LinuxVmMaxMapCountWarningTextSecondary": "目前 vm.max_map_count ({0}) 的數值小於 {1}。某些遊戲可能會嘗試建立比目前允許值更多的記憶體映射。一旦超過此限制,Ryujinx 就會崩潰。\n\n您可能需要手動提高上限,或者安裝 pkexec,讓 Ryujinx 協助提高上限。", "Settings": "設定", "SettingsTabGeneral": "使用者介面", "SettingsTabGeneralGeneral": "一般", "SettingsTabGeneralEnableDiscordRichPresence": "啟用 Discord 動態狀態展示", - "SettingsTabGeneralCheckUpdatesOnLaunch": "自動檢查更新", - "SettingsTabGeneralShowConfirmExitDialog": "顯示「確認離開」對話框", - "SettingsTabGeneralHideCursor": "隱藏滑鼠遊標:", - "SettingsTabGeneralHideCursorNever": "永不", - "SettingsTabGeneralHideCursorOnIdle": "自動隱藏滑鼠", + "SettingsTabGeneralCheckUpdatesOnLaunch": "啟動時檢查更新", + "SettingsTabGeneralShowConfirmExitDialog": "顯示「確認結束」對話方塊", + "SettingsTabGeneralHideCursor": "隱藏滑鼠游標:", + "SettingsTabGeneralHideCursorNever": "從不", + "SettingsTabGeneralHideCursorOnIdle": "閒置時", "SettingsTabGeneralHideCursorAlways": "總是", "SettingsTabGeneralGameDirectories": "遊戲資料夾", "SettingsTabGeneralAdd": "新增", "SettingsTabGeneralRemove": "刪除", "SettingsTabSystem": "系統", "SettingsTabSystemCore": "核心", - "SettingsTabSystemSystemRegion": "系統區域:", + "SettingsTabSystemSystemRegion": "系統區域:", "SettingsTabSystemSystemRegionJapan": "日本", "SettingsTabSystemSystemRegionUSA": "美國", "SettingsTabSystemSystemRegionEurope": "歐洲", "SettingsTabSystemSystemRegionAustralia": "澳洲", "SettingsTabSystemSystemRegionChina": "中國", "SettingsTabSystemSystemRegionKorea": "韓國", - "SettingsTabSystemSystemRegionTaiwan": "台灣", - "SettingsTabSystemSystemLanguage": "系統語言:", + "SettingsTabSystemSystemRegionTaiwan": "台灣 (中華民國)", + "SettingsTabSystemSystemLanguage": "系統語言:", "SettingsTabSystemSystemLanguageJapanese": "日文", "SettingsTabSystemSystemLanguageAmericanEnglish": "英文 (美國)", "SettingsTabSystemSystemLanguageFrench": "法文", @@ -118,70 +123,70 @@ "SettingsTabSystemSystemLanguageRussian": "俄文", "SettingsTabSystemSystemLanguageTaiwanese": "中文 (台灣)", "SettingsTabSystemSystemLanguageBritishEnglish": "英文 (英國)", - "SettingsTabSystemSystemLanguageCanadianFrench": "加拿大法語", - "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "拉丁美洲西班牙文", + "SettingsTabSystemSystemLanguageCanadianFrench": "加拿大法文", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "美洲西班牙文", "SettingsTabSystemSystemLanguageSimplifiedChinese": "簡體中文", - "SettingsTabSystemSystemLanguageTraditionalChinese": "繁體中文", + "SettingsTabSystemSystemLanguageTraditionalChinese": "正體中文 (建議)", "SettingsTabSystemSystemTimeZone": "系統時區:", "SettingsTabSystemSystemTime": "系統時鐘:", "SettingsTabSystemEnableVsync": "垂直同步", - "SettingsTabSystemEnablePptc": "啟用 PPTC 快取", - "SettingsTabSystemEnableFsIntegrityChecks": "開啟檔案系統完整性檢查", - "SettingsTabSystemAudioBackend": "音效處理後台架構:", - "SettingsTabSystemAudioBackendDummy": "模擬", + "SettingsTabSystemEnablePptc": "PPTC (剖析式持久轉譯快取, Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "檔案系統完整性檢查", + "SettingsTabSystemAudioBackend": "音效後端:", + "SettingsTabSystemAudioBackendDummy": "虛設 (Dummy)", "SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSDL2": "SDL2", - "SettingsTabSystemHacks": "修正", - "SettingsTabSystemHacksNote": " (會引起模擬器不穩定)", - "SettingsTabSystemExpandDramSize": "使用額外的記憶體佈局 (開發人員)", - "SettingsTabSystemIgnoreMissingServices": "忽略缺少的服務", - "SettingsTabGraphics": "圖像", - "SettingsTabGraphicsAPI": "圖像處理應用程式介面", + "SettingsTabSystemHacks": "補釘修正", + "SettingsTabSystemHacksNote": "可能導致不穩定", + "SettingsTabSystemExpandDramSize": "使用其他記憶體配置 (開發者)", + "SettingsTabSystemIgnoreMissingServices": "忽略遺失的服務", + "SettingsTabGraphics": "圖形", + "SettingsTabGraphicsAPI": "圖形 API", "SettingsTabGraphicsEnableShaderCache": "啟用著色器快取", - "SettingsTabGraphicsAnisotropicFiltering": "各向異性過濾:", + "SettingsTabGraphicsAnisotropicFiltering": "各向異性過濾:", "SettingsTabGraphicsAnisotropicFilteringAuto": "自動", "SettingsTabGraphicsAnisotropicFiltering2x": "2 倍", "SettingsTabGraphicsAnisotropicFiltering4x": "4 倍", "SettingsTabGraphicsAnisotropicFiltering8x": "8 倍", - "SettingsTabGraphicsAnisotropicFiltering16x": "16倍", - "SettingsTabGraphicsResolutionScale": "解析度比例:", + "SettingsTabGraphicsAnisotropicFiltering16x": "16 倍", + "SettingsTabGraphicsResolutionScale": "解析度比例:", "SettingsTabGraphicsResolutionScaleCustom": "自訂 (不建議使用)", "SettingsTabGraphicsResolutionScaleNative": "原生 (720p/1080p)", "SettingsTabGraphicsResolutionScale2x": "2 倍 (1440p/2160p)", "SettingsTabGraphicsResolutionScale3x": "3 倍 (2160p/3240p)", - "SettingsTabGraphicsResolutionScale4x": "4 倍 (2880p/4320p)", - "SettingsTabGraphicsAspectRatio": "螢幕長寬比例:", - "SettingsTabGraphicsAspectRatio4x3": "4:3", - "SettingsTabGraphicsAspectRatio16x9": "16:9", - "SettingsTabGraphicsAspectRatio16x10": "16:10", - "SettingsTabGraphicsAspectRatio21x9": "21:9", - "SettingsTabGraphicsAspectRatio32x9": "32:9", - "SettingsTabGraphicsAspectRatioStretch": "伸展至螢幕大小", + "SettingsTabGraphicsResolutionScale4x": "4 倍 (2880p/4320p) (不建議使用)", + "SettingsTabGraphicsAspectRatio": "顯示長寬比例:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "拉伸以適應視窗", "SettingsTabGraphicsDeveloperOptions": "開發者選項", - "SettingsTabGraphicsShaderDumpPath": "圖形著色器轉存路徑:", + "SettingsTabGraphicsShaderDumpPath": "圖形著色器傾印路徑:", "SettingsTabLogging": "日誌", "SettingsTabLoggingLogging": "日誌", - "SettingsTabLoggingEnableLoggingToFile": "儲存記錄日誌為檔案", - "SettingsTabLoggingEnableStubLogs": "啟用 Stub 記錄", - "SettingsTabLoggingEnableInfoLogs": "啟用資訊記錄", - "SettingsTabLoggingEnableWarningLogs": "啟用警告記錄", - "SettingsTabLoggingEnableErrorLogs": "啟用錯誤記錄", - "SettingsTabLoggingEnableTraceLogs": "啟用追蹤記錄", - "SettingsTabLoggingEnableGuestLogs": "啟用賓客記錄", - "SettingsTabLoggingEnableFsAccessLogs": "啟用檔案存取記錄", - "SettingsTabLoggingFsGlobalAccessLogMode": "記錄全域檔案存取模式:", + "SettingsTabLoggingEnableLoggingToFile": "啟用日誌到檔案", + "SettingsTabLoggingEnableStubLogs": "啟用 Stub 日誌", + "SettingsTabLoggingEnableInfoLogs": "啟用資訊日誌", + "SettingsTabLoggingEnableWarningLogs": "啟用警告日誌", + "SettingsTabLoggingEnableErrorLogs": "啟用錯誤日誌", + "SettingsTabLoggingEnableTraceLogs": "啟用追蹤日誌", + "SettingsTabLoggingEnableGuestLogs": "啟用客體日誌", + "SettingsTabLoggingEnableFsAccessLogs": "啟用檔案系統存取日誌", + "SettingsTabLoggingFsGlobalAccessLogMode": "檔案系統全域存取日誌模式:", "SettingsTabLoggingDeveloperOptions": "開發者選項", - "SettingsTabLoggingDeveloperOptionsNote": "警告:此操作會降低效能", - "SettingsTabLoggingGraphicsBackendLogLevel": "圖像處理後台記錄等級:", + "SettingsTabLoggingDeveloperOptionsNote": "警告: 會降低效能", + "SettingsTabLoggingGraphicsBackendLogLevel": "圖形後端日誌等級:", "SettingsTabLoggingGraphicsBackendLogLevelNone": "無", "SettingsTabLoggingGraphicsBackendLogLevelError": "錯誤", "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "減速", "SettingsTabLoggingGraphicsBackendLogLevelAll": "全部", - "SettingsTabLoggingEnableDebugLogs": "啟用除錯記錄", + "SettingsTabLoggingEnableDebugLogs": "啟用偵錯日誌", "SettingsTabInput": "輸入", - "SettingsTabInputEnableDockedMode": "Docked 模式", - "SettingsTabInputDirectKeyboardAccess": "鍵盤直接操作", + "SettingsTabInputEnableDockedMode": "底座模式", + "SettingsTabInputDirectKeyboardAccess": "鍵盤直接存取", "SettingsButtonSave": "儲存", "SettingsButtonClose": "關閉", "SettingsButtonOk": "確定", @@ -196,20 +201,20 @@ "ControllerSettingsPlayer6": "玩家 6", "ControllerSettingsPlayer7": "玩家 7", "ControllerSettingsPlayer8": "玩家 8", - "ControllerSettingsHandheld": "掌機模式", + "ControllerSettingsHandheld": "手提模式", "ControllerSettingsInputDevice": "輸入裝置", - "ControllerSettingsRefresh": "更新", - "ControllerSettingsDeviceDisabled": "關閉", + "ControllerSettingsRefresh": "重新整理", + "ControllerSettingsDeviceDisabled": "停用", "ControllerSettingsControllerType": "控制器類型", - "ControllerSettingsControllerTypeHandheld": "掌機", - "ControllerSettingsControllerTypeProController": "Nintendo Switch Pro控制器", - "ControllerSettingsControllerTypeJoyConPair": "JoyCon", + "ControllerSettingsControllerTypeHandheld": "手提模式", + "ControllerSettingsControllerTypeProController": "Pro 控制器", + "ControllerSettingsControllerTypeJoyConPair": "雙 JoyCon", "ControllerSettingsControllerTypeJoyConLeft": "左 JoyCon", "ControllerSettingsControllerTypeJoyConRight": "右 JoyCon", - "ControllerSettingsProfile": "配置檔案", + "ControllerSettingsProfile": "設定檔", "ControllerSettingsProfileDefault": "預設", "ControllerSettingsLoad": "載入", - "ControllerSettingsAdd": "建立", + "ControllerSettingsAdd": "新增", "ControllerSettingsRemove": "刪除", "ControllerSettingsButtons": "按鍵", "ControllerSettingsButtonA": "A", @@ -231,13 +236,13 @@ "ControllerSettingsStickStick": "搖桿", "ControllerSettingsStickInvertXAxis": "搖桿左右反向", "ControllerSettingsStickInvertYAxis": "搖桿上下反向", - "ControllerSettingsStickDeadzone": "盲區:", + "ControllerSettingsStickDeadzone": "無感帶:", "ControllerSettingsLStick": "左搖桿", "ControllerSettingsRStick": "右搖桿", - "ControllerSettingsTriggersLeft": "左 Triggers", - "ControllerSettingsTriggersRight": "右 Triggers", - "ControllerSettingsTriggersButtonsLeft": "左 Triggers 鍵", - "ControllerSettingsTriggersButtonsRight": "右 Triggers 鍵", + "ControllerSettingsTriggersLeft": "左扳機", + "ControllerSettingsTriggersRight": "右扳機", + "ControllerSettingsTriggersButtonsLeft": "左扳機鍵", + "ControllerSettingsTriggersButtonsRight": "右扳機鍵", "ControllerSettingsTriggers": "板機", "ControllerSettingsTriggerL": "L", "ControllerSettingsTriggerR": "R", @@ -250,407 +255,414 @@ "ControllerSettingsExtraButtonsLeft": "左按鍵", "ControllerSettingsExtraButtonsRight": "右按鍵", "ControllerSettingsMisc": "其他", - "ControllerSettingsTriggerThreshold": "Triggers 閾值:", - "ControllerSettingsMotion": "傳感器", - "ControllerSettingsMotionUseCemuhookCompatibleMotion": "使用 CemuHook 相容性傳感協定", - "ControllerSettingsMotionControllerSlot": "控制器插槽:", + "ControllerSettingsTriggerThreshold": "扳機閾值:", + "ControllerSettingsMotion": "體感", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "使用與 CemuHook 相容的體感", + "ControllerSettingsMotionControllerSlot": "控制器插槽:", "ControllerSettingsMotionMirrorInput": "鏡像輸入", - "ControllerSettingsMotionRightJoyConSlot": "右 JoyCon:", - "ControllerSettingsMotionServerHost": "伺服器IP地址:", - "ControllerSettingsMotionGyroSensitivity": "陀螺儀敏感度:", - "ControllerSettingsMotionGyroDeadzone": "陀螺儀盲區:", + "ControllerSettingsMotionRightJoyConSlot": "右 JoyCon 插槽:", + "ControllerSettingsMotionServerHost": "伺服器主機位址:", + "ControllerSettingsMotionGyroSensitivity": "陀螺儀靈敏度:", + "ControllerSettingsMotionGyroDeadzone": "陀螺儀無感帶:", "ControllerSettingsSave": "儲存", "ControllerSettingsClose": "關閉", - "UserProfilesSelectedUserProfile": "選擇使用者帳戶:", - "UserProfilesSaveProfileName": "儲存帳戶名稱", - "UserProfilesChangeProfileImage": "更換帳戶頭像", - "UserProfilesAvailableUserProfiles": "現有的使用者帳戶:", - "UserProfilesAddNewProfile": "建立帳戶", + "UserProfilesSelectedUserProfile": "選取的使用者設定檔:", + "UserProfilesSaveProfileName": "儲存設定檔名稱", + "UserProfilesChangeProfileImage": "變更設定檔圖像", + "UserProfilesAvailableUserProfiles": "可用的使用者設定檔:", + "UserProfilesAddNewProfile": "建立設定檔", "UserProfilesDelete": "刪除", "UserProfilesClose": "關閉", - "ProfileNameSelectionWatermark": "選擇一個暱稱", - "ProfileImageSelectionTitle": "帳戶頭像選擇", - "ProfileImageSelectionHeader": "選擇帳戶頭像", - "ProfileImageSelectionNote": "你可以導入自訂頭像,或從系統中選擇頭像", - "ProfileImageSelectionImportImage": "導入圖片檔案", - "ProfileImageSelectionSelectAvatar": "選擇系統頭像", - "InputDialogTitle": "輸入對話框", - "InputDialogOk": "完成", + "ProfileNameSelectionWatermark": "選擇暱稱", + "ProfileImageSelectionTitle": "設定檔圖像選取", + "ProfileImageSelectionHeader": "選擇設定檔圖像", + "ProfileImageSelectionNote": "您可以匯入自訂的設定檔圖像,或從系統韌體中選取大頭貼。", + "ProfileImageSelectionImportImage": "匯入圖像檔案", + "ProfileImageSelectionSelectAvatar": "選取韌體大頭貼", + "InputDialogTitle": "輸入對話方塊", + "InputDialogOk": "確定", "InputDialogCancel": "取消", - "InputDialogAddNewProfileTitle": "選擇帳戶名稱", - "InputDialogAddNewProfileHeader": "請輸入帳戶名稱", - "InputDialogAddNewProfileSubtext": "(最大長度:{0})", - "AvatarChoose": "選擇", + "InputDialogAddNewProfileTitle": "選擇設定檔名稱", + "InputDialogAddNewProfileHeader": "請輸入設定檔名稱", + "InputDialogAddNewProfileSubtext": "(最大長度: {0})", + "AvatarChoose": "選擇大頭貼", "AvatarSetBackgroundColor": "設定背景顏色", "AvatarClose": "關閉", - "ControllerSettingsLoadProfileToolTip": "載入配置檔案", - "ControllerSettingsAddProfileToolTip": "新增配置檔案", - "ControllerSettingsRemoveProfileToolTip": "刪除配置檔案", - "ControllerSettingsSaveProfileToolTip": "儲存配置檔案", - "MenuBarFileToolsTakeScreenshot": "儲存截圖", - "MenuBarFileToolsHideUi": "隱藏使用者介面", - "GameListContextMenuRunApplication": "執行程式", - "GameListContextMenuToggleFavorite": "標記為收藏", - "GameListContextMenuToggleFavoriteToolTip": "啟用或取消收藏標記", - "SettingsTabGeneralTheme": "佈景主題", - "SettingsTabGeneralThemeCustomTheme": "自訂佈景主題路徑", - "SettingsTabGeneralThemeBaseStyle": "基本佈景主題式樣", - "SettingsTabGeneralThemeBaseStyleDark": "深色模式", - "SettingsTabGeneralThemeBaseStyleLight": "淺色模式", - "SettingsTabGeneralThemeEnableCustomTheme": "使用自訂佈景主題", - "ButtonBrowse": "瀏覽", + "ControllerSettingsLoadProfileToolTip": "載入設定檔", + "ControllerSettingsAddProfileToolTip": "新增設定檔", + "ControllerSettingsRemoveProfileToolTip": "刪除設定檔", + "ControllerSettingsSaveProfileToolTip": "儲存設定檔", + "MenuBarFileToolsTakeScreenshot": "儲存擷取畫面", + "MenuBarFileToolsHideUi": "隱藏 UI", + "GameListContextMenuRunApplication": "執行應用程式", + "GameListContextMenuToggleFavorite": "加入/移除為我的最愛", + "GameListContextMenuToggleFavoriteToolTip": "切換遊戲的我的最愛狀態", + "SettingsTabGeneralTheme": "佈景主題:", + "SettingsTabGeneralThemeDark": "深色", + "SettingsTabGeneralThemeLight": "淺色", "ControllerSettingsConfigureGeneral": "配置", "ControllerSettingsRumble": "震動", "ControllerSettingsRumbleStrongMultiplier": "強震動調節", "ControllerSettingsRumbleWeakMultiplier": "弱震動調節", - "DialogMessageSaveNotAvailableMessage": "沒有{0} [{1:x16}]的遊戲存檔", - "DialogMessageSaveNotAvailableCreateSaveMessage": "是否建立該遊戲的存檔資料夾?", - "DialogConfirmationTitle": "Ryujinx - 設定", - "DialogUpdaterTitle": "Ryujinx - 更新", + "DialogMessageSaveNotAvailableMessage": "沒有 {0} [{1:x16}] 的存檔", + "DialogMessageSaveNotAvailableCreateSaveMessage": "您想為這款遊戲建立存檔嗎?", + "DialogConfirmationTitle": "Ryujinx - 確認", + "DialogUpdaterTitle": "Ryujinx - 更新程式", "DialogErrorTitle": "Ryujinx - 錯誤", "DialogWarningTitle": "Ryujinx - 警告", - "DialogExitTitle": "Ryujinx - 關閉", + "DialogExitTitle": "Ryujinx - 結束", "DialogErrorMessage": "Ryujinx 遇到了錯誤", - "DialogExitMessage": "你確定要關閉 Ryujinx 嗎?", - "DialogExitSubMessage": "所有未儲存的資料將會遺失!", - "DialogMessageCreateSaveErrorMessage": "建立特定的存檔時出現錯誤: {0}", - "DialogMessageFindSaveErrorMessage": "查找特定的存檔時出現錯誤: {0}", + "DialogExitMessage": "您確定要關閉 Ryujinx 嗎?", + "DialogExitSubMessage": "所有未儲存的資料將會遺失!", + "DialogMessageCreateSaveErrorMessage": "建立指定的存檔時出現錯誤: {0}", + "DialogMessageFindSaveErrorMessage": "尋找指定的存檔時出現錯誤: {0}", "FolderDialogExtractTitle": "選擇要解壓到的資料夾", - "DialogNcaExtractionMessage": "提取{1}的{0}分區...", - "DialogNcaExtractionTitle": "Ryujinx - NCA分區提取", - "DialogNcaExtractionMainNcaNotFoundErrorMessage": "提取失敗。所選檔案中不含主NCA檔案", - "DialogNcaExtractionCheckLogErrorMessage": "提取失敗。請查看日誌檔案取得詳情。", + "DialogNcaExtractionMessage": "從 {1} 提取 {0} 分區...", + "DialogNcaExtractionTitle": "Ryujinx - NCA 分區提取器", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "提取失敗。所選檔案中不存在主 NCA 檔案。", + "DialogNcaExtractionCheckLogErrorMessage": "提取失敗。請閱讀日誌檔案了解更多資訊。", "DialogNcaExtractionSuccessMessage": "提取成功。", - "DialogUpdaterConvertFailedMessage": "無法轉換目前 Ryujinx 版本。", - "DialogUpdaterCancelUpdateMessage": "更新取消!", - "DialogUpdaterAlreadyOnLatestVersionMessage": "你使用的 Ryujinx 是最新版本。", - "DialogUpdaterFailedToGetVersionMessage": "嘗試從 Github 取得版本訊息時失敗。可能是因為 GitHub Actions 正在編譯新版本。請於數分數後重試。", - "DialogUpdaterConvertFailedGithubMessage": "無法轉換從 Github 接收到的 Ryujinx 版本。", - "DialogUpdaterDownloadingMessage": "下載最新版本中...", + "DialogUpdaterConvertFailedMessage": "無法轉換目前的 Ryujinx 版本。", + "DialogUpdaterCancelUpdateMessage": "取消更新!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "您已經在使用最新版本的 Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "嘗試從 GitHub Release 取得發布資訊時發生錯誤。如果 GitHub Actions 正在編譯新版本,則可能會出現這種情況。請幾分鐘後再試一次。", + "DialogUpdaterConvertFailedGithubMessage": "無法轉換從 Github Release 接收到的 Ryujinx 版本。", + "DialogUpdaterDownloadingMessage": "正在下載更新...", "DialogUpdaterExtractionMessage": "正在提取更新...", - "DialogUpdaterRenamingMessage": "正在刪除舊檔案...", - "DialogUpdaterAddingFilesMessage": "安裝更新中...", + "DialogUpdaterRenamingMessage": "重新命名更新...", + "DialogUpdaterAddingFilesMessage": "加入新更新...", "DialogUpdaterCompleteMessage": "更新成功!", - "DialogUpdaterRestartMessage": "你確定要立即重新啟動 Ryujinx 嗎?", - "DialogUpdaterArchNotSupportedMessage": "你執行的系統架構不被支援!", - "DialogUpdaterArchNotSupportedSubMessage": "(僅支援 x64 系統)", - "DialogUpdaterNoInternetMessage": "你沒有連接到網際網絡!", - "DialogUpdaterNoInternetSubMessage": "請確保網際網絡連接正常!", - "DialogUpdaterDirtyBuildMessage": "不能更新非官方版本的 Ryujinx!", - "DialogUpdaterDirtyBuildSubMessage": "如果你希望使用被受支援的Ryujinx版本,請你在官方網址 https://ryujinx.org/ 下載.", - "DialogRestartRequiredMessage": "模擬器必須重新啟動", - "DialogThemeRestartMessage": "佈景主題設定已儲存。需要重新啟動才能生效。", - "DialogThemeRestartSubMessage": "你確定要現在重新啟動嗎?", - "DialogFirmwareInstallEmbeddedMessage": "要安裝遊戲內建的韌體嗎?(韌體 {0})", + "DialogUpdaterRestartMessage": "您現在要重新啟動 Ryujinx 嗎?", + "DialogUpdaterNoInternetMessage": "您沒有連線到網際網路!", + "DialogUpdaterNoInternetSubMessage": "請確認您的網際網路連線正常!", + "DialogUpdaterDirtyBuildMessage": "您無法更新非官方版本的 Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "如果您正在尋找受官方支援的版本,請從 https://ryujinx.org/ 下載 Ryujinx。", + "DialogRestartRequiredMessage": "需要重新啟動", + "DialogThemeRestartMessage": "佈景主題設定已儲存。需要重新啟動才能套用主題。", + "DialogThemeRestartSubMessage": "您要重新啟動嗎", + "DialogFirmwareInstallEmbeddedMessage": "您想安裝遊戲內建的韌體嗎? (韌體 {0})", "DialogFirmwareInstallEmbeddedSuccessMessage": "未找到已安裝的韌體,但 Ryujinx 可以從現有的遊戲安裝韌體{0}.\\n模擬器現在可以執行。", "DialogFirmwareNoFirmwareInstalledMessage": "未安裝韌體", "DialogFirmwareInstalledMessage": "已安裝韌體{0}", - "DialogInstallFileTypesSuccessMessage": "成功註冊檔案類型!", - "DialogInstallFileTypesErrorMessage": "註冊檔案類型失敗。", - "DialogUninstallFileTypesSuccessMessage": "成功取消註冊檔案類型!", - "DialogUninstallFileTypesErrorMessage": "取消註冊檔案類型失敗。", + "DialogInstallFileTypesSuccessMessage": "成功安裝檔案類型!", + "DialogInstallFileTypesErrorMessage": "無法安裝檔案類型。", + "DialogUninstallFileTypesSuccessMessage": "成功移除檔案類型!", + "DialogUninstallFileTypesErrorMessage": "無法移除檔案類型。", "DialogOpenSettingsWindowLabel": "開啟設定視窗", - "DialogControllerAppletTitle": "控制器小視窗", - "DialogMessageDialogErrorExceptionMessage": "顯示訊息對話框時出現錯誤: {0}", - "DialogSoftwareKeyboardErrorExceptionMessage": "顯示軟體鍵盤時出現錯誤: {0}", - "DialogErrorAppletErrorExceptionMessage": "顯示錯誤對話框時出現錯誤: {0}", - "DialogUserErrorDialogMessage": "{0}: {1}", - "DialogUserErrorDialogInfoMessage": "\n有關修復此錯誤的更多訊息,可以遵循我們的設定指南。", + "DialogControllerAppletTitle": "控制器小程式", + "DialogMessageDialogErrorExceptionMessage": "顯示訊息對話方塊時出現錯誤: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "顯示軟體鍵盤時出現錯誤: {0}", + "DialogErrorAppletErrorExceptionMessage": "顯示錯誤對話方塊時出現錯誤: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\n有關如何修復此錯誤的更多資訊,請參閱我們的設定指南。", "DialogUserErrorDialogTitle": "Ryujinx 錯誤 ({0})", - "DialogAmiiboApiTitle": "Amiibo 應用程式介面", - "DialogAmiiboApiFailFetchMessage": "從 API 取得訊息時出錯。", - "DialogAmiiboApiConnectErrorMessage": "無法連接到 Amiibo API 伺服器。伺服器可能已關閉,或你沒有連接到網際網路。", - "DialogProfileInvalidProfileErrorMessage": "配置檔案 {0} 與目前輸入系統不相容。", - "DialogProfileDefaultProfileOverwriteErrorMessage": "無法覆蓋預設的配置檔案", - "DialogProfileDeleteProfileTitle": "刪除帳戶", - "DialogProfileDeleteProfileMessage": "此操作不可撤銷, 您確定要繼續嗎?", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "從 API 取得資訊時出現錯誤。", + "DialogAmiiboApiConnectErrorMessage": "無法連接 Amiibo API 伺服器。服務可能已停機,或者您可能需要確認網際網路連線是否在線上。", + "DialogProfileInvalidProfileErrorMessage": "設定檔 {0} 與目前輸入配置系統不相容。", + "DialogProfileDefaultProfileOverwriteErrorMessage": "無法覆蓋預設設定檔", + "DialogProfileDeleteProfileTitle": "刪除設定檔", + "DialogProfileDeleteProfileMessage": "此動作不可復原,您確定要繼續嗎?", "DialogWarning": "警告", - "DialogPPTCDeletionMessage": "下一次重啟時將會重新建立以下遊戲的 PPTC 快取\n\n{0}\n\n你確定要繼續嗎?", - "DialogPPTCDeletionErrorMessage": "清除位於{0}的 PPTC 快取時出錯: {1}", - "DialogShaderDeletionMessage": "即將刪除以下遊戲的著色器快取:\n\n{0}\n\n你確定要繼續嗎?", - "DialogShaderDeletionErrorMessage": "清除{0}的著色器快取時出現錯誤: {1}", + "DialogPPTCDeletionMessage": "您將在下一次啟動時佇列重建以下遊戲的 PPTC:\n\n{0}\n\n您確定要繼續嗎?", + "DialogPPTCDeletionErrorMessage": "在 {0} 清除 PPTC 快取時出錯: {1}", + "DialogShaderDeletionMessage": "您將刪除以下遊戲的著色器快取:\n\n{0}\n\n您確定要繼續嗎?", + "DialogShaderDeletionErrorMessage": "在 {0} 清除著色器快取時出錯: {1}", "DialogRyujinxErrorMessage": "Ryujinx 遇到錯誤", - "DialogInvalidTitleIdErrorMessage": "UI 錯誤:所選遊戲沒有有效的標題ID", - "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "路徑{0}找不到有效的系統韌體。", - "DialogFirmwareInstallerFirmwareInstallTitle": "安裝韌體{0}", - "DialogFirmwareInstallerFirmwareInstallMessage": "將安裝{0}版本的系統。", - "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n這將替換目前系統版本{0}。", - "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "你確定要繼續嗎?", - "DialogFirmwareInstallerFirmwareInstallWaitMessage": "安裝韌體中...", - "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "成功安裝系統版本{0}。", - "DialogUserProfileDeletionWarningMessage": "刪除後將沒有可選擇的使用者帳戶", - "DialogUserProfileDeletionConfirmMessage": "你確定要刪除選擇中的帳戶嗎?", - "DialogUserProfileUnsavedChangesTitle": "警告 - 有未儲存的更改", - "DialogUserProfileUnsavedChangesMessage": "你對此帳戶所做的更改尚未儲存.", - "DialogUserProfileUnsavedChangesSubMessage": "你確定要捨棄更改嗎?", - "DialogControllerSettingsModifiedConfirmMessage": "目前的輸入配置檔案已更新", - "DialogControllerSettingsModifiedConfirmSubMessage": "你確定要儲存嗎?", - "DialogLoadNcaErrorMessage": "{0}. 錯誤的檔案: {1}", - "DialogDlcNoDlcErrorMessage": "選擇的檔案不包含所選遊戲的 DLC!", - "DialogPerformanceCheckLoggingEnabledMessage": "你啟用了跟蹤記錄,它的設計僅限開發人員使用。", - "DialogPerformanceCheckLoggingEnabledConfirmMessage": "為了獲得最佳效能,建議停用追蹤記錄。你是否要立即停用?", - "DialogPerformanceCheckShaderDumpEnabledMessage": "你啟用了著色器轉存,它的設計僅限開發人員使用。", - "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "為了獲得最佳效能,建議停用著色器轉存。你是否要立即停用?", - "DialogLoadAppGameAlreadyLoadedMessage": "目前已載入此遊戲", - "DialogLoadAppGameAlreadyLoadedSubMessage": "請停止模擬或關閉程式,再啟動另一個遊戲。", - "DialogUpdateAddUpdateErrorMessage": "選擇的檔案不包含所選遊戲的更新!", - "DialogSettingsBackendThreadingWarningTitle": "警告 - 後台多工執行中", - "DialogSettingsBackendThreadingWarningMessage": "更改此選項後必須重啟 Ryujinx 才能生效。根據你的硬體,您開啟該選項時,可能需要手動停用驅動程式本身的GPU多執行緒。", + "DialogInvalidTitleIdErrorMessage": "UI 錯誤: 所選遊戲沒有有效的遊戲 ID", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "在 {0} 中未發現有效的系統韌體。", + "DialogFirmwareInstallerFirmwareInstallTitle": "安裝韌體 {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "將安裝系統版本 {0}。", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n這將取代目前的系統版本 {0}。", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n您確定要繼續嗎?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "正在安裝韌體...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "成功安裝系統版本 {0}。", + "DialogUserProfileDeletionWarningMessage": "如果刪除選取的設定檔,將無法開啟其他設定檔", + "DialogUserProfileDeletionConfirmMessage": "您是否要刪除所選設定檔", + "DialogUserProfileUnsavedChangesTitle": "警告 - 未儲存的變更", + "DialogUserProfileUnsavedChangesMessage": "您對該使用者設定檔所做的變更尚未儲存。", + "DialogUserProfileUnsavedChangesSubMessage": "您確定要放棄變更嗎?", + "DialogControllerSettingsModifiedConfirmMessage": "目前控制器設定已更新。", + "DialogControllerSettingsModifiedConfirmSubMessage": "您想要儲存嗎?", + "DialogLoadFileErrorMessage": "{0}。出錯檔案: {1}", + "DialogModAlreadyExistsMessage": "模組已經存在", + "DialogModInvalidMessage": "指定資料夾不包含模組!", + "DialogModDeleteNoParentMessage": "刪除失敗: 無法找到模組「{0}」的父資料夾!", + "DialogDlcNoDlcErrorMessage": "指定檔案不包含所選遊戲的 DLC!", + "DialogPerformanceCheckLoggingEnabledMessage": "您已啟用追蹤日誌,該功能僅供開發者使用。", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "為獲得最佳效能,建議停用追蹤日誌。您是否要立即停用追蹤日誌嗎?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "您已啟用著色器傾印,該功能僅供開發者使用。", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "為獲得最佳效能,建議停用著色器傾印。您是否要立即停用著色器傾印嗎?", + "DialogLoadAppGameAlreadyLoadedMessage": "已載入此遊戲", + "DialogLoadAppGameAlreadyLoadedSubMessage": "請停止模擬或關閉模擬器,然後再啟動另一款遊戲。", + "DialogUpdateAddUpdateErrorMessage": "指定檔案不包含所選遊戲的更新!", + "DialogSettingsBackendThreadingWarningTitle": "警告 - 後端執行緒處理中", + "DialogSettingsBackendThreadingWarningMessage": "變更此選項後,必須重新啟動 Ryujinx 才能完全生效。使用 Ryujinx 的多執行緒功能時,可能需要手動停用驅動程式本身的多執行緒功能,這取決於您的平台。", + "DialogModManagerDeletionWarningMessage": "您將刪除模組: {0}\n\n您確定要繼續嗎?", + "DialogModManagerDeletionAllWarningMessage": "您即將刪除此遊戲的所有模組。\n\n您確定要繼續嗎?", "SettingsTabGraphicsFeaturesOptions": "功能", - "SettingsTabGraphicsBackendMultithreading": "圖像處理後台多線程支援:", - "CommonAuto": "自動(推薦)", + "SettingsTabGraphicsBackendMultithreading": "圖形後端多執行緒:", + "CommonAuto": "自動", "CommonOff": "關閉", - "CommonOn": "打開", + "CommonOn": "開啟", "InputDialogYes": "是", "InputDialogNo": "否", - "DialogProfileInvalidProfileNameErrorMessage": "檔案名包含無效字元,請重試。", + "DialogProfileInvalidProfileNameErrorMessage": "檔案名稱包含無效字元。請重試。", "MenuBarOptionsPauseEmulation": "暫停", "MenuBarOptionsResumeEmulation": "繼續", - "AboutUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的官方網站。", - "AboutDisclaimerMessage": "Ryujinx 與 Nintendo™ 並沒有任何關聯, 包括其合作伙伴, 及任何形式上。", - "AboutAmiiboDisclaimerMessage": "我們的 Amiibo 模擬使用了\nAmiiboAPI (www.amiiboapi.com) ", - "AboutPatreonUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 Patreon 贊助網頁。", - "AboutGithubUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 GitHub 儲存庫。", - "AboutDiscordUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 Discord 伺服器邀請連結。", - "AboutTwitterUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 Twitter 首頁。", - "AboutRyujinxAboutTitle": "關於:", - "AboutRyujinxAboutContent": "Ryujinx 是 Nintendo Switch™ 的一款模擬器。\n懇請您在 Patreon 上贊助我們。\n關注 Twitter 或 Discord 可以取得我們的最新動態。\n如果您對開發本軟體感興趣,歡迎來 GitHub 和 Discord 加入我們!", - "AboutRyujinxMaintainersTitle": "開發及維護名單:", - "AboutRyujinxMaintainersContentTooltipMessage": "在瀏覽器中打開貢獻者的網頁", - "AboutRyujinxSupprtersTitle": "Patreon 的贊助人:", + "AboutUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 網站。", + "AboutDisclaimerMessage": "Ryujinx 和 Nintendo™\n或其任何合作夥伴完全沒有關聯。", + "AboutAmiiboDisclaimerMessage": "我們在 Amiibo 模擬中\n使用了 AmiiboAPI (www.amiiboapi.com)。", + "AboutPatreonUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 Patreon 網頁。", + "AboutGithubUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 GitHub 網頁。", + "AboutDiscordUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 Discord 邀請連結。", + "AboutTwitterUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 Twitter 網頁。", + "AboutRyujinxAboutTitle": "關於:", + "AboutRyujinxAboutContent": "Ryujinx 是一款 Nintendo Switch™ 模擬器。\n請在 Patreon 上支持我們。\n關注我們的 Twitter 或 Discord 取得所有最新消息。\n對於有興趣貢獻的開發者,可以在我們的 GitHub 或 Discord 上了解更多資訊。", + "AboutRyujinxMaintainersTitle": "維護者:", + "AboutRyujinxMaintainersContentTooltipMessage": "在預設瀏覽器中開啟貢獻者的網頁", + "AboutRyujinxSupprtersTitle": "Patreon 支持者:", "AmiiboSeriesLabel": "Amiibo 系列", "AmiiboCharacterLabel": "角色", "AmiiboScanButtonLabel": "掃描", "AmiiboOptionsShowAllLabel": "顯示所有 Amiibo", - "AmiiboOptionsUsRandomTagLabel": "侵略:使用隨機標記的 Uuid 編碼", - "DlcManagerTableHeadingEnabledLabel": "已啟用", - "DlcManagerTableHeadingTitleIdLabel": "遊戲ID", - "DlcManagerTableHeadingContainerPathLabel": "資料夾路徑", + "AmiiboOptionsUsRandomTagLabel": "補釘修正: 使用隨機標記的 Uuid", + "DlcManagerTableHeadingEnabledLabel": "啟用", + "DlcManagerTableHeadingTitleIdLabel": "遊戲 ID", + "DlcManagerTableHeadingContainerPathLabel": "容器路徑", "DlcManagerTableHeadingFullPathLabel": "完整路徑", "DlcManagerRemoveAllButton": "全部刪除", - "DlcManagerEnableAllButton": "啟用全部", - "DlcManagerDisableAllButton": "停用全部", - "MenuBarOptionsChangeLanguage": "更改語言", + "DlcManagerEnableAllButton": "全部啟用", + "DlcManagerDisableAllButton": "全部停用", + "ModManagerDeleteAllButton": "全部刪除", + "MenuBarOptionsChangeLanguage": "變更語言", "MenuBarShowFileTypes": "顯示檔案類型", "CommonSort": "排序", "CommonShowNames": "顯示名稱", - "CommonFavorite": "收藏", + "CommonFavorite": "我的最愛", "OrderAscending": "從小到大", "OrderDescending": "從大到小", - "SettingsTabGraphicsFeatures": "功能及優化", + "SettingsTabGraphicsFeatures": "功能與改進", "ErrorWindowTitle": "錯誤視窗", "ToggleDiscordTooltip": "啟用或關閉 Discord 動態狀態展示", - "AddGameDirBoxTooltip": "輸入要添加的遊戲資料夾", - "AddGameDirTooltip": "添加遊戲資料夾到列表中", - "RemoveGameDirTooltip": "移除選擇中的遊戲資料夾", - "CustomThemeCheckTooltip": "啟用或關閉自訂佈景主題", - "CustomThemePathTooltip": "自訂佈景主題的資料夾", - "CustomThemeBrowseTooltip": "查找自訂佈景主題", - "DockModeToggleTooltip": "是否開啟 Switch 的 Docked 模式", - "DirectKeyboardTooltip": "支援鍵盤直接存取 (HID協定) . 可供給遊戲使用你的鍵盤作為輸入文字裝置.", - "DirectMouseTooltip": "支援滑鼠直接存取 (HID協定) . 可供給遊戲使用你的滑鼠作為瞄準裝置.", - "RegionTooltip": "更改系統區域", - "LanguageTooltip": "更改系統語言", - "TimezoneTooltip": "更改系統時區", - "TimeTooltip": "更改系統時鐘", - "VSyncToggleTooltip": "模擬遊戲主機垂直同步更新頻率. 重要地反映著遊戲本身的速度; 關閉它可能會令後使用動態更新率的遊戲速度過高, 或會引致載入錯誤等等.\n\n可在遊戲中利用自訂快速鍵開關此功能. 我們也建議使用快速鍵, 如果你計劃關上它.\n\n如果不確定請設定為\"開啟\".", - "PptcToggleTooltip": "開啟以後減少遊戲啟動時間和卡頓", - "FsIntegrityToggleTooltip": "是否檢查遊戲檔案內容的完整性", - "AudioBackendTooltip": "更改音效處理後台架構.\n\n推薦使用SDL2架構, 而OpenAL及SoundIO架構用作後備. Dummy是沒有音效的.\n\n如果不確定請設定為\"SDL2\".", - "MemoryManagerTooltip": "更改模擬器記憶體至電腦記憶體的映射和存取方式,極其影響CPU效能.\n\n如果不確定, 請設定為\"主機略過檢查模式\".", - "MemoryManagerSoftwareTooltip": "使用軟體虛擬分頁表換算, 最精確但是速度最慢.", - "MemoryManagerHostTooltip": "直接地映射模擬記憶體到電腦記憶體. 對 JIT 編譯和執行效率有顯著提升. ", - "MemoryManagerUnsafeTooltip": "直接地映射模擬記憶體到電腦記憶體, 但是不檢查記憶體溢出. 由於 Ryujinx 的子程式可以存取任何位置的記憶體, 因而相對不安全. 故在此模式下只應執行你信任的遊戲或軟體.", - "UseHypervisorTooltip": "使用 Hypervisor 代替 JIT。在本功能可用時就可以大幅增大效能,但目前狀態還不穩定。", - "DRamTooltip": "利用可選擇性的記憶體模式來模擬Switch發展中型號.\n\n此選項只會對高畫質材質包或4K模組有用. 而這並不會提升效能. \n\n如果不確定請關閉本功能.", - "IgnoreMissingServicesTooltip": "忽略某些未被實施的系統服務. 此功能有助於繞過當啟動遊戲時帶來的故障.\n\n如果不確定請關閉本功能。", - "GraphicsBackendThreadingTooltip": "執行雙線程後台繪圖指令, 能夠減少著色器編譯斷續, 並提高GPU驅動效能, 即將它不支持多線程處理. 而對於多線程處理也有少量提升.\n\n如果你不確定請設定為\"自動\"", - "GalThreadingTooltip": "執行雙線程後台繪圖指令.\n\n能夠加速著色器編譯及減少斷續, 並提高GPU驅動效能, 即將它不支持多線程處理. 而對於多線程處理也有少量提升.\n\n如果你不確定請設定為\"自動\"", - "ShaderCacheToggleTooltip": "儲存著色器快取到硬碟,減少存取斷續。\n\n如果不確定請設定為\"開啟\"。", - "ResolutionScaleTooltip": "解析度繪圖倍率", - "ResolutionScaleEntryTooltip": "盡量使用如1.5的浮點倍數。非整數的倍率易引起錯誤", - "AnisotropyTooltip": "各向異性過濾等級。提高傾斜視角材質的清晰度\n(選擇「自動」將使用遊戲預設指定的等級)", - "AspectRatioTooltip": "模擬器視窗解析度的長寬比", - "ShaderDumpPathTooltip": "圖形著色器轉存路徑", - "FileLogTooltip": "是否儲存日誌檔案到硬碟", - "StubLogTooltip": "在控制台顯示及記錄 Stub 訊息", - "InfoLogTooltip": "在控制台顯示及記錄資訊訊息", - "WarnLogTooltip": "在控制台顯示及記錄警告訊息\n", - "ErrorLogTooltip": "在控制台顯示及記錄錯誤訊息", - "TraceLogTooltip": "在控制台顯示及記錄追蹤訊息", - "GuestLogTooltip": "在控制台顯示及記錄賓客訊息", - "FileAccessLogTooltip": "在控制台顯示及記錄檔案存取訊息", - "FSAccessLogModeTooltip": "在控制台顯示及記錄FS 存取訊息. 可選的模式是 0-3", - "DeveloperOptionTooltip": "使用請謹慎", - "OpenGlLogLevel": "需要打開適當的日誌等級", - "DebugLogTooltip": "在控制台顯示及記錄除錯訊息.\n\n僅限於受訓的成員使用, 因為它很難理解而且令模擬的效能非常地差.\n", - "LoadApplicationFileTooltip": "選擇 Switch 支援的遊戲格式並載入", - "LoadApplicationFolderTooltip": "選擇解包後的 Switch 遊戲並載入", - "OpenRyujinxFolderTooltip": "開啟 Ryujinx 系統資料夾", - "OpenRyujinxLogsTooltip": "開啟存放日誌的資料夾", - "ExitTooltip": "關閉 Ryujinx", + "AddGameDirBoxTooltip": "輸入要新增到清單中的遊戲資料夾", + "AddGameDirTooltip": "新增遊戲資料夾到清單中", + "RemoveGameDirTooltip": "移除選取的遊戲資料夾", + "CustomThemeCheckTooltip": "為圖形使用者介面使用自訂 Avalonia 佈景主題,變更模擬器功能表的外觀", + "CustomThemePathTooltip": "自訂 GUI 佈景主題的路徑", + "CustomThemeBrowseTooltip": "瀏覽自訂 GUI 佈景主題", + "DockModeToggleTooltip": "底座模式可使模擬系統表現為底座的 Nintendo Switch。這可以提高大多數遊戲的圖形保真度。反之,停用該模式將使模擬系統表現為手提模式的 Nintendo Switch,從而降低圖形品質。\n\n如果計劃使用底座模式,請配置玩家 1 控制;如果計劃使用手提模式,請配置手提控制。\n\n如果不確定,請保持開啟狀態。", + "DirectKeyboardTooltip": "支援直接鍵盤存取 (HID)。遊戲可將鍵盤作為文字輸入裝置。\n\n僅適用於在 Switch 硬體上原生支援使用鍵盤的遊戲。\n\n如果不確定,請保持關閉狀態。", + "DirectMouseTooltip": "支援滑鼠直接存取 (HID)。遊戲可將滑鼠作為指向裝置使用。\n\n僅適用於在 Switch 硬體上原生支援滑鼠控制的遊戲,這類遊戲很少。\n\n啟用後,觸控螢幕功能可能無法使用。\n\n如果不確定,請保持關閉狀態。", + "RegionTooltip": "變更系統區域", + "LanguageTooltip": "變更系統語言", + "TimezoneTooltip": "變更系統時區", + "TimeTooltip": "變更系統時鐘", + "VSyncToggleTooltip": "模擬遊戲機的垂直同步。對大多數遊戲來說,它本質上是一個幀率限制器;停用它可能會導致遊戲以更高的速度執行,或使載入畫面耗時更長或卡住。\n\n可以在遊戲中使用快速鍵進行切換 (預設為 F1)。如果您打算停用,我們建議您這樣做。\n\n如果不確定,請保持開啟狀態。", + "PptcToggleTooltip": "儲存已轉譯的 JIT 函數,這樣每次載入遊戲時就無需再轉譯這些函數。\n\n減少遊戲首次啟動後的卡頓現象,並大大加快啟動時間。\n\n如果不確定,請保持開啟狀態。", + "FsIntegrityToggleTooltip": "在啟動遊戲時檢查損壞的檔案,如果檢測到損壞的檔案,則在日誌中顯示雜湊值錯誤。\n\n對效能沒有影響,旨在幫助排除故障。\n\n如果不確定,請保持開啟狀態。", + "AudioBackendTooltip": "變更用於繪製音訊的後端。\n\nSDL2 是首選,而 OpenAL 和 SoundIO 則作為備用。虛設 (Dummy) 將沒有聲音。\n\n如果不確定,請設定為 SDL2。", + "MemoryManagerTooltip": "變更客體記憶體的映射和存取方式。這會極大地影響模擬 CPU 效能。\n\n如果不確定,請設定為主體略過檢查模式。", + "MemoryManagerSoftwareTooltip": "使用軟體分頁表進行位址轉換。精度最高,但效能最差。", + "MemoryManagerHostTooltip": "直接映射主體位址空間中的記憶體。更快的 JIT 編譯和執行速度。", + "MemoryManagerUnsafeTooltip": "直接映射記憶體,但在存取前不封鎖客體位址空間內的位址。速度更快,但相對不安全。訪客應用程式可以從 Ryujinx 中的任何地方存取記憶體,因此只能使用該模式執行您信任的程式。", + "UseHypervisorTooltip": "使用 Hypervisor 取代 JIT。使用時可大幅提高效能,但在目前狀態下可能不穩定。", + "DRamTooltip": "利用另一種 MemoryMode 配置來模仿 Switch 開發模式。\n\n這僅對高解析度紋理套件或 4K 解析度模組有用。不會提高效能。\n\n如果不確定,請保持關閉狀態。", + "IgnoreMissingServicesTooltip": "忽略未實現的 Horizon OS 服務。這可能有助於在啟動某些遊戲時避免崩潰。\n\n如果不確定,請保持關閉狀態。", + "GraphicsBackendThreadingTooltip": "在第二個執行緒上執行圖形後端指令。\n\n在本身不支援多執行緒的 GPU 驅動程式上,可加快著色器編譯、減少卡頓並提高效能。在支援多執行緒的驅動程式上效能略有提升。\n\n如果不確定,請設定為自動。", + "GalThreadingTooltip": "在第二個執行緒上執行圖形後端指令。\n\n在本身不支援多執行緒的 GPU 驅動程式上,可加快著色器編譯、減少卡頓並提高效能。在支援多執行緒的驅動程式上效能略有提升。\n\n如果不確定,請設定為自動。", + "ShaderCacheToggleTooltip": "儲存磁碟著色器快取,減少後續執行時的卡頓。\n\n如果不確定,請保持開啟狀態。", + "ResolutionScaleTooltip": "使用倍數提升遊戲的繪製解析度。\n\n少數遊戲可能無法使用此功能,即使提高解析度也會顯得像素化;對於這些遊戲,您可能需要找到去除反鋸齒或提高內部繪製解析度的模組。對於後者,您可能需要選擇原生。\n\n此選項可在遊戲執行時透過點選下方的「套用」進行變更;您只需將設定視窗移到一旁,然後進行試驗,直到找到您喜歡的遊戲效果。\n\n請記住,4 倍幾乎對任何設定都是過度的。", + "ResolutionScaleEntryTooltip": "浮點解析度刻度,如 1.5。非整數刻度更容易出現問題或崩潰。", + "AnisotropyTooltip": "各向異性過濾等級。設定為自動可使用遊戲要求的值。", + "AspectRatioTooltip": "套用於繪製器視窗的長寬比。\n\n只有在遊戲中使用長寬比模組時才可變更,否則圖形會被拉伸。\n\n如果不確定,請保持 16:9 狀態。", + "ShaderDumpPathTooltip": "圖形著色器傾印路徑", + "FileLogTooltip": "將控制台日誌儲存到磁碟上的日誌檔案中。不會影響效能。", + "StubLogTooltip": "在控制台中輸出日誌訊息。不會影響效能。", + "InfoLogTooltip": "在控制台中輸出資訊日誌訊息。不會影響效能。", + "WarnLogTooltip": "在控制台中輸出警告日誌訊息。不會影響效能。", + "ErrorLogTooltip": "在控制台中輸出錯誤日誌訊息。不會影響效能。", + "TraceLogTooltip": "在控制台中輸出追蹤日誌訊息。不會影響效能。", + "GuestLogTooltip": "在控制台中輸出客體日誌訊息。不會影響效能。", + "FileAccessLogTooltip": "在控制台中輸出檔案存取日誌訊息。", + "FSAccessLogModeTooltip": "啟用檔案系統存取日誌輸出到控制台中。可能的模式為 0 到 3", + "DeveloperOptionTooltip": "謹慎使用", + "OpenGlLogLevel": "需要啟用適當的日誌等級", + "DebugLogTooltip": "在控制台中輸出偵錯日誌訊息。\n\n只有在人員特別指示的情況下才能使用,因為這會導致日誌難以閱讀,並降低模擬器效能。", + "LoadApplicationFileTooltip": "開啟檔案總管,選擇與 Switch 相容的檔案來載入", + "LoadApplicationFolderTooltip": "開啟檔案總管,選擇與 Switch 相容且解開封裝的應用程式來載入", + "OpenRyujinxFolderTooltip": "開啟 Ryujinx 檔案系統資料夾", + "OpenRyujinxLogsTooltip": "開啟日誌被寫入的資料夾", + "ExitTooltip": "結束 Ryujinx", "OpenSettingsTooltip": "開啟設定視窗", - "OpenProfileManagerTooltip": "開啟使用者帳戶管理視窗", - "StopEmulationTooltip": "停止執行目前遊戲並回到選擇界面", - "CheckUpdatesTooltip": "檢查 Ryujinx 新版本", + "OpenProfileManagerTooltip": "開啟使用者設定檔管理員視窗", + "StopEmulationTooltip": "停止模擬目前遊戲,返回遊戲選擇介面", + "CheckUpdatesTooltip": "檢查 Ryujinx 的更新", "OpenAboutTooltip": "開啟關於視窗", "GridSize": "網格尺寸", - "GridSizeTooltip": "調整網格模式的大小", - "SettingsTabSystemSystemLanguageBrazilianPortuguese": "巴西葡萄牙語", - "AboutRyujinxContributorsButtonHeader": "查看所有參與者", - "SettingsTabSystemAudioVolume": "音量:", + "GridSizeTooltip": "調整網格的大小", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "巴西葡萄牙文", + "AboutRyujinxContributorsButtonHeader": "查看所有貢獻者", + "SettingsTabSystemAudioVolume": "音量: ", "AudioVolumeTooltip": "調節音量", - "SettingsTabSystemEnableInternetAccess": "啟用網路連接", - "EnableInternetAccessTooltip": "開啟網路存取。此選項打開後,效果類似於 Switch 連接到網路的狀態。注意即使此選項關閉,應用程式偶爾也有可能連接到網路", - "GameListContextMenuManageCheatToolTip": "管理金手指", - "GameListContextMenuManageCheat": "管理金手指", - "ControllerSettingsStickRange": "範圍:", + "SettingsTabSystemEnableInternetAccess": "訪客網際網路存取/區域網路模式", + "EnableInternetAccessTooltip": "允許模擬應用程式連線網際網路。\n\n當啟用此功能且系統連線到同一接入點時,具有區域網路模式的遊戲可相互連線。這也包括真正的遊戲機。\n\n不允許連接 Nintendo 伺服器。可能會導致某些嘗試連線網際網路的遊戲崩潰。\n\n如果不確定,請保持關閉狀態。", + "GameListContextMenuManageCheatToolTip": "管理密技", + "GameListContextMenuManageCheat": "管理密技", + "GameListContextMenuManageModToolTip": "管理模組", + "GameListContextMenuManageMod": "管理模組", + "ControllerSettingsStickRange": "範圍:", "DialogStopEmulationTitle": "Ryujinx - 停止模擬", - "DialogStopEmulationMessage": "你確定要停止模擬嗎?", - "SettingsTabCpu": "處理器", + "DialogStopEmulationMessage": "您確定要停止模擬嗎?", + "SettingsTabCpu": "CPU", "SettingsTabAudio": "音訊", "SettingsTabNetwork": "網路", - "SettingsTabNetworkConnection": "網路連接", + "SettingsTabNetworkConnection": "網路連線", "SettingsTabCpuCache": "CPU 快取", "SettingsTabCpuMemory": "CPU 模式", "DialogUpdaterFlatpakNotSupportedMessage": "請透過 Flathub 更新 Ryujinx。", - "UpdaterDisabledWarningTitle": "更新已停用!", - "GameListContextMenuOpenSdModsDirectory": "開啟 Atmosphere 模組資料夾", - "GameListContextMenuOpenSdModsDirectoryToolTip": "開啟此遊戲額外的SD記憶卡Atmosphere模組資料夾. 有用於包裝在真實主機上的模組.\n", + "UpdaterDisabledWarningTitle": "更新已停用!", "ControllerSettingsRotate90": "順時針旋轉 90°", - "IconSize": "圖示尺寸", - "IconSizeTooltip": "更改遊戲圖示大小", + "IconSize": "圖示大小", + "IconSizeTooltip": "變更遊戲圖示的大小", "MenuBarOptionsShowConsole": "顯示控制台", - "ShaderCachePurgeError": "清除 {0} 著色器快取時出現錯誤: {1}", + "ShaderCachePurgeError": "在 {0} 清除著色器快取時出錯: {1}", "UserErrorNoKeys": "找不到金鑰", "UserErrorNoFirmware": "找不到韌體", "UserErrorFirmwareParsingFailed": "韌體解析錯誤", "UserErrorApplicationNotFound": "找不到應用程式", "UserErrorUnknown": "未知錯誤", "UserErrorUndefined": "未定義錯誤", - "UserErrorNoKeysDescription": "Ryujinx 找不到 『prod.keys』 檔案", - "UserErrorNoFirmwareDescription": "Ryujinx 找不到任何已安裝的韌體", - "UserErrorFirmwareParsingFailedDescription": "Ryujinx 無法解密選擇的韌體。這通常是由於金鑰過舊。", - "UserErrorApplicationNotFoundDescription": "Ryujinx 在選中路徑找不到有效的應用程式。", - "UserErrorUnknownDescription": "發生未知錯誤!", - "UserErrorUndefinedDescription": "發生了未定義錯誤!此類錯誤不應出現,請聯絡開發人員!", - "OpenSetupGuideMessage": "開啟設定教學", - "NoUpdate": "沒有新版本", - "TitleUpdateVersionLabel": "版本 {0} - {1}", - "RyujinxInfo": "Ryujinx - 訊息", + "UserErrorNoKeysDescription": "Ryujinx 無法找到您的「prod.keys」檔案", + "UserErrorNoFirmwareDescription": "Ryujinx 無法找到已安裝的任何韌體", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx 無法解析所提供的韌體。這通常是由於金鑰過時造成的。", + "UserErrorApplicationNotFoundDescription": "Ryujinx 無法在指定路徑下找到有效的應用程式。", + "UserErrorUnknownDescription": "發生未知錯誤!", + "UserErrorUndefinedDescription": "發生未定義錯誤! 這種情況不應該發生,請聯絡開發人員", + "OpenSetupGuideMessage": "開啟設定指南", + "NoUpdate": "沒有更新", + "TitleUpdateVersionLabel": "版本 {0}", + "RyujinxInfo": "Ryujinx - 資訊", "RyujinxConfirm": "Ryujinx - 確認", "FileDialogAllTypes": "全部類型", "Never": "從不", - "SwkbdMinCharacters": "至少應為 {0} 個字長", - "SwkbdMinRangeCharacters": "必須為 {0}-{1} 個字長", + "SwkbdMinCharacters": "長度必須至少為 {0} 個字元", + "SwkbdMinRangeCharacters": "長度必須為 {0} 到 {1} 個字元", "SoftwareKeyboard": "軟體鍵盤", - "SoftwareKeyboardModeNumbersOnly": "只接受數字", - "SoftwareKeyboardModeAlphabet": "不支援中日韓統一表意文字字元", - "SoftwareKeyboardModeASCII": "只接受 ASCII 符號", - "DialogControllerAppletMessagePlayerRange": "本遊戲需要 {0} 個玩家持有:\n\n類型:{1}\n\n玩家:{2}\n\n{3}請打開設定畫面並配置控制器,或者關閉本視窗。", - "DialogControllerAppletMessage": "本遊戲需要剛好 {0} 個玩家持有:\n\n類型:{1}\n\n玩家:{2}\n\n{3}請打開設定畫面並配置控制器,或者關閉本視窗。", - "DialogControllerAppletDockModeSet": "現在處於主機模式,無法使用掌機操作方式\n\n", + "SoftwareKeyboardModeNumeric": "必須是 0 到 9 或「.」", + "SoftwareKeyboardModeAlphabet": "必須是非中日韓字元 (non CJK)", + "SoftwareKeyboardModeASCII": "必須是 ASCII 文字", + "ControllerAppletControllers": "支援的控制器:", + "ControllerAppletPlayers": "玩家:", + "ControllerAppletDescription": "您目前的配置無效。開啟設定並重新配置輸入。", + "ControllerAppletDocked": "已設定底座模式。手提控制應該停用。", "UpdaterRenaming": "正在重新命名舊檔案...", - "UpdaterRenameFailed": "更新過程中無法重新命名檔案: {0}", - "UpdaterAddingFiles": "安裝更新中...", + "UpdaterRenameFailed": "更新程式無法重新命名檔案: {0}", + "UpdaterAddingFiles": "正在加入新檔案...", "UpdaterExtracting": "正在提取更新...", - "UpdaterDownloading": "下載新版本中...", + "UpdaterDownloading": "正在下載更新...", "Game": "遊戲", - "Docked": "主機模式", - "Handheld": "掌機模式", + "Docked": "底座模式", + "Handheld": "手提模式", "ConnectionError": "連接錯誤。", - "AboutPageDeveloperListMore": "{0} 等開發者...", - "ApiError": "API 錯誤", - "LoadingHeading": "正在啟動 {0}", - "CompilingPPTC": "編譯 PPTC 快取中", - "CompilingShaders": "編譯著色器中", + "AboutPageDeveloperListMore": "{0} 等人...", + "ApiError": "API 錯誤。", + "LoadingHeading": "正在載入 {0}", + "CompilingPPTC": "正在編譯 PTC", + "CompilingShaders": "正在編譯著色器", "AllKeyboards": "所有鍵盤", - "OpenFileDialogTitle": "選擇支援的檔案格式", - "OpenFolderDialogTitle": "選擇一個包含已解開封裝遊戲的資料夾\n", - "AllSupportedFormats": "全部支援的格式", + "OpenFileDialogTitle": "選取支援的檔案格式", + "OpenFolderDialogTitle": "選取解開封裝遊戲的資料夾", + "AllSupportedFormats": "所有支援的格式", "RyujinxUpdater": "Ryujinx 更新程式", - "SettingsTabHotkeys": "快捷鍵", + "SettingsTabHotkeys": "鍵盤快速鍵", "SettingsTabHotkeysHotkeys": "鍵盤快捷鍵", - "SettingsTabHotkeysToggleVsyncHotkey": "切換垂直同步:", - "SettingsTabHotkeysScreenshotHotkey": "截圖:", - "SettingsTabHotkeysShowUiHotkey": "隱藏使用者介面:", - "SettingsTabHotkeysPauseHotkey": "暫停:", - "SettingsTabHotkeysToggleMuteHotkey": "靜音:", - "ControllerMotionTitle": "體感操作設定", + "SettingsTabHotkeysToggleVsyncHotkey": "切換垂直同步:", + "SettingsTabHotkeysScreenshotHotkey": "擷取畫面:", + "SettingsTabHotkeysShowUiHotkey": "顯示 UI:", + "SettingsTabHotkeysPauseHotkey": "暫停:", + "SettingsTabHotkeysToggleMuteHotkey": "靜音:", + "ControllerMotionTitle": "體感控制設定", "ControllerRumbleTitle": "震動設定", - "SettingsSelectThemeFileDialogTitle": "選擇主題檔案", - "SettingsXamlThemeFile": "Xaml 主題檔案", - "AvatarWindowTitle": "管理帳號 - 頭貼", + "SettingsSelectThemeFileDialogTitle": "選取佈景主題檔案", + "SettingsXamlThemeFile": "Xaml 佈景主題檔案", + "AvatarWindowTitle": "管理帳戶 - 大頭貼", "Amiibo": "Amiibo", "Unknown": "未知", "Usage": "用途", "Writable": "可寫入", - "SelectDlcDialogTitle": "選擇 DLC 檔案", - "SelectUpdateDialogTitle": "選擇更新檔", - "UserProfileWindowTitle": "管理使用者帳戶", - "CheatWindowTitle": "管理遊戲金手指", - "DlcWindowTitle": "管理遊戲 DLC", - "UpdateWindowTitle": "管理遊戲更新", - "CheatWindowHeading": "金手指可用於 {0} [{1}]", - "BuildId": "版本編號:", - "DlcWindowHeading": "DLC 可用於 {0} [{1}]", + "SelectDlcDialogTitle": "選取 DLC 檔案", + "SelectUpdateDialogTitle": "選取更新檔", + "SelectModDialogTitle": "選取模組資料夾", + "UserProfileWindowTitle": "使用者設定檔管理員", + "CheatWindowTitle": "密技管理員", + "DlcWindowTitle": "管理 {0} 的可下載內容 ({1})", + "UpdateWindowTitle": "遊戲更新管理員", + "CheatWindowHeading": "可用於 {0} [{1}] 的密技", + "BuildId": "組建識別碼:", + "DlcWindowHeading": "{0} 個可下載內容", + "ModWindowHeading": "{0} 模組", "UserProfilesEditProfile": "編輯所選", "Cancel": "取消", "Save": "儲存", - "Discard": "放棄更改", - "UserProfilesSetProfileImage": "設定帳戶頭像", - "UserProfileEmptyNameError": "使用者名稱為必填", - "UserProfileNoImageError": "必須設定帳戶頭像", - "GameUpdateWindowHeading": "更新可用於 {0} [{1}]", - "SettingsTabHotkeysResScaleUpHotkey": "提高解析度:", - "SettingsTabHotkeysResScaleDownHotkey": "降低解析度:", - "UserProfilesName": "使用者名稱:", - "UserProfilesUserId": "使用者 ID:", - "SettingsTabGraphicsBackend": "圖像處理後台架構", - "SettingsTabGraphicsBackendTooltip": "用來圖像處理的後台架構", + "Discard": "放棄變更", + "Paused": "暫停", + "UserProfilesSetProfileImage": "設定設定檔圖像", + "UserProfileEmptyNameError": "名稱為必填", + "UserProfileNoImageError": "必須設定設定檔圖像", + "GameUpdateWindowHeading": "可用於 {0} ({1}) 的更新", + "SettingsTabHotkeysResScaleUpHotkey": "提高解析度:", + "SettingsTabHotkeysResScaleDownHotkey": "降低解析度:", + "UserProfilesName": "名稱:", + "UserProfilesUserId": "使用者 ID:", + "SettingsTabGraphicsBackend": "圖形後端", + "SettingsTabGraphicsBackendTooltip": "選擇模擬器將使用的圖形後端。\n\n只要驅動程式是最新的,Vulkan 對所有現代顯示卡來說都更好用。Vulkan 還能在所有 GPU 廠商上實現更快的著色器編譯 (減少卡頓)。\n\nOpenGL 在舊式 Nvidia GPU、Linux 上的舊式 AMD GPU 或 VRAM 較低的 GPU 上可能會取得更好的效果,不過著色器編譯的卡頓會更嚴重。\n\n如果不確定,請設定為 Vulkan。如果您的 GPU 使用最新的圖形驅動程式也不支援 Vulkan,請設定為 OpenGL。", "SettingsEnableTextureRecompression": "開啟材質重新壓縮", - "SettingsEnableTextureRecompressionTooltip": "壓縮某些材質以減少 VRAM 使用。\n\n推薦用於小於 4GiB VRAM 的 GPU。\n\n如果不確定請關閉本功能。", - "SettingsTabGraphicsPreferredGpu": "優先選取的 GPU", - "SettingsTabGraphicsPreferredGpuTooltip": "選擇支持運行Vulkan圖像處理架構的GPU.\n\n此設定不會影響GPU運行OpenGL.\n\n如果不確定, 請設定標籤為\"dGPU\"的GPU. 如果沒有, 請保留原始設定.", - "SettingsAppRequiredRestartMessage": "必須重啟 Ryujinx", - "SettingsGpuBackendRestartMessage": "圖像處理後台架構或GPU相關設定已被修改。需要重新啟動才能套用。", - "SettingsGpuBackendRestartSubMessage": "你確定要現在重新啟動嗎?", - "RyujinxUpdaterMessage": "你確定要將 Ryujinx 更新到最新版本嗎?", - "SettingsTabHotkeysVolumeUpHotkey": "增加音量:", - "SettingsTabHotkeysVolumeDownHotkey": "降低音量:", + "SettingsEnableTextureRecompressionTooltip": "壓縮 ASTC 紋理,以減少 VRAM 占用。\n\n使用這種紋理格式的遊戲包括 Astral Chain、Bayonetta 3、Fire Emblem Engage、Metroid Prime Remastered、Super Mario Bros. Wonder 和 The Legend of Zelda: Tears of the Kingdom。\n\n使用 4GB 或更低 VRAM 的顯示卡在執行這些遊戲時可能會崩潰。\n\n只有在上述遊戲的 VRAM 即將耗盡時才啟用。如果不確定,請保持關閉狀態。", + "SettingsTabGraphicsPreferredGpu": "首選 GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "選擇將與 Vulkan 圖形後端一起使用的顯示卡。\n\n不會影響 OpenGL 將使用的 GPU。\n\n如果不確定,請設定為標記為「dGPU」的 GPU。如果沒有,則保持原狀。", + "SettingsAppRequiredRestartMessage": "需要重新啟動 Ryujinx", + "SettingsGpuBackendRestartMessage": "圖形後端或 GPU 設定已修改。這需要重新啟動才能套用。", + "SettingsGpuBackendRestartSubMessage": "您現在要重新啟動嗎?", + "RyujinxUpdaterMessage": "您想將 Ryujinx 升級到最新版本嗎?", + "SettingsTabHotkeysVolumeUpHotkey": "提高音量:", + "SettingsTabHotkeysVolumeDownHotkey": "降低音量:", "SettingsEnableMacroHLE": "啟用 Macro HLE", - "SettingsEnableMacroHLETooltip": "GPU 微代碼的高階模擬。\n\n可以提升效能,但可能會導致某些遊戲出現圖形顯示故障。\n\n如果不確定請設定為\"開啟\"。", + "SettingsEnableMacroHLETooltip": "GPU 巨集程式碼的進階模擬。\n\n可提高效能,但在某些遊戲中可能會導致圖形閃爍。\n\n如果不確定,請保持開啟狀態。", "SettingsEnableColorSpacePassthrough": "色彩空間直通", - "SettingsEnableColorSpacePassthroughTooltip": "指揮Vulkan後端直通色彩資訊而不需指定色彩空間,對於廣色域顯示的使用者,這會造成較多抖色,犧牲色彩正確性。", + "SettingsEnableColorSpacePassthroughTooltip": "指示 Vulkan 後端在不指定色彩空間的情況下傳遞色彩資訊。對於使用廣色域顯示器的使用者來說,這可能會帶來更鮮艷的色彩,但代價是犧牲色彩的正確性。", "VolumeShort": "音量", - "UserProfilesManageSaves": "管理遊戲存檔", - "DeleteUserSave": "你確定要刪除此遊戲的存檔嗎?", - "IrreversibleActionNote": "本動作將無法挽回。", - "SaveManagerHeading": "管理 {0} 的遊戲存檔", - "SaveManagerTitle": "遊戲存檔管理器", + "UserProfilesManageSaves": "管理存檔", + "DeleteUserSave": "您想刪除此遊戲的使用者存檔嗎?", + "IrreversibleActionNote": "此動作將無法復原。", + "SaveManagerHeading": "管理 {0} 的存檔", + "SaveManagerTitle": "存檔管理員", "Name": "名稱", "Size": "大小", "Search": "搜尋", - "UserProfilesRecoverLostAccounts": "恢復遺失的帳戶", - "Recover": "恢復", - "UserProfilesRecoverHeading": "在以下帳戶找到了一些遊戲存檔", - "UserProfilesRecoverEmptyList": "沒有可恢復的使用者帳戶", - "GraphicsAATooltip": "在遊戲繪圖上套用抗鋸齒", - "GraphicsAALabel": "抗鋸齒:", - "GraphicsScalingFilterLabel": "縮放過濾器:", - "GraphicsScalingFilterTooltip": "啟用畫幀緩衝區縮放", - "GraphicsScalingFilterLevelLabel": "記錄檔等級", - "GraphicsScalingFilterLevelTooltip": "設定縮放過濾器的強度", + "UserProfilesRecoverLostAccounts": "復原遺失的帳戶", + "Recover": "復原", + "UserProfilesRecoverHeading": "發現下列帳戶有一些存檔", + "UserProfilesRecoverEmptyList": "無設定檔可復原", + "GraphicsAATooltip": "對遊戲繪製進行反鋸齒處理。\n\nFXAA 會模糊大部分圖像,而 SMAA 則會嘗試找出鋸齒邊緣並將其平滑化。\n\n不建議與 FSR 縮放濾鏡一起使用。\n\n此選項可在遊戲執行時透過點選下方的「套用」進行變更;您只需將設定視窗移到一旁,然後進行試驗,直到找到您喜歡的遊戲效果。\n\n如果不確定,請選擇無狀態。", + "GraphicsAALabel": "反鋸齒:", + "GraphicsScalingFilterLabel": "縮放過濾器:", + "GraphicsScalingFilterTooltip": "選擇使用解析度縮放時套用的縮放過濾器。\n\nBilinear (雙線性) 濾鏡適用於 3D 遊戲,是一個安全的預設選項。\n\n建議像素美術遊戲使用 Nearest (最近性) 濾鏡。\n\nFSR 1.0 只是一個銳化濾鏡,不建議與 FXAA 或 SMAA 一起使用。\n\n此選項可在遊戲執行時透過點選下方的「套用」進行變更;您只需將設定視窗移到一旁,然後進行試驗,直到找到您喜歡的遊戲效果。\n\n如果不確定,請保持 Bilinear (雙線) 狀態。", + "GraphicsScalingFilterLevelLabel": "日誌等級", + "GraphicsScalingFilterLevelTooltip": "設定 FSR 1.0 銳化等級。越高越清晰。", "SmaaLow": "低階 SMAA", "SmaaMedium": "中階 SMAA", "SmaaHigh": "高階 SMAA", "SmaaUltra": "超高階 SMAA", "UserEditorTitle": "編輯使用者", "UserEditorTitleCreate": "建立使用者", - "SettingsTabNetworkInterface": "網路介面:", - "NetworkInterfaceTooltip": "用於具有 LAN 功能的網路介面", + "SettingsTabNetworkInterface": "網路介面:", + "NetworkInterfaceTooltip": "用於 LAN/LDN 功能的網路介面。\n\n與 VPN 或 XLink Kai 以及支援區域網路的遊戲配合使用,可用於在網路上偽造同網際網路連線。\n\n如果不確定,請保持預設狀態。", "NetworkInterfaceDefault": "預設", "PackagingShaders": "著色器封裝", - "AboutChangelogButton": "在 GitHub 查看更新日誌", - "AboutChangelogButtonTooltipMessage": "在瀏覽器中打開此Ryujinx版本的更新日誌。" -} \ No newline at end of file + "AboutChangelogButton": "在 GitHub 上檢視更新日誌", + "AboutChangelogButtonTooltipMessage": "在預設瀏覽器中開啟此版本的更新日誌。", + "SettingsTabNetworkMultiplayer": "多人遊戲", + "MultiplayerMode": "模式:", + "MultiplayerModeTooltip": "變更 LDN 多人遊戲模式。\n\nLdnMitm 將修改遊戲中的本機無線/本機遊戲功能,使其如同區域網路一樣執行,允許與其他安裝了 ldn_mitm 模組的 Ryujinx 實例和已破解的 Nintendo Switch 遊戲機進行本機同網路連線。\n\n多人遊戲要求所有玩家使用相同的遊戲版本 (例如,Super Smash Bros. Ultimate v13.0.1 無法連接 v13.0.0)。\n\n如果不確定,請保持 Disabled (停用) 狀態。" +} diff --git a/src/Ryujinx/Common/Locale/LocaleManager.cs b/src/Ryujinx/Common/Locale/LocaleManager.cs index 2d3deeaa3..257611e65 100644 --- a/src/Ryujinx/Common/Locale/LocaleManager.cs +++ b/src/Ryujinx/Common/Locale/LocaleManager.cs @@ -104,7 +104,7 @@ public bool IsRTL() { return _localeLanguageCode switch { - "he_IL" => true, + "ar_SA" or "he_IL" => true, _ => false }; } diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj index 2a5a9fadd..38453f2c6 100644 --- a/src/Ryujinx/Ryujinx.csproj +++ b/src/Ryujinx/Ryujinx.csproj @@ -1,4 +1,4 @@ - + net8.0 win-x64;osx-x64;linux-x64 @@ -110,6 +110,7 @@ + @@ -122,6 +123,7 @@ + @@ -135,6 +137,7 @@ + @@ -147,6 +150,7 @@ + From 20a280525f10c4c6a0a2486ddf33b6d04946d1b9 Mon Sep 17 00:00:00 2001 From: WilliamWsyHK Date: Fri, 22 Mar 2024 03:08:25 +0800 Subject: [PATCH 119/126] [UI] Fix Display Name Translations & Update some Chinese Translations (#6388) * Remove incorrect additional back-slash * Touch-up on TChinese translations * Little touch-up on SChinese translations --- src/Ryujinx/Assets/Locales/ar_SA.json | 2 +- src/Ryujinx/Assets/Locales/en_US.json | 2 +- src/Ryujinx/Assets/Locales/fr_FR.json | 2 +- src/Ryujinx/Assets/Locales/he_IL.json | 2 +- src/Ryujinx/Assets/Locales/ja_JP.json | 2 +- src/Ryujinx/Assets/Locales/ko_KR.json | 2 +- src/Ryujinx/Assets/Locales/th_TH.json | 2 +- src/Ryujinx/Assets/Locales/uk_UA.json | 2 +- src/Ryujinx/Assets/Locales/zh_CN.json | 2 +- src/Ryujinx/Assets/Locales/zh_TW.json | 26 +++++++++++++------------- 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Ryujinx/Assets/Locales/ar_SA.json b/src/Ryujinx/Assets/Locales/ar_SA.json index 1fac4850c..57ca5b0ae 100644 --- a/src/Ryujinx/Assets/Locales/ar_SA.json +++ b/src/Ryujinx/Assets/Locales/ar_SA.json @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "تم حفظ السمة. إعادة التشغيل مطلوبة لتطبيق السمة.", "DialogThemeRestartSubMessage": "هل تريد إعادة التشغيل", "DialogFirmwareInstallEmbeddedMessage": "هل ترغب في تثبيت البرنامج الثابت المدمج في هذه اللعبة؟ (البرنامج الثابت {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "لم يتم العثور على أي برنامج ثابت مثبت ولكن Ryujinx كان قادراً على تثبيت البرنامج الثابت {0} من اللعبة المقدمة.\\nسيبدأ المحاكي الآن.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "لم يتم العثور على أي برنامج ثابت مثبت ولكن Ryujinx كان قادراً على تثبيت البرنامج الثابت {0} من اللعبة المقدمة.\nسيبدأ المحاكي الآن.", "DialogFirmwareNoFirmwareInstalledMessage": "لا يوجد برنامج ثابت مثبت", "DialogFirmwareInstalledMessage": "تم تثبيت البرنامج الثابت {0}", "DialogInstallFileTypesSuccessMessage": "تم تثبيت أنواع الملفات بنجاح!", diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json index 2febf90ec..efc4525c0 100644 --- a/src/Ryujinx/Assets/Locales/en_US.json +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "Theme has been saved. A restart is needed to apply the theme.", "DialogThemeRestartSubMessage": "Do you want to restart", "DialogFirmwareInstallEmbeddedMessage": "Would you like to install the firmware embedded in this game? (Firmware {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\\nThe emulator will now start.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\nThe emulator will now start.", "DialogFirmwareNoFirmwareInstalledMessage": "No Firmware Installed", "DialogFirmwareInstalledMessage": "Firmware {0} was installed", "DialogInstallFileTypesSuccessMessage": "Successfully installed file types!", diff --git a/src/Ryujinx/Assets/Locales/fr_FR.json b/src/Ryujinx/Assets/Locales/fr_FR.json index 1c84573c2..b1501d848 100644 --- a/src/Ryujinx/Assets/Locales/fr_FR.json +++ b/src/Ryujinx/Assets/Locales/fr_FR.json @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "Le thème a été enregistré. Un redémarrage est requis pour appliquer le thème.", "DialogThemeRestartSubMessage": "Voulez-vous redémarrer", "DialogFirmwareInstallEmbeddedMessage": "Voulez-vous installer le firmware intégré dans ce jeu ? (Firmware {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "Aucun firmware installé n'a été trouvé mais Ryujinx a pu installer le firmware {0} à partir du jeu fourni.\\nL'émulateur va maintenant démarrer.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Aucun firmware installé n'a été trouvé mais Ryujinx a pu installer le firmware {0} à partir du jeu fourni.\nL'émulateur va maintenant démarrer.", "DialogFirmwareNoFirmwareInstalledMessage": "Aucun Firmware installé", "DialogFirmwareInstalledMessage": "Le firmware {0} a été installé", "DialogInstallFileTypesSuccessMessage": "Types de fichiers installés avec succès!", diff --git a/src/Ryujinx/Assets/Locales/he_IL.json b/src/Ryujinx/Assets/Locales/he_IL.json index 5ba56c269..afa92f8d2 100644 --- a/src/Ryujinx/Assets/Locales/he_IL.json +++ b/src/Ryujinx/Assets/Locales/he_IL.json @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "ערכת הנושא נשמרה. יש צורך בהפעלה מחדש כדי להחיל את ערכת הנושא.", "DialogThemeRestartSubMessage": "האם ברצונך להפעיל מחדש?", "DialogFirmwareInstallEmbeddedMessage": "האם תרצו להתקין את הקושחה המוטמעת במשחק הזה? (קושחה {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "לא נמצאה קושחה מותקנת אבל ריוג'ינקס הצליח להתקין קושחה {0} מהמשחק שסופק. \\nהאמולטור יופעל כעת.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "לא נמצאה קושחה מותקנת אבל ריוג'ינקס הצליח להתקין קושחה {0} מהמשחק שסופק. \nהאמולטור יופעל כעת.", "DialogFirmwareNoFirmwareInstalledMessage": "לא מותקנת קושחה", "DialogFirmwareInstalledMessage": "הקושחה {0} הותקנה", "DialogInstallFileTypesSuccessMessage": "סוגי קבצים הותקנו בהצלחה!", diff --git a/src/Ryujinx/Assets/Locales/ja_JP.json b/src/Ryujinx/Assets/Locales/ja_JP.json index ce4f07c41..b57d15e2e 100644 --- a/src/Ryujinx/Assets/Locales/ja_JP.json +++ b/src/Ryujinx/Assets/Locales/ja_JP.json @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "テーマがセーブされました. テーマを適用するには再起動が必要です.", "DialogThemeRestartSubMessage": "再起動しますか", "DialogFirmwareInstallEmbeddedMessage": "このゲームに含まれるファームウェアをインストールしてよろしいですか? (ファームウェア {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "ファームウェアがインストールされていませんが, ゲームに含まれるファームウェア {0} をインストールできます.\\nエミュレータが開始します.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "ファームウェアがインストールされていませんが, ゲームに含まれるファームウェア {0} をインストールできます.\nエミュレータが開始します.", "DialogFirmwareNoFirmwareInstalledMessage": "ファームウェアがインストールされていません", "DialogFirmwareInstalledMessage": "ファームウェア {0} がインストールされました", "DialogInstallFileTypesSuccessMessage": "ファイル形式のインストールに成功しました!", diff --git a/src/Ryujinx/Assets/Locales/ko_KR.json b/src/Ryujinx/Assets/Locales/ko_KR.json index 1db5215e5..133a51b63 100644 --- a/src/Ryujinx/Assets/Locales/ko_KR.json +++ b/src/Ryujinx/Assets/Locales/ko_KR.json @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "테마가 저장되었습니다. 테마를 적용하려면 다시 시작해야 합니다.", "DialogThemeRestartSubMessage": "다시 시작하겠습니까?", "DialogFirmwareInstallEmbeddedMessage": "이 게임에 내장된 펌웨어를 설치하겠습니까? (펌웨어 {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "설치된 펌웨어가 없지만 Ryujinx가 제공된 게임에서 펌웨어 {0}을(를) 설치할 수 있었습니다.\\n이제 에뮬레이터가 시작됩니다.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "설치된 펌웨어가 없지만 Ryujinx가 제공된 게임에서 펌웨어 {0}을(를) 설치할 수 있었습니다.\n이제 에뮬레이터가 시작됩니다.", "DialogFirmwareNoFirmwareInstalledMessage": "설치된 펌웨어 없음", "DialogFirmwareInstalledMessage": "펌웨어 {0}이(가) 설치됨", "DialogInstallFileTypesSuccessMessage": "파일 형식을 성공적으로 설치했습니다!", diff --git a/src/Ryujinx/Assets/Locales/th_TH.json b/src/Ryujinx/Assets/Locales/th_TH.json index 61f50c7c2..e2132f1a4 100644 --- a/src/Ryujinx/Assets/Locales/th_TH.json +++ b/src/Ryujinx/Assets/Locales/th_TH.json @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "บันทึกธีมแล้ว จำเป็นต้องรีสตาร์ทเพื่อใช้ธีม", "DialogThemeRestartSubMessage": "คุณต้องการรีสตาร์ทหรือไม่?", "DialogFirmwareInstallEmbeddedMessage": "คุณต้องการติดตั้งเฟิร์มแวร์ที่ฝังอยู่ในเกมนี้หรือไม่? (เฟิร์มแวร์ {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "ไม่พบเฟิร์มแวร์ที่ติดตั้งไว้ แต่ รียูจินซ์ สามารถติดตั้งเฟิร์มแวร์ได้ {0} จากเกมที่ให้มา\\nตอนนี้โปรแกรมจำลองจะเริ่มทำงาน", + "DialogFirmwareInstallEmbeddedSuccessMessage": "ไม่พบเฟิร์มแวร์ที่ติดตั้งไว้ แต่ รียูจินซ์ สามารถติดตั้งเฟิร์มแวร์ได้ {0} จากเกมที่ให้มา\nตอนนี้โปรแกรมจำลองจะเริ่มทำงาน", "DialogFirmwareNoFirmwareInstalledMessage": "ไม่มีการติดตั้งเฟิร์มแวร์", "DialogFirmwareInstalledMessage": "เฟิร์มแวร์ทำการติดตั้งแล้ว {0}", "DialogInstallFileTypesSuccessMessage": "ติดตั้งประเภทไฟล์สำเร็จแล้ว!", diff --git a/src/Ryujinx/Assets/Locales/uk_UA.json b/src/Ryujinx/Assets/Locales/uk_UA.json index 61af5c32a..a77135dca 100644 --- a/src/Ryujinx/Assets/Locales/uk_UA.json +++ b/src/Ryujinx/Assets/Locales/uk_UA.json @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "Тему збережено. Щоб застосувати тему, потрібен перезапуск.", "DialogThemeRestartSubMessage": "Ви хочете перезапустити", "DialogFirmwareInstallEmbeddedMessage": "Бажаєте встановити прошивку, вбудовану в цю гру? (Прошивка {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "Встановлену прошивку не знайдено, але Ryujinx вдалося встановити прошивку {0} з наданої гри.\\nТепер запуститься емулятор.", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Встановлену прошивку не знайдено, але Ryujinx вдалося встановити прошивку {0} з наданої гри.\nТепер запуститься емулятор.", "DialogFirmwareNoFirmwareInstalledMessage": "Прошивка не встановлена", "DialogFirmwareInstalledMessage": "Встановлено прошивку {0}", "DialogInstallFileTypesSuccessMessage": "Успішно встановлено типи файлів!", diff --git a/src/Ryujinx/Assets/Locales/zh_CN.json b/src/Ryujinx/Assets/Locales/zh_CN.json index ce07385c1..fcd76f50d 100644 --- a/src/Ryujinx/Assets/Locales/zh_CN.json +++ b/src/Ryujinx/Assets/Locales/zh_CN.json @@ -412,7 +412,7 @@ "MenuBarOptionsResumeEmulation": "继续", "AboutUrlTooltipMessage": "在浏览器中打开 Ryujinx 模拟器官网。", "AboutDisclaimerMessage": "Ryujinx 与 Nintendo™ 以及其合作伙伴没有任何关联。", - "AboutAmiiboDisclaimerMessage": "我们的 Amiibo 模拟使用了\nAmiiboAPI (www.amiiboapi.com) ", + "AboutAmiiboDisclaimerMessage": "我们的 Amiibo 模拟使用了\nAmiiboAPI (www.amiiboapi.com)。", "AboutPatreonUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Patreon 赞助页。", "AboutGithubUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 GitHub 代码库。", "AboutDiscordUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Discord 邀请链接。", diff --git a/src/Ryujinx/Assets/Locales/zh_TW.json b/src/Ryujinx/Assets/Locales/zh_TW.json index d9df134f7..4d3a78d1a 100644 --- a/src/Ryujinx/Assets/Locales/zh_TW.json +++ b/src/Ryujinx/Assets/Locales/zh_TW.json @@ -127,8 +127,8 @@ "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "美洲西班牙文", "SettingsTabSystemSystemLanguageSimplifiedChinese": "簡體中文", "SettingsTabSystemSystemLanguageTraditionalChinese": "正體中文 (建議)", - "SettingsTabSystemSystemTimeZone": "系統時區:", - "SettingsTabSystemSystemTime": "系統時鐘:", + "SettingsTabSystemSystemTimeZone": "系統時區:", + "SettingsTabSystemSystemTime": "系統時鐘:", "SettingsTabSystemEnableVsync": "垂直同步", "SettingsTabSystemEnablePptc": "PPTC (剖析式持久轉譯快取, Profiled Persistent Translation Cache)", "SettingsTabSystemEnableFsIntegrityChecks": "檔案系統完整性檢查", @@ -138,9 +138,9 @@ "SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemHacks": "補釘修正", - "SettingsTabSystemHacksNote": "可能導致不穩定", - "SettingsTabSystemExpandDramSize": "使用其他記憶體配置 (開發者)", - "SettingsTabSystemIgnoreMissingServices": "忽略遺失的服務", + "SettingsTabSystemHacksNote": "可能導致模擬器不穩定", + "SettingsTabSystemExpandDramSize": "使用替代的記憶體配置 (開發者專用)", + "SettingsTabSystemIgnoreMissingServices": "忽略缺少的模擬器功能", "SettingsTabGraphics": "圖形", "SettingsTabGraphicsAPI": "圖形 API", "SettingsTabGraphicsEnableShaderCache": "啟用著色器快取", @@ -204,7 +204,7 @@ "ControllerSettingsHandheld": "手提模式", "ControllerSettingsInputDevice": "輸入裝置", "ControllerSettingsRefresh": "重新整理", - "ControllerSettingsDeviceDisabled": "停用", + "ControllerSettingsDeviceDisabled": "已停用", "ControllerSettingsControllerType": "控制器類型", "ControllerSettingsControllerTypeHandheld": "手提模式", "ControllerSettingsControllerTypeProController": "Pro 控制器", @@ -341,7 +341,7 @@ "DialogThemeRestartMessage": "佈景主題設定已儲存。需要重新啟動才能套用主題。", "DialogThemeRestartSubMessage": "您要重新啟動嗎", "DialogFirmwareInstallEmbeddedMessage": "您想安裝遊戲內建的韌體嗎? (韌體 {0})", - "DialogFirmwareInstallEmbeddedSuccessMessage": "未找到已安裝的韌體,但 Ryujinx 可以從現有的遊戲安裝韌體{0}.\\n模擬器現在可以執行。", + "DialogFirmwareInstallEmbeddedSuccessMessage": "未找到已安裝的韌體,但 Ryujinx 可以從現有的遊戲安裝韌體{0}。\n模擬器現在可以執行。", "DialogFirmwareNoFirmwareInstalledMessage": "未安裝韌體", "DialogFirmwareInstalledMessage": "已安裝韌體{0}", "DialogInstallFileTypesSuccessMessage": "成功安裝檔案類型!", @@ -398,8 +398,8 @@ "DialogUpdateAddUpdateErrorMessage": "指定檔案不包含所選遊戲的更新!", "DialogSettingsBackendThreadingWarningTitle": "警告 - 後端執行緒處理中", "DialogSettingsBackendThreadingWarningMessage": "變更此選項後,必須重新啟動 Ryujinx 才能完全生效。使用 Ryujinx 的多執行緒功能時,可能需要手動停用驅動程式本身的多執行緒功能,這取決於您的平台。", - "DialogModManagerDeletionWarningMessage": "您將刪除模組: {0}\n\n您確定要繼續嗎?", - "DialogModManagerDeletionAllWarningMessage": "您即將刪除此遊戲的所有模組。\n\n您確定要繼續嗎?", + "DialogModManagerDeletionWarningMessage": "您將刪除模組: {0}\n\n您確定要繼續嗎?", + "DialogModManagerDeletionAllWarningMessage": "您即將刪除此遊戲的所有模組。\n\n您確定要繼續嗎?", "SettingsTabGraphicsFeaturesOptions": "功能", "SettingsTabGraphicsBackendMultithreading": "圖形後端多執行緒:", "CommonAuto": "自動", @@ -418,7 +418,7 @@ "AboutDiscordUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 Discord 邀請連結。", "AboutTwitterUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 Twitter 網頁。", "AboutRyujinxAboutTitle": "關於:", - "AboutRyujinxAboutContent": "Ryujinx 是一款 Nintendo Switch™ 模擬器。\n請在 Patreon 上支持我們。\n關注我們的 Twitter 或 Discord 取得所有最新消息。\n對於有興趣貢獻的開發者,可以在我們的 GitHub 或 Discord 上了解更多資訊。", + "AboutRyujinxAboutContent": "Ryujinx 是一款 Nintendo Switch™ 模擬器。\n請在 Patreon 上支持我們。\n關注我們的 Twitter 或 Discord 取得所有最新消息。\n對於有興趣貢獻的開發者,可以在我們的 GitHub 或 Discord 上了解更多資訊。", "AboutRyujinxMaintainersTitle": "維護者:", "AboutRyujinxMaintainersContentTooltipMessage": "在預設瀏覽器中開啟貢獻者的網頁", "AboutRyujinxSupprtersTitle": "Patreon 支持者:", @@ -427,7 +427,7 @@ "AmiiboScanButtonLabel": "掃描", "AmiiboOptionsShowAllLabel": "顯示所有 Amiibo", "AmiiboOptionsUsRandomTagLabel": "補釘修正: 使用隨機標記的 Uuid", - "DlcManagerTableHeadingEnabledLabel": "啟用", + "DlcManagerTableHeadingEnabledLabel": "已啟用", "DlcManagerTableHeadingTitleIdLabel": "遊戲 ID", "DlcManagerTableHeadingContainerPathLabel": "容器路徑", "DlcManagerTableHeadingFullPathLabel": "完整路徑", @@ -538,7 +538,7 @@ "UserErrorFirmwareParsingFailedDescription": "Ryujinx 無法解析所提供的韌體。這通常是由於金鑰過時造成的。", "UserErrorApplicationNotFoundDescription": "Ryujinx 無法在指定路徑下找到有效的應用程式。", "UserErrorUnknownDescription": "發生未知錯誤!", - "UserErrorUndefinedDescription": "發生未定義錯誤! 這種情況不應該發生,請聯絡開發人員", + "UserErrorUndefinedDescription": "發生未定義錯誤! 這種情況不應該發生,請聯絡開發人員!", "OpenSetupGuideMessage": "開啟設定指南", "NoUpdate": "沒有更新", "TitleUpdateVersionLabel": "版本 {0}", @@ -550,7 +550,7 @@ "SwkbdMinRangeCharacters": "長度必須為 {0} 到 {1} 個字元", "SoftwareKeyboard": "軟體鍵盤", "SoftwareKeyboardModeNumeric": "必須是 0 到 9 或「.」", - "SoftwareKeyboardModeAlphabet": "必須是非中日韓字元 (non CJK)", + "SoftwareKeyboardModeAlphabet": "必須是非「中日韓字元」 (non CJK)", "SoftwareKeyboardModeASCII": "必須是 ASCII 文字", "ControllerAppletControllers": "支援的控制器:", "ControllerAppletPlayers": "玩家:", From c94a73ec60f3f75b36179cbc93d046701ed96253 Mon Sep 17 00:00:00 2001 From: Matt Heins Date: Thu, 21 Mar 2024 20:44:11 -0400 Subject: [PATCH 120/126] Updates the default value for BufferedQuery (#6351) AMD GPUs (possibly just RDNA 3) could hang with the previous value until the MaxQueryRetries was hit. Fix #6056 Co-authored-by: riperiperi --- src/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs b/src/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs index 3fdc5afa5..714cb2833 100644 --- a/src/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs +++ b/src/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs @@ -10,8 +10,8 @@ namespace Ryujinx.Graphics.Vulkan.Queries class BufferedQuery : IDisposable { private const int MaxQueryRetries = 5000; - private const long DefaultValue = -1; - private const long DefaultValueInt = 0xFFFFFFFF; + private const long DefaultValue = unchecked((long)0xFFFFFFFEFFFFFFFE); + private const long DefaultValueInt = 0xFFFFFFFE; private const ulong HighMask = 0xFFFFFFFF00000000; private readonly Vk _api; From dbfe859ed717c82d87d6d497fe1d647278f19284 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sat, 23 Mar 2024 16:31:54 -0300 Subject: [PATCH 121/126] Add a few missing locale strings on Avalonia (#6556) * Add a few missing locale strings on Avalonia * Rename LDN MitM to ldn_mitm --- src/Ryujinx/Assets/Locales/en_US.json | 7 ++++++- src/Ryujinx/UI/ViewModels/SettingsViewModel.cs | 5 ----- .../UI/Views/Settings/SettingsGraphicsView.axaml | 6 +++--- .../UI/Views/Settings/SettingsNetworkView.axaml | 10 ++++++++-- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json index efc4525c0..3a3cb3017 100644 --- a/src/Ryujinx/Assets/Locales/en_US.json +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -648,6 +648,9 @@ "GraphicsAALabel": "Anti-Aliasing:", "GraphicsScalingFilterLabel": "Scaling Filter:", "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", "GraphicsScalingFilterLevelLabel": "Level", "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", "SmaaLow": "SMAA Low", @@ -664,5 +667,7 @@ "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser.", "SettingsTabNetworkMultiplayer": "Multiplayer", "MultiplayerMode": "Mode:", - "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure." + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" } diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index bcaa08600..fde8f74ae 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -237,11 +237,6 @@ public AvaloniaList NetworkInterfaceList get => new(_networkInterfaces.Keys); } - public AvaloniaList MultiplayerModes - { - get => new(Enum.GetNames()); - } - public KeyboardHotkeys KeyboardHotkeys { get => _keyboardHotkeys; diff --git a/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml index 224494786..5cffc6848 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml @@ -165,13 +165,13 @@ ToolTip.Tip="{locale:Locale GraphicsScalingFilterTooltip}" SelectedIndex="{Binding ScalingFilter}"> - + - + - + + Width="250"> + + + + + + + From 43514771bf22b3d2f666e83f0b7c7ab515821d7f Mon Sep 17 00:00:00 2001 From: SamusAranX Date: Sat, 23 Mar 2024 21:33:27 +0100 Subject: [PATCH 122/126] New gamecard icons (#6557) * Replaced all gamecard images and added a blank variant * File optimization --- .../Resources/Icon_Blank.png | Bin 0 -> 16179 bytes src/Ryujinx.UI.Common/Resources/Icon_NCA.png | Bin 10190 -> 18432 bytes src/Ryujinx.UI.Common/Resources/Icon_NRO.png | Bin 10254 -> 18404 bytes src/Ryujinx.UI.Common/Resources/Icon_NSO.png | Bin 10354 -> 18705 bytes src/Ryujinx.UI.Common/Resources/Icon_NSP.png | Bin 9899 -> 18260 bytes src/Ryujinx.UI.Common/Resources/Icon_XCI.png | Bin 9809 -> 18220 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/Ryujinx.UI.Common/Resources/Icon_Blank.png diff --git a/src/Ryujinx.UI.Common/Resources/Icon_Blank.png b/src/Ryujinx.UI.Common/Resources/Icon_Blank.png new file mode 100644 index 0000000000000000000000000000000000000000..d2bba8a92f5dd5254d10460b9f6f0af468f8a1af GIT binary patch literal 16179 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 RX(j*w002ovPDHLkV1lP50o?!q literal 0 HcmV?d00001 diff --git a/src/Ryujinx.UI.Common/Resources/Icon_NCA.png b/src/Ryujinx.UI.Common/Resources/Icon_NCA.png index feae77b943f2b6f4498b6f2f5fb49e5e5726b933..99def6cfd74712bffad207f9186a78f4ae141cd1 100644 GIT binary patch literal 18432 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 literal 10190 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 LNkvXXu0mjfJl$nS literal 10254 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 Utxt6oQ~_w*)mAB0vVQ(Q0RPcgegFUf diff --git a/src/Ryujinx.UI.Common/Resources/Icon_NSO.png b/src/Ryujinx.UI.Common/Resources/Icon_NSO.png index 16de84bedbefaead611e52a86a83913cde292e06..ad88459cfc9515dbf5f8ee0a909eb35d078e37e8 100644 GIT binary patch literal 18705 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 fAl6k?RX6Gf@e(gT(sglwS(!HRBfOI1zBHg{j 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 diff --git a/src/Ryujinx.UI.Common/Resources/Icon_NSP.png b/src/Ryujinx.UI.Common/Resources/Icon_NSP.png index 4f98e22eafda5553d04615dba312b927693c278a..a0cef62f4c98e434d33f0554a54ad812025d959d 100644 GIT binary patch literal 18260 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 Ef(IoLLI3~& literal 9899 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 diff --git a/src/Ryujinx.UI.Common/Resources/Icon_XCI.png b/src/Ryujinx.UI.Common/Resources/Icon_XCI.png index f9c34f47f13e6db43afcef5a520888a2caa51044..ef33f281620a5998b261a385c1be245c6febc391 100644 GIT binary patch literal 18220 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 U2G4v@egFUf07*qoM6N<$g7fX;jsO4v literal 9809 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 IE87J9H?dhqZ2$lO From 72bdc24db8047b015490182b6156c67b31f88669 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Tue, 26 Mar 2024 22:27:48 -0300 Subject: [PATCH 123/126] Disable push descriptors for Intel ARC GPUs on Windows (#6551) * Move some init logic out of PrintGpuInformation, then delete it * Disable push descriptors for Intel ARC on Windows * Re-add PrintGpuInformation just to show it in the log --- .../ShaderCollection.cs | 8 +- src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs | 94 ++++++++++--------- 2 files changed, 58 insertions(+), 44 deletions(-) diff --git a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs index 7f687fb4c..e4ea0e4e6 100644 --- a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs +++ b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs @@ -111,8 +111,8 @@ public ShaderCollection( bool usePushDescriptors = !isMinimal && VulkanConfiguration.UsePushDescriptors && _gd.Capabilities.SupportsPushDescriptors && - !_gd.IsNvidiaPreTuring && !IsCompute && + !HasPushDescriptorsBug(gd) && CanUsePushDescriptors(gd, resourceLayout, IsCompute); ReadOnlyCollection sets = usePushDescriptors ? @@ -147,6 +147,12 @@ public ShaderCollection( _firstBackgroundUse = !fromCache; } + private static bool HasPushDescriptorsBug(VulkanRenderer gd) + { + // Those GPUs/drivers do not work properly with push descriptors, so we must force disable them. + return gd.IsNvidiaPreTuring || (gd.IsIntelArc && gd.IsIntelWindows); + } + private static bool CanUsePushDescriptors(VulkanRenderer gd, ResourceLayout layout, bool isCompute) { // If binding 3 is immediately used, use an alternate set of reserved bindings. diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index 7d7c10952..ede54a6fc 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -87,6 +87,7 @@ public sealed class VulkanRenderer : IRenderer internal bool IsIntelWindows { get; private set; } internal bool IsAmdGcn { get; private set; } internal bool IsNvidiaPreTuring { get; private set; } + internal bool IsIntelArc { get; private set; } internal bool IsMoltenVk { get; private set; } internal bool IsTBDR { get; private set; } internal bool IsSharedMemory { get; private set; } @@ -310,6 +311,51 @@ private unsafe void LoadFeatures(uint maxQueueCount, uint queueFamilyIndex) ref var properties = ref properties2.Properties; + var hasDriverProperties = _physicalDevice.TryGetPhysicalDeviceDriverPropertiesKHR(Api, out var driverProperties); + + string vendorName = VendorUtils.GetNameFromId(properties.VendorID); + + Vendor = VendorUtils.FromId(properties.VendorID); + + IsAmdWindows = Vendor == Vendor.Amd && OperatingSystem.IsWindows(); + IsIntelWindows = Vendor == Vendor.Intel && OperatingSystem.IsWindows(); + IsTBDR = + Vendor == Vendor.Apple || + Vendor == Vendor.Qualcomm || + Vendor == Vendor.ARM || + Vendor == Vendor.Broadcom || + Vendor == Vendor.ImgTec; + + GpuVendor = vendorName; + GpuDriver = hasDriverProperties ? Marshal.PtrToStringAnsi((IntPtr)driverProperties.DriverName) : vendorName; // Fall back to vendor name if driver name isn't available. + + fixed (byte* deviceName = properties.DeviceName) + { + GpuRenderer = Marshal.PtrToStringAnsi((IntPtr)deviceName); + } + + GpuVersion = $"Vulkan v{ParseStandardVulkanVersion(properties.ApiVersion)}, Driver v{ParseDriverVersion(ref properties)}"; + + IsAmdGcn = !IsMoltenVk && Vendor == Vendor.Amd && VendorUtils.AmdGcnRegex().IsMatch(GpuRenderer); + + if (Vendor == Vendor.Nvidia) + { + var match = VendorUtils.NvidiaConsumerClassRegex().Match(GpuRenderer); + + if (match != null && int.TryParse(match.Groups[2].Value, out int gpuNumber)) + { + IsNvidiaPreTuring = gpuNumber < 2000; + } + else if (GpuDriver.Contains("TITAN") && !GpuDriver.Contains("RTX")) + { + IsNvidiaPreTuring = true; + } + } + else if (Vendor == Vendor.Intel) + { + IsIntelArc = GpuRenderer.StartsWith("Intel(R) Arc(TM)"); + } + ulong minResourceAlignment = Math.Max( Math.Max( properties.Limits.MinStorageBufferOffsetAlignment, @@ -732,49 +778,6 @@ private static string ParseDriverVersion(ref PhysicalDeviceProperties properties return ParseStandardVulkanVersion(driverVersionRaw); } - private unsafe void PrintGpuInformation() - { - var properties = _physicalDevice.PhysicalDeviceProperties; - - var hasDriverProperties = _physicalDevice.TryGetPhysicalDeviceDriverPropertiesKHR(Api, out var driverProperties); - - string vendorName = VendorUtils.GetNameFromId(properties.VendorID); - - Vendor = VendorUtils.FromId(properties.VendorID); - - IsAmdWindows = Vendor == Vendor.Amd && OperatingSystem.IsWindows(); - IsIntelWindows = Vendor == Vendor.Intel && OperatingSystem.IsWindows(); - IsTBDR = - Vendor == Vendor.Apple || - Vendor == Vendor.Qualcomm || - Vendor == Vendor.ARM || - Vendor == Vendor.Broadcom || - Vendor == Vendor.ImgTec; - - GpuVendor = vendorName; - GpuDriver = hasDriverProperties ? Marshal.PtrToStringAnsi((IntPtr)driverProperties.DriverName) : vendorName; // Fall back to vendor name if driver name isn't available. - GpuRenderer = Marshal.PtrToStringAnsi((IntPtr)properties.DeviceName); - GpuVersion = $"Vulkan v{ParseStandardVulkanVersion(properties.ApiVersion)}, Driver v{ParseDriverVersion(ref properties)}"; - - IsAmdGcn = !IsMoltenVk && Vendor == Vendor.Amd && VendorUtils.AmdGcnRegex().IsMatch(GpuRenderer); - - if (Vendor == Vendor.Nvidia) - { - var match = VendorUtils.NvidiaConsumerClassRegex().Match(GpuRenderer); - - if (match != null && int.TryParse(match.Groups[2].Value, out int gpuNumber)) - { - IsNvidiaPreTuring = gpuNumber < 2000; - } - else if (GpuDriver.Contains("TITAN") && !GpuDriver.Contains("RTX")) - { - IsNvidiaPreTuring = true; - } - } - - Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})"); - } - internal PrimitiveTopology TopologyRemap(PrimitiveTopology topology) { return topology switch @@ -798,6 +801,11 @@ internal bool TopologyUnsupported(PrimitiveTopology topology) }; } + private void PrintGpuInformation() + { + Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})"); + } + public void Initialize(GraphicsDebugLevel logLevel) { SetupContext(logLevel); From f6d24449b6e1ebe753c0a8095a435820c0948f19 Mon Sep 17 00:00:00 2001 From: jcm Date: Tue, 26 Mar 2024 20:54:13 -0500 Subject: [PATCH 124/126] Recreate swapchain correctly when toggling VSync (#6521) Co-authored-by: jcm --- src/Ryujinx/AppHost.cs | 9 +++++++-- src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml.cs | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index 2620ea68c..868d194fb 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -420,6 +420,12 @@ private void UpdateMultiplayerModeState(object sender, ReactiveEventArgs Date: Tue, 26 Mar 2024 23:33:24 -0300 Subject: [PATCH 125/126] Implement host tracked memory manager mode (#6356) * Add host tracked memory manager mode * Skipping flush is no longer needed * Formatting + revert unrelated change * LightningJit: Ensure that dest register is saved for load ops that do partial updates * avoid allocations when doing address space lookup Add missing improvement * IsRmwMemory -> IsPartialRegisterUpdateMemory * Ensure we iterate all private allocations in range * PR feedback and potential fixes * Simplified bridges a lot * Skip calling SignalMappingChanged if Guest is true * Late map bridge too * Force address masking for prefetch instructions * Reprotection for bridges * Move partition list validation to separate debug method * Move host tracked related classes to HostTracked folder * New HostTracked namespace * Move host tracked modes to the end of enum to avoid PPTC invalidation --------- Co-authored-by: riperiperi --- .../Instructions/InstEmitMemoryHelper.cs | 31 +- src/ARMeilleure/Memory/MemoryManagerType.cs | 22 + .../Signal/NativeSignalHandlerGenerator.cs | 101 ++- .../Collections/IntrusiveRedBlackTreeNode.cs | 8 +- .../AppleHv/HvMemoryBlockAllocator.cs | 2 +- .../AddressIntrusiveRedBlackTree.cs | 35 + .../Jit/HostTracked/AddressSpacePartition.cs | 708 ++++++++++++++++++ .../AddressSpacePartitionAllocator.cs | 202 +++++ .../AddressSpacePartitionMultiAllocation.cs | 101 +++ .../HostTracked/AddressSpacePartitioned.cs | 407 ++++++++++ .../Jit/HostTracked/NativePageTable.cs | 223 ++++++ src/Ryujinx.Cpu/Jit/JitCpuContext.cs | 4 +- .../Jit/MemoryManagerHostTracked.cs | 627 ++++++++++++++++ .../Arm32/Target/Arm64/InstEmitMemory.cs | 18 +- .../LightningJit/Arm64/InstName.cs | 32 + .../LightningJit/Arm64/RegisterAllocator.cs | 19 +- .../LightningJit/Arm64/RegisterUtils.cs | 4 +- .../Arm64/Target/Arm64/Compiler.cs | 2 +- .../Arm64/Target/Arm64/Decoder.cs | 7 +- .../Arm64/Target/Arm64/InstEmitMemory.cs | 46 +- src/Ryujinx.Cpu/LightningJit/Translator.cs | 4 +- src/Ryujinx.Cpu/MemoryEhMeilleure.cs | 32 +- src/Ryujinx.Cpu/PrivateMemoryAllocator.cs | 8 +- src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs | 11 +- .../Image/TextureGroup.cs | 8 - .../Memory/PhysicalMemory.cs | 5 - .../HOS/ArmProcessContextFactory.cs | 19 +- .../HOS/Kernel/Memory/KPageTable.cs | 25 + src/Ryujinx.Memory/AddressSpaceManager.cs | 4 +- src/Ryujinx.Memory/Tracking/VirtualRegion.cs | 7 +- 30 files changed, 2647 insertions(+), 75 deletions(-) create mode 100644 src/Ryujinx.Cpu/Jit/HostTracked/AddressIntrusiveRedBlackTree.cs create mode 100644 src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartition.cs create mode 100644 src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitionAllocator.cs create mode 100644 src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitionMultiAllocation.cs create mode 100644 src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitioned.cs create mode 100644 src/Ryujinx.Cpu/Jit/HostTracked/NativePageTable.cs create mode 100644 src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs diff --git a/src/ARMeilleure/Instructions/InstEmitMemoryHelper.cs b/src/ARMeilleure/Instructions/InstEmitMemoryHelper.cs index a807eed51..ace6fe1ce 100644 --- a/src/ARMeilleure/Instructions/InstEmitMemoryHelper.cs +++ b/src/ARMeilleure/Instructions/InstEmitMemoryHelper.cs @@ -157,7 +157,7 @@ public static Operand EmitReadInt(ArmEmitterContext context, Operand address, in context.Copy(temp, value); - if (!context.Memory.Type.IsHostMapped()) + if (!context.Memory.Type.IsHostMappedOrTracked()) { context.Branch(lblEnd); @@ -198,7 +198,7 @@ private static void EmitReadInt(ArmEmitterContext context, Operand address, int SetInt(context, rt, value); - if (!context.Memory.Type.IsHostMapped()) + if (!context.Memory.Type.IsHostMappedOrTracked()) { context.Branch(lblEnd); @@ -265,7 +265,7 @@ private static void EmitReadVector( context.Copy(GetVec(rt), value); - if (!context.Memory.Type.IsHostMapped()) + if (!context.Memory.Type.IsHostMappedOrTracked()) { context.Branch(lblEnd); @@ -312,7 +312,7 @@ private static void EmitWriteInt(ArmEmitterContext context, Operand address, int break; } - if (!context.Memory.Type.IsHostMapped()) + if (!context.Memory.Type.IsHostMappedOrTracked()) { context.Branch(lblEnd); @@ -385,7 +385,7 @@ private static void EmitWriteVector( break; } - if (!context.Memory.Type.IsHostMapped()) + if (!context.Memory.Type.IsHostMappedOrTracked()) { context.Branch(lblEnd); @@ -403,6 +403,27 @@ public static Operand EmitPtPointerLoad(ArmEmitterContext context, Operand addre { return EmitHostMappedPointer(context, address); } + else if (context.Memory.Type.IsHostTracked()) + { + if (address.Type == OperandType.I32) + { + address = context.ZeroExtend32(OperandType.I64, address); + } + + if (context.Memory.Type == MemoryManagerType.HostTracked) + { + Operand mask = Const(ulong.MaxValue >> (64 - context.Memory.AddressSpaceBits)); + address = context.BitwiseAnd(address, mask); + } + + Operand ptBase = !context.HasPtc + ? Const(context.Memory.PageTablePointer.ToInt64()) + : Const(context.Memory.PageTablePointer.ToInt64(), Ptc.PageTableSymbol); + + Operand ptOffset = context.ShiftRightUI(address, Const(PageBits)); + + return context.Add(address, context.Load(OperandType.I64, context.Add(ptBase, context.ShiftLeft(ptOffset, Const(3))))); + } int ptLevelBits = context.Memory.AddressSpaceBits - PageBits; int ptLevelSize = 1 << ptLevelBits; diff --git a/src/ARMeilleure/Memory/MemoryManagerType.cs b/src/ARMeilleure/Memory/MemoryManagerType.cs index b1cdbb069..bc8ae2635 100644 --- a/src/ARMeilleure/Memory/MemoryManagerType.cs +++ b/src/ARMeilleure/Memory/MemoryManagerType.cs @@ -29,6 +29,18 @@ public enum MemoryManagerType /// Allows invalid access from JIT code to the rest of the program, but is faster. /// HostMappedUnsafe, + + /// + /// High level implementation using a software flat page table for address translation + /// with no support for handling invalid or non-contiguous memory access. + /// + HostTracked, + + /// + /// High level implementation using a software flat page table for address translation + /// without masking the address and no support for handling invalid or non-contiguous memory access. + /// + HostTrackedUnsafe, } public static class MemoryManagerTypeExtensions @@ -37,5 +49,15 @@ public static bool IsHostMapped(this MemoryManagerType type) { return type == MemoryManagerType.HostMapped || type == MemoryManagerType.HostMappedUnsafe; } + + public static bool IsHostTracked(this MemoryManagerType type) + { + return type == MemoryManagerType.HostTracked || type == MemoryManagerType.HostTrackedUnsafe; + } + + public static bool IsHostMappedOrTracked(this MemoryManagerType type) + { + return type.IsHostMapped() || type.IsHostTracked(); + } } } diff --git a/src/ARMeilleure/Signal/NativeSignalHandlerGenerator.cs b/src/ARMeilleure/Signal/NativeSignalHandlerGenerator.cs index c5e708e16..2ec5bc1b3 100644 --- a/src/ARMeilleure/Signal/NativeSignalHandlerGenerator.cs +++ b/src/ARMeilleure/Signal/NativeSignalHandlerGenerator.cs @@ -21,10 +21,8 @@ public static class NativeSignalHandlerGenerator private const uint EXCEPTION_ACCESS_VIOLATION = 0xc0000005; - private static Operand EmitGenericRegionCheck(EmitterContext context, IntPtr signalStructPtr, Operand faultAddress, Operand isWrite, int rangeStructSize, ulong pageSize) + private static Operand EmitGenericRegionCheck(EmitterContext context, IntPtr signalStructPtr, Operand faultAddress, Operand isWrite, int rangeStructSize) { - ulong pageMask = pageSize - 1; - Operand inRegionLocal = context.AllocateLocal(OperandType.I32); context.Copy(inRegionLocal, Const(0)); @@ -51,7 +49,7 @@ private static Operand EmitGenericRegionCheck(EmitterContext context, IntPtr sig // Only call tracking if in range. context.BranchIfFalse(nextLabel, inRange, BasicBlockFrequency.Cold); - Operand offset = context.BitwiseAnd(context.Subtract(faultAddress, rangeAddress), Const(~pageMask)); + Operand offset = context.Subtract(faultAddress, rangeAddress); // Call the tracking action, with the pointer's relative offset to the base address. Operand trackingActionPtr = context.Load(OperandType.I64, Const((ulong)signalStructPtr + rangeBaseOffset + 20)); @@ -62,8 +60,10 @@ private static Operand EmitGenericRegionCheck(EmitterContext context, IntPtr sig // Tracking action should be non-null to call it, otherwise assume false return. context.BranchIfFalse(skipActionLabel, trackingActionPtr); - Operand result = context.Call(trackingActionPtr, OperandType.I32, offset, Const(pageSize), isWrite); - context.Copy(inRegionLocal, result); + Operand result = context.Call(trackingActionPtr, OperandType.I64, offset, Const(1UL), isWrite); + context.Copy(inRegionLocal, context.ICompareNotEqual(result, Const(0UL))); + + GenerateFaultAddressPatchCode(context, faultAddress, result); context.MarkLabel(skipActionLabel); @@ -155,7 +155,7 @@ private static Operand GenerateUnixWriteFlag(EmitterContext context, Operand uco throw new PlatformNotSupportedException(); } - public static byte[] GenerateUnixSignalHandler(IntPtr signalStructPtr, int rangeStructSize, ulong pageSize) + public static byte[] GenerateUnixSignalHandler(IntPtr signalStructPtr, int rangeStructSize) { EmitterContext context = new(); @@ -168,7 +168,7 @@ public static byte[] GenerateUnixSignalHandler(IntPtr signalStructPtr, int range Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1. - Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite, rangeStructSize, pageSize); + Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite, rangeStructSize); Operand endLabel = Label(); @@ -203,7 +203,7 @@ public static byte[] GenerateUnixSignalHandler(IntPtr signalStructPtr, int range return Compiler.Compile(cfg, argTypes, OperandType.None, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Code; } - public static byte[] GenerateWindowsSignalHandler(IntPtr signalStructPtr, int rangeStructSize, ulong pageSize) + public static byte[] GenerateWindowsSignalHandler(IntPtr signalStructPtr, int rangeStructSize) { EmitterContext context = new(); @@ -232,7 +232,7 @@ public static byte[] GenerateWindowsSignalHandler(IntPtr signalStructPtr, int ra Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1. - Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite, rangeStructSize, pageSize); + Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite, rangeStructSize); Operand endLabel = Label(); @@ -256,5 +256,86 @@ public static byte[] GenerateWindowsSignalHandler(IntPtr signalStructPtr, int ra return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Code; } + + private static void GenerateFaultAddressPatchCode(EmitterContext context, Operand faultAddress, Operand newAddress) + { + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + if (SupportsFaultAddressPatchingForHostOs()) + { + Operand lblSkip = Label(); + + context.BranchIf(lblSkip, faultAddress, newAddress, Comparison.Equal); + + Operand ucontextPtr = context.LoadArgument(OperandType.I64, 2); + Operand pcCtxAddress = default; + ulong baseRegsOffset = 0; + + if (OperatingSystem.IsLinux()) + { + pcCtxAddress = context.Add(ucontextPtr, Const(440UL)); + baseRegsOffset = 184UL; + } + else if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS()) + { + ucontextPtr = context.Load(OperandType.I64, context.Add(ucontextPtr, Const(48UL))); + + pcCtxAddress = context.Add(ucontextPtr, Const(272UL)); + baseRegsOffset = 16UL; + } + + Operand pc = context.Load(OperandType.I64, pcCtxAddress); + + Operand reg = GetAddressRegisterFromArm64Instruction(context, pc); + Operand reg64 = context.ZeroExtend32(OperandType.I64, reg); + Operand regCtxAddress = context.Add(ucontextPtr, context.Add(context.ShiftLeft(reg64, Const(3)), Const(baseRegsOffset))); + Operand regAddress = context.Load(OperandType.I64, regCtxAddress); + + Operand addressDelta = context.Subtract(regAddress, faultAddress); + + context.Store(regCtxAddress, context.Add(newAddress, addressDelta)); + + context.MarkLabel(lblSkip); + } + } + } + + private static Operand GetAddressRegisterFromArm64Instruction(EmitterContext context, Operand pc) + { + Operand inst = context.Load(OperandType.I32, pc); + Operand reg = context.AllocateLocal(OperandType.I32); + + Operand isSysInst = context.ICompareEqual(context.BitwiseAnd(inst, Const(0xFFF80000)), Const(0xD5080000)); + + Operand lblSys = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblSys, isSysInst, BasicBlockFrequency.Cold); + + context.Copy(reg, context.BitwiseAnd(context.ShiftRightUI(inst, Const(5)), Const(0x1F))); + context.Branch(lblEnd); + + context.MarkLabel(lblSys); + context.Copy(reg, context.BitwiseAnd(inst, Const(0x1F))); + + context.MarkLabel(lblEnd); + + return reg; + } + + public static bool SupportsFaultAddressPatchingForHost() + { + return SupportsFaultAddressPatchingForHostArch() && SupportsFaultAddressPatchingForHostOs(); + } + + private static bool SupportsFaultAddressPatchingForHostArch() + { + return RuntimeInformation.ProcessArchitecture == Architecture.Arm64; + } + + private static bool SupportsFaultAddressPatchingForHostOs() + { + return OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS(); + } } } diff --git a/src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeNode.cs b/src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeNode.cs index 8480d51ad..29d2d0c9a 100644 --- a/src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeNode.cs +++ b/src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeNode.cs @@ -5,10 +5,10 @@ namespace Ryujinx.Common.Collections /// public class IntrusiveRedBlackTreeNode where T : IntrusiveRedBlackTreeNode { - internal bool Color = true; - internal T Left; - internal T Right; - internal T Parent; + public bool Color = true; + public T Left; + public T Right; + public T Parent; public T Predecessor => IntrusiveRedBlackTreeImpl.PredecessorOf((T)this); public T Successor => IntrusiveRedBlackTreeImpl.SuccessorOf((T)this); diff --git a/src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocator.cs b/src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocator.cs index 4e3723d55..86936c592 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocator.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocator.cs @@ -38,7 +38,7 @@ public override void Destroy() private readonly HvIpaAllocator _ipaAllocator; - public HvMemoryBlockAllocator(HvIpaAllocator ipaAllocator, int blockAlignment) : base(blockAlignment, MemoryAllocationFlags.None) + public HvMemoryBlockAllocator(HvIpaAllocator ipaAllocator, ulong blockAlignment) : base(blockAlignment, MemoryAllocationFlags.None) { _ipaAllocator = ipaAllocator; } diff --git a/src/Ryujinx.Cpu/Jit/HostTracked/AddressIntrusiveRedBlackTree.cs b/src/Ryujinx.Cpu/Jit/HostTracked/AddressIntrusiveRedBlackTree.cs new file mode 100644 index 000000000..0e2443303 --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/HostTracked/AddressIntrusiveRedBlackTree.cs @@ -0,0 +1,35 @@ +using Ryujinx.Common.Collections; +using System; + +namespace Ryujinx.Cpu.Jit.HostTracked +{ + internal class AddressIntrusiveRedBlackTree : IntrusiveRedBlackTree where T : IntrusiveRedBlackTreeNode, IComparable, IComparable + { + /// + /// Retrieve the node that is considered equal to the specified address by the comparator. + /// + /// Address to compare with + /// Node that is equal to + public T GetNode(ulong address) + { + T node = Root; + while (node != null) + { + int cmp = node.CompareTo(address); + if (cmp < 0) + { + node = node.Left; + } + else if (cmp > 0) + { + node = node.Right; + } + else + { + return node; + } + } + return null; + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartition.cs b/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartition.cs new file mode 100644 index 000000000..224c5edc3 --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartition.cs @@ -0,0 +1,708 @@ +using Ryujinx.Common; +using Ryujinx.Common.Collections; +using Ryujinx.Memory; +using System; +using System.Diagnostics; +using System.Threading; + +namespace Ryujinx.Cpu.Jit.HostTracked +{ + readonly struct PrivateRange + { + public readonly MemoryBlock Memory; + public readonly ulong Offset; + public readonly ulong Size; + + public static PrivateRange Empty => new(null, 0, 0); + + public PrivateRange(MemoryBlock memory, ulong offset, ulong size) + { + Memory = memory; + Offset = offset; + Size = size; + } + } + + class AddressSpacePartition : IDisposable + { + public const ulong GuestPageSize = 0x1000; + + private const int DefaultBlockAlignment = 1 << 20; + + private enum MappingType : byte + { + None, + Private, + } + + private class Mapping : IntrusiveRedBlackTreeNode, IComparable, IComparable + { + public ulong Address { get; private set; } + public ulong Size { get; private set; } + public ulong EndAddress => Address + Size; + public MappingType Type { get; private set; } + + public Mapping(ulong address, ulong size, MappingType type) + { + Address = address; + Size = size; + Type = type; + } + + public Mapping Split(ulong splitAddress) + { + ulong leftSize = splitAddress - Address; + ulong rightSize = EndAddress - splitAddress; + + Mapping left = new(Address, leftSize, Type); + + Address = splitAddress; + Size = rightSize; + + return left; + } + + public void UpdateState(MappingType newType) + { + Type = newType; + } + + public void Extend(ulong sizeDelta) + { + Size += sizeDelta; + } + + public int CompareTo(Mapping other) + { + if (Address < other.Address) + { + return -1; + } + else if (Address <= other.EndAddress - 1UL) + { + return 0; + } + else + { + return 1; + } + } + + public int CompareTo(ulong address) + { + if (address < Address) + { + return -1; + } + else if (address <= EndAddress - 1UL) + { + return 0; + } + else + { + return 1; + } + } + } + + private class PrivateMapping : IntrusiveRedBlackTreeNode, IComparable, IComparable + { + public ulong Address { get; private set; } + public ulong Size { get; private set; } + public ulong EndAddress => Address + Size; + public PrivateMemoryAllocation PrivateAllocation { get; private set; } + + public PrivateMapping(ulong address, ulong size, PrivateMemoryAllocation privateAllocation) + { + Address = address; + Size = size; + PrivateAllocation = privateAllocation; + } + + public PrivateMapping Split(ulong splitAddress) + { + ulong leftSize = splitAddress - Address; + ulong rightSize = EndAddress - splitAddress; + + Debug.Assert(leftSize > 0); + Debug.Assert(rightSize > 0); + + (var leftAllocation, PrivateAllocation) = PrivateAllocation.Split(leftSize); + + PrivateMapping left = new(Address, leftSize, leftAllocation); + + Address = splitAddress; + Size = rightSize; + + return left; + } + + public void Map(AddressSpacePartitionMultiAllocation baseBlock, ulong baseAddress, PrivateMemoryAllocation newAllocation) + { + baseBlock.MapView(newAllocation.Memory, newAllocation.Offset, Address - baseAddress, Size); + PrivateAllocation = newAllocation; + } + + public void Unmap(AddressSpacePartitionMultiAllocation baseBlock, ulong baseAddress) + { + if (PrivateAllocation.IsValid) + { + baseBlock.UnmapView(PrivateAllocation.Memory, Address - baseAddress, Size); + PrivateAllocation.Dispose(); + } + + PrivateAllocation = default; + } + + public void Extend(ulong sizeDelta) + { + Size += sizeDelta; + } + + public int CompareTo(PrivateMapping other) + { + if (Address < other.Address) + { + return -1; + } + else if (Address <= other.EndAddress - 1UL) + { + return 0; + } + else + { + return 1; + } + } + + public int CompareTo(ulong address) + { + if (address < Address) + { + return -1; + } + else if (address <= EndAddress - 1UL) + { + return 0; + } + else + { + return 1; + } + } + } + + private readonly MemoryBlock _backingMemory; + private readonly AddressSpacePartitionMultiAllocation _baseMemory; + private readonly PrivateMemoryAllocator _privateMemoryAllocator; + + private readonly AddressIntrusiveRedBlackTree _mappingTree; + private readonly AddressIntrusiveRedBlackTree _privateTree; + + private readonly ReaderWriterLockSlim _treeLock; + + private readonly ulong _hostPageSize; + + private ulong? _firstPagePa; + private ulong? _lastPagePa; + private ulong _cachedFirstPagePa; + private ulong _cachedLastPagePa; + private MemoryBlock _firstPageMemoryForUnmap; + private ulong _firstPageOffsetForLateMap; + private MemoryPermission _firstPageMemoryProtection; + + public ulong Address { get; } + public ulong Size { get; } + public ulong EndAddress => Address + Size; + + public AddressSpacePartition(AddressSpacePartitionAllocation baseMemory, MemoryBlock backingMemory, ulong address, ulong size) + { + _privateMemoryAllocator = new PrivateMemoryAllocator(DefaultBlockAlignment, MemoryAllocationFlags.Mirrorable); + _mappingTree = new AddressIntrusiveRedBlackTree(); + _privateTree = new AddressIntrusiveRedBlackTree(); + _treeLock = new ReaderWriterLockSlim(); + + _mappingTree.Add(new Mapping(address, size, MappingType.None)); + _privateTree.Add(new PrivateMapping(address, size, default)); + + _hostPageSize = MemoryBlock.GetPageSize(); + + _backingMemory = backingMemory; + _baseMemory = new(baseMemory); + + _cachedFirstPagePa = ulong.MaxValue; + _cachedLastPagePa = ulong.MaxValue; + + Address = address; + Size = size; + } + + public bool IsEmpty() + { + _treeLock.EnterReadLock(); + + try + { + Mapping map = _mappingTree.GetNode(Address); + + return map != null && map.Address == Address && map.Size == Size && map.Type == MappingType.None; + } + finally + { + _treeLock.ExitReadLock(); + } + } + + public void Map(ulong va, ulong pa, ulong size) + { + Debug.Assert(va >= Address); + Debug.Assert(va + size <= EndAddress); + + if (va == Address) + { + _firstPagePa = pa; + } + + if (va <= EndAddress - GuestPageSize && va + size > EndAddress - GuestPageSize) + { + _lastPagePa = pa + ((EndAddress - GuestPageSize) - va); + } + + Update(va, pa, size, MappingType.Private); + } + + public void Unmap(ulong va, ulong size) + { + Debug.Assert(va >= Address); + Debug.Assert(va + size <= EndAddress); + + if (va == Address) + { + _firstPagePa = null; + } + + if (va <= EndAddress - GuestPageSize && va + size > EndAddress - GuestPageSize) + { + _lastPagePa = null; + } + + Update(va, 0UL, size, MappingType.None); + } + + public void ReprotectAligned(ulong va, ulong size, MemoryPermission protection) + { + Debug.Assert(va >= Address); + Debug.Assert(va + size <= EndAddress); + + _baseMemory.Reprotect(va - Address, size, protection, false); + + if (va == Address) + { + _firstPageMemoryProtection = protection; + } + } + + public void Reprotect( + ulong va, + ulong size, + MemoryPermission protection, + AddressSpacePartitioned addressSpace, + Action updatePtCallback) + { + if (_baseMemory.LazyInitMirrorForProtection(addressSpace, Address, Size, protection)) + { + LateMap(); + } + + updatePtCallback(va, _baseMemory.GetPointerForProtection(va - Address, size, protection), size); + } + + public IntPtr GetPointer(ulong va, ulong size) + { + Debug.Assert(va >= Address); + Debug.Assert(va + size <= EndAddress); + + return _baseMemory.GetPointer(va - Address, size); + } + + public void InsertBridgeAtEnd(AddressSpacePartition partitionAfter, bool useProtectionMirrors) + { + ulong firstPagePa = partitionAfter?._firstPagePa ?? ulong.MaxValue; + ulong lastPagePa = _lastPagePa ?? ulong.MaxValue; + + if (firstPagePa != _cachedFirstPagePa || lastPagePa != _cachedLastPagePa) + { + if (partitionAfter != null && partitionAfter._firstPagePa.HasValue) + { + (MemoryBlock firstPageMemory, ulong firstPageOffset) = partitionAfter.GetFirstPageMemoryAndOffset(); + + _baseMemory.MapView(firstPageMemory, firstPageOffset, Size, _hostPageSize); + + if (!useProtectionMirrors) + { + _baseMemory.Reprotect(Size, _hostPageSize, partitionAfter._firstPageMemoryProtection, throwOnFail: false); + } + + _firstPageMemoryForUnmap = firstPageMemory; + _firstPageOffsetForLateMap = firstPageOffset; + } + else + { + MemoryBlock firstPageMemoryForUnmap = _firstPageMemoryForUnmap; + + if (firstPageMemoryForUnmap != null) + { + _baseMemory.UnmapView(firstPageMemoryForUnmap, Size, _hostPageSize); + _firstPageMemoryForUnmap = null; + } + } + + _cachedFirstPagePa = firstPagePa; + _cachedLastPagePa = lastPagePa; + } + } + + public void ReprotectBridge(MemoryPermission protection) + { + if (_firstPageMemoryForUnmap != null) + { + _baseMemory.Reprotect(Size, _hostPageSize, protection, throwOnFail: false); + } + } + + private (MemoryBlock, ulong) GetFirstPageMemoryAndOffset() + { + _treeLock.EnterReadLock(); + + try + { + PrivateMapping map = _privateTree.GetNode(Address); + + if (map != null && map.PrivateAllocation.IsValid) + { + return (map.PrivateAllocation.Memory, map.PrivateAllocation.Offset + (Address - map.Address)); + } + } + finally + { + _treeLock.ExitReadLock(); + } + + return (_backingMemory, _firstPagePa.Value); + } + + public PrivateRange GetPrivateAllocation(ulong va) + { + _treeLock.EnterReadLock(); + + try + { + PrivateMapping map = _privateTree.GetNode(va); + + if (map != null && map.PrivateAllocation.IsValid) + { + return new(map.PrivateAllocation.Memory, map.PrivateAllocation.Offset + (va - map.Address), map.Size - (va - map.Address)); + } + } + finally + { + _treeLock.ExitReadLock(); + } + + return PrivateRange.Empty; + } + + private void Update(ulong va, ulong pa, ulong size, MappingType type) + { + _treeLock.EnterWriteLock(); + + try + { + Mapping map = _mappingTree.GetNode(va); + + Update(map, va, pa, size, type); + } + finally + { + _treeLock.ExitWriteLock(); + } + } + + private Mapping Update(Mapping map, ulong va, ulong pa, ulong size, MappingType type) + { + ulong endAddress = va + size; + + for (; map != null; map = map.Successor) + { + if (map.Address < va) + { + _mappingTree.Add(map.Split(va)); + } + + if (map.EndAddress > endAddress) + { + Mapping newMap = map.Split(endAddress); + _mappingTree.Add(newMap); + map = newMap; + } + + switch (type) + { + case MappingType.None: + ulong alignment = _hostPageSize; + + bool unmappedBefore = map.Predecessor == null || + (map.Predecessor.Type == MappingType.None && map.Predecessor.Address <= BitUtils.AlignDown(va, alignment)); + + bool unmappedAfter = map.Successor == null || + (map.Successor.Type == MappingType.None && map.Successor.EndAddress >= BitUtils.AlignUp(endAddress, alignment)); + + UnmapPrivate(va, size, unmappedBefore, unmappedAfter); + break; + case MappingType.Private: + MapPrivate(va, size); + break; + } + + map.UpdateState(type); + map = TryCoalesce(map); + + if (map.EndAddress >= endAddress) + { + break; + } + } + + return map; + } + + private Mapping TryCoalesce(Mapping map) + { + Mapping previousMap = map.Predecessor; + Mapping nextMap = map.Successor; + + if (previousMap != null && CanCoalesce(previousMap, map)) + { + previousMap.Extend(map.Size); + _mappingTree.Remove(map); + map = previousMap; + } + + if (nextMap != null && CanCoalesce(map, nextMap)) + { + map.Extend(nextMap.Size); + _mappingTree.Remove(nextMap); + } + + return map; + } + + private static bool CanCoalesce(Mapping left, Mapping right) + { + return left.Type == right.Type; + } + + private void MapPrivate(ulong va, ulong size) + { + ulong endAddress = va + size; + + ulong alignment = _hostPageSize; + + // Expand the range outwards based on page size to ensure that at least the requested region is mapped. + ulong vaAligned = BitUtils.AlignDown(va, alignment); + ulong endAddressAligned = BitUtils.AlignUp(endAddress, alignment); + + PrivateMapping map = _privateTree.GetNode(va); + + for (; map != null; map = map.Successor) + { + if (!map.PrivateAllocation.IsValid) + { + if (map.Address < vaAligned) + { + _privateTree.Add(map.Split(vaAligned)); + } + + if (map.EndAddress > endAddressAligned) + { + PrivateMapping newMap = map.Split(endAddressAligned); + _privateTree.Add(newMap); + map = newMap; + } + + map.Map(_baseMemory, Address, _privateMemoryAllocator.Allocate(map.Size, _hostPageSize)); + } + + if (map.EndAddress >= endAddressAligned) + { + break; + } + } + } + + private void UnmapPrivate(ulong va, ulong size, bool unmappedBefore, bool unmappedAfter) + { + ulong endAddress = va + size; + + ulong alignment = _hostPageSize; + + // If the adjacent mappings are unmapped, expand the range outwards, + // otherwise shrink it inwards. We must ensure we won't unmap pages that might still be in use. + ulong vaAligned = unmappedBefore ? BitUtils.AlignDown(va, alignment) : BitUtils.AlignUp(va, alignment); + ulong endAddressAligned = unmappedAfter ? BitUtils.AlignUp(endAddress, alignment) : BitUtils.AlignDown(endAddress, alignment); + + if (endAddressAligned <= vaAligned) + { + return; + } + + PrivateMapping map = _privateTree.GetNode(vaAligned); + + for (; map != null; map = map.Successor) + { + if (map.PrivateAllocation.IsValid) + { + if (map.Address < vaAligned) + { + _privateTree.Add(map.Split(vaAligned)); + } + + if (map.EndAddress > endAddressAligned) + { + PrivateMapping newMap = map.Split(endAddressAligned); + _privateTree.Add(newMap); + map = newMap; + } + + map.Unmap(_baseMemory, Address); + map = TryCoalesce(map); + } + + if (map.EndAddress >= endAddressAligned) + { + break; + } + } + } + + private PrivateMapping TryCoalesce(PrivateMapping map) + { + PrivateMapping previousMap = map.Predecessor; + PrivateMapping nextMap = map.Successor; + + if (previousMap != null && CanCoalesce(previousMap, map)) + { + previousMap.Extend(map.Size); + _privateTree.Remove(map); + map = previousMap; + } + + if (nextMap != null && CanCoalesce(map, nextMap)) + { + map.Extend(nextMap.Size); + _privateTree.Remove(nextMap); + } + + return map; + } + + private static bool CanCoalesce(PrivateMapping left, PrivateMapping right) + { + return !left.PrivateAllocation.IsValid && !right.PrivateAllocation.IsValid; + } + + private void LateMap() + { + // Map all existing private allocations. + // This is necessary to ensure mirrors that are lazily created have the same mappings as the main one. + + PrivateMapping map = _privateTree.GetNode(Address); + + for (; map != null; map = map.Successor) + { + if (map.PrivateAllocation.IsValid) + { + _baseMemory.LateMapView(map.PrivateAllocation.Memory, map.PrivateAllocation.Offset, map.Address - Address, map.Size); + } + } + + MemoryBlock firstPageMemory = _firstPageMemoryForUnmap; + ulong firstPageOffset = _firstPageOffsetForLateMap; + + if (firstPageMemory != null) + { + _baseMemory.LateMapView(firstPageMemory, firstPageOffset, Size, _hostPageSize); + } + } + + public PrivateRange GetFirstPrivateAllocation(ulong va, ulong size, out ulong nextVa) + { + _treeLock.EnterReadLock(); + + try + { + PrivateMapping map = _privateTree.GetNode(va); + + nextVa = map.EndAddress; + + if (map != null && map.PrivateAllocation.IsValid) + { + ulong startOffset = va - map.Address; + + return new( + map.PrivateAllocation.Memory, + map.PrivateAllocation.Offset + startOffset, + Math.Min(map.PrivateAllocation.Size - startOffset, size)); + } + } + finally + { + _treeLock.ExitReadLock(); + } + + return PrivateRange.Empty; + } + + public bool HasPrivateAllocation(ulong va, ulong size, ulong startVa, ulong startSize, ref PrivateRange range) + { + ulong endVa = va + size; + + _treeLock.EnterReadLock(); + + try + { + for (PrivateMapping map = _privateTree.GetNode(va); map != null && map.Address < endVa; map = map.Successor) + { + if (map.PrivateAllocation.IsValid) + { + if (map.Address <= startVa && map.EndAddress >= startVa + startSize) + { + ulong startOffset = startVa - map.Address; + + range = new( + map.PrivateAllocation.Memory, + map.PrivateAllocation.Offset + startOffset, + Math.Min(map.PrivateAllocation.Size - startOffset, startSize)); + } + + return true; + } + } + } + finally + { + _treeLock.ExitReadLock(); + } + + return false; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + _privateMemoryAllocator.Dispose(); + _baseMemory.Dispose(); + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitionAllocator.cs b/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitionAllocator.cs new file mode 100644 index 000000000..44dedb640 --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitionAllocator.cs @@ -0,0 +1,202 @@ +using Ryujinx.Common; +using Ryujinx.Common.Collections; +using Ryujinx.Memory; +using Ryujinx.Memory.Tracking; +using System; + +namespace Ryujinx.Cpu.Jit.HostTracked +{ + readonly struct AddressSpacePartitionAllocation : IDisposable + { + private readonly AddressSpacePartitionAllocator _owner; + private readonly PrivateMemoryAllocatorImpl.Allocation _allocation; + + public IntPtr Pointer => (IntPtr)((ulong)_allocation.Block.Memory.Pointer + _allocation.Offset); + + public bool IsValid => _owner != null; + + public AddressSpacePartitionAllocation( + AddressSpacePartitionAllocator owner, + PrivateMemoryAllocatorImpl.Allocation allocation) + { + _owner = owner; + _allocation = allocation; + } + + public void RegisterMapping(ulong va, ulong endVa) + { + _allocation.Block.AddMapping(_allocation.Offset, _allocation.Size, va, endVa); + } + + public void MapView(MemoryBlock srcBlock, ulong srcOffset, ulong dstOffset, ulong size) + { + _allocation.Block.Memory.MapView(srcBlock, srcOffset, _allocation.Offset + dstOffset, size); + } + + public void UnmapView(MemoryBlock srcBlock, ulong offset, ulong size) + { + _allocation.Block.Memory.UnmapView(srcBlock, _allocation.Offset + offset, size); + } + + public void Reprotect(ulong offset, ulong size, MemoryPermission permission, bool throwOnFail) + { + _allocation.Block.Memory.Reprotect(_allocation.Offset + offset, size, permission, throwOnFail); + } + + public IntPtr GetPointer(ulong offset, ulong size) + { + return _allocation.Block.Memory.GetPointer(_allocation.Offset + offset, size); + } + + public void Dispose() + { + _allocation.Block.RemoveMapping(_allocation.Offset, _allocation.Size); + _owner.Free(_allocation.Block, _allocation.Offset, _allocation.Size); + } + } + + class AddressSpacePartitionAllocator : PrivateMemoryAllocatorImpl + { + private const ulong DefaultBlockAlignment = 1UL << 32; // 4GB + + public class Block : PrivateMemoryAllocator.Block + { + private readonly MemoryTracking _tracking; + private readonly Func _readPtCallback; + private readonly MemoryEhMeilleure _memoryEh; + + private class Mapping : IntrusiveRedBlackTreeNode, IComparable, IComparable + { + public ulong Address { get; } + public ulong Size { get; } + public ulong EndAddress => Address + Size; + public ulong Va { get; } + public ulong EndVa { get; } + + public Mapping(ulong address, ulong size, ulong va, ulong endVa) + { + Address = address; + Size = size; + Va = va; + EndVa = endVa; + } + + public int CompareTo(Mapping other) + { + if (Address < other.Address) + { + return -1; + } + else if (Address <= other.EndAddress - 1UL) + { + return 0; + } + else + { + return 1; + } + } + + public int CompareTo(ulong address) + { + if (address < Address) + { + return -1; + } + else if (address <= EndAddress - 1UL) + { + return 0; + } + else + { + return 1; + } + } + } + + private readonly AddressIntrusiveRedBlackTree _mappingTree; + private readonly object _lock; + + public Block(MemoryTracking tracking, Func readPtCallback, MemoryBlock memory, ulong size, object locker) : base(memory, size) + { + _tracking = tracking; + _readPtCallback = readPtCallback; + _memoryEh = new(memory, null, tracking, VirtualMemoryEvent); + _mappingTree = new(); + _lock = locker; + } + + public void AddMapping(ulong offset, ulong size, ulong va, ulong endVa) + { + _mappingTree.Add(new(offset, size, va, endVa)); + } + + public void RemoveMapping(ulong offset, ulong size) + { + _mappingTree.Remove(_mappingTree.GetNode(offset)); + } + + private ulong VirtualMemoryEvent(ulong address, ulong size, bool write) + { + Mapping map; + + lock (_lock) + { + map = _mappingTree.GetNode(address); + } + + if (map == null) + { + return 0; + } + + address -= map.Address; + + ulong addressAligned = BitUtils.AlignDown(address, AddressSpacePartition.GuestPageSize); + ulong endAddressAligned = BitUtils.AlignUp(address + size, AddressSpacePartition.GuestPageSize); + ulong sizeAligned = endAddressAligned - addressAligned; + + if (!_tracking.VirtualMemoryEvent(map.Va + addressAligned, sizeAligned, write)) + { + return 0; + } + + return _readPtCallback(map.Va + address); + } + + public override void Destroy() + { + _memoryEh.Dispose(); + + base.Destroy(); + } + } + + private readonly MemoryTracking _tracking; + private readonly Func _readPtCallback; + private readonly object _lock; + + public AddressSpacePartitionAllocator( + MemoryTracking tracking, + Func readPtCallback, + object locker) : base(DefaultBlockAlignment, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible) + { + _tracking = tracking; + _readPtCallback = readPtCallback; + _lock = locker; + } + + public AddressSpacePartitionAllocation Allocate(ulong va, ulong size) + { + AddressSpacePartitionAllocation allocation = new(this, Allocate(size, MemoryBlock.GetPageSize(), CreateBlock)); + allocation.RegisterMapping(va, va + size); + + return allocation; + } + + private Block CreateBlock(MemoryBlock memory, ulong size) + { + return new Block(_tracking, _readPtCallback, memory, size, _lock); + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitionMultiAllocation.cs b/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitionMultiAllocation.cs new file mode 100644 index 000000000..3b065583f --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitionMultiAllocation.cs @@ -0,0 +1,101 @@ +using Ryujinx.Memory; +using System; +using System.Diagnostics; + +namespace Ryujinx.Cpu.Jit.HostTracked +{ + class AddressSpacePartitionMultiAllocation : IDisposable + { + private readonly AddressSpacePartitionAllocation _baseMemory; + private AddressSpacePartitionAllocation _baseMemoryRo; + private AddressSpacePartitionAllocation _baseMemoryNone; + + public AddressSpacePartitionMultiAllocation(AddressSpacePartitionAllocation baseMemory) + { + _baseMemory = baseMemory; + } + + public void MapView(MemoryBlock srcBlock, ulong srcOffset, ulong dstOffset, ulong size) + { + _baseMemory.MapView(srcBlock, srcOffset, dstOffset, size); + + if (_baseMemoryRo.IsValid) + { + _baseMemoryRo.MapView(srcBlock, srcOffset, dstOffset, size); + _baseMemoryRo.Reprotect(dstOffset, size, MemoryPermission.Read, false); + } + } + + public void LateMapView(MemoryBlock srcBlock, ulong srcOffset, ulong dstOffset, ulong size) + { + _baseMemoryRo.MapView(srcBlock, srcOffset, dstOffset, size); + _baseMemoryRo.Reprotect(dstOffset, size, MemoryPermission.Read, false); + } + + public void UnmapView(MemoryBlock srcBlock, ulong offset, ulong size) + { + _baseMemory.UnmapView(srcBlock, offset, size); + + if (_baseMemoryRo.IsValid) + { + _baseMemoryRo.UnmapView(srcBlock, offset, size); + } + } + + public void Reprotect(ulong offset, ulong size, MemoryPermission permission, bool throwOnFail) + { + _baseMemory.Reprotect(offset, size, permission, throwOnFail); + } + + public IntPtr GetPointer(ulong offset, ulong size) + { + return _baseMemory.GetPointer(offset, size); + } + + public bool LazyInitMirrorForProtection(AddressSpacePartitioned addressSpace, ulong blockAddress, ulong blockSize, MemoryPermission permission) + { + if (permission == MemoryPermission.None && !_baseMemoryNone.IsValid) + { + _baseMemoryNone = addressSpace.CreateAsPartitionAllocation(blockAddress, blockSize); + } + else if (permission == MemoryPermission.Read && !_baseMemoryRo.IsValid) + { + _baseMemoryRo = addressSpace.CreateAsPartitionAllocation(blockAddress, blockSize); + + return true; + } + + return false; + } + + public IntPtr GetPointerForProtection(ulong offset, ulong size, MemoryPermission permission) + { + AddressSpacePartitionAllocation allocation = permission switch + { + MemoryPermission.ReadAndWrite => _baseMemory, + MemoryPermission.Read => _baseMemoryRo, + MemoryPermission.None => _baseMemoryNone, + _ => throw new ArgumentException($"Invalid protection \"{permission}\"."), + }; + + Debug.Assert(allocation.IsValid); + + return allocation.GetPointer(offset, size); + } + + public void Dispose() + { + _baseMemory.Dispose(); + + if (_baseMemoryRo.IsValid) + { + _baseMemoryRo.Dispose(); + } + + if (_baseMemoryNone.IsValid) + { + _baseMemoryNone.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitioned.cs b/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitioned.cs new file mode 100644 index 000000000..2cf2c248b --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitioned.cs @@ -0,0 +1,407 @@ +using Ryujinx.Common; +using Ryujinx.Memory; +using Ryujinx.Memory.Tracking; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Ryujinx.Cpu.Jit.HostTracked +{ + class AddressSpacePartitioned : IDisposable + { + private const int PartitionBits = 25; + private const ulong PartitionSize = 1UL << PartitionBits; + + private readonly MemoryBlock _backingMemory; + private readonly List _partitions; + private readonly AddressSpacePartitionAllocator _asAllocator; + private readonly Action _updatePtCallback; + private readonly bool _useProtectionMirrors; + + public AddressSpacePartitioned(MemoryTracking tracking, MemoryBlock backingMemory, NativePageTable nativePageTable, bool useProtectionMirrors) + { + _backingMemory = backingMemory; + _partitions = new(); + _asAllocator = new(tracking, nativePageTable.Read, _partitions); + _updatePtCallback = nativePageTable.Update; + _useProtectionMirrors = useProtectionMirrors; + } + + public void Map(ulong va, ulong pa, ulong size) + { + ulong endVa = va + size; + + lock (_partitions) + { + EnsurePartitionsLocked(va, size); + + while (va < endVa) + { + int partitionIndex = FindPartitionIndexLocked(va); + AddressSpacePartition partition = _partitions[partitionIndex]; + + (ulong clampedVa, ulong clampedEndVa) = ClampRange(partition, va, endVa); + + partition.Map(clampedVa, pa, clampedEndVa - clampedVa); + + ulong currentSize = clampedEndVa - clampedVa; + + va += currentSize; + pa += currentSize; + + InsertOrRemoveBridgeIfNeeded(partitionIndex); + } + } + } + + public void Unmap(ulong va, ulong size) + { + ulong endVa = va + size; + + while (va < endVa) + { + AddressSpacePartition partition; + + lock (_partitions) + { + int partitionIndex = FindPartitionIndexLocked(va); + if (partitionIndex < 0) + { + va += PartitionSize - (va & (PartitionSize - 1)); + + continue; + } + + partition = _partitions[partitionIndex]; + + (ulong clampedVa, ulong clampedEndVa) = ClampRange(partition, va, endVa); + + partition.Unmap(clampedVa, clampedEndVa - clampedVa); + + va += clampedEndVa - clampedVa; + + InsertOrRemoveBridgeIfNeeded(partitionIndex); + + if (partition.IsEmpty()) + { + _partitions.Remove(partition); + partition.Dispose(); + } + } + } + } + + public void Reprotect(ulong va, ulong size, MemoryPermission protection) + { + ulong endVa = va + size; + + lock (_partitions) + { + while (va < endVa) + { + AddressSpacePartition partition = FindPartitionWithIndex(va, out int partitionIndex); + + if (partition == null) + { + va += PartitionSize - (va & (PartitionSize - 1)); + + continue; + } + + (ulong clampedVa, ulong clampedEndVa) = ClampRange(partition, va, endVa); + + if (_useProtectionMirrors) + { + partition.Reprotect(clampedVa, clampedEndVa - clampedVa, protection, this, _updatePtCallback); + } + else + { + partition.ReprotectAligned(clampedVa, clampedEndVa - clampedVa, protection); + + if (clampedVa == partition.Address && + partitionIndex > 0 && + _partitions[partitionIndex - 1].EndAddress == partition.Address) + { + _partitions[partitionIndex - 1].ReprotectBridge(protection); + } + } + + va += clampedEndVa - clampedVa; + } + } + } + + public PrivateRange GetPrivateAllocation(ulong va) + { + AddressSpacePartition partition = FindPartition(va); + + if (partition == null) + { + return PrivateRange.Empty; + } + + return partition.GetPrivateAllocation(va); + } + + public PrivateRange GetFirstPrivateAllocation(ulong va, ulong size, out ulong nextVa) + { + AddressSpacePartition partition = FindPartition(va); + + if (partition == null) + { + nextVa = (va & ~(PartitionSize - 1)) + PartitionSize; + + return PrivateRange.Empty; + } + + return partition.GetFirstPrivateAllocation(va, size, out nextVa); + } + + public bool HasAnyPrivateAllocation(ulong va, ulong size, out PrivateRange range) + { + range = PrivateRange.Empty; + + ulong startVa = va; + ulong endVa = va + size; + + while (va < endVa) + { + AddressSpacePartition partition = FindPartition(va); + + if (partition == null) + { + va += PartitionSize - (va & (PartitionSize - 1)); + + continue; + } + + (ulong clampedVa, ulong clampedEndVa) = ClampRange(partition, va, endVa); + + if (partition.HasPrivateAllocation(clampedVa, clampedEndVa - clampedVa, startVa, size, ref range)) + { + return true; + } + + va += clampedEndVa - clampedVa; + } + + return false; + } + + private void InsertOrRemoveBridgeIfNeeded(int partitionIndex) + { + if (partitionIndex > 0) + { + if (_partitions[partitionIndex - 1].EndAddress == _partitions[partitionIndex].Address) + { + _partitions[partitionIndex - 1].InsertBridgeAtEnd(_partitions[partitionIndex], _useProtectionMirrors); + } + else + { + _partitions[partitionIndex - 1].InsertBridgeAtEnd(null, _useProtectionMirrors); + } + } + + if (partitionIndex + 1 < _partitions.Count && _partitions[partitionIndex].EndAddress == _partitions[partitionIndex + 1].Address) + { + _partitions[partitionIndex].InsertBridgeAtEnd(_partitions[partitionIndex + 1], _useProtectionMirrors); + } + else + { + _partitions[partitionIndex].InsertBridgeAtEnd(null, _useProtectionMirrors); + } + } + + public IntPtr GetPointer(ulong va, ulong size) + { + AddressSpacePartition partition = FindPartition(va); + + return partition.GetPointer(va, size); + } + + private static (ulong, ulong) ClampRange(AddressSpacePartition partition, ulong va, ulong endVa) + { + if (va < partition.Address) + { + va = partition.Address; + } + + if (endVa > partition.EndAddress) + { + endVa = partition.EndAddress; + } + + return (va, endVa); + } + + private AddressSpacePartition FindPartition(ulong va) + { + lock (_partitions) + { + int index = FindPartitionIndexLocked(va); + if (index >= 0) + { + return _partitions[index]; + } + } + + return null; + } + + private AddressSpacePartition FindPartitionWithIndex(ulong va, out int index) + { + lock (_partitions) + { + index = FindPartitionIndexLocked(va); + if (index >= 0) + { + return _partitions[index]; + } + } + + return null; + } + + private int FindPartitionIndexLocked(ulong va) + { + int left = 0; + int middle; + int right = _partitions.Count - 1; + + while (left <= right) + { + middle = left + ((right - left) >> 1); + + AddressSpacePartition partition = _partitions[middle]; + + if (partition.Address <= va && partition.EndAddress > va) + { + return middle; + } + + if (partition.Address >= va) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return -1; + } + + private void EnsurePartitionsLocked(ulong va, ulong size) + { + ulong endVa = BitUtils.AlignUp(va + size, PartitionSize); + va = BitUtils.AlignDown(va, PartitionSize); + + for (int i = 0; i < _partitions.Count && va < endVa; i++) + { + AddressSpacePartition partition = _partitions[i]; + + if (partition.Address <= va && partition.EndAddress > va) + { + if (partition.EndAddress >= endVa) + { + // Fully mapped already. + va = endVa; + + break; + } + + ulong gapSize; + + if (i + 1 < _partitions.Count) + { + AddressSpacePartition nextPartition = _partitions[i + 1]; + + if (partition.EndAddress == nextPartition.Address) + { + va = partition.EndAddress; + + continue; + } + + gapSize = Math.Min(endVa, nextPartition.Address) - partition.EndAddress; + } + else + { + gapSize = endVa - partition.EndAddress; + } + + _partitions.Insert(i + 1, CreateAsPartition(partition.EndAddress, gapSize)); + va = partition.EndAddress + gapSize; + i++; + } + else if (partition.EndAddress > va) + { + Debug.Assert(partition.Address > va); + + ulong gapSize; + + if (partition.Address < endVa) + { + gapSize = partition.Address - va; + } + else + { + gapSize = endVa - va; + } + + _partitions.Insert(i, CreateAsPartition(va, gapSize)); + va = Math.Min(partition.EndAddress, endVa); + i++; + } + } + + if (va < endVa) + { + _partitions.Add(CreateAsPartition(va, endVa - va)); + } + + ValidatePartitionList(); + } + + [Conditional("DEBUG")] + private void ValidatePartitionList() + { + for (int i = 1; i < _partitions.Count; i++) + { + Debug.Assert(_partitions[i].Address > _partitions[i - 1].Address); + Debug.Assert(_partitions[i].EndAddress > _partitions[i - 1].EndAddress); + } + } + + private AddressSpacePartition CreateAsPartition(ulong va, ulong size) + { + return new(CreateAsPartitionAllocation(va, size), _backingMemory, va, size); + } + + public AddressSpacePartitionAllocation CreateAsPartitionAllocation(ulong va, ulong size) + { + return _asAllocator.Allocate(va, size + MemoryBlock.GetPageSize()); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + foreach (AddressSpacePartition partition in _partitions) + { + partition.Dispose(); + } + + _partitions.Clear(); + _asAllocator.Dispose(); + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/HostTracked/NativePageTable.cs b/src/Ryujinx.Cpu/Jit/HostTracked/NativePageTable.cs new file mode 100644 index 000000000..e3174e3fc --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/HostTracked/NativePageTable.cs @@ -0,0 +1,223 @@ +using Ryujinx.Cpu.Signal; +using Ryujinx.Memory; +using System; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Cpu.Jit.HostTracked +{ + sealed class NativePageTable : IDisposable + { + private delegate ulong TrackingEventDelegate(ulong address, ulong size, bool write); + + private const int PageBits = 12; + private const int PageSize = 1 << PageBits; + private const int PageMask = PageSize - 1; + + private const int PteSize = 8; + + private readonly int _bitsPerPtPage; + private readonly int _entriesPerPtPage; + private readonly int _pageCommitmentBits; + + private readonly PageTable _pageTable; + private readonly MemoryBlock _nativePageTable; + private readonly ulong[] _pageCommitmentBitmap; + private readonly ulong _hostPageSize; + + private readonly TrackingEventDelegate _trackingEvent; + + private bool _disposed; + + public IntPtr PageTablePointer => _nativePageTable.Pointer; + + public NativePageTable(ulong asSize) + { + ulong hostPageSize = MemoryBlock.GetPageSize(); + + _entriesPerPtPage = (int)(hostPageSize / sizeof(ulong)); + _bitsPerPtPage = BitOperations.Log2((uint)_entriesPerPtPage); + _pageCommitmentBits = PageBits + _bitsPerPtPage; + + _hostPageSize = hostPageSize; + _pageTable = new PageTable(); + _nativePageTable = new MemoryBlock((asSize / PageSize) * PteSize + _hostPageSize, MemoryAllocationFlags.Reserve); + _pageCommitmentBitmap = new ulong[(asSize >> _pageCommitmentBits) / (sizeof(ulong) * 8)]; + + ulong ptStart = (ulong)_nativePageTable.Pointer; + ulong ptEnd = ptStart + _nativePageTable.Size; + + _trackingEvent = VirtualMemoryEvent; + + bool added = NativeSignalHandler.AddTrackedRegion((nuint)ptStart, (nuint)ptEnd, Marshal.GetFunctionPointerForDelegate(_trackingEvent)); + + if (!added) + { + throw new InvalidOperationException("Number of allowed tracked regions exceeded."); + } + } + + public void Map(ulong va, ulong pa, ulong size, AddressSpacePartitioned addressSpace, MemoryBlock backingMemory, bool privateMap) + { + while (size != 0) + { + _pageTable.Map(va, pa); + + EnsureCommitment(va); + + if (privateMap) + { + _nativePageTable.Write((va / PageSize) * PteSize, GetPte(va, addressSpace.GetPointer(va, PageSize))); + } + else + { + _nativePageTable.Write((va / PageSize) * PteSize, GetPte(va, backingMemory.GetPointer(pa, PageSize))); + } + + va += PageSize; + pa += PageSize; + size -= PageSize; + } + } + + public void Unmap(ulong va, ulong size) + { + IntPtr guardPagePtr = GetGuardPagePointer(); + + while (size != 0) + { + _pageTable.Unmap(va); + _nativePageTable.Write((va / PageSize) * PteSize, GetPte(va, guardPagePtr)); + + va += PageSize; + size -= PageSize; + } + } + + public ulong Read(ulong va) + { + ulong pte = _nativePageTable.Read((va / PageSize) * PteSize); + + pte += va & ~(ulong)PageMask; + + return pte + (va & PageMask); + } + + public void Update(ulong va, IntPtr ptr, ulong size) + { + ulong remainingSize = size; + + while (remainingSize != 0) + { + EnsureCommitment(va); + + _nativePageTable.Write((va / PageSize) * PteSize, GetPte(va, ptr)); + + va += PageSize; + ptr += PageSize; + remainingSize -= PageSize; + } + } + + private void EnsureCommitment(ulong va) + { + ulong bit = va >> _pageCommitmentBits; + + int index = (int)(bit / (sizeof(ulong) * 8)); + int shift = (int)(bit % (sizeof(ulong) * 8)); + + ulong mask = 1UL << shift; + + ulong oldMask = _pageCommitmentBitmap[index]; + + if ((oldMask & mask) == 0) + { + lock (_pageCommitmentBitmap) + { + oldMask = _pageCommitmentBitmap[index]; + + if ((oldMask & mask) != 0) + { + return; + } + + _nativePageTable.Commit(bit * _hostPageSize, _hostPageSize); + + Span pageSpan = MemoryMarshal.Cast(_nativePageTable.GetSpan(bit * _hostPageSize, (int)_hostPageSize)); + + Debug.Assert(pageSpan.Length == _entriesPerPtPage); + + IntPtr guardPagePtr = GetGuardPagePointer(); + + for (int i = 0; i < pageSpan.Length; i++) + { + pageSpan[i] = GetPte((bit << _pageCommitmentBits) | ((ulong)i * PageSize), guardPagePtr); + } + + _pageCommitmentBitmap[index] = oldMask | mask; + } + } + } + + private IntPtr GetGuardPagePointer() + { + return _nativePageTable.GetPointer(_nativePageTable.Size - _hostPageSize, _hostPageSize); + } + + private static ulong GetPte(ulong va, IntPtr ptr) + { + Debug.Assert((va & PageMask) == 0); + + return (ulong)ptr - va; + } + + public ulong GetPhysicalAddress(ulong va) + { + return _pageTable.Read(va) + (va & PageMask); + } + + private ulong VirtualMemoryEvent(ulong address, ulong size, bool write) + { + if (address < _nativePageTable.Size - _hostPageSize) + { + // Some prefetch instructions do not cause faults with invalid addresses. + // Retry if we are hitting a case where the page table is unmapped, the next + // run will execute the actual instruction. + // The address loaded from the page table will be invalid, and it should hit the else case + // if the instruction faults on unmapped or protected memory. + + ulong va = address * (PageSize / sizeof(ulong)); + + EnsureCommitment(va); + + return (ulong)_nativePageTable.Pointer + address; + } + else + { + throw new InvalidMemoryRegionException(); + } + } + + private void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + NativeSignalHandler.RemoveTrackedRegion((nuint)_nativePageTable.Pointer); + + _nativePageTable.Dispose(); + } + + _disposed = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/JitCpuContext.cs b/src/Ryujinx.Cpu/Jit/JitCpuContext.cs index dce0490a4..9893c59b2 100644 --- a/src/Ryujinx.Cpu/Jit/JitCpuContext.cs +++ b/src/Ryujinx.Cpu/Jit/JitCpuContext.cs @@ -15,9 +15,9 @@ public JitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bi _tickSource = tickSource; _translator = new Translator(new JitMemoryAllocator(forJit: true), memory, for64Bit); - if (memory.Type.IsHostMapped()) + if (memory.Type.IsHostMappedOrTracked()) { - NativeSignalHandler.InitializeSignalHandler(MemoryBlock.GetPageSize()); + NativeSignalHandler.InitializeSignalHandler(); } memory.UnmapEvent += UnmapHandler; diff --git a/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs b/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs new file mode 100644 index 000000000..18404bcc7 --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs @@ -0,0 +1,627 @@ +using ARMeilleure.Memory; +using Ryujinx.Cpu.Jit.HostTracked; +using Ryujinx.Cpu.Signal; +using Ryujinx.Memory; +using Ryujinx.Memory.Range; +using Ryujinx.Memory.Tracking; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Cpu.Jit +{ + /// + /// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region. + /// + public sealed class MemoryManagerHostTracked : VirtualMemoryManagerRefCountedBase, IWritableBlock, IMemoryManager, IVirtualMemoryManagerTracked + { + private readonly InvalidAccessHandler _invalidAccessHandler; + private readonly bool _unsafeMode; + + private readonly MemoryBlock _backingMemory; + + public int AddressSpaceBits { get; } + + public MemoryTracking Tracking { get; } + + private readonly NativePageTable _nativePageTable; + private readonly AddressSpacePartitioned _addressSpace; + + private readonly ManagedPageFlags _pages; + + protected override ulong AddressSpaceSize { get; } + + /// + public bool Supports4KBPages => false; + + public IntPtr PageTablePointer => _nativePageTable.PageTablePointer; + + public MemoryManagerType Type => _unsafeMode ? MemoryManagerType.HostTrackedUnsafe : MemoryManagerType.HostTracked; + + public event Action UnmapEvent; + + /// + /// Creates a new instance of the host tracked memory manager. + /// + /// Physical backing memory where virtual memory will be mapped to + /// Size of the address space + /// True if unmanaged access should not be masked (unsafe), false otherwise. + /// Optional function to handle invalid memory accesses + public MemoryManagerHostTracked(MemoryBlock backingMemory, ulong addressSpaceSize, bool unsafeMode, InvalidAccessHandler invalidAccessHandler) + { + bool useProtectionMirrors = MemoryBlock.GetPageSize() > PageSize; + + Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler, useProtectionMirrors); + + _backingMemory = backingMemory; + _invalidAccessHandler = invalidAccessHandler; + _unsafeMode = unsafeMode; + AddressSpaceSize = addressSpaceSize; + + ulong asSize = PageSize; + int asBits = PageBits; + + while (asSize < AddressSpaceSize) + { + asSize <<= 1; + asBits++; + } + + AddressSpaceBits = asBits; + + if (useProtectionMirrors && !NativeSignalHandler.SupportsFaultAddressPatching()) + { + // Currently we require being able to change the fault address to something else + // in order to "emulate" 4KB granularity protection on systems with larger page size. + + throw new PlatformNotSupportedException(); + } + + _pages = new ManagedPageFlags(asBits); + _nativePageTable = new(asSize); + _addressSpace = new(Tracking, backingMemory, _nativePageTable, useProtectionMirrors); + } + + /// + public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags) + { + AssertValidAddressAndSize(va, size); + + if (flags.HasFlag(MemoryMapFlags.Private)) + { + _addressSpace.Map(va, pa, size); + } + + _pages.AddMapping(va, size); + _nativePageTable.Map(va, pa, size, _addressSpace, _backingMemory, flags.HasFlag(MemoryMapFlags.Private)); + + Tracking.Map(va, size); + } + + /// + public void MapForeign(ulong va, nuint hostPointer, ulong size) + { + throw new NotSupportedException(); + } + + /// + public void Unmap(ulong va, ulong size) + { + AssertValidAddressAndSize(va, size); + + _addressSpace.Unmap(va, size); + + UnmapEvent?.Invoke(va, size); + Tracking.Unmap(va, size); + + _pages.RemoveMapping(va, size); + _nativePageTable.Unmap(va, size); + } + + public T Read(ulong va) where T : unmanaged + { + return MemoryMarshal.Cast(GetSpan(va, Unsafe.SizeOf()))[0]; + } + + public T ReadTracked(ulong va) where T : unmanaged + { + try + { + SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), false); + + return Read(va); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + + return default; + } + } + + public override void Read(ulong va, Span data) + { + ReadImpl(va, data); + } + + public void Write(ulong va, T value) where T : unmanaged + { + Write(va, MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1))); + } + + public void Write(ulong va, ReadOnlySpan data) + { + if (data.Length == 0) + { + return; + } + + SignalMemoryTracking(va, (ulong)data.Length, true); + + WriteImpl(va, data); + } + + public void WriteUntracked(ulong va, ReadOnlySpan data) + { + if (data.Length == 0) + { + return; + } + + WriteImpl(va, data); + } + + public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) + { + if (data.Length == 0) + { + return false; + } + + SignalMemoryTracking(va, (ulong)data.Length, false); + + if (TryGetVirtualContiguous(va, data.Length, out MemoryBlock memoryBlock, out ulong offset)) + { + var target = memoryBlock.GetSpan(offset, data.Length); + + bool changed = !data.SequenceEqual(target); + + if (changed) + { + data.CopyTo(target); + } + + return changed; + } + else + { + WriteImpl(va, data); + + return true; + } + } + + private void WriteImpl(ulong va, ReadOnlySpan data) + { + try + { + AssertValidAddressAndSize(va, (ulong)data.Length); + + ulong endVa = va + (ulong)data.Length; + int offset = 0; + + while (va < endVa) + { + (MemoryBlock memory, ulong rangeOffset, ulong copySize) = GetMemoryOffsetAndSize(va, (ulong)(data.Length - offset)); + + data.Slice(offset, (int)copySize).CopyTo(memory.GetSpan(rangeOffset, (int)copySize)); + + va += copySize; + offset += (int)copySize; + } + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } + } + + public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) + { + if (size == 0) + { + return ReadOnlySpan.Empty; + } + + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, false); + } + + if (TryGetVirtualContiguous(va, size, out MemoryBlock memoryBlock, out ulong offset)) + { + return memoryBlock.GetSpan(offset, size); + } + else + { + Span data = new byte[size]; + + ReadImpl(va, data); + + return data; + } + } + + public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) + { + if (size == 0) + { + return new WritableRegion(null, va, Memory.Empty); + } + + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, true); + } + + if (TryGetVirtualContiguous(va, size, out MemoryBlock memoryBlock, out ulong offset)) + { + return new WritableRegion(null, va, memoryBlock.GetMemory(offset, size)); + } + else + { + Memory memory = new byte[size]; + + ReadImpl(va, memory.Span); + + return new WritableRegion(this, va, memory); + } + } + + public ref T GetRef(ulong va) where T : unmanaged + { + if (!TryGetVirtualContiguous(va, Unsafe.SizeOf(), out MemoryBlock memory, out ulong offset)) + { + ThrowMemoryNotContiguous(); + } + + SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), true); + + return ref memory.GetRef(offset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsMapped(ulong va) + { + return ValidateAddress(va) && _pages.IsMapped(va); + } + + public bool IsRangeMapped(ulong va, ulong size) + { + AssertValidAddressAndSize(va, size); + + return _pages.IsRangeMapped(va, size); + } + + private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException(); + + private bool TryGetVirtualContiguous(ulong va, int size, out MemoryBlock memory, out ulong offset) + { + if (_addressSpace.HasAnyPrivateAllocation(va, (ulong)size, out PrivateRange range)) + { + // If we have a private allocation overlapping the range, + // then the access is only considered contiguous if it covers the entire range. + + if (range.Memory != null) + { + memory = range.Memory; + offset = range.Offset; + + return true; + } + + memory = null; + offset = 0; + + return false; + } + + memory = _backingMemory; + offset = GetPhysicalAddressInternal(va); + + return IsPhysicalContiguous(va, size); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsPhysicalContiguous(ulong va, int size) + { + if (!ValidateAddress(va) || !ValidateAddressAndSize(va, (ulong)size)) + { + return false; + } + + int pages = GetPagesCount(va, (uint)size, out va); + + for (int page = 0; page < pages - 1; page++) + { + if (!ValidateAddress(va + PageSize)) + { + return false; + } + + if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize)) + { + return false; + } + + va += PageSize; + } + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ulong GetContiguousSize(ulong va, ulong size) + { + ulong contiguousSize = PageSize - (va & PageMask); + + if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size)) + { + return contiguousSize; + } + + int pages = GetPagesCount(va, size, out va); + + for (int page = 0; page < pages - 1; page++) + { + if (!ValidateAddress(va + PageSize)) + { + return contiguousSize; + } + + if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize)) + { + return contiguousSize; + } + + va += PageSize; + contiguousSize += PageSize; + } + + return Math.Min(contiguousSize, size); + } + + private (MemoryBlock, ulong, ulong) GetMemoryOffsetAndSize(ulong va, ulong size) + { + PrivateRange privateRange = _addressSpace.GetFirstPrivateAllocation(va, size, out ulong nextVa); + + if (privateRange.Memory != null) + { + return (privateRange.Memory, privateRange.Offset, privateRange.Size); + } + + ulong physSize = GetContiguousSize(va, Math.Min(size, nextVa - va)); + + return (_backingMemory, GetPhysicalAddressChecked(va), physSize); + } + + public IEnumerable GetHostRegions(ulong va, ulong size) + { + if (!ValidateAddressAndSize(va, size)) + { + return null; + } + + var regions = new List(); + ulong endVa = va + size; + + try + { + while (va < endVa) + { + (MemoryBlock memory, ulong rangeOffset, ulong rangeSize) = GetMemoryOffsetAndSize(va, endVa - va); + + regions.Add(new((UIntPtr)memory.GetPointer(rangeOffset, rangeSize), rangeSize)); + + va += rangeSize; + } + } + catch (InvalidMemoryRegionException) + { + return null; + } + + return regions; + } + + public IEnumerable GetPhysicalRegions(ulong va, ulong size) + { + if (size == 0) + { + return Enumerable.Empty(); + } + + return GetPhysicalRegionsImpl(va, size); + } + + private List GetPhysicalRegionsImpl(ulong va, ulong size) + { + if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size)) + { + return null; + } + + int pages = GetPagesCount(va, (uint)size, out va); + + var regions = new List(); + + ulong regionStart = GetPhysicalAddressInternal(va); + ulong regionSize = PageSize; + + for (int page = 0; page < pages - 1; page++) + { + if (!ValidateAddress(va + PageSize)) + { + return null; + } + + ulong newPa = GetPhysicalAddressInternal(va + PageSize); + + if (GetPhysicalAddressInternal(va) + PageSize != newPa) + { + regions.Add(new MemoryRange(regionStart, regionSize)); + regionStart = newPa; + regionSize = 0; + } + + va += PageSize; + regionSize += PageSize; + } + + regions.Add(new MemoryRange(regionStart, regionSize)); + + return regions; + } + + private void ReadImpl(ulong va, Span data) + { + if (data.Length == 0) + { + return; + } + + try + { + AssertValidAddressAndSize(va, (ulong)data.Length); + + ulong endVa = va + (ulong)data.Length; + int offset = 0; + + while (va < endVa) + { + (MemoryBlock memory, ulong rangeOffset, ulong copySize) = GetMemoryOffsetAndSize(va, (ulong)(data.Length - offset)); + + memory.GetSpan(rangeOffset, (int)copySize).CopyTo(data.Slice(offset, (int)copySize)); + + va += copySize; + offset += (int)copySize; + } + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } + } + + /// + /// + /// This function also validates that the given range is both valid and mapped, and will throw if it is not. + /// + public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) + { + AssertValidAddressAndSize(va, size); + + if (precise) + { + Tracking.VirtualMemoryEvent(va, size, write, precise: true, exemptId); + return; + } + + // Software table, used for managed memory tracking. + + _pages.SignalMemoryTracking(Tracking, va, size, write, exemptId); + } + + /// + /// Computes the number of pages in a virtual address range. + /// + /// Virtual address of the range + /// Size of the range + /// The virtual address of the beginning of the first page + /// This function does not differentiate between allocated and unallocated pages. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetPagesCount(ulong va, ulong size, out ulong startVa) + { + // WARNING: Always check if ulong does not overflow during the operations. + startVa = va & ~(ulong)PageMask; + ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask; + + return (int)(vaSpan / PageSize); + } + + public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None) + { + return Tracking.BeginTracking(address, size, id, flags); + } + + public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None) + { + return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags); + } + + public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id) + { + return Tracking.BeginSmartGranularTracking(address, size, granularity, id); + } + + private ulong GetPhysicalAddressChecked(ulong va) + { + if (!IsMapped(va)) + { + ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}"); + } + + return GetPhysicalAddressInternal(va); + } + + private ulong GetPhysicalAddressInternal(ulong va) + { + return _nativePageTable.GetPhysicalAddress(va); + } + + /// + public void Reprotect(ulong va, ulong size, MemoryPermission protection) + { + // TODO + } + + /// + public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest) + { + if (guest) + { + _addressSpace.Reprotect(va, size, protection); + } + else + { + _pages.TrackingReprotect(va, size, protection); + } + } + + /// + /// Disposes of resources used by the memory manager. + /// + protected override void Destroy() + { + _addressSpace.Dispose(); + _nativePageTable.Dispose(); + } + + protected override Span GetPhysicalAddressSpan(ulong pa, int size) + => _backingMemory.GetSpan(pa, size); + + protected override ulong TranslateVirtualAddressForRead(ulong va) + => GetPhysicalAddressInternal(va); + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMemory.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMemory.cs index 6ab4b9495..d8caee6e7 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMemory.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMemory.cs @@ -1126,11 +1126,23 @@ public static void WriteAddressTranslation(MemoryManagerType mmType, RegisterAll Operand destination64 = new(destination.Kind, OperandType.I64, destination.Value); Operand basePointer = new(regAlloc.FixedPageTableRegister, RegisterType.Integer, OperandType.I64); - if (mmType == MemoryManagerType.HostMapped || mmType == MemoryManagerType.HostMappedUnsafe) + // We don't need to mask the address for the safe mode, since it is already naturally limited to 32-bit + // and can never reach out of the guest address space. + + if (mmType.IsHostTracked()) { - // We don't need to mask the address for the safe mode, since it is already naturally limited to 32-bit - // and can never reach out of the guest address space. + int tempRegister = regAlloc.AllocateTempGprRegister(); + + Operand pte = new(tempRegister, RegisterType.Integer, OperandType.I64); + + asm.Lsr(pte, guestAddress, new Operand(OperandKind.Constant, OperandType.I32, 12)); + asm.LdrRr(pte, basePointer, pte, ArmExtensionType.Uxtx, true); + asm.Add(destination64, pte, guestAddress); + regAlloc.FreeTempGprRegister(tempRegister); + } + else if (mmType.IsHostMapped()) + { asm.Add(destination64, basePointer, guestAddress); } else diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/InstName.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/InstName.cs index 365640645..3391a2c14 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm64/InstName.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/InstName.cs @@ -1131,5 +1131,37 @@ public static bool IsPrivileged(this InstName name) return false; } + + public static bool IsPartialRegisterUpdateMemory(this InstName name) + { + switch (name) + { + case InstName.Ld1AdvsimdSnglAsNoPostIndex: + case InstName.Ld1AdvsimdSnglAsPostIndex: + case InstName.Ld2AdvsimdSnglAsNoPostIndex: + case InstName.Ld2AdvsimdSnglAsPostIndex: + case InstName.Ld3AdvsimdSnglAsNoPostIndex: + case InstName.Ld3AdvsimdSnglAsPostIndex: + case InstName.Ld4AdvsimdSnglAsNoPostIndex: + case InstName.Ld4AdvsimdSnglAsPostIndex: + return true; + } + + return false; + } + + public static bool IsPrefetchMemory(this InstName name) + { + switch (name) + { + case InstName.PrfmImm: + case InstName.PrfmLit: + case InstName.PrfmReg: + case InstName.Prfum: + return true; + } + + return false; + } } } diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterAllocator.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterAllocator.cs index c9a932093..1c6eab0de 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterAllocator.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterAllocator.cs @@ -1,15 +1,12 @@ +using ARMeilleure.Memory; using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; using System; -using System.Diagnostics; using System.Numerics; namespace Ryujinx.Cpu.LightningJit.Arm64 { class RegisterAllocator { - public const int MaxTemps = 1; - public const int MaxTempsInclFixed = MaxTemps + 2; - private uint _gprMask; private readonly uint _fpSimdMask; private readonly uint _pStateMask; @@ -25,7 +22,7 @@ class RegisterAllocator public uint AllFpSimdMask => _fpSimdMask; public uint AllPStateMask => _pStateMask; - public RegisterAllocator(uint gprMask, uint fpSimdMask, uint pStateMask, bool hasHostCall) + public RegisterAllocator(MemoryManagerType mmType, uint gprMask, uint fpSimdMask, uint pStateMask, bool hasHostCall) { _gprMask = gprMask; _fpSimdMask = fpSimdMask; @@ -56,7 +53,7 @@ public RegisterAllocator(uint gprMask, uint fpSimdMask, uint pStateMask, bool ha BuildRegisterMap(_registerMap); - Span tempRegisters = stackalloc int[MaxTemps]; + Span tempRegisters = stackalloc int[CalculateMaxTemps(mmType)]; for (int index = 0; index < tempRegisters.Length; index++) { @@ -150,5 +147,15 @@ private static void FreeTempRegister(ref uint mask, int index) { mask &= ~(1u << index); } + + public static int CalculateMaxTemps(MemoryManagerType mmType) + { + return mmType.IsHostMapped() ? 1 : 2; + } + + public static int CalculateMaxTempsInclFixed(MemoryManagerType mmType) + { + return CalculateMaxTemps(mmType) + 2; + } } } diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterUtils.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterUtils.cs index eb3fc229f..191e03e7b 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterUtils.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterUtils.cs @@ -247,7 +247,7 @@ public static (uint, uint) PopulateWriteMasks(InstName name, InstFlags flags, ui } } - if (!flags.HasFlag(InstFlags.ReadRt)) + if (!flags.HasFlag(InstFlags.ReadRt) || name.IsPartialRegisterUpdateMemory()) { if (flags.HasFlag(InstFlags.Rt)) { @@ -281,7 +281,7 @@ public static (uint, uint) PopulateWriteMasks(InstName name, InstFlags flags, ui gprMask |= MaskFromIndex(ExtractRd(flags, encoding)); } - if (!flags.HasFlag(InstFlags.ReadRt)) + if (!flags.HasFlag(InstFlags.ReadRt) || name.IsPartialRegisterUpdateMemory()) { if (flags.HasFlag(InstFlags.Rt)) { diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Compiler.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Compiler.cs index 7ef3bf49b..7a6d761e8 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Compiler.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Compiler.cs @@ -316,7 +316,7 @@ public static CompiledFunction Compile(CpuPreset cpuPreset, IMemoryManager memor uint pStateUseMask = multiBlock.GlobalUseMask.PStateMask; CodeWriter writer = new(); - RegisterAllocator regAlloc = new(gprUseMask, fpSimdUseMask, pStateUseMask, multiBlock.HasHostCall); + RegisterAllocator regAlloc = new(memoryManager.Type, gprUseMask, fpSimdUseMask, pStateUseMask, multiBlock.HasHostCall); RegisterSaveRestore rsr = new( regAlloc.AllGprMask & AbiConstants.GprCalleeSavedRegsMask, regAlloc.AllFpSimdMask & AbiConstants.FpSimdCalleeSavedRegsMask, diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Decoder.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Decoder.cs index 00a1758f2..d5e1eb19c 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Decoder.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Decoder.cs @@ -274,7 +274,8 @@ private static Block Decode( uint tempGprUseMask = gprUseMask | instGprReadMask | instGprWriteMask; - if (CalculateAvailableTemps(tempGprUseMask) < CalculateRequiredGprTemps(tempGprUseMask) || totalInsts++ >= MaxInstructionsPerFunction) + if (CalculateAvailableTemps(tempGprUseMask) < CalculateRequiredGprTemps(memoryManager.Type, tempGprUseMask) || + totalInsts++ >= MaxInstructionsPerFunction) { isTruncated = true; address -= 4UL; @@ -378,9 +379,9 @@ private static bool IsBackwardsBranch(InstName name, uint encoding) return false; } - private static int CalculateRequiredGprTemps(uint gprUseMask) + private static int CalculateRequiredGprTemps(MemoryManagerType mmType, uint gprUseMask) { - return BitOperations.PopCount(gprUseMask & RegisterUtils.ReservedRegsMask) + RegisterAllocator.MaxTempsInclFixed; + return BitOperations.PopCount(gprUseMask & RegisterUtils.ReservedRegsMask) + RegisterAllocator.CalculateMaxTempsInclFixed(mmType); } private static int CalculateAvailableTemps(uint gprUseMask) diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitMemory.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitMemory.cs index e03d9373a..790a7de95 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitMemory.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitMemory.cs @@ -55,6 +55,16 @@ public static void RewriteInstruction( ulong pc, uint encoding) { + if (name.IsPrefetchMemory() && mmType == MemoryManagerType.HostTrackedUnsafe) + { + // Prefetch to invalid addresses do not cause faults, so for memory manager + // types where we need to access the page table before doing the prefetch, + // we should make sure we won't try to access an out of bounds page table region. + // To do this, we force the masked memory manager variant to be used. + + mmType = MemoryManagerType.HostTracked; + } + switch (addressForm) { case AddressForm.OffsetReg: @@ -511,18 +521,48 @@ private static void WriteAddressTranslation( WriteAddressTranslation(asBits, mmType, regAlloc, ref asm, destination, guestAddress); } - private static void WriteAddressTranslation(int asBits, MemoryManagerType mmType, RegisterAllocator regAlloc, ref Assembler asm, Operand destination, ulong guestAddress) + private static void WriteAddressTranslation( + int asBits, + MemoryManagerType mmType, + RegisterAllocator regAlloc, + ref Assembler asm, + Operand destination, + ulong guestAddress) { asm.Mov(destination, guestAddress); WriteAddressTranslation(asBits, mmType, regAlloc, ref asm, destination, destination); } - private static void WriteAddressTranslation(int asBits, MemoryManagerType mmType, RegisterAllocator regAlloc, ref Assembler asm, Operand destination, Operand guestAddress) + private static void WriteAddressTranslation( + int asBits, + MemoryManagerType mmType, + RegisterAllocator regAlloc, + ref Assembler asm, + Operand destination, + Operand guestAddress) { Operand basePointer = new(regAlloc.FixedPageTableRegister, RegisterType.Integer, OperandType.I64); - if (mmType == MemoryManagerType.HostMapped || mmType == MemoryManagerType.HostMappedUnsafe) + if (mmType.IsHostTracked()) + { + int tempRegister = regAlloc.AllocateTempGprRegister(); + + Operand pte = new(tempRegister, RegisterType.Integer, OperandType.I64); + + asm.Lsr(pte, guestAddress, new Operand(OperandKind.Constant, OperandType.I32, 12)); + + if (mmType == MemoryManagerType.HostTracked) + { + asm.And(pte, pte, new Operand(OperandKind.Constant, OperandType.I64, ulong.MaxValue >> (64 - (asBits - 12)))); + } + + asm.LdrRr(pte, basePointer, pte, ArmExtensionType.Uxtx, true); + asm.Add(destination, pte, guestAddress); + + regAlloc.FreeTempGprRegister(tempRegister); + } + else if (mmType.IsHostMapped()) { if (mmType == MemoryManagerType.HostMapped) { diff --git a/src/Ryujinx.Cpu/LightningJit/Translator.cs b/src/Ryujinx.Cpu/LightningJit/Translator.cs index c883c1d60..d62410253 100644 --- a/src/Ryujinx.Cpu/LightningJit/Translator.cs +++ b/src/Ryujinx.Cpu/LightningJit/Translator.cs @@ -68,9 +68,9 @@ public Translator(IMemoryManager memory, bool for64Bits) FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub; - if (memory.Type.IsHostMapped()) + if (memory.Type.IsHostMappedOrTracked()) { - NativeSignalHandler.InitializeSignalHandler(MemoryBlock.GetPageSize()); + NativeSignalHandler.InitializeSignalHandler(); } } diff --git a/src/Ryujinx.Cpu/MemoryEhMeilleure.cs b/src/Ryujinx.Cpu/MemoryEhMeilleure.cs index f3a5b056b..379ace941 100644 --- a/src/Ryujinx.Cpu/MemoryEhMeilleure.cs +++ b/src/Ryujinx.Cpu/MemoryEhMeilleure.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common; using Ryujinx.Cpu.Signal; using Ryujinx.Memory; using Ryujinx.Memory.Tracking; @@ -8,19 +9,27 @@ namespace Ryujinx.Cpu { public class MemoryEhMeilleure : IDisposable { - private delegate bool TrackingEventDelegate(ulong address, ulong size, bool write); + public delegate ulong TrackingEventDelegate(ulong address, ulong size, bool write); + private readonly MemoryTracking _tracking; private readonly TrackingEventDelegate _trackingEvent; + private readonly ulong _pageSize; + private readonly ulong _baseAddress; private readonly ulong _mirrorAddress; - public MemoryEhMeilleure(MemoryBlock addressSpace, MemoryBlock addressSpaceMirror, MemoryTracking tracking) + public MemoryEhMeilleure(MemoryBlock addressSpace, MemoryBlock addressSpaceMirror, MemoryTracking tracking, TrackingEventDelegate trackingEvent = null) { _baseAddress = (ulong)addressSpace.Pointer; + ulong endAddress = _baseAddress + addressSpace.Size; - _trackingEvent = tracking.VirtualMemoryEvent; + _tracking = tracking; + _trackingEvent = trackingEvent ?? VirtualMemoryEvent; + + _pageSize = MemoryBlock.GetPageSize(); + bool added = NativeSignalHandler.AddTrackedRegion((nuint)_baseAddress, (nuint)endAddress, Marshal.GetFunctionPointerForDelegate(_trackingEvent)); if (!added) @@ -28,7 +37,7 @@ public MemoryEhMeilleure(MemoryBlock addressSpace, MemoryBlock addressSpaceMirro throw new InvalidOperationException("Number of allowed tracked regions exceeded."); } - if (OperatingSystem.IsWindows()) + if (OperatingSystem.IsWindows() && addressSpaceMirror != null) { // Add a tracking event with no signal handler for the mirror on Windows. // The native handler has its own code to check for the partial overlap race when regions are protected by accident, @@ -46,6 +55,21 @@ public MemoryEhMeilleure(MemoryBlock addressSpace, MemoryBlock addressSpaceMirro } } + private ulong VirtualMemoryEvent(ulong address, ulong size, bool write) + { + ulong pageSize = _pageSize; + ulong addressAligned = BitUtils.AlignDown(address, pageSize); + ulong endAddressAligned = BitUtils.AlignUp(address + size, pageSize); + ulong sizeAligned = endAddressAligned - addressAligned; + + if (_tracking.VirtualMemoryEvent(addressAligned, sizeAligned, write)) + { + return _baseAddress + address; + } + + return 0; + } + public void Dispose() { GC.SuppressFinalize(this); diff --git a/src/Ryujinx.Cpu/PrivateMemoryAllocator.cs b/src/Ryujinx.Cpu/PrivateMemoryAllocator.cs index ce8e83419..8db74f1e9 100644 --- a/src/Ryujinx.Cpu/PrivateMemoryAllocator.cs +++ b/src/Ryujinx.Cpu/PrivateMemoryAllocator.cs @@ -143,7 +143,7 @@ public virtual void Destroy() } } - public PrivateMemoryAllocator(int blockAlignment, MemoryAllocationFlags allocationFlags) : base(blockAlignment, allocationFlags) + public PrivateMemoryAllocator(ulong blockAlignment, MemoryAllocationFlags allocationFlags) : base(blockAlignment, allocationFlags) { } @@ -180,10 +180,10 @@ public Allocation(T block, ulong offset, ulong size) private readonly List _blocks; - private readonly int _blockAlignment; + private readonly ulong _blockAlignment; private readonly MemoryAllocationFlags _allocationFlags; - public PrivateMemoryAllocatorImpl(int blockAlignment, MemoryAllocationFlags allocationFlags) + public PrivateMemoryAllocatorImpl(ulong blockAlignment, MemoryAllocationFlags allocationFlags) { _blocks = new List(); _blockAlignment = blockAlignment; @@ -212,7 +212,7 @@ protected Allocation Allocate(ulong size, ulong alignment, Func customSignalHandlerFactory = null) + public static void InitializeSignalHandler(Func customSignalHandlerFactory = null) { if (_initialized) { @@ -90,7 +90,7 @@ public static void InitializeSignalHandler(ulong pageSize, FuncThe size of the flushing memory access public void FlushAction(TextureGroupHandle handle, ulong address, ulong size) { - // If the page size is larger than 4KB, we will have a lot of false positives for flushing. - // Let's avoid flushing textures that are unlikely to be read from CPU to improve performance - // on those platforms. - if (!_physicalMemory.Supports4KBPages && !Storage.Info.IsLinear && !_context.IsGpuThread()) - { - return; - } - // There is a small gap here where the action is removed but _actionRegistered is still 1. // In this case it will skip registering the action, but here we are already handling it, // so there shouldn't be any issue as it's the same handler for all actions. diff --git a/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs b/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs index cca02bb15..ce970fab7 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs @@ -23,11 +23,6 @@ class PhysicalMemory : IDisposable private readonly IVirtualMemoryManagerTracked _cpuMemory; private int _referenceCount; - /// - /// Indicates whenever the memory manager supports 4KB pages. - /// - public bool Supports4KBPages => _cpuMemory.Supports4KBPages; - /// /// In-memory shader cache. /// diff --git a/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs index 06b8fd345..e8c433269 100644 --- a/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs +++ b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs @@ -72,7 +72,8 @@ public IProcessContext Create(KernelContext context, ulong pid, ulong addressSpa AddressSpace addressSpace = null; - if (mode == MemoryManagerMode.HostMapped || mode == MemoryManagerMode.HostMappedUnsafe) + // We want to use host tracked mode if the host page size is > 4KB. + if ((mode == MemoryManagerMode.HostMapped || mode == MemoryManagerMode.HostMappedUnsafe) && MemoryBlock.GetPageSize() <= 0x1000) { if (!AddressSpace.TryCreate(context.Memory, addressSpaceSize, MemoryBlock.GetPageSize() == MemoryManagerHostMapped.PageSize, out addressSpace)) { @@ -91,13 +92,21 @@ public IProcessContext Create(KernelContext context, ulong pid, ulong addressSpa case MemoryManagerMode.HostMapped: case MemoryManagerMode.HostMappedUnsafe: - if (addressSpaceSize != addressSpace.AddressSpaceSize) + if (addressSpace == null) { - Logger.Warning?.Print(LogClass.Emulation, $"Allocated address space (0x{addressSpace.AddressSpaceSize:X}) is smaller than guest application requirements (0x{addressSpaceSize:X})"); + var memoryManagerHostTracked = new MemoryManagerHostTracked(context.Memory, addressSpaceSize, mode == MemoryManagerMode.HostMappedUnsafe, invalidAccessHandler); + processContext = new ArmProcessContext(pid, cpuEngine, _gpu, memoryManagerHostTracked, addressSpaceSize, for64Bit); } + else + { + if (addressSpaceSize != addressSpace.AddressSpaceSize) + { + Logger.Warning?.Print(LogClass.Emulation, $"Allocated address space (0x{addressSpace.AddressSpaceSize:X}) is smaller than guest application requirements (0x{addressSpaceSize:X})"); + } - var memoryManagerHostMapped = new MemoryManagerHostMapped(addressSpace, mode == MemoryManagerMode.HostMappedUnsafe, invalidAccessHandler); - processContext = new ArmProcessContext(pid, cpuEngine, _gpu, memoryManagerHostMapped, addressSpace.AddressSpaceSize, for64Bit); + var memoryManagerHostMapped = new MemoryManagerHostMapped(addressSpace, mode == MemoryManagerMode.HostMappedUnsafe, invalidAccessHandler); + processContext = new ArmProcessContext(pid, cpuEngine, _gpu, memoryManagerHostMapped, addressSpace.AddressSpaceSize, for64Bit); + } break; default: diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs index 543acb7a0..d7b601d1c 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs @@ -165,6 +165,29 @@ protected override Result MapPages( /// protected override Result MapForeign(IEnumerable regions, ulong va, ulong size) { + ulong backingStart = (ulong)Context.Memory.Pointer; + ulong backingEnd = backingStart + Context.Memory.Size; + + KPageList pageList = new(); + + foreach (HostMemoryRange region in regions) + { + // If the range is inside the physical memory, it is shared and we should increment the page count, + // otherwise it is private and we don't need to increment the page count. + + if (region.Address >= backingStart && region.Address < backingEnd) + { + pageList.AddRange(region.Address - backingStart + DramMemoryMap.DramBase, region.Size / PageSize); + } + } + + using var scopedPageList = new KScopedPageList(Context.MemoryManager, pageList); + + foreach (var pageNode in pageList) + { + Context.CommitMemory(pageNode.Address - DramMemoryMap.DramBase, pageNode.PagesCount * PageSize); + } + ulong offset = 0; foreach (var region in regions) @@ -174,6 +197,8 @@ protected override Result MapForeign(IEnumerable regions, ulong offset += region.Size; } + scopedPageList.SignalSuccess(); + return Result.Success; } diff --git a/src/Ryujinx.Memory/AddressSpaceManager.cs b/src/Ryujinx.Memory/AddressSpaceManager.cs index 0a4a95143..f19b45b65 100644 --- a/src/Ryujinx.Memory/AddressSpaceManager.cs +++ b/src/Ryujinx.Memory/AddressSpaceManager.cs @@ -283,9 +283,9 @@ public IEnumerable GetPhysicalRegions(ulong va, ulong size) { var hostRegion = hostRegions[i]; - if ((ulong)hostRegion.Address >= backingStart && (ulong)hostRegion.Address < backingEnd) + if (hostRegion.Address >= backingStart && hostRegion.Address < backingEnd) { - regions[count++] = new MemoryRange((ulong)hostRegion.Address - backingStart, hostRegion.Size); + regions[count++] = new MemoryRange(hostRegion.Address - backingStart, hostRegion.Size); } } diff --git a/src/Ryujinx.Memory/Tracking/VirtualRegion.cs b/src/Ryujinx.Memory/Tracking/VirtualRegion.cs index bb087e9af..35e9c2d9b 100644 --- a/src/Ryujinx.Memory/Tracking/VirtualRegion.cs +++ b/src/Ryujinx.Memory/Tracking/VirtualRegion.cs @@ -70,9 +70,12 @@ public void SignalMappingChanged(bool mapped) { _lastPermission = MemoryPermission.Invalid; - foreach (RegionHandle handle in Handles) + if (!Guest) { - handle.SignalMappingChanged(mapped); + foreach (RegionHandle handle in Handles) + { + handle.SignalMappingChanged(mapped); + } } } From 7124d679fd4345f2ed517e77ab40d90e6bef9650 Mon Sep 17 00:00:00 2001 From: MutantAura <44103205+MutantAura@users.noreply.github.com> Date: Wed, 27 Mar 2024 17:55:34 +0000 Subject: [PATCH 126/126] UI: Friendly driver name reporting. (#6530) * Implement friendly VkDriverID names for UI. * Capitalise NVIDIA * Prefer vendor name on macOS * Typo fix Co-authored-by: gdkchan --------- Co-authored-by: gdkchan --- src/Ryujinx.Graphics.Vulkan/Vendor.cs | 32 +++++++++++++++++++ src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs | 7 ++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/Ryujinx.Graphics.Vulkan/Vendor.cs b/src/Ryujinx.Graphics.Vulkan/Vendor.cs index ff841dec9..e0f569079 100644 --- a/src/Ryujinx.Graphics.Vulkan/Vendor.cs +++ b/src/Ryujinx.Graphics.Vulkan/Vendor.cs @@ -1,3 +1,4 @@ +using Silk.NET.Vulkan; using System.Text.RegularExpressions; namespace Ryujinx.Graphics.Vulkan @@ -61,5 +62,36 @@ public static string GetNameFromId(uint id) _ => $"0x{id:X}", }; } + + public static string GetFriendlyDriverName(DriverId id) + { + return id switch + { + 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.MesaTurnip => "Turnip", + DriverId.MesaV3DV => "V3DV", + DriverId.MesaVenus => "Venus", + DriverId.Moltenvk => "MoltenVK", + DriverId.NvidiaProprietary => "NVIDIA", + DriverId.QualcommProprietary => "Qualcomm", + DriverId.SamsungProprietary => "Samsung", + DriverId.VerisiliconProprietary => "Verisilicon", + _ => id.ToString(), + }; + } } } diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index ede54a6fc..d1afeaeae 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -313,8 +313,6 @@ private unsafe void LoadFeatures(uint maxQueueCount, uint queueFamilyIndex) var hasDriverProperties = _physicalDevice.TryGetPhysicalDeviceDriverPropertiesKHR(Api, out var driverProperties); - string vendorName = VendorUtils.GetNameFromId(properties.VendorID); - Vendor = VendorUtils.FromId(properties.VendorID); IsAmdWindows = Vendor == Vendor.Amd && OperatingSystem.IsWindows(); @@ -326,8 +324,9 @@ private unsafe void LoadFeatures(uint maxQueueCount, uint queueFamilyIndex) Vendor == Vendor.Broadcom || Vendor == Vendor.ImgTec; - GpuVendor = vendorName; - GpuDriver = hasDriverProperties ? Marshal.PtrToStringAnsi((IntPtr)driverProperties.DriverName) : vendorName; // Fall back to vendor name if driver name isn't available. + GpuVendor = VendorUtils.GetNameFromId(properties.VendorID); + GpuDriver = hasDriverProperties && !OperatingSystem.IsMacOS() ? + VendorUtils.GetFriendlyDriverName(driverProperties.DriverID) : GpuVendor; // Fallback to vendor name if driver is unavailable or on MacOS where vendor is preferred. fixed (byte* deviceName = properties.DeviceName) {