From 95017b8c66f70406e926b278ecdd6d4ec0a93110 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Mon, 2 May 2022 20:30:02 -0300 Subject: [PATCH 01/33] Support memory aliasing (#2954) * Back to the origins: Make memory manager take guest PA rather than host address once again * Direct mapping with alias support on Windows * Fixes and remove more of the emulated shared memory * Linux support * Make shared and transfer memory not depend on SharedMemoryStorage * More efficient view mapping on Windows (no more restricted to 4KB pages at a time) * Handle potential access violations caused by partial unmap * Implement host mapping using shared memory on Linux * Add new GetPhysicalAddressChecked method, used to ensure the virtual address is mapped before address translation Also align GetRef behaviour with software memory manager * We don't need a mirrorable memory block for software memory manager mode * Disable memory aliasing tests while we don't have shared memory support on Mac * Shared memory & SIGBUS handler for macOS * Fix typo + nits + re-enable memory tests * Set MAP_JIT_DARWIN on x86 Mac too * Add back the address space mirror * Only set MAP_JIT_DARWIN if we are mapping as executable * Disable aliasing tests again (still fails on Mac) * Fix UnmapView4KB (by not casting size to int) * Use ref counting on memory blocks to delay closing the shared memory handle until all blocks using it are disposed * Address PR feedback * Make RO hold a reference to the guest process memory manager to avoid early disposal Co-authored-by: nastys --- ARMeilleure/Signal/NativeSignalHandler.cs | 2 +- .../Signal/UnixSignalHandlerRegistration.cs | 15 +- ARMeilleure/Translation/IntervalTree.cs | 40 +- Ryujinx.Common/Collections/IntervalTree.cs | 36 +- Ryujinx.Common/Collections/TreeDictionary.cs | 26 +- Ryujinx.Cpu/MemoryEhMeilleure.cs | 2 +- Ryujinx.Cpu/MemoryManager.cs | 128 +-- Ryujinx.Cpu/MemoryManagerHostMapped.cs | 126 ++- Ryujinx.HLE/HOS/ApplicationLoader.cs | 10 +- Ryujinx.HLE/HOS/ArmProcessContextFactory.cs | 9 +- Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs | 78 +- .../HOS/Kernel/Memory/KPageTableBase.cs | 64 +- .../HOS/Kernel/Memory/KPageTableHostMapped.cs | 139 ---- .../HOS/Kernel/Memory/KSharedMemory.cs | 35 +- .../HOS/Kernel/Memory/KTransferMemory.cs | 47 +- .../HOS/Kernel/Memory/SharedMemoryStorage.cs | 64 +- Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs | 9 +- .../Kernel/Process/ProcessContextFactory.cs | 2 +- Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs | 11 + Ryujinx.HLE/Switch.cs | 7 +- .../MockVirtualMemoryManager.cs | 6 +- Ryujinx.Memory.Tests/Tests.cs | 44 +- Ryujinx.Memory/AddressSpaceManager.cs | 190 ++--- Ryujinx.Memory/IVirtualMemoryManager.cs | 6 +- Ryujinx.Memory/MemoryAllocationFlags.cs | 14 +- Ryujinx.Memory/MemoryBlock.cs | 130 ++- Ryujinx.Memory/MemoryManagement.cs | 116 ++- Ryujinx.Memory/MemoryManagementUnix.cs | 209 ++--- Ryujinx.Memory/MemoryManagementWindows.cs | 331 +++----- Ryujinx.Memory/MemoryManagerUnixHelper.cs | 41 +- Ryujinx.Memory/PageTable.cs | 2 +- Ryujinx.Memory/Range/HostMemoryRange.cs | 71 -- Ryujinx.Memory/Tracking/MemoryTracking.cs | 24 + .../EmulatedSharedMemoryWindows.cs | 703 ----------------- Ryujinx.Memory/WindowsShared/IntervalTree.cs | 740 ++++++++++++++++++ .../WindowsShared/PlaceholderList.cs | 293 ------- .../WindowsShared/PlaceholderManager.cs | 633 +++++++++++++++ Ryujinx.Memory/WindowsShared/WindowsApi.cs | 93 +++ .../WindowsShared/WindowsApiException.cs | 24 + Ryujinx.Tests/Cpu/CpuTest.cs | 4 +- Ryujinx.Tests/Cpu/CpuTest32.cs | 4 +- 41 files changed, 2373 insertions(+), 2155 deletions(-) delete mode 100644 Ryujinx.HLE/HOS/Kernel/Memory/KPageTableHostMapped.cs delete mode 100644 Ryujinx.Memory/Range/HostMemoryRange.cs delete mode 100644 Ryujinx.Memory/WindowsShared/EmulatedSharedMemoryWindows.cs create mode 100644 Ryujinx.Memory/WindowsShared/IntervalTree.cs delete mode 100644 Ryujinx.Memory/WindowsShared/PlaceholderList.cs create mode 100644 Ryujinx.Memory/WindowsShared/PlaceholderManager.cs create mode 100644 Ryujinx.Memory/WindowsShared/WindowsApi.cs create mode 100644 Ryujinx.Memory/WindowsShared/WindowsApiException.cs diff --git a/ARMeilleure/Signal/NativeSignalHandler.cs b/ARMeilleure/Signal/NativeSignalHandler.cs index 8cb2ee987..cad0d4202 100644 --- a/ARMeilleure/Signal/NativeSignalHandler.cs +++ b/ARMeilleure/Signal/NativeSignalHandler.cs @@ -103,7 +103,7 @@ namespace ARMeilleure.Signal // Unix siginfo struct locations. // NOTE: These are incredibly likely to be different between kernel version and architectures. - config.StructAddressOffset = 16; // si_addr + config.StructAddressOffset = OperatingSystem.IsMacOS() ? 24 : 16; // si_addr config.StructWriteOffset = 8; // si_code _signalHandlerPtr = Marshal.GetFunctionPointerForDelegate(GenerateUnixSignalHandler(_handlerConfig)); diff --git a/ARMeilleure/Signal/UnixSignalHandlerRegistration.cs b/ARMeilleure/Signal/UnixSignalHandlerRegistration.cs index 40268a91e..12bda3de2 100644 --- a/ARMeilleure/Signal/UnixSignalHandlerRegistration.cs +++ b/ARMeilleure/Signal/UnixSignalHandlerRegistration.cs @@ -21,6 +21,7 @@ namespace ARMeilleure.Signal static class UnixSignalHandlerRegistration { private const int SIGSEGV = 11; + private const int SIGBUS = 10; private const int SA_SIGINFO = 0x00000004; [DllImport("libc", SetLastError = true)] @@ -43,7 +44,17 @@ namespace ARMeilleure.Signal if (result != 0) { - throw new InvalidOperationException($"Could not register sigaction. Error: {result}"); + throw new InvalidOperationException($"Could not register SIGSEGV sigaction. Error: {result}"); + } + + if (OperatingSystem.IsMacOS()) + { + result = sigaction(SIGBUS, ref sig, out SigAction oldb); + + if (result != 0) + { + throw new InvalidOperationException($"Could not register SIGBUS sigaction. Error: {result}"); + } } return old; @@ -51,7 +62,7 @@ namespace ARMeilleure.Signal public static bool RestoreExceptionHandler(SigAction oldAction) { - return sigaction(SIGSEGV, ref oldAction, out SigAction _) == 0; + return sigaction(SIGSEGV, ref oldAction, out SigAction _) == 0 && (!OperatingSystem.IsMacOS() || sigaction(SIGBUS, ref oldAction, out SigAction _) == 0); } } } diff --git a/ARMeilleure/Translation/IntervalTree.cs b/ARMeilleure/Translation/IntervalTree.cs index 0f7b64850..51b9a51ff 100644 --- a/ARMeilleure/Translation/IntervalTree.cs +++ b/ARMeilleure/Translation/IntervalTree.cs @@ -8,7 +8,7 @@ namespace ARMeilleure.Translation /// /// Key /// Value - public class IntervalTree where K : IComparable + class IntervalTree where K : IComparable { private const int ArrayGrowthSize = 32; @@ -53,7 +53,7 @@ namespace ARMeilleure.Translation /// Number of intervals found public int Get(K start, K end, ref K[] overlaps, int overlapCount = 0) { - GetValues(_root, start, end, ref overlaps, ref overlapCount); + GetKeys(_root, start, end, ref overlaps, ref overlapCount); return overlapCount; } @@ -180,20 +180,20 @@ namespace ARMeilleure.Translation } /// - /// Retrieve all values that overlap the given start and end keys. + /// Retrieve all keys that overlap the given start and end keys. /// /// Start of the range /// End of the range /// Overlaps array to place results in /// Overlaps count to update - private void GetValues(IntervalTreeNode node, K start, K end, ref K[] overlaps, ref int overlapCount) + private void GetKeys(IntervalTreeNode node, K start, K end, ref K[] overlaps, ref int overlapCount) { if (node == null || start.CompareTo(node.Max) >= 0) { return; } - GetValues(node.Left, start, end, ref overlaps, ref overlapCount); + GetKeys(node.Left, start, end, ref overlaps, ref overlapCount); bool endsOnRight = end.CompareTo(node.Start) > 0; if (endsOnRight) @@ -208,7 +208,7 @@ namespace ARMeilleure.Translation overlaps[overlapCount++] = node.Start; } - GetValues(node.Right, start, end, ref overlaps, ref overlapCount); + GetKeys(node.Right, start, end, ref overlaps, ref overlapCount); } } @@ -717,40 +717,40 @@ namespace ARMeilleure.Translation /// /// Key type of the node /// Value type of the node - internal class IntervalTreeNode + class IntervalTreeNode { - internal bool Color = true; - internal IntervalTreeNode Left = null; - internal IntervalTreeNode Right = null; - internal IntervalTreeNode Parent = null; + public bool Color = true; + public IntervalTreeNode Left = null; + public IntervalTreeNode Right = null; + public IntervalTreeNode Parent = null; /// /// The start of the range. /// - internal K Start; + public K Start; /// /// The end of the range. /// - internal K End; + public K End; /// /// The maximum end value of this node and all its children. /// - internal K Max; + public K Max; /// /// Value stored on this node. /// - internal V Value; + public V Value; public IntervalTreeNode(K start, K end, V value, IntervalTreeNode parent) { - this.Start = start; - this.End = end; - this.Max = end; - this.Value = value; - this.Parent = parent; + Start = start; + End = end; + Max = end; + Value = value; + Parent = parent; } } } diff --git a/Ryujinx.Common/Collections/IntervalTree.cs b/Ryujinx.Common/Collections/IntervalTree.cs index e1b81f4e0..514fd5341 100644 --- a/Ryujinx.Common/Collections/IntervalTree.cs +++ b/Ryujinx.Common/Collections/IntervalTree.cs @@ -1,7 +1,5 @@ using System; -using System.Collections; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; namespace Ryujinx.Common.Collections @@ -212,7 +210,7 @@ namespace Ryujinx.Common.Collections /// Overlaps array to place results in /// Overlaps count to update private void GetValues(IntervalTreeNode node, K start, K end, ref V[] overlaps, ref int overlapCount) - { + { if (node == null || start.CompareTo(node.Max) >= 0) { return; @@ -624,7 +622,7 @@ namespace Ryujinx.Common.Collections node.Right = LeftOf(right); if (node.Right != null) { - node.Right.Parent = node; + node.Right.Parent = node; } IntervalTreeNode nodeParent = ParentOf(node); right.Parent = nodeParent; @@ -638,7 +636,7 @@ namespace Ryujinx.Common.Collections } else { - nodeParent.Right = right; + nodeParent.Right = right; } right.Left = node; node.Parent = right; @@ -779,37 +777,37 @@ namespace Ryujinx.Common.Collections /// /// Key type of the node /// Value type of the node - internal class IntervalTreeNode + class IntervalTreeNode { - internal bool Color = true; - internal IntervalTreeNode Left = null; - internal IntervalTreeNode Right = null; - internal IntervalTreeNode Parent = null; + public bool Color = true; + public IntervalTreeNode Left = null; + public IntervalTreeNode Right = null; + public IntervalTreeNode Parent = null; /// /// The start of the range. /// - internal K Start; + public K Start; /// /// The end of the range - maximum of all in the Values list. /// - internal K End; + public K End; /// /// The maximum end value of this node and all its children. /// - internal K Max; + public K Max; - internal List> Values; + public List> Values; public IntervalTreeNode(K start, K end, V value, IntervalTreeNode parent) { - this.Start = start; - this.End = end; - this.Max = end; - this.Values = new List> { new RangeNode(start, end, value) }; - this.Parent = parent; + Start = start; + End = end; + Max = end; + Values = new List> { new RangeNode(start, end, value) }; + Parent = parent; } } } diff --git a/Ryujinx.Common/Collections/TreeDictionary.cs b/Ryujinx.Common/Collections/TreeDictionary.cs index ca9467df7..108fa773b 100644 --- a/Ryujinx.Common/Collections/TreeDictionary.cs +++ b/Ryujinx.Common/Collections/TreeDictionary.cs @@ -921,10 +921,10 @@ namespace Ryujinx.Common.Collections public bool IsReadOnly => false; - public V this[K key] - { + public V this[K key] + { get => Get(key); - set => Add(key, value); + set => Add(key, value); } #endregion @@ -967,20 +967,20 @@ namespace Ryujinx.Common.Collections /// /// Key of the node /// Value of the node - internal class Node + class Node { - internal bool Color = true; - internal Node Left = null; - internal Node Right = null; - internal Node Parent = null; - internal K Key; - internal V Value; + public bool Color = true; + public Node Left = null; + public Node Right = null; + public Node Parent = null; + public K Key; + public V Value; public Node(K key, V value, Node parent) { - this.Key = key; - this.Value = value; - this.Parent = parent; + Key = key; + Value = value; + Parent = parent; } } } diff --git a/Ryujinx.Cpu/MemoryEhMeilleure.cs b/Ryujinx.Cpu/MemoryEhMeilleure.cs index 73edec47e..a82295819 100644 --- a/Ryujinx.Cpu/MemoryEhMeilleure.cs +++ b/Ryujinx.Cpu/MemoryEhMeilleure.cs @@ -24,7 +24,7 @@ namespace Ryujinx.Cpu _baseAddress = (ulong)_addressSpace.Pointer; ulong endAddress = _baseAddress + addressSpace.Size; - _trackingEvent = new TrackingEventDelegate(tracking.VirtualMemoryEvent); + _trackingEvent = new TrackingEventDelegate(tracking.VirtualMemoryEventEh); bool added = NativeSignalHandler.AddTrackedRegion((nuint)_baseAddress, (nuint)endAddress, Marshal.GetFunctionPointerForDelegate(_trackingEvent)); if (!added) diff --git a/Ryujinx.Cpu/MemoryManager.cs b/Ryujinx.Cpu/MemoryManager.cs index 85ab763e4..b9769fd40 100644 --- a/Ryujinx.Cpu/MemoryManager.cs +++ b/Ryujinx.Cpu/MemoryManager.cs @@ -25,6 +25,7 @@ namespace Ryujinx.Cpu private const int PointerTagBit = 62; + private readonly MemoryBlock _backingMemory; private readonly InvalidAccessHandler _invalidAccessHandler; /// @@ -50,10 +51,12 @@ namespace Ryujinx.Cpu /// /// Creates a new instance of the memory manager. /// + /// Physical backing memory where virtual memory will be mapped to /// Size of the address space /// Optional function to handle invalid memory accesses - public MemoryManager(ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler = null) + public MemoryManager(MemoryBlock backingMemory, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler = null) { + _backingMemory = backingMemory; _invalidAccessHandler = invalidAccessHandler; ulong asSize = PageSize; @@ -73,18 +76,19 @@ namespace Ryujinx.Cpu } /// - public void Map(ulong va, nuint hostAddress, ulong size) + public void Map(ulong va, ulong pa, ulong size) { AssertValidAddressAndSize(va, size); ulong remainingSize = size; ulong oVa = va; + ulong oPa = pa; while (remainingSize != 0) { - _pageTable.Write((va / PageSize) * PteSize, hostAddress); + _pageTable.Write((va / PageSize) * PteSize, PaToPte(pa)); va += PageSize; - hostAddress += PageSize; + pa += PageSize; remainingSize -= PageSize; } Tracking.Map(oVa, size); @@ -107,7 +111,7 @@ namespace Ryujinx.Cpu ulong remainingSize = size; while (remainingSize != 0) { - _pageTable.Write((va / PageSize) * PteSize, (nuint)0); + _pageTable.Write((va / PageSize) * PteSize, 0UL); va += PageSize; remainingSize -= PageSize; @@ -123,8 +127,21 @@ namespace Ryujinx.Cpu /// public T ReadTracked(ulong va) where T : unmanaged { - SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), false); - return MemoryMarshal.Cast(GetSpan(va, Unsafe.SizeOf()))[0]; + try + { + SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), false); + + return Read(va); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + + return default; + } } /// @@ -177,7 +194,7 @@ namespace Ryujinx.Cpu if (IsContiguousAndMapped(va, data.Length)) { - data.CopyTo(GetHostSpanContiguous(va, data.Length)); + data.CopyTo(_backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length)); } else { @@ -185,18 +202,22 @@ namespace Ryujinx.Cpu if ((va & PageMask) != 0) { + ulong pa = GetPhysicalAddressInternal(va); + size = Math.Min(data.Length, PageSize - (int)(va & PageMask)); - data.Slice(0, size).CopyTo(GetHostSpanContiguous(va, size)); + data.Slice(0, size).CopyTo(_backingMemory.GetSpan(pa, size)); offset += size; } for (; offset < data.Length; offset += size) { + ulong pa = GetPhysicalAddressInternal(va + (ulong)offset); + size = Math.Min(data.Length - offset, PageSize); - data.Slice(offset, size).CopyTo(GetHostSpanContiguous(va + (ulong)offset, size)); + data.Slice(offset, size).CopyTo(_backingMemory.GetSpan(pa, size)); } } } @@ -224,7 +245,7 @@ namespace Ryujinx.Cpu if (IsContiguousAndMapped(va, size)) { - return GetHostSpanContiguous(va, size); + return _backingMemory.GetSpan(GetPhysicalAddressInternal(va), size); } else { @@ -251,7 +272,7 @@ namespace Ryujinx.Cpu SignalMemoryTracking(va, (ulong)size, true); } - return new WritableRegion(null, va, new NativeMemoryManager((byte*)GetHostAddress(va), size).Memory); + return new WritableRegion(null, va, _backingMemory.GetMemory(GetPhysicalAddressInternal(va), size)); } else { @@ -264,7 +285,7 @@ namespace Ryujinx.Cpu } /// - public unsafe ref T GetRef(ulong va) where T : unmanaged + public ref T GetRef(ulong va) where T : unmanaged { if (!IsContiguous(va, Unsafe.SizeOf())) { @@ -273,7 +294,7 @@ namespace Ryujinx.Cpu SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), true); - return ref *(T*)GetHostAddress(va); + return ref _backingMemory.GetRef(GetPhysicalAddressInternal(va)); } /// @@ -293,7 +314,7 @@ namespace Ryujinx.Cpu return (int)(vaSpan / PageSize); } - private void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException(); + private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException(); [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool IsContiguousAndMapped(ulong va, int size) => IsContiguous(va, size) && IsMapped(va); @@ -315,7 +336,7 @@ namespace Ryujinx.Cpu return false; } - if (GetHostAddress(va) + PageSize != GetHostAddress(va + PageSize)) + if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize)) { return false; } @@ -327,11 +348,11 @@ namespace Ryujinx.Cpu } /// - public IEnumerable GetPhysicalRegions(ulong va, ulong size) + public IEnumerable GetPhysicalRegions(ulong va, ulong size) { if (size == 0) { - return Enumerable.Empty(); + return Enumerable.Empty(); } if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size)) @@ -341,9 +362,9 @@ namespace Ryujinx.Cpu int pages = GetPagesCount(va, (uint)size, out va); - var regions = new List(); + var regions = new List(); - nuint regionStart = GetHostAddress(va); + ulong regionStart = GetPhysicalAddressInternal(va); ulong regionSize = PageSize; for (int page = 0; page < pages - 1; page++) @@ -353,12 +374,12 @@ namespace Ryujinx.Cpu return null; } - nuint newHostAddress = GetHostAddress(va + PageSize); + ulong newPa = GetPhysicalAddressInternal(va + PageSize); - if (GetHostAddress(va) + PageSize != newHostAddress) + if (GetPhysicalAddressInternal(va) + PageSize != newPa) { - regions.Add(new HostMemoryRange(regionStart, regionSize)); - regionStart = newHostAddress; + regions.Add(new MemoryRange(regionStart, regionSize)); + regionStart = newPa; regionSize = 0; } @@ -366,7 +387,7 @@ namespace Ryujinx.Cpu regionSize += PageSize; } - regions.Add(new HostMemoryRange(regionStart, regionSize)); + regions.Add(new MemoryRange(regionStart, regionSize)); return regions; } @@ -386,18 +407,22 @@ namespace Ryujinx.Cpu if ((va & PageMask) != 0) { + ulong pa = GetPhysicalAddressInternal(va); + size = Math.Min(data.Length, PageSize - (int)(va & PageMask)); - GetHostSpanContiguous(va, size).CopyTo(data.Slice(0, size)); + _backingMemory.GetSpan(pa, size).CopyTo(data.Slice(0, size)); offset += size; } for (; offset < data.Length; offset += size) { + ulong pa = GetPhysicalAddressInternal(va + (ulong)offset); + size = Math.Min(data.Length - offset, PageSize); - GetHostSpanContiguous(va + (ulong)offset, size).CopyTo(data.Slice(offset, size)); + _backingMemory.GetSpan(pa, size).CopyTo(data.Slice(offset, size)); } } catch (InvalidMemoryRegionException) @@ -446,7 +471,7 @@ namespace Ryujinx.Cpu return false; } - return _pageTable.Read((va / PageSize) * PteSize) != 0; + return _pageTable.Read((va / PageSize) * PteSize) != 0; } private bool ValidateAddress(ulong va) @@ -480,37 +505,20 @@ namespace Ryujinx.Cpu } } - /// - /// Get a span representing the given virtual address and size range in host memory. - /// This function assumes that the requested virtual memory region is contiguous. - /// - /// Virtual address of the range - /// Size of the range in bytes - /// A span representing the given virtual range in host memory - /// Throw when the base virtual address is not mapped - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe Span GetHostSpanContiguous(ulong va, int size) + private ulong GetPhysicalAddress(ulong va) { - return new Span((void*)GetHostAddress(va), size); - } - - /// - /// Get the host address for a given virtual address, using the page table. - /// - /// Virtual address - /// The corresponding host address for the given virtual address - /// Throw when the virtual address is not mapped - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private nuint GetHostAddress(ulong va) - { - nuint pageBase = _pageTable.Read((va / PageSize) * PteSize) & unchecked((nuint)0xffff_ffff_ffffUL); - - if (pageBase == 0) + // We return -1L if the virtual address is invalid or unmapped. + if (!ValidateAddress(va) || !IsMapped(va)) { - ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}"); + return ulong.MaxValue; } - return pageBase + (nuint)(va & PageMask); + return GetPhysicalAddressInternal(va); + } + + private ulong GetPhysicalAddressInternal(ulong va) + { + return PteToPa(_pageTable.Read((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask); } /// @@ -604,6 +612,16 @@ namespace Ryujinx.Cpu } } + private ulong PaToPte(ulong pa) + { + return (ulong)_backingMemory.GetPointer(pa, PageSize); + } + + private ulong PteToPa(ulong pte) + { + return (ulong)((long)pte - _backingMemory.Pointer.ToInt64()); + } + /// /// Disposes of resources used by the memory manager. /// diff --git a/Ryujinx.Cpu/MemoryManagerHostMapped.cs b/Ryujinx.Cpu/MemoryManagerHostMapped.cs index c37d23a51..b5abae0cf 100644 --- a/Ryujinx.Cpu/MemoryManagerHostMapped.cs +++ b/Ryujinx.Cpu/MemoryManagerHostMapped.cs @@ -5,7 +5,6 @@ using Ryujinx.Memory.Range; using Ryujinx.Memory.Tracking; using System; using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; using System.Threading; @@ -14,7 +13,7 @@ namespace Ryujinx.Cpu /// /// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region. /// - public class MemoryManagerHostMapped : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked + public class MemoryManagerHostMapped : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock { public const int PageBits = 12; public const int PageSize = 1 << PageBits; @@ -42,9 +41,12 @@ namespace Ryujinx.Cpu private readonly MemoryBlock _addressSpaceMirror; private readonly ulong _addressSpaceSize; + private readonly MemoryBlock _backingMemory; + private readonly PageTable _pageTable; + private readonly MemoryEhMeilleure _memoryEh; - private ulong[] _pageTable; + private readonly ulong[] _pageBitmap; public int AddressSpaceBits { get; } @@ -59,11 +61,14 @@ namespace Ryujinx.Cpu /// /// Creates a new instance of the host mapped 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 MemoryManagerHostMapped(ulong addressSpaceSize, bool unsafeMode, InvalidAccessHandler invalidAccessHandler = null) + public MemoryManagerHostMapped(MemoryBlock backingMemory, ulong addressSpaceSize, bool unsafeMode, InvalidAccessHandler invalidAccessHandler = null) { + _backingMemory = backingMemory; + _pageTable = new PageTable(); _invalidAccessHandler = invalidAccessHandler; _unsafeMode = unsafeMode; _addressSpaceSize = addressSpaceSize; @@ -79,9 +84,13 @@ namespace Ryujinx.Cpu AddressSpaceBits = asBits; - _pageTable = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))]; - _addressSpace = new MemoryBlock(asSize, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.Mirrorable); - _addressSpaceMirror = _addressSpace.CreateMirror(); + _pageBitmap = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))]; + + MemoryAllocationFlags asFlags = MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible; + + _addressSpace = new MemoryBlock(asSize, asFlags); + _addressSpaceMirror = new MemoryBlock(asSize, asFlags | MemoryAllocationFlags.ForceWindows4KBViewMapping); + Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler); _memoryEh = new MemoryEhMeilleure(_addressSpace, Tracking); } @@ -136,12 +145,14 @@ namespace Ryujinx.Cpu } /// - public void Map(ulong va, nuint hostAddress, ulong size) + public void Map(ulong va, ulong pa, ulong size) { AssertValidAddressAndSize(va, size); - _addressSpace.Commit(va, size); + _addressSpace.MapView(_backingMemory, pa, va, size); + _addressSpaceMirror.MapView(_backingMemory, pa, va, size); AddMapping(va, size); + PtMap(va, pa, size); Tracking.Map(va, size); } @@ -155,7 +166,32 @@ namespace Ryujinx.Cpu Tracking.Unmap(va, size); RemoveMapping(va, size); - _addressSpace.Decommit(va, size); + PtUnmap(va, size); + _addressSpace.UnmapView(_backingMemory, va, size); + _addressSpaceMirror.UnmapView(_backingMemory, va, size); + } + + private void PtMap(ulong va, ulong pa, ulong size) + { + while (size != 0) + { + _pageTable.Map(va, pa); + + va += PageSize; + pa += PageSize; + size -= PageSize; + } + } + + private void PtUnmap(ulong va, ulong size) + { + while (size != 0) + { + _pageTable.Unmap(va); + + va += PageSize; + size -= PageSize; + } } /// @@ -216,6 +252,7 @@ namespace Ryujinx.Cpu } } + /// public void Write(ulong va, T value) where T : unmanaged { @@ -267,7 +304,7 @@ namespace Ryujinx.Cpu throw; } } -} + } /// public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) @@ -322,7 +359,7 @@ namespace Ryujinx.Cpu int bit = (int)((page & 31) << 1); int pageIndex = (int)(page >> PageToPteShift); - ref ulong pageRef = ref _pageTable[pageIndex]; + ref ulong pageRef = ref _pageBitmap[pageIndex]; ulong pte = Volatile.Read(ref pageRef); @@ -373,7 +410,7 @@ namespace Ryujinx.Cpu mask &= endMask; } - ref ulong pageRef = ref _pageTable[pageIndex++]; + ref ulong pageRef = ref _pageBitmap[pageIndex++]; ulong pte = Volatile.Read(ref pageRef); pte |= pte >> 1; @@ -389,16 +426,53 @@ namespace Ryujinx.Cpu } /// - public IEnumerable GetPhysicalRegions(ulong va, ulong size) + public IEnumerable GetPhysicalRegions(ulong va, ulong size) { - if (size == 0) + int pages = GetPagesCount(va, (uint)size, out va); + + var regions = new List(); + + ulong regionStart = GetPhysicalAddressChecked(va); + ulong regionSize = PageSize; + + for (int page = 0; page < pages - 1; page++) { - return Enumerable.Empty(); + if (!ValidateAddress(va + PageSize)) + { + return null; + } + + ulong newPa = GetPhysicalAddressChecked(va + PageSize); + + if (GetPhysicalAddressChecked(va) + PageSize != newPa) + { + regions.Add(new MemoryRange(regionStart, regionSize)); + regionStart = newPa; + regionSize = 0; + } + + va += PageSize; + regionSize += PageSize; } - AssertMapped(va, size); + regions.Add(new MemoryRange(regionStart, regionSize)); - return new HostMemoryRange[] { new HostMemoryRange(_addressSpaceMirror.GetPointer(va, size), size) }; + return regions; + } + + private ulong GetPhysicalAddressChecked(ulong va) + { + if (!IsMapped(va)) + { + ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}"); + } + + return GetPhysicalAddressInternal(va); + } + + private ulong GetPhysicalAddressInternal(ulong va) + { + return _pageTable.Read(va) + (va & PageMask); } /// @@ -427,7 +501,7 @@ namespace Ryujinx.Cpu int bit = (int)((pageStart & 31) << 1); int pageIndex = (int)(pageStart >> PageToPteShift); - ref ulong pageRef = ref _pageTable[pageIndex]; + ref ulong pageRef = ref _pageBitmap[pageIndex]; ulong pte = Volatile.Read(ref pageRef); ulong state = ((pte >> bit) & 3); @@ -459,7 +533,7 @@ namespace Ryujinx.Cpu mask &= endMask; } - ref ulong pageRef = ref _pageTable[pageIndex++]; + ref ulong pageRef = ref _pageBitmap[pageIndex++]; ulong pte = Volatile.Read(ref pageRef); ulong mappedMask = mask & BlockMappedMask; @@ -530,7 +604,7 @@ namespace Ryujinx.Cpu ulong tag = protTag << bit; int pageIndex = (int)(pageStart >> PageToPteShift); - ref ulong pageRef = ref _pageTable[pageIndex]; + ref ulong pageRef = ref _pageBitmap[pageIndex]; ulong pte; @@ -562,7 +636,7 @@ namespace Ryujinx.Cpu mask &= endMask; } - ref ulong pageRef = ref _pageTable[pageIndex++]; + ref ulong pageRef = ref _pageBitmap[pageIndex++]; ulong pte; ulong mappedMask; @@ -632,7 +706,7 @@ namespace Ryujinx.Cpu mask &= endMask; } - ref ulong pageRef = ref _pageTable[pageIndex++]; + ref ulong pageRef = ref _pageBitmap[pageIndex++]; ulong pte; ulong mappedMask; @@ -677,7 +751,7 @@ namespace Ryujinx.Cpu mask |= endMask; } - ref ulong pageRef = ref _pageTable[pageIndex++]; + ref ulong pageRef = ref _pageBitmap[pageIndex++]; ulong pte; do @@ -695,11 +769,11 @@ namespace Ryujinx.Cpu /// protected override void Destroy() { - _addressSpaceMirror.Dispose(); _addressSpace.Dispose(); + _addressSpaceMirror.Dispose(); _memoryEh.Dispose(); } - private void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message); + private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message); } } diff --git a/Ryujinx.HLE/HOS/ApplicationLoader.cs b/Ryujinx.HLE/HOS/ApplicationLoader.cs index 1c177d20f..80d609b66 100644 --- a/Ryujinx.HLE/HOS/ApplicationLoader.cs +++ b/Ryujinx.HLE/HOS/ApplicationLoader.cs @@ -17,6 +17,7 @@ using Ryujinx.Common.Logging; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.Loaders.Executables; +using Ryujinx.Memory; using System; using System.Collections.Generic; using System.Globalization; @@ -561,7 +562,14 @@ namespace Ryujinx.HLE.HOS Graphics.Gpu.GraphicsConfig.TitleId = TitleIdText; _device.Gpu.HostInitalized.Set(); - Ptc.Initialize(TitleIdText, DisplayVersion, usePtc, _device.Configuration.MemoryManagerMode); + MemoryManagerMode memoryManagerMode = _device.Configuration.MemoryManagerMode; + + if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible)) + { + memoryManagerMode = MemoryManagerMode.SoftwarePageTable; + } + + Ptc.Initialize(TitleIdText, DisplayVersion, usePtc, memoryManagerMode); metaData.GetNpdm(out Npdm npdm).ThrowIfFailure(); ProgramLoader.LoadNsos(_device.System.KernelContext, out ProcessTamperInfo tamperInfo, metaData, new ProgramInfo(in npdm), executables: programs); diff --git a/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs b/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs index 2556b2cc8..0561193ca 100644 --- a/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs +++ b/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs @@ -21,15 +21,20 @@ namespace Ryujinx.HLE.HOS { MemoryManagerMode mode = context.Device.Configuration.MemoryManagerMode; + if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible)) + { + mode = MemoryManagerMode.SoftwarePageTable; + } + switch (mode) { case MemoryManagerMode.SoftwarePageTable: - return new ArmProcessContext(pid, _gpu, new MemoryManager(addressSpaceSize, invalidAccessHandler), for64Bit); + return new ArmProcessContext(pid, _gpu, new MemoryManager(context.Memory, addressSpaceSize, invalidAccessHandler), for64Bit); case MemoryManagerMode.HostMapped: case MemoryManagerMode.HostMappedUnsafe: bool unsafeMode = mode == MemoryManagerMode.HostMappedUnsafe; - return new ArmProcessContext(pid, _gpu, new MemoryManagerHostMapped(addressSpaceSize, unsafeMode, invalidAccessHandler), for64Bit); + return new ArmProcessContext(pid, _gpu, new MemoryManagerHostMapped(context.Memory, addressSpaceSize, unsafeMode, invalidAccessHandler), for64Bit); default: throw new ArgumentOutOfRangeException(); diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs index d7ee04e32..9d5212315 100644 --- a/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs @@ -1,10 +1,7 @@ using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.Memory; -using Ryujinx.Memory.Range; using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; namespace Ryujinx.HLE.HOS.Kernel.Memory { @@ -12,17 +9,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory { private readonly IVirtualMemoryManager _cpuMemory; - public override bool SupportsMemoryAliasing => true; - public KPageTable(KernelContext context, IVirtualMemoryManager cpuMemory) : base(context) { _cpuMemory = cpuMemory; } /// - protected override IEnumerable GetPhysicalRegions(ulong va, ulong size) + protected override void GetPhysicalRegions(ulong va, ulong size, KPageList pageList) { - return _cpuMemory.GetPhysicalRegions(va, size); + var ranges = _cpuMemory.GetPhysicalRegions(va, size); + foreach (var range in ranges) + { + pageList.AddRange(range.Address + DramMemoryMap.DramBase, range.Size / PageSize); + } } /// @@ -34,7 +33,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory /// protected override KernelResult MapMemory(ulong src, ulong dst, ulong pagesCount, KMemoryPermission oldSrcPermission, KMemoryPermission newDstPermission) { - var srcRanges = GetPhysicalRegions(src, pagesCount * PageSize); + KPageList pageList = new KPageList(); + GetPhysicalRegions(src, pagesCount * PageSize, pageList); KernelResult result = Reprotect(src, pagesCount, KMemoryPermission.None); @@ -43,7 +43,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory return result; } - result = MapPages(dst, srcRanges, newDstPermission); + result = MapPages(dst, pageList, newDstPermission, false, 0); if (result != KernelResult.Success) { @@ -59,10 +59,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory { ulong size = pagesCount * PageSize; - var srcRanges = GetPhysicalRegions(src, size); - var dstRanges = GetPhysicalRegions(dst, size); + KPageList srcPageList = new KPageList(); + KPageList dstPageList = new KPageList(); - if (!dstRanges.SequenceEqual(srcRanges)) + GetPhysicalRegions(src, size, srcPageList); + GetPhysicalRegions(dst, size, dstPageList); + + if (!dstPageList.IsEqual(srcPageList)) { return KernelResult.InvalidMemRange; } @@ -78,7 +81,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory if (result != KernelResult.Success) { - KernelResult mapResult = MapPages(dst, dstRanges, oldDstPermission); + KernelResult mapResult = MapPages(dst, dstPageList, oldDstPermission, false, 0); Debug.Assert(mapResult == KernelResult.Success); } @@ -92,7 +95,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory Context.Memory.Commit(srcPa - DramMemoryMap.DramBase, size); - _cpuMemory.Map(dstVa, Context.Memory.GetPointer(srcPa - DramMemoryMap.DramBase, size), size); + _cpuMemory.Map(dstVa, srcPa - DramMemoryMap.DramBase, size); if (DramMemoryMap.IsHeapPhysicalAddress(srcPa)) { @@ -121,7 +124,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory Context.Memory.Commit(addr, size); - _cpuMemory.Map(currentVa, Context.Memory.GetPointer(addr, size), size); + _cpuMemory.Map(currentVa, addr, size); if (shouldFillPages) { @@ -136,33 +139,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory return KernelResult.Success; } - /// - protected override KernelResult MapPages(ulong address, IEnumerable ranges, KMemoryPermission permission) - { - ulong currentVa = address; - - foreach (var range in ranges) - { - ulong size = range.Size; - - ulong pa = GetDramAddressFromHostAddress(range.Address); - if (pa != ulong.MaxValue) - { - pa += DramMemoryMap.DramBase; - if (DramMemoryMap.IsHeapPhysicalAddress(pa)) - { - Context.MemoryManager.IncrementPagesReferenceCount(pa, size / PageSize); - } - } - - _cpuMemory.Map(currentVa, range.Address, size); - - currentVa += size; - } - - return KernelResult.Success; - } - /// protected override KernelResult Unmap(ulong address, ulong pagesCount) { @@ -172,13 +148,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory foreach (var region in regions) { - ulong pa = GetDramAddressFromHostAddress(region.Address); - if (pa == ulong.MaxValue) - { - continue; - } - - pa += DramMemoryMap.DramBase; + ulong pa = region.Address + DramMemoryMap.DramBase; if (DramMemoryMap.IsHeapPhysicalAddress(pa)) { pagesToClose.AddRange(pa, region.Size / PageSize); @@ -217,15 +187,5 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory { _cpuMemory.Write(va, data); } - - private ulong GetDramAddressFromHostAddress(nuint hostAddress) - { - if (hostAddress < (nuint)(ulong)Context.Memory.Pointer || hostAddress >= (nuint)((ulong)Context.Memory.Pointer + Context.Memory.Size)) - { - return ulong.MaxValue; - } - - return hostAddress - (ulong)Context.Memory.Pointer; - } } } diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs index fb3d669de..518a0f096 100644 --- a/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs @@ -1,11 +1,9 @@ using Ryujinx.Common; using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Process; -using Ryujinx.Memory.Range; using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; namespace Ryujinx.HLE.HOS.Kernel.Memory { @@ -73,8 +71,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory private MersenneTwister _randomNumberGenerator; - public abstract bool SupportsMemoryAliasing { get; } - private MemoryFillValue _heapFillValue; private MemoryFillValue _ipcFillValue; @@ -305,7 +301,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory TlsIoRegionStart = tlsIoRegion.Start; TlsIoRegionEnd = tlsIoRegion.End; - // TODO: Check kernel configuration via secure monitor call when implemented to set memory fill values. + // TODO: Check kernel configuration via secure monitor call when implemented to set memory fill values. _currentHeapAddr = HeapRegionStart; _heapCapacity = 0; @@ -380,8 +376,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory } } - public KernelResult UnmapPages(ulong address, ulong pagesCount, IEnumerable ranges, MemoryState stateExpected) + public KernelResult UnmapPages(ulong address, KPageList pageList, MemoryState stateExpected) { + ulong pagesCount = pageList.GetPagesCount(); ulong size = pagesCount * PageSize; ulong endAddr = address + size; @@ -405,9 +402,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory lock (_blockManager) { - var currentRanges = GetPhysicalRegions(address, size); + KPageList currentPageList = new KPageList(); - if (!currentRanges.SequenceEqual(ranges)) + GetPhysicalRegions(address, size, currentPageList); + + if (!currentPageList.IsEqual(pageList)) { return KernelResult.InvalidMemRange; } @@ -1690,11 +1689,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory bool send, out ulong dst) { - if (!SupportsMemoryAliasing) - { - throw new NotSupportedException("Memory aliasing not supported, can't map IPC buffers."); - } - dst = 0; if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion)) @@ -1828,7 +1822,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory { ulong alignedSize = endAddrTruncated - addressRounded; - KernelResult result = MapPages(currentVa, srcPageTable.GetPhysicalRegions(addressRounded, alignedSize), permission); + KPageList pageList = new KPageList(); + srcPageTable.GetPhysicalRegions(addressRounded, alignedSize, pageList); + + KernelResult result = MapPages(currentVa, pageList, permission); if (result != KernelResult.Success) { @@ -2041,7 +2038,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory MemoryAttribute.Borrowed); } - public KernelResult BorrowTransferMemory(List ranges, ulong address, ulong size, KMemoryPermission permission) + public KernelResult BorrowTransferMemory(KPageList pageList, ulong address, ulong size, KMemoryPermission permission) { return SetAttributesAndChangePermission( address, @@ -2054,7 +2051,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory MemoryAttribute.None, permission, MemoryAttribute.Borrowed, - ranges); + pageList); } private KernelResult SetAttributesAndChangePermission( @@ -2068,7 +2065,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory MemoryAttribute attributeExpected, KMemoryPermission newPermission, MemoryAttribute attributeSetMask, - List ranges = null) + KPageList pageList = null) { if (address + size <= address || !InsideAddrSpace(address, size)) { @@ -2093,7 +2090,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory { ulong pagesCount = size / PageSize; - ranges?.AddRange(GetPhysicalRegions(address, size)); + if (pageList != null) + { + GetPhysicalRegions(address, pagesCount * PageSize, pageList); + } if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion)) { @@ -2143,7 +2143,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory MemoryAttribute.Borrowed); } - public KernelResult UnborrowTransferMemory(ulong address, ulong size, List ranges) + public KernelResult UnborrowTransferMemory(ulong address, ulong size, KPageList pageList) { return ClearAttributesAndChangePermission( address, @@ -2156,7 +2156,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory MemoryAttribute.Borrowed, KMemoryPermission.ReadAndWrite, MemoryAttribute.Borrowed, - ranges); + pageList); } private KernelResult ClearAttributesAndChangePermission( @@ -2170,7 +2170,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory MemoryAttribute attributeExpected, KMemoryPermission newPermission, MemoryAttribute attributeClearMask, - List ranges = null) + KPageList pageList = null) { if (address + size <= address || !InsideAddrSpace(address, size)) { @@ -2195,11 +2195,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory { ulong pagesCount = size / PageSize; - if (ranges != null) + if (pageList != null) { - var currentRanges = GetPhysicalRegions(address, size); + KPageList currentPageList = new KPageList(); - if (!currentRanges.SequenceEqual(ranges)) + GetPhysicalRegions(address, pagesCount * PageSize, currentPageList); + + if (!currentPageList.IsEqual(pageList)) { return KernelResult.InvalidMemRange; } @@ -2741,8 +2743,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory /// /// Virtual address of the range /// Size of the range - /// Array of physical regions - protected abstract IEnumerable GetPhysicalRegions(ulong va, ulong size); + /// Page list where the ranges will be added + protected abstract void GetPhysicalRegions(ulong va, ulong size, KPageList pageList); /// /// Gets a read-only span of data from CPU mapped memory. @@ -2803,16 +2805,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory /// Result of the mapping operation protected abstract KernelResult MapPages(ulong address, KPageList pageList, KMemoryPermission permission, bool shouldFillPages = false, byte fillValue = 0); - /// - /// Maps a region of memory into the specified host memory ranges. - /// - /// Destination virtual address that should be mapped - /// Ranges of host memory that should be mapped - /// Permission of the region to be mapped - /// Result of the mapping operation - /// The implementation does not support memory aliasing - protected abstract KernelResult MapPages(ulong address, IEnumerable ranges, KMemoryPermission permission); - /// /// Unmaps a region of memory that was previously mapped with one of the page mapping methods. /// diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableHostMapped.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableHostMapped.cs deleted file mode 100644 index 29a7b2ed6..000000000 --- a/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableHostMapped.cs +++ /dev/null @@ -1,139 +0,0 @@ -using Ryujinx.HLE.HOS.Kernel.Common; -using Ryujinx.Memory; -using Ryujinx.Memory.Range; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Ryujinx.HLE.HOS.Kernel.Memory -{ - class KPageTableHostMapped : KPageTableBase - { - private const int CopyChunckSize = 0x100000; - - private readonly IVirtualMemoryManager _cpuMemory; - - public override bool SupportsMemoryAliasing => false; - - public KPageTableHostMapped(KernelContext context, IVirtualMemoryManager cpuMemory) : base(context) - { - _cpuMemory = cpuMemory; - } - - /// - protected override IEnumerable GetPhysicalRegions(ulong va, ulong size) - { - return _cpuMemory.GetPhysicalRegions(va, size); - } - - /// - protected override ReadOnlySpan GetSpan(ulong va, int size) - { - return _cpuMemory.GetSpan(va, size); - } - - /// - protected override KernelResult MapMemory(ulong src, ulong dst, ulong pagesCount, KMemoryPermission oldSrcPermission, KMemoryPermission newDstPermission) - { - ulong size = pagesCount * PageSize; - - _cpuMemory.Map(dst, 0, size); - - ulong currentSize = size; - while (currentSize > 0) - { - ulong copySize = Math.Min(currentSize, CopyChunckSize); - _cpuMemory.Write(dst, _cpuMemory.GetSpan(src, (int)copySize)); - currentSize -= copySize; - } - - return KernelResult.Success; - } - - /// - protected override KernelResult UnmapMemory(ulong dst, ulong src, ulong pagesCount, KMemoryPermission oldDstPermission, KMemoryPermission newSrcPermission) - { - ulong size = pagesCount * PageSize; - - // TODO: Validation. - - ulong currentSize = size; - while (currentSize > 0) - { - ulong copySize = Math.Min(currentSize, CopyChunckSize); - _cpuMemory.Write(src, _cpuMemory.GetSpan(dst, (int)copySize)); - currentSize -= copySize; - } - - _cpuMemory.Unmap(dst, size); - return KernelResult.Success; - } - - /// - protected override KernelResult MapPages(ulong dstVa, ulong pagesCount, ulong srcPa, KMemoryPermission permission, bool shouldFillPages, byte fillValue) - { - _cpuMemory.Map(dstVa, 0, pagesCount * PageSize); - - if (shouldFillPages) - { - _cpuMemory.Fill(dstVa, pagesCount * PageSize, fillValue); - } - - return KernelResult.Success; - } - - /// - protected override KernelResult MapPages(ulong address, KPageList pageList, KMemoryPermission permission, bool shouldFillPages, byte fillValue) - { - ulong pagesCount = pageList.GetPagesCount(); - - _cpuMemory.Map(address, 0, pagesCount * PageSize); - - if (shouldFillPages) - { - _cpuMemory.Fill(address, pagesCount * PageSize, fillValue); - } - - return KernelResult.Success; - } - - /// - protected override KernelResult MapPages(ulong address, IEnumerable ranges, KMemoryPermission permission) - { - throw new NotSupportedException(); - } - - /// - protected override KernelResult Unmap(ulong address, ulong pagesCount) - { - _cpuMemory.Unmap(address, pagesCount * PageSize); - return KernelResult.Success; - } - - /// - protected override KernelResult Reprotect(ulong address, ulong pagesCount, KMemoryPermission permission) - { - // TODO. - return KernelResult.Success; - } - - /// - protected override KernelResult ReprotectWithAttributes(ulong address, ulong pagesCount, KMemoryPermission permission) - { - // TODO. - return KernelResult.Success; - } - - /// - protected override void SignalMemoryTracking(ulong va, ulong size, bool write) - { - _cpuMemory.SignalMemoryTracking(va, size, write); - } - - /// - protected override void Write(ulong va, ReadOnlySpan data) - { - _cpuMemory.Write(va, data); - } - } -} diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs index 4e5168427..3711b9dfa 100644 --- a/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs @@ -6,7 +6,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory { class KSharedMemory : KAutoObject { - private readonly SharedMemoryStorage _storage; + private readonly KPageList _pageList; private readonly ulong _ownerPid; @@ -20,7 +20,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory KMemoryPermission ownerPermission, KMemoryPermission userPermission) : base(context) { - _storage = storage; + _pageList = storage.GetPageList(); _ownerPid = ownerPid; _ownerPermission = ownerPermission; _userPermission = userPermission; @@ -33,10 +33,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory KProcess process, KMemoryPermission permission) { - ulong pagesCountRounded = BitUtils.DivRoundUp(size, KPageTableBase.PageSize); - - var pageList = _storage.GetPageList(); - if (pageList.GetPagesCount() != pagesCountRounded) + if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize)) { return KernelResult.InvalidSize; } @@ -50,35 +47,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory return KernelResult.InvalidPermission; } - KernelResult result = memoryManager.MapPages(address, pageList, MemoryState.SharedMemory, permission); - - if (result == KernelResult.Success && !memoryManager.SupportsMemoryAliasing) - { - _storage.Borrow(process, address); - } - - return result; + return memoryManager.MapPages(address, _pageList, MemoryState.SharedMemory, permission); } - public KernelResult UnmapFromProcess( - KPageTableBase memoryManager, - ulong address, - ulong size, - KProcess process) + public KernelResult UnmapFromProcess(KPageTableBase memoryManager, ulong address, ulong size, KProcess process) { - ulong pagesCountRounded = BitUtils.DivRoundUp(size, KPageTableBase.PageSize); - - var pageList = _storage.GetPageList(); - ulong pagesCount = pageList.GetPagesCount(); - - if (pagesCount != pagesCountRounded) + if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize)) { return KernelResult.InvalidSize; } - var ranges = _storage.GetRanges(); - - return memoryManager.UnmapPages(address, pagesCount, ranges, MemoryState.SharedMemory); + return memoryManager.UnmapPages(address, _pageList, MemoryState.SharedMemory); } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs index 3051d998c..107f04c91 100644 --- a/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs @@ -1,9 +1,7 @@ using Ryujinx.Common; using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Process; -using Ryujinx.Memory.Range; using System; -using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Kernel.Memory { @@ -14,9 +12,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory // TODO: Remove when we no longer need to read it from the owner directly. public KProcess Creator => _creator; - private readonly List _ranges; - - private readonly SharedMemoryStorage _storage; + private readonly KPageList _pageList; public ulong Address { get; private set; } public ulong Size { get; private set; } @@ -28,12 +24,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory public KTransferMemory(KernelContext context) : base(context) { - _ranges = new List(); + _pageList = new KPageList(); } public KTransferMemory(KernelContext context, SharedMemoryStorage storage) : base(context) { - _storage = storage; + _pageList = storage.GetPageList(); Permission = KMemoryPermission.ReadAndWrite; _hasBeenInitialized = true; @@ -46,7 +42,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory _creator = creator; - KernelResult result = creator.MemoryManager.BorrowTransferMemory(_ranges, address, size, permission); + KernelResult result = creator.MemoryManager.BorrowTransferMemory(_pageList, address, size, permission); if (result != KernelResult.Success) { @@ -71,15 +67,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory KProcess process, KMemoryPermission permission) { - if (_storage == null) - { - throw new NotImplementedException(); - } - - ulong pagesCountRounded = BitUtils.DivRoundUp(size, KPageTableBase.PageSize); - - var pageList = _storage.GetPageList(); - if (pageList.GetPagesCount() != pagesCountRounded) + if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize)) { return KernelResult.InvalidSize; } @@ -91,16 +79,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory MemoryState state = Permission == KMemoryPermission.None ? MemoryState.TransferMemoryIsolated : MemoryState.TransferMemory; - KernelResult result = memoryManager.MapPages(address, pageList, state, KMemoryPermission.ReadAndWrite); + KernelResult result = memoryManager.MapPages(address, _pageList, state, KMemoryPermission.ReadAndWrite); if (result == KernelResult.Success) { _isMapped = true; - - if (!memoryManager.SupportsMemoryAliasing) - { - _storage.Borrow(process, address); - } } return result; @@ -112,26 +95,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory ulong size, KProcess process) { - if (_storage == null) - { - throw new NotImplementedException(); - } - - ulong pagesCountRounded = BitUtils.DivRoundUp(size, KPageTableBase.PageSize); - - var pageList = _storage.GetPageList(); - ulong pagesCount = pageList.GetPagesCount(); - - if (pagesCount != pagesCountRounded) + if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize)) { return KernelResult.InvalidSize; } - var ranges = _storage.GetRanges(); - MemoryState state = Permission == KMemoryPermission.None ? MemoryState.TransferMemoryIsolated : MemoryState.TransferMemory; - KernelResult result = memoryManager.UnmapPages(address, pagesCount, ranges, state); + KernelResult result = memoryManager.UnmapPages(address, _pageList, state); if (result == KernelResult.Success) { @@ -145,7 +116,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory { if (_hasBeenInitialized) { - if (!_isMapped && _creator.MemoryManager.UnborrowTransferMemory(Address, Size, _ranges) != KernelResult.Success) + if (!_isMapped && _creator.MemoryManager.UnborrowTransferMemory(Address, Size, _pageList) != KernelResult.Success) { throw new InvalidOperationException("Unexpected failure restoring transfer memory attributes."); } diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/SharedMemoryStorage.cs b/Ryujinx.HLE/HOS/Kernel/Memory/SharedMemoryStorage.cs index cd22b65f7..167e0aa90 100644 --- a/Ryujinx.HLE/HOS/Kernel/Memory/SharedMemoryStorage.cs +++ b/Ryujinx.HLE/HOS/Kernel/Memory/SharedMemoryStorage.cs @@ -1,8 +1,4 @@ -using Ryujinx.HLE.HOS.Kernel.Process; -using Ryujinx.Memory; -using Ryujinx.Memory.Range; -using System; -using System.Collections.Generic; +using System; namespace Ryujinx.HLE.HOS.Kernel.Memory { @@ -12,9 +8,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory private readonly KPageList _pageList; private readonly ulong _size; - private IVirtualMemoryManager _borrowerMemory; - private ulong _borrowerVa; - public SharedMemoryStorage(KernelContext context, KPageList pageList) { _context = context; @@ -29,24 +22,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory } } - public void Borrow(KProcess dstProcess, ulong va) - { - ulong currentOffset = 0; - - foreach (KPageNode pageNode in _pageList) - { - ulong address = pageNode.Address - DramMemoryMap.DramBase; - ulong size = pageNode.PagesCount * KPageTableBase.PageSize; - - dstProcess.CpuMemory.Write(va + currentOffset, _context.Memory.GetSpan(address + currentOffset, (int)size)); - - currentOffset += size; - } - - _borrowerMemory = dstProcess.CpuMemory; - _borrowerVa = va; - } - public void ZeroFill() { for (ulong offset = 0; offset < _size; offset += sizeof(ulong)) @@ -57,42 +32,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory public ref T GetRef(ulong offset) where T : unmanaged { - if (_borrowerMemory == null) + if (_pageList.Nodes.Count == 1) { - if (_pageList.Nodes.Count == 1) - { - ulong address = _pageList.Nodes.First.Value.Address - DramMemoryMap.DramBase; - return ref _context.Memory.GetRef(address + offset); - } - - throw new NotImplementedException("Non-contiguous shared memory is not yet supported."); + ulong address = _pageList.Nodes.First.Value.Address - DramMemoryMap.DramBase; + return ref _context.Memory.GetRef(address + offset); } - else - { - return ref _borrowerMemory.GetRef(_borrowerVa + offset); - } - } - public IEnumerable GetRanges() - { - if (_borrowerMemory == null) - { - var ranges = new List(); - - foreach (KPageNode pageNode in _pageList) - { - ulong address = pageNode.Address - DramMemoryMap.DramBase; - ulong size = pageNode.PagesCount * KPageTableBase.PageSize; - - ranges.Add(new HostMemoryRange(_context.Memory.GetPointer(address, size), size)); - } - - return ranges; - } - else - { - return _borrowerMemory.GetPhysicalRegions(_borrowerVa, _size); - } + throw new NotImplementedException("Non-contiguous shared memory is not yet supported."); } public KPageList GetPageList() diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs index 9cb7945e6..93ecf2973 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -1076,14 +1076,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process Context = _contextFactory.Create(KernelContext, Pid, 1UL << addrSpaceBits, InvalidAccessHandler, for64Bit); - if (Context.AddressSpace is MemoryManagerHostMapped) - { - MemoryManager = new KPageTableHostMapped(KernelContext, CpuMemory); - } - else - { - MemoryManager = new KPageTable(KernelContext, CpuMemory); - } + MemoryManager = new KPageTable(KernelContext, CpuMemory); } private bool InvalidAccessHandler(ulong va) diff --git a/Ryujinx.HLE/HOS/Kernel/Process/ProcessContextFactory.cs b/Ryujinx.HLE/HOS/Kernel/Process/ProcessContextFactory.cs index d81f1d0ac..1c5798b40 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/ProcessContextFactory.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/ProcessContextFactory.cs @@ -6,7 +6,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { public IProcessContext Create(KernelContext context, ulong pid, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler, bool for64Bit) { - return new ProcessContext(new AddressSpaceManager(addressSpaceSize)); + return new ProcessContext(new AddressSpaceManager(context.Memory, addressSpaceSize)); } } } diff --git a/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs b/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs index 713ef6cc1..0ce65e3a7 100644 --- a/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs +++ b/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs @@ -5,6 +5,7 @@ using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.Loaders.Executables; +using Ryujinx.Memory; using System; using System.Collections.Generic; using System.IO; @@ -566,6 +567,11 @@ namespace Ryujinx.HLE.HOS.Services.Ro _owner = context.Process.HandleTable.GetKProcess(context.Request.HandleDesc.ToCopy[0]); context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]); + if (_owner?.CpuMemory is IRefCounted rc) + { + rc.IncrementReferenceCount(); + } + return ResultCode.Success; } @@ -579,6 +585,11 @@ namespace Ryujinx.HLE.HOS.Services.Ro } _nroInfos.Clear(); + + if (_owner?.CpuMemory is IRefCounted rc) + { + rc.DecrementReferenceCount(); + } } } } diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs index 366a26f4a..67559f458 100644 --- a/Ryujinx.HLE/Switch.cs +++ b/Ryujinx.HLE/Switch.cs @@ -1,5 +1,6 @@ using Ryujinx.Audio.Backends.CompatLayer; using Ryujinx.Audio.Integration; +using Ryujinx.Common.Configuration; using Ryujinx.Graphics.Gpu; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS; @@ -48,8 +49,12 @@ namespace Ryujinx.HLE FileSystem = Configuration.VirtualFileSystem; UiHandler = Configuration.HostUiHandler; + MemoryAllocationFlags memoryAllocationFlags = configuration.MemoryManagerMode == MemoryManagerMode.SoftwarePageTable + ? MemoryAllocationFlags.Reserve + : MemoryAllocationFlags.Reserve | MemoryAllocationFlags.Mirrorable; + AudioDeviceDriver = new CompatLayerHardwareDeviceDriver(Configuration.AudioDeviceDriver); - Memory = new MemoryBlock(Configuration.MemoryConfiguration.ToDramSize(), MemoryAllocationFlags.Reserve); + Memory = new MemoryBlock(Configuration.MemoryConfiguration.ToDramSize(), memoryAllocationFlags); Gpu = new GpuContext(Configuration.GpuRenderer); System = new Horizon(this); Statistics = new PerformanceStatistics(); diff --git a/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs b/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs index 05e157b66..cad0c2b53 100644 --- a/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs +++ b/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs @@ -14,7 +14,7 @@ namespace Ryujinx.Memory.Tests { } - public void Map(ulong va, nuint hostAddress, ulong size) + public void Map(ulong va, ulong pa, ulong size) { throw new NotImplementedException(); } @@ -59,9 +59,9 @@ namespace Ryujinx.Memory.Tests throw new NotImplementedException(); } - IEnumerable IVirtualMemoryManager.GetPhysicalRegions(ulong va, ulong size) + IEnumerable IVirtualMemoryManager.GetPhysicalRegions(ulong va, ulong size) { - return NoMappings ? new HostMemoryRange[0] : new HostMemoryRange[] { new HostMemoryRange((nuint)va, size) }; + return NoMappings ? new MemoryRange[0] : new MemoryRange[] { new MemoryRange(va, size) }; } public bool IsMapped(ulong va) diff --git a/Ryujinx.Memory.Tests/Tests.cs b/Ryujinx.Memory.Tests/Tests.cs index 0f5fcaf5c..aa20c38a8 100644 --- a/Ryujinx.Memory.Tests/Tests.cs +++ b/Ryujinx.Memory.Tests/Tests.cs @@ -1,5 +1,4 @@ using NUnit.Framework; -using Ryujinx.Memory; using System; using System.Runtime.InteropServices; @@ -38,5 +37,48 @@ namespace Ryujinx.Memory.Tests Assert.AreEqual(Marshal.ReadInt32(_memoryBlock.Pointer, 0x2040), 0xbadc0de); } + + [Test, Explicit] + public void Test_Alias() + { + using MemoryBlock backing = new MemoryBlock(0x10000, MemoryAllocationFlags.Mirrorable); + using MemoryBlock toAlias = new MemoryBlock(0x10000, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible); + + toAlias.MapView(backing, 0x1000, 0, 0x4000); + toAlias.UnmapView(backing, 0x3000, 0x1000); + + toAlias.Write(0, 0xbadc0de); + Assert.AreEqual(Marshal.ReadInt32(backing.Pointer, 0x1000), 0xbadc0de); + } + + [Test, Explicit] + public void Test_AliasRandom() + { + using MemoryBlock backing = new MemoryBlock(0x80000, MemoryAllocationFlags.Mirrorable); + using MemoryBlock toAlias = new MemoryBlock(0x80000, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible); + + Random rng = new Random(123); + + for (int i = 0; i < 20000; i++) + { + int srcPage = rng.Next(0, 64); + int dstPage = rng.Next(0, 64); + int pages = rng.Next(1, 65); + + if ((rng.Next() & 1) != 0) + { + toAlias.MapView(backing, (ulong)srcPage << 12, (ulong)dstPage << 12, (ulong)pages << 12); + + int offset = rng.Next(0, 0x1000 - sizeof(int)); + + toAlias.Write((ulong)((dstPage << 12) + offset), 0xbadc0de); + Assert.AreEqual(Marshal.ReadInt32(backing.Pointer, (srcPage << 12) + offset), 0xbadc0de); + } + else + { + toAlias.UnmapView(backing, (ulong)dstPage << 12, (ulong)pages << 12); + } + } + } } } \ No newline at end of file diff --git a/Ryujinx.Memory/AddressSpaceManager.cs b/Ryujinx.Memory/AddressSpaceManager.cs index 0195644d4..45f3225e1 100644 --- a/Ryujinx.Memory/AddressSpaceManager.cs +++ b/Ryujinx.Memory/AddressSpaceManager.cs @@ -13,9 +13,9 @@ namespace Ryujinx.Memory /// public sealed class AddressSpaceManager : IVirtualMemoryManager, IWritableBlock { - public const int PageBits = PageTable.PageBits; - public const int PageSize = PageTable.PageSize; - public const int PageMask = PageTable.PageMask; + public const int PageBits = PageTable.PageBits; + public const int PageSize = PageTable.PageSize; + public const int PageMask = PageTable.PageMask; /// /// Address space width in bits. @@ -24,14 +24,15 @@ namespace Ryujinx.Memory private readonly ulong _addressSpaceSize; - private readonly PageTable _pageTable; + private readonly MemoryBlock _backingMemory; + private readonly PageTable _pageTable; /// /// Creates a new instance of the memory manager. /// /// Physical backing memory where virtual memory will be mapped to /// Size of the address space - public AddressSpaceManager(ulong addressSpaceSize) + public AddressSpaceManager(MemoryBlock backingMemory, ulong addressSpaceSize) { ulong asSize = PageSize; int asBits = PageBits; @@ -44,37 +45,26 @@ namespace Ryujinx.Memory AddressSpaceBits = asBits; _addressSpaceSize = asSize; - _pageTable = new PageTable(); + _backingMemory = backingMemory; + _pageTable = new PageTable(); } - /// - /// Maps a virtual memory range into a physical memory range. - /// - /// - /// Addresses and size must be page aligned. - /// - /// Virtual memory address - /// Physical memory address - /// Size to be mapped - public void Map(ulong va, nuint hostAddress, ulong size) + /// + public void Map(ulong va, ulong pa, ulong size) { AssertValidAddressAndSize(va, size); while (size != 0) { - _pageTable.Map(va, hostAddress); + _pageTable.Map(va, pa); va += PageSize; - hostAddress += PageSize; + pa += PageSize; size -= PageSize; } } - /// - /// Unmaps a previously mapped range of virtual memory. - /// - /// Virtual address of the range to be unmapped - /// Size of the range to be unmapped + /// public void Unmap(ulong va, ulong size) { AssertValidAddressAndSize(va, size); @@ -88,47 +78,25 @@ namespace Ryujinx.Memory } } - /// - /// Reads data from mapped memory. - /// - /// Type of the data being read - /// Virtual address of the data in memory - /// The data - /// Throw for unhandled invalid or unmapped memory accesses + /// public T Read(ulong va) where T : unmanaged { return MemoryMarshal.Cast(GetSpan(va, Unsafe.SizeOf()))[0]; } - /// - /// Reads data from mapped memory. - /// - /// Virtual address of the data in memory - /// Span to store the data being read into - /// Throw for unhandled invalid or unmapped memory accesses + /// public void Read(ulong va, Span data) { ReadImpl(va, data); } - /// - /// Writes data to mapped memory. - /// - /// Type of the data being written - /// Virtual address to write the data into - /// Data to be written - /// Throw for unhandled invalid or unmapped memory accesses + /// public void Write(ulong va, T value) where T : unmanaged { Write(va, MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1))); } - /// - /// Writes data to mapped memory. - /// - /// Virtual address to write the data into - /// Data to be written - /// Throw for unhandled invalid or unmapped memory accesses + /// public void Write(ulong va, ReadOnlySpan data) { if (data.Length == 0) @@ -140,7 +108,7 @@ namespace Ryujinx.Memory if (IsContiguousAndMapped(va, data.Length)) { - data.CopyTo(GetHostSpanContiguous(va, data.Length)); + data.CopyTo(_backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length)); } else { @@ -148,34 +116,27 @@ namespace Ryujinx.Memory if ((va & PageMask) != 0) { + ulong pa = GetPhysicalAddressInternal(va); + size = Math.Min(data.Length, PageSize - (int)(va & PageMask)); - data.Slice(0, size).CopyTo(GetHostSpanContiguous(va, size)); + data.Slice(0, size).CopyTo(_backingMemory.GetSpan(pa, size)); offset += size; } for (; offset < data.Length; offset += size) { + ulong pa = GetPhysicalAddressInternal(va + (ulong)offset); + size = Math.Min(data.Length - offset, PageSize); - data.Slice(offset, size).CopyTo(GetHostSpanContiguous(va + (ulong)offset, size)); + data.Slice(offset, size).CopyTo(_backingMemory.GetSpan(pa, size)); } } } - /// - /// Gets a read-only span of data from mapped memory. - /// - /// - /// This may perform a allocation if the data is not contiguous in memory. - /// For this reason, the span is read-only, you can't modify the data. - /// - /// Virtual address of the data - /// Size of the data - /// True if read tracking is triggered on the span - /// A read-only span of the data - /// Throw for unhandled invalid or unmapped memory accesses + /// public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) { if (size == 0) @@ -185,7 +146,7 @@ namespace Ryujinx.Memory if (IsContiguousAndMapped(va, size)) { - return GetHostSpanContiguous(va, size); + return _backingMemory.GetSpan(GetPhysicalAddressInternal(va), size); } else { @@ -197,19 +158,7 @@ namespace Ryujinx.Memory } } - /// - /// Gets a region of memory that can be written to. - /// - /// - /// If the requested region is not contiguous in physical memory, - /// this will perform an allocation, and flush the data (writing it - /// back to the backing memory) on disposal. - /// - /// Virtual address of the data - /// Size of the data - /// True if write tracking is triggered on the span - /// A writable region of memory containing the data - /// Throw for unhandled invalid or unmapped memory accesses + /// public unsafe WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) { if (size == 0) @@ -219,7 +168,7 @@ namespace Ryujinx.Memory if (IsContiguousAndMapped(va, size)) { - return new WritableRegion(null, va, new NativeMemoryManager((byte*)GetHostAddress(va), size).Memory); + return new WritableRegion(null, va, _backingMemory.GetMemory(GetPhysicalAddressInternal(va), size)); } else { @@ -231,33 +180,18 @@ namespace Ryujinx.Memory } } - /// - /// Gets a reference for the given type at the specified virtual memory address. - /// - /// - /// The data must be located at a contiguous memory region. - /// - /// Type of the data to get the reference - /// Virtual address of the data - /// A reference to the data in memory - /// Throw if the specified memory region is not contiguous in physical memory - public unsafe ref T GetRef(ulong va) where T : unmanaged + /// + public ref T GetRef(ulong va) where T : unmanaged { if (!IsContiguous(va, Unsafe.SizeOf())) { ThrowMemoryNotContiguous(); } - return ref *(T*)GetHostAddress(va); + return ref _backingMemory.GetRef(GetPhysicalAddressInternal(va)); } - /// - /// 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, uint size, out ulong startVa) { @@ -268,7 +202,7 @@ namespace Ryujinx.Memory return (int)(vaSpan / PageSize); } - private void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException(); + private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException(); [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool IsContiguousAndMapped(ulong va, int size) => IsContiguous(va, size) && IsMapped(va); @@ -290,7 +224,7 @@ namespace Ryujinx.Memory return false; } - if (GetHostAddress(va) + PageSize != GetHostAddress(va + PageSize)) + if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize)) { return false; } @@ -301,18 +235,12 @@ namespace Ryujinx.Memory return true; } - /// - /// Gets the physical regions that make up the given virtual address region. - /// If any part of the virtual region is unmapped, null is returned. - /// - /// Virtual address of the range - /// Size of the range - /// Array of physical regions - public IEnumerable GetPhysicalRegions(ulong va, ulong size) + /// + public IEnumerable GetPhysicalRegions(ulong va, ulong size) { if (size == 0) { - return Enumerable.Empty(); + return Enumerable.Empty(); } if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size)) @@ -322,9 +250,9 @@ namespace Ryujinx.Memory int pages = GetPagesCount(va, (uint)size, out va); - var regions = new List(); + var regions = new List(); - nuint regionStart = GetHostAddress(va); + ulong regionStart = GetPhysicalAddressInternal(va); ulong regionSize = PageSize; for (int page = 0; page < pages - 1; page++) @@ -334,12 +262,12 @@ namespace Ryujinx.Memory return null; } - nuint newHostAddress = GetHostAddress(va + PageSize); + ulong newPa = GetPhysicalAddressInternal(va + PageSize); - if (GetHostAddress(va) + PageSize != newHostAddress) + if (GetPhysicalAddressInternal(va) + PageSize != newPa) { - regions.Add(new HostMemoryRange(regionStart, regionSize)); - regionStart = newHostAddress; + regions.Add(new MemoryRange(regionStart, regionSize)); + regionStart = newPa; regionSize = 0; } @@ -347,7 +275,7 @@ namespace Ryujinx.Memory regionSize += PageSize; } - regions.Add(new HostMemoryRange(regionStart, regionSize)); + regions.Add(new MemoryRange(regionStart, regionSize)); return regions; } @@ -365,26 +293,26 @@ namespace Ryujinx.Memory if ((va & PageMask) != 0) { + ulong pa = GetPhysicalAddressInternal(va); + size = Math.Min(data.Length, PageSize - (int)(va & PageMask)); - GetHostSpanContiguous(va, size).CopyTo(data.Slice(0, size)); + _backingMemory.GetSpan(pa, size).CopyTo(data.Slice(0, size)); offset += size; } for (; offset < data.Length; offset += size) { + ulong pa = GetPhysicalAddressInternal(va + (ulong)offset); + size = Math.Min(data.Length - offset, PageSize); - GetHostSpanContiguous(va + (ulong)offset, size).CopyTo(data.Slice(offset, size)); + _backingMemory.GetSpan(pa, size).CopyTo(data.Slice(offset, size)); } } - /// - /// Checks if the page at a given virtual address is mapped. - /// - /// Virtual address to check - /// True if the address is mapped, false otherwise + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsMapped(ulong va) { @@ -396,12 +324,7 @@ namespace Ryujinx.Memory return _pageTable.Read(va) != 0; } - /// - /// 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 bool IsRangeMapped(ulong va, ulong size) { if (size == 0UL) @@ -460,14 +383,9 @@ namespace Ryujinx.Memory } } - private unsafe Span GetHostSpanContiguous(ulong va, int size) + private ulong GetPhysicalAddressInternal(ulong va) { - return new Span((void*)GetHostAddress(va), size); - } - - private nuint GetHostAddress(ulong va) - { - return _pageTable.Read(va) + (nuint)(va & PageMask); + return _pageTable.Read(va) + (va & PageMask); } /// diff --git a/Ryujinx.Memory/IVirtualMemoryManager.cs b/Ryujinx.Memory/IVirtualMemoryManager.cs index 8bccfbad4..f97cb0b57 100644 --- a/Ryujinx.Memory/IVirtualMemoryManager.cs +++ b/Ryujinx.Memory/IVirtualMemoryManager.cs @@ -13,9 +13,9 @@ namespace Ryujinx.Memory /// Addresses and size must be page aligned. /// /// Virtual memory address - /// Pointer where the region should be mapped to + /// Physical memory address where the region should be mapped to /// Size to be mapped - void Map(ulong va, nuint hostAddress, ulong size); + void Map(ulong va, ulong pa, ulong size); /// /// Unmaps a previously mapped range of virtual memory. @@ -111,7 +111,7 @@ namespace Ryujinx.Memory /// Virtual address of the range /// Size of the range /// Array of physical regions - IEnumerable GetPhysicalRegions(ulong va, ulong size); + IEnumerable GetPhysicalRegions(ulong va, ulong size); /// /// Checks if the page at a given CPU virtual address is mapped. diff --git a/Ryujinx.Memory/MemoryAllocationFlags.cs b/Ryujinx.Memory/MemoryAllocationFlags.cs index d9420dd37..8706a25b8 100644 --- a/Ryujinx.Memory/MemoryAllocationFlags.cs +++ b/Ryujinx.Memory/MemoryAllocationFlags.cs @@ -29,6 +29,18 @@ namespace Ryujinx.Memory /// Enables mirroring of the memory block through aliasing of memory pages. /// When enabled, this allows creating more memory blocks sharing the same backing storage. /// - Mirrorable = 1 << 2 + Mirrorable = 1 << 2, + + /// + /// Indicates that the memory block should support mapping views of a mirrorable memory block. + /// The block that is to have their views mapped should be created with the flag. + /// + ViewCompatible = 1 << 3, + + /// + /// Forces views to be mapped page by page on Windows. When partial unmaps are done, this avoids the need + /// to unmap the full range and remap sub-ranges, which creates a time window with incorrectly unmapped memory. + /// + ForceWindows4KBViewMapping = 1 << 4 } } diff --git a/Ryujinx.Memory/MemoryBlock.cs b/Ryujinx.Memory/MemoryBlock.cs index 3561b81a6..82a7d882e 100644 --- a/Ryujinx.Memory/MemoryBlock.cs +++ b/Ryujinx.Memory/MemoryBlock.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Runtime.CompilerServices; using System.Threading; @@ -11,8 +12,12 @@ namespace Ryujinx.Memory { private readonly bool _usesSharedMemory; private readonly bool _isMirror; + private readonly bool _viewCompatible; + private readonly bool _forceWindows4KBView; private IntPtr _sharedMemory; private IntPtr _pointer; + private ConcurrentDictionary _viewStorages; + private int _viewCount; /// /// Pointer to the memory block data. @@ -36,12 +41,14 @@ namespace Ryujinx.Memory if (flags.HasFlag(MemoryAllocationFlags.Mirrorable)) { _sharedMemory = MemoryManagement.CreateSharedMemory(size, flags.HasFlag(MemoryAllocationFlags.Reserve)); - _pointer = MemoryManagement.MapSharedMemory(_sharedMemory); + _pointer = MemoryManagement.MapSharedMemory(_sharedMemory, size); _usesSharedMemory = true; } else if (flags.HasFlag(MemoryAllocationFlags.Reserve)) { - _pointer = MemoryManagement.Reserve(size); + _viewCompatible = flags.HasFlag(MemoryAllocationFlags.ViewCompatible); + _forceWindows4KBView = flags.HasFlag(MemoryAllocationFlags.ForceWindows4KBViewMapping); + _pointer = MemoryManagement.Reserve(size, _viewCompatible); } else { @@ -49,6 +56,10 @@ namespace Ryujinx.Memory } Size = size; + + _viewStorages = new ConcurrentDictionary(); + _viewStorages.TryAdd(this, 0); + _viewCount = 1; } /// @@ -60,7 +71,7 @@ namespace Ryujinx.Memory /// Throw when the current platform is not supported private MemoryBlock(ulong size, IntPtr sharedMemory) { - _pointer = MemoryManagement.MapSharedMemory(sharedMemory); + _pointer = MemoryManagement.MapSharedMemory(sharedMemory, size); Size = size; _usesSharedMemory = true; _isMirror = true; @@ -112,6 +123,42 @@ namespace Ryujinx.Memory return MemoryManagement.Decommit(GetPointerInternal(offset, size), size); } + /// + /// Maps a view of memory from another memory block. + /// + /// Memory block from where the backing memory will be taken + /// Offset on of the region that should be mapped + /// Offset to map the view into on this block + /// Size of the range to be mapped + /// Throw when the source memory block does not support mirroring + /// Throw when the memory block has already been disposed + /// Throw when either or are out of range + public void MapView(MemoryBlock srcBlock, ulong srcOffset, ulong dstOffset, ulong size) + { + if (srcBlock._sharedMemory == IntPtr.Zero) + { + throw new ArgumentException("The source memory block is not mirrorable, and thus cannot be mapped on the current block."); + } + + if (_viewStorages.TryAdd(srcBlock, 0)) + { + srcBlock.IncrementViewCount(); + } + + MemoryManagement.MapView(srcBlock._sharedMemory, srcOffset, GetPointerInternal(dstOffset, size), size, _forceWindows4KBView); + } + + /// + /// Unmaps a view of memory from another memory block. + /// + /// Memory block from where the backing memory was taken during map + /// Offset of the view previously mapped with + /// Size of the range to be unmapped + public void UnmapView(MemoryBlock srcBlock, ulong offset, ulong size) + { + MemoryManagement.UnmapView(srcBlock._sharedMemory, GetPointerInternal(offset, size), size, _forceWindows4KBView); + } + /// /// Reprotects a region of memory. /// @@ -124,21 +171,7 @@ namespace Ryujinx.Memory /// Throw when is invalid public void Reprotect(ulong offset, ulong size, MemoryPermission permission, bool throwOnFail = true) { - MemoryManagement.Reprotect(GetPointerInternal(offset, size), size, permission, throwOnFail); - } - - /// - /// Remaps a region of memory into this memory block. - /// - /// Starting offset of the range to be remapped into - /// Starting offset of the range to be remapped from - /// Size of the range to be remapped - /// Throw when the memory block has already been disposed - /// Throw when either or are out of range - /// Throw when is invalid - public void Remap(ulong offset, IntPtr sourceAddress, ulong size) - { - MemoryManagement.Remap(GetPointerInternal(offset, size), sourceAddress, size); + MemoryManagement.Reprotect(GetPointerInternal(offset, size), size, permission, _viewCompatible, _forceWindows4KBView, throwOnFail); } /// @@ -274,7 +307,7 @@ namespace Ryujinx.Memory /// Throw when the memory block has already been disposed /// Throw when either or are out of range [MethodImpl(MethodImplOptions.AggressiveInlining)] - public nuint GetPointer(ulong offset, ulong size) => (nuint)(ulong)GetPointerInternal(offset, size); + public IntPtr GetPointer(ulong offset, ulong size) => GetPointerInternal(offset, size); [MethodImpl(MethodImplOptions.AggressiveInlining)] private IntPtr GetPointerInternal(ulong offset, ulong size) @@ -367,22 +400,63 @@ namespace Ryujinx.Memory { if (_usesSharedMemory) { - MemoryManagement.UnmapSharedMemory(ptr); - - if (_sharedMemory != IntPtr.Zero && !_isMirror) - { - MemoryManagement.DestroySharedMemory(_sharedMemory); - _sharedMemory = IntPtr.Zero; - } + MemoryManagement.UnmapSharedMemory(ptr, Size); } else { MemoryManagement.Free(ptr); } + + foreach (MemoryBlock viewStorage in _viewStorages.Keys) + { + viewStorage.DecrementViewCount(); + } + + _viewStorages.Clear(); } } - private void ThrowObjectDisposed() => throw new ObjectDisposedException(nameof(MemoryBlock)); - private void ThrowInvalidMemoryRegionException() => throw new InvalidMemoryRegionException(); + /// + /// Increments the number of views that uses this memory block as storage. + /// + private void IncrementViewCount() + { + Interlocked.Increment(ref _viewCount); + } + + /// + /// Decrements the number of views that uses this memory block as storage. + /// + private void DecrementViewCount() + { + if (Interlocked.Decrement(ref _viewCount) == 0 && _sharedMemory != IntPtr.Zero && !_isMirror) + { + MemoryManagement.DestroySharedMemory(_sharedMemory); + _sharedMemory = IntPtr.Zero; + } + } + + /// + /// Checks if the specified memory allocation flags are supported on the current platform. + /// + /// Flags to be checked + /// True if the platform supports all the flags, false otherwise + public static bool SupportsFlags(MemoryAllocationFlags flags) + { + if (flags.HasFlag(MemoryAllocationFlags.ViewCompatible)) + { + if (OperatingSystem.IsWindows()) + { + return OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134); + } + + return OperatingSystem.IsLinux() || OperatingSystem.IsMacOS(); + } + + return true; + } + + private static void ThrowObjectDisposed() => throw new ObjectDisposedException(nameof(MemoryBlock)); + private static void ThrowInvalidMemoryRegionException() => throw new InvalidMemoryRegionException(); } } diff --git a/Ryujinx.Memory/MemoryManagement.cs b/Ryujinx.Memory/MemoryManagement.cs index 680969b6d..81262152b 100644 --- a/Ryujinx.Memory/MemoryManagement.cs +++ b/Ryujinx.Memory/MemoryManagement.cs @@ -12,8 +12,7 @@ namespace Ryujinx.Memory return MemoryManagementWindows.Allocate(sizeNint); } - else if (OperatingSystem.IsLinux() || - OperatingSystem.IsMacOS()) + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) { return MemoryManagementUnix.Allocate(size); } @@ -23,16 +22,15 @@ namespace Ryujinx.Memory } } - public static IntPtr Reserve(ulong size) + public static IntPtr Reserve(ulong size, bool viewCompatible) { if (OperatingSystem.IsWindows()) { IntPtr sizeNint = new IntPtr((long)size); - return MemoryManagementWindows.Reserve(sizeNint); + return MemoryManagementWindows.Reserve(sizeNint, viewCompatible); } - else if (OperatingSystem.IsLinux() || - OperatingSystem.IsMacOS()) + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) { return MemoryManagementUnix.Reserve(size); } @@ -50,8 +48,7 @@ namespace Ryujinx.Memory return MemoryManagementWindows.Commit(address, sizeNint); } - else if (OperatingSystem.IsLinux() || - OperatingSystem.IsMacOS()) + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) { return MemoryManagementUnix.Commit(address, size); } @@ -69,8 +66,7 @@ namespace Ryujinx.Memory return MemoryManagementWindows.Decommit(address, sizeNint); } - else if (OperatingSystem.IsLinux() || - OperatingSystem.IsMacOS()) + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) { return MemoryManagementUnix.Decommit(address, size); } @@ -80,7 +76,57 @@ namespace Ryujinx.Memory } } - public static void Reprotect(IntPtr address, ulong size, MemoryPermission permission, bool throwOnFail) + public static void MapView(IntPtr sharedMemory, ulong srcOffset, IntPtr address, ulong size, bool force4KBMap) + { + if (OperatingSystem.IsWindows()) + { + IntPtr sizeNint = new IntPtr((long)size); + + if (force4KBMap) + { + MemoryManagementWindows.MapView4KB(sharedMemory, srcOffset, address, sizeNint); + } + else + { + MemoryManagementWindows.MapView(sharedMemory, srcOffset, address, sizeNint); + } + } + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + MemoryManagementUnix.MapView(sharedMemory, srcOffset, address, size); + } + else + { + throw new PlatformNotSupportedException(); + } + } + + public static void UnmapView(IntPtr sharedMemory, IntPtr address, ulong size, bool force4KBMap) + { + if (OperatingSystem.IsWindows()) + { + IntPtr sizeNint = new IntPtr((long)size); + + if (force4KBMap) + { + MemoryManagementWindows.UnmapView4KB(address, sizeNint); + } + else + { + MemoryManagementWindows.UnmapView(sharedMemory, address, sizeNint); + } + } + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + MemoryManagementUnix.UnmapView(address, size); + } + else + { + throw new PlatformNotSupportedException(); + } + } + + public static void Reprotect(IntPtr address, ulong size, MemoryPermission permission, bool forView, bool force4KBMap, bool throwOnFail) { bool result; @@ -88,10 +134,16 @@ namespace Ryujinx.Memory { IntPtr sizeNint = new IntPtr((long)size); - result = MemoryManagementWindows.Reprotect(address, sizeNint, permission); + if (forView && force4KBMap) + { + result = MemoryManagementWindows.Reprotect4KB(address, sizeNint, permission, forView); + } + else + { + result = MemoryManagementWindows.Reprotect(address, sizeNint, permission, forView); + } } - else if (OperatingSystem.IsLinux() || - OperatingSystem.IsMacOS()) + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) { result = MemoryManagementUnix.Reprotect(address, size, permission); } @@ -112,8 +164,7 @@ namespace Ryujinx.Memory { return MemoryManagementWindows.Free(address); } - else if (OperatingSystem.IsLinux() || - OperatingSystem.IsMacOS()) + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) { return MemoryManagementUnix.Free(address); } @@ -131,8 +182,7 @@ namespace Ryujinx.Memory return MemoryManagementWindows.CreateSharedMemory(sizeNint, reserve); } - else if (OperatingSystem.IsLinux() || - OperatingSystem.IsMacOS()) + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) { return MemoryManagementUnix.CreateSharedMemory(size, reserve); } @@ -148,8 +198,7 @@ namespace Ryujinx.Memory { MemoryManagementWindows.DestroySharedMemory(handle); } - else if (OperatingSystem.IsLinux() || - OperatingSystem.IsMacOS()) + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) { MemoryManagementUnix.DestroySharedMemory(handle); } @@ -159,16 +208,15 @@ namespace Ryujinx.Memory } } - public static IntPtr MapSharedMemory(IntPtr handle) + public static IntPtr MapSharedMemory(IntPtr handle, ulong size) { if (OperatingSystem.IsWindows()) { return MemoryManagementWindows.MapSharedMemory(handle); } - else if (OperatingSystem.IsLinux() || - OperatingSystem.IsMacOS()) + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) { - return MemoryManagementUnix.MapSharedMemory(handle); + return MemoryManagementUnix.MapSharedMemory(handle, size); } else { @@ -176,29 +224,15 @@ namespace Ryujinx.Memory } } - public static void UnmapSharedMemory(IntPtr address) + public static void UnmapSharedMemory(IntPtr address, ulong size) { if (OperatingSystem.IsWindows()) { MemoryManagementWindows.UnmapSharedMemory(address); } - else if (OperatingSystem.IsLinux() || - OperatingSystem.IsMacOS()) + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) { - MemoryManagementUnix.UnmapSharedMemory(address); - } - else - { - throw new PlatformNotSupportedException(); - } - } - - public static IntPtr Remap(IntPtr target, IntPtr source, ulong size) - { - if (OperatingSystem.IsLinux() || - OperatingSystem.IsMacOS()) - { - return MemoryManagementUnix.Remap(target, source, size); + MemoryManagementUnix.UnmapSharedMemory(address, size); } else { diff --git a/Ryujinx.Memory/MemoryManagementUnix.cs b/Ryujinx.Memory/MemoryManagementUnix.cs index 9e3ec48af..db7539f03 100644 --- a/Ryujinx.Memory/MemoryManagementUnix.cs +++ b/Ryujinx.Memory/MemoryManagementUnix.cs @@ -1,8 +1,7 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Runtime.InteropServices; using System.Runtime.Versioning; +using System.Text; using static Ryujinx.Memory.MemoryManagerUnixHelper; @@ -12,15 +11,6 @@ namespace Ryujinx.Memory [SupportedOSPlatform("macos")] static class MemoryManagementUnix { - private struct UnixSharedMemory - { - public IntPtr Pointer; - public ulong Size; - public IntPtr SourcePointer; - } - - private static readonly List _sharedMemory = new List(); - private static readonly ConcurrentDictionary _sharedMemorySource = new ConcurrentDictionary(); private static readonly ConcurrentDictionary _allocations = new ConcurrentDictionary(); public static IntPtr Allocate(ulong size) @@ -69,40 +59,15 @@ namespace Ryujinx.Memory public static bool Commit(IntPtr address, ulong size) { - bool success = mprotect(address, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE) == 0; - - if (success) - { - foreach (var shared in _sharedMemory) - { - if ((ulong)address + size > (ulong)shared.SourcePointer && (ulong)address < (ulong)shared.SourcePointer + shared.Size) - { - ulong sharedAddress = ((ulong)address - (ulong)shared.SourcePointer) + (ulong)shared.Pointer; - - if (mprotect((IntPtr)sharedAddress, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE) != 0) - { - return false; - } - } - } - } - - return success; + return mprotect(address, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE) == 0; } public static bool Decommit(IntPtr address, ulong size) { - bool isShared; - - lock (_sharedMemory) - { - isShared = _sharedMemory.Exists(x => (ulong)address >= (ulong)x.Pointer && (ulong)address + size <= (ulong)x.Pointer + x.Size); - } - // Must be writable for madvise to work properly. mprotect(address, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE); - madvise(address, size, isShared ? MADV_REMOVE : MADV_DONTNEED); + madvise(address, size, MADV_REMOVE); return mprotect(address, size, MmapProts.PROT_NONE) == 0; } @@ -136,139 +101,83 @@ namespace Ryujinx.Memory return false; } - public static IntPtr Remap(IntPtr target, IntPtr source, ulong size) + public static bool Unmap(IntPtr address, ulong size) { - int flags = 1; - - if (target != IntPtr.Zero) - { - flags |= 2; - } - - IntPtr result = mremap(source, 0, size, flags, target); - - if (result == IntPtr.Zero) - { - throw new InvalidOperationException(); - } - - return result; + return munmap(address, size) == 0; } - public static IntPtr CreateSharedMemory(ulong size, bool reserve) + public unsafe static IntPtr CreateSharedMemory(ulong size, bool reserve) { - IntPtr result = AllocateInternal( - size, - reserve ? MmapProts.PROT_NONE : MmapProts.PROT_READ | MmapProts.PROT_WRITE, - true); + int fd; - if (result == IntPtr.Zero) + if (OperatingSystem.IsMacOS()) + { + byte[] memName = Encoding.ASCII.GetBytes("Ryujinx-XXXXXX"); + + fixed (byte* pMemName = memName) + { + fd = shm_open((IntPtr)pMemName, 0x2 | 0x200 | 0x800 | 0x400, 384); // O_RDWR | O_CREAT | O_EXCL | O_TRUNC, 0600 + if (fd == -1) + { + throw new OutOfMemoryException(); + } + + if (shm_unlink((IntPtr)pMemName) != 0) + { + throw new OutOfMemoryException(); + } + } + } + else + { + byte[] fileName = Encoding.ASCII.GetBytes("/dev/shm/Ryujinx-XXXXXX"); + + fixed (byte* pFileName = fileName) + { + fd = mkstemp((IntPtr)pFileName); + if (fd == -1) + { + throw new OutOfMemoryException(); + } + + if (unlink((IntPtr)pFileName) != 0) + { + throw new OutOfMemoryException(); + } + } + } + + if (ftruncate(fd, (IntPtr)size) != 0) { throw new OutOfMemoryException(); } - _sharedMemorySource[result] = (ulong)size; - - return result; + return (IntPtr)fd; } public static void DestroySharedMemory(IntPtr handle) { - lock (_sharedMemory) - { - foreach (var memory in _sharedMemory) - { - if (memory.SourcePointer == handle) - { - throw new InvalidOperationException("Shared memory cannot be destroyed unless fully unmapped."); - } - } - } - - _sharedMemorySource.Remove(handle, out ulong _); + close((int)handle); } - public static IntPtr MapSharedMemory(IntPtr handle) + public static IntPtr MapSharedMemory(IntPtr handle, ulong size) { - // Try find the handle for this shared memory. If it is mapped, then we want to map - // it a second time in another location. - // If it is not mapped, then its handle is the mapping. - - ulong size = _sharedMemorySource[handle]; - - if (size == 0) - { - throw new InvalidOperationException("Shared memory cannot be mapped after its source is unmapped."); - } - - lock (_sharedMemory) - { - foreach (var memory in _sharedMemory) - { - if (memory.Pointer == handle) - { - IntPtr result = AllocateInternal( - memory.Size, - MmapProts.PROT_NONE - ); - - if (result == IntPtr.Zero) - { - throw new OutOfMemoryException(); - } - - Remap(result, handle, memory.Size); - - _sharedMemory.Add(new UnixSharedMemory - { - Pointer = result, - Size = memory.Size, - - SourcePointer = handle - }); - - return result; - } - } - - _sharedMemory.Add(new UnixSharedMemory - { - Pointer = handle, - Size = size, - - SourcePointer = handle - }); - } - - return handle; + return mmap(IntPtr.Zero, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE, MmapFlags.MAP_SHARED, (int)handle, 0); } - public static void UnmapSharedMemory(IntPtr address) + public static void UnmapSharedMemory(IntPtr address, ulong size) { - lock (_sharedMemory) - { - int removed = _sharedMemory.RemoveAll(memory => - { - if (memory.Pointer == address) - { - if (memory.Pointer == memory.SourcePointer) - { - // After removing the original mapping, it cannot be mapped again. - _sharedMemorySource[memory.SourcePointer] = 0; - } + munmap(address, size); + } - Free(address); - return true; - } + public static void MapView(IntPtr sharedMemory, ulong srcOffset, IntPtr location, ulong size) + { + mmap(location, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE, MmapFlags.MAP_FIXED | MmapFlags.MAP_SHARED, (int)sharedMemory, (long)srcOffset); + } - return false; - }); - - if (removed == 0) - { - throw new InvalidOperationException("Shared memory mapping could not be found."); - } - } + public static void UnmapView(IntPtr location, ulong size) + { + mmap(location, size, MmapProts.PROT_NONE, MmapFlags.MAP_FIXED, -1, 0); } } } \ No newline at end of file diff --git a/Ryujinx.Memory/MemoryManagementWindows.cs b/Ryujinx.Memory/MemoryManagementWindows.cs index 48616ec30..1885376ca 100644 --- a/Ryujinx.Memory/MemoryManagementWindows.cs +++ b/Ryujinx.Memory/MemoryManagementWindows.cs @@ -1,7 +1,5 @@ using Ryujinx.Memory.WindowsShared; using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; using System.Runtime.Versioning; namespace Ryujinx.Memory @@ -9,74 +7,42 @@ namespace Ryujinx.Memory [SupportedOSPlatform("windows")] static class MemoryManagementWindows { - private static readonly IntPtr InvalidHandleValue = new IntPtr(-1); - private static bool UseWin10Placeholders; + private const int PageSize = 0x1000; - private static object _emulatedHandleLock = new object(); - private static EmulatedSharedMemoryWindows[] _emulatedShared = new EmulatedSharedMemoryWindows[64]; - private static List _emulatedSharedList = new List(); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern IntPtr VirtualAlloc( - IntPtr lpAddress, - IntPtr dwSize, - AllocationType flAllocationType, - MemoryProtection flProtect); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool VirtualProtect( - IntPtr lpAddress, - IntPtr dwSize, - MemoryProtection flNewProtect, - out MemoryProtection lpflOldProtect); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool VirtualFree(IntPtr lpAddress, IntPtr dwSize, AllocationType dwFreeType); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern IntPtr CreateFileMapping( - IntPtr hFile, - IntPtr lpFileMappingAttributes, - FileMapProtection flProtect, - uint dwMaximumSizeHigh, - uint dwMaximumSizeLow, - [MarshalAs(UnmanagedType.LPWStr)] string lpName); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool CloseHandle(IntPtr hObject); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern IntPtr MapViewOfFile( - IntPtr hFileMappingObject, - uint dwDesiredAccess, - uint dwFileOffsetHigh, - uint dwFileOffsetLow, - IntPtr dwNumberOfBytesToMap); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool UnmapViewOfFile(IntPtr lpBaseAddress); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern uint GetLastError(); - - static MemoryManagementWindows() - { - UseWin10Placeholders = OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134); - } + private static readonly PlaceholderManager _placeholders = new PlaceholderManager(); public static IntPtr Allocate(IntPtr size) { return AllocateInternal(size, AllocationType.Reserve | AllocationType.Commit); } - public static IntPtr Reserve(IntPtr size) + public static IntPtr Reserve(IntPtr size, bool viewCompatible) { + if (viewCompatible) + { + IntPtr baseAddress = AllocateInternal2(size, AllocationType.Reserve | AllocationType.ReservePlaceholder); + _placeholders.ReserveRange((ulong)baseAddress, (ulong)size); + return baseAddress; + } + return AllocateInternal(size, AllocationType.Reserve); } private static IntPtr AllocateInternal(IntPtr size, AllocationType flags = 0) { - IntPtr ptr = VirtualAlloc(IntPtr.Zero, size, flags, MemoryProtection.ReadWrite); + IntPtr ptr = WindowsApi.VirtualAlloc(IntPtr.Zero, size, flags, MemoryProtection.ReadWrite); + + if (ptr == IntPtr.Zero) + { + throw new OutOfMemoryException(); + } + + return ptr; + } + + private static IntPtr AllocateInternal2(IntPtr size, AllocationType flags = 0) + { + IntPtr ptr = WindowsApi.VirtualAlloc2(WindowsApi.CurrentProcessHandle, IntPtr.Zero, size, flags, MemoryProtection.NoAccess, IntPtr.Zero, 0); if (ptr == IntPtr.Zero) { @@ -88,164 +54,131 @@ namespace Ryujinx.Memory public static bool Commit(IntPtr location, IntPtr size) { - if (UseWin10Placeholders) - { - lock (_emulatedSharedList) - { - foreach (var shared in _emulatedSharedList) - { - if (shared.CommitMap(location, size)) - { - return true; - } - } - } - } - - return VirtualAlloc(location, size, AllocationType.Commit, MemoryProtection.ReadWrite) != IntPtr.Zero; + return WindowsApi.VirtualAlloc(location, size, AllocationType.Commit, MemoryProtection.ReadWrite) != IntPtr.Zero; } public static bool Decommit(IntPtr location, IntPtr size) { - if (UseWin10Placeholders) - { - lock (_emulatedSharedList) - { - foreach (var shared in _emulatedSharedList) - { - if (shared.DecommitMap(location, size)) - { - return true; - } - } - } - } - - return VirtualFree(location, size, AllocationType.Decommit); + return WindowsApi.VirtualFree(location, size, AllocationType.Decommit); } - public static bool Reprotect(IntPtr address, IntPtr size, MemoryPermission permission) + public static void MapView(IntPtr sharedMemory, ulong srcOffset, IntPtr location, IntPtr size) { - if (UseWin10Placeholders) + _placeholders.MapView(sharedMemory, srcOffset, location, size); + } + + public static void MapView4KB(IntPtr sharedMemory, ulong srcOffset, IntPtr location, IntPtr size) + { + ulong uaddress = (ulong)location; + ulong usize = (ulong)size; + IntPtr endLocation = (IntPtr)(uaddress + usize); + + while (location != endLocation) { - ulong uaddress = (ulong)address; - ulong usize = (ulong)size; - while (usize > 0) + WindowsApi.VirtualFree(location, (IntPtr)PageSize, AllocationType.Release | AllocationType.PreservePlaceholder); + + var ptr = WindowsApi.MapViewOfFile3( + sharedMemory, + WindowsApi.CurrentProcessHandle, + location, + srcOffset, + (IntPtr)PageSize, + 0x4000, + MemoryProtection.ReadWrite, + IntPtr.Zero, + 0); + + if (ptr == IntPtr.Zero) { - ulong nextGranular = (uaddress & ~EmulatedSharedMemoryWindows.MappingMask) + EmulatedSharedMemoryWindows.MappingGranularity; - ulong mapSize = Math.Min(usize, nextGranular - uaddress); - - if (!VirtualProtect((IntPtr)uaddress, (IntPtr)mapSize, GetProtection(permission), out _)) - { - return false; - } - - uaddress = nextGranular; - usize -= mapSize; + throw new WindowsApiException("MapViewOfFile3"); } - return true; + location += PageSize; + srcOffset += PageSize; + } + } + + public static void UnmapView(IntPtr sharedMemory, IntPtr location, IntPtr size) + { + _placeholders.UnmapView(sharedMemory, location, size); + } + + public static void UnmapView4KB(IntPtr location, IntPtr size) + { + ulong uaddress = (ulong)location; + ulong usize = (ulong)size; + IntPtr endLocation = (IntPtr)(uaddress + usize); + + while (location != endLocation) + { + bool result = WindowsApi.UnmapViewOfFile2(WindowsApi.CurrentProcessHandle, location, 2); + if (!result) + { + throw new WindowsApiException("UnmapViewOfFile2"); + } + + location += PageSize; + } + } + + public static bool Reprotect(IntPtr address, IntPtr size, MemoryPermission permission, bool forView) + { + if (forView) + { + return _placeholders.ReprotectView(address, size, permission); } else { - return VirtualProtect(address, size, GetProtection(permission), out _); + return WindowsApi.VirtualProtect(address, size, WindowsApi.GetProtection(permission), out _); } } - private static MemoryProtection GetProtection(MemoryPermission permission) + public static bool Reprotect4KB(IntPtr address, IntPtr size, MemoryPermission permission, bool forView) { - return permission switch + ulong uaddress = (ulong)address; + ulong usize = (ulong)size; + while (usize > 0) { - MemoryPermission.None => MemoryProtection.NoAccess, - MemoryPermission.Read => MemoryProtection.ReadOnly, - MemoryPermission.ReadAndWrite => MemoryProtection.ReadWrite, - MemoryPermission.ReadAndExecute => MemoryProtection.ExecuteRead, - MemoryPermission.ReadWriteExecute => MemoryProtection.ExecuteReadWrite, - MemoryPermission.Execute => MemoryProtection.Execute, - _ => throw new MemoryProtectionException(permission) - }; + if (!WindowsApi.VirtualProtect((IntPtr)uaddress, (IntPtr)PageSize, WindowsApi.GetProtection(permission), out _)) + { + return false; + } + + uaddress += PageSize; + usize -= PageSize; + } + + return true; } public static bool Free(IntPtr address) { - return VirtualFree(address, IntPtr.Zero, AllocationType.Release); - } - - private static int GetEmulatedHandle() - { - // Assumes we have the handle lock. - - for (int i = 0; i < _emulatedShared.Length; i++) - { - if (_emulatedShared[i] == null) - { - return i + 1; - } - } - - throw new InvalidProgramException("Too many shared memory handles were created."); - } - - public static bool EmulatedHandleValid(ref int handle) - { - handle--; - return handle >= 0 && handle < _emulatedShared.Length && _emulatedShared[handle] != null; + return WindowsApi.VirtualFree(address, IntPtr.Zero, AllocationType.Release); } public static IntPtr CreateSharedMemory(IntPtr size, bool reserve) { - if (UseWin10Placeholders && reserve) + var prot = reserve ? FileMapProtection.SectionReserve : FileMapProtection.SectionCommit; + + IntPtr handle = WindowsApi.CreateFileMapping( + WindowsApi.InvalidHandleValue, + IntPtr.Zero, + FileMapProtection.PageReadWrite | prot, + (uint)(size.ToInt64() >> 32), + (uint)size.ToInt64(), + null); + + if (handle == IntPtr.Zero) { - lock (_emulatedHandleLock) - { - int handle = GetEmulatedHandle(); - _emulatedShared[handle - 1] = new EmulatedSharedMemoryWindows((ulong)size); - _emulatedSharedList.Add(_emulatedShared[handle - 1]); - - return (IntPtr)handle; - } + throw new OutOfMemoryException(); } - else - { - var prot = reserve ? FileMapProtection.SectionReserve : FileMapProtection.SectionCommit; - IntPtr handle = CreateFileMapping( - InvalidHandleValue, - IntPtr.Zero, - FileMapProtection.PageReadWrite | prot, - (uint)(size.ToInt64() >> 32), - (uint)size.ToInt64(), - null); - - if (handle == IntPtr.Zero) - { - throw new OutOfMemoryException(); - } - - return handle; - } + return handle; } public static void DestroySharedMemory(IntPtr handle) { - if (UseWin10Placeholders) - { - lock (_emulatedHandleLock) - { - int iHandle = (int)(ulong)handle; - - if (EmulatedHandleValid(ref iHandle)) - { - _emulatedSharedList.Remove(_emulatedShared[iHandle]); - _emulatedShared[iHandle].Dispose(); - _emulatedShared[iHandle] = null; - - return; - } - } - } - - if (!CloseHandle(handle)) + if (!WindowsApi.CloseHandle(handle)) { throw new ArgumentException("Invalid handle.", nameof(handle)); } @@ -253,20 +186,7 @@ namespace Ryujinx.Memory public static IntPtr MapSharedMemory(IntPtr handle) { - if (UseWin10Placeholders) - { - lock (_emulatedHandleLock) - { - int iHandle = (int)(ulong)handle; - - if (EmulatedHandleValid(ref iHandle)) - { - return _emulatedShared[iHandle].Map(); - } - } - } - - IntPtr ptr = MapViewOfFile(handle, 4 | 2, 0, 0, IntPtr.Zero); + IntPtr ptr = WindowsApi.MapViewOfFile(handle, 4 | 2, 0, 0, IntPtr.Zero); if (ptr == IntPtr.Zero) { @@ -278,24 +198,15 @@ namespace Ryujinx.Memory public static void UnmapSharedMemory(IntPtr address) { - if (UseWin10Placeholders) - { - lock (_emulatedHandleLock) - { - foreach (EmulatedSharedMemoryWindows shared in _emulatedSharedList) - { - if (shared.Unmap((ulong)address)) - { - return; - } - } - } - } - - if (!UnmapViewOfFile(address)) + if (!WindowsApi.UnmapViewOfFile(address)) { throw new ArgumentException("Invalid address.", nameof(address)); } } + + public static bool RetryFromAccessViolation() + { + return _placeholders.RetryFromAccessViolation(); + } } } \ No newline at end of file diff --git a/Ryujinx.Memory/MemoryManagerUnixHelper.cs b/Ryujinx.Memory/MemoryManagerUnixHelper.cs index 4409ccee1..8e6e79352 100644 --- a/Ryujinx.Memory/MemoryManagerUnixHelper.cs +++ b/Ryujinx.Memory/MemoryManagerUnixHelper.cs @@ -21,7 +21,23 @@ namespace Ryujinx.Memory MAP_PRIVATE = 2, MAP_ANONYMOUS = 4, MAP_NORESERVE = 8, - MAP_UNLOCKED = 16 + MAP_FIXED = 16, + MAP_UNLOCKED = 32 + } + + [Flags] + public enum OpenFlags : uint + { + O_RDONLY = 0, + O_WRONLY = 1, + O_RDWR = 2, + O_CREAT = 4, + O_EXCL = 8, + O_NOCTTY = 16, + O_TRUNC = 32, + O_APPEND = 64, + O_NONBLOCK = 128, + O_SYNC = 256, } private const int MAP_ANONYMOUS_LINUX_GENERIC = 0x20; @@ -50,6 +66,24 @@ namespace Ryujinx.Memory [DllImport("libc", SetLastError = true)] public static extern int madvise(IntPtr address, ulong size, int advice); + [DllImport("libc", SetLastError = true)] + public static extern int mkstemp(IntPtr template); + + [DllImport("libc", SetLastError = true)] + public static extern int unlink(IntPtr pathname); + + [DllImport("libc", SetLastError = true)] + public static extern int ftruncate(int fildes, IntPtr length); + + [DllImport("libc", SetLastError = true)] + public static extern int close(int fd); + + [DllImport("libc", SetLastError = true)] + public static extern int shm_open(IntPtr name, int oflag, uint mode); + + [DllImport("libc", SetLastError = true)] + public static extern int shm_unlink(IntPtr name); + private static int MmapFlagsToSystemFlags(MmapFlags flags) { int result = 0; @@ -64,6 +98,11 @@ namespace Ryujinx.Memory result |= (int)MmapFlags.MAP_PRIVATE; } + if (flags.HasFlag(MmapFlags.MAP_FIXED)) + { + result |= (int)MmapFlags.MAP_FIXED; + } + if (flags.HasFlag(MmapFlags.MAP_ANONYMOUS)) { if (OperatingSystem.IsLinux()) diff --git a/Ryujinx.Memory/PageTable.cs b/Ryujinx.Memory/PageTable.cs index 71db1e762..8fdedd4f8 100644 --- a/Ryujinx.Memory/PageTable.cs +++ b/Ryujinx.Memory/PageTable.cs @@ -1,6 +1,6 @@ namespace Ryujinx.Memory { - class PageTable where T : unmanaged + public class PageTable where T : unmanaged { public const int PageBits = 12; public const int PageSize = 1 << PageBits; diff --git a/Ryujinx.Memory/Range/HostMemoryRange.cs b/Ryujinx.Memory/Range/HostMemoryRange.cs deleted file mode 100644 index c6d8689c5..000000000 --- a/Ryujinx.Memory/Range/HostMemoryRange.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; - -namespace Ryujinx.Memory.Range -{ - /// - /// Range of memory composed of an address and size. - /// - public struct HostMemoryRange : IEquatable - { - /// - /// An empty memory range, with a null address and zero size. - /// - public static HostMemoryRange Empty => new HostMemoryRange(0, 0); - - /// - /// Start address of the range. - /// - public nuint Address { get; } - - /// - /// Size of the range in bytes. - /// - public ulong Size { get; } - - /// - /// Address where the range ends (exclusive). - /// - public nuint EndAddress => Address + (nuint)Size; - - /// - /// Creates a new memory range with the specified address and size. - /// - /// Start address - /// Size in bytes - public HostMemoryRange(nuint address, ulong size) - { - Address = address; - Size = size; - } - - /// - /// Checks if the range overlaps with another. - /// - /// The other range to check for overlap - /// True if the ranges overlap, false otherwise - public bool OverlapsWith(HostMemoryRange other) - { - nuint thisAddress = Address; - nuint thisEndAddress = EndAddress; - nuint otherAddress = other.Address; - nuint otherEndAddress = other.EndAddress; - - return thisAddress < otherEndAddress && otherAddress < thisEndAddress; - } - - public override bool Equals(object obj) - { - return obj is HostMemoryRange other && Equals(other); - } - - public bool Equals(HostMemoryRange other) - { - return Address == other.Address && Size == other.Size; - } - - public override int GetHashCode() - { - return HashCode.Combine(Address, Size); - } - } -} diff --git a/Ryujinx.Memory/Tracking/MemoryTracking.cs b/Ryujinx.Memory/Tracking/MemoryTracking.cs index aa7f9a6c7..e9a4793e8 100644 --- a/Ryujinx.Memory/Tracking/MemoryTracking.cs +++ b/Ryujinx.Memory/Tracking/MemoryTracking.cs @@ -188,6 +188,30 @@ namespace Ryujinx.Memory.Tracking return VirtualMemoryEvent(address, 1, write); } + /// + /// Signal that a virtual memory event happened at the given location. + /// This is similar VirtualMemoryEvent, but on Windows, it might also return true after a partial unmap. + /// This should only be called from the exception handler. + /// + /// Virtual address accessed + /// Size of the region affected in bytes + /// Whether the region was written to or read + /// True if the access is precise, false otherwise + /// True if the event triggered any tracking regions, false otherwise + public bool VirtualMemoryEventEh(ulong address, ulong size, bool write, bool precise = false) + { + // Windows has a limitation, it can't do partial unmaps. + // For this reason, we need to unmap the whole range and then remap the sub-ranges. + // When this happens, we might have caused a undesirable access violation from the time that the range was unmapped. + // In this case, try again as the memory might be mapped now. + if (OperatingSystem.IsWindows() && MemoryManagementWindows.RetryFromAccessViolation()) + { + return true; + } + + return VirtualMemoryEvent(address, size, write, precise); + } + /// /// Signal that a virtual memory event happened at the given location. /// This can be flagged as a precise event, which will avoid reprotection and call special handlers if possible. diff --git a/Ryujinx.Memory/WindowsShared/EmulatedSharedMemoryWindows.cs b/Ryujinx.Memory/WindowsShared/EmulatedSharedMemoryWindows.cs deleted file mode 100644 index 1417f7d5a..000000000 --- a/Ryujinx.Memory/WindowsShared/EmulatedSharedMemoryWindows.cs +++ /dev/null @@ -1,703 +0,0 @@ -using Ryujinx.Memory.Range; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; - -namespace Ryujinx.Memory.WindowsShared -{ - class EmulatedSharedMemoryWindows : IDisposable - { - private static readonly IntPtr InvalidHandleValue = new IntPtr(-1); - private static readonly IntPtr CurrentProcessHandle = new IntPtr(-1); - - public const int MappingBits = 16; // Windows 64kb granularity. - public const ulong MappingGranularity = 1 << MappingBits; - public const ulong MappingMask = MappingGranularity - 1; - - public const ulong BackingSize32GB = 32UL * 1024UL * 1024UL * 1024UL; // Reasonable max size of 32GB. - - private class SharedMemoryMapping : INonOverlappingRange - { - public ulong Address { get; } - - public ulong Size { get; private set; } - - public ulong EndAddress { get; private set; } - - public List Blocks; - - public SharedMemoryMapping(ulong address, ulong size, List blocks = null) - { - Address = address; - Size = size; - EndAddress = address + size; - - Blocks = blocks ?? new List(); - } - - public bool OverlapsWith(ulong address, ulong size) - { - return Address < address + size && address < EndAddress; - } - - public void ExtendTo(ulong endAddress, RangeList list) - { - EndAddress = endAddress; - Size = endAddress - Address; - - list.UpdateEndAddress(this); - } - - public void AddBlocks(IEnumerable blocks) - { - if (Blocks.Count > 0 && blocks.Count() > 0 && Blocks.Last() == blocks.First()) - { - Blocks.AddRange(blocks.Skip(1)); - } - else - { - Blocks.AddRange(blocks); - } - } - - public INonOverlappingRange Split(ulong splitAddress) - { - SharedMemoryMapping newRegion = new SharedMemoryMapping(splitAddress, EndAddress - splitAddress); - - int end = (int)((EndAddress + MappingMask) >> MappingBits); - int start = (int)(Address >> MappingBits); - - Size = splitAddress - Address; - EndAddress = splitAddress; - - int splitEndBlock = (int)((splitAddress + MappingMask) >> MappingBits); - int splitStartBlock = (int)(splitAddress >> MappingBits); - - newRegion.AddBlocks(Blocks.Skip(splitStartBlock - start)); - Blocks.RemoveRange(splitEndBlock - start, end - splitEndBlock); - - return newRegion; - } - } - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern IntPtr CreateFileMapping( - IntPtr hFile, - IntPtr lpFileMappingAttributes, - FileMapProtection flProtect, - uint dwMaximumSizeHigh, - uint dwMaximumSizeLow, - [MarshalAs(UnmanagedType.LPWStr)] string lpName); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool CloseHandle(IntPtr hObject); - - [DllImport("KernelBase.dll", SetLastError = true)] - private static extern IntPtr VirtualAlloc2( - IntPtr process, - IntPtr lpAddress, - IntPtr dwSize, - AllocationType flAllocationType, - MemoryProtection flProtect, - IntPtr extendedParameters, - ulong parameterCount); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool VirtualFree(IntPtr lpAddress, IntPtr dwSize, AllocationType dwFreeType); - - [DllImport("KernelBase.dll", SetLastError = true)] - private static extern IntPtr MapViewOfFile3( - IntPtr hFileMappingObject, - IntPtr process, - IntPtr baseAddress, - ulong offset, - IntPtr dwNumberOfBytesToMap, - ulong allocationType, - MemoryProtection dwDesiredAccess, - IntPtr extendedParameters, - ulong parameterCount); - - [DllImport("KernelBase.dll", SetLastError = true)] - private static extern bool UnmapViewOfFile2(IntPtr process, IntPtr lpBaseAddress, ulong unmapFlags); - - private ulong _size; - - private object _lock = new object(); - - private ulong _backingSize; - private IntPtr _backingMemHandle; - private int _backingEnd; - private int _backingAllocated; - private Queue _backingFreeList; - - private List _mappedBases; - private RangeList _mappings; - private SharedMemoryMapping[] _foundMappings = new SharedMemoryMapping[32]; - private PlaceholderList _placeholders; - - public EmulatedSharedMemoryWindows(ulong size) - { - ulong backingSize = BackingSize32GB; - - _size = size; - _backingSize = backingSize; - - _backingMemHandle = CreateFileMapping( - InvalidHandleValue, - IntPtr.Zero, - FileMapProtection.PageReadWrite | FileMapProtection.SectionReserve, - (uint)(backingSize >> 32), - (uint)backingSize, - null); - - if (_backingMemHandle == IntPtr.Zero) - { - throw new OutOfMemoryException(); - } - - _backingFreeList = new Queue(); - _mappings = new RangeList(); - _mappedBases = new List(); - _placeholders = new PlaceholderList(size >> MappingBits); - } - - private (ulong granularStart, ulong granularEnd) GetAlignedRange(ulong address, ulong size) - { - return (address & (~MappingMask), (address + size + MappingMask) & (~MappingMask)); - } - - private void Commit(ulong address, ulong size) - { - (ulong granularStart, ulong granularEnd) = GetAlignedRange(address, size); - - ulong endAddress = address + size; - - lock (_lock) - { - // Search a bit before and after the new mapping. - // When adding our new mapping, we may need to join an existing mapping into our new mapping (or in some cases, to the other side!) - ulong searchStart = granularStart == 0 ? 0 : (granularStart - 1); - int mappingCount = _mappings.FindOverlapsNonOverlapping(searchStart, (granularEnd - searchStart) + 1, ref _foundMappings); - - int first = -1; - int last = -1; - SharedMemoryMapping startOverlap = null; - SharedMemoryMapping endOverlap = null; - - int lastIndex = (int)(address >> MappingBits); - int endIndex = (int)((endAddress + MappingMask) >> MappingBits); - int firstBlock = -1; - int endBlock = -1; - - for (int i = 0; i < mappingCount; i++) - { - SharedMemoryMapping mapping = _foundMappings[i]; - - if (mapping.Address < address) - { - if (mapping.EndAddress >= address) - { - startOverlap = mapping; - } - - if ((int)((mapping.EndAddress - 1) >> MappingBits) == lastIndex) - { - lastIndex = (int)((mapping.EndAddress + MappingMask) >> MappingBits); - firstBlock = mapping.Blocks.Last(); - } - } - - if (mapping.EndAddress > endAddress) - { - if (mapping.Address <= endAddress) - { - endOverlap = mapping; - } - - if ((int)((mapping.Address) >> MappingBits) + 1 == endIndex) - { - endIndex = (int)((mapping.Address) >> MappingBits); - endBlock = mapping.Blocks.First(); - } - } - - if (mapping.OverlapsWith(address, size)) - { - if (first == -1) - { - first = i; - } - - last = i; - } - } - - if (startOverlap == endOverlap && startOverlap != null) - { - // Already fully committed. - return; - } - - var blocks = new List(); - int lastBlock = -1; - - if (firstBlock != -1) - { - blocks.Add(firstBlock); - lastBlock = firstBlock; - } - - bool hasMapped = false; - Action map = () => - { - if (!hasMapped) - { - _placeholders.EnsurePlaceholders(address >> MappingBits, (granularEnd - granularStart) >> MappingBits, SplitPlaceholder); - hasMapped = true; - } - - // There's a gap between this index and the last. Allocate blocks to fill it. - blocks.Add(MapBackingBlock(MappingGranularity * (ulong)lastIndex++)); - }; - - if (first != -1) - { - for (int i = first; i <= last; i++) - { - SharedMemoryMapping mapping = _foundMappings[i]; - int mapIndex = (int)(mapping.Address >> MappingBits); - - while (lastIndex < mapIndex) - { - map(); - } - - if (lastBlock == mapping.Blocks[0]) - { - blocks.AddRange(mapping.Blocks.Skip(1)); - } - else - { - blocks.AddRange(mapping.Blocks); - } - - lastIndex = (int)((mapping.EndAddress - 1) >> MappingBits) + 1; - } - } - - while (lastIndex < endIndex) - { - map(); - } - - if (endBlock != -1 && endBlock != lastBlock) - { - blocks.Add(endBlock); - } - - if (startOverlap != null && endOverlap != null) - { - // Both sides should be coalesced. Extend the start overlap to contain the end overlap, and add together their blocks. - - _mappings.Remove(endOverlap); - - startOverlap.ExtendTo(endOverlap.EndAddress, _mappings); - - startOverlap.AddBlocks(blocks); - startOverlap.AddBlocks(endOverlap.Blocks); - } - else if (startOverlap != null) - { - startOverlap.ExtendTo(endAddress, _mappings); - - startOverlap.AddBlocks(blocks); - } - else - { - var mapping = new SharedMemoryMapping(address, size, blocks); - - if (endOverlap != null) - { - mapping.ExtendTo(endOverlap.EndAddress, _mappings); - - mapping.AddBlocks(endOverlap.Blocks); - - _mappings.Remove(endOverlap); - } - - _mappings.Add(mapping); - } - } - } - - private void Decommit(ulong address, ulong size) - { - (ulong granularStart, ulong granularEnd) = GetAlignedRange(address, size); - ulong endAddress = address + size; - - lock (_lock) - { - int mappingCount = _mappings.FindOverlapsNonOverlapping(granularStart, granularEnd - granularStart, ref _foundMappings); - - int first = -1; - int last = -1; - - for (int i = 0; i < mappingCount; i++) - { - SharedMemoryMapping mapping = _foundMappings[i]; - - if (mapping.OverlapsWith(address, size)) - { - if (first == -1) - { - first = i; - } - - last = i; - } - } - - if (first == -1) - { - return; // Could not find any regions to decommit. - } - - int lastReleasedBlock = -1; - - bool releasedFirst = false; - bool releasedLast = false; - - for (int i = last; i >= first; i--) - { - SharedMemoryMapping mapping = _foundMappings[i]; - bool releaseEnd = true; - bool releaseStart = true; - - if (i == last) - { - // If this is the last region, do not release the block if there is a page ahead of us, or the block continues after us. (it is keeping the block alive) - releaseEnd = last == mappingCount - 1; - - // If the end region starts after the decommit end address, split and readd it after modifying its base address. - if (mapping.EndAddress > endAddress) - { - var newMapping = (SharedMemoryMapping)mapping.Split(endAddress); - _mappings.UpdateEndAddress(mapping); - _mappings.Add(newMapping); - - if ((endAddress & MappingMask) != 0) - { - releaseEnd = false; - } - } - - releasedLast = releaseEnd; - } - - if (i == first) - { - // If this is the first region, do not release the block if there is a region behind us. (it is keeping the block alive) - releaseStart = first == 0; - - // If the first region starts before the decommit address, split it by modifying its end address. - if (mapping.Address < address) - { - var oldMapping = mapping; - mapping = (SharedMemoryMapping)mapping.Split(address); - _mappings.UpdateEndAddress(oldMapping); - - if ((address & MappingMask) != 0) - { - releaseStart = false; - } - } - - releasedFirst = releaseStart; - } - - _mappings.Remove(mapping); - - ulong releasePointer = (mapping.EndAddress + MappingMask) & (~MappingMask); - for (int j = mapping.Blocks.Count - 1; j >= 0; j--) - { - int blockId = mapping.Blocks[j]; - - releasePointer -= MappingGranularity; - - if (lastReleasedBlock == blockId) - { - // When committed regions are fragmented, multiple will have the same block id for their start/end granular block. - // Avoid releasing these blocks twice. - continue; - } - - if ((j != 0 || releaseStart) && (j != mapping.Blocks.Count - 1 || releaseEnd)) - { - ReleaseBackingBlock(releasePointer, blockId); - } - - lastReleasedBlock = blockId; - } - } - - ulong placeholderStart = (granularStart >> MappingBits) + (releasedFirst ? 0UL : 1UL); - ulong placeholderEnd = (granularEnd >> MappingBits) - (releasedLast ? 0UL : 1UL); - - if (placeholderEnd > placeholderStart) - { - _placeholders.RemovePlaceholders(placeholderStart, placeholderEnd - placeholderStart, CoalescePlaceholder); - } - } - } - - public bool CommitMap(IntPtr address, IntPtr size) - { - lock (_lock) - { - foreach (ulong mapping in _mappedBases) - { - ulong offset = (ulong)address - mapping; - - if (offset < _size) - { - Commit(offset, (ulong)size); - return true; - } - } - } - - return false; - } - - public bool DecommitMap(IntPtr address, IntPtr size) - { - lock (_lock) - { - foreach (ulong mapping in _mappedBases) - { - ulong offset = (ulong)address - mapping; - - if (offset < _size) - { - Decommit(offset, (ulong)size); - return true; - } - } - } - - return false; - } - - private int MapBackingBlock(ulong offset) - { - bool allocate = false; - int backing; - - if (_backingFreeList.Count > 0) - { - backing = _backingFreeList.Dequeue(); - } - else - { - if (_backingAllocated == _backingEnd) - { - // Allocate the backing. - _backingAllocated++; - allocate = true; - } - - backing = _backingEnd++; - } - - ulong backingOffset = MappingGranularity * (ulong)backing; - - foreach (ulong baseAddress in _mappedBases) - { - CommitToMap(baseAddress, offset, MappingGranularity, backingOffset, allocate); - allocate = false; - } - - return backing; - } - - private void ReleaseBackingBlock(ulong offset, int id) - { - foreach (ulong baseAddress in _mappedBases) - { - DecommitFromMap(baseAddress, offset); - } - - if (_backingEnd - 1 == id) - { - _backingEnd = id; - } - else - { - _backingFreeList.Enqueue(id); - } - } - - public IntPtr Map() - { - IntPtr newMapping = VirtualAlloc2( - CurrentProcessHandle, - IntPtr.Zero, - (IntPtr)_size, - AllocationType.Reserve | AllocationType.ReservePlaceholder, - MemoryProtection.NoAccess, - IntPtr.Zero, - 0); - - if (newMapping == IntPtr.Zero) - { - throw new OutOfMemoryException(); - } - - // Apply all existing mappings to the new mapping - lock (_lock) - { - int lastBlock = -1; - foreach (SharedMemoryMapping mapping in _mappings) - { - ulong blockAddress = mapping.Address & (~MappingMask); - foreach (int block in mapping.Blocks) - { - if (block != lastBlock) - { - ulong backingOffset = MappingGranularity * (ulong)block; - - CommitToMap((ulong)newMapping, blockAddress, MappingGranularity, backingOffset, false); - - lastBlock = block; - } - - blockAddress += MappingGranularity; - } - } - - _mappedBases.Add((ulong)newMapping); - } - - return newMapping; - } - - private void SplitPlaceholder(ulong address, ulong size) - { - ulong byteAddress = address << MappingBits; - IntPtr byteSize = (IntPtr)(size << MappingBits); - - foreach (ulong mapAddress in _mappedBases) - { - bool result = VirtualFree((IntPtr)(mapAddress + byteAddress), byteSize, AllocationType.PreservePlaceholder | AllocationType.Release); - - if (!result) - { - throw new InvalidOperationException("Placeholder could not be split."); - } - } - } - - private void CoalescePlaceholder(ulong address, ulong size) - { - ulong byteAddress = address << MappingBits; - IntPtr byteSize = (IntPtr)(size << MappingBits); - - foreach (ulong mapAddress in _mappedBases) - { - bool result = VirtualFree((IntPtr)(mapAddress + byteAddress), byteSize, AllocationType.CoalescePlaceholders | AllocationType.Release); - - if (!result) - { - throw new InvalidOperationException("Placeholder could not be coalesced."); - } - } - } - - private void CommitToMap(ulong mapAddress, ulong address, ulong size, ulong backingOffset, bool allocate) - { - IntPtr targetAddress = (IntPtr)(mapAddress + address); - - // Assume the placeholder worked (or already exists) - // Map the backing memory into the mapped location. - - IntPtr mapped = MapViewOfFile3( - _backingMemHandle, - CurrentProcessHandle, - targetAddress, - backingOffset, - (IntPtr)MappingGranularity, - 0x4000, // REPLACE_PLACEHOLDER - MemoryProtection.ReadWrite, - IntPtr.Zero, - 0); - - if (mapped == IntPtr.Zero) - { - throw new InvalidOperationException($"Could not map view of backing memory. (va=0x{address:X16} size=0x{size:X16}, error code {Marshal.GetLastWin32Error()})"); - } - - if (allocate) - { - // Commit this part of the shared memory. - VirtualAlloc2(CurrentProcessHandle, targetAddress, (IntPtr)MappingGranularity, AllocationType.Commit, MemoryProtection.ReadWrite, IntPtr.Zero, 0); - } - } - - private void DecommitFromMap(ulong baseAddress, ulong address) - { - UnmapViewOfFile2(CurrentProcessHandle, (IntPtr)(baseAddress + address), 2); - } - - public bool Unmap(ulong baseAddress) - { - lock (_lock) - { - if (_mappedBases.Remove(baseAddress)) - { - int lastBlock = -1; - - foreach (SharedMemoryMapping mapping in _mappings) - { - ulong blockAddress = mapping.Address & (~MappingMask); - foreach (int block in mapping.Blocks) - { - if (block != lastBlock) - { - DecommitFromMap(baseAddress, blockAddress); - - lastBlock = block; - } - - blockAddress += MappingGranularity; - } - } - - if (!VirtualFree((IntPtr)baseAddress, (IntPtr)0, AllocationType.Release)) - { - throw new InvalidOperationException("Couldn't free mapping placeholder."); - } - - return true; - } - - return false; - } - } - - public void Dispose() - { - // Remove all file mappings - lock (_lock) - { - foreach (ulong baseAddress in _mappedBases.ToArray()) - { - Unmap(baseAddress); - } - } - - // Finally, delete the file mapping. - CloseHandle(_backingMemHandle); - } - } -} diff --git a/Ryujinx.Memory/WindowsShared/IntervalTree.cs b/Ryujinx.Memory/WindowsShared/IntervalTree.cs new file mode 100644 index 000000000..eac5c5456 --- /dev/null +++ b/Ryujinx.Memory/WindowsShared/IntervalTree.cs @@ -0,0 +1,740 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Memory.WindowsShared +{ + /// + /// An Augmented Interval Tree based off of the "TreeDictionary"'s Red-Black Tree. Allows fast overlap checking of ranges. + /// + /// Key + /// Value + class IntervalTree where K : IComparable + { + private const int ArrayGrowthSize = 32; + + private const bool Black = true; + private const bool Red = false; + private IntervalTreeNode _root = null; + private int _count = 0; + + public int Count => _count; + + public IntervalTree() { } + + #region Public Methods + + /// + /// Gets the values of the interval whose key is . + /// + /// Key of the node value to get + /// Value with the given + /// True if the key is on the dictionary, false otherwise + public bool TryGet(K key, out V value) + { + IntervalTreeNode node = GetNode(key); + + if (node == null) + { + value = default; + return false; + } + + value = node.Value; + return true; + } + + /// + /// Returns the start addresses of the intervals whose start and end keys overlap the given range. + /// + /// Start of the range + /// End of the range + /// Overlaps array to place results in + /// Index to start writing results into the array. Defaults to 0 + /// Number of intervals found + public int Get(K start, K end, ref IntervalTreeNode[] overlaps, int overlapCount = 0) + { + GetNodes(_root, start, end, ref overlaps, ref overlapCount); + + return overlapCount; + } + + /// + /// Adds a new interval into the tree whose start is , end is and value is . + /// + /// Start of the range to add + /// End of the range to insert + /// Value to add + /// is null + public void Add(K start, K end, V value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + BSTInsert(start, end, value, null, out _); + } + + /// + /// Removes a value from the tree, searching for it with . + /// + /// Key of the node to remove + /// Number of deleted values + public int Remove(K key) + { + return Remove(GetNode(key)); + } + + /// + /// Removes a value from the tree, searching for it with . + /// + /// Node to be removed + /// Number of deleted values + public int Remove(IntervalTreeNode nodeToDelete) + { + if (nodeToDelete == null) + { + return 0; + } + + Delete(nodeToDelete); + + _count--; + + return 1; + } + + /// + /// Adds all the nodes in the dictionary into . + /// + /// A list of all values sorted by Key Order + public List AsList() + { + List list = new List(); + + AddToList(_root, list); + + return list; + } + + #endregion + + #region Private Methods (BST) + + /// + /// Adds all values that are children of or contained within into , in Key Order. + /// + /// The node to search for values within + /// The list to add values to + private void AddToList(IntervalTreeNode node, List list) + { + if (node == null) + { + return; + } + + AddToList(node.Left, list); + + list.Add(node.Value); + + AddToList(node.Right, list); + } + + /// + /// Retrieve the node reference whose key is , or null if no such node exists. + /// + /// Key of the node to get + /// is null + /// Node reference in the tree + private IntervalTreeNode GetNode(K key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + IntervalTreeNode node = _root; + while (node != null) + { + int cmp = key.CompareTo(node.Start); + if (cmp < 0) + { + node = node.Left; + } + else if (cmp > 0) + { + node = node.Right; + } + else + { + return node; + } + } + return null; + } + + /// + /// Retrieve all nodes that overlap the given start and end keys. + /// + /// Start of the range + /// End of the range + /// Overlaps array to place results in + /// Overlaps count to update + private void GetNodes(IntervalTreeNode node, K start, K end, ref IntervalTreeNode[] overlaps, ref int overlapCount) + { + if (node == null || start.CompareTo(node.Max) >= 0) + { + return; + } + + GetNodes(node.Left, start, end, ref overlaps, ref overlapCount); + + bool endsOnRight = end.CompareTo(node.Start) > 0; + if (endsOnRight) + { + if (start.CompareTo(node.End) < 0) + { + if (overlaps.Length >= overlapCount) + { + Array.Resize(ref overlaps, overlapCount + ArrayGrowthSize); + } + + overlaps[overlapCount++] = node; + } + + GetNodes(node.Right, start, end, ref overlaps, ref overlapCount); + } + } + + /// + /// Propagate an increase in max value starting at the given node, heading up the tree. + /// This should only be called if the max increases - not for rebalancing or removals. + /// + /// The node to start propagating from + private void PropagateIncrease(IntervalTreeNode node) + { + K max = node.Max; + IntervalTreeNode ptr = node; + + while ((ptr = ptr.Parent) != null) + { + if (max.CompareTo(ptr.Max) > 0) + { + ptr.Max = max; + } + else + { + break; + } + } + } + + /// + /// Propagate recalculating max value starting at the given node, heading up the tree. + /// This fully recalculates the max value from all children when there is potential for it to decrease. + /// + /// The node to start propagating from + private void PropagateFull(IntervalTreeNode node) + { + IntervalTreeNode ptr = node; + + do + { + K max = ptr.End; + + if (ptr.Left != null && ptr.Left.Max.CompareTo(max) > 0) + { + max = ptr.Left.Max; + } + + if (ptr.Right != null && ptr.Right.Max.CompareTo(max) > 0) + { + max = ptr.Right.Max; + } + + ptr.Max = max; + } while ((ptr = ptr.Parent) != null); + } + + /// + /// Insertion Mechanism for the interval tree. Similar to a BST insert, with the start of the range as the key. + /// Iterates the tree starting from the root and inserts a new node where all children in the left subtree are less than , and all children in the right subtree are greater than . + /// Each node can contain multiple values, and has an end address which is the maximum of all those values. + /// Post insertion, the "max" value of the node and all parents are updated. + /// + /// Start of the range to insert + /// End of the range to insert + /// Value to insert + /// Optional factory used to create a new value if is already on the tree + /// Node that was inserted or modified + /// True if was not yet on the tree, false otherwise + private bool BSTInsert(K start, K end, V value, Func updateFactoryCallback, out IntervalTreeNode outNode) + { + IntervalTreeNode parent = null; + IntervalTreeNode node = _root; + + while (node != null) + { + parent = node; + int cmp = start.CompareTo(node.Start); + if (cmp < 0) + { + node = node.Left; + } + else if (cmp > 0) + { + node = node.Right; + } + else + { + outNode = node; + + if (updateFactoryCallback != null) + { + // Replace + node.Value = updateFactoryCallback(start, node.Value); + + int endCmp = end.CompareTo(node.End); + + if (endCmp > 0) + { + node.End = end; + if (end.CompareTo(node.Max) > 0) + { + node.Max = end; + PropagateIncrease(node); + RestoreBalanceAfterInsertion(node); + } + } + else if (endCmp < 0) + { + node.End = end; + PropagateFull(node); + } + } + + return false; + } + } + IntervalTreeNode newNode = new IntervalTreeNode(start, end, value, parent); + if (newNode.Parent == null) + { + _root = newNode; + } + else if (start.CompareTo(parent.Start) < 0) + { + parent.Left = newNode; + } + else + { + parent.Right = newNode; + } + + PropagateIncrease(newNode); + _count++; + RestoreBalanceAfterInsertion(newNode); + outNode = newNode; + return true; + } + + /// + /// Removes the value from the dictionary after searching for it with . + /// + /// Tree node to be removed + private void Delete(IntervalTreeNode nodeToDelete) + { + IntervalTreeNode replacementNode; + + if (LeftOf(nodeToDelete) == null || RightOf(nodeToDelete) == null) + { + replacementNode = nodeToDelete; + } + else + { + replacementNode = PredecessorOf(nodeToDelete); + } + + IntervalTreeNode tmp = LeftOf(replacementNode) ?? RightOf(replacementNode); + + if (tmp != null) + { + tmp.Parent = ParentOf(replacementNode); + } + + if (ParentOf(replacementNode) == null) + { + _root = tmp; + } + else if (replacementNode == LeftOf(ParentOf(replacementNode))) + { + ParentOf(replacementNode).Left = tmp; + } + else + { + ParentOf(replacementNode).Right = tmp; + } + + if (replacementNode != nodeToDelete) + { + nodeToDelete.Start = replacementNode.Start; + nodeToDelete.Value = replacementNode.Value; + nodeToDelete.End = replacementNode.End; + nodeToDelete.Max = replacementNode.Max; + } + + PropagateFull(replacementNode); + + if (tmp != null && ColorOf(replacementNode) == Black) + { + RestoreBalanceAfterRemoval(tmp); + } + } + + /// + /// Returns the node with the largest key where is considered the root node. + /// + /// Root Node + /// Node with the maximum key in the tree of + private static IntervalTreeNode Maximum(IntervalTreeNode node) + { + IntervalTreeNode tmp = node; + while (tmp.Right != null) + { + tmp = tmp.Right; + } + + return tmp; + } + + /// + /// Finds the node whose key is immediately less than . + /// + /// Node to find the predecessor of + /// Predecessor of + private static IntervalTreeNode PredecessorOf(IntervalTreeNode node) + { + if (node.Left != null) + { + return Maximum(node.Left); + } + IntervalTreeNode parent = node.Parent; + while (parent != null && node == parent.Left) + { + node = parent; + parent = parent.Parent; + } + return parent; + } + + #endregion + + #region Private Methods (RBL) + + private void RestoreBalanceAfterRemoval(IntervalTreeNode balanceNode) + { + IntervalTreeNode ptr = balanceNode; + + while (ptr != _root && ColorOf(ptr) == Black) + { + if (ptr == LeftOf(ParentOf(ptr))) + { + IntervalTreeNode sibling = RightOf(ParentOf(ptr)); + + if (ColorOf(sibling) == Red) + { + SetColor(sibling, Black); + SetColor(ParentOf(ptr), Red); + RotateLeft(ParentOf(ptr)); + sibling = RightOf(ParentOf(ptr)); + } + if (ColorOf(LeftOf(sibling)) == Black && ColorOf(RightOf(sibling)) == Black) + { + SetColor(sibling, Red); + ptr = ParentOf(ptr); + } + else + { + if (ColorOf(RightOf(sibling)) == Black) + { + SetColor(LeftOf(sibling), Black); + SetColor(sibling, Red); + RotateRight(sibling); + sibling = RightOf(ParentOf(ptr)); + } + SetColor(sibling, ColorOf(ParentOf(ptr))); + SetColor(ParentOf(ptr), Black); + SetColor(RightOf(sibling), Black); + RotateLeft(ParentOf(ptr)); + ptr = _root; + } + } + else + { + IntervalTreeNode sibling = LeftOf(ParentOf(ptr)); + + if (ColorOf(sibling) == Red) + { + SetColor(sibling, Black); + SetColor(ParentOf(ptr), Red); + RotateRight(ParentOf(ptr)); + sibling = LeftOf(ParentOf(ptr)); + } + if (ColorOf(RightOf(sibling)) == Black && ColorOf(LeftOf(sibling)) == Black) + { + SetColor(sibling, Red); + ptr = ParentOf(ptr); + } + else + { + if (ColorOf(LeftOf(sibling)) == Black) + { + SetColor(RightOf(sibling), Black); + SetColor(sibling, Red); + RotateLeft(sibling); + sibling = LeftOf(ParentOf(ptr)); + } + SetColor(sibling, ColorOf(ParentOf(ptr))); + SetColor(ParentOf(ptr), Black); + SetColor(LeftOf(sibling), Black); + RotateRight(ParentOf(ptr)); + ptr = _root; + } + } + } + SetColor(ptr, Black); + } + + private void RestoreBalanceAfterInsertion(IntervalTreeNode balanceNode) + { + SetColor(balanceNode, Red); + while (balanceNode != null && balanceNode != _root && ColorOf(ParentOf(balanceNode)) == Red) + { + if (ParentOf(balanceNode) == LeftOf(ParentOf(ParentOf(balanceNode)))) + { + IntervalTreeNode sibling = RightOf(ParentOf(ParentOf(balanceNode))); + + if (ColorOf(sibling) == Red) + { + SetColor(ParentOf(balanceNode), Black); + SetColor(sibling, Black); + SetColor(ParentOf(ParentOf(balanceNode)), Red); + balanceNode = ParentOf(ParentOf(balanceNode)); + } + else + { + if (balanceNode == RightOf(ParentOf(balanceNode))) + { + balanceNode = ParentOf(balanceNode); + RotateLeft(balanceNode); + } + SetColor(ParentOf(balanceNode), Black); + SetColor(ParentOf(ParentOf(balanceNode)), Red); + RotateRight(ParentOf(ParentOf(balanceNode))); + } + } + else + { + IntervalTreeNode sibling = LeftOf(ParentOf(ParentOf(balanceNode))); + + if (ColorOf(sibling) == Red) + { + SetColor(ParentOf(balanceNode), Black); + SetColor(sibling, Black); + SetColor(ParentOf(ParentOf(balanceNode)), Red); + balanceNode = ParentOf(ParentOf(balanceNode)); + } + else + { + if (balanceNode == LeftOf(ParentOf(balanceNode))) + { + balanceNode = ParentOf(balanceNode); + RotateRight(balanceNode); + } + SetColor(ParentOf(balanceNode), Black); + SetColor(ParentOf(ParentOf(balanceNode)), Red); + RotateLeft(ParentOf(ParentOf(balanceNode))); + } + } + } + SetColor(_root, Black); + } + + private void RotateLeft(IntervalTreeNode node) + { + if (node != null) + { + IntervalTreeNode right = RightOf(node); + node.Right = LeftOf(right); + if (node.Right != null) + { + node.Right.Parent = node; + } + IntervalTreeNode nodeParent = ParentOf(node); + right.Parent = nodeParent; + if (nodeParent == null) + { + _root = right; + } + else if (node == LeftOf(nodeParent)) + { + nodeParent.Left = right; + } + else + { + nodeParent.Right = right; + } + right.Left = node; + node.Parent = right; + + PropagateFull(node); + } + } + + private void RotateRight(IntervalTreeNode node) + { + if (node != null) + { + IntervalTreeNode left = LeftOf(node); + node.Left = RightOf(left); + if (node.Left != null) + { + node.Left.Parent = node; + } + IntervalTreeNode nodeParent = ParentOf(node); + left.Parent = nodeParent; + if (nodeParent == null) + { + _root = left; + } + else if (node == RightOf(nodeParent)) + { + nodeParent.Right = left; + } + else + { + nodeParent.Left = left; + } + left.Right = node; + node.Parent = left; + + PropagateFull(node); + } + } + + #endregion + + #region Safety-Methods + + // These methods save memory by allowing us to forego sentinel nil nodes, as well as serve as protection against NullReferenceExceptions. + + /// + /// Returns the color of , or Black if it is null. + /// + /// Node + /// The boolean color of , or black if null + private static bool ColorOf(IntervalTreeNode node) + { + return node == null || node.Color; + } + + /// + /// Sets the color of node to . + ///

+ /// This method does nothing if is null. + ///
+ /// Node to set the color of + /// Color (Boolean) + private static void SetColor(IntervalTreeNode node, bool color) + { + if (node != null) + { + node.Color = color; + } + } + + /// + /// This method returns the left node of , or null if is null. + /// + /// Node to retrieve the left child from + /// Left child of + private static IntervalTreeNode LeftOf(IntervalTreeNode node) + { + return node?.Left; + } + + /// + /// This method returns the right node of , or null if is null. + /// + /// Node to retrieve the right child from + /// Right child of + private static IntervalTreeNode RightOf(IntervalTreeNode node) + { + return node?.Right; + } + + /// + /// Returns the parent node of , or null if is null. + /// + /// Node to retrieve the parent from + /// Parent of + private static IntervalTreeNode ParentOf(IntervalTreeNode node) + { + return node?.Parent; + } + + #endregion + + public bool ContainsKey(K key) + { + return GetNode(key) != null; + } + + public void Clear() + { + _root = null; + _count = 0; + } + } + + /// + /// Represents a node in the IntervalTree which contains start and end keys of type K, and a value of generic type V. + /// + /// Key type of the node + /// Value type of the node + class IntervalTreeNode + { + public bool Color = true; + public IntervalTreeNode Left = null; + public IntervalTreeNode Right = null; + public IntervalTreeNode Parent = null; + + /// + /// The start of the range. + /// + public K Start; + + /// + /// The end of the range. + /// + public K End; + + /// + /// The maximum end value of this node and all its children. + /// + public K Max; + + /// + /// Value stored on this node. + /// + public V Value; + + public IntervalTreeNode(K start, K end, V value, IntervalTreeNode parent) + { + Start = start; + End = end; + Max = end; + Value = value; + Parent = parent; + } + } +} diff --git a/Ryujinx.Memory/WindowsShared/PlaceholderList.cs b/Ryujinx.Memory/WindowsShared/PlaceholderList.cs deleted file mode 100644 index 848e410e8..000000000 --- a/Ryujinx.Memory/WindowsShared/PlaceholderList.cs +++ /dev/null @@ -1,293 +0,0 @@ -using Ryujinx.Memory.Range; -using System; -using System.Diagnostics; - -namespace Ryujinx.Memory.WindowsShared -{ - /// - /// A specialized list used for keeping track of Windows 10's memory placeholders. - /// This is used to make splitting a large placeholder into equally small - /// granular chunks much easier, while avoiding slowdown due to a large number of - /// placeholders by coalescing adjacent granular placeholders after they are unused. - /// - class PlaceholderList - { - private class PlaceholderBlock : IRange - { - public ulong Address { get; } - public ulong Size { get; private set; } - public ulong EndAddress { get; private set; } - public bool IsGranular { get; set; } - - public PlaceholderBlock(ulong id, ulong size, bool isGranular) - { - Address = id; - Size = size; - EndAddress = id + size; - IsGranular = isGranular; - } - - public bool OverlapsWith(ulong address, ulong size) - { - return Address < address + size && address < EndAddress; - } - - public void ExtendTo(ulong end, RangeList list) - { - EndAddress = end; - Size = end - Address; - - list.UpdateEndAddress(this); - } - } - - private RangeList _placeholders; - private PlaceholderBlock[] _foundBlocks = new PlaceholderBlock[32]; - - /// - /// Create a new list to manage placeholders. - /// Note that a size is measured in granular placeholders. - /// If the placeholder granularity is 65536 bytes, then a 65536 region will be covered by 1 placeholder granularity. - /// - /// Size measured in granular placeholders - public PlaceholderList(ulong size) - { - _placeholders = new RangeList(); - - _placeholders.Add(new PlaceholderBlock(0, size, false)); - } - - /// - /// Ensure that the given range of placeholders is granular. - /// - /// Start of the range, measured in granular placeholders - /// Size of the range, measured in granular placeholders - /// Callback function to run when splitting placeholders, calls with (start, middle) - public void EnsurePlaceholders(ulong id, ulong size, Action splitPlaceholderCallback) - { - // Search 1 before and after the placeholders, as we may need to expand/join granular regions surrounding the requested area. - - ulong endId = id + size; - ulong searchStartId = id == 0 ? 0 : (id - 1); - int blockCount = _placeholders.FindOverlapsNonOverlapping(searchStartId, (endId - searchStartId) + 1, ref _foundBlocks); - - PlaceholderBlock first = _foundBlocks[0]; - PlaceholderBlock last = _foundBlocks[blockCount - 1]; - bool overlapStart = first.EndAddress >= id && id != 0; - bool overlapEnd = last.Address <= endId; - - for (int i = 0; i < blockCount; i++) - { - // Go through all non-granular blocks in the range and create placeholders. - PlaceholderBlock block = _foundBlocks[i]; - - if (block.Address <= id && block.EndAddress >= endId && block.IsGranular) - { - return; // The region we're searching for is already granular. - } - - if (!block.IsGranular) - { - ulong placeholderStart = Math.Max(block.Address, id); - ulong placeholderEnd = Math.Min(block.EndAddress - 1, endId); - - if (placeholderStart != block.Address && placeholderStart != block.EndAddress) - { - splitPlaceholderCallback(block.Address, placeholderStart - block.Address); - } - - for (ulong j = placeholderStart; j < placeholderEnd; j++) - { - splitPlaceholderCallback(j, 1); - } - } - - if (!((block == first && overlapStart) || (block == last && overlapEnd))) - { - // Remove blocks that will be replaced - _placeholders.Remove(block); - } - } - - if (overlapEnd) - { - if (!(first == last && overlapStart)) - { - _placeholders.Remove(last); - } - - if (last.IsGranular) - { - endId = last.EndAddress; - } - else if (last.EndAddress != endId) - { - _placeholders.Add(new PlaceholderBlock(endId, last.EndAddress - endId, false)); - } - } - - if (overlapStart && first.IsGranular) - { - first.ExtendTo(endId, _placeholders); - } - else - { - if (overlapStart) - { - first.ExtendTo(id, _placeholders); - } - - _placeholders.Add(new PlaceholderBlock(id, endId - id, true)); - } - - ValidateList(); - } - - /// - /// Coalesces placeholders in a given region, as they are not being used. - /// This assumes that the region only contains placeholders - all views and allocations must have been replaced with placeholders. - /// - /// Start of the range, measured in granular placeholders - /// Size of the range, measured in granular placeholders - /// Callback function to run when coalescing two placeholders, calls with (start, end) - public void RemovePlaceholders(ulong id, ulong size, Action coalescePlaceholderCallback) - { - ulong endId = id + size; - int blockCount = _placeholders.FindOverlapsNonOverlapping(id, size, ref _foundBlocks); - - PlaceholderBlock first = _foundBlocks[0]; - PlaceholderBlock last = _foundBlocks[blockCount - 1]; - - // All granular blocks must have non-granular blocks surrounding them, unless they start at 0. - // We must extend the non-granular blocks into the granular ones. This does mean that we need to search twice. - - if (first.IsGranular || last.IsGranular) - { - ulong surroundStart = Math.Max(0, (first.IsGranular && first.Address != 0) ? first.Address - 1 : id); - blockCount = _placeholders.FindOverlapsNonOverlapping( - surroundStart, - (last.IsGranular ? last.EndAddress + 1 : endId) - surroundStart, - ref _foundBlocks); - - first = _foundBlocks[0]; - last = _foundBlocks[blockCount - 1]; - } - - if (first == last) - { - return; // Already coalesced. - } - - PlaceholderBlock extendBlock = id == 0 ? null : first; - bool newBlock = false; - for (int i = extendBlock == null ? 0 : 1; i < blockCount; i++) - { - // Go through all granular blocks in the range and extend placeholders. - PlaceholderBlock block = _foundBlocks[i]; - - ulong blockEnd = block.EndAddress; - ulong extendFrom; - ulong extent = Math.Min(blockEnd, endId); - - if (block.Address < id && blockEnd > id) - { - block.ExtendTo(id, _placeholders); - extendBlock = null; - } - else - { - _placeholders.Remove(block); - } - - if (extendBlock == null) - { - extendFrom = id; - extendBlock = new PlaceholderBlock(id, extent - id, false); - _placeholders.Add(extendBlock); - - if (blockEnd > extent) - { - _placeholders.Add(new PlaceholderBlock(extent, blockEnd - extent, true)); - - // Skip the next non-granular block, and extend from that into the granular block afterwards. - // (assuming that one is still in the requested range) - - if (i + 1 < blockCount) - { - extendBlock = _foundBlocks[i + 1]; - } - - i++; - } - - newBlock = true; - } - else - { - extendFrom = extendBlock.Address; - extendBlock.ExtendTo(block.IsGranular ? extent : block.EndAddress, _placeholders); - } - - if (block.IsGranular) - { - ulong placeholderStart = Math.Max(block.Address, id); - ulong placeholderEnd = extent; - - if (newBlock) - { - placeholderStart++; - newBlock = false; - } - - for (ulong j = placeholderStart; j < placeholderEnd; j++) - { - coalescePlaceholderCallback(extendFrom, (j + 1) - extendFrom); - } - - if (extent < block.EndAddress) - { - _placeholders.Add(new PlaceholderBlock(placeholderEnd, block.EndAddress - placeholderEnd, true)); - ValidateList(); - return; - } - } - else - { - coalescePlaceholderCallback(extendFrom, block.EndAddress - extendFrom); - } - } - - ValidateList(); - } - - /// - /// Ensure that the placeholder list is valid. - /// A valid list should not have any gaps between the placeholders, - /// and there may be no placehonders with the same IsGranular value next to each other. - /// - [Conditional("DEBUG")] - private void ValidateList() - { - bool isGranular = false; - bool first = true; - ulong lastAddress = 0; - - foreach (var placeholder in _placeholders) - { - if (placeholder.Address != lastAddress) - { - throw new InvalidOperationException("Gap in placeholder list."); - } - - if (isGranular == placeholder.IsGranular && !first) - { - throw new InvalidOperationException("Placeholder list not alternating."); - } - - first = false; - isGranular = placeholder.IsGranular; - lastAddress = placeholder.EndAddress; - } - } - } -} diff --git a/Ryujinx.Memory/WindowsShared/PlaceholderManager.cs b/Ryujinx.Memory/WindowsShared/PlaceholderManager.cs new file mode 100644 index 000000000..a16a8c7fa --- /dev/null +++ b/Ryujinx.Memory/WindowsShared/PlaceholderManager.cs @@ -0,0 +1,633 @@ +using System; +using System.Diagnostics; +using System.Threading; + +namespace Ryujinx.Memory.WindowsShared +{ + /// + /// Windows memory placeholder manager. + /// + class PlaceholderManager + { + private const ulong MinimumPageSize = 0x1000; + + [ThreadStatic] + private static int _threadLocalPartialUnmapsCount; + + private readonly IntervalTree _mappings; + private readonly IntervalTree _protections; + private readonly ReaderWriterLock _partialUnmapLock; + private int _partialUnmapsCount; + + /// + /// Creates a new instance of the Windows memory placeholder manager. + /// + public PlaceholderManager() + { + _mappings = new IntervalTree(); + _protections = new IntervalTree(); + _partialUnmapLock = new ReaderWriterLock(); + } + + /// + /// Reserves a range of the address space to be later mapped as shared memory views. + /// + /// Start address of the region to reserve + /// Size in bytes of the region to reserve + public void ReserveRange(ulong address, ulong size) + { + lock (_mappings) + { + _mappings.Add(address, address + size, ulong.MaxValue); + } + } + + /// + /// Maps a shared memory view on a previously reserved memory region. + /// + /// Shared memory that will be the backing storage for the view + /// Offset in the shared memory to map + /// Address to map the view into + /// Size of the view in bytes + public void MapView(IntPtr sharedMemory, ulong srcOffset, IntPtr location, IntPtr size) + { + _partialUnmapLock.AcquireReaderLock(Timeout.Infinite); + + try + { + UnmapViewInternal(sharedMemory, location, size); + MapViewInternal(sharedMemory, srcOffset, location, size); + } + finally + { + _partialUnmapLock.ReleaseReaderLock(); + } + } + + /// + /// Maps a shared memory view on a previously reserved memory region. + /// + /// Shared memory that will be the backing storage for the view + /// Offset in the shared memory to map + /// Address to map the view into + /// Size of the view in bytes + /// Thrown when the Windows API returns an error mapping the memory + private void MapViewInternal(IntPtr sharedMemory, ulong srcOffset, IntPtr location, IntPtr size) + { + SplitForMap((ulong)location, (ulong)size, srcOffset); + + var ptr = WindowsApi.MapViewOfFile3( + sharedMemory, + WindowsApi.CurrentProcessHandle, + location, + srcOffset, + size, + 0x4000, + MemoryProtection.ReadWrite, + IntPtr.Zero, + 0); + + if (ptr == IntPtr.Zero) + { + throw new WindowsApiException("MapViewOfFile3"); + } + } + + /// + /// Splits a larger placeholder, slicing at the start and end address, for a new memory mapping. + /// + /// Address to split + /// Size of the new region + /// Offset in the shared memory that will be mapped + private void SplitForMap(ulong address, ulong size, ulong backingOffset) + { + ulong endAddress = address + size; + + var overlaps = Array.Empty>(); + + lock (_mappings) + { + int count = _mappings.Get(address, endAddress, ref overlaps); + + Debug.Assert(count == 1); + Debug.Assert(!IsMapped(overlaps[0].Value)); + + var overlap = overlaps[0]; + + // Tree operations might modify the node start/end values, so save a copy before we modify the tree. + ulong overlapStart = overlap.Start; + ulong overlapEnd = overlap.End; + ulong overlapValue = overlap.Value; + + _mappings.Remove(overlap); + + bool overlapStartsBefore = overlapStart < address; + bool overlapEndsAfter = overlapEnd > endAddress; + + if (overlapStartsBefore && overlapEndsAfter) + { + CheckFreeResult(WindowsApi.VirtualFree( + (IntPtr)address, + (IntPtr)size, + AllocationType.Release | AllocationType.PreservePlaceholder)); + + _mappings.Add(overlapStart, address, overlapValue); + _mappings.Add(endAddress, overlapEnd, AddBackingOffset(overlapValue, endAddress - overlapStart)); + } + else if (overlapStartsBefore) + { + ulong overlappedSize = overlapEnd - address; + + CheckFreeResult(WindowsApi.VirtualFree( + (IntPtr)address, + (IntPtr)overlappedSize, + AllocationType.Release | AllocationType.PreservePlaceholder)); + + _mappings.Add(overlapStart, address, overlapValue); + } + else if (overlapEndsAfter) + { + ulong overlappedSize = endAddress - overlapStart; + + CheckFreeResult(WindowsApi.VirtualFree( + (IntPtr)overlapStart, + (IntPtr)overlappedSize, + AllocationType.Release | AllocationType.PreservePlaceholder)); + + _mappings.Add(endAddress, overlapEnd, AddBackingOffset(overlapValue, overlappedSize)); + } + + _mappings.Add(address, endAddress, backingOffset); + } + } + + /// + /// Unmaps a view that has been previously mapped with . + /// + /// + /// For "partial unmaps" (when not the entire mapped range is being unmapped), it might be + /// necessary to unmap the whole range and then remap the sub-ranges that should remain mapped. + /// + /// Shared memory that the view being unmapped belongs to + /// Address to unmap + /// Size of the region to unmap in bytes + public void UnmapView(IntPtr sharedMemory, IntPtr location, IntPtr size) + { + _partialUnmapLock.AcquireReaderLock(Timeout.Infinite); + + try + { + UnmapViewInternal(sharedMemory, location, size); + } + finally + { + _partialUnmapLock.ReleaseReaderLock(); + } + } + + /// + /// Unmaps a view that has been previously mapped with . + /// + /// + /// For "partial unmaps" (when not the entire mapped range is being unmapped), it might be + /// necessary to unmap the whole range and then remap the sub-ranges that should remain mapped. + /// + /// Shared memory that the view being unmapped belongs to + /// Address to unmap + /// Size of the region to unmap in bytes + /// Thrown when the Windows API returns an error unmapping or remapping the memory + private void UnmapViewInternal(IntPtr sharedMemory, IntPtr location, IntPtr size) + { + ulong startAddress = (ulong)location; + ulong unmapSize = (ulong)size; + ulong endAddress = startAddress + unmapSize; + + var overlaps = Array.Empty>(); + int count = 0; + + lock (_mappings) + { + count = _mappings.Get(startAddress, endAddress, ref overlaps); + } + + for (int index = 0; index < count; index++) + { + var overlap = overlaps[index]; + + if (IsMapped(overlap.Value)) + { + if (!WindowsApi.UnmapViewOfFile2(WindowsApi.CurrentProcessHandle, (IntPtr)overlap.Start, 2)) + { + throw new WindowsApiException("UnmapViewOfFile2"); + } + + // Tree operations might modify the node start/end values, so save a copy before we modify the tree. + ulong overlapStart = overlap.Start; + ulong overlapEnd = overlap.End; + ulong overlapValue = overlap.Value; + + _mappings.Remove(overlap); + _mappings.Add(overlapStart, overlapEnd, ulong.MaxValue); + + bool overlapStartsBefore = overlapStart < startAddress; + bool overlapEndsAfter = overlapEnd > endAddress; + + if (overlapStartsBefore || overlapEndsAfter) + { + // If the overlap extends beyond the region we are unmapping, + // then we need to re-map the regions that are supposed to remain mapped. + // This is necessary because Windows does not support partial view unmaps. + // That is, you can only fully unmap a view that was previously mapped, you can't just unmap a chunck of it. + + LockCookie lockCookie = _partialUnmapLock.UpgradeToWriterLock(Timeout.Infinite); + + _partialUnmapsCount++; + + if (overlapStartsBefore) + { + ulong remapSize = startAddress - overlapStart; + + MapViewInternal(sharedMemory, overlapValue, (IntPtr)overlapStart, (IntPtr)remapSize); + RestoreRangeProtection(overlapStart, remapSize); + } + + if (overlapEndsAfter) + { + ulong overlappedSize = endAddress - overlapStart; + ulong remapBackingOffset = overlapValue + overlappedSize; + ulong remapAddress = overlapStart + overlappedSize; + ulong remapSize = overlapEnd - endAddress; + + MapViewInternal(sharedMemory, remapBackingOffset, (IntPtr)remapAddress, (IntPtr)remapSize); + RestoreRangeProtection(remapAddress, remapSize); + } + + _partialUnmapLock.DowngradeFromWriterLock(ref lockCookie); + } + } + } + + CoalesceForUnmap(startAddress, unmapSize); + RemoveProtection(startAddress, unmapSize); + } + + /// + /// Coalesces adjacent placeholders after unmap. + /// + /// Address of the region that was unmapped + /// Size of the region that was unmapped in bytes + private void CoalesceForUnmap(ulong address, ulong size) + { + ulong endAddress = address + size; + var overlaps = Array.Empty>(); + int unmappedCount = 0; + + lock (_mappings) + { + int count = _mappings.Get(address - MinimumPageSize, endAddress + MinimumPageSize, ref overlaps); + if (count < 2) + { + // Nothing to coalesce if we only have 1 or no overlaps. + return; + } + + for (int index = 0; index < count; index++) + { + var overlap = overlaps[index]; + + if (!IsMapped(overlap.Value)) + { + if (address > overlap.Start) + { + address = overlap.Start; + } + + if (endAddress < overlap.End) + { + endAddress = overlap.End; + } + + _mappings.Remove(overlap); + + unmappedCount++; + } + } + + _mappings.Add(address, endAddress, ulong.MaxValue); + } + + if (unmappedCount > 1) + { + size = endAddress - address; + + CheckFreeResult(WindowsApi.VirtualFree( + (IntPtr)address, + (IntPtr)size, + AllocationType.Release | AllocationType.CoalescePlaceholders)); + } + } + + /// + /// Reprotects a region of memory that has been mapped. + /// + /// Address of the region to reprotect + /// Size of the region to reprotect in bytes + /// New permissions + /// True if the reprotection was successful, false otherwise + public bool ReprotectView(IntPtr address, IntPtr size, MemoryPermission permission) + { + _partialUnmapLock.AcquireReaderLock(Timeout.Infinite); + + try + { + return ReprotectViewInternal(address, size, permission, false); + } + finally + { + _partialUnmapLock.ReleaseReaderLock(); + } + } + + /// + /// Reprotects a region of memory that has been mapped. + /// + /// Address of the region to reprotect + /// Size of the region to reprotect in bytes + /// New permissions + /// Throw an exception instead of returning an error if the operation fails + /// True if the reprotection was successful or if is true, false otherwise + /// If is true, it is thrown when the Windows API returns an error reprotecting the memory + private bool ReprotectViewInternal(IntPtr address, IntPtr size, MemoryPermission permission, bool throwOnError) + { + ulong reprotectAddress = (ulong)address; + ulong reprotectSize = (ulong)size; + ulong endAddress = reprotectAddress + reprotectSize; + + var overlaps = Array.Empty>(); + int count = 0; + + lock (_mappings) + { + count = _mappings.Get(reprotectAddress, endAddress, ref overlaps); + } + + bool success = true; + + for (int index = 0; index < count; index++) + { + var overlap = overlaps[index]; + + ulong mappedAddress = overlap.Start; + ulong mappedSize = overlap.End - overlap.Start; + + if (mappedAddress < reprotectAddress) + { + ulong delta = reprotectAddress - mappedAddress; + mappedAddress = reprotectAddress; + mappedSize -= delta; + } + + ulong mappedEndAddress = mappedAddress + mappedSize; + + if (mappedEndAddress > endAddress) + { + ulong delta = mappedEndAddress - endAddress; + mappedSize -= delta; + } + + if (!WindowsApi.VirtualProtect((IntPtr)mappedAddress, (IntPtr)mappedSize, WindowsApi.GetProtection(permission), out _)) + { + if (throwOnError) + { + throw new WindowsApiException("VirtualProtect"); + } + + success = false; + } + + // We only keep track of "non-standard" protections, + // that is, everything that is not just RW (which is the default when views are mapped). + if (permission == MemoryPermission.ReadAndWrite) + { + RemoveProtection(mappedAddress, mappedSize); + } + else + { + AddProtection(mappedAddress, mappedSize, permission); + } + } + + return success; + } + + /// + /// Checks the result of a VirtualFree operation, throwing if needed. + /// + /// Operation result + /// Thrown if is false + private static void CheckFreeResult(bool success) + { + if (!success) + { + throw new WindowsApiException("VirtualFree"); + } + } + + /// + /// Adds an offset to a backing offset. This will do nothing if the backing offset is the special "unmapped" value. + /// + /// Backing offset + /// Offset to be added + /// Added offset or just if the region is unmapped + private static ulong AddBackingOffset(ulong backingOffset, ulong offset) + { + if (backingOffset == ulong.MaxValue) + { + return backingOffset; + } + + return backingOffset + offset; + } + + /// + /// Checks if a region is unmapped. + /// + /// Backing offset to check + /// True if the backing offset is the special "unmapped" value, false otherwise + private static bool IsMapped(ulong backingOffset) + { + return backingOffset != ulong.MaxValue; + } + + /// + /// Adds a protection to the list of protections. + /// + /// Address of the protected region + /// Size of the protected region in bytes + /// Memory permissions of the region + private void AddProtection(ulong address, ulong size, MemoryPermission permission) + { + ulong endAddress = address + size; + var overlaps = Array.Empty>(); + int count = 0; + + lock (_protections) + { + count = _protections.Get(address, endAddress, ref overlaps); + + Debug.Assert(count > 0); + + if (count == 1 && + overlaps[0].Start <= address && + overlaps[0].End >= endAddress && + overlaps[0].Value == permission) + { + return; + } + + ulong startAddress = address; + + for (int index = 0; index < count; index++) + { + var protection = overlaps[index]; + + ulong protAddress = protection.Start; + ulong protEndAddress = protection.End; + MemoryPermission protPermission = protection.Value; + + _protections.Remove(protection); + + if (protection.Value == permission) + { + if (startAddress > protAddress) + { + startAddress = protAddress; + } + + if (endAddress < protEndAddress) + { + endAddress = protEndAddress; + } + } + else + { + if (startAddress > protAddress) + { + _protections.Add(protAddress, startAddress, protPermission); + } + + if (endAddress < protEndAddress) + { + _protections.Add(endAddress, protEndAddress, protPermission); + } + } + } + + _protections.Add(startAddress, endAddress, permission); + } + } + + /// + /// Removes protection from the list of protections. + /// + /// Address of the protected region + /// Size of the protected region in bytes + private void RemoveProtection(ulong address, ulong size) + { + ulong endAddress = address + size; + var overlaps = Array.Empty>(); + int count = 0; + + lock (_protections) + { + count = _protections.Get(address, endAddress, ref overlaps); + + for (int index = 0; index < count; index++) + { + var protection = overlaps[index]; + + ulong protAddress = protection.Start; + ulong protEndAddress = protection.End; + MemoryPermission protPermission = protection.Value; + + _protections.Remove(protection); + + if (address > protAddress) + { + _protections.Add(protAddress, address, protPermission); + } + + if (endAddress < protEndAddress) + { + _protections.Add(endAddress, protEndAddress, protPermission); + } + } + } + } + + /// + /// Restores the protection of a given memory region that was remapped, using the protections list. + /// + /// Address of the remapped region + /// Size of the remapped region in bytes + private void RestoreRangeProtection(ulong address, ulong size) + { + ulong endAddress = address + size; + var overlaps = Array.Empty>(); + int count = 0; + + lock (_protections) + { + count = _protections.Get(address, endAddress, ref overlaps); + } + + ulong startAddress = address; + + for (int index = 0; index < count; index++) + { + var protection = overlaps[index]; + + ulong protAddress = protection.Start; + ulong protEndAddress = protection.End; + + if (protAddress < address) + { + protAddress = address; + } + + if (protEndAddress > endAddress) + { + protEndAddress = endAddress; + } + + ReprotectViewInternal((IntPtr)protAddress, (IntPtr)(protEndAddress - protAddress), protection.Value, true); + } + } + + /// + /// Checks if an access violation handler should retry execution due to a fault caused by partial unmap. + /// + /// + /// Due to Windows limitations, might need to unmap more memory than requested. + /// The additional memory that was unmapped is later remapped, however this leaves a time gap where the + /// memory might be accessed but is unmapped. Users of the API must compensate for that by catching the + /// access violation and retrying if it happened between the unmap and remap operation. + /// This method can be used to decide if retrying in such cases is necessary or not. + /// + /// True if execution should be retried, false otherwise + public bool RetryFromAccessViolation() + { + _partialUnmapLock.AcquireReaderLock(Timeout.Infinite); + + bool retry = _threadLocalPartialUnmapsCount != _partialUnmapsCount; + if (retry) + { + _threadLocalPartialUnmapsCount = _partialUnmapsCount; + } + + _partialUnmapLock.ReleaseReaderLock(); + + return retry; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Memory/WindowsShared/WindowsApi.cs b/Ryujinx.Memory/WindowsShared/WindowsApi.cs new file mode 100644 index 000000000..297bd1eee --- /dev/null +++ b/Ryujinx.Memory/WindowsShared/WindowsApi.cs @@ -0,0 +1,93 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Memory.WindowsShared +{ + static class WindowsApi + { + public static readonly IntPtr InvalidHandleValue = new IntPtr(-1); + public static readonly IntPtr CurrentProcessHandle = new IntPtr(-1); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr VirtualAlloc( + IntPtr lpAddress, + IntPtr dwSize, + AllocationType flAllocationType, + MemoryProtection flProtect); + + [DllImport("KernelBase.dll", SetLastError = true)] + public static extern IntPtr VirtualAlloc2( + IntPtr process, + IntPtr lpAddress, + IntPtr dwSize, + AllocationType flAllocationType, + MemoryProtection flProtect, + IntPtr extendedParameters, + ulong parameterCount); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool VirtualProtect( + IntPtr lpAddress, + IntPtr dwSize, + MemoryProtection flNewProtect, + out MemoryProtection lpflOldProtect); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool VirtualFree(IntPtr lpAddress, IntPtr dwSize, AllocationType dwFreeType); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr CreateFileMapping( + IntPtr hFile, + IntPtr lpFileMappingAttributes, + FileMapProtection flProtect, + uint dwMaximumSizeHigh, + uint dwMaximumSizeLow, + [MarshalAs(UnmanagedType.LPWStr)] string lpName); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool CloseHandle(IntPtr hObject); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr MapViewOfFile( + IntPtr hFileMappingObject, + uint dwDesiredAccess, + uint dwFileOffsetHigh, + uint dwFileOffsetLow, + IntPtr dwNumberOfBytesToMap); + + [DllImport("KernelBase.dll", SetLastError = true)] + public static extern IntPtr MapViewOfFile3( + IntPtr hFileMappingObject, + IntPtr process, + IntPtr baseAddress, + ulong offset, + IntPtr dwNumberOfBytesToMap, + ulong allocationType, + MemoryProtection dwDesiredAccess, + IntPtr extendedParameters, + ulong parameterCount); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool UnmapViewOfFile(IntPtr lpBaseAddress); + + [DllImport("KernelBase.dll", SetLastError = true)] + public static extern bool UnmapViewOfFile2(IntPtr process, IntPtr lpBaseAddress, ulong unmapFlags); + + [DllImport("kernel32.dll")] + public static extern uint GetLastError(); + + public static MemoryProtection GetProtection(MemoryPermission permission) + { + return permission switch + { + MemoryPermission.None => MemoryProtection.NoAccess, + MemoryPermission.Read => MemoryProtection.ReadOnly, + MemoryPermission.ReadAndWrite => MemoryProtection.ReadWrite, + MemoryPermission.ReadAndExecute => MemoryProtection.ExecuteRead, + MemoryPermission.ReadWriteExecute => MemoryProtection.ExecuteReadWrite, + MemoryPermission.Execute => MemoryProtection.Execute, + _ => throw new MemoryProtectionException(permission) + }; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Memory/WindowsShared/WindowsApiException.cs b/Ryujinx.Memory/WindowsShared/WindowsApiException.cs new file mode 100644 index 000000000..3140d705b --- /dev/null +++ b/Ryujinx.Memory/WindowsShared/WindowsApiException.cs @@ -0,0 +1,24 @@ +using System; + +namespace Ryujinx.Memory.WindowsShared +{ + class WindowsApiException : Exception + { + public WindowsApiException() + { + } + + public WindowsApiException(string functionName) : base(CreateMessage(functionName)) + { + } + + public WindowsApiException(string functionName, Exception inner) : base(CreateMessage(functionName), inner) + { + } + + private static string CreateMessage(string functionName) + { + return $"{functionName} returned error code 0x{WindowsApi.GetLastError():X}."; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Tests/Cpu/CpuTest.cs b/Ryujinx.Tests/Cpu/CpuTest.cs index 26f0d286c..9cee54970 100644 --- a/Ryujinx.Tests/Cpu/CpuTest.cs +++ b/Ryujinx.Tests/Cpu/CpuTest.cs @@ -54,9 +54,9 @@ namespace Ryujinx.Tests.Cpu _currAddress = CodeBaseAddress; _ram = new MemoryBlock(Size * 2); - _memory = new MemoryManager(1ul << 16); + _memory = new MemoryManager(_ram, 1ul << 16); _memory.IncrementReferenceCount(); - _memory.Map(CodeBaseAddress, _ram.GetPointer(0, Size * 2), Size * 2); + _memory.Map(CodeBaseAddress, 0, Size * 2); _context = CpuContext.CreateExecutionContext(); Translator.IsReadyForTranslation.Set(); diff --git a/Ryujinx.Tests/Cpu/CpuTest32.cs b/Ryujinx.Tests/Cpu/CpuTest32.cs index 2176e5adf..d5dc18eac 100644 --- a/Ryujinx.Tests/Cpu/CpuTest32.cs +++ b/Ryujinx.Tests/Cpu/CpuTest32.cs @@ -49,9 +49,9 @@ namespace Ryujinx.Tests.Cpu _currAddress = CodeBaseAddress; _ram = new MemoryBlock(Size * 2); - _memory = new MemoryManager(1ul << 16); + _memory = new MemoryManager(_ram, 1ul << 16); _memory.IncrementReferenceCount(); - _memory.Map(CodeBaseAddress, _ram.GetPointer(0, Size * 2), Size * 2); + _memory.Map(CodeBaseAddress, 0, Size * 2); _context = CpuContext.CreateExecutionContext(); _context.IsAarch32 = true; From 1cbca5eecbb6b7bce94dca864b5cffda4db02d39 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Tue, 3 May 2022 08:16:31 -0300 Subject: [PATCH 02/33] Implement code memory syscalls (#2958) * Implement code memory syscalls * Remove owner process validation * Add 32-bit code memory syscalls * Remove unused field --- Ryujinx.HLE/HOS/Kernel/Memory/KCodeMemory.cs | 168 +++++++++++ .../HOS/Kernel/Memory/KPageTableBase.cs | 146 +++++++++ .../SupervisorCall/CodeMemoryOperation.cs | 10 + .../HOS/Kernel/SupervisorCall/Syscall.cs | 279 +++++++++++++++--- .../HOS/Kernel/SupervisorCall/Syscall32.cs | 62 +++- .../HOS/Kernel/SupervisorCall/Syscall64.cs | 30 +- .../HOS/Kernel/SupervisorCall/SyscallTable.cs | 8 + 7 files changed, 647 insertions(+), 56 deletions(-) create mode 100644 Ryujinx.HLE/HOS/Kernel/Memory/KCodeMemory.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/SupervisorCall/CodeMemoryOperation.cs diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KCodeMemory.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KCodeMemory.cs new file mode 100644 index 000000000..2df93d789 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KCodeMemory.cs @@ -0,0 +1,168 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using System; +using System.Diagnostics; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KCodeMemory : KAutoObject + { + public KProcess Owner { get; private set; } + private readonly KPageList _pageList; + private readonly object _lock; + private ulong _address; + private bool _isOwnerMapped; + private bool _isMapped; + + public KCodeMemory(KernelContext context) : base(context) + { + _pageList = new KPageList(); + _lock = new object(); + } + + public KernelResult Initialize(ulong address, ulong size) + { + Owner = KernelStatic.GetCurrentProcess(); + + KernelResult result = Owner.MemoryManager.BorrowCodeMemory(_pageList, address, size); + + if (result != KernelResult.Success) + { + return result; + } + + Owner.CpuMemory.Fill(address, size, 0xff); + Owner.IncrementReferenceCount(); + + _address = address; + _isMapped = false; + _isOwnerMapped = false; + + return KernelResult.Success; + } + + public KernelResult Map(ulong address, ulong size, KMemoryPermission perm) + { + if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize)) + { + return KernelResult.InvalidSize; + } + + lock (_lock) + { + if (_isMapped) + { + return KernelResult.InvalidState; + } + + KProcess process = KernelStatic.GetCurrentProcess(); + + KernelResult result = process.MemoryManager.MapPages(address, _pageList, MemoryState.CodeWritable, KMemoryPermission.ReadAndWrite); + + if (result != KernelResult.Success) + { + return result; + } + + _isMapped = true; + } + + return KernelResult.Success; + } + + public KernelResult MapToOwner(ulong address, ulong size, KMemoryPermission permission) + { + if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize)) + { + return KernelResult.InvalidSize; + } + + lock (_lock) + { + if (_isOwnerMapped) + { + return KernelResult.InvalidState; + } + + Debug.Assert(permission == KMemoryPermission.Read || permission == KMemoryPermission.ReadAndExecute); + + KernelResult result = Owner.MemoryManager.MapPages(address, _pageList, MemoryState.CodeReadOnly, permission); + + if (result != KernelResult.Success) + { + return result; + } + + _isOwnerMapped = true; + } + + return KernelResult.Success; + } + + public KernelResult Unmap(ulong address, ulong size) + { + if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize)) + { + return KernelResult.InvalidSize; + } + + lock (_lock) + { + KProcess process = KernelStatic.GetCurrentProcess(); + + KernelResult result = process.MemoryManager.UnmapPages(address, _pageList, MemoryState.CodeWritable); + + if (result != KernelResult.Success) + { + return result; + } + + Debug.Assert(_isMapped); + + _isMapped = false; + } + + return KernelResult.Success; + } + + public KernelResult UnmapFromOwner(ulong address, ulong size) + { + if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize)) + { + return KernelResult.InvalidSize; + } + + lock (_lock) + { + KernelResult result = Owner.MemoryManager.UnmapPages(address, _pageList, MemoryState.CodeReadOnly); + + if (result != KernelResult.Success) + { + return result; + } + + Debug.Assert(_isOwnerMapped); + + _isOwnerMapped = false; + } + + return KernelResult.Success; + } + + protected override void Destroy() + { + if (!_isMapped && !_isOwnerMapped) + { + ulong size = _pageList.GetPagesCount() * KPageTableBase.PageSize; + + if (Owner.MemoryManager.UnborrowCodeMemory(_address, size, _pageList) != KernelResult.Success) + { + throw new InvalidOperationException("Unexpected failure restoring transfer memory attributes."); + } + } + + Owner.DecrementReferenceCount(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs index 518a0f096..94e8fb6a1 100644 --- a/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs @@ -1095,6 +1095,77 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory } } + public KernelResult UnmapProcessMemory(ulong dst, ulong size, KPageTableBase srcPageTable, ulong src) + { + lock (_blockManager) + { + lock (srcPageTable._blockManager) + { + bool success = CheckRange( + dst, + size, + MemoryState.Mask, + MemoryState.ProcessMemory, + KMemoryPermission.ReadAndWrite, + KMemoryPermission.ReadAndWrite, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _); + + success &= srcPageTable.CheckRange( + src, + size, + MemoryState.MapProcessAllowed, + MemoryState.MapProcessAllowed, + KMemoryPermission.None, + KMemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _); + + if (!success) + { + return KernelResult.InvalidMemState; + } + + KPageList srcPageList = new KPageList(); + KPageList dstPageList = new KPageList(); + + srcPageTable.GetPhysicalRegions(src, size, srcPageList); + GetPhysicalRegions(dst, size, dstPageList); + + if (!dstPageList.IsEqual(srcPageList)) + { + return KernelResult.InvalidMemRange; + } + } + + if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong pagesCount = size / PageSize; + + KernelResult result = Unmap(dst, pagesCount); + + if (result != KernelResult.Success) + { + return result; + } + + _blockManager.InsertBlock(dst, pagesCount, MemoryState.Unmapped); + + return KernelResult.Success; + } + } + public KernelResult SetProcessMemoryPermission(ulong address, ulong size, KMemoryPermission permission) { lock (_blockManager) @@ -2023,6 +2094,49 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory block.RestoreIpcMappingPermission(); } + public KernelResult GetPagesIfStateEquals( + ulong address, + ulong size, + MemoryState stateMask, + MemoryState stateExpected, + KMemoryPermission permissionMask, + KMemoryPermission permissionExpected, + MemoryAttribute attributeMask, + MemoryAttribute attributeExpected, + KPageList pageList) + { + if (!InsideAddrSpace(address, size)) + { + return KernelResult.InvalidMemState; + } + + lock (_blockManager) + { + if (CheckRange( + address, + size, + stateMask | MemoryState.IsPoolAllocated, + stateExpected | MemoryState.IsPoolAllocated, + permissionMask, + permissionExpected, + attributeMask, + attributeExpected, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _)) + { + GetPhysicalRegions(address, size, pageList); + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + public KernelResult BorrowIpcBuffer(ulong address, ulong size) { return SetAttributesAndChangePermission( @@ -2054,6 +2168,22 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory pageList); } + public KernelResult BorrowCodeMemory(KPageList pageList, ulong address, ulong size) + { + return SetAttributesAndChangePermission( + address, + size, + MemoryState.CodeMemoryAllowed, + MemoryState.CodeMemoryAllowed, + KMemoryPermission.Mask, + KMemoryPermission.ReadAndWrite, + MemoryAttribute.Mask, + MemoryAttribute.None, + KMemoryPermission.None, + MemoryAttribute.Borrowed, + pageList); + } + private KernelResult SetAttributesAndChangePermission( ulong address, ulong size, @@ -2159,6 +2289,22 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory pageList); } + public KernelResult UnborrowCodeMemory(ulong address, ulong size, KPageList pageList) + { + return ClearAttributesAndChangePermission( + address, + size, + MemoryState.CodeMemoryAllowed, + MemoryState.CodeMemoryAllowed, + KMemoryPermission.None, + KMemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.Borrowed, + KMemoryPermission.ReadAndWrite, + MemoryAttribute.Borrowed, + pageList); + } + private KernelResult ClearAttributesAndChangePermission( ulong address, ulong size, diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/CodeMemoryOperation.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/CodeMemoryOperation.cs new file mode 100644 index 000000000..511ee99af --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/CodeMemoryOperation.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + enum CodeMemoryOperation : uint + { + Map, + MapToOwner, + Unmap, + UnmapFromOwner + }; +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs index 3c6e2586d..0c111bc44 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs @@ -1317,6 +1317,248 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return process.MemoryManager.UnmapPhysicalMemory(address, size); } + public KernelResult CreateCodeMemory(ulong address, ulong size, out int handle) + { + handle = 0; + + if (!PageAligned(address)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (size + address <= address) + { + return KernelResult.InvalidMemState; + } + + KCodeMemory codeMemory = new KCodeMemory(_context); + + using var _ = new OnScopeExit(codeMemory.DecrementReferenceCount); + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (!currentProcess.MemoryManager.InsideAddrSpace(address, size)) + { + return KernelResult.InvalidMemState; + } + + KernelResult result = codeMemory.Initialize(address, size); + + if (result != KernelResult.Success) + { + return result; + } + + return currentProcess.HandleTable.GenerateHandle(codeMemory, out handle); + } + + public KernelResult ControlCodeMemory(int handle, CodeMemoryOperation op, ulong address, ulong size, KMemoryPermission permission) + { + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + KCodeMemory codeMemory = currentProcess.HandleTable.GetObject(handle); + + // Newer versions of the return also returns an error here if the owner and process + // where the operation will happen are the same. We do not return an error here + // because some homebrew requires this to be patched out to work (for JIT). + if (codeMemory == null /* || codeMemory.Owner == currentProcess */) + { + return KernelResult.InvalidHandle; + } + + switch (op) + { + case CodeMemoryOperation.Map: + if (!currentProcess.MemoryManager.CanContain(address, size, MemoryState.CodeWritable)) + { + return KernelResult.InvalidMemRange; + } + + if (permission != KMemoryPermission.ReadAndWrite) + { + return KernelResult.InvalidPermission; + } + + return codeMemory.Map(address, size, permission); + + case CodeMemoryOperation.MapToOwner: + if (!currentProcess.MemoryManager.CanContain(address, size, MemoryState.CodeReadOnly)) + { + return KernelResult.InvalidMemRange; + } + + if (permission != KMemoryPermission.Read && permission != KMemoryPermission.ReadAndExecute) + { + return KernelResult.InvalidPermission; + } + + return codeMemory.MapToOwner(address, size, permission); + + case CodeMemoryOperation.Unmap: + if (!currentProcess.MemoryManager.CanContain(address, size, MemoryState.CodeWritable)) + { + return KernelResult.InvalidMemRange; + } + + if (permission != KMemoryPermission.None) + { + return KernelResult.InvalidPermission; + } + + return codeMemory.Unmap(address, size); + + case CodeMemoryOperation.UnmapFromOwner: + if (!currentProcess.MemoryManager.CanContain(address, size, MemoryState.CodeReadOnly)) + { + return KernelResult.InvalidMemRange; + } + + if (permission != KMemoryPermission.None) + { + return KernelResult.InvalidPermission; + } + + return codeMemory.UnmapFromOwner(address, size); + + default: return KernelResult.InvalidEnumValue; + } + } + + public KernelResult SetProcessMemoryPermission(int handle, ulong src, ulong size, KMemoryPermission permission) + { + if (!PageAligned(src)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (permission != KMemoryPermission.None && + permission != KMemoryPermission.Read && + permission != KMemoryPermission.ReadAndWrite && + permission != KMemoryPermission.ReadAndExecute) + { + return KernelResult.InvalidPermission; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + KProcess targetProcess = currentProcess.HandleTable.GetObject(handle); + + if (targetProcess == null) + { + return KernelResult.InvalidHandle; + } + + if (targetProcess.MemoryManager.OutsideAddrSpace(src, size)) + { + return KernelResult.InvalidMemState; + } + + return targetProcess.MemoryManager.SetProcessMemoryPermission(src, size, permission); + } + + public KernelResult MapProcessMemory(ulong dst, int handle, ulong src, ulong size) + { + if (!PageAligned(src) || !PageAligned(dst)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (dst + size <= dst || src + size <= src) + { + return KernelResult.InvalidMemRange; + } + + KProcess dstProcess = KernelStatic.GetCurrentProcess(); + KProcess srcProcess = dstProcess.HandleTable.GetObject(handle); + + if (srcProcess == null) + { + return KernelResult.InvalidHandle; + } + + if (!srcProcess.MemoryManager.InsideAddrSpace(src, size) || + !dstProcess.MemoryManager.CanContain(dst, size, MemoryState.ProcessMemory)) + { + return KernelResult.InvalidMemRange; + } + + KPageList pageList = new KPageList(); + + KernelResult result = srcProcess.MemoryManager.GetPagesIfStateEquals( + src, + size, + MemoryState.MapProcessAllowed, + MemoryState.MapProcessAllowed, + KMemoryPermission.None, + KMemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + pageList); + + if (result != KernelResult.Success) + { + return result; + } + + return dstProcess.MemoryManager.MapPages(dst, pageList, MemoryState.ProcessMemory, KMemoryPermission.ReadAndWrite); + } + + public KernelResult UnmapProcessMemory(ulong dst, int handle, ulong src, ulong size) + { + if (!PageAligned(src) || !PageAligned(dst)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (dst + size <= dst || src + size <= src) + { + return KernelResult.InvalidMemRange; + } + + KProcess dstProcess = KernelStatic.GetCurrentProcess(); + KProcess srcProcess = dstProcess.HandleTable.GetObject(handle); + + if (srcProcess == null) + { + return KernelResult.InvalidHandle; + } + + if (!srcProcess.MemoryManager.InsideAddrSpace(src, size) || + !dstProcess.MemoryManager.CanContain(dst, size, MemoryState.ProcessMemory)) + { + return KernelResult.InvalidMemRange; + } + + KernelResult result = dstProcess.MemoryManager.UnmapProcessMemory(dst, size, srcProcess.MemoryManager, src); + + if (result != KernelResult.Success) + { + return result; + } + + return KernelResult.Success; + } + public KernelResult MapProcessCodeMemory(int handle, ulong dst, ulong src, ulong size) { if (!PageAligned(dst) || !PageAligned(src)) @@ -1391,43 +1633,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return targetProcess.MemoryManager.UnmapProcessCodeMemory(dst, src, size); } - public KernelResult SetProcessMemoryPermission(int handle, ulong src, ulong size, KMemoryPermission permission) - { - if (!PageAligned(src)) - { - return KernelResult.InvalidAddress; - } - - if (!PageAligned(size) || size == 0) - { - return KernelResult.InvalidSize; - } - - if (permission != KMemoryPermission.None && - permission != KMemoryPermission.Read && - permission != KMemoryPermission.ReadAndWrite && - permission != KMemoryPermission.ReadAndExecute) - { - return KernelResult.InvalidPermission; - } - - KProcess currentProcess = KernelStatic.GetCurrentProcess(); - - KProcess targetProcess = currentProcess.HandleTable.GetObject(handle); - - if (targetProcess == null) - { - return KernelResult.InvalidHandle; - } - - if (targetProcess.MemoryManager.OutsideAddrSpace(src, size)) - { - return KernelResult.InvalidMemState; - } - - return targetProcess.MemoryManager.SetProcessMemoryPermission(src, size, permission); - } - private static bool PageAligned(ulong address) { return (address & (KPageTableBase.PageSize - 1)) == 0; diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall32.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall32.cs index bf4eee797..27ff8ef7f 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall32.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall32.cs @@ -143,6 +143,26 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return _syscall.CreateTransferMemory(out handle, address, size, permission); } + public KernelResult CreateCodeMemory32([R(1)] uint address, [R(2)] uint size, [R(1)] out int handle) + { + return _syscall.CreateCodeMemory(address, size, out handle); + } + + public KernelResult ControlCodeMemory32( + [R(0)] int handle, + [R(1)] CodeMemoryOperation op, + [R(2)] uint addressLow, + [R(3)] uint addressHigh, + [R(4)] uint sizeLow, + [R(5)] uint sizeHigh, + [R(6)] KMemoryPermission permission) + { + ulong address = addressLow | ((ulong)addressHigh << 32); + ulong size = sizeLow | ((ulong)sizeHigh << 32); + + return _syscall.ControlCodeMemory(handle, op, address, size, permission); + } + public KernelResult MapTransferMemory32([R(0)] int handle, [R(1)] uint address, [R(2)] uint size, [R(3)] KMemoryPermission permission) { return _syscall.MapTransferMemory(handle, address, size, permission); @@ -163,6 +183,34 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return _syscall.UnmapPhysicalMemory(address, size); } + public KernelResult SetProcessMemoryPermission32( + [R(0)] int handle, + [R(1)] uint sizeLow, + [R(2)] uint srcLow, + [R(3)] uint srcHigh, + [R(4)] uint sizeHigh, + [R(5)] KMemoryPermission permission) + { + ulong src = srcLow | ((ulong)srcHigh << 32); + ulong size = sizeLow | ((ulong)sizeHigh << 32); + + return _syscall.SetProcessMemoryPermission(handle, src, size, permission); + } + + public KernelResult MapProcessMemory32([R(0)] uint dst, [R(1)] int handle, [R(2)] uint srcLow, [R(3)] uint srcHigh, [R(4)] uint size) + { + ulong src = srcLow | ((ulong)srcHigh << 32); + + return _syscall.MapProcessMemory(dst, handle, src, size); + } + + public KernelResult UnmapProcessMemory32([R(0)] uint dst, [R(1)] int handle, [R(2)] uint srcLow, [R(3)] uint srcHigh, [R(4)] uint size) + { + ulong src = srcLow | ((ulong)srcHigh << 32); + + return _syscall.UnmapProcessMemory(dst, handle, src, size); + } + public KernelResult MapProcessCodeMemory32([R(0)] int handle, [R(1)] uint srcLow, [R(2)] uint dstLow, [R(3)] uint dstHigh, [R(4)] uint srcHigh, [R(5)] uint sizeLow, [R(6)] uint sizeHigh) { ulong src = srcLow | ((ulong)srcHigh << 32); @@ -181,20 +229,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return _syscall.UnmapProcessCodeMemory(handle, dst, src, size); } - public KernelResult SetProcessMemoryPermission32( - [R(0)] int handle, - [R(1)] uint sizeLow, - [R(2)] uint srcLow, - [R(3)] uint srcHigh, - [R(4)] uint sizeHigh, - [R(5)] KMemoryPermission permission) - { - ulong src = srcLow | ((ulong)srcHigh << 32); - ulong size = sizeLow | ((ulong)sizeHigh << 32); - - return _syscall.SetProcessMemoryPermission(handle, src, size, permission); - } - // System public void ExitProcess32() diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall64.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall64.cs index 2af736d8a..76e853799 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall64.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall64.cs @@ -160,6 +160,16 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return _syscall.CreateTransferMemory(out handle, address, size, permission); } + public KernelResult CreateCodeMemory64([R(1)] ulong address, [R(2)] ulong size, [R(1)] out int handle) + { + return _syscall.CreateCodeMemory(address, size, out handle); + } + + public KernelResult ControlCodeMemory64([R(0)] int handle, [R(1)] CodeMemoryOperation op, [R(2)] ulong address, [R(3)] ulong size, [R(4)] KMemoryPermission permission) + { + return _syscall.ControlCodeMemory(handle, op, address, size, permission); + } + public KernelResult MapTransferMemory64([R(0)] int handle, [R(1)] ulong address, [R(2)] ulong size, [R(3)] KMemoryPermission permission) { return _syscall.MapTransferMemory(handle, address, size, permission); @@ -180,6 +190,21 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return _syscall.UnmapPhysicalMemory(address, size); } + public KernelResult SetProcessMemoryPermission64([R(0)] int handle, [R(1)] ulong src, [R(2)] ulong size, [R(3)] KMemoryPermission permission) + { + return _syscall.SetProcessMemoryPermission(handle, src, size, permission); + } + + public KernelResult MapProcessMemory64([R(0)] ulong dst, [R(1)] int handle, [R(2)] ulong src, [R(3)] ulong size) + { + return _syscall.MapProcessMemory(dst, handle, src, size); + } + + public KernelResult UnmapProcessMemory64([R(0)] ulong dst, [R(1)] int handle, [R(2)] ulong src, [R(3)] ulong size) + { + return _syscall.UnmapProcessMemory(dst, handle, src, size); + } + public KernelResult MapProcessCodeMemory64([R(0)] int handle, [R(1)] ulong dst, [R(2)] ulong src, [R(3)] ulong size) { return _syscall.MapProcessCodeMemory(handle, dst, src, size); @@ -190,11 +215,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return _syscall.UnmapProcessCodeMemory(handle, dst, src, size); } - public KernelResult SetProcessMemoryPermission64([R(0)] int handle, [R(1)] ulong src, [R(2)] ulong size, [R(3)] KMemoryPermission permission) - { - return _syscall.SetProcessMemoryPermission(handle, src, size, permission); - } - // System public void ExitProcess64() diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallTable.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallTable.cs index 29e7ce160..6e0b70100 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallTable.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallTable.cs @@ -78,6 +78,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall { 0x43, nameof(Syscall64.ReplyAndReceive64) }, { 0x44, nameof(Syscall64.ReplyAndReceiveWithUserBuffer64) }, { 0x45, nameof(Syscall64.CreateEvent64) }, + { 0x4b, nameof(Syscall64.CreateCodeMemory64) }, + { 0x4c, nameof(Syscall64.ControlCodeMemory64) }, { 0x51, nameof(Syscall64.MapTransferMemory64) }, { 0x52, nameof(Syscall64.UnmapTransferMemory64) }, { 0x65, nameof(Syscall64.GetProcessList64) }, @@ -86,6 +88,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall { 0x71, nameof(Syscall64.ManageNamedPort64) }, { 0x72, nameof(Syscall64.ConnectToPort64) }, { 0x73, nameof(Syscall64.SetProcessMemoryPermission64) }, + { 0x74, nameof(Syscall64.MapProcessMemory64) }, + { 0x75, nameof(Syscall64.UnmapProcessMemory64) }, { 0x77, nameof(Syscall64.MapProcessCodeMemory64) }, { 0x78, nameof(Syscall64.UnmapProcessCodeMemory64) }, { 0x7B, nameof(Syscall64.TerminateProcess64) }, @@ -152,6 +156,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall { 0x41, nameof(Syscall32.AcceptSession32) }, { 0x43, nameof(Syscall32.ReplyAndReceive32) }, { 0x45, nameof(Syscall32.CreateEvent32) }, + { 0x4b, nameof(Syscall32.CreateCodeMemory32) }, + { 0x4c, nameof(Syscall32.ControlCodeMemory32) }, { 0x51, nameof(Syscall32.MapTransferMemory32) }, { 0x52, nameof(Syscall32.UnmapTransferMemory32) }, { 0x5F, nameof(Syscall32.FlushProcessDataCache32) }, @@ -161,6 +167,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall { 0x71, nameof(Syscall32.ManageNamedPort32) }, { 0x72, nameof(Syscall32.ConnectToPort32) }, { 0x73, nameof(Syscall32.SetProcessMemoryPermission32) }, + { 0x74, nameof(Syscall32.MapProcessMemory32) }, + { 0x75, nameof(Syscall32.UnmapProcessMemory32) }, { 0x77, nameof(Syscall32.MapProcessCodeMemory32) }, { 0x78, nameof(Syscall32.UnmapProcessCodeMemory32) }, { 0x7B, nameof(Syscall32.TerminateProcess32) }, From 556be08c4e9ad9f508dc2647ef70d3b89c2c242c Mon Sep 17 00:00:00 2001 From: gdkchan Date: Tue, 3 May 2022 18:28:32 -0300 Subject: [PATCH 03/33] Implement PM GetProcessInfo atmosphere extension (partially) (#2966) --- Ryujinx.HLE/HOS/ApplicationLoader.cs | 16 +++++++----- Ryujinx.HLE/HOS/Kernel/KernelStatic.cs | 10 ++++++++ Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs | 6 ++++- .../HOS/Kernel/SupervisorCall/Syscall.cs | 7 +++--- Ryujinx.HLE/HOS/ProgramLoader.cs | 14 ++++++++--- .../HOS/Services/Pm/IDebugMonitorInterface.cs | 25 ++++++++++++++++++- 6 files changed, 63 insertions(+), 15 deletions(-) diff --git a/Ryujinx.HLE/HOS/ApplicationLoader.cs b/Ryujinx.HLE/HOS/ApplicationLoader.cs index 80d609b66..b3d23ea7b 100644 --- a/Ryujinx.HLE/HOS/ApplicationLoader.cs +++ b/Ryujinx.HLE/HOS/ApplicationLoader.cs @@ -86,8 +86,8 @@ namespace Ryujinx.HLE.HOS MetaLoader metaData = ReadNpdm(codeFs); _device.Configuration.VirtualFileSystem.ModLoader.CollectMods( - new[] { TitleId }, - _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(), + new[] { TitleId }, + _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(), _device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath()); if (TitleId != 0) @@ -393,8 +393,8 @@ namespace Ryujinx.HLE.HOS MetaLoader metaData = ReadNpdm(codeFs); _device.Configuration.VirtualFileSystem.ModLoader.CollectMods( - _device.Configuration.ContentManager.GetAocTitleIds().Prepend(TitleId), - _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(), + _device.Configuration.ContentManager.GetAocTitleIds().Prepend(TitleId), + _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(), _device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath()); if (controlNca != null) @@ -571,8 +571,12 @@ namespace Ryujinx.HLE.HOS Ptc.Initialize(TitleIdText, DisplayVersion, usePtc, memoryManagerMode); + // We allow it for nx-hbloader because it can be used to launch homebrew. + bool allowCodeMemoryForJit = TitleId == 0x010000000000100DUL; + metaData.GetNpdm(out Npdm npdm).ThrowIfFailure(); - ProgramLoader.LoadNsos(_device.System.KernelContext, out ProcessTamperInfo tamperInfo, metaData, new ProgramInfo(in npdm), executables: programs); + ProgramInfo programInfo = new ProgramInfo(in npdm, allowCodeMemoryForJit); + ProgramLoader.LoadNsos(_device.System.KernelContext, out ProcessTamperInfo tamperInfo, metaData, programInfo, executables: programs); _device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, tamperInfo, _device.TamperMachine); } @@ -581,7 +585,7 @@ namespace Ryujinx.HLE.HOS { MetaLoader metaData = GetDefaultNpdm(); metaData.GetNpdm(out Npdm npdm).ThrowIfFailure(); - ProgramInfo programInfo = new ProgramInfo(in npdm); + ProgramInfo programInfo = new ProgramInfo(in npdm, allowCodeMemoryForJit: true); bool isNro = Path.GetExtension(filePath).ToLower() == ".nro"; diff --git a/Ryujinx.HLE/HOS/Kernel/KernelStatic.cs b/Ryujinx.HLE/HOS/Kernel/KernelStatic.cs index 2446ace4b..625a007dd 100644 --- a/Ryujinx.HLE/HOS/Kernel/KernelStatic.cs +++ b/Ryujinx.HLE/HOS/Kernel/KernelStatic.cs @@ -59,5 +59,15 @@ namespace Ryujinx.HLE.HOS.Kernel { return GetCurrentThread().Owner; } + + internal static KProcess GetProcessByPid(ulong pid) + { + if (Context.Processes.TryGetValue(pid, out KProcess process)) + { + return process; + } + + return null; + } } } diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs index 93ecf2973..b10737b43 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -60,6 +60,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process public KProcessCapabilities Capabilities { get; private set; } + public bool AllowCodeMemoryForJit { get; private set; } + public ulong TitleId { get; private set; } public bool IsApplication { get; private set; } public ulong Pid { get; private set; } @@ -90,7 +92,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process public HleProcessDebugger Debugger { get; private set; } - public KProcess(KernelContext context) : base(context) + public KProcess(KernelContext context, bool allowCodeMemoryForJit = false) : base(context) { _processLock = new object(); _threadingLock = new object(); @@ -102,6 +104,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process Capabilities = new KProcessCapabilities(); + AllowCodeMemoryForJit = allowCodeMemoryForJit; + RandomEntropy = new ulong[KScheduler.CpuCoresCount]; PinnedThreads = new KThread[KScheduler.CpuCoresCount]; diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs index 0c111bc44..d9d492a52 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs @@ -7,7 +7,6 @@ using Ryujinx.HLE.HOS.Kernel.Ipc; using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Threading; -using Ryujinx.Memory; using System; using System.Threading; @@ -1363,10 +1362,10 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall KCodeMemory codeMemory = currentProcess.HandleTable.GetObject(handle); - // Newer versions of the return also returns an error here if the owner and process + // Newer versions of the kernel also returns an error here if the owner and process // where the operation will happen are the same. We do not return an error here - // because some homebrew requires this to be patched out to work (for JIT). - if (codeMemory == null /* || codeMemory.Owner == currentProcess */) + // for homebrew because some of them requires this to be patched out to work (for JIT). + if (codeMemory == null || (!currentProcess.AllowCodeMemoryForJit && codeMemory.Owner == currentProcess)) { return KernelResult.InvalidHandle; } diff --git a/Ryujinx.HLE/HOS/ProgramLoader.cs b/Ryujinx.HLE/HOS/ProgramLoader.cs index 403e0227c..294ca5b82 100644 --- a/Ryujinx.HLE/HOS/ProgramLoader.cs +++ b/Ryujinx.HLE/HOS/ProgramLoader.cs @@ -20,11 +20,13 @@ namespace Ryujinx.HLE.HOS { public string Name; public ulong ProgramId; + public bool AllowCodeMemoryForJit; - public ProgramInfo(in Npdm npdm) + public ProgramInfo(in Npdm npdm, bool allowCodeMemoryForJit) { Name = StringUtils.Utf8ZToString(npdm.Meta.Value.ProgramName); ProgramId = npdm.Aci.Value.ProgramId.Value; + AllowCodeMemoryForJit = allowCodeMemoryForJit; } } @@ -141,7 +143,13 @@ namespace Ryujinx.HLE.HOS return true; } - public static bool LoadNsos(KernelContext context, out ProcessTamperInfo tamperInfo, MetaLoader metaData, ProgramInfo programInfo, byte[] arguments = null, params IExecutable[] executables) + public static bool LoadNsos( + KernelContext context, + out ProcessTamperInfo tamperInfo, + MetaLoader metaData, + ProgramInfo programInfo, + byte[] arguments = null, + params IExecutable[] executables) { LibHac.Result rc = metaData.GetNpdm(out var npdm); @@ -243,7 +251,7 @@ namespace Ryujinx.HLE.HOS return false; } - KProcess process = new KProcess(context); + KProcess process = new KProcess(context, programInfo.AllowCodeMemoryForJit); MemoryRegion memoryRegion = (MemoryRegion)((npdm.Acid.Value.Flags >> 2) & 0xf); diff --git a/Ryujinx.HLE/HOS/Services/Pm/IDebugMonitorInterface.cs b/Ryujinx.HLE/HOS/Services/Pm/IDebugMonitorInterface.cs index 06c119436..4b0df9b7b 100644 --- a/Ryujinx.HLE/HOS/Services/Pm/IDebugMonitorInterface.cs +++ b/Ryujinx.HLE/HOS/Services/Pm/IDebugMonitorInterface.cs @@ -1,8 +1,31 @@ -namespace Ryujinx.HLE.HOS.Services.Pm +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; + +namespace Ryujinx.HLE.HOS.Services.Pm { [Service("pm:dmnt")] class IDebugMonitorInterface : IpcService { public IDebugMonitorInterface(ServiceCtx context) { } + + [CommandHipc(65000)] + // AtmosphereGetProcessInfo(os::ProcessId process_id) -> sf::OutCopyHandle out_process_handle, sf::Out out_loc, sf::Out out_status + public ResultCode GetProcessInfo(ServiceCtx context) + { + ulong pid = context.RequestData.ReadUInt64(); + + KProcess process = KernelStatic.GetProcessByPid(pid); + + if (context.Process.HandleTable.GenerateHandle(process, out int processHandle) != KernelResult.Success) + { + throw new System.Exception("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(processHandle); + + return ResultCode.Success; + } } } \ No newline at end of file From 256514c7c9fd5108213425fcd0fc288321b77a79 Mon Sep 17 00:00:00 2001 From: voldemort2826 <102014910+voldemort2826@users.noreply.github.com> Date: Wed, 4 May 2022 04:35:12 +0700 Subject: [PATCH 04/33] Update the artifact build's version number (#3297) --- .github/workflows/build.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3b922cf30..6e5dcaecf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,6 +46,7 @@ jobs: env: POWERSHELL_TELEMETRY_OPTOUT: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1 + RYUJINX_BASE_VERSION: "1.1.0" steps: - uses: actions/checkout@v2 - uses: actions/setup-dotnet@v1 @@ -59,24 +60,24 @@ jobs: - name: Clear run: dotnet clean && dotnet nuget locals all --clear - name: Build - run: dotnet build -c "${{ matrix.configuration }}" /p:Version="1.1.0" /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER + run: dotnet build -c "${{ matrix.configuration }}" /p:Version="${{ env.RYUJINX_BASE_VERSION }}" /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER - name: Test run: dotnet test -c "${{ matrix.configuration }}" - name: Publish Ryujinx - run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish /p:Version="1.1.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx --self-contained + run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish /p:Version="${{ env.RYUJINX_BASE_VERSION }}" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx --self-contained if: github.event_name == 'pull_request' - name: Publish Ryujinx.Headless.SDL2 - run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless /p:Version="1.1.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Headless.SDL2 --self-contained + run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless /p:Version="${{ env.RYUJINX_BASE_VERSION }}" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Headless.SDL2 --self-contained if: github.event_name == 'pull_request' - name: Upload Ryujinx artifact uses: actions/upload-artifact@v2 with: - name: ryujinx-${{ matrix.configuration }}-1.0.0+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }} + name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }} path: publish if: github.event_name == 'pull_request' - name: Upload Ryujinx.Headless.SDL2 artifact uses: actions/upload-artifact@v2 with: - name: ryujinx-headless-sdl2-${{ matrix.configuration }}-1.0.0+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }} + name: ryujinx-headless-sdl2-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }} path: publish_sdl2_headless if: github.event_name == 'pull_request' From 074190e03c975cab0fdc2be35adfaf0fe831f29b Mon Sep 17 00:00:00 2001 From: gdkchan Date: Wed, 4 May 2022 14:07:10 -0300 Subject: [PATCH 05/33] Remove AddProtection count > 0 assert (#3315) --- Ryujinx.Memory/WindowsShared/PlaceholderManager.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Ryujinx.Memory/WindowsShared/PlaceholderManager.cs b/Ryujinx.Memory/WindowsShared/PlaceholderManager.cs index a16a8c7fa..b0b3bf050 100644 --- a/Ryujinx.Memory/WindowsShared/PlaceholderManager.cs +++ b/Ryujinx.Memory/WindowsShared/PlaceholderManager.cs @@ -469,14 +469,12 @@ namespace Ryujinx.Memory.WindowsShared { ulong endAddress = address + size; var overlaps = Array.Empty>(); - int count = 0; + int count; lock (_protections) { count = _protections.Get(address, endAddress, ref overlaps); - Debug.Assert(count > 0); - if (count == 1 && overlaps[0].Start <= address && overlaps[0].End >= endAddress && @@ -574,7 +572,7 @@ namespace Ryujinx.Memory.WindowsShared { ulong endAddress = address + size; var overlaps = Array.Empty>(); - int count = 0; + int count; lock (_protections) { From 39bdf6d41e6cad370f7a10b766d91418c194ead8 Mon Sep 17 00:00:00 2001 From: Mary Date: Wed, 4 May 2022 20:21:27 +0200 Subject: [PATCH 06/33] infra: Warn about support drop of old Windows versions (#3299) * infra: Warn about support drop of old Windows versions See #3298. * Address comment --- Ryujinx/Program.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs index 26b89d06f..267acefa6 100644 --- a/Ryujinx/Program.cs +++ b/Ryujinx/Program.cs @@ -32,8 +32,20 @@ namespace Ryujinx [DllImport("libX11")] private extern static int XInitThreads(); + [DllImport("user32.dll", SetLastError = true)] + public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type); + + private const uint MB_ICONWARNING = 0x30; + static void Main(string[] args) - { + { + Version = ReleaseInformations.GetVersion(); + + if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134)) + { + MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MB_ICONWARNING); + } + // Parse Arguments. string launchPathArg = null; string baseDirPathArg = null; @@ -82,8 +94,6 @@ namespace Ryujinx // Delete backup files after updating. Task.Run(Updater.CleanupUpdate); - Version = ReleaseInformations.GetVersion(); - Console.Title = $"Ryujinx Console {Version}"; // NOTE: GTK3 doesn't init X11 in a multi threaded way. From 54deded929203a64555d97424d5bb4b884fff69f Mon Sep 17 00:00:00 2001 From: gdkchan Date: Thu, 5 May 2022 14:58:59 -0300 Subject: [PATCH 07/33] Fix shared memory leak on Windows (#3319) * Fix shared memory leak on Windows * Fix memory leak caused by RO session disposal not decrementing the memory manager ref count * Fix UnmapViewInternal deadlock * Was not supposed to add those back --- Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs | 12 +- Ryujinx.Memory/MemoryBlock.cs | 4 +- Ryujinx.Memory/MemoryManagement.cs | 44 ++--- Ryujinx.Memory/MemoryManagementWindows.cs | 40 +++-- .../WindowsShared/PlaceholderManager.cs | 15 +- .../WindowsShared/PlaceholderManager4KB.cs | 170 ++++++++++++++++++ 6 files changed, 226 insertions(+), 59 deletions(-) create mode 100644 Ryujinx.Memory/WindowsShared/PlaceholderManager4KB.cs diff --git a/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs b/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs index 0ce65e3a7..d986bc41f 100644 --- a/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs +++ b/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs @@ -30,6 +30,7 @@ namespace Ryujinx.HLE.HOS.Services.Ro private List _nroInfos; private KProcess _owner; + private IVirtualMemoryManager _ownerMm; private static Random _random = new Random(); @@ -38,6 +39,7 @@ namespace Ryujinx.HLE.HOS.Services.Ro _nrrInfos = new List(MaxNrr); _nroInfos = new List(MaxNro); _owner = null; + _ownerMm = null; } private ResultCode ParseNrr(out NrrInfo nrrInfo, ServiceCtx context, ulong nrrAddress, ulong nrrSize) @@ -564,10 +566,12 @@ namespace Ryujinx.HLE.HOS.Services.Ro return ResultCode.InvalidSession; } - _owner = context.Process.HandleTable.GetKProcess(context.Request.HandleDesc.ToCopy[0]); - context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]); + int processHandle = context.Request.HandleDesc.ToCopy[0]; + _owner = context.Process.HandleTable.GetKProcess(processHandle); + _ownerMm = _owner?.CpuMemory; + context.Device.System.KernelContext.Syscall.CloseHandle(processHandle); - if (_owner?.CpuMemory is IRefCounted rc) + if (_ownerMm is IRefCounted rc) { rc.IncrementReferenceCount(); } @@ -586,7 +590,7 @@ namespace Ryujinx.HLE.HOS.Services.Ro _nroInfos.Clear(); - if (_owner?.CpuMemory is IRefCounted rc) + if (_ownerMm is IRefCounted rc) { rc.DecrementReferenceCount(); } diff --git a/Ryujinx.Memory/MemoryBlock.cs b/Ryujinx.Memory/MemoryBlock.cs index 82a7d882e..c6b85b582 100644 --- a/Ryujinx.Memory/MemoryBlock.cs +++ b/Ryujinx.Memory/MemoryBlock.cs @@ -48,7 +48,7 @@ namespace Ryujinx.Memory { _viewCompatible = flags.HasFlag(MemoryAllocationFlags.ViewCompatible); _forceWindows4KBView = flags.HasFlag(MemoryAllocationFlags.ForceWindows4KBViewMapping); - _pointer = MemoryManagement.Reserve(size, _viewCompatible); + _pointer = MemoryManagement.Reserve(size, _viewCompatible, _forceWindows4KBView); } else { @@ -404,7 +404,7 @@ namespace Ryujinx.Memory } else { - MemoryManagement.Free(ptr); + MemoryManagement.Free(ptr, Size, _forceWindows4KBView); } foreach (MemoryBlock viewStorage in _viewStorages.Keys) diff --git a/Ryujinx.Memory/MemoryManagement.cs b/Ryujinx.Memory/MemoryManagement.cs index 81262152b..3b8a96649 100644 --- a/Ryujinx.Memory/MemoryManagement.cs +++ b/Ryujinx.Memory/MemoryManagement.cs @@ -8,9 +8,7 @@ namespace Ryujinx.Memory { if (OperatingSystem.IsWindows()) { - IntPtr sizeNint = new IntPtr((long)size); - - return MemoryManagementWindows.Allocate(sizeNint); + return MemoryManagementWindows.Allocate((IntPtr)size); } else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) { @@ -22,13 +20,11 @@ namespace Ryujinx.Memory } } - public static IntPtr Reserve(ulong size, bool viewCompatible) + public static IntPtr Reserve(ulong size, bool viewCompatible, bool force4KBMap) { if (OperatingSystem.IsWindows()) { - IntPtr sizeNint = new IntPtr((long)size); - - return MemoryManagementWindows.Reserve(sizeNint, viewCompatible); + return MemoryManagementWindows.Reserve((IntPtr)size, viewCompatible, force4KBMap); } else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) { @@ -44,9 +40,7 @@ namespace Ryujinx.Memory { if (OperatingSystem.IsWindows()) { - IntPtr sizeNint = new IntPtr((long)size); - - return MemoryManagementWindows.Commit(address, sizeNint); + return MemoryManagementWindows.Commit(address, (IntPtr)size); } else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) { @@ -62,9 +56,7 @@ namespace Ryujinx.Memory { if (OperatingSystem.IsWindows()) { - IntPtr sizeNint = new IntPtr((long)size); - - return MemoryManagementWindows.Decommit(address, sizeNint); + return MemoryManagementWindows.Decommit(address, (IntPtr)size); } else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) { @@ -80,15 +72,13 @@ namespace Ryujinx.Memory { if (OperatingSystem.IsWindows()) { - IntPtr sizeNint = new IntPtr((long)size); - if (force4KBMap) { - MemoryManagementWindows.MapView4KB(sharedMemory, srcOffset, address, sizeNint); + MemoryManagementWindows.MapView4KB(sharedMemory, srcOffset, address, (IntPtr)size); } else { - MemoryManagementWindows.MapView(sharedMemory, srcOffset, address, sizeNint); + MemoryManagementWindows.MapView(sharedMemory, srcOffset, address, (IntPtr)size); } } else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) @@ -105,15 +95,13 @@ namespace Ryujinx.Memory { if (OperatingSystem.IsWindows()) { - IntPtr sizeNint = new IntPtr((long)size); - if (force4KBMap) { - MemoryManagementWindows.UnmapView4KB(address, sizeNint); + MemoryManagementWindows.UnmapView4KB(address, (IntPtr)size); } else { - MemoryManagementWindows.UnmapView(sharedMemory, address, sizeNint); + MemoryManagementWindows.UnmapView(sharedMemory, address, (IntPtr)size); } } else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) @@ -132,15 +120,13 @@ namespace Ryujinx.Memory if (OperatingSystem.IsWindows()) { - IntPtr sizeNint = new IntPtr((long)size); - if (forView && force4KBMap) { - result = MemoryManagementWindows.Reprotect4KB(address, sizeNint, permission, forView); + result = MemoryManagementWindows.Reprotect4KB(address, (IntPtr)size, permission, forView); } else { - result = MemoryManagementWindows.Reprotect(address, sizeNint, permission, forView); + result = MemoryManagementWindows.Reprotect(address, (IntPtr)size, permission, forView); } } else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) @@ -158,11 +144,11 @@ namespace Ryujinx.Memory } } - public static bool Free(IntPtr address) + public static bool Free(IntPtr address, ulong size, bool force4KBMap) { if (OperatingSystem.IsWindows()) { - return MemoryManagementWindows.Free(address); + return MemoryManagementWindows.Free(address, (IntPtr)size, force4KBMap); } else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) { @@ -178,9 +164,7 @@ namespace Ryujinx.Memory { if (OperatingSystem.IsWindows()) { - IntPtr sizeNint = new IntPtr((long)size); - - return MemoryManagementWindows.CreateSharedMemory(sizeNint, reserve); + return MemoryManagementWindows.CreateSharedMemory((IntPtr)size, reserve); } else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) { diff --git a/Ryujinx.Memory/MemoryManagementWindows.cs b/Ryujinx.Memory/MemoryManagementWindows.cs index 1885376ca..3d51b64f7 100644 --- a/Ryujinx.Memory/MemoryManagementWindows.cs +++ b/Ryujinx.Memory/MemoryManagementWindows.cs @@ -7,21 +7,27 @@ namespace Ryujinx.Memory [SupportedOSPlatform("windows")] static class MemoryManagementWindows { - private const int PageSize = 0x1000; + public const int PageSize = 0x1000; private static readonly PlaceholderManager _placeholders = new PlaceholderManager(); + private static readonly PlaceholderManager4KB _placeholders4KB = new PlaceholderManager4KB(); public static IntPtr Allocate(IntPtr size) { return AllocateInternal(size, AllocationType.Reserve | AllocationType.Commit); } - public static IntPtr Reserve(IntPtr size, bool viewCompatible) + public static IntPtr Reserve(IntPtr size, bool viewCompatible, bool force4KBMap) { if (viewCompatible) { IntPtr baseAddress = AllocateInternal2(size, AllocationType.Reserve | AllocationType.ReservePlaceholder); - _placeholders.ReserveRange((ulong)baseAddress, (ulong)size); + + if (!force4KBMap) + { + _placeholders.ReserveRange((ulong)baseAddress, (ulong)size); + } + return baseAddress; } @@ -69,6 +75,8 @@ namespace Ryujinx.Memory public static void MapView4KB(IntPtr sharedMemory, ulong srcOffset, IntPtr location, IntPtr size) { + _placeholders4KB.UnmapAndMarkRangeAsMapped(location, size); + ulong uaddress = (ulong)location; ulong usize = (ulong)size; IntPtr endLocation = (IntPtr)(uaddress + usize); @@ -105,20 +113,7 @@ namespace Ryujinx.Memory public static void UnmapView4KB(IntPtr location, IntPtr size) { - ulong uaddress = (ulong)location; - ulong usize = (ulong)size; - IntPtr endLocation = (IntPtr)(uaddress + usize); - - while (location != endLocation) - { - bool result = WindowsApi.UnmapViewOfFile2(WindowsApi.CurrentProcessHandle, location, 2); - if (!result) - { - throw new WindowsApiException("UnmapViewOfFile2"); - } - - location += PageSize; - } + _placeholders4KB.UnmapView(location, size); } public static bool Reprotect(IntPtr address, IntPtr size, MemoryPermission permission, bool forView) @@ -151,8 +146,17 @@ namespace Ryujinx.Memory return true; } - public static bool Free(IntPtr address) + public static bool Free(IntPtr address, IntPtr size, bool force4KBMap) { + if (force4KBMap) + { + _placeholders4KB.UnmapRange(address, size); + } + else + { + _placeholders.UnmapView(IntPtr.Zero, address, size); + } + return WindowsApi.VirtualFree(address, IntPtr.Zero, AllocationType.Release); } diff --git a/Ryujinx.Memory/WindowsShared/PlaceholderManager.cs b/Ryujinx.Memory/WindowsShared/PlaceholderManager.cs index b0b3bf050..d465f3416 100644 --- a/Ryujinx.Memory/WindowsShared/PlaceholderManager.cs +++ b/Ryujinx.Memory/WindowsShared/PlaceholderManager.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Runtime.Versioning; using System.Threading; namespace Ryujinx.Memory.WindowsShared @@ -7,6 +8,7 @@ namespace Ryujinx.Memory.WindowsShared /// /// Windows memory placeholder manager. /// + [SupportedOSPlatform("windows")] class PlaceholderManager { private const ulong MinimumPageSize = 0x1000; @@ -203,7 +205,7 @@ namespace Ryujinx.Memory.WindowsShared ulong endAddress = startAddress + unmapSize; var overlaps = Array.Empty>(); - int count = 0; + int count; lock (_mappings) { @@ -226,8 +228,11 @@ namespace Ryujinx.Memory.WindowsShared ulong overlapEnd = overlap.End; ulong overlapValue = overlap.Value; - _mappings.Remove(overlap); - _mappings.Add(overlapStart, overlapEnd, ulong.MaxValue); + lock (_mappings) + { + _mappings.Remove(overlap); + _mappings.Add(overlapStart, overlapEnd, ulong.MaxValue); + } bool overlapStartsBefore = overlapStart < startAddress; bool overlapEndsAfter = overlapEnd > endAddress; @@ -364,7 +369,7 @@ namespace Ryujinx.Memory.WindowsShared ulong endAddress = reprotectAddress + reprotectSize; var overlaps = Array.Empty>(); - int count = 0; + int count; lock (_mappings) { @@ -534,7 +539,7 @@ namespace Ryujinx.Memory.WindowsShared { ulong endAddress = address + size; var overlaps = Array.Empty>(); - int count = 0; + int count; lock (_protections) { diff --git a/Ryujinx.Memory/WindowsShared/PlaceholderManager4KB.cs b/Ryujinx.Memory/WindowsShared/PlaceholderManager4KB.cs new file mode 100644 index 000000000..fc056a2f7 --- /dev/null +++ b/Ryujinx.Memory/WindowsShared/PlaceholderManager4KB.cs @@ -0,0 +1,170 @@ +using System; +using System.Runtime.Versioning; + +namespace Ryujinx.Memory.WindowsShared +{ + /// + /// Windows 4KB memory placeholder manager. + /// + [SupportedOSPlatform("windows")] + class PlaceholderManager4KB + { + private const int PageSize = MemoryManagementWindows.PageSize; + + private readonly IntervalTree _mappings; + + /// + /// Creates a new instance of the Windows 4KB memory placeholder manager. + /// + public PlaceholderManager4KB() + { + _mappings = new IntervalTree(); + } + + /// + /// Unmaps the specified range of memory and marks it as mapped internally. + /// + /// + /// Since this marks the range as mapped, the expectation is that the range will be mapped after calling this method. + /// + /// Memory address to unmap and mark as mapped + /// Size of the range in bytes + public void UnmapAndMarkRangeAsMapped(IntPtr location, IntPtr size) + { + ulong startAddress = (ulong)location; + ulong unmapSize = (ulong)size; + ulong endAddress = startAddress + unmapSize; + + var overlaps = Array.Empty>(); + int count = 0; + + lock (_mappings) + { + count = _mappings.Get(startAddress, endAddress, ref overlaps); + } + + for (int index = 0; index < count; index++) + { + var overlap = overlaps[index]; + + // Tree operations might modify the node start/end values, so save a copy before we modify the tree. + ulong overlapStart = overlap.Start; + ulong overlapEnd = overlap.End; + ulong overlapValue = overlap.Value; + + _mappings.Remove(overlap); + + ulong unmapStart = Math.Max(overlapStart, startAddress); + ulong unmapEnd = Math.Min(overlapEnd, endAddress); + + if (overlapStart < startAddress) + { + startAddress = overlapStart; + } + + if (overlapEnd > endAddress) + { + endAddress = overlapEnd; + } + + ulong currentAddress = unmapStart; + while (currentAddress < unmapEnd) + { + WindowsApi.UnmapViewOfFile2(WindowsApi.CurrentProcessHandle, (IntPtr)currentAddress, 2); + currentAddress += PageSize; + } + } + + _mappings.Add(startAddress, endAddress, 0); + } + + /// + /// Unmaps views at the specified memory range. + /// + /// Address of the range + /// Size of the range in bytes + public void UnmapView(IntPtr location, IntPtr size) + { + ulong startAddress = (ulong)location; + ulong unmapSize = (ulong)size; + ulong endAddress = startAddress + unmapSize; + + var overlaps = Array.Empty>(); + int count = 0; + + lock (_mappings) + { + count = _mappings.Get(startAddress, endAddress, ref overlaps); + } + + for (int index = 0; index < count; index++) + { + var overlap = overlaps[index]; + + // Tree operations might modify the node start/end values, so save a copy before we modify the tree. + ulong overlapStart = overlap.Start; + ulong overlapEnd = overlap.End; + + _mappings.Remove(overlap); + + if (overlapStart < startAddress) + { + _mappings.Add(overlapStart, startAddress, 0); + } + + if (overlapEnd > endAddress) + { + _mappings.Add(endAddress, overlapEnd, 0); + } + + ulong unmapStart = Math.Max(overlapStart, startAddress); + ulong unmapEnd = Math.Min(overlapEnd, endAddress); + + ulong currentAddress = unmapStart; + while (currentAddress < unmapEnd) + { + WindowsApi.UnmapViewOfFile2(WindowsApi.CurrentProcessHandle, (IntPtr)currentAddress, 2); + currentAddress += PageSize; + } + } + } + + /// + /// Unmaps mapped memory at a given range. + /// + /// Address of the range + /// Size of the range in bytes + public void UnmapRange(IntPtr location, IntPtr size) + { + ulong startAddress = (ulong)location; + ulong unmapSize = (ulong)size; + ulong endAddress = startAddress + unmapSize; + + var overlaps = Array.Empty>(); + int count = 0; + + lock (_mappings) + { + count = _mappings.Get(startAddress, endAddress, ref overlaps); + } + + for (int index = 0; index < count; index++) + { + var overlap = overlaps[index]; + + // Tree operations might modify the node start/end values, so save a copy before we modify the tree. + ulong unmapStart = Math.Max(overlap.Start, startAddress); + ulong unmapEnd = Math.Min(overlap.End, endAddress); + + _mappings.Remove(overlap); + + ulong currentAddress = unmapStart; + while (currentAddress < unmapEnd) + { + WindowsApi.UnmapViewOfFile2(WindowsApi.CurrentProcessHandle, (IntPtr)currentAddress, 2); + currentAddress += PageSize; + } + } + } + } +} \ No newline at end of file From 42a2a80b876bfbaac212f4f485f3e4019893b8a0 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Thu, 5 May 2022 15:23:30 -0300 Subject: [PATCH 08/33] Enable JIT service LLE (#2959) * Enable JIT service LLE * Force disable PPTC when using the JIT service PPTC does not support multiple guest processes * Fix build * Make SM service registration per emulation context rather than global * Address PR feedback --- Ryujinx.HLE/HOS/ApplicationLoader.cs | 103 ++++++++++++++++-- Ryujinx.HLE/HOS/Horizon.cs | 7 +- .../ApplicationProxy/IApplicationFunctions.cs | 33 ++++++ Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs | 9 ++ Ryujinx.HLE/HOS/Services/ServerBase.cs | 1 - .../Settings/ISystemSettingsServer.cs | 13 ++- Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs | 45 ++++---- Ryujinx.HLE/HOS/Services/Sm/SmRegistry.cs | 49 +++++++++ 8 files changed, 229 insertions(+), 31 deletions(-) create mode 100644 Ryujinx.HLE/HOS/Services/Sm/SmRegistry.cs diff --git a/Ryujinx.HLE/HOS/ApplicationLoader.cs b/Ryujinx.HLE/HOS/ApplicationLoader.cs index b3d23ea7b..50ab9e1f8 100644 --- a/Ryujinx.HLE/HOS/ApplicationLoader.cs +++ b/Ryujinx.HLE/HOS/ApplicationLoader.cs @@ -24,6 +24,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Reflection; +using System.Text; using static LibHac.Fs.ApplicationSaveDataManagement; using static Ryujinx.HLE.HOS.ModLoader; @@ -308,6 +309,94 @@ namespace Ryujinx.HLE.HOS LoadNca(nca, null, null); } + public void LoadServiceNca(string ncaFile) + { + // Disable PPTC here as it does not support multiple processes running. + // TODO: This should be eventually removed and it should stop using global state and + // instead manage the cache per process. + Ptc.Close(); + PtcProfiler.Stop(); + + FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read); + Nca mainNca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false)); + + if (mainNca.Header.ContentType != NcaContentType.Program) + { + Logger.Error?.Print(LogClass.Loader, "Selected NCA is not a \"Program\" NCA"); + + return; + } + + IFileSystem codeFs = null; + + if (mainNca.CanOpenSection(NcaSectionType.Code)) + { + codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, _device.System.FsIntegrityCheckLevel); + } + + if (codeFs == null) + { + Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA"); + + return; + } + + using var npdmFile = new UniqueRef(); + + Result result = codeFs.OpenFile(ref npdmFile.Ref(), "/main.npdm".ToU8Span(), OpenMode.Read); + + MetaLoader metaData; + + npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure(); + + var npdmBuffer = new byte[fileSize]; + npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure(); + + metaData = new MetaLoader(); + metaData.Load(npdmBuffer).ThrowIfFailure(); + + NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length]; + + for (int i = 0; i < nsos.Length; i++) + { + string name = ExeFsPrefixes[i]; + + if (!codeFs.FileExists($"/{name}")) + { + continue; // File doesn't exist, skip. + } + + Logger.Info?.Print(LogClass.Loader, $"Loading {name}..."); + + using var nsoFile = new UniqueRef(); + + codeFs.OpenFile(ref nsoFile.Ref(), $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name); + } + + // Collect the nsos, ignoring ones that aren't used. + NsoExecutable[] programs = nsos.Where(x => x != null).ToArray(); + + MemoryManagerMode memoryManagerMode = _device.Configuration.MemoryManagerMode; + + if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible)) + { + memoryManagerMode = MemoryManagerMode.SoftwarePageTable; + } + + metaData.GetNpdm(out Npdm npdm).ThrowIfFailure(); + ProgramInfo programInfo = new ProgramInfo(in npdm, allowCodeMemoryForJit: false); + ProgramLoader.LoadNsos(_device.System.KernelContext, out _, metaData, programInfo, executables: programs); + + string titleIdText = npdm.Aci.Value.ProgramId.Value.ToString("x16"); + bool titleIs64Bit = (npdm.Meta.Value.Flags & 1) != 0; + + string programName = Encoding.ASCII.GetString(npdm.Meta.Value.ProgramName).TrimEnd('\0'); + + Logger.Info?.Print(LogClass.Loader, $"Service Loaded: {programName} [{titleIdText}] [{(titleIs64Bit ? "64-bit" : "32-bit")}]"); + } + private void LoadNca(Nca mainNca, Nca patchNca, Nca controlNca) { if (mainNca.Header.ContentType != NcaContentType.Program) @@ -508,7 +597,7 @@ namespace Ryujinx.HLE.HOS { if (_device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(TitleId, ref codeFs)) { - metaData = null; //TODO: Check if we should retain old npdm + metaData = null; // TODO: Check if we should retain old npdm. } metaData ??= ReadNpdm(codeFs); @@ -521,7 +610,7 @@ namespace Ryujinx.HLE.HOS if (!codeFs.FileExists($"/{name}")) { - continue; // file doesn't exist, skip + continue; // File doesn't exist, skip. } Logger.Info?.Print(LogClass.Loader, $"Loading {name}..."); @@ -533,13 +622,13 @@ namespace Ryujinx.HLE.HOS nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name); } - // ExeFs file replacements + // ExeFs file replacements. ModLoadResult modLoadResult = _device.Configuration.VirtualFileSystem.ModLoader.ApplyExefsMods(TitleId, nsos); - // collect the nsos, ignoring ones that aren't used + // Collect the nsos, ignoring ones that aren't used. NsoExecutable[] programs = nsos.Where(x => x != null).ToArray(); - // take the npdm from mods if present + // Take the npdm from mods if present. if (modLoadResult.Npdm != null) { metaData = modLoadResult.Npdm; @@ -598,7 +687,7 @@ namespace Ryujinx.HLE.HOS executable = obj; - // homebrew NRO can actually have some data after the actual NRO + // Homebrew NRO can actually have some data after the actual NRO. if (input.Length > obj.FileSize) { input.Position = obj.FileSize; @@ -677,7 +766,7 @@ namespace Ryujinx.HLE.HOS TitleIs64Bit = (npdm.Meta.Value.Flags & 1) != 0; _device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId); - // Explicitly null titleid to disable the shader cache + // Explicitly null titleid to disable the shader cache. Graphics.Gpu.GraphicsConfig.TitleId = null; _device.Gpu.HostInitalized.Set(); diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index 24d04c122..7de9bdf33 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -72,6 +72,8 @@ namespace Ryujinx.HLE.HOS internal List NfpDevices { get; private set; } + internal SmRegistry SmRegistry { get; private set; } + internal ServerBase SmServer { get; private set; } internal ServerBase BsdServer { get; private set; } internal ServerBase AudRenServer { get; private set; } @@ -291,7 +293,8 @@ namespace Ryujinx.HLE.HOS private void InitializeServices() { - SmServer = new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext)); + SmRegistry = new SmRegistry(); + SmServer = new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext, SmRegistry)); // Wait until SM server thread is done with initialization, // only then doing connections to SM is safe. @@ -468,7 +471,7 @@ namespace Ryujinx.HLE.HOS AudioRendererManager.Dispose(); LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure(); - + KernelContext.Dispose(); } } diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs index efc1884f7..b4a92d693 100644 --- a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs +++ b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs @@ -2,9 +2,12 @@ using LibHac; using LibHac.Account; using LibHac.Common; using LibHac.Fs; +using LibHac.FsSystem; +using LibHac.Ncm; using LibHac.Ns; using Ryujinx.Common; using Ryujinx.Common.Logging; +using Ryujinx.HLE.Exceptions; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Memory; @@ -12,9 +15,11 @@ using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage; using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService; +using Ryujinx.HLE.HOS.Services.Sm; using Ryujinx.HLE.HOS.SystemState; using System; using System.Numerics; +using System.Threading; using static LibHac.Fs.ApplicationSaveDataManagement; using AccountUid = Ryujinx.HLE.HOS.Services.Account.Acc.UserId; @@ -37,6 +42,8 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati private int _notificationStorageChannelEventHandle; private int _healthWarningDisappearedSystemEventHandle; + private int _jitLoaded; + private HorizonClient _horizon; public IApplicationFunctions(Horizon system) @@ -631,5 +638,31 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati return ResultCode.Success; } + + [CommandHipc(1001)] // 10.0.0+ + // PrepareForJit() + public ResultCode PrepareForJit(ServiceCtx context) + { + if (Interlocked.Exchange(ref _jitLoaded, 1) == 0) + { + string jitPath = context.Device.System.ContentManager.GetInstalledContentPath(0x010000000000003B, StorageId.BuiltInSystem, NcaContentType.Program); + string filePath = context.Device.FileSystem.SwitchPathToSystemPath(jitPath); + + if (string.IsNullOrWhiteSpace(filePath)) + { + throw new InvalidSystemResourceException($"JIT (010000000000003B) system title not found! The JIT will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx#requirements for more information)"); + } + + context.Device.Application.LoadServiceNca(filePath); + + // FIXME: Most likely not how this should be done? + while (!context.Device.System.SmRegistry.IsServiceRegistered("jit:u")) + { + context.Device.System.SmRegistry.WaitForServiceRegistration(); + } + } + + return ResultCode.Success; + } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs b/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs index d986bc41f..5590bfddf 100644 --- a/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs +++ b/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs @@ -579,6 +579,15 @@ namespace Ryujinx.HLE.HOS.Services.Ro return ResultCode.Success; } + [CommandHipc(10)] + // LoadNrr2(u64, u64, u64, pid) + public ResultCode LoadNrr2(ServiceCtx context) + { + context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]); + + return LoadNrr(context); + } + protected override void Dispose(bool isDisposing) { if (isDisposing) diff --git a/Ryujinx.HLE/HOS/Services/ServerBase.cs b/Ryujinx.HLE/HOS/Services/ServerBase.cs index 1a1e4a340..907833441 100644 --- a/Ryujinx.HLE/HOS/Services/ServerBase.cs +++ b/Ryujinx.HLE/HOS/Services/ServerBase.cs @@ -4,7 +4,6 @@ using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Ipc; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Threading; -using Ryujinx.HLE.HOS.Services.Sm; using System; using System.Buffers.Binary; using System.Collections.Generic; diff --git a/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs b/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs index 88888f34f..1ce7bbfc1 100644 --- a/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs +++ b/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs @@ -222,7 +222,7 @@ namespace Ryujinx.HLE.HOS.Services.Settings return ResultCode.Success; } - [CommandHipc(60)] + [CommandHipc(60)] // IsUserSystemClockAutomaticCorrectionEnabled() -> bool public ResultCode IsUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context) { @@ -234,6 +234,17 @@ namespace Ryujinx.HLE.HOS.Services.Settings return ResultCode.Success; } + [CommandHipc(62)] + // GetDebugModeFlag() -> bool + public ResultCode GetDebugModeFlag(ServiceCtx context) + { + context.ResponseData.Write(false); + + Logger.Stub?.PrintStub(LogClass.ServiceSet); + + return ResultCode.Success; + } + [CommandHipc(77)] // GetDeviceNickName() -> buffer public ResultCode GetDeviceNickName(ServiceCtx context) diff --git a/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs b/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs index a5595e310..8e66b28da 100644 --- a/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs +++ b/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs @@ -1,11 +1,9 @@ using Ryujinx.Common.Logging; -using Ryujinx.HLE.Exceptions; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Ipc; using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; @@ -17,21 +15,19 @@ namespace Ryujinx.HLE.HOS.Services.Sm { private static Dictionary _services; - private static readonly ConcurrentDictionary _registeredServices; - + private readonly SmRegistry _registry; private readonly ServerBase _commonServer; private bool _isInitialized; - public IUserInterface(KernelContext context) + public IUserInterface(KernelContext context, SmRegistry registry) { _commonServer = new ServerBase(context, "CommonServer"); + _registry = registry; } static IUserInterface() { - _registeredServices = new ConcurrentDictionary(); - _services = Assembly.GetExecutingAssembly().GetTypes() .SelectMany(type => type.GetCustomAttributes(typeof(ServiceAttribute), true) .Select(service => (((ServiceAttribute)service).Name, type))) @@ -74,7 +70,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm KSession session = new KSession(context.Device.System.KernelContext); - if (_registeredServices.TryGetValue(name, out KPort port)) + if (_registry.TryGetService(name, out KPort port)) { KernelResult result = port.EnqueueIncomingSession(session.ServerSession); @@ -82,6 +78,15 @@ namespace Ryujinx.HLE.HOS.Services.Sm { throw new InvalidOperationException($"Session enqueue on port returned error \"{result}\"."); } + + if (context.Process.HandleTable.GenerateHandle(session.ClientSession, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + session.ClientSession.DecrementReferenceCount(); + + context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle); } else { @@ -107,18 +112,18 @@ namespace Ryujinx.HLE.HOS.Services.Sm throw new NotImplementedException(name); } } + + if (context.Process.HandleTable.GenerateHandle(session.ClientSession, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + session.ServerSession.DecrementReferenceCount(); + session.ClientSession.DecrementReferenceCount(); + + context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle); } - if (context.Process.HandleTable.GenerateHandle(session.ClientSession, out int handle) != KernelResult.Success) - { - throw new InvalidOperationException("Out of handles!"); - } - - session.ServerSession.DecrementReferenceCount(); - session.ClientSession.DecrementReferenceCount(); - - context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle); - return ResultCode.Success; } @@ -179,7 +184,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm KPort port = new KPort(context.Device.System.KernelContext, maxSessions, isLight, 0); - if (!_registeredServices.TryAdd(name, port)) + if (!_registry.TryRegister(name, port)) { return ResultCode.AlreadyRegistered; } @@ -219,7 +224,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm return ResultCode.InvalidName; } - if (!_registeredServices.TryRemove(name, out _)) + if (!_registry.Unregister(name)) { return ResultCode.NotRegistered; } diff --git a/Ryujinx.HLE/HOS/Services/Sm/SmRegistry.cs b/Ryujinx.HLE/HOS/Services/Sm/SmRegistry.cs new file mode 100644 index 000000000..e62e0eb53 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sm/SmRegistry.cs @@ -0,0 +1,49 @@ +using Ryujinx.HLE.HOS.Kernel.Ipc; +using System.Collections.Concurrent; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Sm +{ + class SmRegistry + { + private readonly ConcurrentDictionary _registeredServices; + private readonly AutoResetEvent _serviceRegistrationEvent; + + public SmRegistry() + { + _registeredServices = new ConcurrentDictionary(); + _serviceRegistrationEvent = new AutoResetEvent(false); + } + + public bool TryGetService(string name, out KPort port) + { + return _registeredServices.TryGetValue(name, out port); + } + + public bool TryRegister(string name, KPort port) + { + if (_registeredServices.TryAdd(name, port)) + { + _serviceRegistrationEvent.Set(); + return true; + } + + return false; + } + + public bool Unregister(string name) + { + return _registeredServices.TryRemove(name, out _); + } + + public bool IsServiceRegistered(string name) + { + return _registeredServices.TryGetValue(name, out _); + } + + public void WaitForServiceRegistration() + { + _serviceRegistrationEvent.WaitOne(); + } + } +} \ No newline at end of file From 50d7ecf76d07366a814cae19038199b887d3fda6 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Thu, 5 May 2022 20:16:58 +0100 Subject: [PATCH 09/33] Add alternative "GL" enum values for StencilOp (#3321) This PR adds the alternative enum values for StencilOp. Similar to the other enums, I added these with the same names but with Gl added to the end. These are used by homebrew using Nouveau, though they might be used by games with the official Vulkan driver. https://github.com/envytools/envytools/blob/39d90be897f41434d67277ebdf244d6bd419ecd9/rnndb/graph/nv_3ddefs.xml#L77 Fixes some broken graphics in Citra, such as missing shadows in Mario Kart 7. Likely fixes other homebrew. --- Ryujinx.Graphics.GAL/StencilOp.cs | 11 ++++++++++- Ryujinx.Graphics.OpenGL/EnumConversion.cs | 8 ++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Ryujinx.Graphics.GAL/StencilOp.cs b/Ryujinx.Graphics.GAL/StencilOp.cs index f0ac829e6..fe999b0fc 100644 --- a/Ryujinx.Graphics.GAL/StencilOp.cs +++ b/Ryujinx.Graphics.GAL/StencilOp.cs @@ -9,6 +9,15 @@ namespace Ryujinx.Graphics.GAL DecrementAndClamp, Invert, IncrementAndWrap, - DecrementAndWrap + DecrementAndWrap, + + ZeroGl = 0x0, + InvertGl = 0x150a, + KeepGl = 0x1e00, + ReplaceGl = 0x1e01, + IncrementAndClampGl = 0x1e02, + DecrementAndClampGl = 0x1e03, + IncrementAndWrapGl = 0x8507, + DecrementAndWrapGl = 0x8508 } } \ No newline at end of file diff --git a/Ryujinx.Graphics.OpenGL/EnumConversion.cs b/Ryujinx.Graphics.OpenGL/EnumConversion.cs index 24cf1fc4d..4a06e9649 100644 --- a/Ryujinx.Graphics.OpenGL/EnumConversion.cs +++ b/Ryujinx.Graphics.OpenGL/EnumConversion.cs @@ -379,20 +379,28 @@ namespace Ryujinx.Graphics.OpenGL switch (op) { case GAL.StencilOp.Keep: + case GAL.StencilOp.KeepGl: return OpenTK.Graphics.OpenGL.StencilOp.Keep; case GAL.StencilOp.Zero: + case GAL.StencilOp.ZeroGl: return OpenTK.Graphics.OpenGL.StencilOp.Zero; case GAL.StencilOp.Replace: + case GAL.StencilOp.ReplaceGl: return OpenTK.Graphics.OpenGL.StencilOp.Replace; case GAL.StencilOp.IncrementAndClamp: + case GAL.StencilOp.IncrementAndClampGl: return OpenTK.Graphics.OpenGL.StencilOp.Incr; case GAL.StencilOp.DecrementAndClamp: + case GAL.StencilOp.DecrementAndClampGl: return OpenTK.Graphics.OpenGL.StencilOp.Decr; case GAL.StencilOp.Invert: + case GAL.StencilOp.InvertGl: return OpenTK.Graphics.OpenGL.StencilOp.Invert; case GAL.StencilOp.IncrementAndWrap: + case GAL.StencilOp.IncrementAndWrapGl: return OpenTK.Graphics.OpenGL.StencilOp.IncrWrap; case GAL.StencilOp.DecrementAndWrap: + case GAL.StencilOp.DecrementAndWrapGl: return OpenTK.Graphics.OpenGL.StencilOp.DecrWrap; } From 92ca1cb0cbab228e5ef22645cd4f9d06b1da4766 Mon Sep 17 00:00:00 2001 From: Ac_K Date: Sun, 8 May 2022 00:28:54 +0200 Subject: [PATCH 10/33] hid: Various fixes and cleanup (#3326) * hid: Various fix and cleanup * Add IsValidNpadIdType * remove () --- .../ApplicationProxy/IApplicationFunctions.cs | 16 +- .../Services/Hid/HidDevices/NpadDevices.cs | 26 +-- .../HOS/Services/Hid/HidServer/HidUtils.cs | 5 + .../Types/Npad/HidNpadJoyAssignmentMode.cs | 8 - ...nMode.cs => NpadHandheldActivationMode.cs} | 2 +- ...dJoyDeviceType.cs => NpadJoyDeviceType.cs} | 2 +- ...rameters.cs => AccelerometerParameters.cs} | 2 +- ...DriftMode.cs => GyroscopeZeroDriftMode.cs} | 2 +- ...arameters.cs => SensorFusionParameters.cs} | 2 +- .../Vibration/HidVibrationDeviceValue.cs | 8 - ...viceHandle.cs => VibrationDeviceHandle.cs} | 2 +- ...Position.cs => VibrationDevicePosition.cs} | 2 +- ...onDeviceType.cs => VibrationDeviceType.cs} | 2 +- .../Types/Vibration/VibrationDeviceValue.cs | 8 + ...HidVibrationValue.cs => VibrationValue.cs} | 7 +- Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs | 168 ++++++++++-------- Ryujinx.Input/HLE/NpadController.cs | 8 +- 17 files changed, 147 insertions(+), 123 deletions(-) delete mode 100644 Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/HidNpadJoyAssignmentMode.cs rename Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/{HidNpadHandheldActivationMode.cs => NpadHandheldActivationMode.cs} (68%) rename Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/{HidNpadJoyDeviceType.cs => NpadJoyDeviceType.cs} (69%) rename Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/{HidAccelerometerParameters.cs => AccelerometerParameters.cs} (70%) rename Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/{HidGyroscopeZeroDriftMode.cs => GyroscopeZeroDriftMode.cs} (71%) rename Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/{HidSensorFusionParameters.cs => SensorFusionParameters.cs} (73%) delete mode 100644 Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceValue.cs rename Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/{HidVibrationDeviceHandle.cs => VibrationDeviceHandle.cs} (80%) rename Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/{HidVibrationDevicePosition.cs => VibrationDevicePosition.cs} (69%) rename Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/{HidVibrationDeviceType.cs => VibrationDeviceType.cs} (75%) create mode 100644 Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceValue.cs rename Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/{HidVibrationValue.cs => VibrationValue.cs} (78%) diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs index b4a92d693..957fb9a16 100644 --- a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs +++ b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs @@ -15,7 +15,6 @@ using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage; using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService; -using Ryujinx.HLE.HOS.Services.Sm; using Ryujinx.HLE.HOS.SystemState; using System; using System.Numerics; @@ -42,6 +41,8 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati private int _notificationStorageChannelEventHandle; private int _healthWarningDisappearedSystemEventHandle; + private bool _gamePlayRecordingState; + private int _jitLoaded; private HorizonClient _horizon; @@ -349,6 +350,15 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati return ResultCode.Success; } + [CommandHipc(65)] // 3.0.0+ + // IsGamePlayRecordingSupported() -> u8 + public ResultCode IsGamePlayRecordingSupported(ServiceCtx context) + { + context.ResponseData.Write(_gamePlayRecordingState); + + return ResultCode.Success; + } + [CommandHipc(66)] // 3.0.0+ // InitializeGamePlayRecording(u64, handle) public ResultCode InitializeGamePlayRecording(ServiceCtx context) @@ -362,9 +372,9 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati // SetGamePlayRecordingState(u32) public ResultCode SetGamePlayRecordingState(ServiceCtx context) { - int state = context.RequestData.ReadInt32(); + _gamePlayRecordingState = context.RequestData.ReadInt32() != 0; - Logger.Stub?.PrintStub(LogClass.ServiceAm, new { state }); + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { _gamePlayRecordingState }); return ResultCode.Success; } diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs index 5f56c770d..528b6dcea 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs @@ -21,7 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid private ControllerType[] _configuredTypes; private KEvent[] _styleSetUpdateEvents; private bool[] _supportedPlayers; - private static HidVibrationValue _neutralVibrationValue = new HidVibrationValue + private static VibrationValue _neutralVibrationValue = new VibrationValue { AmplitudeLow = 0f, FrequencyLow = 160f, @@ -33,8 +33,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid internal bool SixAxisActive = false; // TODO: link to hidserver when implemented internal ControllerType SupportedStyleSets { get; set; } - public Dictionary> RumbleQueues = new Dictionary>(); - public Dictionary LastVibrationValues = new Dictionary(); + public Dictionary> RumbleQueues = new Dictionary>(); + public Dictionary LastVibrationValues = new Dictionary(); public NpadDevices(Switch device, bool active = true) : base(device, active) { @@ -588,21 +588,21 @@ namespace Ryujinx.HLE.HOS.Services.Hid WriteNewSixInputEntry(ref currentNpad.JoyRightSixAxisSensor, ref newState); } - public void UpdateRumbleQueue(PlayerIndex index, Dictionary dualVibrationValues) + public void UpdateRumbleQueue(PlayerIndex index, Dictionary dualVibrationValues) { - if (RumbleQueues.TryGetValue(index, out ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> currentQueue)) + if (RumbleQueues.TryGetValue(index, out ConcurrentQueue<(VibrationValue, VibrationValue)> currentQueue)) { - if (!dualVibrationValues.TryGetValue(0, out HidVibrationValue leftVibrationValue)) + if (!dualVibrationValues.TryGetValue(0, out VibrationValue leftVibrationValue)) { leftVibrationValue = _neutralVibrationValue; } - if (!dualVibrationValues.TryGetValue(1, out HidVibrationValue rightVibrationValue)) + if (!dualVibrationValues.TryGetValue(1, out VibrationValue rightVibrationValue)) { rightVibrationValue = _neutralVibrationValue; } - if (!LastVibrationValues.TryGetValue(index, out (HidVibrationValue, HidVibrationValue) dualVibrationValue) || !leftVibrationValue.Equals(dualVibrationValue.Item1) || !rightVibrationValue.Equals(dualVibrationValue.Item2)) + if (!LastVibrationValues.TryGetValue(index, out (VibrationValue, VibrationValue) dualVibrationValue) || !leftVibrationValue.Equals(dualVibrationValue.Item1) || !rightVibrationValue.Equals(dualVibrationValue.Item2)) { currentQueue.Enqueue((leftVibrationValue, rightVibrationValue)); @@ -611,9 +611,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid } } - public HidVibrationValue GetLastVibrationValue(PlayerIndex index, byte position) + public VibrationValue GetLastVibrationValue(PlayerIndex index, byte position) { - if (!LastVibrationValues.TryGetValue(index, out (HidVibrationValue, HidVibrationValue) dualVibrationValue)) + if (!LastVibrationValues.TryGetValue(index, out (VibrationValue, VibrationValue) dualVibrationValue)) { return _neutralVibrationValue; } @@ -621,11 +621,11 @@ namespace Ryujinx.HLE.HOS.Services.Hid return (position == 0) ? dualVibrationValue.Item1 : dualVibrationValue.Item2; } - public ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> GetRumbleQueue(PlayerIndex index) + public ConcurrentQueue<(VibrationValue, VibrationValue)> GetRumbleQueue(PlayerIndex index) { - if (!RumbleQueues.TryGetValue(index, out ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> rumbleQueue)) + if (!RumbleQueues.TryGetValue(index, out ConcurrentQueue<(VibrationValue, VibrationValue)> rumbleQueue)) { - rumbleQueue = new ConcurrentQueue<(HidVibrationValue, HidVibrationValue)>(); + rumbleQueue = new ConcurrentQueue<(VibrationValue, VibrationValue)>(); _device.Hid.Npads.RumbleQueues[index] = rumbleQueue; } diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidServer/HidUtils.cs b/Ryujinx.HLE/HOS/Services/Hid/HidServer/HidUtils.cs index c2cd84329..65a69bb77 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/HidServer/HidUtils.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/HidServer/HidUtils.cs @@ -35,5 +35,10 @@ namespace Ryujinx.HLE.HOS.Services.Hid.HidServer PlayerIndex.Unknown => NpadIdType.Unknown, _ => throw new ArgumentOutOfRangeException(nameof(index)) }; + + public static bool IsValidNpadIdType(NpadIdType npadIdType) + { + return npadIdType <= NpadIdType.Player8 || npadIdType == NpadIdType.Handheld || npadIdType == NpadIdType.Unknown; + } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/HidNpadJoyAssignmentMode.cs b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/HidNpadJoyAssignmentMode.cs deleted file mode 100644 index a2e22661d..000000000 --- a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/HidNpadJoyAssignmentMode.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Hid -{ - public enum HidNpadJoyAssignmentMode - { - Dual, - Single - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/HidNpadHandheldActivationMode.cs b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadHandheldActivationMode.cs similarity index 68% rename from Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/HidNpadHandheldActivationMode.cs rename to Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadHandheldActivationMode.cs index 0aa8334d8..0cf4a0472 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/HidNpadHandheldActivationMode.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadHandheldActivationMode.cs @@ -1,6 +1,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid { - public enum HidNpadHandheldActivationMode + public enum NpadHandheldActivationMode { Dual, Single, diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/HidNpadJoyDeviceType.cs b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadJoyDeviceType.cs similarity index 69% rename from Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/HidNpadJoyDeviceType.cs rename to Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadJoyDeviceType.cs index d0b34def2..05587bfd2 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/HidNpadJoyDeviceType.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadJoyDeviceType.cs @@ -1,6 +1,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid { - public enum HidNpadJoyDeviceType + public enum NpadJoyDeviceType { Left, Right diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/HidAccelerometerParameters.cs b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/AccelerometerParameters.cs similarity index 70% rename from Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/HidAccelerometerParameters.cs rename to Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/AccelerometerParameters.cs index fe7e4cc93..4fd0a1b5b 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/HidAccelerometerParameters.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/AccelerometerParameters.cs @@ -1,6 +1,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid { - public struct HidAccelerometerParameters + public struct AccelerometerParameters { public float X; public float Y; diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/HidGyroscopeZeroDriftMode.cs b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/GyroscopeZeroDriftMode.cs similarity index 71% rename from Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/HidGyroscopeZeroDriftMode.cs rename to Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/GyroscopeZeroDriftMode.cs index cd3aa3180..db7467fa7 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/HidGyroscopeZeroDriftMode.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/GyroscopeZeroDriftMode.cs @@ -1,6 +1,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid { - public enum HidGyroscopeZeroDriftMode + public enum GyroscopeZeroDriftMode { Loose, Standard, diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/HidSensorFusionParameters.cs b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/SensorFusionParameters.cs similarity index 73% rename from Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/HidSensorFusionParameters.cs rename to Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/SensorFusionParameters.cs index cadf5ec02..2683ffee4 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/HidSensorFusionParameters.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/SensorFusionParameters.cs @@ -1,6 +1,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid { - public struct HidSensorFusionParameters + public struct SensorFusionParameters { public float RevisePower; public float ReviseRange; diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceValue.cs b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceValue.cs deleted file mode 100644 index 7905ecfdc..000000000 --- a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceValue.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Hid -{ - public struct HidVibrationDeviceValue - { - public HidVibrationDeviceType DeviceType; - public HidVibrationDevicePosition Position; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceHandle.cs b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceHandle.cs similarity index 80% rename from Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceHandle.cs rename to Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceHandle.cs index 4501c721a..fe50e6718 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceHandle.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceHandle.cs @@ -1,6 +1,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid { - public struct HidVibrationDeviceHandle + public struct VibrationDeviceHandle { public byte DeviceType; public byte PlayerId; diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDevicePosition.cs b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDevicePosition.cs similarity index 69% rename from Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDevicePosition.cs rename to Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDevicePosition.cs index 0ab84af3d..117451f16 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDevicePosition.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDevicePosition.cs @@ -1,6 +1,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid { - public enum HidVibrationDevicePosition + public enum VibrationDevicePosition { None, Left, diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceType.cs b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceType.cs similarity index 75% rename from Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceType.cs rename to Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceType.cs index 898384be7..4e5557c94 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceType.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceType.cs @@ -1,6 +1,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid { - public enum HidVibrationDeviceType + public enum VibrationDeviceType { None, LinearResonantActuator, diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceValue.cs b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceValue.cs new file mode 100644 index 000000000..91a23eb7e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceValue.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct VibrationDeviceValue + { + public VibrationDeviceType DeviceType; + public VibrationDevicePosition Position; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationValue.cs b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationValue.cs similarity index 78% rename from Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationValue.cs rename to Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationValue.cs index 3f45d2699..38ac9ccac 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationValue.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationValue.cs @@ -1,9 +1,8 @@ -using Ryujinx.HLE.HOS.Tamper; -using System; +using System; namespace Ryujinx.HLE.HOS.Services.Hid { - public struct HidVibrationValue + public struct VibrationValue { public float AmplitudeLow; public float FrequencyLow; @@ -12,7 +11,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid public override bool Equals(object obj) { - return obj is HidVibrationValue value && + return obj is VibrationValue value && AmplitudeLow == value.AmplitudeLow && AmplitudeHigh == value.AmplitudeHigh; } diff --git a/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs b/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs index abc76c62f..957cd5530 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs @@ -5,6 +5,7 @@ using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Services.Hid.HidServer; using Ryujinx.HLE.HOS.Services.Hid.Types; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad; using System; using System.Collections.Generic; using System.Runtime.InteropServices; @@ -26,9 +27,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid private bool _isFirmwareUpdateAvailableForSixAxisSensor; private bool _isSixAxisSensorUnalteredPassthroughEnabled; - private HidNpadJoyAssignmentMode _npadJoyAssignmentMode; - private HidNpadHandheldActivationMode _npadHandheldActivationMode; - private HidGyroscopeZeroDriftMode _gyroscopeZeroDriftMode; + private NpadHandheldActivationMode _npadHandheldActivationMode; + private GyroscopeZeroDriftMode _gyroscopeZeroDriftMode; private long _npadCommunicationMode; private uint _accelerometerPlayMode; @@ -37,22 +37,21 @@ namespace Ryujinx.HLE.HOS.Services.Hid #pragma warning restore CS0649 private float _sevenSixAxisSensorFusionStrength; - private HidSensorFusionParameters _sensorFusionParams; - private HidAccelerometerParameters _accelerometerParams; + private SensorFusionParameters _sensorFusionParams; + private AccelerometerParameters _accelerometerParams; public IHidServer(ServiceCtx context) : base(context.Device.System.HidServer) { _xpadIdEvent = new KEvent(context.Device.System.KernelContext); _palmaOperationCompleteEvent = new KEvent(context.Device.System.KernelContext); - _npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Dual; - _npadHandheldActivationMode = HidNpadHandheldActivationMode.Dual; - _gyroscopeZeroDriftMode = HidGyroscopeZeroDriftMode.Standard; + _npadHandheldActivationMode = NpadHandheldActivationMode.Dual; + _gyroscopeZeroDriftMode = GyroscopeZeroDriftMode.Standard; _isFirmwareUpdateAvailableForSixAxisSensor = false; - _sensorFusionParams = new HidSensorFusionParameters(); - _accelerometerParams = new HidAccelerometerParameters(); + _sensorFusionParams = new SensorFusionParameters(); + _accelerometerParams = new AccelerometerParameters(); // TODO: signal event at right place _xpadIdEvent.ReadableEvent.Signal(); @@ -393,7 +392,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid int sixAxisSensorHandle = context.RequestData.ReadInt32(); context.RequestData.BaseStream.Position += 4; // Padding - _sensorFusionParams = new HidSensorFusionParameters + _sensorFusionParams = new SensorFusionParameters { RevisePower = context.RequestData.ReadInt32(), ReviseRange = context.RequestData.ReadInt32() @@ -445,7 +444,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid int sixAxisSensorHandle = context.RequestData.ReadInt32(); context.RequestData.BaseStream.Position += 4; // Padding - _accelerometerParams = new HidAccelerometerParameters + _accelerometerParams = new AccelerometerParameters { X = context.RequestData.ReadInt32(), Y = context.RequestData.ReadInt32() @@ -539,7 +538,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid public ResultCode SetGyroscopeZeroDriftMode(ServiceCtx context) { int sixAxisSensorHandle = context.RequestData.ReadInt32(); - _gyroscopeZeroDriftMode = (HidGyroscopeZeroDriftMode)context.RequestData.ReadInt32(); + _gyroscopeZeroDriftMode = (GyroscopeZeroDriftMode)context.RequestData.ReadInt32(); long appletResourceUserId = context.RequestData.ReadInt64(); Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode }); @@ -570,7 +569,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid context.RequestData.BaseStream.Position += 4; // Padding long appletResourceUserId = context.RequestData.ReadInt64(); - _gyroscopeZeroDriftMode = HidGyroscopeZeroDriftMode.Standard; + _gyroscopeZeroDriftMode = GyroscopeZeroDriftMode.Standard; Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode }); @@ -909,54 +908,63 @@ namespace Ryujinx.HLE.HOS.Services.Hid // SetNpadJoyAssignmentModeSingleByDefault(uint HidControllerId, nn::applet::AppletResourceUserId) public ResultCode SetNpadJoyAssignmentModeSingleByDefault(ServiceCtx context) { - PlayerIndex hidControllerId = (PlayerIndex)context.RequestData.ReadInt32(); - long appletResourceUserId = context.RequestData.ReadInt64(); + NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); - _npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Single; - - Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, hidControllerId, _npadJoyAssignmentMode }); + if (HidUtils.IsValidNpadIdType(npadIdType)) + { + context.Device.Hid.SharedMemory.Npads[(int)HidUtils.GetIndexFromNpadIdType(npadIdType)].InternalState.JoyAssignmentMode = NpadJoyAssignmentMode.Single; + } return ResultCode.Success; } [CommandHipc(123)] - // SetNpadJoyAssignmentModeSingle(uint HidControllerId, nn::applet::AppletResourceUserId, long HidNpadJoyDeviceType) + // SetNpadJoyAssignmentModeSingle(uint npadIdType, nn::applet::AppletResourceUserId, uint npadJoyDeviceType) public ResultCode SetNpadJoyAssignmentModeSingle(ServiceCtx context) { - PlayerIndex hidControllerId = (PlayerIndex)context.RequestData.ReadInt32(); - long appletResourceUserId = context.RequestData.ReadInt64(); - HidNpadJoyDeviceType hidNpadJoyDeviceType = (HidNpadJoyDeviceType)context.RequestData.ReadInt64(); + NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + NpadJoyDeviceType npadJoyDeviceType = (NpadJoyDeviceType)context.RequestData.ReadUInt32(); - _npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Single; - - Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, hidControllerId, hidNpadJoyDeviceType, _npadJoyAssignmentMode }); + if (HidUtils.IsValidNpadIdType(npadIdType)) + { + SetNpadJoyAssignmentModeSingleWithDestinationImpl(context, npadIdType, appletResourceUserId, npadJoyDeviceType, out _, out _); + } return ResultCode.Success; } [CommandHipc(124)] - // SetNpadJoyAssignmentModeDual(uint HidControllerId, nn::applet::AppletResourceUserId) + // SetNpadJoyAssignmentModeDual(uint npadIdType, nn::applet::AppletResourceUserId) public ResultCode SetNpadJoyAssignmentModeDual(ServiceCtx context) { - PlayerIndex hidControllerId = HidUtils.GetIndexFromNpadIdType((NpadIdType)context.RequestData.ReadInt32()); - long appletResourceUserId = context.RequestData.ReadInt64(); + NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); - _npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Dual; - - Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, hidControllerId, _npadJoyAssignmentMode }); + if (HidUtils.IsValidNpadIdType(npadIdType)) + { + context.Device.Hid.SharedMemory.Npads[(int)HidUtils.GetIndexFromNpadIdType(npadIdType)].InternalState.JoyAssignmentMode = NpadJoyAssignmentMode.Dual; + } return ResultCode.Success; } [CommandHipc(125)] - // MergeSingleJoyAsDualJoy(uint SingleJoyId0, uint SingleJoyId1, nn::applet::AppletResourceUserId) + // MergeSingleJoyAsDualJoy(uint npadIdType0, uint npadIdType1, nn::applet::AppletResourceUserId) public ResultCode MergeSingleJoyAsDualJoy(ServiceCtx context) { - long singleJoyId0 = context.RequestData.ReadInt32(); - long singleJoyId1 = context.RequestData.ReadInt32(); - long appletResourceUserId = context.RequestData.ReadInt64(); + NpadIdType npadIdType0 = (NpadIdType)context.RequestData.ReadUInt32(); + NpadIdType npadIdType1 = (NpadIdType)context.RequestData.ReadUInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); - Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, singleJoyId0, singleJoyId1 }); + if (HidUtils.IsValidNpadIdType(npadIdType0) && HidUtils.IsValidNpadIdType(npadIdType1)) + { + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, npadIdType0, npadIdType1 }); + } return ResultCode.Success; } @@ -988,7 +996,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid public ResultCode SetNpadHandheldActivationMode(ServiceCtx context) { long appletResourceUserId = context.RequestData.ReadInt64(); - _npadHandheldActivationMode = (HidNpadHandheldActivationMode)context.RequestData.ReadInt64(); + _npadHandheldActivationMode = (NpadHandheldActivationMode)context.RequestData.ReadInt64(); Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadHandheldActivationMode }); @@ -1049,37 +1057,47 @@ namespace Ryujinx.HLE.HOS.Services.Hid } [CommandHipc(133)] // 5.0.0+ - // SetNpadJoyAssignmentModeSingleWithDestination(uint HidControllerId, long HidNpadJoyDeviceType, nn::applet::AppletResourceUserId) -> bool Unknown0, uint Unknown1 + // SetNpadJoyAssignmentModeSingleWithDestination(uint npadIdType, uint npadJoyDeviceType, nn::applet::AppletResourceUserId) -> bool npadIdTypeIsSet, uint npadIdTypeSet public ResultCode SetNpadJoyAssignmentModeSingleWithDestination(ServiceCtx context) { - PlayerIndex hidControllerId = (PlayerIndex)context.RequestData.ReadInt32(); - HidNpadJoyDeviceType hidNpadJoyDeviceType = (HidNpadJoyDeviceType)context.RequestData.ReadInt64(); - long appletResourceUserId = context.RequestData.ReadInt64(); + NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadInt32(); + NpadJoyDeviceType npadJoyDeviceType = (NpadJoyDeviceType)context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); - _npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Single; + if (HidUtils.IsValidNpadIdType(npadIdType)) + { + SetNpadJoyAssignmentModeSingleWithDestinationImpl(context, npadIdType, appletResourceUserId, npadJoyDeviceType, out NpadIdType npadIdTypeSet, out bool npadIdTypeIsSet); - context.ResponseData.Write(0); //Unknown0 - context.ResponseData.Write(0); //Unknown1 - - Logger.Stub?.PrintStub(LogClass.ServiceHid, new { - appletResourceUserId, - hidControllerId, - hidNpadJoyDeviceType, - _npadJoyAssignmentMode, - Unknown0 = 0, - Unknown1 = 0 - }); + if (npadIdTypeIsSet) + { + context.ResponseData.Write(npadIdTypeIsSet); + context.ResponseData.Write((uint)npadIdTypeSet); + } + } return ResultCode.Success; } + private void SetNpadJoyAssignmentModeSingleWithDestinationImpl(ServiceCtx context, NpadIdType npadIdType, long appletResourceUserId, NpadJoyDeviceType npadJoyDeviceType, out NpadIdType npadIdTypeSet, out bool npadIdTypeIsSet) + { + npadIdTypeSet = default; + npadIdTypeIsSet = false; + + context.Device.Hid.SharedMemory.Npads[(int)HidUtils.GetIndexFromNpadIdType(npadIdType)].InternalState.JoyAssignmentMode = NpadJoyAssignmentMode.Single; + + // TODO: Service seems to use the npadJoyDeviceType to find the nearest other Npad available and merge them to dual. + // If one is found, it returns the npadIdType of the other Npad and a bool. + // If not, it returns nothing. + } + [CommandHipc(200)] // GetVibrationDeviceInfo(nn::hid::VibrationDeviceHandle) -> nn::hid::VibrationDeviceInfo public ResultCode GetVibrationDeviceInfo(ServiceCtx context) { - HidVibrationDeviceHandle deviceHandle = context.RequestData.ReadStruct(); - NpadStyleIndex deviceType = (NpadStyleIndex)deviceHandle.DeviceType; - NpadIdType npadIdType = (NpadIdType)deviceHandle.PlayerId; + VibrationDeviceHandle deviceHandle = context.RequestData.ReadStruct(); + NpadStyleIndex deviceType = (NpadStyleIndex)deviceHandle.DeviceType; + NpadIdType npadIdType = (NpadIdType)deviceHandle.PlayerId; if (deviceType < NpadStyleIndex.System || deviceType >= NpadStyleIndex.FullKey) { @@ -1093,28 +1111,28 @@ namespace Ryujinx.HLE.HOS.Services.Hid return ResultCode.InvalidDeviceIndex; } - HidVibrationDeviceType vibrationDeviceType = HidVibrationDeviceType.None; + VibrationDeviceType vibrationDeviceType = VibrationDeviceType.None; - if (Enum.IsDefined(deviceType)) + if (Enum.IsDefined(deviceType)) { - vibrationDeviceType = HidVibrationDeviceType.LinearResonantActuator; + vibrationDeviceType = VibrationDeviceType.LinearResonantActuator; } else if ((uint)deviceType == 8) { - vibrationDeviceType = HidVibrationDeviceType.GcErm; + vibrationDeviceType = VibrationDeviceType.GcErm; } - HidVibrationDevicePosition vibrationDevicePosition = HidVibrationDevicePosition.None; + VibrationDevicePosition vibrationDevicePosition = VibrationDevicePosition.None; - if (vibrationDeviceType == HidVibrationDeviceType.LinearResonantActuator) + if (vibrationDeviceType == VibrationDeviceType.LinearResonantActuator) { if (deviceHandle.Position == 0) { - vibrationDevicePosition = HidVibrationDevicePosition.Left; + vibrationDevicePosition = VibrationDevicePosition.Left; } else if (deviceHandle.Position == 1) { - vibrationDevicePosition = HidVibrationDevicePosition.Right; + vibrationDevicePosition = VibrationDevicePosition.Right; } else { @@ -1122,7 +1140,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid } } - HidVibrationDeviceValue deviceInfo = new HidVibrationDeviceValue + VibrationDeviceValue deviceInfo = new VibrationDeviceValue { DeviceType = vibrationDeviceType, Position = vibrationDevicePosition @@ -1140,7 +1158,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid // SendVibrationValue(nn::hid::VibrationDeviceHandle, nn::hid::VibrationValue, nn::applet::AppletResourceUserId) public ResultCode SendVibrationValue(ServiceCtx context) { - HidVibrationDeviceHandle deviceHandle = new HidVibrationDeviceHandle + VibrationDeviceHandle deviceHandle = new VibrationDeviceHandle { DeviceType = context.RequestData.ReadByte(), PlayerId = context.RequestData.ReadByte(), @@ -1148,7 +1166,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid Reserved = context.RequestData.ReadByte() }; - HidVibrationValue vibrationValue = new HidVibrationValue + VibrationValue vibrationValue = new VibrationValue { AmplitudeLow = context.RequestData.ReadSingle(), FrequencyLow = context.RequestData.ReadSingle(), @@ -1158,7 +1176,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid long appletResourceUserId = context.RequestData.ReadInt64(); - Dictionary dualVibrationValues = new Dictionary(); + Dictionary dualVibrationValues = new Dictionary(); dualVibrationValues[deviceHandle.Position] = vibrationValue; @@ -1171,7 +1189,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid // GetActualVibrationValue(nn::hid::VibrationDeviceHandle, nn::applet::AppletResourceUserId) -> nn::hid::VibrationValue public ResultCode GetActualVibrationValue(ServiceCtx context) { - HidVibrationDeviceHandle deviceHandle = new HidVibrationDeviceHandle + VibrationDeviceHandle deviceHandle = new VibrationDeviceHandle { DeviceType = context.RequestData.ReadByte(), PlayerId = context.RequestData.ReadByte(), @@ -1181,7 +1199,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid long appletResourceUserId = context.RequestData.ReadInt64(); - HidVibrationValue vibrationValue = context.Device.Hid.Npads.GetLastVibrationValue((PlayerIndex)deviceHandle.PlayerId, deviceHandle.Position); + VibrationValue vibrationValue = context.Device.Hid.Npads.GetLastVibrationValue((PlayerIndex)deviceHandle.PlayerId, deviceHandle.Position); context.ResponseData.Write(vibrationValue.AmplitudeLow); context.ResponseData.Write(vibrationValue.FrequencyLow); @@ -1234,12 +1252,12 @@ namespace Ryujinx.HLE.HOS.Services.Hid context.Memory.Read(context.Request.PtrBuff[1].Position, vibrationValueBuffer); - Span deviceHandles = MemoryMarshal.Cast(vibrationDeviceHandleBuffer); - Span vibrationValues = MemoryMarshal.Cast(vibrationValueBuffer); + Span deviceHandles = MemoryMarshal.Cast(vibrationDeviceHandleBuffer); + Span vibrationValues = MemoryMarshal.Cast(vibrationValueBuffer); if (!deviceHandles.IsEmpty && vibrationValues.Length == deviceHandles.Length) { - Dictionary dualVibrationValues = new Dictionary(); + Dictionary dualVibrationValues = new Dictionary(); PlayerIndex currentIndex = (PlayerIndex)deviceHandles[0].PlayerId; for (int deviceCounter = 0; deviceCounter < deviceHandles.Length; deviceCounter++) @@ -1250,7 +1268,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid if (index != currentIndex || dualVibrationValues.Count == 2) { context.Device.Hid.Npads.UpdateRumbleQueue(currentIndex, dualVibrationValues); - dualVibrationValues = new Dictionary(); + dualVibrationValues = new Dictionary(); } dualVibrationValues[position] = vibrationValues[deviceCounter]; diff --git a/Ryujinx.Input/HLE/NpadController.cs b/Ryujinx.Input/HLE/NpadController.cs index 1e0789b77..6706ad0b6 100644 --- a/Ryujinx.Input/HLE/NpadController.cs +++ b/Ryujinx.Input/HLE/NpadController.cs @@ -543,14 +543,14 @@ namespace Ryujinx.Input.HLE Dispose(true); } - public void UpdateRumble(ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> queue) + public void UpdateRumble(ConcurrentQueue<(VibrationValue, VibrationValue)> queue) { - if (queue.TryDequeue(out (HidVibrationValue, HidVibrationValue) dualVibrationValue)) + if (queue.TryDequeue(out (VibrationValue, VibrationValue) dualVibrationValue)) { if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Rumble.EnableRumble) { - HidVibrationValue leftVibrationValue = dualVibrationValue.Item1; - HidVibrationValue rightVibrationValue = dualVibrationValue.Item2; + VibrationValue leftVibrationValue = dualVibrationValue.Item1; + VibrationValue rightVibrationValue = dualVibrationValue.Item2; float low = Math.Min(1f, (float)((rightVibrationValue.AmplitudeLow * 0.85 + rightVibrationValue.AmplitudeHigh * 0.15) * controllerConfig.Rumble.StrongRumble)); float high = Math.Min(1f, (float)((leftVibrationValue.AmplitudeLow * 0.15 + leftVibrationValue.AmplitudeHigh * 0.85) * controllerConfig.Rumble.WeakRumble)); From 43b4b34376cdea486906f8bb4058dda3be7e1bd8 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Thu, 12 May 2022 14:47:13 +0100 Subject: [PATCH 11/33] Implement Viewport Transform Disable (#3328) * Initial implementation (no specialization) * Use specialization * Fix render scale, increase code gen version * Revert accidental change * Address Feedback --- Ryujinx.Graphics.GAL/IPipeline.cs | 2 +- .../Commands/SetViewportsCommand.cs | 6 ++- .../Multithreading/ThreadedPipeline.cs | 4 +- Ryujinx.Graphics.GAL/SupportBufferUpdater.cs | 7 +++ .../Engine/Threed/StateUpdater.cs | 23 +++++++-- .../Shader/Cache/Migration.cs | 3 +- .../Shader/DiskCache/DiskCacheGpuAccessor.cs | 6 +++ .../Shader/DiskCache/DiskCacheHostStorage.cs | 2 +- Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs | 6 +++ .../Shader/GpuChannelGraphicsState.cs | 9 +++- Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs | 8 +-- .../Shader/ShaderCacheHashTable.cs | 4 +- .../Shader/ShaderSpecializationList.cs | 9 +++- .../Shader/ShaderSpecializationState.cs | 8 ++- Ryujinx.Graphics.OpenGL/Pipeline.cs | 15 +++++- .../CodeGen/Glsl/Declarations.cs | 9 ++-- .../CodeGen/Glsl/DefaultNames.cs | 1 + .../CodeGen/Glsl/OperandManager.cs | 5 +- Ryujinx.Graphics.Shader/IGpuAccessor.cs | 9 ++++ .../Instructions/InstEmitAttribute.cs | 28 ++++++++++- Ryujinx.Graphics.Shader/SupportBuffer.cs | 3 ++ .../Translation/AttributeConsts.cs | 3 ++ .../Translation/EmitterContext.cs | 49 ++++++++++++++++++- .../Translation/ShaderConfig.cs | 7 +++ 24 files changed, 200 insertions(+), 26 deletions(-) diff --git a/Ryujinx.Graphics.GAL/IPipeline.cs b/Ryujinx.Graphics.GAL/IPipeline.cs index 75c3077eb..aec096e72 100644 --- a/Ryujinx.Graphics.GAL/IPipeline.cs +++ b/Ryujinx.Graphics.GAL/IPipeline.cs @@ -94,7 +94,7 @@ namespace Ryujinx.Graphics.GAL void SetVertexAttribs(ReadOnlySpan vertexAttribs); void SetVertexBuffers(ReadOnlySpan vertexBuffers); - void SetViewports(int first, ReadOnlySpan viewports); + void SetViewports(int first, ReadOnlySpan viewports, bool disableTransform); void TextureBarrier(); void TextureBarrierTiled(); diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetViewportsCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetViewportsCommand.cs index e11b00e84..b208d9fe8 100644 --- a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetViewportsCommand.cs +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetViewportsCommand.cs @@ -9,17 +9,19 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands public CommandType CommandType => CommandType.SetViewports; private int _first; private SpanRef _viewports; + private bool _disableTransform; - public void Set(int first, SpanRef viewports) + public void Set(int first, SpanRef viewports, bool disableTransform) { _first = first; _viewports = viewports; + _disableTransform = disableTransform; } public static void Run(ref SetViewportsCommand command, ThreadedRenderer threaded, IRenderer renderer) { ReadOnlySpan viewports = command._viewports.Get(threaded); - renderer.Pipeline.SetViewports(command._first, viewports); + renderer.Pipeline.SetViewports(command._first, viewports, command._disableTransform); command._viewports.Dispose(threaded); } } diff --git a/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs b/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs index b6acfaa83..010ee7e65 100644 --- a/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs +++ b/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs @@ -304,9 +304,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading _renderer.QueueCommand(); } - public void SetViewports(int first, ReadOnlySpan viewports) + public void SetViewports(int first, ReadOnlySpan viewports, bool disableTransform) { - _renderer.New().Set(first, _renderer.CopySpan(viewports)); + _renderer.New().Set(first, _renderer.CopySpan(viewports), disableTransform); _renderer.QueueCommand(); } diff --git a/Ryujinx.Graphics.GAL/SupportBufferUpdater.cs b/Ryujinx.Graphics.GAL/SupportBufferUpdater.cs index cb24bdd75..da7a24613 100644 --- a/Ryujinx.Graphics.GAL/SupportBufferUpdater.cs +++ b/Ryujinx.Graphics.GAL/SupportBufferUpdater.cs @@ -72,6 +72,13 @@ namespace Ryujinx.Graphics.GAL UpdateGenericField(SupportBuffer.FragmentIsBgraOffset, data, Data.FragmentIsBgra.ToSpan(), offset, count); } + public void UpdateViewportInverse(Vector4 data) + { + Data.ViewportInverse = data; + + MarkDirty(SupportBuffer.ViewportInverseOffset, SupportBuffer.FieldSize); + } + public void Commit() { if (_startOffset != -1) diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs index 3bc15a317..d0c3bc5ae 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs @@ -113,7 +113,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed nameof(ThreedClassState.DepthMode), nameof(ThreedClassState.ViewportTransform), nameof(ThreedClassState.ViewportExtents), - nameof(ThreedClassState.YControl)), + nameof(ThreedClassState.YControl), + nameof(ThreedClassState.ViewportTransformEnable)), new StateUpdateCallbackEntry(UpdatePolygonMode, nameof(ThreedClassState.PolygonModeFront), @@ -200,7 +201,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed // of the shader for the new state. if (_shaderSpecState != null) { - if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState())) + if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState(), GetGraphicsState())) { ForceShaderUpdate(); } @@ -568,6 +569,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed var yControl = _state.State.YControl; var face = _state.State.FaceState; + bool disableTransform = _state.State.ViewportTransformEnable == 0; + UpdateFrontFace(yControl, face.FrontFace); UpdateDepthMode(); @@ -577,6 +580,17 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed for (int index = 0; index < Constants.TotalViewports; index++) { + if (disableTransform) + { + ref var scissor = ref _state.State.ScreenScissorState; + + float rScale = _channel.TextureManager.RenderTargetScale; + var scissorRect = new RectangleF(0, 0, (scissor.X + scissor.Width) * rScale, (scissor.Y + scissor.Height) * rScale); + + viewports[index] = new Viewport(scissorRect, ViewportSwizzle.PositiveX, ViewportSwizzle.PositiveY, ViewportSwizzle.PositiveZ, ViewportSwizzle.PositiveW, 0, 1); + continue; + } + ref var transform = ref _state.State.ViewportTransform[index]; ref var extents = ref _state.State.ViewportExtents[index]; @@ -628,7 +642,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed viewports[index] = new Viewport(region, swizzleX, swizzleY, swizzleZ, swizzleW, depthNear, depthFar); } - _context.Renderer.Pipeline.SetViewports(0, viewports); + _context.Renderer.Pipeline.SetViewports(0, viewports, disableTransform); } /// @@ -1194,7 +1208,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed return new GpuChannelGraphicsState( _state.State.EarlyZForce, _drawState.Topology, - _state.State.TessMode); + _state.State.TessMode, + _state.State.ViewportTransformEnable == 0); } /// diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs index 27fac8f37..4de6eff91 100644 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs +++ b/Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs @@ -166,7 +166,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache GpuChannelGraphicsState graphicsState = new GpuChannelGraphicsState( accessorHeader.StateFlags.HasFlag(GuestGpuStateFlags.EarlyZForce), topology, - tessMode); + tessMode, + false); TransformFeedbackDescriptor[] tfdNew = null; diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs index b1c04eac0..bc63f714d 100644 --- a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs +++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs @@ -185,6 +185,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache return _oldSpecState.GraphicsState.EarlyZForce; } + /// + public bool QueryViewportTransformDisable() + { + return _oldSpecState.GraphicsState.ViewportTransformDisable; + } + /// public void RegisterTexture(int handle, int cbufSlot) { diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index 0028e8796..5d99957f0 100644 --- a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs +++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs @@ -21,7 +21,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMinor = 1; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; - private const uint CodeGenVersion = 0; + private const uint CodeGenVersion = 1; private const string SharedTocFileName = "shared.toc"; private const string SharedDataFileName = "shared.data"; diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs index 192467b75..5cd966af7 100644 --- a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs +++ b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs @@ -217,6 +217,12 @@ namespace Ryujinx.Graphics.Gpu.Shader return _state.GraphicsState.EarlyZForce; } + /// + public bool QueryViewportTransformDisable() + { + return _state.GraphicsState.ViewportTransformDisable; + } + /// public void RegisterTexture(int handle, int cbufSlot) { diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs b/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs index 5eb31db69..92ec117f3 100644 --- a/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs +++ b/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs @@ -25,17 +25,24 @@ namespace Ryujinx.Graphics.Gpu.Shader /// public readonly TessMode TessellationMode; + /// + /// Indicates whenever the viewport transform is disabled. + /// + public readonly bool ViewportTransformDisable; + /// /// Creates a new GPU graphics state. /// /// Early Z force enable /// Primitive topology /// Tessellation mode - public GpuChannelGraphicsState(bool earlyZForce, PrimitiveTopology topology, TessMode tessellationMode) + /// Indicates whenever the viewport transform is disabled + public GpuChannelGraphicsState(bool earlyZForce, PrimitiveTopology topology, TessMode tessellationMode, bool viewportTransformDisable) { EarlyZForce = earlyZForce; Topology = topology; TessellationMode = tessellationMode; + ViewportTransformDisable = viewportTransformDisable; } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs index 03d5ecade..df4b9d128 100644 --- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs +++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -249,12 +249,12 @@ namespace Ryujinx.Graphics.Gpu.Shader GpuChannelGraphicsState graphicsState, ShaderAddresses addresses) { - if (_gpPrograms.TryGetValue(addresses, out var gpShaders) && IsShaderEqual(channel, poolState, gpShaders, addresses)) + if (_gpPrograms.TryGetValue(addresses, out var gpShaders) && IsShaderEqual(channel, poolState, graphicsState, gpShaders, addresses)) { return gpShaders; } - if (_graphicsShaderCache.TryFind(channel, poolState, addresses, out gpShaders, out var cachedGuestCode)) + if (_graphicsShaderCache.TryFind(channel, poolState, graphicsState, addresses, out gpShaders, out var cachedGuestCode)) { _gpPrograms[addresses] = gpShaders; return gpShaders; @@ -429,12 +429,14 @@ namespace Ryujinx.Graphics.Gpu.Shader /// /// GPU channel using the shader /// GPU channel state to verify shader compatibility + /// GPU channel graphics state to verify shader compatibility /// Cached graphics shaders /// GPU virtual addresses of all enabled shader stages /// True if the code is different, false otherwise private static bool IsShaderEqual( GpuChannel channel, GpuChannelPoolState poolState, + GpuChannelGraphicsState graphicsState, CachedShaderProgram gpShaders, ShaderAddresses addresses) { @@ -452,7 +454,7 @@ namespace Ryujinx.Graphics.Gpu.Shader } } - return gpShaders.SpecializationState.MatchesGraphics(channel, poolState); + return gpShaders.SpecializationState.MatchesGraphics(channel, poolState, graphicsState); } /// diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs index 065f9ba90..3d74e53a1 100644 --- a/Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs +++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs @@ -208,6 +208,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// /// GPU channel /// Texture pool state + /// Graphics state /// Guest addresses of the shaders to find /// Cached host program for the given state, if found /// Cached guest code, if any found @@ -215,6 +216,7 @@ namespace Ryujinx.Graphics.Gpu.Shader public bool TryFind( GpuChannel channel, GpuChannelPoolState poolState, + GpuChannelGraphicsState graphicsState, ShaderAddresses addresses, out CachedShaderProgram program, out CachedGraphicsGuestCode guestCode) @@ -234,7 +236,7 @@ namespace Ryujinx.Graphics.Gpu.Shader if (found && _shaderPrograms.TryGetValue(idTable, out ShaderSpecializationList specList)) { - return specList.TryFindForGraphics(channel, poolState, out program); + return specList.TryFindForGraphics(channel, poolState, graphicsState, out program); } return false; diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs index 87e087544..e3e57d745 100644 --- a/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs +++ b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs @@ -24,13 +24,18 @@ namespace Ryujinx.Graphics.Gpu.Shader /// /// GPU channel /// Texture pool state + /// Graphics state /// Cached program, if found /// True if a compatible program is found, false otherwise - public bool TryFindForGraphics(GpuChannel channel, GpuChannelPoolState poolState, out CachedShaderProgram program) + public bool TryFindForGraphics( + GpuChannel channel, + GpuChannelPoolState poolState, + GpuChannelGraphicsState graphicsState, + out CachedShaderProgram program) { foreach (var entry in _entries) { - if (entry.SpecializationState.MatchesGraphics(channel, poolState)) + if (entry.SpecializationState.MatchesGraphics(channel, poolState, graphicsState)) { program = entry; return true; diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs index 2bbc3d2c1..418c7b1a7 100644 --- a/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs +++ b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs @@ -395,9 +395,15 @@ namespace Ryujinx.Graphics.Gpu.Shader ///
/// GPU channel /// Texture pool state + /// Graphics state /// True if the state matches, false otherwise - public bool MatchesGraphics(GpuChannel channel, GpuChannelPoolState poolState) + public bool MatchesGraphics(GpuChannel channel, GpuChannelPoolState poolState, GpuChannelGraphicsState graphicsState) { + if (graphicsState.ViewportTransformDisable != GraphicsState.ViewportTransformDisable) + { + return false; + } + return Matches(channel, poolState, isCompute: false); } diff --git a/Ryujinx.Graphics.OpenGL/Pipeline.cs b/Ryujinx.Graphics.OpenGL/Pipeline.cs index 114fa6855..0326f9809 100644 --- a/Ryujinx.Graphics.OpenGL/Pipeline.cs +++ b/Ryujinx.Graphics.OpenGL/Pipeline.cs @@ -1266,7 +1266,7 @@ namespace Ryujinx.Graphics.OpenGL _vertexArray.SetVertexBuffers(vertexBuffers); } - public void SetViewports(int first, ReadOnlySpan viewports) + public void SetViewports(int first, ReadOnlySpan viewports, bool disableTransform) { Array.Resize(ref _viewportArray, viewports.Length * 4); Array.Resize(ref _depthRangeArray, viewports.Length * 2); @@ -1305,6 +1305,19 @@ namespace Ryujinx.Graphics.OpenGL GL.ViewportArray(first, viewports.Length, viewportArray); GL.DepthRangeArray(first, viewports.Length, depthRangeArray); + + float disableTransformF = disableTransform ? 1.0f : 0.0f; + if (_supportBuffer.Data.ViewportInverse.W != disableTransformF || disableTransform) + { + float scale = _renderScale[0].X; + _supportBuffer.UpdateViewportInverse(new Vector4 + { + X = scale * 2f / viewports[first].Region.Width, + Y = scale * 2f / viewports[first].Region.Height, + Z = 1, + W = disableTransformF + }); + } } public void TextureBarrier() diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs index c955a6161..59a7ccdca 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs @@ -249,7 +249,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl context.AppendLine(); } } - else if (isFragment) + else if (isFragment || context.Config.Stage == ShaderStage.Vertex) { DeclareSupportUniformBlock(context, context.Config.Stage, 0); } @@ -615,8 +615,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl private static void DeclareSupportUniformBlock(CodeGenContext context, ShaderStage stage, int scaleElements) { - bool isFragment = stage == ShaderStage.Fragment; - if (!isFragment && scaleElements == 0) + bool needsSupportBlock = stage == ShaderStage.Fragment || + (context.Config.LastInVertexPipeline && context.Config.GpuAccessor.QueryViewportTransformDisable()); + + if (!needsSupportBlock && scaleElements == 0) { return; } @@ -630,6 +632,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl case ShaderStage.Vertex: context.AppendLine($"uint {DefaultNames.SupportBlockAlphaTestName};"); context.AppendLine($"bool {DefaultNames.SupportBlockIsBgraName}[{SupportBuffer.FragmentIsBgraCount}];"); + context.AppendLine($"vec4 {DefaultNames.SupportBlockViewportInverse};"); context.AppendLine($"int {DefaultNames.SupportBlockFragmentScaleCount};"); break; case ShaderStage.Compute: diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs index 76203522e..3ab4814ce 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs @@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl public const string SupportBlockName = "support_block"; public const string SupportBlockAlphaTestName = "s_alpha_test"; public const string SupportBlockIsBgraName = "s_is_bgra"; + public const string SupportBlockViewportInverse = "s_viewport_inverse"; public const string SupportBlockFragmentScaleCount = "s_frag_scale_count"; public const string SupportBlockRenderScaleName = "s_render_scale"; diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs index 2d6607ad0..334c744d7 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs @@ -84,7 +84,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { AttributeConsts.FragmentOutputIsBgraBase + 16, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[4]", VariableType.Bool) }, { AttributeConsts.FragmentOutputIsBgraBase + 20, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[5]", VariableType.Bool) }, { AttributeConsts.FragmentOutputIsBgraBase + 24, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[6]", VariableType.Bool) }, - { AttributeConsts.FragmentOutputIsBgraBase + 28, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[7]", VariableType.Bool) } + { AttributeConsts.FragmentOutputIsBgraBase + 28, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[7]", VariableType.Bool) }, + + { AttributeConsts.SupportBlockViewInverseX, new BuiltInAttribute($"{DefaultNames.SupportBlockViewportInverse}.x", VariableType.F32) }, + { AttributeConsts.SupportBlockViewInverseY, new BuiltInAttribute($"{DefaultNames.SupportBlockViewportInverse}.y", VariableType.F32) } }; private Dictionary _locals; diff --git a/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/Ryujinx.Graphics.Shader/IGpuAccessor.cs index 9c624d90d..180fc1874 100644 --- a/Ryujinx.Graphics.Shader/IGpuAccessor.cs +++ b/Ryujinx.Graphics.Shader/IGpuAccessor.cs @@ -329,6 +329,15 @@ namespace Ryujinx.Graphics.Shader return false; } + /// + /// Queries if host state disables the viewport transform. + /// + /// True if the viewport transform is disabled + bool QueryViewportTransformDisable() + { + return false; + } + /// /// Registers a texture used by the shader. /// diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs index 1cdb38422..6ce2e5372 100644 --- a/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs @@ -206,7 +206,33 @@ namespace Ryujinx.Graphics.Shader.Instructions if (emit) { - context.EmitVertex(); + if (context.Config.LastInVertexPipeline) + { + context.PrepareForVertexReturn(out var tempXLocal, out var tempYLocal, out var tempZLocal); + + context.EmitVertex(); + + // Restore output position value before transformation. + + if (tempXLocal != null) + { + context.Copy(Attribute(AttributeConsts.PositionX), tempXLocal); + } + + if (tempYLocal != null) + { + context.Copy(Attribute(AttributeConsts.PositionY), tempYLocal); + } + + if (tempZLocal != null) + { + context.Copy(Attribute(AttributeConsts.PositionZ), tempZLocal); + } + } + else + { + context.EmitVertex(); + } } if (cut) diff --git a/Ryujinx.Graphics.Shader/SupportBuffer.cs b/Ryujinx.Graphics.Shader/SupportBuffer.cs index 47a47ea63..28a48c2ad 100644 --- a/Ryujinx.Graphics.Shader/SupportBuffer.cs +++ b/Ryujinx.Graphics.Shader/SupportBuffer.cs @@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Shader public static int FragmentAlphaTestOffset; public static int FragmentIsBgraOffset; + public static int ViewportInverseOffset; public static int FragmentRenderScaleCountOffset; public static int GraphicsRenderScaleOffset; public static int ComputeRenderScaleOffset; @@ -40,6 +41,7 @@ namespace Ryujinx.Graphics.Shader FragmentAlphaTestOffset = OffsetOf(ref instance, ref instance.FragmentAlphaTest); FragmentIsBgraOffset = OffsetOf(ref instance, ref instance.FragmentIsBgra); + ViewportInverseOffset = OffsetOf(ref instance, ref instance.ViewportInverse); FragmentRenderScaleCountOffset = OffsetOf(ref instance, ref instance.FragmentRenderScaleCount); GraphicsRenderScaleOffset = OffsetOf(ref instance, ref instance.RenderScale); ComputeRenderScaleOffset = GraphicsRenderScaleOffset + FieldSize; @@ -47,6 +49,7 @@ namespace Ryujinx.Graphics.Shader public Vector4 FragmentAlphaTest; public Array8> FragmentIsBgra; + public Vector4 ViewportInverse; public Vector4 FragmentRenderScaleCount; // Render scale max count: 1 + 32 + 8. First scale is fragment output scale, others are textures/image inputs. diff --git a/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs b/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs index 370af0097..ada60ab97 100644 --- a/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs +++ b/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs @@ -67,6 +67,9 @@ namespace Ryujinx.Graphics.Shader.Translation public const int FragmentOutputIsBgraBase = 0x1000100; public const int FragmentOutputIsBgraEnd = FragmentOutputIsBgraBase + 8 * 4; + public const int SupportBlockViewInverseX = 0x1000200; + public const int SupportBlockViewInverseY = 0x1000204; + public const int ThreadIdX = 0x2000000; public const int ThreadIdY = 0x2000004; public const int ThreadIdZ = 0x2000008; diff --git a/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs b/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs index 775f12179..ba3b551d9 100644 --- a/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs +++ b/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs @@ -154,9 +154,56 @@ namespace Ryujinx.Graphics.Shader.Translation return label; } + public void PrepareForVertexReturn() + { + if (Config.GpuAccessor.QueryViewportTransformDisable()) + { + Operand x = Attribute(AttributeConsts.PositionX | AttributeConsts.LoadOutputMask); + Operand y = Attribute(AttributeConsts.PositionY | AttributeConsts.LoadOutputMask); + Operand xScale = Attribute(AttributeConsts.SupportBlockViewInverseX); + Operand yScale = Attribute(AttributeConsts.SupportBlockViewInverseY); + Operand negativeOne = ConstF(-1.0f); + + this.Copy(Attribute(AttributeConsts.PositionX), this.FPFusedMultiplyAdd(x, xScale, negativeOne)); + this.Copy(Attribute(AttributeConsts.PositionY), this.FPFusedMultiplyAdd(y, yScale, negativeOne)); + } + } + + public void PrepareForVertexReturn(out Operand oldXLocal, out Operand oldYLocal, out Operand oldZLocal) + { + if (Config.GpuAccessor.QueryViewportTransformDisable()) + { + oldXLocal = Local(); + this.Copy(oldXLocal, Attribute(AttributeConsts.PositionX | AttributeConsts.LoadOutputMask)); + oldYLocal = Local(); + this.Copy(oldYLocal, Attribute(AttributeConsts.PositionY | AttributeConsts.LoadOutputMask)); + } + else + { + oldXLocal = null; + oldYLocal = null; + } + + // Will be used by Vulkan backend for depth mode emulation. + oldZLocal = null; + + PrepareForVertexReturn(); + } + public void PrepareForReturn() { - if (!IsNonMain && Config.Stage == ShaderStage.Fragment) + if (IsNonMain) + { + return; + } + + if (Config.LastInVertexPipeline && + (Config.Stage == ShaderStage.Vertex || Config.Stage == ShaderStage.TessellationEvaluation) && + (Config.Options.Flags & TranslationFlags.VertexA) == 0) + { + PrepareForVertexReturn(); + } + else if (Config.Stage == ShaderStage.Fragment) { if (Config.OmapDepth) { diff --git a/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs b/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs index 23b8b9510..27d72cd53 100644 --- a/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs +++ b/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs @@ -14,6 +14,7 @@ namespace Ryujinx.Graphics.Shader.Translation public ShaderStage Stage { get; } public bool GpPassthrough { get; } + public bool LastInVertexPipeline { get; private set; } public int ThreadsPerInputPrimitive { get; } @@ -135,6 +136,7 @@ namespace Ryujinx.Graphics.Shader.Translation OmapSampleMask = header.OmapSampleMask; OmapDepth = header.OmapDepth; TransformFeedbackEnabled = gpuAccessor.QueryTransformFeedbackEnabled(); + LastInVertexPipeline = header.Stage < ShaderStage.Fragment; } public int GetDepthRegister() @@ -274,6 +276,11 @@ namespace Ryujinx.Graphics.Shader.Translation NextInputAttributesPerPatchComponents = config.ThisInputAttributesPerPatchComponents; NextUsesFixedFuncAttributes = config.UsedFeatures.HasFlag(FeatureFlags.FixedFuncAttr); MergeOutputUserAttributes(config.UsedInputAttributes, config.UsedInputAttributesPerPatch); + + if (config.Stage != ShaderStage.Fragment) + { + LastInVertexPipeline = false; + } } public void MergeOutputUserAttributes(int mask, int maskPerPatch) From 9ba73ffbe5f78c0403cf102b95768f388da05122 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Sat, 14 May 2022 15:58:33 +0100 Subject: [PATCH 12/33] Prefetch capabilities before spawning translation threads. (#3338) * Prefetch capabilities before spawning translation threads. The Backend Multithreading only expects one thread to submit commands at a time. When compiling shaders, the translator may request the host GPU capabilities from the backend. It's possible for a bunch of translators to do this at the same time. There's a caching mechanism in place so that the capabilities are only fetched once. By triggering this before spawning the thread, the async translation threads no longer try to queue onto the backend queue all at the same time. The Capabilities do need to be checked from the GPU thread, due to OpenGL needing a context to check them, so it's not possible to call the underlying backend directly. * Initialize the capabilities when setting the GPU thread + missing call in headless * Remove private variables --- Ryujinx.Graphics.Gpu/GpuContext.cs | 18 +++--------------- Ryujinx.Headless.SDL2/WindowBase.cs | 1 + 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/Ryujinx.Graphics.Gpu/GpuContext.cs b/Ryujinx.Graphics.Gpu/GpuContext.cs index 66077c3bf..67edd8427 100644 --- a/Ryujinx.Graphics.Gpu/GpuContext.cs +++ b/Ryujinx.Graphics.Gpu/GpuContext.cs @@ -82,27 +82,13 @@ namespace Ryujinx.Graphics.Gpu /// /// Host hardware capabilities. /// - internal ref Capabilities Capabilities - { - get - { - if (!_capsLoaded) - { - _caps = Renderer.GetCapabilities(); - _capsLoaded = true; - } - - return ref _caps; - } - } + internal Capabilities Capabilities { get; private set; } /// /// Event for signalling shader cache loading progress. /// public event Action ShaderCacheStateChanged; - private bool _capsLoaded; - private Capabilities _caps; private Thread _gpuThread; /// @@ -254,6 +240,8 @@ namespace Ryujinx.Graphics.Gpu public void SetGpuThread() { _gpuThread = Thread.CurrentThread; + + Capabilities = Renderer.GetCapabilities(); } /// diff --git a/Ryujinx.Headless.SDL2/WindowBase.cs b/Ryujinx.Headless.SDL2/WindowBase.cs index 74eb0d31a..c7c455962 100644 --- a/Ryujinx.Headless.SDL2/WindowBase.cs +++ b/Ryujinx.Headless.SDL2/WindowBase.cs @@ -164,6 +164,7 @@ namespace Ryujinx.Headless.SDL2 Device.Gpu.Renderer.RunLoop(() => { + Device.Gpu.SetGpuThread(); Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token); Translator.IsReadyForTranslation.Set(); From deb99d2cae3e80bdf70cb52c6c160094dc7c9292 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Sun, 15 May 2022 11:30:15 +0000 Subject: [PATCH 13/33] Avalonia UI - Part 1 (#3270) * avalonia part 1 * remove vulkan ui backend * move ui common files to ui common project * get name for oading screen from device * rebase. * review 1 * review 1.1 * review * cleanup * addressed review * use cancellation token * review * review * rebased * cancel library loading when closing window * remove star image, use fonticon instead * delete render control frame buffer when game ends. change position of fav star * addressed @Thog review * ensure the right ui is downloaded in updates * fix crash when showing not supported dialog during controller request * add prefix to artifact names * Auto-format Avalonia project * Fix input * Fix build, simplify app disposal * remove nv stutter thread * addressed review * add missing change * maintain window size if new size is zero length * add game, handheld, docked to local * reverse scale main window * Update de_DE.json * Update de_DE.json * Update de_DE.json * Update italian json * Update it_IT.json * let render timer poll with no wait * remove unused code * more unused code * enabled tiered compilation and trimming * check if window event is not closed before signaling * fix atmospher case * locale fix * locale fix * remove explicit tiered compilation declarations * Remove ) it_IT.json * Remove ) de_DE.json * Update it_IT.json * Update pt_BR locale with latest strings * Remove ')' * add more strings to locale * update locale * remove extra slash * remove extra slash * set firmware version to 0 if key's not found * fix * revert timer changes * lock on object instead * Update it_IT.json * remove unused method * add load screen text to locale * drop swap event * Update de_DE.json * Update de_DE.json * do null check when stopping emulator * Update de_DE.json * Create tr_TR.json * Add tr_TR * Add tr_TR + Turkish * Update it_IT.json * Update Ryujinx.Ava/Input/AvaloniaMappingHelper.cs Co-authored-by: Ac_K * Apply suggestions from code review Co-authored-by: Ac_K * Apply suggestions from code review Co-authored-by: Ac_K * addressed review * Update Ryujinx.Ava/Ui/Backend/OpenGl/OpenGlRenderTarget.cs Co-authored-by: gdkchan * use avalonia's inbuilt renderer on linux * removed whitespace * workaround for queue render crash with vsync off * drop custom backend * format files * fix not closing issue * remove warnings * rebase * update avalonia library * Reposition the Text and Button on About Page * Assign build version * Remove appveyor text Co-authored-by: gdk Co-authored-by: Niwu34 <67392333+Niwu34@users.noreply.github.com> Co-authored-by: Antonio Brugnolo <36473846+AntoSkate@users.noreply.github.com> Co-authored-by: aegiff <99728970+aegiff@users.noreply.github.com> Co-authored-by: Ac_K Co-authored-by: MostlyWhat <78652091+MostlyWhat@users.noreply.github.com> --- .github/workflows/build.yml | 11 +- README.md | 2 +- Ryujinx.Ava/App.axaml | 9 + Ryujinx.Ava/App.axaml.cs | 126 ++ Ryujinx.Ava/AppHost.cs | 1056 ++++++++++++ Ryujinx.Ava/Assets/Fonts/SegoeFluentIcons.ttf | Bin 0 -> 408752 bytes Ryujinx.Ava/Assets/Locales/de_DE.json | 549 +++++++ Ryujinx.Ava/Assets/Locales/el_GR.json | 499 ++++++ Ryujinx.Ava/Assets/Locales/en_US.json | 552 +++++++ Ryujinx.Ava/Assets/Locales/es_ES.json | 497 ++++++ Ryujinx.Ava/Assets/Locales/fr_FR.json | 270 +++ Ryujinx.Ava/Assets/Locales/it_IT.json | 547 +++++++ Ryujinx.Ava/Assets/Locales/ko_KR.json | 497 ++++++ Ryujinx.Ava/Assets/Locales/pt_BR.json | 544 +++++++ Ryujinx.Ava/Assets/Locales/ru_RU.json | 497 ++++++ Ryujinx.Ava/Assets/Locales/tr_TR.json | 547 +++++++ Ryujinx.Ava/Assets/Styles/BaseDark.xaml | 56 + Ryujinx.Ava/Assets/Styles/BaseLight.xaml | 51 + Ryujinx.Ava/Assets/Styles/Styles.xaml | 266 +++ Ryujinx.Ava/Common/ApplicationHelper.cs | 417 +++++ Ryujinx.Ava/Common/ApplicationSort.cs | 15 + Ryujinx.Ava/Common/KeyboardHotkeyState.cs | 12 + Ryujinx.Ava/Common/Locale/LocaleExtension.cs | 30 + Ryujinx.Ava/Common/Locale/LocaleManager.cs | 105 ++ Ryujinx.Ava/Input/AvaloniaKeyboard.cs | 205 +++ Ryujinx.Ava/Input/AvaloniaKeyboardDriver.cs | 113 ++ Ryujinx.Ava/Input/AvaloniaMappingHelper.cs | 188 +++ Ryujinx.Ava/Input/AvaloniaMouse.cs | 90 + Ryujinx.Ava/Input/AvaloniaMouseDriver.cs | 129 ++ Ryujinx.Ava/Modules/Updater/Updater.cs | 634 ++++++++ Ryujinx.Ava/Program.cs | 275 ++++ Ryujinx.Ava/Ryujinx.Ava.csproj | 141 ++ Ryujinx.Ava/Ryujinx.ico | Bin 0 -> 108122 bytes Ryujinx.Ava/Ui/Applet/AvaHostUiHandler.cs | 149 ++ .../Applet/AvaloniaDynamicTextInputHandler.cs | 162 ++ Ryujinx.Ava/Ui/Applet/AvaloniaHostUiTheme.cs | 43 + Ryujinx.Ava/Ui/Applet/ErrorAppletWindow.axaml | 31 + .../Ui/Applet/ErrorAppletWindow.axaml.cs | 89 + Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml | 32 + .../Ui/Applet/SwkbdAppletDialog.axaml.cs | 152 ++ .../Ui/Controls/ApplicationOpenedEventArgs.cs | 16 + Ryujinx.Ava/Ui/Controls/AvaloniaGlxContext.cs | 16 + Ryujinx.Ava/Ui/Controls/AvaloniaWglContext.cs | 16 + .../Ui/Controls/BitmapArrayValueConverter.cs | 35 + .../Ui/Controls/ContentDialogHelper.cs | 357 ++++ Ryujinx.Ava/Ui/Controls/GameGridView.axaml | 188 +++ Ryujinx.Ava/Ui/Controls/GameGridView.axaml.cs | 82 + Ryujinx.Ava/Ui/Controls/GameListView.axaml | 188 +++ Ryujinx.Ava/Ui/Controls/GameListView.axaml.cs | 82 + Ryujinx.Ava/Ui/Controls/Glyph.cs | 9 + .../Ui/Controls/GlyphValueConverter.cs | 49 + Ryujinx.Ava/Ui/Controls/HotKeyControl.cs | 52 + .../Ui/Controls/IGlContextExtension.cs | 25 + Ryujinx.Ava/Ui/Controls/InputDialog.axaml | 18 + Ryujinx.Ava/Ui/Controls/InputDialog.axaml.cs | 67 + Ryujinx.Ava/Ui/Controls/KeyValueConverter.cs | 46 + Ryujinx.Ava/Ui/Controls/MiniCommand.cs | 71 + Ryujinx.Ava/Ui/Controls/OffscreenTextBox.cs | 40 + .../Ui/Controls/OpenToolkitBindingsContext.cs | 20 + Ryujinx.Ava/Ui/Controls/RenderTimer.cs | 100 ++ Ryujinx.Ava/Ui/Controls/RendererControl.cs | 273 ++++ Ryujinx.Ava/Ui/Controls/SPBOpenGLContext.cs | 47 + .../Ui/Controls/UpdateWaitWindow.axaml | 28 + .../Ui/Controls/UpdateWaitWindow.axaml.cs | 35 + Ryujinx.Ava/Ui/Controls/UserErrorDialog.cs | 91 ++ Ryujinx.Ava/Ui/Controls/UserResult.cs | 12 + Ryujinx.Ava/Ui/Models/FileSizeSortComparer.cs | 45 + .../Ui/Models/Generic/FileSizeSortComparer.cs | 50 + .../Models/Generic/LastPlayedSortComparer.cs | 33 + .../Models/Generic/TimePlayedSortComparer.cs | 66 + Ryujinx.Ava/Ui/Models/InputConfiguration.cs | 456 ++++++ .../Ui/Models/LastPlayedSortComparer.cs | 27 + Ryujinx.Ava/Ui/Models/ProfileImageModel.cs | 14 + .../Ui/Models/StatusUpdatedEventArgs.cs | 26 + .../Ui/Models/TimePlayedSortComparer.cs | 61 + Ryujinx.Ava/Ui/Models/TitleUpdateModel.cs | 22 + Ryujinx.Ava/Ui/ViewModels/BaseModel.cs | 15 + .../Ui/ViewModels/MainWindowViewModel.cs | 1449 +++++++++++++++++ Ryujinx.Ava/Ui/Windows/AboutWindow.axaml | 163 ++ Ryujinx.Ava/Ui/Windows/AboutWindow.axaml.cs | 92 ++ Ryujinx.Ava/Ui/Windows/IconColorPicker.cs | 192 +++ Ryujinx.Ava/Ui/Windows/MainWindow.axaml | 639 ++++++++ Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs | 719 ++++++++ Ryujinx.Ava/Ui/Windows/StyleableWindow.cs | 47 + Ryujinx.Ava/Ui/Windows/TimeZone.cs | 16 + Ryujinx.Ava/Ui/Windows/UpdaterWindow.axaml | 37 + Ryujinx.Ava/Ui/Windows/UpdaterWindow.axaml.cs | 93 ++ .../GenericControllerInputConfig.cs | 38 +- .../Configuration/Hid/InputConfig.cs | 16 +- Ryujinx.Graphics.GAL/IWindow.cs | 2 +- .../Commands/Window/WindowPresentCommand.cs | 4 +- .../Multithreading/ThreadedWindow.cs | 4 +- Ryujinx.Graphics.Gpu/Window.cs | 4 +- Ryujinx.Graphics.OpenGL/Window.cs | 83 +- .../SoftwareKeyboardRendererBase.cs | 12 +- Ryujinx.HLE/Switch.cs | 4 +- Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs | 24 +- .../Ryujinx.Headless.SDL2.csproj | 20 +- Ryujinx.Headless.SDL2/WindowBase.cs | 15 +- Ryujinx.Input.SDL2/SDL2Keyboard.cs | 1 - Ryujinx.Input/Key.cs | 2 +- .../App/ApplicationAddedEventArgs.cs | 2 +- .../App/ApplicationCountUpdatedEventArgs.cs | 2 +- .../App/ApplicationData.cs | 2 +- Ryujinx.Ui.Common/App/ApplicationLibrary.cs | 851 ++++++++++ .../App/ApplicationMetadata.cs | 2 +- .../Configuration/AudioBackend.cs | 2 +- .../Configuration/ConfigurationFileFormat.cs | 49 +- .../Configuration/ConfigurationState.cs | 158 +- .../Configuration/LoggerModule.cs | 2 +- .../Configuration/System/Language.cs | 2 +- .../Configuration/System/Region.cs | 2 +- .../Configuration/Ui/ColumnSort.cs | 2 +- .../Configuration/Ui/GuiColumns.cs | 2 +- .../DiscordIntegrationModule.cs | 7 +- .../Helper/ConsoleHelper.cs | 2 +- .../Helper/OpenHelper.cs | 4 +- .../Helper/SetupValidator.cs | 6 +- .../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.png | Bin .../Resources/Logo_GitHub.png | Bin .../Resources/Logo_Patreon.png | Bin .../Resources/Logo_Ryujinx.png | Bin .../Resources/Logo_Twitter.png | Bin Ryujinx.Ui.Common/Ryujinx.Ui.Common.csproj | 52 + .../UserError.cs | 2 +- Ryujinx.sln | 18 +- Ryujinx/Modules/Updater/UpdateDialog.cs | 3 +- Ryujinx/Modules/Updater/Updater.cs | 2 +- Ryujinx/Program.cs | 3 +- Ryujinx/Ryujinx.csproj | 58 +- Ryujinx/Ui/App/ApplicationLibrary.cs | 648 -------- Ryujinx/Ui/Applet/ErrorAppletDialog.cs | 3 +- .../Ui/Applet/GtkDynamicTextInputHandler.cs | 4 +- Ryujinx/Ui/GLRenderer.cs | 24 +- Ryujinx/Ui/Helper/ThemeHelper.cs | 2 +- Ryujinx/Ui/MainWindow.cs | 7 +- Ryujinx/Ui/RendererWidgetBase.cs | 6 +- Ryujinx/Ui/VKRenderer.cs | 2 +- Ryujinx/Ui/Widgets/GameTableContextMenu.cs | 6 +- Ryujinx/Ui/Widgets/GtkDialog.cs | 5 +- Ryujinx/Ui/Widgets/ProfileDialog.cs | 3 +- Ryujinx/Ui/Widgets/UserErrorDialog.cs | 2 + Ryujinx/Ui/Windows/AboutWindow.Designer.cs | 11 +- Ryujinx/Ui/Windows/AboutWindow.cs | 4 +- Ryujinx/Ui/Windows/AmiiboWindow.cs | 3 +- Ryujinx/Ui/Windows/AvatarWindow.cs | 3 +- Ryujinx/Ui/Windows/ControllerWindow.cs | 12 +- Ryujinx/Ui/Windows/SettingsWindow.cs | 8 +- .../Ui/Windows/UserProfilesManagerWindow.cs | 4 +- {Ryujinx => distribution/legal}/THIRDPARTY.md | 0 {Ryujinx => distribution/windows}/alsoft.ini | 0 161 files changed, 17179 insertions(+), 855 deletions(-) create mode 100644 Ryujinx.Ava/App.axaml create mode 100644 Ryujinx.Ava/App.axaml.cs create mode 100644 Ryujinx.Ava/AppHost.cs create mode 100644 Ryujinx.Ava/Assets/Fonts/SegoeFluentIcons.ttf create mode 100644 Ryujinx.Ava/Assets/Locales/de_DE.json create mode 100644 Ryujinx.Ava/Assets/Locales/el_GR.json create mode 100644 Ryujinx.Ava/Assets/Locales/en_US.json create mode 100644 Ryujinx.Ava/Assets/Locales/es_ES.json create mode 100644 Ryujinx.Ava/Assets/Locales/fr_FR.json create mode 100644 Ryujinx.Ava/Assets/Locales/it_IT.json create mode 100644 Ryujinx.Ava/Assets/Locales/ko_KR.json create mode 100644 Ryujinx.Ava/Assets/Locales/pt_BR.json create mode 100644 Ryujinx.Ava/Assets/Locales/ru_RU.json create mode 100644 Ryujinx.Ava/Assets/Locales/tr_TR.json create mode 100644 Ryujinx.Ava/Assets/Styles/BaseDark.xaml create mode 100644 Ryujinx.Ava/Assets/Styles/BaseLight.xaml create mode 100644 Ryujinx.Ava/Assets/Styles/Styles.xaml create mode 100644 Ryujinx.Ava/Common/ApplicationHelper.cs create mode 100644 Ryujinx.Ava/Common/ApplicationSort.cs create mode 100644 Ryujinx.Ava/Common/KeyboardHotkeyState.cs create mode 100644 Ryujinx.Ava/Common/Locale/LocaleExtension.cs create mode 100644 Ryujinx.Ava/Common/Locale/LocaleManager.cs create mode 100644 Ryujinx.Ava/Input/AvaloniaKeyboard.cs create mode 100644 Ryujinx.Ava/Input/AvaloniaKeyboardDriver.cs create mode 100644 Ryujinx.Ava/Input/AvaloniaMappingHelper.cs create mode 100644 Ryujinx.Ava/Input/AvaloniaMouse.cs create mode 100644 Ryujinx.Ava/Input/AvaloniaMouseDriver.cs create mode 100644 Ryujinx.Ava/Modules/Updater/Updater.cs create mode 100644 Ryujinx.Ava/Program.cs create mode 100644 Ryujinx.Ava/Ryujinx.Ava.csproj create mode 100644 Ryujinx.Ava/Ryujinx.ico create mode 100644 Ryujinx.Ava/Ui/Applet/AvaHostUiHandler.cs create mode 100644 Ryujinx.Ava/Ui/Applet/AvaloniaDynamicTextInputHandler.cs create mode 100644 Ryujinx.Ava/Ui/Applet/AvaloniaHostUiTheme.cs create mode 100644 Ryujinx.Ava/Ui/Applet/ErrorAppletWindow.axaml create mode 100644 Ryujinx.Ava/Ui/Applet/ErrorAppletWindow.axaml.cs create mode 100644 Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml create mode 100644 Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml.cs create mode 100644 Ryujinx.Ava/Ui/Controls/ApplicationOpenedEventArgs.cs create mode 100644 Ryujinx.Ava/Ui/Controls/AvaloniaGlxContext.cs create mode 100644 Ryujinx.Ava/Ui/Controls/AvaloniaWglContext.cs create mode 100644 Ryujinx.Ava/Ui/Controls/BitmapArrayValueConverter.cs create mode 100644 Ryujinx.Ava/Ui/Controls/ContentDialogHelper.cs create mode 100644 Ryujinx.Ava/Ui/Controls/GameGridView.axaml create mode 100644 Ryujinx.Ava/Ui/Controls/GameGridView.axaml.cs create mode 100644 Ryujinx.Ava/Ui/Controls/GameListView.axaml create mode 100644 Ryujinx.Ava/Ui/Controls/GameListView.axaml.cs create mode 100644 Ryujinx.Ava/Ui/Controls/Glyph.cs create mode 100644 Ryujinx.Ava/Ui/Controls/GlyphValueConverter.cs create mode 100644 Ryujinx.Ava/Ui/Controls/HotKeyControl.cs create mode 100644 Ryujinx.Ava/Ui/Controls/IGlContextExtension.cs create mode 100644 Ryujinx.Ava/Ui/Controls/InputDialog.axaml create mode 100644 Ryujinx.Ava/Ui/Controls/InputDialog.axaml.cs create mode 100644 Ryujinx.Ava/Ui/Controls/KeyValueConverter.cs create mode 100644 Ryujinx.Ava/Ui/Controls/MiniCommand.cs create mode 100644 Ryujinx.Ava/Ui/Controls/OffscreenTextBox.cs create mode 100644 Ryujinx.Ava/Ui/Controls/OpenToolkitBindingsContext.cs create mode 100644 Ryujinx.Ava/Ui/Controls/RenderTimer.cs create mode 100644 Ryujinx.Ava/Ui/Controls/RendererControl.cs create mode 100644 Ryujinx.Ava/Ui/Controls/SPBOpenGLContext.cs create mode 100644 Ryujinx.Ava/Ui/Controls/UpdateWaitWindow.axaml create mode 100644 Ryujinx.Ava/Ui/Controls/UpdateWaitWindow.axaml.cs create mode 100644 Ryujinx.Ava/Ui/Controls/UserErrorDialog.cs create mode 100644 Ryujinx.Ava/Ui/Controls/UserResult.cs create mode 100644 Ryujinx.Ava/Ui/Models/FileSizeSortComparer.cs create mode 100644 Ryujinx.Ava/Ui/Models/Generic/FileSizeSortComparer.cs create mode 100644 Ryujinx.Ava/Ui/Models/Generic/LastPlayedSortComparer.cs create mode 100644 Ryujinx.Ava/Ui/Models/Generic/TimePlayedSortComparer.cs create mode 100644 Ryujinx.Ava/Ui/Models/InputConfiguration.cs create mode 100644 Ryujinx.Ava/Ui/Models/LastPlayedSortComparer.cs create mode 100644 Ryujinx.Ava/Ui/Models/ProfileImageModel.cs create mode 100644 Ryujinx.Ava/Ui/Models/StatusUpdatedEventArgs.cs create mode 100644 Ryujinx.Ava/Ui/Models/TimePlayedSortComparer.cs create mode 100644 Ryujinx.Ava/Ui/Models/TitleUpdateModel.cs create mode 100644 Ryujinx.Ava/Ui/ViewModels/BaseModel.cs create mode 100644 Ryujinx.Ava/Ui/ViewModels/MainWindowViewModel.cs create mode 100644 Ryujinx.Ava/Ui/Windows/AboutWindow.axaml create mode 100644 Ryujinx.Ava/Ui/Windows/AboutWindow.axaml.cs create mode 100644 Ryujinx.Ava/Ui/Windows/IconColorPicker.cs create mode 100644 Ryujinx.Ava/Ui/Windows/MainWindow.axaml create mode 100644 Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs create mode 100644 Ryujinx.Ava/Ui/Windows/StyleableWindow.cs create mode 100644 Ryujinx.Ava/Ui/Windows/TimeZone.cs create mode 100644 Ryujinx.Ava/Ui/Windows/UpdaterWindow.axaml create mode 100644 Ryujinx.Ava/Ui/Windows/UpdaterWindow.axaml.cs rename {Ryujinx/Ui => Ryujinx.Ui.Common}/App/ApplicationAddedEventArgs.cs (81%) rename {Ryujinx/Ui => Ryujinx.Ui.Common}/App/ApplicationCountUpdatedEventArgs.cs (85%) rename {Ryujinx/Ui => Ryujinx.Ui.Common}/App/ApplicationData.cs (95%) create mode 100644 Ryujinx.Ui.Common/App/ApplicationLibrary.cs rename {Ryujinx/Ui => Ryujinx.Ui.Common}/App/ApplicationMetadata.cs (85%) rename {Ryujinx => Ryujinx.Ui.Common}/Configuration/AudioBackend.cs (70%) rename {Ryujinx => Ryujinx.Ui.Common}/Configuration/ConfigurationFileFormat.cs (88%) rename {Ryujinx => Ryujinx.Ui.Common}/Configuration/ConfigurationState.cs (82%) rename {Ryujinx => Ryujinx.Ui.Common}/Configuration/LoggerModule.cs (98%) rename {Ryujinx => Ryujinx.Ui.Common}/Configuration/System/Language.cs (88%) rename {Ryujinx => Ryujinx.Ui.Common}/Configuration/System/Region.cs (73%) rename {Ryujinx => Ryujinx.Ui.Common}/Configuration/Ui/ColumnSort.cs (74%) rename {Ryujinx => Ryujinx.Ui.Common}/Configuration/Ui/GuiColumns.cs (92%) rename {Ryujinx/Modules => Ryujinx.Ui.Common}/DiscordIntegrationModule.cs (96%) rename {Ryujinx/Ui => Ryujinx.Ui.Common}/Helper/ConsoleHelper.cs (97%) rename {Ryujinx/Ui => Ryujinx.Ui.Common}/Helper/OpenHelper.cs (93%) rename {Ryujinx/Ui => Ryujinx.Ui.Common}/Helper/SetupValidator.cs (97%) rename {Ryujinx/Ui => Ryujinx.Ui.Common}/Resources/Controller_JoyConLeft.svg (100%) rename {Ryujinx/Ui => Ryujinx.Ui.Common}/Resources/Controller_JoyConPair.svg (100%) rename {Ryujinx/Ui => Ryujinx.Ui.Common}/Resources/Controller_JoyConRight.svg (100%) rename {Ryujinx/Ui => Ryujinx.Ui.Common}/Resources/Controller_ProCon.svg (100%) rename {Ryujinx/Ui => Ryujinx.Ui.Common}/Resources/Icon_NCA.png (100%) rename {Ryujinx/Ui => Ryujinx.Ui.Common}/Resources/Icon_NRO.png (100%) rename {Ryujinx/Ui => Ryujinx.Ui.Common}/Resources/Icon_NSO.png (100%) rename {Ryujinx/Ui => Ryujinx.Ui.Common}/Resources/Icon_NSP.png (100%) rename {Ryujinx/Ui => Ryujinx.Ui.Common}/Resources/Icon_XCI.png (100%) rename {Ryujinx/Ui => Ryujinx.Ui.Common}/Resources/Logo_Amiibo.png (100%) rename {Ryujinx/Ui => Ryujinx.Ui.Common}/Resources/Logo_Discord.png (100%) rename {Ryujinx/Ui => Ryujinx.Ui.Common}/Resources/Logo_GitHub.png (100%) rename {Ryujinx/Ui => Ryujinx.Ui.Common}/Resources/Logo_Patreon.png (100%) rename {Ryujinx/Ui => Ryujinx.Ui.Common}/Resources/Logo_Ryujinx.png (100%) rename {Ryujinx/Ui => Ryujinx.Ui.Common}/Resources/Logo_Twitter.png (100%) create mode 100644 Ryujinx.Ui.Common/Ryujinx.Ui.Common.csproj rename {Ryujinx/Ui/Widgets => Ryujinx.Ui.Common}/UserError.cs (96%) delete mode 100644 Ryujinx/Ui/App/ApplicationLibrary.cs rename {Ryujinx => distribution/legal}/THIRDPARTY.md (100%) rename {Ryujinx => distribution/windows}/alsoft.ini (100%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6e5dcaecf..566b2f96e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -69,6 +69,9 @@ jobs: - 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 Ryujinx.Headless.SDL2 --self-contained if: github.event_name == 'pull_request' + - name: Publish Ryujinx.Ava + run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava /p:Version="1.0.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Ava + if: github.event_name == 'pull_request' - name: Upload Ryujinx artifact uses: actions/upload-artifact@v2 with: @@ -78,6 +81,12 @@ jobs: - name: Upload Ryujinx.Headless.SDL2 artifact uses: actions/upload-artifact@v2 with: - name: ryujinx-headless-sdl2-${{ 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.RELEASE_ZIP_OS_NAME }} path: publish_sdl2_headless if: github.event_name == 'pull_request' + - name: Upload Ryujinx.Ava artifact + uses: actions/upload-artifact@v2 + with: + name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }} + path: publish_ava + if: github.event_name == 'pull_request' diff --git a/README.md b/README.md index 1cc633042..f131dfab7 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ This software is licensed under the terms of the LGPLv3 license.
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](Ryujinx/THIRDPARTY.md) for more details. +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. diff --git a/Ryujinx.Ava/App.axaml b/Ryujinx.Ava/App.axaml new file mode 100644 index 000000000..0db016297 --- /dev/null +++ b/Ryujinx.Ava/App.axaml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/Ryujinx.Ava/App.axaml.cs b/Ryujinx.Ava/App.axaml.cs new file mode 100644 index 000000000..a76de8b32 --- /dev/null +++ b/Ryujinx.Ava/App.axaml.cs @@ -0,0 +1,126 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using Avalonia.Styling; +using Avalonia.Threading; +using FluentAvalonia.Styling; +using Ryujinx.Ava.Ui.Windows; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Ui.Common.Configuration; +using System; +using System.IO; + +namespace Ryujinx.Ava +{ + public class App : Avalonia.Application + { + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow(); + } + + base.OnFrameworkInitializationCompleted(); + + if (Program.PreviewerDetached) + { + ApplyConfiguredTheme(); + + ConfigurationState.Instance.Ui.BaseStyle.Event += ThemeChanged_Event; + ConfigurationState.Instance.Ui.CustomThemePath.Event += ThemeChanged_Event; + ConfigurationState.Instance.Ui.EnableCustomTheme.Event += CustomThemeChanged_Event; + } + } + + private void CustomThemeChanged_Event(object sender, ReactiveEventArgs e) + { + ApplyConfiguredTheme(); + } + + private void ShowRestartDialog() + { + // TODO. Implement Restart Dialog when SettingsWindow is implemented. + } + + private void ThemeChanged_Event(object sender, ReactiveEventArgs e) + { + ApplyConfiguredTheme(); + } + + private void ApplyConfiguredTheme() + { + try + { + string baseStyle = ConfigurationState.Instance.Ui.BaseStyle; + string themePath = ConfigurationState.Instance.Ui.CustomThemePath; + bool enableCustomTheme = ConfigurationState.Instance.Ui.EnableCustomTheme; + + const string BaseStyleUrl = "avares://Ryujinx.Ava/Assets/Styles/Base{0}.xaml"; + + if (string.IsNullOrWhiteSpace(baseStyle)) + { + ConfigurationState.Instance.Ui.BaseStyle.Value = "Dark"; + + baseStyle = ConfigurationState.Instance.Ui.BaseStyle; + } + + var theme = AvaloniaLocator.Current.GetService(); + + theme.RequestedTheme = baseStyle; + + var currentStyles = this.Styles; + + // Remove all styles except the base style. + if (currentStyles.Count > 1) + { + currentStyles.RemoveRange(1, currentStyles.Count - 1); + } + + IStyle newStyles = null; + + // Load requested style, and fallback to Dark theme if loading failed. + try + { + newStyles = (Styles)AvaloniaXamlLoader.Load(new Uri(string.Format(BaseStyleUrl, baseStyle), UriKind.Absolute)); + } + catch (XamlLoadException) + { + newStyles = (Styles)AvaloniaXamlLoader.Load(new Uri(string.Format(BaseStyleUrl, "Dark"), UriKind.Absolute)); + } + + currentStyles.Add(newStyles); + + if (enableCustomTheme) + { + if (!string.IsNullOrWhiteSpace(themePath)) + { + try + { + var themeContent = File.ReadAllText(themePath); + var customStyle = AvaloniaRuntimeXamlLoader.Parse(themeContent); + + currentStyles.Add(customStyle); + } + catch (Exception ex) + { + Logger.Error?.Print(LogClass.Application, $"Failed to Apply Custom Theme. Error: {ex.Message}"); + } + } + } + } + catch (Exception) + { + Logger.Warning?.Print(LogClass.Application, "Failed to Apply Theme. A restart is needed to apply the selected theme"); + + ShowRestartDialog(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Ava/AppHost.cs b/Ryujinx.Ava/AppHost.cs new file mode 100644 index 000000000..30d725808 --- /dev/null +++ b/Ryujinx.Ava/AppHost.cs @@ -0,0 +1,1056 @@ +using ARMeilleure.Translation; +using ARMeilleure.Translation.PTC; +using Avalonia.Input; +using Avalonia.Threading; +using LibHac.Tools.FsSystem; +using Ryujinx.Audio.Backends.Dummy; +using Ryujinx.Audio.Backends.OpenAL; +using Ryujinx.Audio.Backends.SDL2; +using Ryujinx.Audio.Backends.SoundIo; +using Ryujinx.Audio.Integration; +using Ryujinx.Ava.Common; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Input; +using Ryujinx.Ava.Ui.Controls; +using Ryujinx.Ava.Ui.Models; +using Ryujinx.Ava.Ui.Windows; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.System; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.GAL.Multithreading; +using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.OpenGL; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.HLE.HOS.SystemState; +using Ryujinx.Input; +using Ryujinx.Input.HLE; +using Ryujinx.Ui.Common; +using Ryujinx.Ui.Common.Configuration; +using Ryujinx.Ui.Common.Helper; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +using InputManager = Ryujinx.Input.HLE.InputManager; +using Key = Ryujinx.Input.Key; +using MouseButton = Ryujinx.Input.MouseButton; +using Size = Avalonia.Size; +using Switch = Ryujinx.HLE.Switch; +using WindowState = Avalonia.Controls.WindowState; + +namespace Ryujinx.Ava +{ + public class AppHost + { + private const int CursorHideIdleTime = 8; // Hide Cursor seconds + + private static readonly Cursor InvisibleCursor = new Cursor(StandardCursorType.None); + + private readonly AccountManager _accountManager; + private UserChannelPersistence _userChannelPersistence; + + private readonly InputManager _inputManager; + + private readonly IKeyboard _keyboardInterface; + + private readonly MainWindow _parent; + + private readonly GraphicsDebugLevel _glLogLevel; + + private bool _hideCursorOnIdle; + private bool _isStopped; + private bool _isActive; + private long _lastCursorMoveTime; + + private KeyboardHotkeyState _prevHotkeyState; + + private IRenderer _renderer; + private readonly Thread _renderingThread; + + private bool _isMouseInClient; + private bool _renderingStarted; + private bool _dialogShown; + + private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution; + private KeyboardStateSnapshot _lastKeyboardSnapshot; + + private readonly CancellationTokenSource _gpuCancellationTokenSource; + + public event EventHandler AppExit; + public event EventHandler StatusUpdatedEvent; + + public RendererControl Renderer { get; } + public VirtualFileSystem VirtualFileSystem { get; } + public ContentManager ContentManager { get; } + public Switch Device { get; set; } + public NpadManager NpadManager { get; } + public TouchScreenManager TouchScreenManager { get; } + + public int Width { get; private set; } + public int Height { get; private set; } + public string ApplicationPath { get; private set; } + + private bool _isFirmwareTitle; + + public bool ScreenshotRequested { get; set; } + + private object _lockObject = new(); + + public AppHost( + RendererControl renderer, + InputManager inputManager, + string applicationPath, + VirtualFileSystem virtualFileSystem, + ContentManager contentManager, + AccountManager accountManager, + UserChannelPersistence userChannelPersistence, + MainWindow parent) + { + _parent = parent; + _inputManager = inputManager; + _accountManager = accountManager; + _userChannelPersistence = userChannelPersistence; + _renderingThread = new Thread(RenderLoop) { Name = "GUI.RenderThread" }; + _hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle; + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + _glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel; + _inputManager.SetMouseDriver(new AvaloniaMouseDriver(renderer)); + _keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0"); + _lastKeyboardSnapshot = _keyboardInterface.GetKeyboardStateSnapshot(); + + NpadManager = _inputManager.CreateNpadManager(); + TouchScreenManager = _inputManager.CreateTouchScreenManager(); + Renderer = renderer; + ApplicationPath = applicationPath; + VirtualFileSystem = virtualFileSystem; + ContentManager = contentManager; + + if (ApplicationPath.StartsWith("@SystemContent")) + { + ApplicationPath = _parent.VirtualFileSystem.SwitchPathToSystemPath(ApplicationPath); + + _isFirmwareTitle = true; + } + + ConfigurationState.Instance.HideCursorOnIdle.Event += HideCursorState_Changed; + + _parent.PointerEnter += Parent_PointerEntered; + _parent.PointerLeave += Parent_PointerLeft; + _parent.PointerMoved += Parent_PointerMoved; + + ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState; + ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState; + ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState; + ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState; + + _gpuCancellationTokenSource = new CancellationTokenSource(); + } + + private void Parent_PointerMoved(object sender, PointerEventArgs e) + { + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + } + + private void Parent_PointerLeft(object sender, PointerEventArgs e) + { + Renderer.Cursor = ConfigurationState.Instance.Hid.EnableMouse ? InvisibleCursor : Cursor.Default; + + _isMouseInClient = false; + } + + private void Parent_PointerEntered(object sender, PointerEventArgs e) + { + _isMouseInClient = true; + } + + private void SetRendererWindowSize(Size size) + { + if (_renderer != null) + { + double scale = Program.WindowScaleFactor; + _renderer.Window.SetSize((int)(size.Width * scale), (int)(size.Height * scale)); + } + } + + private unsafe void Renderer_ScreenCaptured(object sender, ScreenCaptureImageInfo e) + { + if (e.Data.Length > 0 && e.Height > 0 && e.Width > 0) + { + Task.Run(() => + { + lock (_lockObject) + { + 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 directory = AppDataManager.Mode switch + { + AppDataManager.LaunchMode.Portable => Path.Combine(AppDataManager.BaseDirPath, "screenshots"), + _ => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Ryujinx") + }; + + string path = Path.Combine(directory, filename); + + try + { + Directory.CreateDirectory(directory); + } + catch (Exception ex) + { + Logger.Error?.Print(LogClass.Application, $"Failed to create directory at path {directory}. Error : {ex.GetType().Name}", "Screenshot"); + + return; + } + + Image image = e.IsBgra ? Image.LoadPixelData(e.Data, e.Width, e.Height) + : Image.LoadPixelData(e.Data, e.Width, e.Height); + + if (e.FlipX) + { + image.Mutate(x => x.Flip(FlipMode.Horizontal)); + } + + if (e.FlipY) + { + image.Mutate(x => x.Flip(FlipMode.Vertical)); + } + + image.SaveAsPng(path, new PngEncoder() + { + ColorType = PngColorType.Rgb + }); + + image.Dispose(); + + Logger.Notice.Print(LogClass.Application, $"Screenshot saved to {path}", "Screenshot"); + } + }); + } + else + { + Logger.Error?.Print(LogClass.Application, $"Screenshot is empty. Size : {e.Data.Length} bytes. Resolution : {e.Width}x{e.Height}", "Screenshot"); + } + } + + public void Start() + { + if (OperatingSystem.IsWindows()) + { + _windowsMultimediaTimerResolution = new WindowsMultimediaTimerResolution(1); + } + + DisplaySleep.Prevent(); + + NpadManager.Initialize(Device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); + TouchScreenManager.Initialize(Device); + + _parent.ViewModel.IsGameRunning = true; + + string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) + ? string.Empty + : $" - {Device.Application.TitleName}"; + + string titleVersionSection = string.IsNullOrWhiteSpace(Device.Application.DisplayVersion) + ? string.Empty + : $" v{Device.Application.DisplayVersion}"; + + string titleIdSection = string.IsNullOrWhiteSpace(Device.Application.TitleIdText) + ? string.Empty + : $" ({Device.Application.TitleIdText.ToUpper()})"; + + string titleArchSection = Device.Application.TitleIs64Bit + ? " (64-bit)" + : " (32-bit)"; + + Dispatcher.UIThread.InvokeAsync(() => + { + _parent.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}"; + }); + + _parent.ViewModel.HandleShaderProgress(Device); + + Renderer.SizeChanged += Window_SizeChanged; + + _isActive = true; + + _renderingThread.Start(); + + _parent.ViewModel.Volume = ConfigurationState.Instance.System.AudioVolume.Value; + + MainLoop(); + + Exit(); + } + + private void UpdateIgnoreMissingServicesState(object sender, ReactiveEventArgs args) + { + if (Device != null) + { + Device.Configuration.IgnoreMissingServices = args.NewValue; + } + } + + private void UpdateAspectRatioState(object sender, ReactiveEventArgs args) + { + if (Device != null) + { + Device.Configuration.AspectRatio = args.NewValue; + } + } + + private void UpdateDockedModeState(object sender, ReactiveEventArgs e) + { + Device?.System.ChangeDockedModeState(e.NewValue); + } + + private void UpdateAudioVolumeState(object sender, ReactiveEventArgs e) + { + Device?.SetVolume(e.NewValue); + Dispatcher.UIThread.Post(() => + { + var value = e.NewValue; + _parent.ViewModel.Volume = e.NewValue; + }); + } + + public void Stop() + { + _isActive = false; + } + + private void Exit() + { + (_keyboardInterface as AvaloniaKeyboard)?.Clear(); + + if (_isStopped) + { + return; + } + + _isStopped = true; + _isActive = false; + } + + public void DisposeContext() + { + Dispose(); + + _isActive = false; + + _renderingThread.Join(); + + DisplaySleep.Restore(); + + Ptc.Close(); + PtcProfiler.Stop(); + NpadManager.Dispose(); + TouchScreenManager.Dispose(); + Device.Dispose(); + + DisposeGpu(); + + AppExit?.Invoke(this, EventArgs.Empty); + } + + private void Dispose() + { + if (Device.Application != null) + { + _parent.UpdateGameMetadata(Device.Application.TitleIdText); + } + + ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState; + ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState; + ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState; + + _gpuCancellationTokenSource.Cancel(); + _gpuCancellationTokenSource.Dispose(); + } + + public void DisposeGpu() + { + if (OperatingSystem.IsWindows()) + { + _windowsMultimediaTimerResolution?.Dispose(); + _windowsMultimediaTimerResolution = null; + } + + Renderer?.MakeCurrent(); + + Device.DisposeGpu(); + + Renderer?.DestroyBackgroundContext(); + Renderer?.MakeCurrent(null); + } + + private void HideCursorState_Changed(object sender, ReactiveEventArgs state) + { + Dispatcher.UIThread.InvokeAsync(delegate + { + _hideCursorOnIdle = state.NewValue; + + if (_hideCursorOnIdle) + { + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + } + else + { + _parent.Cursor = Cursor.Default; + } + }); + } + + public async Task LoadGuestApplication() + { + InitializeSwitchInstance(); + + MainWindow.UpdateGraphicsConfig(); + + SystemVersion firmwareVersion = ContentManager.GetCurrentFirmwareVersion(); + + if (!SetupValidator.CanStartApplication(ContentManager, ApplicationPath, out UserError userError)) + { + if (SetupValidator.CanFixStartApplication(ContentManager, ApplicationPath, userError, out firmwareVersion)) + { + if (userError == UserError.NoFirmware) + { + string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedMessage"], firmwareVersion.VersionString); + + UserResult result = await ContentDialogHelper.CreateConfirmationDialog(_parent, + LocaleManager.Instance["DialogFirmwareNoFirmwareInstalledMessage"], message, LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], ""); + + if (result != UserResult.Yes) + { + Dispatcher.UIThread.Post(async () => await + UserErrorDialog.ShowUserErrorDialog(userError, _parent)); + Device.Dispose(); + + return false; + } + } + + if (!SetupValidator.TryFixStartApplication(ContentManager, ApplicationPath, userError, out _)) + { + Dispatcher.UIThread.Post(async () => await + UserErrorDialog.ShowUserErrorDialog(userError, _parent)); + Device.Dispose(); + + return false; + } + + // Tell the user that we installed a firmware for them. + if (userError == UserError.NoFirmware) + { + firmwareVersion = ContentManager.GetCurrentFirmwareVersion(); + + _parent.RefreshFirmwareStatus(); + + string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedSuccessMessage"], firmwareVersion.VersionString); + + await ContentDialogHelper.CreateInfoDialog(_parent, + string.Format(LocaleManager.Instance["DialogFirmwareInstalledMessage"], firmwareVersion.VersionString), + message, + LocaleManager.Instance["InputDialogOk"], + "", + LocaleManager.Instance["RyujinxInfo"]); + } + } + else + { + Dispatcher.UIThread.Post(async () => await + UserErrorDialog.ShowUserErrorDialog(userError, _parent)); + + Device.Dispose(); + + return false; + } + } + + Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}"); + + if (_isFirmwareTitle) + { + Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA)."); + + Device.LoadNca(ApplicationPath); + } + else if (Directory.Exists(ApplicationPath)) + { + string[] romFsFiles = Directory.GetFiles(ApplicationPath, "*.istorage"); + + if (romFsFiles.Length == 0) + { + romFsFiles = Directory.GetFiles(ApplicationPath, "*.romfs"); + } + + if (romFsFiles.Length > 0) + { + Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); + + Device.LoadCart(ApplicationPath, romFsFiles[0]); + } + else + { + Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); + + Device.LoadCart(ApplicationPath); + } + } + else if (File.Exists(ApplicationPath)) + { + switch (System.IO.Path.GetExtension(ApplicationPath).ToLowerInvariant()) + { + case ".xci": + { + Logger.Info?.Print(LogClass.Application, "Loading as XCI."); + + Device.LoadXci(ApplicationPath); + + break; + } + case ".nca": + { + Logger.Info?.Print(LogClass.Application, "Loading as NCA."); + + Device.LoadNca(ApplicationPath); + + break; + } + case ".nsp": + case ".pfs0": + { + Logger.Info?.Print(LogClass.Application, "Loading as NSP."); + + Device.LoadNsp(ApplicationPath); + + break; + } + default: + { + Logger.Info?.Print(LogClass.Application, "Loading as homebrew."); + + try + { + Device.LoadProgram(ApplicationPath); + } + catch (ArgumentOutOfRangeException) + { + Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx."); + + Dispose(); + + return false; + } + + break; + } + } + } + else + { + Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); + + Dispose(); + + return false; + } + + DiscordIntegrationModule.SwitchToPlayingState(Device.Application.TitleIdText, Device.Application.TitleName); + + _parent.ApplicationLibrary.LoadAndSaveMetaData(Device.Application.TitleIdText, appMetadata => + { + appMetadata.LastPlayed = DateTime.UtcNow.ToString(); + }); + + return true; + } + + internal void Resume() + { + Device?.System.TogglePauseEmulation(false); + _parent.ViewModel.IsPaused = false; + } + + internal void Pause() + { + Device?.System.TogglePauseEmulation(true); + _parent.ViewModel.IsPaused = true; + } + + private void InitializeSwitchInstance() + { + VirtualFileSystem.ReloadKeySet(); + + IRenderer renderer = new Renderer(); + IHardwareDeviceDriver deviceDriver = new DummyHardwareDeviceDriver(); + + BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading; + + var isGALthreaded = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading); + + if (isGALthreaded) + { + renderer = new ThreadedRenderer(renderer); + } + + Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend Threading ({threadingMode}): {isGALthreaded}"); + + if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SDL2) + { + if (SDL2HardwareDeviceDriver.IsSupported) + { + deviceDriver = new SDL2HardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "SDL2 is not supported, trying to fall back to OpenAL."); + + if (OpenALHardwareDeviceDriver.IsSupported) + { + Logger.Warning?.Print(LogClass.Audio, "Found OpenAL, changing configuration."); + + ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.OpenAl; + MainWindow.SaveConfig(); + + deviceDriver = new OpenALHardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, trying to fall back to SoundIO."); + + if (SoundIoHardwareDeviceDriver.IsSupported) + { + Logger.Warning?.Print(LogClass.Audio, "Found SoundIO, changing configuration."); + + ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SoundIo; + MainWindow.SaveConfig(); + + deviceDriver = new SoundIoHardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, falling back to dummy audio out."); + } + } + } + } + else if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SoundIo) + { + if (SoundIoHardwareDeviceDriver.IsSupported) + { + deviceDriver = new SoundIoHardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, trying to fall back to SDL2."); + + if (SDL2HardwareDeviceDriver.IsSupported) + { + Logger.Warning?.Print(LogClass.Audio, "Found SDL2, changing configuration."); + + ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SDL2; + MainWindow.SaveConfig(); + + deviceDriver = new SDL2HardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "SDL2 is not supported, trying to fall back to OpenAL."); + + if (OpenALHardwareDeviceDriver.IsSupported) + { + Logger.Warning?.Print(LogClass.Audio, "Found OpenAL, changing configuration."); + + ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.OpenAl; + MainWindow.SaveConfig(); + + deviceDriver = new OpenALHardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, falling back to dummy audio out."); + } + } + } + } + else if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.OpenAl) + { + if (OpenALHardwareDeviceDriver.IsSupported) + { + deviceDriver = new OpenALHardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, trying to fall back to SDL2."); + + if (SDL2HardwareDeviceDriver.IsSupported) + { + Logger.Warning?.Print(LogClass.Audio, "Found SDL2, changing configuration."); + + ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SDL2; + MainWindow.SaveConfig(); + + deviceDriver = new SDL2HardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "SDL2 is not supported, trying to fall back to SoundIO."); + + if (SoundIoHardwareDeviceDriver.IsSupported) + { + Logger.Warning?.Print(LogClass.Audio, "Found SoundIO, changing configuration."); + + ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SoundIo; + MainWindow.SaveConfig(); + + deviceDriver = new SoundIoHardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, falling back to dummy audio out."); + } + } + } + } + + var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value + ? HLE.MemoryConfiguration.MemoryConfiguration6GB + : HLE.MemoryConfiguration.MemoryConfiguration4GB; + + IntegrityCheckLevel fsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None; + + HLE.HLEConfiguration configuration = new HLE.HLEConfiguration(VirtualFileSystem, + _parent.LibHacHorizonManager, + ContentManager, + _accountManager, + _userChannelPersistence, + renderer, + deviceDriver, + memoryConfiguration, + _parent.UiHandler, + (SystemLanguage)ConfigurationState.Instance.System.Language.Value, + (RegionCode)ConfigurationState.Instance.System.Region.Value, + ConfigurationState.Instance.Graphics.EnableVsync, + ConfigurationState.Instance.System.EnableDockedMode, + ConfigurationState.Instance.System.EnablePtc, + ConfigurationState.Instance.System.EnableInternetAccess, + fsIntegrityCheckLevel, + ConfigurationState.Instance.System.FsGlobalAccessLogMode, + ConfigurationState.Instance.System.SystemTimeOffset, + ConfigurationState.Instance.System.TimeZone, + ConfigurationState.Instance.System.MemoryManagerMode, + ConfigurationState.Instance.System.IgnoreMissingServices, + ConfigurationState.Instance.Graphics.AspectRatio, + ConfigurationState.Instance.System.AudioVolume); + + Device = new Switch(configuration); + } + + private void Window_SizeChanged(object sender, Size e) + { + Width = (int)e.Width; + Height = (int)e.Height; + + SetRendererWindowSize(e); + } + + private void MainLoop() + { + while (_isActive) + { + UpdateFrame(); + + // Polling becomes expensive if it's not slept + Thread.Sleep(1); + } + } + + private unsafe void RenderLoop() + { + Dispatcher.UIThread.InvokeAsync(() => + { + if (_parent.ViewModel.StartGamesInFullscreen) + { + _parent.WindowState = WindowState.FullScreen; + } + + if (_parent.WindowState == WindowState.FullScreen) + { + _parent.ViewModel.ShowMenuAndStatusBar = false; + } + }); + + IRenderer renderer = Device.Gpu.Renderer; + + if (renderer is ThreadedRenderer tr) + { + renderer = tr.BaseRenderer; + } + + _renderer = renderer; + + _renderer.ScreenCaptured += Renderer_ScreenCaptured; + + (_renderer as Renderer).InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(Renderer.GameContext)); + + Renderer.MakeCurrent(); + + Device.Gpu.Renderer.Initialize(_glLogLevel); + + Width = (int)Renderer.Bounds.Width; + Height = (int)Renderer.Bounds.Height; + + _renderer.Window.SetSize((int)(Width * Program.WindowScaleFactor), (int)(Height * Program.WindowScaleFactor)); + + Device.Gpu.Renderer.RunLoop(() => + { + Device.Gpu.SetGpuThread(); + Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token); + Translator.IsReadyForTranslation.Set(); + + Renderer.Start(); + + Renderer.QueueRender(); + + while (_isActive) + { + if (Device.WaitFifo()) + { + Device.Statistics.RecordFifoStart(); + Device.ProcessFrame(); + Device.Statistics.RecordFifoEnd(); + } + + while (Device.ConsumeFrameAvailable()) + { + if (!_renderingStarted) + { + _renderingStarted = true; + _parent.SwitchToGameControl(); + } + + Device.PresentFrame(Present); + } + } + + Renderer.Stop(); + }); + + Renderer?.MakeCurrent(null); + + Renderer.SizeChanged -= Window_SizeChanged; + } + + private void Present(object image) + { + // Run a status update only when a frame is to be drawn. This prevents from updating the ui and wasting a render when no frame is queued + string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? LocaleManager.Instance["Docked"] : LocaleManager.Instance["Handheld"]; + float scale = GraphicsConfig.ResScale; + + if (scale != 1) + { + dockedMode += $" ({scale}x)"; + } + + string vendor = _renderer is Renderer renderer ? renderer.GpuVendor : ""; + + StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs( + Device.EnableDeviceVsync, + Device.GetVolume(), + dockedMode, + ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(), + LocaleManager.Instance["Game"] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)", + $"FIFO: {Device.Statistics.GetFifoPercent():00.00} %", + $"GPU: {vendor}")); + + Renderer.Present(image); + } + + public async Task ShowExitPrompt() + { + bool shouldExit = !ConfigurationState.Instance.ShowConfirmExit; + + if (!shouldExit) + { + if (_dialogShown) + { + return; + } + + _dialogShown = true; + shouldExit = await ContentDialogHelper.CreateStopEmulationDialog(_parent); + + _dialogShown = false; + } + + if (shouldExit) + { + Stop(); + } + } + + private void HandleScreenState(KeyboardStateSnapshot keyboard, KeyboardStateSnapshot lastKeyboard) + { + if (ConfigurationState.Instance.Hid.EnableMouse) + { + if (_isMouseInClient) + { + Dispatcher.UIThread.Post(() => + { + _parent.Cursor = InvisibleCursor; + }); + } + } + else + { + if (_hideCursorOnIdle) + { + long cursorMoveDelta = Stopwatch.GetTimestamp() - _lastCursorMoveTime; + + Dispatcher.UIThread.Post(() => + { + _parent.Cursor = cursorMoveDelta >= CursorHideIdleTime * Stopwatch.Frequency ? InvisibleCursor : Cursor.Default; + }); + } + } + } + + private bool UpdateFrame() + { + if (!_isActive) + { + return false; + } + + if (_parent.IsActive) + { + Dispatcher.UIThread.Post(() => + { + KeyboardStateSnapshot keyboard = _keyboardInterface.GetKeyboardStateSnapshot(); + + HandleScreenState(keyboard, _lastKeyboardSnapshot); + + if (keyboard.IsPressed(Key.Delete)) + { + if (_parent.WindowState != WindowState.FullScreen) + { + Ptc.Continue(); + } + } + + _lastKeyboardSnapshot = keyboard; + }); + } + + NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); + + if (_parent.IsActive) + { + KeyboardHotkeyState currentHotkeyState = GetHotkeyState(); + + if (currentHotkeyState != _prevHotkeyState) + { + switch (currentHotkeyState) + { + case KeyboardHotkeyState.ToggleVSync: + Device.EnableDeviceVsync = !Device.EnableDeviceVsync; + break; + case KeyboardHotkeyState.Screenshot: + ScreenshotRequested = true; + break; + case KeyboardHotkeyState.ShowUi: + _parent.ViewModel.ShowMenuAndStatusBar = true; + break; + case KeyboardHotkeyState.Pause: + if (_parent.ViewModel.IsPaused) + { + Resume(); + } + else + { + Pause(); + } + break; + case KeyboardHotkeyState.ToggleMute: + if (Device.IsAudioMuted()) + { + Device.SetVolume(ConfigurationState.Instance.System.AudioVolume); + } + else + { + Device.SetVolume(0); + } + + _parent.ViewModel.Volume = Device.GetVolume(); + break; + case KeyboardHotkeyState.None: + (_keyboardInterface as AvaloniaKeyboard).Clear(); + break; + } + } + + _prevHotkeyState = currentHotkeyState; + + if (ScreenshotRequested) + { + ScreenshotRequested = false; + _renderer.Screenshot(); + } + } + + // Touchscreen + bool hasTouch = false; + + if (_parent.IsActive && !ConfigurationState.Instance.Hid.EnableMouse) + { + hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as AvaloniaMouseDriver).IsButtonPressed(MouseButton.Button1), ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); + } + + if (!hasTouch) + { + Device.Hid.Touchscreen.Update(); + } + + Device.Hid.DebugPad.Update(); + + return true; + } + + private KeyboardHotkeyState GetHotkeyState() + { + KeyboardHotkeyState state = KeyboardHotkeyState.None; + + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleVsync)) + { + state = KeyboardHotkeyState.ToggleVSync; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot)) + { + state = KeyboardHotkeyState.Screenshot; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUi)) + { + state = KeyboardHotkeyState.ShowUi; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause)) + { + state = KeyboardHotkeyState.Pause; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleMute)) + { + state = KeyboardHotkeyState.ToggleMute; + } + + return state; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Ava/Assets/Fonts/SegoeFluentIcons.ttf b/Ryujinx.Ava/Assets/Fonts/SegoeFluentIcons.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8f05a4bbc13461ba7144efa9c0d3f38634de1283 GIT binary patch literal 408752 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%0A|xtMb)(B7--Xm2F6 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�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 z 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

m`f zt8g}1{0VaR^rXy;|*48Yp8SSt>`_pHMm%LVSh?^SjGz~FFUz=Kp)Pv2iMny z+UmlFVRYs|Fq00J?z%+hiDq08UtQd*6~0Zkv4zOn7JVYcv~gYK+51Gg;G)pUkA^Q6 zK8(3}2y@ekxw)TJ{pEl?O+cDFjf$7r^wgX&Ye@iCXDBF#N=jzUQJ6E%Hc9`=4AoXX z*3^cnSd~lOskc;A)rLa3N(bsn!uDz%srEZe4%;Ve6+)HKQXy(9KH>2>A~mj&YCsuC zNyrDl5l-3|->g^p&&- zyuK>&6y1!Do#Jyvp)8*O10F~Ha}7p|#wPr&71c&UfLCz#0md+nPSo8;o3H~Rn z9CM#N!dc0Q3pIm?ExZFs(S3q4^O|k8p(Zni%^YfWnB25gQRPpBo$^uOcr=K#%W6vm zl`*pVV>U~`BpgpRmcEK0QQOMtv1-APO8UQ0wqITAroeZIX1rWo>CVQSZJvLnU;m*^ z`v2leYL^Kn{i;V-g>to_si)|Gc%6j$Ch9_R!K5tgTWshOn=$;JM`n%;s7HvX$8FjO zMn(hqT^X7G%ify+wo#sW<8$b=Y}t}5%a$zLl6Cl$5BWZ2J9eBnmvbNFCJ7`YA>?R6 zI3_>{1QJ>(rOUBU($a&r^k24gfi6`~*mk!EwB2pH+tMxVcH8aOZfRL!Ta)kiyzh)8 zA0fb&?*8{{z|zcU=AD`Mc<$%-Knj_AWWwHYePA$77#M*D#v7hBENJzEMDdNgAP>E5 zdQV2-X^V-&zYw)~(`4L|V-RI5DNe|BTJv@ZH8_TeU|q6FwMKS8#j<8OtlF?+&*DIC zeV%Ld*3i6RLuS-RM$!b-P+grpRMSW!?2~`yr|&2~{h*=OaGCKn%4XuUimeDD6xo!@ zSzO}S5D$go8yqFYPUCA{hx(Ai`&=j<4?X8~h}#`rj1=-{7t}`_gbHDdC?Vm)%HSZ1vc*Qeeew^eHaIM8$uGB|+)UI~o*#^P)PEGK zJh5QVqkdogz9$%r<=Vt5^&gRlYe-~?e7ES^Pki?^^*j1GbR9KC*Xb>xb!rN&2O97$ z=f$MiYT5xCOoDm_K}lL{9)&+;gl%TXpv4R0vx`y*4-Fn#-5g(YXmIG@DpXxRXxfoD^4P9i zpS`&^P+VB$>fJI_S-oWcNRB(F#P0+ah0lA%hY$Csz7J6O!NJ3;k+XexaPZ(70Pe`* zu=st`-e zu@?Bl?y_o!E9~CzgPT^KxpsGxBjS(sS}Y}lp@Zs=)c1Mox`V;)I&TU4uJ?)`^w#yz zkrMqE@%t@XPp`3;IEd(bJtd8vO=}~gy9SDU9Sx2md;M7D-UIS0fu4FVyMn&!(PmE_ z|L$h{)0?9@9Ob!_ za=bmh?&$E~bq&Vc+ydiu()kiGTwLGO6{%a;S_ZU)gNA+Lscn;YuknVZ>x>1t1~q=$ z=LOjuv1=yZvrRl@*w3C4Yr?D`R<#v+8=Fhu z!RT$oqtYjr>Ph)x0wy^}Gw|UkU`3;X3~0^TM-pqsXPQcq>ccUJX-i@OwsMj~Z*#E4 zl#pb)3Uh@K`l4B(Q&UWHQq!uH%=SfgE;}^^oee?CbS~QCdt3`OGV6O4DsL=gD4jE~ z5B$I%W=u6JP6%Fo$RAwzhWv3c98GQ`pmQCJQXumH!V01d$<=R~0hTZeNK;8A=#hCy zePY4!N7w7sY&U=K=&<%9dn;793y+>#wd&kx*ynM4%Hg4p>^Njn+CSD`+pX*3TAYkQ z&`pf$3+o>_I-(c59lh?cU+TZ4P6C?jNPR3;ub%dW?Pu)xpFEEZ0-pz7*baeA2n-fx z7z_~A&}IU|)L}vKnw0|>Igqt!62~uAlNCmkbh(=bZ?SZF{dI$~N6$B(`nSRB>QfIE zdrj}r)zqoqR*#mZ9xN!3F83|05|^ojSb`FX04?D;ZrEf;fJw;<;TVL zzo#CL$K_Ryh?^=?NKaPQP#h1QaFqfSTET?edYbTF$)s-K8%nglAN9U-?aUyxMKXC2 zUXkxJq-nH@nzSSUk?jU!2TCUwJ8p|&C`U4&2cYfM9i&>U}R-r3GW37EDb zqS6&b{+gl8m>F{2B*U%Cg3wb8()PiP^~)=yH3sTj{thxDa{8R7Z)T>V*&!;<{M1M7 zm4#KMsfY4i^wv5$9!KRv;}c(Z<)hikiqs={E+gv|YMaL<(gf05CW3>%$;S-RHhVrA zL_7sJlUARk7n=4BSnG`Vk#@$-Yg|vGU;}_kB|d?x+qAlS ztm~pgIXtAOF;gkBH6c-vaH0v^l^}*Cg=2@}i}wy!R1GYPZhK$>ypGNa%QMB^?W>J8 zQ*MD=)7)4QtdmTx%C@S(Cl6Z9PesD53R~xY>uvC?9+#{2H%1}Q9gdf%`2KO-!SG)JiH;#?slk~+mH4fOfEE-3T_wk`W8BJ~{zw zd)rod-+2n$eq!OHAiU7?v$)%zgtxuJ{rAqYE@m0|bC10RX8-3Nd#mEnpL^`jJ?1R@ zGaY-YI?OxWW4hF#Ah&KKDh9&7Kxw-(TyQ+ecn%w`-d#U2jy>#ihB>s6W z^}mT;`rkE|bPe;5Oqbr-sq`nN-S1Ezy|WW(dLQk6r}-Q4%|GHCqJ+yoGGAl(F{zic z?US6V?Re+Lx3yT3V_S zZ+p>oMpMaX=bXHb5C`5KA5UBRv;d~J#l<< z7EZK$y$!6Yftp516LVTN%&R(#I+C^iY^a!fcnqIl zbLZQN>3*0lu6uYLq6IhgUtkeTeS?F26vu_Sni>WgqXk$lgsO^AGZu~4%qq;lRvj+# zWCf?Pjz)A*CT`0SU0r!ck?#Jw`x-st;4IAX-n$i9{Br_UJ5NO{ibIvm@2mwR2G&Dg(FWj9hW^ z$jHrCjN}k_yE4C%)bUJZt*L)1D7Hwy(c79(Jn<5X-ApZ-kl5MamS| zY?K8{g$r1tvS1AR@^Zy)^=sJ3%{41drvpH3xw$wA`L&9MM%Ku%_^v{Z^qshs8!%!V z)R%b+rVk84^(mTOj}e}0QOpc0mS@C!n|X2&3%zsA_)S)Tqyz&$b0cIz;JAJPMQf5# zmrQ{^HLXZUiEKcy*bL!F0 z-1R^w>52{ghCi*eJO-MrJ3{Pd*|lu`SFzgJHw*V+1sE}cSop}lq}V+K*f}|Pf0^WT z7fN;snGF#T4tz0qQ@vClh19cJ^sG>lA%+W%wtMdC1(-{GZr3d(A{2BX<(l^kKGeQ4FD$QSqc!*cji8H8|M&-*o<>z@F zAzo9cZ~yKq+ARfE;QUsaM(><2JKP>uuGw2%n$J(5;xqa!-^90&Bj6t!okA_IVTW*w z@O~gCzaV@ctAl4ZWc9F~)TQ%)nduBO>x~(Syk$*$W5C4G=%{`MC>AJPOlDM#g=Z75 zKt?3}B(@OPBdxwR=9QyX%zYvoWAK)AtAs>L3shrDqM)-2Li-|IFDVW8RXx11wPn+A zHFuNI7FEXW*gS$DOCnP`v!L(PNn!|9!<$-J1>KR&Q?H8OD^e2-Ffg`gd#lYncE|1n z7J+89h6_LmHB-bt9Fj+W1v^Fe*8uJy=|)yBY=GL9DZN({Ppk za!SuUqA*s6rDxCDA$LW8uz3Bk_pTVBC%YhcLNSltLU}{Lfy{+SZtD^h@rv}!qcy(% z?)?iK_7XciHod^U4Q|JfmfCXYZAZ>*|*5=@(a7 zbL59+V>2P?!z~K#nzAzY86t0I++}4obVO1{4*$i9RSzB;Gl<7uxNGr}vo9PUIeh;L zk1^M{q^n_3tAC(5G_P{>ss*9Y=vAYY^TOR7@m2e}d-ttusz_8Y%s~#9PpC~mAN&@z zN{ycp*HgEukDXz+y#VkqzD8O8*9zc5oJ4hvhlFnkKM;N{{0=w?f`(fSf*Cbxb>YG! zZSj<&b_b7)CTTMqY00c-K>TWv?X<2mlsU$$z-Mul*xDnuvxd+vNvt>vxg#ES6{7aD zV#$r!+a&yRy(G`?t0#g({L_5otf_PZ2aq@PcN+SV911!wmrZue}CkCr;n+1PE z&`FcBerc(?RxEEBjz))D%0+c;>C$?d&(2_jUq2^2>=QpH(%F%b)(U_^f6g}?)=!$k z!sA>EH6BJf`vydH2tVT*b)HDAbdHGPN6;$nG*zXaYZCM=;@hr*AB*fbW{Y=zN$8;2JIiZGjzumsyQUP==^ZpWlsdv8_!Y_Wr zU+VrDtpNU){Q&6~!W_$L3Nf24kO8xVC`{M1yv3mv;%RjVP^CoVQF9Rr(R93%wpJIL zWz5P6<&UclMDFJSt_aU_ABW${a#Db%Y45 zKACuN1y-qy3;iTnF%gOip~ZY+g0APlCa43h^(QndL$2@v^uT7|&#hqyxEY{dJ+Xii zAF)lE6DuEp)FW0~080nx&+To_))p#rbn!Fv6a{t$X#uqMXyuuN+}!-~{KT$EV=UGf z*_Ftzu;=DNI(FtXtXVuzvS&}pz>+mFt1A~3mTk6uN008a*`i*%(^lTyQd=;8etunR zcUewho;Uis+0xkB+DNrc%L*$R0}e-^sl2ev8~KT&$OiV%b7txGZ|&Xtt?f(C_F&gm ze=HS}xSGN54_1AwK-@Scp&}>FgiUPOoDp7doAyK%><}UDt%~tQ=!o}f7KJ}?b zDwix8Dz;d{C}QQz89cRO#i>D7*tIykX4Ce@hV7fzhCCtW`5|s&R?w%EiOD94Mrmdm z{D~Z8W*LD5zhTuNK9^|tXwGCR18fmaALOoKXbp->a!s`I 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%0A8TuF$PW8~*o|99)+yH2ieQ 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 T-|Wu*C93JG*3SoP9%lXz9{#lZ literal 0 HcmV?d00001 diff --git a/Ryujinx.Ava/Assets/Locales/de_DE.json b/Ryujinx.Ava/Assets/Locales/de_DE.json new file mode 100644 index 000000000..ab859a599 --- /dev/null +++ b/Ryujinx.Ava/Assets/Locales/de_DE.json @@ -0,0 +1,549 @@ +{ + "MenuBarFileOpenApplet": "Applet öffnen", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Öffnet das Mii Editor Applet im Standalone Modus", + "SettingsTabInputDirectMouseAccess": "Direkter Mauszugriff", + "SettingsTabSystemMemoryManagerMode": "Speichermanagermodus:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Host (schnell)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host ungeprüft (am schnellsten, unsicher)", + "MenuBarFile": "_Datei", + "MenuBarFileOpenFromFile": "_Datei öffnen", + "MenuBarFileOpenUnpacked": "_Entpacktes Spiel öffnen", + "MenuBarFileOpenEmuFolder": "Ryujinx-Ordner öffnen", + "MenuBarFileOpenLogsFolder": "Logs-Ordner öffnen", + "MenuBarFileExit": "_Beenden", + "MenuBarOptions": "Optionen", + "MenuBarOptionsToggleFullscreen": "Vollbild", + "MenuBarOptionsStartGamesInFullscreen": "Spiele im Vollbildmodus starten", + "MenuBarOptionsStopEmulation": "Emulation beenden", + "MenuBarOptionsSettings": "_Einstellungen", + "MenuBarOptionsManageUserProfiles": "_Profilverwaltung", + "MenuBarActions": "_Aktionen", + "MenuBarOptionsSimulateWakeUpMessage": "Aufwachnachricht", + "MenuBarActionsScanAmiibo": "Amiibo scannen", + "MenuBarTools": "_Werkzeuge", + "MenuBarToolsInstallFirmware": "Firmware installieren", + "MenuBarFileToolsInstallFirmwareFromFile": "Installiere Firmware von einer XCI oder einer ZIP Datei", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Installiere Firmware aus einem Verzeichnis", + "MenuBarHelp": "Hilfe", + "MenuBarHelpCheckForUpdates": "Nach Updates suchen", + "MenuBarHelpAbout": "Über Ryujinx", + "MenuSearch": "Suchen...", + "GameListHeaderFavorite": "Favorit", + "GameListHeaderIcon": "Icon", + "GameListHeaderApplication": "Name", + "GameListHeaderDeveloper": "Entwickler", + "GameListHeaderVersion": "Version", + "GameListHeaderTimePlayed": "Spielzeit", + "GameListHeaderLastPlayed": "Zuletzt gespielt", + "GameListHeaderFileExtension": "Dateiformat", + "GameListHeaderFileSize": "Dateigröße", + "GameListHeaderPath": "Pfad", + "GameListContextMenuOpenUserSaveDirectory": "Spielstand-Verzeichnis öffnen", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Öffnet das Verzeichnis, welches den Benutzer-Spielstand beinhaltet", + "GameListContextMenuOpenUserDeviceDirectory": "Benutzer-Geräte-Verzeichnis öffnen", + "GameListContextMenuOpenUserDeviceDirectoryToolTip": "Öffnet das Verzeichnis, welches den Geräte-Spielstände beinhaltet", + "GameListContextMenuOpenUserBcatDirectory": "Benutzer-BCAT-Vezeichnis öffnen", + "GameListContextMenuOpenUserBcatDirectoryToolTip": "Öffnet das Verzeichnis, welches den BCAT Cache des Spiels beinhaltet", + "GameListContextMenuManageTitleUpdates": "Verwalten von Spiel Updates", + "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", + "GameListContextMenuCacheManagementPurgePptc": "PPTC Cache löschen", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Löscht den PPTC Cache der Anwendung", + "GameListContextMenuCacheManagementPurgeShaderCache": "Shader Cache löschen", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Löscht den Shader Cache der Anwendung", + "GameListContextMenuCacheManagementOpenPptcDirectory": "PPTC Verzeichnis öffnen", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Öffnet das Verzeichnis, das den PPTC Cache der Anwendung beinhaltet", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Shader Cache Verzeichnis öffnen", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Öffnet das Verzeichnis, das den Shader Cache der Anwendung beinhaltet", + "GameListContextMenuExtractData": "Daten extrahieren", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extrahiert das ExeFS aus der aktuellen Anwendungskonfiguration (einschließlich Updates)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extrahiert das RomFS aus der aktuellen Anwendungskonfiguration (einschließlich Updates)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Extrahiert das Logo aus der aktuellen Anwendungskonfiguration (einschließlich Updates)", + "StatusBarGamesLoaded": "{0}/{1} Spiele geladen", + "StatusBarSystemVersion": "Systemversion: {0}", + "Settings": "Einstellungen", + "SettingsTabGeneral": "Allgemein", + "SettingsTabGeneralGeneral": "Allgemein", + "SettingsTabGeneralEnableDiscordRichPresence": "Aktiviere Discord Rich Presence", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Beim Start nach Updates suchen", + "SettingsTabGeneralShowConfirmExitDialog": "Zeige den \"Beenden bestätigen\" Dialog", + "SettingsTabGeneralHideCursorOnIdle": "Mauszeiger bei Inaktivität ausblenden", + "SettingsTabGeneralGameDirectories": "Spielverzeichnisse", + "SettingsTabGeneralAdd": "Hinzufügen", + "SettingsTabGeneralRemove": "Entfernen", + "SettingsTabSystem": "System", + "SettingsTabSystemCore": "Kern", + "SettingsTabSystemSystemRegion": "System Region:", + "SettingsTabSystemSystemRegionJapan": "Japan", + "SettingsTabSystemSystemRegionUSA": "USA", + "SettingsTabSystemSystemRegionEurope": "Europa", + "SettingsTabSystemSystemRegionAustralia": "Australien", + "SettingsTabSystemSystemRegionChina": "China", + "SettingsTabSystemSystemRegionKorea": "Korea", + "SettingsTabSystemSystemRegionTaiwan": "Taiwan", + "SettingsTabSystemSystemLanguage": "Systemsprache:", + "SettingsTabSystemSystemLanguageJapanese": "Japanisch", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Amerikanisches Englisch", + "SettingsTabSystemSystemLanguageFrench": "Französisch", + "SettingsTabSystemSystemLanguageGerman": "Deutsch", + "SettingsTabSystemSystemLanguageItalian": "Italienisch", + "SettingsTabSystemSystemLanguageSpanish": "Spanisch", + "SettingsTabSystemSystemLanguageChinese": "Chinesisch", + "SettingsTabSystemSystemLanguageKorean": "Koreanisch", + "SettingsTabSystemSystemLanguageDutch": "Niederländisch", + "SettingsTabSystemSystemLanguagePortuguese": "Portugiesisch", + "SettingsTabSystemSystemLanguageRussian": "Russisch", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanesisch", + "SettingsTabSystemSystemLanguageBritishEnglish": "Britisches Englisch", + "SettingsTabSystemSystemLanguageCanadianFrench": "Kanadisches Französisch", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Lateinamerikanisches Spanisch", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Vereinfachtes Chinesisch", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Traditionelles Chinesisch", + "SettingsTabSystemSystemTimeZone": "System Zeitzone:", + "SettingsTabSystemSystemTime": "System Zeit:", + "SettingsTabSystemEnableVsync": "Aktiviere VSync", + "SettingsTabSystemEnablePptc": "Aktiviere PPTC Cache (Profiled presistent translastion cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Aktiviere die FS Integritätsprüfung", + "SettingsTabSystemAudioBackend": "Audio-Backend:", + "SettingsTabSystemAudioBackendDummy": "Dummy", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": " - Können Fehler verursachen", + "SettingsTabSystemExpandDramSize": "Erweitere DRAM Größe auf 6GB", + "SettingsTabSystemIgnoreMissingServices": "Ignoriere fehlende Dienste", + "SettingsTabGraphics": "Grafik", + "SettingsTabGraphicsEnhancements": "Verbesserungen", + "SettingsTabGraphicsEnableShaderCache": "Aktiviere den Shader Cache", + "SettingsTabGraphicsAnisotropicFiltering": "Anisotrope Filterung:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Auflösungsskalierung:", + "SettingsTabGraphicsResolutionScaleCustom": "Benutzerdefiniert (nicht empfohlen)", + "SettingsTabGraphicsResolutionScaleNative": "Nativ (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Bildseitenverhältnis:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Dehnen, um sich an das Fenster anzupassen", + "SettingsTabGraphicsDeveloperOptions": "Optionen für Entwickler", + "SettingsTabGraphicsShaderDumpPath": "Grafik-Shader Dump Pfad:", + "SettingsTabLogging": "Logs", + "SettingsTabLoggingLogging": "Logs", + "SettingsTabLoggingEnableLoggingToFile": "Aktiviere Erstellung von Log-Datei", + "SettingsTabLoggingEnableStubLogs": "Aktiviere Stub-Logs", + "SettingsTabLoggingEnableInfoLogs": "Aktiviere Info-Logs", + "SettingsTabLoggingEnableWarningLogs": "Aktiviere Warn-Logs", + "SettingsTabLoggingEnableErrorLogs": "Aktiviere Fehler-Logs", + "SettingsTabLoggingEnableTraceLogs": "Aktiviere Trace-Logs", + "SettingsTabLoggingEnableGuestLogs": "Aktiviere Gast-Logs", + "SettingsTabLoggingEnableFsAccessLogs": "Aktiviere Fs Zugriff-Logs", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs Globaler Zugriff-Log-Modus:", + "SettingsTabLoggingDeveloperOptions": "Entwickleroptionen (WARNUNG: Beeinträchtigt die Leistung)", + "SettingsTabLoggingOpenglLogLevel": "OpenGL Logstufe:", + "SettingsTabLoggingOpenglLogLevelNone": "Keine", + "SettingsTabLoggingOpenglLogLevelError": "Fehler", + "SettingsTabLoggingOpenglLogLevelPerformance": "Verlangsamungen", + "SettingsTabLoggingOpenglLogLevelAll": "Alle", + "SettingsTabLoggingEnableDebugLogs": "Aktiviere Debug-Log", + "SettingsTabInput": "Eingabe", + "SettingsTabInputEnableDockedMode": "Docked Modus", + "SettingsTabInputDirectKeyboardAccess": "Direkter Tastaturzugriff", + "SettingsButtonSave": "Speichern", + "SettingsButtonClose": "Schließen", + "SettingsButtonApply": "Übernehmen", + "ControllerSettingsPlayer": "Spieler", + "ControllerSettingsPlayer1": "Spieler 1", + "ControllerSettingsPlayer2": "Spieler 2", + "ControllerSettingsPlayer3": "Spieler 3", + "ControllerSettingsPlayer4": "Spieler 4", + "ControllerSettingsPlayer5": "Spieler 5", + "ControllerSettingsPlayer6": "Spieler 6", + "ControllerSettingsPlayer7": "Spieler 7", + "ControllerSettingsPlayer8": "Spieler 8", + "ControllerSettingsHandheld": "Handheld", + "ControllerSettingsInputDevice": "Eingabegerät", + "ControllerSettingsRefresh": "Aktualisieren", + "ControllerSettingsDeviceDisabled": "Deaktiviert", + "ControllerSettingsControllerType": "Controller Typ", + "ControllerSettingsControllerTypeHandheld": "Handheld", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon Paar", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Links", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon Rechts", + "ControllerSettingsProfile": "Profil", + "ControllerSettingsProfileDefault": "Default", + "ControllerSettingsLoad": "Laden", + "ControllerSettingsAdd": "Hinzufügen", + "ControllerSettingsRemove": "Entfernen", + "ControllerSettingsButtons": "Aktionstasten", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Steuerkreuz", + "ControllerSettingsDPadUp": "Hoch", + "ControllerSettingsDPadDown": "Runter", + "ControllerSettingsDPadLeft": "Links", + "ControllerSettingsDPadRight": "Rechts", + "ControllerSettingsLStick": "Linker Analogstick", + "ControllerSettingsLStickButton": "L3", + "ControllerSettingsLStickUp": "Hoch", + "ControllerSettingsLStickDown": "Runter", + "ControllerSettingsLStickLeft": "Links", + "ControllerSettingsLStickRight": "Rechts", + "ControllerSettingsLStickStick": "Analogstick", + "ControllerSettingsLStickInvertXAxis": "Invertiert X-Achse", + "ControllerSettingsLStickInvertYAxis": "Invertiert Y-Achse", + "ControllerSettingsLStickDeadzone": "Deadzone:", + "ControllerSettingsRStick": "Rechter Analogstick", + "ControllerSettingsRStickButton": "R3", + "ControllerSettingsRStickUp": "Hoch", + "ControllerSettingsRStickDown": "Runter", + "ControllerSettingsRStickLeft": "Links", + "ControllerSettingsRStickRight": "Rechts", + "ControllerSettingsRStickStick": "Analogstick", + "ControllerSettingsRStickInvertXAxis": "Invertiert X-Achse", + "ControllerSettingsRStickInvertYAxis": "Invertiert Y-Achse", + "ControllerSettingsRStickDeadzone": "Deadzone:", + "ControllerSettingsTriggersLeft": "Linker Trigger", + "ControllerSettingsTriggersRight": "Rechter Trigger", + "ControllerSettingsTriggersButtonsLeft": "Linke Schultertaste", + "ControllerSettingsTriggersButtonsRight": "Rechte Schultertaste", + "ControllerSettingsTriggers": "Triggers", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Linke Aktionstasten", + "ControllerSettingsExtraButtonsRight": "Rechte Aktionstasten", + "ControllerSettingsMisc": "Verschiedenes", + "ControllerSettingsTriggerThreshold": "Empfindlichkeit:", + "ControllerSettingsMotion": "Bewegungssteuerung", + "ControllerSettingsCemuHook": "CemuHook", + "ControllerSettingsMotionEnableMotionControls": "Aktiviere Bewegungssteuerung", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook kompatible Bewegungssteuerung", + "ControllerSettingsMotionControllerSlot": "Controller Slot:", + "ControllerSettingsMotionMirrorInput": "Spiegele Eingabe", + "ControllerSettingsMotionRightJoyConSlot": "Rechter JoyCon Slot:", + "ControllerSettingsMotionServerHost": "Server Host:", + "ControllerSettingsMotionGyroSensitivity": "Gyro Empfindlichkeit:", + "ControllerSettingsMotionGyroDeadzone": "Gyro Deadzone:", + "ControllerSettingsSave": "Speichern", + "ControllerSettingsClose": "Schließen", + "UserProfilesSelectedUserProfile": "Ausgewähltes Profil:", + "UserProfilesSaveProfileName": "Profilname speichern", + "UserProfilesChangeProfileImage": "Profilbild ändern", + "UserProfilesAvailableUserProfiles": "Verfügbare Profile:", + "UserProfilesAddNewProfile": "Neues Profil hinzufügen", + "UserProfilesDeleteSelectedProfile": "Ausgewähltes Profil löschen", + "UserProfilesClose": "Schließen", + "ProfileImageSelectionTitle": "Auswahl des Profilbildes", + "ProfileImageSelectionHeader": "Wähle ein Profilbild aus", + "ProfileImageSelectionNote": "Es kann ein eigenes Profilbild importiert werden oder ein Avatar aus der System-Firmware", + "ProfileImageSelectionImportImage": "Bilddatei importieren", + "ProfileImageSelectionSelectAvatar": "Firmware Avatar auswählen", + "InputDialogTitle": "Eingabe Dialog", + "InputDialogOk": "OK", + "InputDialogCancel": "Abbrechen", + "InputDialogAddNewProfileTitle": "Wähle den Profilnamen", + "InputDialogAddNewProfileHeader": "Bitte gebe einen Profilnamen ein", + "InputDialogAddNewProfileSubtext": "(Maximale Länge: {0})", + "AvatarChoose": "Bestätigen", + "AvatarSetBackgroundColor": "Hintergrundfarbe einstellen", + "AvatarClose": "Schließen", + "ControllerSettingsLoadProfileToolTip": "Lädt ein Profil", + "ControllerSettingsAddProfileToolTip": "Fügt ein Profil hinzu", + "ControllerSettingsRemoveProfileToolTip": "Entfernt ein Profil", + "ControllerSettingsSaveProfileToolTip": "Speichert ein Profil", + "MenuBarFileToolsTakeScreenshot": "Screenshot aufnehmen", + "MenuBarFileToolsHideUi": "Verstecke UI", + "GameListContextMenuToggleFavorite": "Als Favoriten hinzufügen/entfernen", + "GameListContextMenuToggleFavoriteToolTip": "Aktiviert den Favoriten-Status des Spiels", + "SettingsTabGeneralTheme": "Thema", + "SettingsTabGeneralThemeCustomTheme": "Verzeichnis für benutzerdefiniertes Thema", + "SettingsTabGeneralThemeBaseStyle": "Farbschema", + "SettingsTabGeneralThemeBaseStyleDark": "Dunkel", + "SettingsTabGeneralThemeBaseStyleLight": "Hell", + "SettingsTabGeneralThemeEnableCustomTheme": "Benutzerdefiniertes Thema", + "ButtonBrowse": "Durchsuchen", + "ControllerSettingsMotionConfigureCemuHookSettings": "CemuHook Motion konfigurieren", + "ControllerSettingsRumble": "Vibration", + "ControllerSettingsRumbleEnable": "Aktiviere Vibration", + "ControllerSettingsRumbleStrongMultiplier": "Starke Vibration - Multiplikator", + "ControllerSettingsRumbleWeakMultiplier": "Schwache Vibration - Multiplikator", + "DialogMessageSaveNotAvailableMessage": "Es existieren keine Speicherdaten für {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Soll Ryujinx Speicherdaten für dieses Spiel erstellen?", + "DialogConfirmationTitle": "Ryujinx - Bestätigung", + "DialogUpdaterTitle": "Ryujinx - Updater", + "DialogErrorTitle": "Ryujinx - Fehler", + "DialogWarningTitle": "Ryujinx - Warnung", + "DialogExitTitle": "Ryujinx - Beenden", + "DialogErrorMessage": "Ein Fehler ist aufgetreten", + "DialogExitMessage": "Ryujinx wirklich schließen?", + "DialogExitSubMessage": "Alle nicht gespeicherten Daten gehen verloren!", + "DialogMessageCreateSaveErrorMessage": "Es ist ein Fehler bei der Erstellung der angegebenen Speicherdaten aufgetreten: {0}", + "DialogMessageFindSaveErrorMessage": "Es ist ein Fehler beim Auffinden der angegebenen Speicherdaten aufgetreten: {0}", + "FolderDialogExtractTitle": "Wähle den Ordner, in welchen die Dateien entpackt werden sollen", + "DialogNcaExtractionMessage": "Extrahiert {0} abschnitt von {1}...", + "DialogNcaExtractionTitle": "Ryujinx - NCA-Abschnitt-Extraktor", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Extraktion fehlgeschlagen. Der Hauptheader der NCA war in der ausgewählten Datei nicht vorhanden.", + "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!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Es wird bereits die aktuellste Version von Ryujinx benutzt", + "DialogUpdaterFailedToGetVersionMessage": "Bei dem Versuch Versionsinformationen von AppVeyor zu erhalten, ist ein Fehler aufgetreten ", + "DialogUpdaterConvertFailedAppveyorMessage": "Die von AppVeyor erhaltene Ryujinx Version konnte nicht konvertiert werden.", + "DialogUpdaterDownloadingMessage": "Update wird Heruntergeladen...", + "DialogUpdaterExtractionMessage": "Update wird entpackt...", + "DialogUpdaterRenamingMessage": "Update wird umbenannt...", + "DialogUpdaterAddingFilesMessage": "Update wird hinzugefügt...", + "DialogUpdaterCompleteMessage": "Update abgeschlossen!", + "DialogUpdaterRestartMessage": "Ryujinx jetzt neu starten?", + "DialogUpdaterArchNotSupportedMessage": "Eine nicht unterstützte Systemarchitektur wird benutzt!", + "DialogUpdaterArchNotSupportedSubMessage": "Nur x64 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", + "DialogUpdaterDirtyBuildSubMessage": "Für eine unterstütze Version: Ryujinx bitte von hier herunterladen https://ryujinx.org/", + "DialogRestartRequiredMessage": "Neustart erforderlich", + "DialogThemeRestartMessage": "Das Thema wurde gespeichert. Ein Neustart ist erforderlich, um das Thema anzuwenden.", + "DialogThemeRestartSubMessage": "Jetzt neu starten?", + "DialogFirmwareInstallEmbeddedMessage": "Die in diesem Spiel enthaltene Firmware installieren? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Es wurde keine installierte Firmware gefunden, aber Ryujinx konnte die Firmware {0} aus dem bereitgestellten Spiel installieren.\nRyujinx wird nun gestartet.", + "DialogFirmwareNoFirmwareInstalledMessage": "Keine Firmware installiert", + "DialogFirmwareInstalledMessage": "Firmware {0} wurde installiert", + "DialogOpenSettingsWindowLabel": "Fenster-Einstellungen öffnen", + "DialogControllerAppletTitle": "Controller-Applet", + "DialogMessageDialogErrorExceptionMessage": "Fehler bei der Anzeige des Meldungs-Dialogs: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Fehler bei der Anzeige der Software-Tastatur: {0}", + "DialogErrorAppletErrorExceptionMessage": "Fehler beim Anzeigen des ErrorApplet-Dialogs: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nWeitere Informationen zur Behebung dieses Fehlers können in unserem Setup-Guide gefunden werden.", + "DialogUserErrorDialogTitle": "Ryujinx Fehler ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Beim Abrufen von Informationen aus der API ist ein Fehler aufgetreten.", + "DialogAmiiboApiConnectErrorMessage": "Verbindung zum Amiibo API Server kann nicht hergestellt werden. Der Dienst ist möglicherweise nicht verfügbar oder es existiert keine Internetverbindung.", + "DialogProfileInvalidProfileErrorMessage": "Das Profil {0} ist mit dem aktuellen Eingabekonfigurationssystem nicht kompatibel.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Das Standardprofil kann nicht überschrieben werden", + "DialogProfileDeleteProfileTitle": "Profil löschen", + "DialogProfileDeleteProfileMessage": "Diese Aktion kann nicht rückgängig gemacht werden. Wirklich fortfahren?", + "DialogWarning": "Warnung", + "DialogPPTCDeletionMessage": "Du bist dabei, den PPTC Cache zu löschen für :\n\n{0}\n\nWirklich fortfahren?", + "DialogPPTCDeletionErrorMessage": "Fehler bei der Löschung des PPTC Caches bei {0}: {1}", + "DialogShaderDeletionMessage": "Du bist dabei, den Shader Cache zu löschen für :\n\n{0}\n\nWirklich fortfahren?", + "DialogShaderDeletionErrorMessage": "Es ist ein Fehler bei der Löschung des Shader Caches bei {0}: {1} aufgetreten", + "DialogRyujinxErrorMessage": "Ein Fehler ist aufgetreten", + "DialogInvalidTitleIdErrorMessage": "UI Fehler: Das ausgewählte Spiel hat keine gültige Titel-ID", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Es wurde keine gültige System-Firmware gefunden in {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Installiere Firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Systemversion {0} wird jetzt installiert.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nDies wird die aktuelle Systemversion {0} ersetzen.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nMöchtest du fortfahren?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Firmware wird installiert...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Systemversion {0} wurde erfolgreich installiert.", + "DialogUserProfileDeletionWarningMessage": "Es können keine anderen Profile geöffnet werden, wenn das ausgewählte Profil gelöscht wird.", + "DialogUserProfileDeletionConfirmMessage": "Möchtest du das ausgewählte Profil löschen?", + "DialogControllerSettingsModifiedConfirmMessage": "Die aktuellen Controller-Einstellungen wurden aktualisiert.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Controller-Einstellungen speichern?", + "DialogDlcLoadNcaErrorMessage": "{0}. Fehlerhafte Datei: {1}", + "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?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Es wurde das Shader Dumping aktiviert, das nur von Entwicklern verwendet werden soll.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Für eine optimale Leistung wird empfohlen, das Shader Dumping zu deaktivieren. Shader Dumping jetzt deaktivieren?", + "DialogLoadAppGameAlreadyLoadedMessage": "Es wurde bereits ein Spiel gestartet", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Bitte beende die Emulation oder schließe den Emulator, vor dem Starten eines neuen Spiels", + "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.", + "SettingsTabGraphicsFeaturesOptions": "Erweiterungen", + "SettingsTabGraphicsBackendMultithreading": "Grafikbackend Multithreading:", + "CommonAuto": "Auto", + "CommonOff": "Aus", + "CommonOn": "An", + "InputDialogYes": "Ja", + "InputDialogNo": "Nein", + "DialogProfileInvalidProfileNameErrorMessage": "Der Dateiname enthält ungültige Zeichen. Bitte erneut versuchen.", + "MenuBarOptionsPauseEmulation": "Pause", + "MenuBarOptionsResumeEmulation": "Fortsetzen", + "AboutUrlTooltipMessage": "Klicke hier, um die Ryujinx Website im Standardbrowser zu öffnen.", + "AboutDisclaimerMessage": "Ryujinx ist in keinster Weise weder mit Nintendo™, \nnoch mit deren Partnern verbunden.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) wird in unserer Amiibo \nEmulation benutzt.", + "AboutPatreonUrlTooltipMessage": "Klicke hier, um die Ryujinx Patreon Seite im Standardbrowser zu öffnen.", + "AboutGithubUrlTooltipMessage": "Klicke hier, um die Ryujinx GitHub Seite im Standardbrowser zu öffnen.", + "AboutDiscordUrlTooltipMessage": "Klicke hier, um eine Einladung zum Ryujinx Discord Server im Standardbrowser zu öffnen.", + "AboutTwitterUrlTooltipMessage": "Klicke hier, um die Ryujinx Twitter Seite im Standardbrowser zu öffnen.", + "AboutRyujinxAboutTitle": "Über:", + "AboutRyujinxAboutContent": "Ryujinx ist ein Nintendo Switch™ Emulator.\nBitte unterstütze uns auf Patreon.\nAuf Twitter oder Discord erfährst du alle Neuigkeiten.\nEntwickler, die an einer Mitarbeit interessiert sind, können auf GitHub oder Discord mehr erfahren.", + "AboutRyujinxMaintainersTitle": "Entwickelt von:", + "AboutRyujinxMaintainersContentTooltipMessage": "Klicke hier, um die Liste der Mitwirkenden im Standardbrowser zu öffnen.", + "AboutRyujinxSupprtersTitle": "Unterstützt auf Patreon von:", + "AmiiboSeriesLabel": "Amiibo Serie", + "AmiiboCharacterLabel": "Charakter", + "AmiiboScanButtonLabel": "Einscannen", + "AmiiboOptionsShowAllLabel": "Zeige alle Amiibos", + "AmiiboOptionsUsRandomTagLabel": "Hack: Benutze zufällige Tag-UUID", + "DlcManagerTableHeadingEnabledLabel": "Aktiviert", + "DlcManagerTableHeadingTitleIdLabel": "Title-ID", + "DlcManagerTableHeadingContainerPathLabel": "Container-Pfad", + "DlcManagerTableHeadingFullPathLabel": "Vollständiger-Pfad", + "DlcManagerRemoveAllButton": "Entferne alle", + "MenuBarOptionsChangeLanguage": "Sprache ändern", + "CommonSort": "Sortieren", + "CommonShowNames": "Spiel-Namen anzeigen", + "CommonFavorite": "Favoriten", + "OrderAscending": "Aufsteigend", + "OrderDescending": "Absteigend", + "SettingsTabGraphicsFeatures": "Erweiterungen", + "ErrorWindowTitle": "Fehler-Fenster", + "ToggleDiscordTooltip": "Aktiviert/Deaktiviert Discord Rich Presence", + "AddGameDirBoxTooltip": "Gibt das Spielverzeichnis an, das der Liste hinzuzufügt wird", + "AddGameDirTooltip": "Fügt ein neues Spielverzeichnis hinzu", + "RemoveGameDirTooltip": "Entfernt das ausgewähltes Spielverzeichnis", + "CustomThemeCheckTooltip": "Aktiviert/Deaktiviert die benutzerdefinierten Thema in dem GUI", + "CustomThemePathTooltip": " Gibt das Verzeichnis zum benutzerdefinierten GUI Thema an", + "CustomThemeBrowseTooltip": "Ermöglicht die Suche nach benutzerdefinierten GUI Thema", + "DockModeToggleTooltip": "Aktiviert/Deaktiviert den Docked Mode", + "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)", + "RegionTooltip": "Ändert die Systemregion", + "LanguageTooltip": "Ändert die Systemsprache", + "TimezoneTooltip": "Ändert die Systemzeitzone", + "TimeTooltip": "Ändert die Systemzeit", + "VSyncToggleTooltip": "Aktiviert/Deaktiviert die Vertikale Synchronisierung", + "PptcToggleTooltip": "Aktiviert/Deaktiviert den PPTC", + "FsIntegrityToggleTooltip": "Aktiviert/Deaktiviert die Integritätsprüfung der Spieldateien", + "AudioBackendTooltip": "Ändert das Audio-Backend", + "MemoryManagerTooltip": "Ändert wie der Gastspeicher abgebildet wird und wie auf ihn zugegriffen wird. Beinflusst die Leistung der emulierten CPU erheblich.", + "MemoryManagerSoftwareTooltip": "Verwendung einer Software-Seitentabelle für die Adressumsetzung. Höchste Genauigkeit, aber langsamste Leistung.", + "MemoryManagerHostTooltip": "Direkte Zuordnung von Speicher im Host-Adressraum. Viel schnellere JIT-Kompilierung und Ausführung.", + "MemoryManagerUnsafeTooltip": "Direkte Zuordnung des Speichers, aber keine Maskierung der Adresse innerhalb des Gastadressraums vor dem Zugriff. Schneller, aber auf Kosten der Sicherheit. Die Gastanwendung kann von überall in Ryujinx auf den Speicher zugreifen, daher sollte in diesem Modus nur Programme ausgeführt werden denen vertraut wird.", + "DRamTooltip": "Erweitert den Speicher des emulierten Systems von 4 GB auf 6 GB", + "IgnoreMissingServicesTooltip": "Aktiviert/Deaktiviert die 'Ignoriere fehlende Dienste' Option", + "GraphicsBackendThreadingTooltip": "Aktiviert das Grafikbackend Multithreading", + "GalThreadingTooltip": "Führt Grafikbackendbefehle auf einem zweiten Thread aus. Ermöglicht Multithreading bei der Shader Kompilierung zur Laufzeit, reduziert Stottern und verbessert die Leistung bei Treibern ohne eigene Multithreading Unterstützung. Geringfügig abweichende Spitzenleistung bei Treibern mit Multithreading. Ryujinx muss möglicherweise neu gestartet werden, um das in den Treiber integrierte Multithreading korrekt zu deaktivieren, oder es muss manuell getan werden, um die beste Leistung zu erzielen.", + "ShaderCacheToggleTooltip": "Aktiviert/Deaktiviert Shader Cache", + "ResolutionScaleTooltip": "Wendet die Auflösungsskalierung auf anwendbare Render Ziele", + "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.", + "ShaderDumpPathTooltip": "Grafik-Shader-Dump-Pfad", + "FileLogTooltip": "Aktiviert/Deaktiviert die Erstellung und Speicherung eines Logs", + "StubLogTooltip": "Aktiviert die Ausgabe von Stub-Logs in der Konsole", + "InfoLogTooltip": "Aktiviert die Ausgabe von Info-Logs in der Konsole", + "WarnLogTooltip": "Aktiviert die Ausgabe von Warn-Logs in der Konsole", + "TraceLogTooltip": "Aktiviert die Ausgabe von Trace-Log in der Konsole", + "ErrorLogTooltip": "Aktiviert die Ausgabe von Fehler-Logs in der Konsole", + "GuestLogTooltip": "Aktiviert die Ausgabe von Gast-Logs in der Konsole", + "FileAccessLogTooltip": "Aktiviert die Ausgabe von FS-Zugriff-Logs in der Konsole", + "FSAccessLogModeTooltip": "Aktiviert die Ausgabe des FS-Zugriff-Logs in der Konsole. Mögliche Modi sind 0-3", + "DeveloperOptionTooltip": "Mit Vorsicht verwenden", + "OpenGlLogLevel": "Erfordert die Aktivierung der entsprechenden Log-Level", + "DebugLogTooltip": "Aktiviert das Dokumentierern von Debug-Protokollmeldungen", + "LoadApplicationFileTooltip": "Öffnet die Dateiauswahl um Datei zu laden, welche mit der Switch kompatibel ist", + "LoadApplicationFolderTooltip": "Öffnet die Dateiauswahl um ein Spiel zu laden, welches mit der Switch kompatibel ist", + "OpenRyujinxFolderTooltip": "Öffnet den Ordner, der das Ryujinx Dateisystem enthält", + "OpenRyujinxLogsTooltip": "Öffnet den Ordner, in welchem die Logs gespeichert werden", + "ExitTooltip": "Beendet Ryujinx", + "OpenSettingsTooltip": "Öffnet das Einstellungsfenster", + "OpenProfileManagerTooltip": "Öffnet das Profilverwaltungsfenster", + "StopEmulationTooltip": "Beendet die Emulation des derzeitigen Spiels und kehrt zu der Spielauswahl zurück", + "CheckUpdatesTooltip": "Sucht nach Updates für Ryujinx", + "OpenAboutTooltip": "Öffnet das 'Über Ryujinx'-Fenster", + "GridSize": "Rastergröße", + "GridSizeTooltip": "Ändert die Größe der Rasterelemente", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brasilianisches Portugiesisch", + "AboutRyujinxContributorsButtonHeader": "Alle Mitwirkenden anzeigen", + "SettingsTabSystemAudioVolume" : "Lautstärke: ", + "AudioVolumeTooltip": "Ändert die Lautstärke", + "SettingsTabSystemEnableInternetAccess": "Aktiviert den Gast-Internet-Zugang", + "EnableInternetAccessTooltip": "Aktiviert den Gast-Internet-Zugang. Die Anwendung verhält sich so, als ob die emulierte Switch-Konsole mit dem Internet verbunden wäre. Beachte, dass in einigen Fällen Anwendungen auch bei deaktivierter Option auf das Internet zugreifen können", + "GameListContextMenuManageCheatToolTip": "Öffnet den Cheat-Manager", + "GameListContextMenuManageCheat": "Cheats verwalten", + "ControllerSettingsStickRange": "Bereich", + "DialogStopEmulationTitle": "Ryujinx - Beende Emulation", + "DialogStopEmulationMessage": "Emulation wirklich beenden?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Audio", + "SettingsTabNetwork": "Netzwerk", + "SettingsTabNetworkConnection": "Netwerkverbindung", + "SettingsTabGraphicsFrameRate": "Host Aktualisierungsrate:", + "SettingsTabGraphicsFrameRateTooltip": "Aktiviert die Host Aktualisierungsrate. Auf 0 setzen, um den Grenzwert aufzuheben.", + "SettingsTabCpuCache": "CPU-Cache", + "SettingsTabCpuMemory": "CPU-Speicher", + "DialogUpdaterFlatpakNotSupportedMessage": "Bitte Aktualisiere Ryujinx mit FlatHub", + "UpdaterDisabledWarningTitle": "Updater deaktiviert!", + "GameListContextMenuOpenSdModsDirectory": "Atmosphere-Mod-Verzeichnis öffnen", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Öffnet das alternative SD-Karten-Atmosphere-Verzeichnis, das die Mods der Anwendung enthält", + "ControllerSettingsRotate90": "Rotiert um 90°", + "IconSize": "Cover Größe", + "IconSizeTooltip": "Ändert die Größe der Spiel-Cover", + "MenuBarOptionsShowConsole": "Zeige Konsole", + "ShaderCachePurgeError": "Es ist ein Fehler beim löschen des Shader Caches aufgetreten bei {0}: {1}", + "UserErrorNoKeys": "Keys nicht gefunden", + "UserErrorNoFirmware": "Firmware nicht gefunden", + "UserErrorFirmwareParsingFailed": "Firmware-Analysierung-Fehler", + "UserErrorApplicationNotFound": "Anwendung nicht gefunden", + "UserErrorUnknown": "Unbekannter Fehler", + "UserErrorUndefined": "Undefinierter Fehler", + "UserErrorNoKeysDescription": "Ryujinx konnte deine 'prod.keys' Datei nicht finden", + "UserErrorNoFirmwareDescription": "Ryujinx konnte keine installierte Firmware finden!", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx konnte die zu verfügung gestellte Firmware nicht analysieren. Ein möglicher Grund dafür sind veraltete keys.", + "UserErrorApplicationNotFoundDescription": "Ryujinx konnte keine valide Anwendung an dem gegeben Pfad finden.", + "UserErrorUnknownDescription": "Ein unbekannter Fehler ist aufgetreten!", + "UserErrorUndefinedDescription": "Ein undefinierter Fehler ist aufgetreten! Dies sollte nicht passieren. Bitte kontaktiere einen Entwickler!", + "OpenSetupGuideMessage": "Öffne den 'Setup Guide'", + "NoUpdate": "Kein Update", + "TitleUpdateVersionLabel": "Version {0} - {1}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Bestätigung", + "FileDialogAllTypes": "Alle Typen", + "Never": "Niemals", + "SwkbdMinCharacters": "Muss mindestens {0} Zeichen lang sein", + "SwkbdMinRangeCharacters": "Muss {0}-{1} Zeichen lang sein", + "SoftwareKeyboard": "Software-Tastatur", + "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", + "UpdaterRenaming": "Alte Dateien umbenennen...", + "UpdaterRenameFailed": "Der Updater konnte die folgende Datei nicht umbenennen: {0}", + "UpdaterAddingFiles": "Neue Dateien hinzufügen...", + "UpdaterExtracting": "Update extrahieren...", + "UpdaterDownloading": "Update herunterladen...", + "Game": "Spiel", + "Docked": "Docked", + "Handheld": "Handheld", + "ConnectionError": "Verbindungsfehler.", + "AboutPageDeveloperListMore": "{0} und mehr...", + "ApiError": "API Fehler.", + "LoadingHeading": "{0} wird gestartet", + "CompilingPPTC": "PTC wird kompiliert", + "CompilingShaders": "Shader werden kompiliert", + "SettingsTabGraphicsBackend" : "Grafik-Backend", + "GraphicsBackendTooltip" : "Ändert das Grafik-Backend" +} diff --git a/Ryujinx.Ava/Assets/Locales/el_GR.json b/Ryujinx.Ava/Assets/Locales/el_GR.json new file mode 100644 index 000000000..82a525bef --- /dev/null +++ b/Ryujinx.Ava/Assets/Locales/el_GR.json @@ -0,0 +1,499 @@ +{ + "MenuBarFileOpenApplet": "Άνοιγμα Applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Άνοιγμα του Mii Editor Applet σε Αυτόνομη λειτουργία", + "SettingsTabInputDirectMouseAccess": "Άμεση Πρόσβαση Ποντικιού", + "SettingsTabSystemMemoryManagerMode": "Λειτουργία Διαχείρισης Μνήμης:", + "SettingsTabSystemMemoryManagerModeSoftware": "Λογισμικό", + "SettingsTabSystemMemoryManagerModeHost": "Υπολογιστής (γρήγορο)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Χωρίς Ελέγχους (γρηγορότερο, μη ασφαλές)", + "MenuBarFile": "_Αρχείο", + "MenuBarFileOpenFromFile": "_Φόρτωση Αρχείου Εφαρμογής", + "MenuBarFileOpenUnpacked": "Φόρτωση Απακετάριστου _Παιχνιδιού", + "MenuBarFileOpenEmuFolder": "Άνοιγμα Φακέλου Ryujinx", + "MenuBarFileOpenLogsFolder": "Άνοιγμα Φακέλου Καταγραφής", + "MenuBarFileExit": "_Έξοδος", + "MenuBarOptions": "Επιλογές", + "MenuBarOptionsToggleFullscreen": "Λειτουργία Πλήρους Οθόνης", + "MenuBarOptionsStartGamesInFullscreen": "Εκκίνηση Παιχνιδιών σε Πλήρη Οθόνη", + "MenuBarOptionsStopEmulation": "Διακοπή Εξομοίωσης", + "MenuBarOptionsSettings": "_Ρυθμίσεις", + "MenuBarOptionsManageUserProfiles": "Διαχείριση Προφίλ _Χρηστών", + "MenuBarActions": "_Δράσεις", + "MenuBarOptionsSimulateWakeUpMessage": "Προσομοίωση Μηνύματος Αφύπνισης", + "MenuBarActionsScanAmiibo": "Σάρωση Amiibo", + "MenuBarTools": "Εργα_λεία", + "MenuBarToolsInstallFirmware": "Εγκατάσταση Firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Εγκατάσταση Firmware από XCI ή ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Εγκατάσταση Firmware από τοποθεσία", + "MenuBarHelp": "Βοήθεια", + "MenuBarHelpCheckForUpdates": "Έλεγχος για Ενημερώσεις", + "MenuBarHelpAbout": "Σχετικά με", + "MenuSearch": "Αναζήτηση...", + "GameListHeaderFavorite": "Αγαπημένο", + "GameListHeaderIcon": "Εικονίδιο", + "GameListHeaderApplication": "Όνομα", + "GameListHeaderDeveloper": "Προγραμματιστής", + "GameListHeaderVersion": "Έκδοση", + "GameListHeaderTimePlayed": "Χρόνος", + "GameListHeaderLastPlayed": "Παίχτηκε", + "GameListHeaderFileExtension": "Κατάληξη", + "GameListHeaderFileSize": "Μέγεθος", + "GameListHeaderPath": "Τοποθεσία", + "GameListContextMenuOpenUserSaveDirectory": "Άνοιγμα Τοποθεσίας Αποθήκευσης Χρήστη", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση Χρήστη της εφαρμογής", + "GameListContextMenuOpenUserDeviceDirectory": "Άνοιγμα Τοποθεσίας Συσκευής Χρήστη", + "GameListContextMenuOpenUserDeviceDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση Συσκευής της εφαρμογής", + "GameListContextMenuOpenUserBcatDirectory": "Άνοιγμα Τοποθεσίας BCAT", + "GameListContextMenuOpenUserBcatDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση BCAT της εφαρμογής", + "GameListContextMenuManageTitleUpdates": "Διαχείριση Ενημερώσεων Παιχνιδιού", + "GameListContextMenuManageTitleUpdatesToolTip": "Ανοίγει το παράθυρο διαχείρισης Ενημερώσεων Παιχνιδιού", + "GameListContextMenuManageDlc": "Διαχείριση DLC", + "GameListContextMenuManageDlcToolTip": "Ανοίγει το παράθυρο διαχείρισης DLC", + "GameListContextMenuOpenModsDirectory": "Άνοιγμα Τοποθεσίας Τροποποιήσεων", + "GameListContextMenuOpenModsDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει τις Τροποποιήσεις της εφαρμογής", + "GameListContextMenuCacheManagement": "Διαχείριση Προσωρινής Μνήμης", + "GameListContextMenuCacheManagementPurgePptc": "Εκκαθάριση Προσωρινής Μνήμης PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Διαγράφει την προσωρινή μνήμη PPTC της εφαρμογής", + "GameListContextMenuCacheManagementPurgeShaderCache": "Εκκαθάριση Προσωρινής Μνήμης Shader", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Διαγράφει την προσωρινή μνήμη Shader της εφαρμογής", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Άνοιγμα Τοποθεσίας PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει τη προσωρινή μνήμη PPTC της εφαρμογής", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Άνοιγμα τοποθεσίας προσωρινής μνήμης Shader", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την προσωρινή μνήμη Shader της εφαρμογής", + "GameListContextMenuExtractData": "Εξαγωγή Δεδομένων", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Εξαγωγή της ενότητας ExeFS από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Εξαγωγή της ενότητας RomFS από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Εξαγωγή της ενότητας Logo από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)", + "StatusBarGamesLoaded": "{0}/{1} Φορτωμένα Παιχνίδια", + "StatusBarSystemVersion": "Έκδοση Συστήματος: {0}", + "Settings": "Ρυθμίσεις", + "SettingsTabGeneral": "Εμφάνιση", + "SettingsTabGeneralGeneral": "Γενικά", + "SettingsTabGeneralEnableDiscordRichPresence": "Ενεργοποίηση Εμπλουτισμένης Παρουσίας Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Έλεγχος για Ενημερώσεις στην Εκκίνηση", + "SettingsTabGeneralShowConfirmExitDialog": "Εμφάνιση διαλόγου \"Επιβεβαίωση Εξόδου\".", + "SettingsTabGeneralHideCursorOnIdle": "Απόκρυψη Δρομέα στην Αδράνεια", + "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": "Ενεργοποίηση Κατακόρυφου Συγχρονισμού", + "SettingsTabSystemEnablePptc": "Ενεργοποίηση PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Ενεργοποίηση Ελέγχων Ακεραιότητας FS", + "SettingsTabSystemAudioBackend": "Backend Ήχου:", + "SettingsTabSystemAudioBackendDummy": "Απενεργοποιημένο", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Μικροδιορθώσεις", + "SettingsTabSystemHacksNote": " - Μπορεί να προκαλέσουν αστάθεια", + "SettingsTabSystemExpandDramSize": "Επέκταση μεγέθους DRAM στα 6GB", + "SettingsTabSystemIgnoreMissingServices": "Αγνόηση υπηρεσιών που λείπουν", + "SettingsTabGraphics": "Γραφικά", + "SettingsTabGraphicsEnhancements": "Βελτιώσεις", + "SettingsTabGraphicsEnableShaderCache": "Ενεργοποίηση Προσωρινής Μνήμης Shader", + "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": "Τοποθεσία Shaders Γραφικών:", + "SettingsTabLogging": "Καταγραφή", + "SettingsTabLoggingLogging": "Καταγραφή", + "SettingsTabLoggingEnableLoggingToFile": "Ενεργοποίηση Καταγραφής Αρχείου", + "SettingsTabLoggingEnableStubLogs": "Ενεργοποίηση Καταγραφής Stub", + "SettingsTabLoggingEnableInfoLogs": "Ενεργοποίηση Καταγραφής Πληροφοριών", + "SettingsTabLoggingEnableWarningLogs": "Ενεργοποίηση Καταγραφής Προειδοποίησης", + "SettingsTabLoggingEnableErrorLogs": "Ενεργοποίηση Καταγραφής Σφαλμάτων", + "SettingsTabLoggingEnableTraceLogs": "Ενεργοποίηση Καταγραφής Ιχνών", + "SettingsTabLoggingEnableGuestLogs": "Ενεργοποίηση Καταγραφής Επισκεπτών", + "SettingsTabLoggingEnableFsAccessLogs": "Ενεργοποίηση Καταγραφής Πρόσβασης FS", + "SettingsTabLoggingFsGlobalAccessLogMode": "Λειτουργία Καταγραφής Καθολικής Πρόσβασης FS:", + "SettingsTabLoggingDeveloperOptions": "Επιλογές Προγραμματιστή (ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Η απόδοση Θα μειωθεί)", + "SettingsTabLoggingOpenglLogLevel": "Επίπεδο Καταγραφής OpenGL:", + "SettingsTabLoggingOpenglLogLevelNone": "Κανένα", + "SettingsTabLoggingOpenglLogLevelError": "Σφάλμα", + "SettingsTabLoggingOpenglLogLevelPerformance": "Επιβραδύνσεις", + "SettingsTabLoggingOpenglLogLevelAll": "Όλα", + "SettingsTabLoggingEnableDebugLogs": "Ενεργοποίηση Αρχείων Καταγραφής Εντοπισμού Σφαλμάτων", + "SettingsTabInput": "Χειρισμός", + "SettingsTabInputEnableDockedMode": "Ενεργοποίηση Docked Mode", + "SettingsTabInputDirectKeyboardAccess": "Άμεση Πρόσβαση στο Πληκτρολόγιο", + "SettingsButtonSave": "Αποθήκευση", + "SettingsButtonClose": "Κλείσιμο", + "SettingsButtonApply": "Εφαρμογή", + "ControllerSettingsPlayer": "Παίχτης", + "ControllerSettingsPlayer1": "Παίχτης 1", + "ControllerSettingsPlayer2": "Παίχτης 2", + "ControllerSettingsPlayer3": "Παίχτης 3", + "ControllerSettingsPlayer4": "Παίχτης 4", + "ControllerSettingsPlayer5": "Παίχτης 5", + "ControllerSettingsPlayer6": "Παίχτης 6", + "ControllerSettingsPlayer7": "Παίχτης 7", + "ControllerSettingsPlayer8": "Παίχτης 8", + "ControllerSettingsHandheld": "Χειροκίνητο", + "ControllerSettingsInputDevice": "Συσκευή Χειρισμού", + "ControllerSettingsRefresh": "Ανανέωση", + "ControllerSettingsDeviceDisabled": "Απενεργοποιημένο", + "ControllerSettingsControllerType": "Τύπος Χειριστηρίου", + "ControllerSettingsControllerTypeHandheld": "Handheld", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "Ζεύγος JoyCon", + "ControllerSettingsControllerTypeJoyConLeft": "Αριστερό JoyCon", + "ControllerSettingsControllerTypeJoyConRight": "Δεξί JoyCon", + "ControllerSettingsProfile": "Προφίλ", + "ControllerSettingsProfileDefault": "Προκαθορισμένο", + "ControllerSettingsLoad": "Φόρτωση", + "ControllerSettingsAdd": "Προσθήκη", + "ControllerSettingsRemove": "Αφαίρεση", + "ControllerSettingsButtons": "Κουμπιά", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Κατευθυντικό Pad", + "ControllerSettingsDPadUp": "Πάνω", + "ControllerSettingsDPadDown": "Κάτω", + "ControllerSettingsDPadLeft": "Αριστερά", + "ControllerSettingsDPadRight": "Δεξιά", + "ControllerSettingsLStick": "Αριστερός Μοχλός", + "ControllerSettingsLStickButton": "Κουμπί", + "ControllerSettingsLStickUp": "Πάνω", + "ControllerSettingsLStickDown": "Κάτω", + "ControllerSettingsLStickLeft": "Αριστερά", + "ControllerSettingsLStickRight": "Δεξιά", + "ControllerSettingsLStickStick": "Stick", + "ControllerSettingsLStickInvertXAxis": "Αντιστροφή Μοχλού X", + "ControllerSettingsLStickInvertYAxis": "Αντιστροφή Μοχλού Y", + "ControllerSettingsLStickDeadzone": "Νεκρή Ζώνη:", + "ControllerSettingsRStick": "Δεξιός Μοχλός", + "ControllerSettingsRStickButton": "Κουμπί", + "ControllerSettingsRStickUp": "Πάνω", + "ControllerSettingsRStickDown": "Κάτω", + "ControllerSettingsRStickLeft": "Αριστερά", + "ControllerSettingsRStickRight": "Δεξιά", + "ControllerSettingsRStickStick": "Stick", + "ControllerSettingsRStickInvertXAxis": "Αντιστροφή Μοχλού X", + "ControllerSettingsRStickInvertYAxis": "Αντιστροφή Μοχλού Y", + "ControllerSettingsRStickDeadzone": "Νεκρή Ζώνη:", + "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": "Κίνηση", + "ControllerSettingsCemuHook": "CemuHook", + "ControllerSettingsMotionEnableMotionControls": "Ενεργοποίηση Κίνησης", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Κίνηση συμβατή με CemuHook", + "ControllerSettingsMotionControllerSlot": "Υποδοχή Χειριστηρίου:", + "ControllerSettingsMotionMirrorInput": "Καθρεπτισμός Χειρισμού", + "ControllerSettingsMotionRightJoyConSlot": "Δεξιά Υποδοχή JoyCon:", + "ControllerSettingsMotionServerHost": "Κεντρικός Υπολογιστής Διακομιστή:", + "ControllerSettingsMotionGyroSensitivity": "Ευαισθησία Γυροσκοπίου:", + "ControllerSettingsMotionGyroDeadzone": "Νεκρή Ζώνη Γυροσκοπίου:", + "ControllerSettingsSave": "Αποθήκευση", + "ControllerSettingsClose": "Κλείσιμο", + "UserProfilesSelectedUserProfile": "Επιλεγμένο Προφίλ Χρήστη:", + "UserProfilesSaveProfileName": "Αποθήκευση Ονόματος Προφίλ", + "UserProfilesChangeProfileImage": "Αλλαγή Εικόνας Προφίλ", + "UserProfilesAvailableUserProfiles": "Διαθέσιμα Προφίλ Χρηστών:", + "UserProfilesAddNewProfile": "Προσθήκη Νέου Προφίλ", + "UserProfilesDeleteSelectedProfile": "Διαγραφή Επιλεγμένου Προφίλ", + "UserProfilesClose": "Κλείσιμο", + "ProfileImageSelectionTitle": "Επιλογή Εικόνας Προφίλ", + "ProfileImageSelectionHeader": "Επιλέξτε μία Εικόνα Προφίλ", + "ProfileImageSelectionNote": "Μπορείτε να εισαγάγετε μία προσαρμοσμένη εικόνα προφίλ ή να επιλέξετε ένα avatar από το Firmware", + "ProfileImageSelectionImportImage": "Εισαγωγή Αρχείου Εικόνας", + "ProfileImageSelectionSelectAvatar": "Επιλέξτε Avatar από Firmware", + "InputDialogTitle": "Διάλογος Εισαγωγής", + "InputDialogOk": "OK", + "InputDialogCancel": "Ακύρωση", + "InputDialogAddNewProfileTitle": "Επιλογή Ονόματος Προφίλ", + "InputDialogAddNewProfileHeader": "Εισαγωγή Ονόματος Προφίλ", + "InputDialogAddNewProfileSubtext": "(Σύνολο Χαρακτήρων: {0})", + "AvatarChoose": "Επιλογή", + "AvatarSetBackgroundColor": "Ορισμός Χρώματος Φόντου", + "AvatarClose": "Κλείσιμο", + "ControllerSettingsLoadProfileToolTip": "Φόρτωση Προφίλ", + "ControllerSettingsAddProfileToolTip": "Προσθήκη Προφίλ", + "ControllerSettingsRemoveProfileToolTip": "Κατάργηση Προφίλ", + "ControllerSettingsSaveProfileToolTip": "Αποθήκευση Προφίλ", + "MenuBarFileToolsTakeScreenshot": "Λήψη Στιγμιότυπου", + "MenuBarFileToolsHideUi": "Απόκρυψη UI", + "GameListContextMenuToggleFavorite": "Εναλλαγή Αγαπημένου", + "GameListContextMenuToggleFavoriteToolTip": "Εναλλαγή της Κατάστασης Αγαπημένο του Παιχνιδιού", + "SettingsTabGeneralTheme": "Θέμα", + "SettingsTabGeneralThemeCustomTheme": "Προσαρμοσμένη Τοποθεσία Θέματος", + "SettingsTabGeneralThemeBaseStyle": "Βασικό Στυλ", + "SettingsTabGeneralThemeBaseStyleDark": "Σκούρο", + "SettingsTabGeneralThemeBaseStyleLight": "Ανοιχτό", + "SettingsTabGeneralThemeEnableCustomTheme": "Ενεργοποίηση Προσαρμοσμένου Θέματος", + "ButtonBrowse": "Αναζήτηση", + "ControllerSettingsMotionConfigureCemuHookSettings": "Ρύθμιση Παραμέτρων Κίνησης CemuHook", + "ControllerSettingsRumble": "Δόνηση", + "ControllerSettingsRumbleEnable": "Ενεργοποίηση Δόνησης", + "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 Release. Αυτό μπορεί να προκληθεί εάν μία νέα έκδοση συντάσσεται από το GitHub Actions. Δοκιμάστε ξανά σε λίγα λεπτά.", + "DialogUpdaterConvertFailedGithubMessage": "Αποτυχία μετατροπής της ληφθείσας έκδοσης Ryujinx από την έκδοση GitHub.", + "DialogUpdaterDownloadingMessage": "Λήψη Ενημέρωσης...", + "DialogUpdaterExtractionMessage": "Εξαγωγή Ενημέρωσης...", + "DialogUpdaterRenamingMessage": "Μετονομασία Ενημέρωσης...", + "DialogUpdaterAddingFilesMessage": "Προσθήκη Νέας Ενημέρωσης...", + "DialogUpdaterCompleteMessage": "Η Ενημέρωση Ολοκληρώθηκε!", + "DialogUpdaterRestartMessage": "Θέλετε να επανεκκινήσετε το Ryujinx τώρα;", + "DialogUpdaterArchNotSupportedMessage": "Δεν υπάρχει υποστηριζόμενη αρχιτεκτονική συστήματος!", + "DialogUpdaterArchNotSupportedSubMessage": "(Υποστηρίζονται μόνο συστήματα x64!)", + "DialogUpdaterNoInternetMessage": "Δεν είστε συνδεδεμένοι στο Διαδίκτυο!", + "DialogUpdaterNoInternetSubMessage": "Επαληθεύστε ότι έχετε σύνδεση στο Διαδίκτυο που λειτουργεί!", + "DialogUpdaterDirtyBuildMessage": "Δεν μπορείτε να ενημερώσετε μία Πρόχειρη Έκδοση του Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Κάντε λήψη του Ryujinx στη διεύθυνση https://ryujinx.org/ εάν αναζητάτε μία υποστηριζόμενη έκδοση.", + "DialogRestartRequiredMessage": "Απαιτείται Επανεκκίνηση", + "DialogThemeRestartMessage": "Το θέμα έχει αποθηκευτεί. Απαιτείται επανεκκίνηση για την εφαρμογή του θέματος.", + "DialogThemeRestartSubMessage": "Θέλετε να κάνετε επανεκκίνηση", + "DialogFirmwareInstallEmbeddedMessage": "Θα θέλατε να εγκαταστήσετε το Firmware που είναι ενσωματωμένο σε αυτό το παιχνίδι; (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Δεν βρέθηκε εγκατεστημένο Firmware, αλλά το Ryujinx μπόρεσε να εγκαταστήσει το Firmware {0} από το παρεχόμενο παιχνίδι.\nΟ εξομοιωτής θα ξεκινήσει τώρα.", + "DialogFirmwareNoFirmwareInstalledMessage": "Δεν έχει εγκατασταθεί Firmware", + "DialogFirmwareInstalledMessage": "Το Firmware {0} εγκαταστάθηκε", + "DialogOpenSettingsWindowLabel": "Άνοιγμα Παραθύρου Ρυθμίσεων", + "DialogControllerAppletTitle": "Applet Χειρισμού", + "DialogMessageDialogErrorExceptionMessage": "Σφάλμα εμφάνισης του διαλόγου Μηνυμάτων: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Σφάλμα εμφάνισης Λογισμικού Πληκτρολογίου: {0}", + "DialogErrorAppletErrorExceptionMessage": "Σφάλμα εμφάνισης του διαλόγου ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nΓια πληροφορίες σχετικά με τον τρόπο διόρθωσης του σφάλματος, ακολουθήστε τον Οδηγό Εγκατάστασης.", + "DialogUserErrorDialogTitle": "Σφάλμα Ryujinx ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Παρουσιάστηκε σφάλμα κατά την ανάκτηση πληροφοριών από το API.", + "DialogAmiiboApiConnectErrorMessage": "Δεν είναι δυνατή η σύνδεση με τον διακομιστή Amiibo API. Η υπηρεσία μπορεί να είναι εκτός λειτουργίας ή μπορεί να χρειαστεί να επαληθεύσετε ότι έχετε ενεργή σύνδεσή στο Διαδίκτυο.", + "DialogProfileInvalidProfileErrorMessage": "Το προφίλ {0} δεν είναι συμβατό με το τρέχον σύστημα χειρισμού.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Το προεπιλεγμένο προφίλ δεν μπορεί να αντικατασταθεί", + "DialogProfileDeleteProfileTitle": "Διαγραφή Προφίλ", + "DialogProfileDeleteProfileMessage": "Αυτή η ενέργεια είναι μη αναστρέψιμη, είστε βέβαιοι ότι θέλετε να συνεχίσετε;", + "DialogWarning": "Προειδοποίηση", + "DialogPPTCDeletionMessage": "Πρόκειται να διαγράψετε την προσωρινή μνήμη PPTC για :\n\n{0}\n\nΕίστε βέβαιοι ότι θέλετε να συνεχίσετε;", + "DialogPPTCDeletionErrorMessage": "Σφάλμα κατά την εκκαθάριση προσωρινής μνήμης PPTC στο {0}: {1}", + "DialogShaderDeletionMessage": "Πρόκειται να διαγράψετε την προσωρινή μνήμη Shader για :\n\n{0}\n\nΕίστε βέβαιοι ότι θέλετε να συνεχίσετε;", + "DialogShaderDeletionErrorMessage": "Σφάλμα κατά την εκκαθάριση προσωρινής μνήμης Shader στο {0}: {1}", + "DialogRyujinxErrorMessage": "Το Ryujinx αντιμετώπισε σφάλμα", + "DialogInvalidTitleIdErrorMessage": "Σφάλμα UI: Το επιλεγμένο παιχνίδι δεν έχει έγκυρο αναγνωριστικό τίτλου", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Δεν βρέθηκε έγκυρο Firmware συστήματος στο {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Εγκατάσταση Firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Θα εγκατασταθεί η έκδοση συστήματος {0}.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nΑυτό θα αντικαταστήσει την τρέχουσα έκδοση συστήματος {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nΘέλετε να συνεχίσετε;", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Εγκατάσταση Firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Η έκδοση συστήματος {0} εγκαταστάθηκε με επιτυχία.", + "DialogUserProfileDeletionWarningMessage": "Δεν θα υπάρχουν άλλα προφίλ εάν διαγραφεί το επιλεγμένο", + "DialogUserProfileDeletionConfirmMessage": "Θέλετε να διαγράψετε το επιλεγμένο προφίλ", + "DialogControllerSettingsModifiedConfirmMessage": "Οι τρέχουσες ρυθμίσεις χειρισμού έχουν ενημερωθεί.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Θέλετε να αποθηκεύσετε;", + "DialogDlcLoadNcaErrorMessage": "{0}. Σφάλμα Αρχείου: {1}", + "DialogDlcNoDlcErrorMessage": "Το αρχείο δεν περιέχει DLC για τον επιλεγμένο τίτλο!", + "DialogPerformanceCheckLoggingEnabledMessage": "Έχετε ενεργοποιημένη την καταγραφή εντοπισμού σφαλμάτων, η οποία έχει σχεδιαστεί για χρήση μόνο από προγραμματιστές.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Για βέλτιστη απόδοση, συνιστάται η απενεργοποίηση καταγραφής εντοπισμού σφαλμάτων. Θέλετε να απενεργοποιήσετε την καταγραφή τώρα;", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Έχετε ενεργοποιήσει το Shader Dumping, το οποίο έχει σχεδιαστεί για χρήση μόνο από προγραμματιστές.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Για βέλτιστη απόδοση, συνιστάται να απενεργοποιήσετε το Shader Dumping. Θέλετε να απενεργοποιήσετε τώρα το Shader Dumping;", + "DialogLoadAppGameAlreadyLoadedMessage": "Ένα παιχνίδι έχει ήδη φορτωθεί", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Σταματήστε την εξομοίωση ή κλείστε τον εξομοιωτή πριν ξεκινήσετε ένα άλλο παιχνίδι.", + "DialogUpdateAddUpdateErrorMessage": "Το αρχείο δεν περιέχει ενημέρωση για τον επιλεγμένο τίτλο!", + "DialogSettingsBackendThreadingWarningTitle": "Προειδοποίηση - Backend Threading", + "DialogSettingsBackendThreadingWarningMessage": "Το Ryujinx πρέπει να επανεκκινηθεί αφού αλλάξει αυτή η επιλογή για να εφαρμοστεί πλήρως. Ανάλογα με την πλατφόρμα σας, μπορεί να χρειαστεί να απενεργοποιήσετε με μη αυτόματο τρόπο το multithreading του ίδιου του προγράμματος οδήγησης όταν χρησιμοποιείτε το Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Χαρακτηριστικά", + "SettingsTabGraphicsBackendMultithreading": "Πολυνηματική Επεξεργασία Γραφικών:", + "CommonAuto": "Αυτόματο", + "CommonOff": "Ανενεργό", + "CommonOn": "Ενεργό", + "InputDialogYes": "Ναί", + "InputDialogNo": "Όχι", + "DialogProfileInvalidProfileNameErrorMessage": "Το όνομα αρχείου περιέχει μη έγκυρους χαρακτήρες. Παρακαλώ προσπαθήστε ξανά.", + "MenuBarOptionsPauseEmulation": "Παύση", + "MenuBarOptionsResumeEmulation": "Συνέχιση", + "AboutUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τον ιστότοπο Ryujinx στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutDisclaimerMessage": "Το Ryujinx δεν είναι συνδεδεμένο με τη Nintendo™,\nούτε με κανέναν από τους συνεργάτες της, με οποιονδήποτε τρόπο.", + "AboutAmiiboDisclaimerMessage": "Το AmiiboAPI (www.amiiboapi.com) χρησιμοποιείται\nστην προσομοίωση Amiibo.", + "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": "Hack: Χρησιμοποιήστε τυχαίο αναγνωριστικό UUID", + "DlcManagerTableHeadingEnabledLabel": "Ενεργοποιήθηκε", + "DlcManagerTableHeadingTitleIdLabel": "Αναγνωριστικό τίτλου", + "DlcManagerTableHeadingContainerPathLabel": "Τοποθεσία DLC", + "DlcManagerTableHeadingFullPathLabel": "Πλήρης τοποθεσία", + "DlcManagerRemoveAllButton": "Αφαίρεση όλων", + "MenuBarOptionsChangeLanguage": "Αλλαξε γλώσσα", + "CommonSort": "Κατάταξη", + "CommonShowNames": "Εμφάνιση ονομάτων", + "CommonFavorite": "Αγαπημένα", + "OrderAscending": "Αύξουσα", + "OrderDescending": "Φθίνουσα", + "SettingsTabGraphicsFeatures": "Χαρακτηριστικά", + "ErrorWindowTitle": "Παράθυρο σφάλματος", + "ToggleDiscordTooltip": "Ενεργοποιεί ή απενεργοποιεί την Εμπλουτισμένη Παρουσία σας στο Discord", + "AddGameDirBoxTooltip": "Εισαγάγετε μία τοποθεσία παιχνιδιών για προσθήκη στη λίστα", + "AddGameDirTooltip": "Προσθέστε μία τοποθεσία παιχνιδιών στη λίστα", + "RemoveGameDirTooltip": "Αφαιρέστε την επιλεγμένη τοποθεσία παιχνιδιών", + "CustomThemeCheckTooltip": "Ενεργοποίηση ή απενεργοποίηση προσαρμοσμένων θεμάτων στο GUI", + "CustomThemePathTooltip": "Διαδρομή προς το προσαρμοσμένο θέμα GUI", + "CustomThemeBrowseTooltip": "Αναζητήστε ένα προσαρμοσμένο θέμα GUI", + "DockModeToggleTooltip": "Ενεργοποιήστε ή απενεργοποιήστε τη λειτουργία σύνδεσης", + "DirectKeyboardTooltip": "Ενεργοποίηση ή απενεργοποίηση της \"υποστήριξης άμεσης πρόσβασης πληκτρολογίου (HID)\" (Παρέχει πρόσβαση στα παιχνίδια στο πληκτρολόγιό σας ως συσκευή εισαγωγής κειμένου)", + "DirectMouseTooltip": "Ενεργοποίηση ή απενεργοποίηση της \"υποστήριξης άμεσης πρόσβασης ποντικιού (HID)\" (Παρέχει πρόσβαση στα παιχνίδια στο ποντίκι σας ως συσκευή κατάδειξης)", + "RegionTooltip": "Αλλαγή Περιοχής Συστήματος", + "LanguageTooltip": "Αλλαγή Γλώσσας Συστήματος", + "TimezoneTooltip": "Αλλαγή Ζώνης Ώρας Συστήματος", + "TimeTooltip": "Αλλαγή Ώρας Συστήματος", + "VSyncToggleTooltip": "Ενεργοποιεί ή απενεργοποιεί τον κατακόρυφο συγχρονισμό", + "PptcToggleTooltip": "Ενεργοποιεί ή απενεργοποιεί το PPTC", + "FsIntegrityToggleTooltip": "Ενεργοποιεί τους ελέγχους ακεραιότητας σε αρχεία περιεχομένου παιχνιδιού", + "AudioBackendTooltip": "Αλλαγή ήχου υποστήριξης", + "MemoryManagerTooltip": "Αλλάξτε τον τρόπο αντιστοίχισης και πρόσβασης στη μνήμη επισκέπτη. Επηρεάζει σε μεγάλο βαθμό την απόδοση της προσομοίωσης της CPU.", + "MemoryManagerSoftwareTooltip": "Χρησιμοποιήστε έναν πίνακα σελίδων λογισμικού για τη μετάφραση διευθύνσεων. Υψηλότερη ακρίβεια αλλά πιο αργή απόδοση.", + "MemoryManagerHostTooltip": "Απευθείας αντιστοίχιση της μνήμης στον χώρο διευθύνσεων υπολογιστή υποδοχής. Πολύ πιο γρήγορη μεταγλώττιση και εκτέλεση JIT.", + "MemoryManagerUnsafeTooltip": "Απευθείας χαρτογράφηση της μνήμης, αλλά μην καλύπτετε τη διεύθυνση εντός του χώρου διευθύνσεων επισκέπτη πριν από την πρόσβαση. Πιο γρήγορα, αλλά με κόστος ασφάλειας. Η εφαρμογή μπορεί να έχει πρόσβαση στη μνήμη από οπουδήποτε στο Ryujinx, επομένως εκτελείτε μόνο προγράμματα που εμπιστεύεστε με αυτήν τη λειτουργία.", + "DRamTooltip": "Επεκτείνει την ποσότητα της μνήμης στο εξομοιούμενο σύστημα από 4 GB σε 6 GB", + "IgnoreMissingServicesTooltip": "Ενεργοποίηση ή απενεργοποίηση της αγνοώησης για υπηρεσίες που λείπουν", + "GraphicsBackendThreadingTooltip": "Ενεργοποίηση Πολυνηματικής Επεξεργασίας Γραφικών", + "GalThreadingTooltip": "Εκτελεί εντολές γραφικών σε ένα δεύτερο νήμα. Επιτρέπει την πολυνηματική μεταγλώττιση Shader σε χρόνο εκτέλεσης, μειώνει το τρεμόπαιγμα και βελτιώνει την απόδοση των προγραμμάτων οδήγησης χωρίς τη δική τους υποστήριξη πολλαπλών νημάτων. Ποικίλες κορυφαίες επιδόσεις σε προγράμματα οδήγησης με multithreading. Μπορεί να χρειαστεί επανεκκίνηση του Ryujinx για να απενεργοποιήσετε σωστά την ενσωματωμένη λειτουργία πολλαπλών νημάτων του προγράμματος οδήγησης ή ίσως χρειαστεί να το κάνετε χειροκίνητα για να έχετε την καλύτερη απόδοση.", + "ShaderCacheToggleTooltip": "Ενεργοποιεί ή απενεργοποιεί την Προσωρινή Μνήμη Shader", + "ResolutionScaleTooltip": "Κλίμακα ανάλυσης που εφαρμόστηκε σε ισχύοντες στόχους απόδοσης", + "ResolutionScaleEntryTooltip": "Κλίμακα ανάλυσης κινητής υποδιαστολής, όπως 1,5. Οι μη αναπόσπαστες τιμές είναι πιθανό να προκαλέσουν προβλήματα ή σφάλματα.", + "AnisotropyTooltip": "Επίπεδο Ανισότροπου Φιλτραρίσματος (ρυθμίστε το στο Αυτόματο για να χρησιμοποιηθεί η τιμή που ζητήθηκε από το παιχνίδι)", + "AspectRatioTooltip": "Λόγος διαστάσεων που εφαρμόστηκε στο παράθυρο απόδοσης.", + "ShaderDumpPathTooltip": "Τοποθεσία Εναπόθεσης Προσωρινής Μνήμης Shaders", + "FileLogTooltip": "Ενεργοποιεί ή απενεργοποιεί την καταγραφή σε ένα αρχείο στο δίσκο", + "StubLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων καταγραφής ατελειών", + "InfoLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής πληροφοριών", + "WarnLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων καταγραφής προειδοποιήσεων", + "ErrorLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής σφαλμάτων", + "TraceLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής ιχνών", + "GuestLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων καταγραφής επισκεπτών", + "FileAccessLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής πρόσβασης", + "FSAccessLogModeTooltip": "Ενεργοποιεί την έξοδο καταγραφής πρόσβασης FS στην κονσόλα. Οι πιθανοί τρόποι λειτουργίας είναι 0-3", + "DeveloperOptionTooltip": "Χρησιμοποιήστε με προσοχή", + "OpenGlLogLevel": "Απαιτεί τα κατάλληλα επίπεδα καταγραφής ενεργοποιημένα", + "DebugLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής εντοπισμού σφαλμάτων", + "LoadApplicationFileTooltip": "Ανοίξτε έναν επιλογέα αρχείων για να επιλέξετε ένα αρχείο συμβατό με το Switch για φόρτωση", + "LoadApplicationFolderTooltip": "Ανοίξτε έναν επιλογέα αρχείων για να επιλέξετε μία μη συσκευασμένη εφαρμογή, συμβατή με το Switch για φόρτωση", + "OpenRyujinxFolderTooltip": "Ανοίξτε το φάκελο συστήματος αρχείων Ryujinx", + "OpenRyujinxLogsTooltip": "Ανοίξτε το φάκελο στον οποίο διατηρούνται τα αρχεία καταγραφής", + "ExitTooltip": "Έξοδος από το Ryujinx", + "OpenSettingsTooltip": "Ανοίξτε το παράθυρο Ρυθμίσεων", + "OpenProfileManagerTooltip": "Ανοίξτε το παράθυρο Διαχείρισης Προφίλ Χρήστη", + "StopEmulationTooltip": "Σταματήστε την εξομοίωση του τρέχοντος παιχνιδιού και επιστρέψτε στην επιλογή παιχνιδιού", + "CheckUpdatesTooltip": "Ελέγξτε για ενημερώσεις του Ryujinx", + "OpenAboutTooltip": "Ανοίξτε το Παράθυρο Σχετικά", + "GridSize": "Μέγεθος Πλέγματος", + "GridSizeTooltip": "Αλλαγή μεγέθους στοιχείων πλέγματος", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Πορτογαλικά Βραζιλίας", + "AboutRyujinxContributorsButtonHeader": "Δείτε Όλους τους Συντελεστές", + "SettingsTabSystemAudioVolume": "Ενταση Ήχου: ", + "AudioVolumeTooltip": "Αλλαγή Έντασης Ήχου", + "SettingsTabSystemEnableInternetAccess": "Ενεργοποίηση πρόσβασης επισκέπτη στο Διαδίκτυο", + "EnableInternetAccessTooltip": "Επιτρέπει την πρόσβαση επισκέπτη στο Διαδίκτυο. Εάν ενεργοποιηθεί, η εξομοιωμένη κονσόλα Switch θα συμπεριφέρεται σαν να είναι συνδεδεμένη στο Διαδίκτυο. Λάβετε υπόψη ότι σε ορισμένες περιπτώσεις, οι εφαρμογές ενδέχεται να εξακολουθούν να έχουν πρόσβαση στο Διαδίκτυο, ακόμη και όταν αυτή η επιλογή είναι απενεργοποιημένη", + "GameListContextMenuManageCheatToolTip" : "Διαχείριση Κόλπων", + "GameListContextMenuManageCheat" : "Διαχείριση Κόλπων", + "ControllerSettingsStickRange" : "Εύρος", + "DialogStopEmulationTitle" : "Ryujinx - Διακοπή εξομοίωσης", + "DialogStopEmulationMessage": "Είστε βέβαιοι ότι θέλετε να σταματήσετε την εξομοίωση;", + "SettingsTabCpu": "Επεξεργαστής", + "SettingsTabAudio": "Ήχος", + "SettingsTabNetwork": "Δίκτυο", + "SettingsTabNetworkConnection" : "Σύνδεση δικτύου", + "SettingsTabGraphicsFrameRate" : "Ρυθμός Ανανέωσης Υπολογιστή:", + "SettingsTabGraphicsFrameRateTooltip" : "Προκαθορίζει το ρυθμό ανανέωσης του υπολογιστή. Ορίστε το στο 0 για να αφαιρέσετε το όριο.", + "SettingsTabCpuCache" : "Προσωρινή Μνήμη CPU", + "SettingsTabCpuMemory" : "Μνήμη CPU" +} diff --git a/Ryujinx.Ava/Assets/Locales/en_US.json b/Ryujinx.Ava/Assets/Locales/en_US.json new file mode 100644 index 000000000..ff8a2674b --- /dev/null +++ b/Ryujinx.Ava/Assets/Locales/en_US.json @@ -0,0 +1,552 @@ +{ + "MenuBarFileOpenApplet": "Open Applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Open Mii Editor Applet in Standalone mode", + "SettingsTabInputDirectMouseAccess": "Direct Mouse Access", + "SettingsTabSystemMemoryManagerMode": "Memory Manager Mode:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Host (fast)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Unchecked (fastest, unsafe)", + "MenuBarFile": "_File", + "MenuBarFileOpenFromFile": "_Load Application From File", + "MenuBarFileOpenUnpacked": "Load _Unpacked Game", + "MenuBarFileOpenEmuFolder": "Open Ryujinx Folder", + "MenuBarFileOpenLogsFolder": "Open Logs Folder", + "MenuBarFileExit": "_Exit", + "MenuBarOptions": "Options", + "MenuBarOptionsToggleFullscreen": "Toggle Fullscreen", + "MenuBarOptionsStartGamesInFullscreen": "Start Games in Fullscreen Mode", + "MenuBarOptionsStopEmulation": "Stop Emulation", + "MenuBarOptionsSettings": "_Settings", + "MenuBarOptionsManageUserProfiles": "_Manage User Profiles", + "MenuBarActions": "_Actions", + "MenuBarOptionsSimulateWakeUpMessage": "Simulate Wake-up message", + "MenuBarActionsScanAmiibo": "Scan An Amiibo", + "MenuBarTools": "_Tools", + "MenuBarToolsInstallFirmware": "Install Firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Install a firmware from XCI or ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Install a firmware from a directory", + "MenuBarHelp": "Help", + "MenuBarHelpCheckForUpdates": "Check for Updates", + "MenuBarHelpAbout": "About", + "MenuSearch": "Search...", + "GameListHeaderFavorite": "Fav", + "GameListHeaderIcon": "Icon", + "GameListHeaderApplication": "Name", + "GameListHeaderDeveloper": "Developer", + "GameListHeaderVersion": "Version", + "GameListHeaderTimePlayed": "Play Time", + "GameListHeaderLastPlayed": "Last Played", + "GameListHeaderFileExtension": "File Ext", + "GameListHeaderFileSize": "File Size", + "GameListHeaderPath": "Path", + "GameListContextMenuOpenUserSaveDirectory": "Open User Save Directory", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Opens the directory which contains Application's User Save", + "GameListContextMenuOpenUserDeviceDirectory": "Open User Device Directory", + "GameListContextMenuOpenUserDeviceDirectoryToolTip": "Opens the directory which contains Application's Device Save", + "GameListContextMenuOpenUserBcatDirectory": "Open User BCAT Directory", + "GameListContextMenuOpenUserBcatDirectoryToolTip": "Opens the directory which contains Application's BCAT Save", + "GameListContextMenuManageTitleUpdates": "Manage Title Updates", + "GameListContextMenuManageTitleUpdatesToolTip": "Opens the Title Update management window", + "GameListContextMenuManageDlc": "Manage DLC", + "GameListContextMenuManageDlcToolTip": "Opens the DLC management window", + "GameListContextMenuOpenModsDirectory": "Open Mods Directory", + "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", + "GameListContextMenuCacheManagement": "Cache Management", + "GameListContextMenuCacheManagementPurgePptc": "Purge PPTC Cache", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Deletes Application's PPTC cache", + "GameListContextMenuCacheManagementPurgeShaderCache": "Purge Shader Cache", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Deletes Application's shader cache", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Open PPTC Directory", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Opens the directory which contains Application's PPTC cache", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Open Shader Cache Directory", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Opens the directory which contains Application's shader cache", + "GameListContextMenuExtractData": "Extract Data", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extract the ExeFS section from Application's current config (including updates)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extract the RomFS section from Application's current config (including updates)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Extract the Logo section from Application's current config (including updates)", + "StatusBarGamesLoaded": "{0}/{1} Games Loaded", + "StatusBarSystemVersion": "System Version: {0}", + "Settings": "Settings", + "SettingsTabGeneral": "User Interface", + "SettingsTabGeneralGeneral": "General", + "SettingsTabGeneralEnableDiscordRichPresence": "Enable Discord Rich Presence", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Check for Updates on Launch", + "SettingsTabGeneralShowConfirmExitDialog": "Show \"Confirm Exit\" Dialog", + "SettingsTabGeneralHideCursorOnIdle": "Hide Cursor on Idle", + "SettingsTabGeneralGameDirectories": "Game Directories", + "SettingsTabGeneralAdd": "Add", + "SettingsTabGeneralRemove": "Remove", + "SettingsTabSystem": "System", + "SettingsTabSystemCore": "Core", + "SettingsTabSystemSystemRegion": "System Region:", + "SettingsTabSystemSystemRegionJapan": "Japan", + "SettingsTabSystemSystemRegionUSA": "USA", + "SettingsTabSystemSystemRegionEurope": "Europe", + "SettingsTabSystemSystemRegionAustralia": "Australia", + "SettingsTabSystemSystemRegionChina": "China", + "SettingsTabSystemSystemRegionKorea": "Korea", + "SettingsTabSystemSystemRegionTaiwan": "Taiwan", + "SettingsTabSystemSystemLanguage": "System Language:", + "SettingsTabSystemSystemLanguageJapanese": "Japanese", + "SettingsTabSystemSystemLanguageAmericanEnglish": "American English", + "SettingsTabSystemSystemLanguageFrench": "French", + "SettingsTabSystemSystemLanguageGerman": "German", + "SettingsTabSystemSystemLanguageItalian": "Italian", + "SettingsTabSystemSystemLanguageSpanish": "Spanish", + "SettingsTabSystemSystemLanguageChinese": "Chinese", + "SettingsTabSystemSystemLanguageKorean": "Korean", + "SettingsTabSystemSystemLanguageDutch": "Dutch", + "SettingsTabSystemSystemLanguagePortuguese": "Portuguese", + "SettingsTabSystemSystemLanguageRussian": "Russian", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanese", + "SettingsTabSystemSystemLanguageBritishEnglish": "British English", + "SettingsTabSystemSystemLanguageCanadianFrench": "Canadian French", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Latin American Spanish", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Simplified Chinese", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Traditional Chinese", + "SettingsTabSystemSystemTimeZone": "System TimeZone:", + "SettingsTabSystemSystemTime": "System Time:", + "SettingsTabSystemEnableVsync": "Enable VSync", + "SettingsTabSystemEnablePptc": "Enable PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Enable FS Integrity Checks", + "SettingsTabSystemAudioBackend": "Audio Backend:", + "SettingsTabSystemAudioBackendDummy": "Dummy", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": " - These many cause instabilities", + "SettingsTabSystemExpandDramSize": "Expand DRAM size to 6GB", + "SettingsTabSystemIgnoreMissingServices": "Ignore Missing Services", + "SettingsTabGraphics": "Graphics", + "SettingsTabGraphicsEnhancements": "Enhancements", + "SettingsTabGraphicsEnableShaderCache": "Enable Shader Cache", + "SettingsTabGraphicsAnisotropicFiltering": "Anisotropic Filtering:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Resolution Scale:", + "SettingsTabGraphicsResolutionScaleCustom": "Custom (Not recommended)", + "SettingsTabGraphicsResolutionScaleNative": "Native (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Aspect Ratio:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Stretch to Fit Window", + "SettingsTabGraphicsDeveloperOptions": "Developer Options", + "SettingsTabGraphicsShaderDumpPath": "Graphics Shader Dump Path:", + "SettingsTabLogging": "Logging", + "SettingsTabLoggingLogging": "Logging", + "SettingsTabLoggingEnableLoggingToFile": "Enable Logging to File", + "SettingsTabLoggingEnableStubLogs": "Enable Stub Logs", + "SettingsTabLoggingEnableInfoLogs": "Enable Info Logs", + "SettingsTabLoggingEnableWarningLogs": "Enable Warning Logs", + "SettingsTabLoggingEnableErrorLogs": "Enable Error Logs", + "SettingsTabLoggingEnableTraceLogs": "Enable Trace Logs", + "SettingsTabLoggingEnableGuestLogs": "Enable Guest Logs", + "SettingsTabLoggingEnableFsAccessLogs": "Enable Fs Access Logs", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs Global Access Log Mode:", + "SettingsTabLoggingDeveloperOptions": "Developer Options (WARNING: Will reduce performance)", + "SettingsTabLoggingOpenglLogLevel": "OpenGL Log Level:", + "SettingsTabLoggingOpenglLogLevelNone": "None", + "SettingsTabLoggingOpenglLogLevelError": "Error", + "SettingsTabLoggingOpenglLogLevelPerformance": "Slowdowns", + "SettingsTabLoggingOpenglLogLevelAll": "All", + "SettingsTabLoggingEnableDebugLogs": "Enable Debug Logs", + "SettingsTabInput": "Input", + "SettingsTabInputEnableDockedMode": "Enable Docked Mode", + "SettingsTabInputDirectKeyboardAccess": "Direct Keyboard Access", + "SettingsButtonSave": "Save", + "SettingsButtonClose": "Close", + "SettingsButtonApply": "Apply", + "ControllerSettingsPlayer": "Player", + "ControllerSettingsPlayer1": "Player 1", + "ControllerSettingsPlayer2": "Player 2", + "ControllerSettingsPlayer3": "Player 3", + "ControllerSettingsPlayer4": "Player 4", + "ControllerSettingsPlayer5": "Player 5", + "ControllerSettingsPlayer6": "Player 6", + "ControllerSettingsPlayer7": "Player 7", + "ControllerSettingsPlayer8": "Player 8", + "ControllerSettingsHandheld": "Handheld", + "ControllerSettingsInputDevice": "Input Device", + "ControllerSettingsRefresh": "Refresh", + "ControllerSettingsDeviceDisabled": "Disabled", + "ControllerSettingsControllerType": "Controller Type", + "ControllerSettingsControllerTypeHandheld": "Handheld", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon Pair", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Left", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon Right", + "ControllerSettingsProfile": "Profile", + "ControllerSettingsProfileDefault": "Default", + "ControllerSettingsLoad": "Load", + "ControllerSettingsAdd": "Add", + "ControllerSettingsRemove": "Remove", + "ControllerSettingsButtons": "Buttons", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Directional Pad", + "ControllerSettingsDPadUp": "Up", + "ControllerSettingsDPadDown": "Down", + "ControllerSettingsDPadLeft": "Left", + "ControllerSettingsDPadRight": "Right", + "ControllerSettingsLStick": "Left Stick", + "ControllerSettingsLStickButton": "Button", + "ControllerSettingsLStickUp": "Up", + "ControllerSettingsLStickDown": "Down", + "ControllerSettingsLStickLeft": "Left", + "ControllerSettingsLStickRight": "Right", + "ControllerSettingsLStickStick": "Stick", + "ControllerSettingsLStickInvertXAxis": "Invert Stick X", + "ControllerSettingsLStickInvertYAxis": "Invert Stick Y", + "ControllerSettingsLStickDeadzone": "Deadzone:", + "ControllerSettingsRStick": "Right Stick", + "ControllerSettingsRStickButton": "Button", + "ControllerSettingsRStickUp": "Up", + "ControllerSettingsRStickDown": "Down", + "ControllerSettingsRStickLeft": "Left", + "ControllerSettingsRStickRight": "Right", + "ControllerSettingsRStickStick": "Stick", + "ControllerSettingsRStickInvertXAxis": "Invert Stick X", + "ControllerSettingsRStickInvertYAxis": "Invert Stick Y", + "ControllerSettingsRStickDeadzone": "Deadzone:", + "ControllerSettingsTriggersLeft": "Triggers Left", + "ControllerSettingsTriggersRight": "Triggers Right", + "ControllerSettingsTriggersButtonsLeft": "Trigger Buttons Left", + "ControllerSettingsTriggersButtonsRight": "Trigger Buttons Right", + "ControllerSettingsTriggers": "Triggers", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Buttons Left", + "ControllerSettingsExtraButtonsRight": "Buttons Right", + "ControllerSettingsMisc": "Miscellaneous", + "ControllerSettingsTriggerThreshold": "Trigger Threshold:", + "ControllerSettingsMotion": "Motion", + "ControllerSettingsCemuHook": "CemuHook", + "ControllerSettingsMotionEnableMotionControls": "Enable Motion Controls", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Use CemuHook compatible motion", + "ControllerSettingsMotionControllerSlot": "Controller Slot:", + "ControllerSettingsMotionMirrorInput": "Mirror Input", + "ControllerSettingsMotionRightJoyConSlot": "Right JoyCon Slot:", + "ControllerSettingsMotionServerHost": "Server Host:", + "ControllerSettingsMotionGyroSensitivity": "Gyro Sensitivity:", + "ControllerSettingsMotionGyroDeadzone": "Gyro Deadzone:", + "ControllerSettingsSave": "Save", + "ControllerSettingsClose": "Close", + "UserProfilesSelectedUserProfile": "Selected User Profile:", + "UserProfilesSaveProfileName": "Save Profile Name", + "UserProfilesChangeProfileImage": "Change Profile Image", + "UserProfilesAvailableUserProfiles": "Available User Profiles:", + "UserProfilesAddNewProfile": "Add New Profile", + "UserProfilesDeleteSelectedProfile": "Delete Selected Profile", + "UserProfilesClose": "Close", + "ProfileImageSelectionTitle": "Profile Image Selection", + "ProfileImageSelectionHeader": "Choose a profile Image", + "ProfileImageSelectionNote": "You may import a custom profile image, or select an avatar from system firmware", + "ProfileImageSelectionImportImage": "Import Image File", + "ProfileImageSelectionSelectAvatar": "Select Firmware Avatar", + "InputDialogTitle": "Input Dialog", + "InputDialogOk": "OK", + "InputDialogCancel": "Cancel", + "InputDialogAddNewProfileTitle": "Choose the Profile Name", + "InputDialogAddNewProfileHeader": "Please Enter a Profile Name", + "InputDialogAddNewProfileSubtext": "(Max Length: {0})", + "AvatarChoose": "Choose", + "AvatarSetBackgroundColor": "Set Background Color", + "AvatarClose": "Close", + "ControllerSettingsLoadProfileToolTip": "Load Profile", + "ControllerSettingsAddProfileToolTip": "Add Profile", + "ControllerSettingsRemoveProfileToolTip": "Remove Profile", + "ControllerSettingsSaveProfileToolTip": "Save Profile", + "MenuBarFileToolsTakeScreenshot": "Take Screenshot", + "MenuBarFileToolsHideUi": "Hide Ui", + "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", + "ControllerSettingsMotionConfigureCemuHookSettings": "Configure CemuHook Motion", + "ControllerSettingsRumble": "Rumble", + "ControllerSettingsRumbleEnable": "Enable Rumble", + "ControllerSettingsRumbleStrongMultiplier": "Strong Rumble Multiplier", + "ControllerSettingsRumbleWeakMultiplier": "Weak Rumble Multiplier", + "DialogMessageSaveNotAvailableMessage": "There is no savedata for {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Would you like to create savedata for this game?", + "DialogConfirmationTitle": "Ryujinx - Confirmation", + "DialogUpdaterTitle": "Ryujinx - Updater", + "DialogErrorTitle": "Ryujinx - Error", + "DialogWarningTitle": "Ryujinx - Warning", + "DialogExitTitle": "Ryujinx - Exit", + "DialogErrorMessage": "Ryujinx has encountered an error", + "DialogExitMessage": "Are you sure you want to close Ryujinx?", + "DialogExitSubMessage": "All unsaved data will be lost!", + "DialogMessageCreateSaveErrorMessage": "There was an error creating the specified savedata: {0}", + "DialogMessageFindSaveErrorMessage": "There was an error finding the specified savedata: {0}", + "FolderDialogExtractTitle": "Choose the folder to extract into", + "DialogNcaExtractionMessage": "Extracting {0} section from {1}...", + "DialogNcaExtractionTitle": "Ryujinx - NCA Section Extractor", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Extraction failure. The main NCA was not present in the selected file.", + "DialogNcaExtractionCheckLogErrorMessage": "Extraction failure. Read the log file for further information.", + "DialogNcaExtractionSuccessMessage": "Extraction completed successfully.", + "DialogUpdaterConvertFailedMessage": "Failed to convert the current Ryujinx version.", + "DialogUpdaterCancelUpdateMessage": "Cancelling Update!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "You are already using the most updated version of Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "An error has 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.", + "DialogUpdaterConvertFailedGithubMessage": "Failed to convert the received Ryujinx version from Github Release.", + "DialogUpdaterDownloadingMessage": "Downloading Update...", + "DialogUpdaterExtractionMessage": "Extracting Update...", + "DialogUpdaterRenamingMessage": "Renaming Update...", + "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!", + "DialogUpdaterDirtyBuildSubMessage": "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version.", + "DialogRestartRequiredMessage": "Restart Required", + "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.", + "DialogFirmwareNoFirmwareInstalledMessage": "No Firmware Installed", + "DialogFirmwareInstalledMessage": "Firmware {0} was installed", + "DialogOpenSettingsWindowLabel": "Open Settings Window", + "DialogControllerAppletTitle": "Controller Applet", + "DialogMessageDialogErrorExceptionMessage": "Error displaying Message Dialog: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Error displaying Software Keyboard: {0}", + "DialogErrorAppletErrorExceptionMessage": "Error displaying ErrorApplet Dialog: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nFor more information on how to fix this error, follow our Setup Guide.", + "DialogUserErrorDialogTitle": "Ryujinx Error ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "An error occured while fetching information from the API.", + "DialogAmiiboApiConnectErrorMessage": "Unable to connect to Amiibo API server. The service may be down or you may need to verify your internet connection is online.", + "DialogProfileInvalidProfileErrorMessage": "Profile {0} is incompatible with the current input configuration system.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Default Profile can not be overwritten", + "DialogProfileDeleteProfileTitle": "Deleting Profile", + "DialogProfileDeleteProfileMessage": "This action is irreversible, are you sure you want to continue?", + "DialogWarning": "Warning", + "DialogPPTCDeletionMessage": "You are about to delete the PPTC cache for :\n\n{0}\n\nAre you sure you want to proceed?", + "DialogPPTCDeletionErrorMessage": "Error purging PPTC cache at {0}: {1}", + "DialogShaderDeletionMessage": "You are about to delete the Shader cache for :\n\n{0}\n\nAre you sure you want to proceed?", + "DialogShaderDeletionErrorMessage": "Error purging Shader cache at {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx has encountered an error", + "DialogInvalidTitleIdErrorMessage": "UI error: The selected game did not have a valid title ID", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "A valid system firmware was not found in {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Install Firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "System version {0} will be installed.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nThis will replace the current system version {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nDo you want to continue?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Installing firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "System version {0} successfully installed.", + "DialogUserProfileDeletionWarningMessage": "There would be no other profiles to be opened if selected profile is deleted", + "DialogUserProfileDeletionConfirmMessage": "Do you want to delete the selected profile", + "DialogControllerSettingsModifiedConfirmMessage": "The current controller settings has been updated.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Do you want to save?", + "DialogDlcLoadNcaErrorMessage": "{0}. Errored File: {1}", + "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?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "You have shader dumping enabled, which is designed to be used by developers only.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "For optimal performance, it's recommended to disable shader dumping. Would you like to disable shader dumping now?", + "DialogLoadAppGameAlreadyLoadedMessage": "A game has already been loaded", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Please stop emulation or close the emulator before launching another game.", + "DialogUpdateAddUpdateErrorMessage": "The specified file does not contain an update for the selected title!", + "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.", + "SettingsTabGraphicsFeaturesOptions": "Features", + "SettingsTabGraphicsBackendMultithreading": "Graphics Backend Multithreading:", + "CommonAuto": "Auto", + "CommonOff": "Off", + "CommonOn": "On", + "InputDialogYes": "Yes", + "InputDialogNo": "No", + "DialogProfileInvalidProfileNameErrorMessage": "The file name contains invalid characters. Please try again.", + "MenuBarOptionsPauseEmulation": "Pause", + "MenuBarOptionsResumeEmulation": "Resume", + "AboutUrlTooltipMessage": "Click to open the Ryujinx website in your default browser.", + "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": "About:", + "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": "Maintained By:", + "AboutRyujinxMaintainersContentTooltipMessage": "Click to open the Contributors page in your default browser.", + "AboutRyujinxSupprtersTitle": "Supported on Patreon By:", + "AmiiboSeriesLabel": "Amiibo Series", + "AmiiboCharacterLabel": "Character", + "AmiiboScanButtonLabel": "Scan It", + "AmiiboOptionsShowAllLabel": "Show All Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack: Use Random tag Uuid", + "DlcManagerTableHeadingEnabledLabel": "Enabled", + "DlcManagerTableHeadingTitleIdLabel": "Title ID", + "DlcManagerTableHeadingContainerPathLabel": "Container Path", + "DlcManagerTableHeadingFullPathLabel": "Full Path", + "DlcManagerRemoveAllButton": "Remove All", + "MenuBarOptionsChangeLanguage": "Change Language", + "CommonSort": "Sort", + "CommonShowNames": "Show Names", + "CommonFavorite": "Favorite", + "OrderAscending": "Ascending", + "OrderDescending": "Descending", + "SettingsTabGraphicsFeatures": "Features", + "ErrorWindowTitle": "Error Window", + "ToggleDiscordTooltip": "Enables or disables Discord Rich Presence", + "AddGameDirBoxTooltip": "Enter a game directory to add to the list", + "AddGameDirTooltip": "Add a game directory to the list", + "RemoveGameDirTooltip": "Remove selected game directory", + "CustomThemeCheckTooltip": "Enable or disable custom themes in the GUI", + "CustomThemePathTooltip": "Path to custom GUI theme", + "CustomThemeBrowseTooltip": "Browse for a custom GUI theme", + "DockModeToggleTooltip": "Enable or disable Docked Mode", + "DirectKeyboardTooltip": "Enable or disable \"direct keyboard access (HID) support\" (Provides games access to your keyboard as a text entry device)", + "DirectMouseTooltip": "Enable or disable \"direct mouse access (HID) support\" (Provides games access to your mouse as a pointing device)", + "RegionTooltip": "Change System Region", + "LanguageTooltip": "Change System Language", + "TimezoneTooltip": "Change System TimeZone", + "TimeTooltip": "Change System Time", + "VSyncToggleTooltip": "Enables or disables Vertical Sync", + "PptcToggleTooltip": "Enables or disables PPTC", + "FsIntegrityToggleTooltip": "Enables integrity checks on Game content files", + "AudioBackendTooltip": "Change Audio Backend", + "MemoryManagerTooltip": "Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance.", + "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.", + "DRamTooltip": "Expands the amount of memory on the emulated system from 4GB to 6GB", + "IgnoreMissingServicesTooltip": "Enable or disable ignoring missing services option", + "GraphicsBackendThreadingTooltip": "Enable Graphics Backend Multithreading", + "GalThreadingTooltip": "Executes graphics backend commands on a second thread. Allows runtime multithreading of shader compilation, reduces stuttering, and improves performance on drivers without multithreading support of their own. Slightly varying peak performance on drivers with multithreading. Ryujinx may need to be restarted to correctly disable driver built-in multithreading, or you may need to do it manually to get the best performance.", + "ShaderCacheToggleTooltip": "Enables or disables Shader Cache", + "ResolutionScaleTooltip": "Resolution Scale applied to applicable render targets", + "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.", + "ShaderDumpPathTooltip": "Graphics Shaders Dump Path", + "FileLogTooltip": "Enables or disables logging to a file on disk", + "StubLogTooltip": "Enables printing stub log messages", + "InfoLogTooltip": "Enables printing info log messages", + "WarnLogTooltip": "Enables printing warning log messages", + "ErrorLogTooltip": "Enables printing error log messages", + "TraceLogTooltip": "Enables printing trace log messages", + "GuestLogTooltip": "Enables printing guest log messages", + "FileAccessLogTooltip": "Enables printing file access log messages", + "FSAccessLogModeTooltip": "Enables FS access log output to the console. Possible modes are 0-3", + "DeveloperOptionTooltip": "Use with care", + "OpenGlLogLevel": "Requires appropriate log levels enabled", + "DebugLogTooltip": "Enables printing debug log messages", + "LoadApplicationFileTooltip": "Open a file chooser to choose a Switch compatible file to load", + "LoadApplicationFolderTooltip": "Open a file chooser to choose a Switch compatible, unpacked application to load", + "OpenRyujinxFolderTooltip": "Open Ryujinx filesystem folder", + "OpenRyujinxLogsTooltip": "Opens the folder where logs are written to", + "ExitTooltip": "Exit Ryujinx", + "OpenSettingsTooltip": "Open settings window", + "OpenProfileManagerTooltip": "Open User Profiles Manager window", + "StopEmulationTooltip": "Stop emulation of the current game and return to game selection", + "CheckUpdatesTooltip": "Check for updates to Ryujinx", + "OpenAboutTooltip": "Open About Window", + "GridSize": "Grid Size", + "GridSizeTooltip": "Change the size of grid items", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brazilian Portuguese", + "AboutRyujinxContributorsButtonHeader": "See All Contributors", + "SettingsTabSystemAudioVolume": "Volume: ", + "AudioVolumeTooltip": "Change Audio Volume", + "SettingsTabSystemEnableInternetAccess": "Enable Guest Internet Access", + "EnableInternetAccessTooltip": "Enables guest Internet access. If enabled, the application will behave as if the emulated Switch console was connected to the Internet. Note that in some cases, applications may still access the Internet even with this option disabled", + "GameListContextMenuManageCheatToolTip": "Manage Cheats", + "GameListContextMenuManageCheat": "Manage Cheats", + "ControllerSettingsStickRange": "Range", + "DialogStopEmulationTitle": "Ryujinx - Stop Emulation", + "DialogStopEmulationMessage": "Are you sure you want to stop emulation?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Audio", + "SettingsTabNetwork": "Network", + "SettingsTabNetworkConnection": "Network Connection", + "[REMOVE]SettingsTabGraphicsFrameRate": "Host Refresh Rate:", + "[REMOVE]SettingsTabGraphicsFrameRateTooltip": "Sets host refresh rate. Set to 0 to remove limit.", + "SettingsTabCpuCache": "CPU Cache", + "SettingsTabCpuMemory": "CPU Memory", + "DialogUpdaterFlatpakNotSupportedMessage": "Please update Ryujinx via FlatHub.", + "UpdaterDisabledWarningTitle": "Updater Disabled!", + "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods", + "ControllerSettingsRotate90": "Rotate 90° Clockwise", + "IconSize": "Icon Size", + "IconSizeTooltip": "Change the size of game icon", + "MenuBarOptionsShowConsole": "Show Console", + "ShaderCachePurgeError": "Error purging shader cache at {0}: {1}", + "UserErrorNoKeys": "Keys not found", + "UserErrorNoFirmware": "Firmware not found", + "UserErrorFirmwareParsingFailed": "Firmware parsing error", + "UserErrorApplicationNotFound": "Application not found", + "UserErrorUnknown": "Unknown error", + "UserErrorUndefined": "Undefined error", + "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": "An unknown error occured!", + "UserErrorUndefinedDescription": "An undefined error occured! This shouldn't happen, please contact a dev!", + "OpenSetupGuideMessage": "Open the Setup Guide", + "NoUpdate": "No Update", + "TitleUpdateVersionLabel": "Version {0} - {1}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Confirmation", + "FileDialogAllTypes": "All types", + "Never": "Never", + "SwkbdMinCharacters": "Must be at least {0} characters long", + "SwkbdMinRangeCharacters": "Must be {0}-{1} characters long", + "SoftwareKeyboard": "Software Keyboard", + "DialogControllerAppletMessagePlayerRange": "Application requests {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.", + "DialogControllerAppletMessage": "Application requests exactly {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.", + "DialogControllerAppletDockModeSet": "Docked mode set. Handheld is also invalid.\n\n", + "UpdaterRenaming": "Renaming Old Files...", + "UpdaterRenameFailed": "Updater was unable to rename file: {0}", + "UpdaterAddingFiles": "Adding New Files...", + "UpdaterExtracting": "Extracting Update...", + "UpdaterDownloading": "Downloading Update...", + "Game": "Game", + "Docked": "Docked", + "Handheld": "Handheld", + "ConnectionError": "Connection Error.", + "AboutPageDeveloperListMore": "{0} and more...", + "ApiError": "API Error.", + "LoadingHeading": "Loading {0}", + "CompilingPPTC": "Compiling PTC", + "CompilingShaders": "Compiling Shaders", + "AllKeyboards": "All keyboards", + "OpenFileDialogTitle": "Select a supported file to open", + "OpenFolderDialogTitle": "Select a folder with an unpacked game", + "AllSupportedFormats": "All Supported Formats", + "RyujinxUpdater": "Ryujinx Updater" +} diff --git a/Ryujinx.Ava/Assets/Locales/es_ES.json b/Ryujinx.Ava/Assets/Locales/es_ES.json new file mode 100644 index 000000000..49eb47198 --- /dev/null +++ b/Ryujinx.Ava/Assets/Locales/es_ES.json @@ -0,0 +1,497 @@ +{ + "MenuBarFileOpenApplet": "Abrir applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Abre el editor de Mii en modo autónomo", + "SettingsTabInputDirectMouseAccess": "Acceso directo al ratón", + "SettingsTabSystemMemoryManagerMode": "Modo del administrador de memoria:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Host (rápido)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host sin verificación (más rápido, inseguro)", + "MenuBarFile": "_Archivo", + "MenuBarFileOpenFromFile": "_Cargar aplicación desde un archivo", + "MenuBarFileOpenUnpacked": "Cargar juego _desempaquetado", + "MenuBarFileOpenEmuFolder": "Abrir carpeta de Ryujinx", + "MenuBarFileOpenLogsFolder": "Abrir carpeta de registros", + "MenuBarFileExit": "_Salir", + "MenuBarOptions": "_Opciones", + "MenuBarOptionsToggleFullscreen": "Alternar pantalla completa", + "MenuBarOptionsStartGamesInFullscreen": "Iniciar juegos en pantalla completa", + "MenuBarOptionsStopEmulation": "Detener emulación", + "MenuBarOptionsSettings": "_Configuración", + "MenuBarOptionsManageUserProfiles": "_Gestionar perfiles de usuario", + "MenuBarActions": "Accio_nes", + "MenuBarOptionsSimulateWakeUpMessage": "Simular mensaje de _reactivación", + "MenuBarActionsScanAmiibo": "_Escanear Amiibo", + "MenuBarTools": "_Herramientas", + "MenuBarToolsInstallFirmware": "Instalar firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Instalar firmware desde un archivo XCI o ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Instalar firmware desde una carpeta", + "MenuBarHelp": "A_yuda", + "MenuBarHelpCheckForUpdates": "Buscar actualizaciones", + "MenuBarHelpAbout": "Acerca de", + "MenuSearch": "Buscar...", + "GameListHeaderFavorite": "Fav", + "GameListHeaderIcon": "Icono", + "GameListHeaderApplication": "Aplicación", + "GameListHeaderDeveloper": "Desarrollador", + "GameListHeaderVersion": "Versión", + "GameListHeaderTimePlayed": "Tiempo jugado", + "GameListHeaderLastPlayed": "Última vez", + "GameListHeaderFileExtension": "Extensión", + "GameListHeaderFileSize": "Tamaño", + "GameListHeaderPath": "Directorio", + "GameListContextMenuOpenUserSaveDirectory": "Abrir carpeta de guardado de este usuario", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Abre la carpeta que contiene la partida guardada del usuario para esta aplicación", + "GameListContextMenuOpenUserDeviceDirectory": "Abrir carpeta de guardado del sistema para el usuario actual", + "GameListContextMenuOpenUserDeviceDirectoryToolTip": "Abre la carpeta que contiene la partida guardada del sistema para esta aplicación", + "GameListContextMenuOpenUserBcatDirectory": "Abrir carpeta de guardado BCAT del usuario", + "GameListContextMenuOpenUserBcatDirectoryToolTip": "Abre la carpeta que contiene el guardado BCAT de esta aplicación", + "GameListContextMenuManageTitleUpdates": "Gestionar actualizaciones del juego", + "GameListContextMenuManageTitleUpdatesToolTip": "Abre la ventana de gestión de actualizaciones de esta aplicación", + "GameListContextMenuManageDlc": "Gestionar DLC", + "GameListContextMenuManageDlcToolTip": "Abre la ventana de gestión del contenido descargable", + "GameListContextMenuOpenModsDirectory": "Abrir carpeta de mods", + "GameListContextMenuOpenModsDirectoryToolTip": "Abre la carpeta que contiene los mods (archivos modificantes) de esta aplicación", + "GameListContextMenuCacheManagement": "Gestión de cachés", + "GameListContextMenuCacheManagementPurgePptc": "Purgar caché de PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Elimina la caché de PPTC de esta aplicación", + "GameListContextMenuCacheManagementPurgeShaderCache": "Purgar caché de sombreadores", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Elimina la caché de sombreadores de esta aplicación", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Abrir carpeta de PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Abre la carpeta que contiene la caché de PPTC de esta aplicación", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Abrir carpeta de caché de sombreadores", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Abre la carpeta que contiene la caché de sombreadores de esta aplicación", + "GameListContextMenuExtractData": "Extraer datos", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extrae la sección ExeFS de la configuración actual de la aplicación (incluyendo actualizaciones)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extrae la sección RomFS de la configuración actual de la aplicación (incluyendo actualizaciones)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Extrae la sección Logo de la configuración actual de la aplicación (incluyendo actualizaciones)", + "StatusBarGamesLoaded": "{0}/{1} juegos cargados", + "StatusBarSystemVersion": "Versión del sistema: {0}", + "Settings": "Configuración", + "SettingsTabGeneral": "General", + "SettingsTabGeneralGeneral": "General", + "SettingsTabGeneralEnableDiscordRichPresence": "Habilitar presencia enriquecida de Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Buscar actualizaciones al iniciar", + "SettingsTabGeneralShowConfirmExitDialog": "Mostrar diálogo de confirmación al cerrar", + "SettingsTabGeneralHideCursorOnIdle": "Ocultar cursor cuando esté inactivo", + "SettingsTabGeneralGameDirectories": "Carpetas de juegos", + "SettingsTabGeneralAdd": "Agregar", + "SettingsTabGeneralRemove": "Quitar", + "SettingsTabSystem": "Sistema", + "SettingsTabSystemCore": "Principal", + "SettingsTabSystemSystemRegion": "Región del sistema:", + "SettingsTabSystemSystemRegionJapan": "Japón", + "SettingsTabSystemSystemRegionUSA": "EEUU", + "SettingsTabSystemSystemRegionEurope": "Europa", + "SettingsTabSystemSystemRegionAustralia": "Australia", + "SettingsTabSystemSystemRegionChina": "China", + "SettingsTabSystemSystemRegionKorea": "Corea", + "SettingsTabSystemSystemRegionTaiwan": "Taiwán", + "SettingsTabSystemSystemLanguage": "Idioma del sistema:", + "SettingsTabSystemSystemLanguageJapanese": "Japonés", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Inglés americano", + "SettingsTabSystemSystemLanguageFrench": "Francés", + "SettingsTabSystemSystemLanguageGerman": "Alemán", + "SettingsTabSystemSystemLanguageItalian": "Italiano", + "SettingsTabSystemSystemLanguageSpanish": "Español", + "SettingsTabSystemSystemLanguageChinese": "Chino", + "SettingsTabSystemSystemLanguageKorean": "Coreano", + "SettingsTabSystemSystemLanguageDutch": "Neerlandés/Holandés", + "SettingsTabSystemSystemLanguagePortuguese": "Portugués", + "SettingsTabSystemSystemLanguageRussian": "Ruso", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanés", + "SettingsTabSystemSystemLanguageBritishEnglish": "Inglés británico", + "SettingsTabSystemSystemLanguageCanadianFrench": "Francés canadiense", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Español latinoamericano", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chino simplificado", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Chino tradicional", + "SettingsTabSystemSystemTimeZone": "Zona horaria del sistema:", + "SettingsTabSystemSystemTime": "Hora del sistema:", + "SettingsTabSystemEnableVsync": "Habilitar sincronización vertical", + "SettingsTabSystemEnablePptc": "Habilitar PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Habilitar comprobaciones de integridad FS", + "SettingsTabSystemAudioBackend": "Motor de audio:", + "SettingsTabSystemAudioBackendDummy": "Dummy", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": " - Pueden causar inestabilidad", + "SettingsTabSystemExpandDramSize": "Expandir DRAM a 6GB", + "SettingsTabSystemIgnoreMissingServices": "Ignorar falta de servicios", + "SettingsTabGraphics": "Gráficos", + "SettingsTabGraphicsEnhancements": "Mejoras", + "SettingsTabGraphicsEnableShaderCache": "Habilitar caché de sombreadores", + "SettingsTabGraphicsAnisotropicFiltering": "Filtro anisotrópico:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", + "SettingsTabGraphicsAnisotropicFiltering2x": "x2", + "SettingsTabGraphicsAnisotropicFiltering4x": "x4", + "SettingsTabGraphicsAnisotropicFiltering8x": "x8", + "SettingsTabGraphicsAnisotropicFiltering16x": "x16", + "SettingsTabGraphicsResolutionScale": "Escala de resolución:", + "SettingsTabGraphicsResolutionScaleCustom": "Personalizada (no recomendado)", + "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "x2 (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "x3 (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "x4 (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Relación de aspecto:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Estirar a la ventana", + "SettingsTabGraphicsDeveloperOptions": "Opciones de desarrollador", + "SettingsTabGraphicsShaderDumpPath": "Directorio de volcado de sombreadores:", + "SettingsTabLogging": "Registros", + "SettingsTabLoggingLogging": "Registros", + "SettingsTabLoggingEnableLoggingToFile": "Escribir registros en archivo", + "SettingsTabLoggingEnableStubLogs": "Habilitar registros de Stub", + "SettingsTabLoggingEnableInfoLogs": "Habilitar registros de Info", + "SettingsTabLoggingEnableWarningLogs": "Habilitar registros de Advertencia", + "SettingsTabLoggingEnableErrorLogs": "Habilitar registros de Error", + "SettingsTabLoggingEnableGuestLogs": "Habilitar registros de Guest", + "SettingsTabLoggingEnableFsAccessLogs": "Habilitar registros de Fs Access", + "SettingsTabLoggingFsGlobalAccessLogMode": "Modo de registros Fs Global Access:", + "SettingsTabLoggingDeveloperOptions": "Opciones de desarrollador (ADVERTENCIA: empeorarán el rendimiento)", + "SettingsTabLoggingOpenglLogLevel": "Nivel de registro de OpenGL:", + "SettingsTabLoggingOpenglLogLevelNone": "Nada", + "SettingsTabLoggingOpenglLogLevelError": "Errores", + "SettingsTabLoggingOpenglLogLevelPerformance": "Ralentizaciones", + "SettingsTabLoggingOpenglLogLevelAll": "Todo", + "SettingsTabLoggingEnableDebugLogs": "Habilitar registros de debug", + "SettingsTabInput": "Entrada", + "SettingsTabInputEnableDockedMode": "Habilitar modo acoplado (dock/TV)", + "SettingsTabInputDirectKeyboardAccess": "Acceso directo al teclado", + "SettingsButtonSave": "Guardar", + "SettingsButtonClose": "Cerrar", + "SettingsButtonApply": "Aplicar", + "ControllerSettingsPlayer": "Jugador", + "ControllerSettingsPlayer1": "Jugador 1", + "ControllerSettingsPlayer2": "Jugador 2", + "ControllerSettingsPlayer3": "Jugador 3", + "ControllerSettingsPlayer4": "Jugador 4", + "ControllerSettingsPlayer5": "Jugador 5", + "ControllerSettingsPlayer6": "Jugador 6", + "ControllerSettingsPlayer7": "Jugador 7", + "ControllerSettingsPlayer8": "Jugador 8", + "ControllerSettingsHandheld": "Portátil", + "ControllerSettingsInputDevice": "Dispositivo de entrada", + "ControllerSettingsRefresh": "Actualizar", + "ControllerSettingsDeviceDisabled": "Deshabilitado", + "ControllerSettingsControllerType": "Tipo de Mando", + "ControllerSettingsControllerTypeHandheld": "Portátil", + "ControllerSettingsControllerTypeProController": "Mando Pro", + "ControllerSettingsControllerTypeJoyConPair": "Doble Joy-Con", + "ControllerSettingsControllerTypeJoyConLeft": "Joy-Con Izquierdo", + "ControllerSettingsControllerTypeJoyConRight": "Joy-Con Derecho", + "ControllerSettingsProfile": "Perfil", + "ControllerSettingsProfileDefault": "Predeterminado", + "ControllerSettingsLoad": "Cargar", + "ControllerSettingsAdd": "Agregar", + "ControllerSettingsRemove": "Quitar", + "ControllerSettingsButtons": "Botones", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Pad direccional", + "ControllerSettingsDPadUp": "Arriba", + "ControllerSettingsDPadDown": "Abajo", + "ControllerSettingsDPadLeft": "Izquierda", + "ControllerSettingsDPadRight": "Derecha", + "ControllerSettingsLStick": "Palanca izquierda", + "ControllerSettingsLStickButton": "Botón (L3)", + "ControllerSettingsLStickUp": "Arriba", + "ControllerSettingsLStickDown": "Abajo", + "ControllerSettingsLStickLeft": "Izquierda", + "ControllerSettingsLStickRight": "Derecha", + "ControllerSettingsLStickStick": "Palanca", + "ControllerSettingsLStickInvertXAxis": "Invertir eje X", + "ControllerSettingsLStickInvertYAxis": "Invertir eje Y", + "ControllerSettingsLStickDeadzone": "Zona muerta:", + "ControllerSettingsRStick": "Palanca derecha", + "ControllerSettingsRStickButton": "Botón (R3)", + "ControllerSettingsRStickUp": "Arriba", + "ControllerSettingsRStickDown": "Abajo", + "ControllerSettingsRStickLeft": "Izquierda", + "ControllerSettingsRStickRight": "Derecha", + "ControllerSettingsRStickStick": "Palanca", + "ControllerSettingsRStickInvertXAxis": "Invertir eje X", + "ControllerSettingsRStickInvertYAxis": "Invertir eje Y", + "ControllerSettingsRStickDeadzone": "Zona muerta:", + "ControllerSettingsTriggersLeft": "Gatillos izquierdos", + "ControllerSettingsTriggersRight": "Gatillos derechos", + "ControllerSettingsTriggersButtonsLeft": "Botones de gatillo izquierdos", + "ControllerSettingsTriggersButtonsRight": "Botones de gatillo derechos", + "ControllerSettingsTriggers": "Gatillos", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Botones izquierdos", + "ControllerSettingsExtraButtonsRight": "Botones derechos", + "ControllerSettingsMisc": "Misceláneo", + "ControllerSettingsTriggerThreshold": "Límite de gatillos:", + "ControllerSettingsMotion": "Movimiento", + "ControllerSettingsCemuHook": "CemuHook", + "ControllerSettingsMotionEnableMotionControls": "Habilitar controles por movimiento", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Usar movimiento compatible con CemuHook", + "ControllerSettingsMotionControllerSlot": "Puerto del mando:", + "ControllerSettingsMotionMirrorInput": "Paralelizar derecho e izquierdo", + "ControllerSettingsMotionRightJoyConSlot": "Puerto del Joy-Con derecho:", + "ControllerSettingsMotionServerHost": "Host del servidor:", + "ControllerSettingsMotionGyroSensitivity": "Sensibilidad de Gyro:", + "ControllerSettingsMotionGyroDeadzone": "Zona muerta de Gyro:", + "ControllerSettingsSave": "Guardar", + "ControllerSettingsClose": "Cerrar", + "UserProfilesSelectedUserProfile": "Perfil de usuario seleccionado:", + "UserProfilesSaveProfileName": "Guardar nombre de perfil", + "UserProfilesChangeProfileImage": "Cambiar imagen de perfil", + "UserProfilesAvailableUserProfiles": "Perfiles de usuario disponibles:", + "UserProfilesAddNewProfile": "Añadir nuevo perfil", + "UserProfilesDeleteSelectedProfile": "Eliminar perfil seleccionado", + "UserProfilesClose": "Cerrar", + "ProfileImageSelectionTitle": "Selección de imagen de perfil", + "ProfileImageSelectionHeader": "Elige una imagen de perfil", + "ProfileImageSelectionNote": "Puedes importar una imagen de perfil personalizada, o seleccionar un avatar del firmware de sistema", + "ProfileImageSelectionImportImage": "Importar imagen", + "ProfileImageSelectionSelectAvatar": "Seleccionar avatar del firmware", + "InputDialogTitle": "Cuadro de diálogo de entrada", + "InputDialogOk": "Aceptar", + "InputDialogCancel": "Cancelar", + "InputDialogAddNewProfileTitle": "Introducir nombre de perfil", + "InputDialogAddNewProfileHeader": "Por favor elige un nombre de usuario", + "InputDialogAddNewProfileSubtext": "(Máximo de caracteres: {0})", + "AvatarChoose": "Escoger", + "AvatarSetBackgroundColor": "Establecer color de fondo", + "AvatarClose": "Cerrar", + "ControllerSettingsLoadProfileToolTip": "Cargar perfil", + "ControllerSettingsAddProfileToolTip": "Agregar perfil", + "ControllerSettingsRemoveProfileToolTip": "Eliminar perfil", + "ControllerSettingsSaveProfileToolTip": "Guardar perfil", + "MenuBarFileToolsTakeScreenshot": "Captura de pantalla", + "MenuBarFileToolsHideUi": "Ocultar interfaz", + "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", + "ControllerSettingsMotionConfigureCemuHookSettings": "Configurar controles por movimiento de CemuHook", + "ControllerSettingsRumble": "Vibración", + "ControllerSettingsRumbleEnable": "Habilitar vibraciones", + "ControllerSettingsRumbleStrongMultiplier": "Multiplicador de vibraciones fuertes", + "ControllerSettingsRumbleWeakMultiplier": "Multiplicador de vibraciones débiles", + "DialogMessageSaveNotAvailableMessage": "No hay datos de guardado para {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "¿Quieres crear datos de guardado para este juego?", + "DialogConfirmationTitle": "Ryujinx - Confirmación", + "DialogUpdaterTitle": "Ryujinx - Actualizador", + "DialogErrorTitle": "Ryujinx - Error", + "DialogWarningTitle": "Ryujinx - Advertencia", + "DialogExitTitle": "Ryujinx - Salir", + "DialogErrorMessage": "Ryujinx encontró un error", + "DialogExitMessage": "¿Seguro que quieres cerrar Ryujinx?", + "DialogExitSubMessage": "¡Se perderán los datos no guardados!", + "DialogMessageCreateSaveErrorMessage": "Hubo un error al crear los datos de guardado especificados: {0}", + "DialogMessageFindSaveErrorMessage": "Hubo un error encontrando los datos de guardado especificados: {0}", + "FolderDialogExtractTitle": "Elige la carpeta en la que deseas extraer", + "DialogNcaExtractionMessage": "Extrayendo {0} sección de {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Extractor de sección NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Fallo de extracción. El NCA principal no estaba presente en el archivo seleccionado.", + "DialogNcaExtractionCheckLogErrorMessage": "Fallo de extracción. Lee el registro para más información.", + "DialogNcaExtractionSuccessMessage": "Se completó la extracción con éxito.", + "DialogUpdaterConvertFailedMessage": "No se pudo convertir la versión actual de Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "¡Cancelando actualización!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "¡Ya tienes la versión más reciente de Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Ha ocurrido un error al intentar obtener información de versión desde AppVeyor.", + "DialogUpdaterConvertFailedAppveyorMessage": "No se pudo convertir la versión de Ryujinx recibida de AppVeyor.", + "DialogUpdaterDownloadingMessage": "Descargando actualización...", + "DialogUpdaterExtractionMessage": "Extrayendo actualización...", + "DialogUpdaterRenamingMessage": "Renombrando actualización...", + "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!", + "DialogUpdaterDirtyBuildSubMessage": "Por favor, descarga Ryujinx en https://ryujinx.org/ si buscas una versión con soporte.", + "DialogRestartRequiredMessage": "Se necesita reiniciar", + "DialogThemeRestartMessage": "Tema guardado. Se necesita reiniciar para aplicar el tema.", + "DialogThemeRestartSubMessage": "¿Quieres reiniciar?", + "DialogFirmwareInstallEmbeddedMessage": "¿Quieres instalar el firmware incluido en este juego? (Firmware versión {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "No se encontró firmware instalado pero Ryujinx pudo instalar el firmware {0} a partir de este juego.\nA continuación, se iniciará el emulador.", + "DialogFirmwareNoFirmwareInstalledMessage": "No hay firmware instalado", + "DialogFirmwareInstalledMessage": "Se instaló el firmware {0}", + "DialogOpenSettingsWindowLabel": "Abrir ventana de opciones", + "DialogControllerAppletTitle": "Applet de mandos", + "DialogMessageDialogErrorExceptionMessage": "Error al mostrar cuadro de diálogo: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Error al mostrar teclado de software: {0}", + "DialogErrorAppletErrorExceptionMessage": "Error al mostrar díalogo ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nPara más información sobre cómo arreglar este error, sigue nuestra Guía de Instalación.", + "DialogUserErrorDialogTitle": "Ryujinx Error ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Ocurrió un error al recibir información de la API.", + "DialogAmiiboApiConnectErrorMessage": "No se pudo conectar al servidor de la API Amiibo. El servicio puede estar caído o tu conexión a internet puede haberse desconectado.", + "DialogProfileInvalidProfileErrorMessage": "El perfil {0} no es compatible con el sistema actual de configuración de entrada.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "El perfil predeterminado no se puede sobreescribir", + "DialogProfileDeleteProfileTitle": "Eliminando perfil", + "DialogProfileDeleteProfileMessage": "Esta acción es irreversible, ¿estás seguro de querer continuar?", + "DialogWarning": "Advertencia", + "DialogPPTCDeletionMessage": "Vas a borrar la caché de PPTC para:\n\n{0}\n\n¿Estás seguro de querer continuar?", + "DialogPPTCDeletionErrorMessage": "Error purgando la caché de PPTC en {0}: {1}", + "DialogShaderDeletionMessage": "Vas a borrar la caché de sombreadores para:\n\n{0}\n\n¿Estás seguro de querer continuar?", + "DialogShaderDeletionErrorMessage": "Error purgando la caché de sombreadores en {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx ha encontrado un error", + "DialogInvalidTitleIdErrorMessage": "Error de interfaz: El juego seleccionado no tiene una ID válida", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "No se pudo encontrar un firmware válido en {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Instalar firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Se instalará la versión de sistema {0}.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nEsto reemplazará la versión de sistema actual, {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n¿Continuar?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Instalando firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Versión de sistema {0} instalada con éxito.", + "DialogUserProfileDeletionWarningMessage": "Si eliminas el perfil seleccionado no quedará ningún otro perfil", + "DialogUserProfileDeletionConfirmMessage": "¿Quieres eliminar el perfil seleccionado?", + "DialogControllerSettingsModifiedConfirmMessage": "Se ha actualizado la configuración del mando actual.", + "DialogControllerSettingsModifiedConfirmSubMessage": "¿Guardar cambios?", + "DialogDlcLoadNcaErrorMessage": "{0}. Archivo con error: {1}", + "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 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.", + "SettingsTabGraphicsFeaturesOptions": "Funcionalidades", + "SettingsTabGraphicsBackendMultithreading": "Multihilado del motor gráfico:", + "CommonAuto": "Auto", + "CommonOff": "Desactivado", + "CommonOn": "Activado", + "InputDialogYes": "Sí", + "InputDialogNo": "No", + "DialogProfileInvalidProfileNameErrorMessage": "El nombre de archivo contiene caracteres inválidos. Por favor, inténtalo de nuevo.", + "MenuBarOptionsPauseEmulation": "Pausar", + "MenuBarOptionsResumeEmulation": "Reanudar", + "AboutUrlTooltipMessage": "Haz clic para abrir el sitio web de Ryujinx en tu navegador predeterminado.", + "AboutDisclaimerMessage": "Ryujinx no tiene afiliación alguna con Nintendo™,\nni con ninguno de sus socios.", + "AboutAmiiboDisclaimerMessage": "Utilizamos AmiiboAPI (www.amiiboapi.com)\nen nuestra emulación de Amiibo.", + "AboutPatreonUrlTooltipMessage": "Haz clic para abrir el Patreon de Ryujinx en tu navegador predeterminado.", + "AboutGithubUrlTooltipMessage": "Haz clic para abrir el GitHub de Ryujinx en tu navegador predeterminado.", + "AboutDiscordUrlTooltipMessage": "Haz clic para recibir una invitación al Discord de Ryujinx en tu navegador predeterminado.", + "AboutTwitterUrlTooltipMessage": "Haz clic para abrir el Twitter de Ryujinx en tu navegador predeterminado.", + "AboutRyujinxAboutTitle": "Acerca de:", + "AboutRyujinxAboutContent": "Ryujinx es un emulador para Nintendo Switch™.\nPor favor, apóyanos en Patreon.\nEncuentra las noticias más recientes en nuestro Twitter o Discord.\nDesarrolladores interesados en contribuir pueden encontrar más información en GitHub o Discord.", + "AboutRyujinxMaintainersTitle": "Mantenido por:", + "AboutRyujinxMaintainersContentTooltipMessage": "Haz clic para abrir la página de contribuidores en tu navegador predeterminado.", + "AboutRyujinxSupprtersTitle": "Mecenas en Patreon:", + "AmiiboSeriesLabel": "Serie de Amiibo", + "AmiiboCharacterLabel": "Personaje", + "AmiiboScanButtonLabel": "Escanear", + "AmiiboOptionsShowAllLabel": "Mostrar todos los Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack: usar etiqueta aleatoria Uuid", + "DlcManagerTableHeadingEnabledLabel": "Habilitado", + "DlcManagerTableHeadingTitleIdLabel": "ID de título", + "DlcManagerTableHeadingContainerPathLabel": "Directorio del contenedor", + "DlcManagerTableHeadingFullPathLabel": "Directorio completo", + "DlcManagerRemoveAllButton": "Quitar todo", + "MenuBarOptionsChangeLanguage": "Cambiar idioma", + "CommonSort": "Orden", + "CommonShowNames": "Mostrar nombres", + "CommonFavorite": "Favorito", + "OrderAscending": "Ascendente", + "OrderDescending": "Descendente", + "SettingsTabGraphicsFeatures": "Funcionalidades", + "ErrorWindowTitle": "Ventana de error", + "ToggleDiscordTooltip": "Activa o desactiva la presencia enriquecida de Discord", + "AddGameDirBoxTooltip": "Elige un directorio de juegos para mostrar en la ventana principal", + "AddGameDirTooltip": "Agrega un directorio de juegos a la lista", + "RemoveGameDirTooltip": "Quita el directorio seleccionado de la lista", + "CustomThemeCheckTooltip": "Activa o desactiva los temas personalizados para la interfaz", + "CustomThemePathTooltip": "Carpeta que contiene los temas personalizados para la interfaz", + "CustomThemeBrowseTooltip": "Busca un tema personalizado para la interfaz", + "DockModeToggleTooltip": "Activa o desactiva el modo TV de la Nintendo Switch", + "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)", + "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": "Activa o desactiva la sincronización vertical", + "PptcToggleTooltip": "Activa o desactiva la caché de PPTC", + "FsIntegrityToggleTooltip": "Activa comprobaciones de integridad de los archivos de un juego", + "AudioBackendTooltip": "Cambia el motor de audio", + "MemoryManagerTooltip": "Cambia la forma de mapear y acceder a la memoria del guest. Afecta en gran medida al rendimiento de la CPU emulada.", + "MemoryManagerSoftwareTooltip": "Usa una tabla de paginación de software para traducir direcciones. Ofrece la precisión más exacta pero el rendimiento más lento.", + "MemoryManagerHostTooltip": "Mapea la memoria directamente en la dirección de espacio del host. Compilación y ejecución JIT mucho más rápida.", + "MemoryManagerUnsafeTooltip": "Mapea la memoria directamente, pero no enmascara la dirección dentro del espacio de dirección del guest antes del acceso. El modo más rápido, pero a costa de seguridad. La aplicación guest puede acceder a la memoria desde cualquier parte en Ryujinx, así que ejecuta solo programas en los que confíes cuando uses este modo.", + "DRamTooltip": "Expande la memoria DRAM del sistema emulado de 4GB a 6GB. Utilizar solo con mods 4K.", + "IgnoreMissingServicesTooltip": "Activa o desactiva un hack para ignorar servicios no implementados", + "GraphicsBackendThreadingTooltip": "Activa el multihilado del motor gráfico", + "GalThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Permite multihilar la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos sin necesidad de que utilicen su propio multihilado. Rendimiento máximo ligeramente variable en controladores gráficos que soporten multihilado. Ryujinx puede necesitar reiniciarse para desactivar correctamente el multihilado de tu controlador de gráficos, o puede que tengas que desactivarlo manualmente para lograr el mejor rendimiento posible.", + "ShaderCacheToggleTooltip": "Activa o desactiva la caché de sombreadores", + "ResolutionScaleTooltip": "Escala de resolución aplicada a objetivos aplicables en el renderizado", + "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.", + "ShaderDumpPathTooltip": "Directorio en el cual se volcarán los sombreadores de los gráficos", + "FileLogTooltip": "Activa o desactiva el guardado de registros en archivos en disco", + "StubLogTooltip": "Activa mensajes de Stub en la consola", + "InfoLogTooltip": "Activa mensajes de Info en la consola", + "WarnLogTooltip": "Activa mensajes de Advertencia en la consola", + "ErrorLogTooltip": "Activa mensajes de Error en la consola", + "GuestLogTooltip": "Activa mensajes de Guest en la consola", + "FileAccessLogTooltip": "Activa mensajes de acceso a archivo en la consola", + "FSAccessLogModeTooltip": "Activa registros FS Access en la consola. Los modos posibles son entre 0 y 3", + "DeveloperOptionTooltip": "Usar con cuidado", + "OpenGlLogLevel": "Requiere activar los niveles de registro apropiados", + "DebugLogTooltip": "Activa mensajes de debug en la consola", + "LoadApplicationFileTooltip": "Abre el explorador de archivos para elegir un archivo compatible con Switch para cargar", + "LoadApplicationFolderTooltip": "Abre el explorador de archivos para elegir un archivo desempaquetado y compatible con Switch para cargar", + "OpenRyujinxFolderTooltip": "Abre la carpeta de sistema de Ryujinx", + "OpenRyujinxLogsTooltip": "Abre la carpeta en la que se guardan los registros", + "ExitTooltip": "Cierra Ryujinx", + "OpenSettingsTooltip": "Abre la ventana de configuración", + "OpenProfileManagerTooltip": "Abre la ventana para gestionar los perfiles de usuario", + "StopEmulationTooltip": "Detiene la emulación del juego actual y regresa a la selección de juegos", + "CheckUpdatesTooltip": "Busca actualizaciones para Ryujinx", + "OpenAboutTooltip": "Abre la ventana \"Acerca de\"", + "GridSize": "Tamaño de cuadrícula", + "GridSizeTooltip": "Cambia el tamaño de los objetos en la cuadrícula", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portugués brasileño", + "AboutRyujinxContributorsButtonHeader" : "Ver todos los contribuidores", + "SettingsTabSystemAudioVolume": "Volumen: ", + "AudioVolumeTooltip": "Ajusta el nivel de volumen", + "SettingsTabSystemEnableInternetAccess": "Habilitar acceso a Internet del guest", + "EnableInternetAccessTooltip": "Activa el acceso a Internet del guest. Cuando esté activo, la aplicación actuará como si la Nintendo Switch emulada estuviese conectada a Internet. Ten en cuenta que algunas aplicaciones pueden intentar acceder a Internet incluso con esta opción desactivada.", + "GameListContextMenuManageCheatToolTip" : "Activa o desactiva los cheats", + "GameListContextMenuManageCheat" : "Administrar cheats", + "ControllerSettingsStickRange" : "Alcance", + "DialogStopEmulationTitle" : "Ryujinx - Detener emulación", + "DialogStopEmulationMessage": "¿Seguro que quieres detener la emulación actual?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Audio", + "SettingsTabNetwork": "Red", + "SettingsTabNetworkConnection" : "Conexión de red", + "SettingsTabGraphicsFrameRate" : "Velocidad máxima de fotogramas:", + "SettingsTabGraphicsFrameRateTooltip" : "Fija el límite de fotogramas del host. Elige 0 para deshabilitar el límite.", + "SettingsTabCpuCache" : "Caché de CPU", + "SettingsTabCpuMemory" : "Memoria de CPU" +} diff --git a/Ryujinx.Ava/Assets/Locales/fr_FR.json b/Ryujinx.Ava/Assets/Locales/fr_FR.json new file mode 100644 index 000000000..95dfb906e --- /dev/null +++ b/Ryujinx.Ava/Assets/Locales/fr_FR.json @@ -0,0 +1,270 @@ +{ + "MenuBarFile": "_Fichier", + "MenuBarFileOpenFromFile": "_Charger un jeu depuis un fichier", + "MenuBarFileOpenUnpacked": "Charger un jeu _extrait", + "MenuBarFileOpenEmuFolder": "Ouvrir le dossier Ryujinx", + "MenuBarFileOpenLogsFolder": "Ouvrir le dossier de Logs", + "MenuBarFileExit": "_Quitter", + "MenuBarOptions": "Options", + "MenuBarOptionsToggleFullscreen": "Basculer 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", + "MenuBarActions": "_Actions", + "MenuBarOptionsSimulateWakeUpMessage": "Simuler une sortie de veille", + "MenuBarActionsScanAmiibo": "Scanner un Amiibo", + "MenuBarTools": "_Outils", + "MenuBarToolsInstallFirmware": "Installer un firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Installer un firmware depuis un fichier XCI ou ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Installer un firmware depuis un dossier", + "MenuBarHelp": "Aide", + "MenuBarHelpCheckForUpdates": "Vérifier les mises à jour", + "MenuBarHelpAbout": "Á propos", + "MenuSearch": "Rechercher...", + "GameListHeaderFavorite": "Favoris", + "GameListHeaderIcon": "Icône", + "GameListHeaderApplication": "Application", + "GameListHeaderDeveloper": "Développeur", + "GameListHeaderVersion": "Version", + "GameListHeaderTimePlayed": "Temps de jeu", + "GameListHeaderLastPlayed": "Dernière partie", + "GameListHeaderFileExtension": "Extension du Fichier", + "GameListHeaderFileSize": "Taille du Fichier", + "GameListHeaderPath": "Chemin", + "GameListContextMenuOpenUserSaveDirectory": "Ouvrir le dossier de sauvegarde utilisateur", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde utilisateur du jeu", + "GameListContextMenuOpenUserDeviceDirectory": "Ouvrir le dossier de sauvegarde console", + "GameListContextMenuOpenUserDeviceDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde console du jeu", + "GameListContextMenuOpenUserBcatDirectory": "Ouvrir le dossier de sauvegarde BCAT", + "GameListContextMenuOpenUserBcatDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde BCAT du jeu", + "GameListContextMenuManageTitleUpdates": "Gérer les mises à jour", + "GameListContextMenuManageTitleUpdatesToolTip": "Ouvre la fenêtre de gestion des mises à jour", + "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", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Supprime le cache des shaders du jeu", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Ouvrir le dossier du PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Ouvre le dossier contenant le PPTC du jeu", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Ouvrir le dossier du cache des shaders", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Ouvre le dossier contenant le cache des shaders du jeu", + "GameListContextMenuExtractData": "Extraire les données", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extrait la section ExeFS du jeu (mise à jour incluse)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extrait la section RomFS du jeu (mise à jour incluse)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Extrait la section Logo du jeu (mise à jour incluse)", + "StatusBarGamesLoaded": "{0}/{1} Jeux chargés", + "StatusBarSystemVersion": "Version du Firmware: {0}", + "Settings": "Paramètres", + "SettingsTabGeneral": "Général", + "SettingsTabGeneralGeneral": "Général", + "SettingsTabGeneralEnableDiscordRichPresence": "Active Discord Rich Presence", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Vérifier les mises à jour au démarrage", + "SettingsTabGeneralShowConfirmExitDialog": "Afficher le message de \"Confirmation de fermeture\"", + "SettingsTabGeneralHideCursorOnIdle": "Masquer le curseur si inactif", + "SettingsTabGeneralGameDirectories": "Dossiers de Jeux", + "SettingsTabGeneralAdd": "Ajouter", + "SettingsTabGeneralRemove": "Supprimer", + "SettingsTabSystem": "Système", + "SettingsTabSystemCore": "Core", + "SettingsTabSystemSystemRegion": "Région du système:", + "SettingsTabSystemSystemRegionJapan": "Japon", + "SettingsTabSystemSystemRegionUSA": "USA", + "SettingsTabSystemSystemRegionEurope": "Europe", + "SettingsTabSystemSystemRegionAustralia": "Australie", + "SettingsTabSystemSystemRegionChina": "Chine", + "SettingsTabSystemSystemRegionKorea": "Corée", + "SettingsTabSystemSystemRegionTaiwan": "Taïwan", + "SettingsTabSystemSystemLanguage": "Langue du système:", + "SettingsTabSystemSystemLanguageJapanese": "Japonais", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Américain", + "SettingsTabSystemSystemLanguageFrench": "Français", + "SettingsTabSystemSystemLanguageGerman": "Allemand", + "SettingsTabSystemSystemLanguageItalian": "Italien", + "SettingsTabSystemSystemLanguageSpanish": "Espagnol", + "SettingsTabSystemSystemLanguageChinese": "Chinois", + "SettingsTabSystemSystemLanguageKorean": "Coréen", + "SettingsTabSystemSystemLanguageDutch": "Néerlandais", + "SettingsTabSystemSystemLanguagePortuguese": "Portugais", + "SettingsTabSystemSystemLanguageRussian": "Russe", + "SettingsTabSystemSystemLanguageTaiwanese": "Taïwanais", + "SettingsTabSystemSystemLanguageBritishEnglish": "Anglais", + "SettingsTabSystemSystemLanguageCanadianFrench": "Canadien", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Espagnol latino-américain", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chinois simplifié", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Chinois traditionnel", + "SettingsTabSystemSystemTimeZone": "Fuseau horaire du système:", + "SettingsTabSystemSystemTime": "Heure du système:", + "SettingsTabSystemEnableVsync": "Activer la VSync", + "SettingsTabSystemEnablePptc": "Activer le PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Activer la vérification de l'intégrité du système de fichiers", + "SettingsTabSystemAudioBackend": "Bibliothèque Audio:", + "SettingsTabSystemAudioBackendDummy": "Dummy", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": " - Cela peut causer des instabilitées", + "SettingsTabSystemExpandDramSize": "Augmenter la taille de la DRAM à 6GB", + "SettingsTabSystemIgnoreMissingServices": "Ignorer les services manquant", + "SettingsTabGraphics": "Graphique", + "SettingsTabGraphicsEnhancements": "Améliorations", + "SettingsTabGraphicsEnableShaderCache": "Activer le cache des shaders", + "SettingsTabGraphicsAnisotropicFiltering": "Filtrage anisotrope:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Échelle de résolution:", + "SettingsTabGraphicsResolutionScaleCustom": "Customisée (Non recommandée)", + "SettingsTabGraphicsResolutionScaleNative": "Native (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Format:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Écran étiré", + "SettingsTabGraphicsDeveloperOptions": "Options développeur", + "SettingsTabGraphicsShaderDumpPath": "Chemin du dossier de dump des shaders:", + "SettingsTabLogging": "Journaux", + "SettingsTabLoggingLogging": "Journaux", + "SettingsTabLoggingEnableLoggingToFile": "Activer la sauvegarde des journaux vers un fichier", + "SettingsTabLoggingEnableStubLogs": "Activer les journaux stub", + "SettingsTabLoggingEnableInfoLogs": "Activer les journaux d'informations", + "SettingsTabLoggingEnableWarningLogs": "Activer les journaux d'avertissements", + "SettingsTabLoggingEnableErrorLogs": "Activer les journaux d'erreurs", + "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)", + "SettingsTabLoggingOpenglLogLevel": "Niveau des journaux OpenGL:", + "SettingsTabLoggingOpenglLogLevelNone": "Aucun", + "SettingsTabLoggingOpenglLogLevelError": "Erreur", + "SettingsTabLoggingOpenglLogLevelPerformance": "Ralentissements", + "SettingsTabLoggingOpenglLogLevelAll": "Tout", + "SettingsTabLoggingEnableDebugLogs": "Activer les journaux de debug", + "SettingsTabInput": "Contrôles", + "SettingsTabInputEnableDockedMode": "Active le mode station d'accueil", + "SettingsTabInputDirectKeyboardAccess": "Accès direct au clavier", + "SettingsButtonSave": "Enregistrer", + "SettingsButtonClose": "Fermer", + "SettingsButtonApply": "Appliquer", + "ControllerSettingsPlayer": "Joueur", + "ControllerSettingsPlayer1": "Joueur 1", + "ControllerSettingsPlayer2": "Joueur 2", + "ControllerSettingsPlayer3": "Joueur 3", + "ControllerSettingsPlayer4": "Joueur 4", + "ControllerSettingsPlayer5": "Joueur 5", + "ControllerSettingsPlayer6": "Joueur 6", + "ControllerSettingsPlayer7": "Joueur 7", + "ControllerSettingsPlayer8": "Joueur 8", + "ControllerSettingsHandheld": "Portable", + "ControllerSettingsInputDevice": "Périphériques", + "ControllerSettingsRefresh": "Actualiser", + "ControllerSettingsDeviceDisabled": "Désactivé", + "ControllerSettingsControllerType": "Type de Controleur", + "ControllerSettingsControllerTypeHandheld": "Portable", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon Joints", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Gauche", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon Droite", + "ControllerSettingsProfile": "Profil", + "ControllerSettingsProfileDefault": "Défaut", + "ControllerSettingsLoad": "Charger", + "ControllerSettingsAdd": "Ajouter", + "ControllerSettingsRemove": "Supprimer", + "ControllerSettingsButtons": "Boutons", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Croix Directionnelle", + "ControllerSettingsDPadUp": "Haut", + "ControllerSettingsDPadDown": "Bas", + "ControllerSettingsDPadLeft": "Gauche", + "ControllerSettingsDPadRight": "Droite", + "ControllerSettingsLStick": "Joystick Gauche", + "ControllerSettingsLStickButton": "Bouton", + "ControllerSettingsLStickUp": "Haut", + "ControllerSettingsLStickDown": "Bas", + "ControllerSettingsLStickLeft": "Gauche", + "ControllerSettingsLStickRight": "Droite", + "ControllerSettingsLStickStick": "Joystick", + "ControllerSettingsLStickInvertXAxis": "Inverser l'axe X", + "ControllerSettingsLStickInvertYAxis": "Inverser l'axe Y", + "ControllerSettingsLStickDeadzone": "Zone morte:", + "ControllerSettingsRStick": "Joystick Droit", + "ControllerSettingsRStickButton": "Bouton", + "ControllerSettingsRStickUp": "Haut", + "ControllerSettingsRStickDown": "Bas", + "ControllerSettingsRStickLeft": "Gauche", + "ControllerSettingsRStickRight": "Droite", + "ControllerSettingsRStickStick": "Joystick", + "ControllerSettingsRStickInvertXAxis": "Inverser l'axe X", + "ControllerSettingsRStickInvertYAxis": "Inverser l'axe Y", + "ControllerSettingsRStickDeadzone": "Zone morte:", + "ControllerSettingsTriggers": "Gachettes", + "ControllerSettingsTriggersLeft": "Gachettes Gauche", + "ControllerSettingsTriggersRight": "Gachettes Droite", + "ControllerSettingsTriggersButtonsLeft": "Boutons Gachettes Gauche", + "ControllerSettingsTriggersButtonsRight": "Boutons Gachettes Droite", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Boutons Gauche", + "ControllerSettingsExtraButtonsRight": "Boutons Droite", + "ControllerSettingsMisc": "Divers", + "ControllerSettingsTriggerThreshold": "Seuil de gachettes:", + "ControllerSettingsMotion": "Mouvements", + "ControllerSettingsCemuHook": "CemuHook", + "ControllerSettingsMotionEnableMotionControls": "Activer le capteur de mouvements", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Utiliser un capteur de mouvements CemuHook", + "ControllerSettingsMotionControllerSlot": "Contrôleur ID:", + "ControllerSettingsMotionMirrorInput": "Inverser les contrôles", + "ControllerSettingsMotionRightJoyConSlot": "JoyCon Droit ID:", + "ControllerSettingsMotionServerHost": "Addresse du Server:", + "ControllerSettingsMotionGyroSensitivity": "Sensibilitée du gyroscope:", + "ControllerSettingsMotionGyroDeadzone": "Zone morte du gyroscope:", + "ControllerSettingsSave": "Enregistrer", + "ControllerSettingsClose": "Fermer", + "UserProfilesSelectedUserProfile": "Choisir un profil utilisateur:", + "UserProfilesSaveProfileName": "Enregistrer le nom du profil", + "UserProfilesChangeProfileImage": "Changer l'image du profil", + "UserProfilesAvailableUserProfiles": "Profils utilisateurs disponible:", + "UserProfilesAddNewProfile": "Ajouter un nouveau profil", + "UserProfilesDeleteSelectedProfile": "Supprimer le profil sélectionné", + "UserProfilesClose": "Fermer", + "ProfileImageSelectionTitle": "Sélection de l'image du profil", + "ProfileImageSelectionHeader": "Choisir l'image du profil", + "ProfileImageSelectionNote": "Vous pouvez importer une image de profil personnalisée ou sélectionner un avatar à partir du firmware", + "ProfileImageSelectionImportImage": "Importer une image", + "ProfileImageSelectionSelectAvatar": "Choisir un avatar du firmware", + "InputDialogTitle": "Fenêtre d'entrée de texte", + "InputDialogOk": "OK", + "InputDialogCancel": "Annuler", + "InputDialogAddNewProfileTitle": "Choisir un nom de profil", + "InputDialogAddNewProfileHeader": "Merci d'entrer un nom de profil", + "InputDialogAddNewProfileSubtext": "(Longueur max.: {0})", + "AvatarChoose": "Choisir", + "AvatarSetBackgroundColor": "Choisir une couleur de fond", + "AvatarClose": "Fermer" +} diff --git a/Ryujinx.Ava/Assets/Locales/it_IT.json b/Ryujinx.Ava/Assets/Locales/it_IT.json new file mode 100644 index 000000000..a914b7dbe --- /dev/null +++ b/Ryujinx.Ava/Assets/Locales/it_IT.json @@ -0,0 +1,547 @@ +{ + "MenuBarFileOpenApplet": "Apri applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Apri l'editor Mii in modalità standalone", + "SettingsTabInputDirectMouseAccess": "Accesso diretto al mouse", + "SettingsTabSystemMemoryManagerMode": "Modalità di gestione della memoria:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Host (veloce)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Unchecked (più veloce, non sicura)", + "MenuBarFile": "_File", + "MenuBarFileOpenFromFile": "_Carica applicazione da un file", + "MenuBarFileOpenUnpacked": "Carica _gioco estratto", + "MenuBarFileOpenEmuFolder": "Apri cartella di Ryujinx", + "MenuBarFileOpenLogsFolder": "Apri cartella dei logs", + "MenuBarFileExit": "_Esci", + "MenuBarOptions": "Opzioni", + "MenuBarOptionsToggleFullscreen": "Schermo intero", + "MenuBarOptionsStartGamesInFullscreen": "Avvia i giochi a schermo intero", + "MenuBarOptionsStopEmulation": "Ferma emulazione", + "MenuBarOptionsSettings": "_Impostazioni", + "MenuBarOptionsManageUserProfiles": "_Gestisci i profili utente", + "MenuBarActions": "_Azioni", + "MenuBarOptionsSimulateWakeUpMessage": "Simula messaggio Wake-up", + "MenuBarActionsScanAmiibo": "Scansiona un Amiibo", + "MenuBarTools": "_Strumenti", + "MenuBarToolsInstallFirmware": "Installa firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Installa un firmware da file XCI o ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Installa un firmare da una cartella", + "MenuBarHelp": "Aiuto", + "MenuBarHelpCheckForUpdates": "Controlla aggiornamenti", + "MenuBarHelpAbout": "Informazioni", + "MenuSearch": "Cerca...", + "GameListHeaderFavorite": "Pref", + "GameListHeaderIcon": "Icona", + "GameListHeaderApplication": "Nome", + "GameListHeaderDeveloper": "Sviluppatore", + "GameListHeaderVersion": "Versione", + "GameListHeaderTimePlayed": "Tempo di gioco", + "GameListHeaderLastPlayed": "Giocato l'ultima volta", + "GameListHeaderFileExtension": "Estensione", + "GameListHeaderFileSize": "Dimensione file", + "GameListHeaderPath": "Percorso", + "GameListContextMenuOpenUserSaveDirectory": "Apri la cartella salvataggi dell'utente", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Apre la cartella che contiene i salvataggi di gioco dell'utente", + "GameListContextMenuOpenUserDeviceDirectory": "Apri la cartella dispositivo dell'utente", + "GameListContextMenuOpenUserDeviceDirectoryToolTip": "Apre la cartella che contiene i salvataggi di gioco del dispositivo", + "GameListContextMenuOpenUserBcatDirectory": "Apri la cartella BCAT dell'utente", + "GameListContextMenuOpenUserBcatDirectoryToolTip": "Apre la cartella che contiene i salvataggi BCAT dell'applicazione", + "GameListContextMenuManageTitleUpdates": "Gestisci aggiornamenti del gioco", + "GameListContextMenuManageTitleUpdatesToolTip": "Apre la finestra di gestione aggiornamenti del gioco", + "GameListContextMenuManageDlc": "Gestici DLC", + "GameListContextMenuManageDlcToolTip": "Apre la finestra di gestione DLC", + "GameListContextMenuOpenModsDirectory": "Apri cartella delle mods", + "GameListContextMenuOpenModsDirectoryToolTip": "Apre la cartella che contiene le mods dell'applicazione", + "GameListContextMenuCacheManagement": "Gestione della cache", + "GameListContextMenuCacheManagementPurgePptc": "Pulisci PPTC cache", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Elimina la PPTC cache dell'applicazione", + "GameListContextMenuCacheManagementPurgeShaderCache": "Pulisci shader cache", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Elimina la shader cache dell'applicazione", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Apri cartella PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Apre la cartella che contiene la PPTC cache dell'applicazione", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Apri cartella shader cache", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Apre la cartella che contiene la shader cache dell'applicazione", + "GameListContextMenuExtractData": "Estrai dati", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Estrae la sezione ExeFS dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "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)", + "StatusBarGamesLoaded": "{0}/{1} Giochi caricati", + "StatusBarSystemVersion": "Versione di sistema: {0}", + "Settings": "Impostazioni", + "SettingsTabGeneral": "Interfaccia utente", + "SettingsTabGeneralGeneral": "Generali", + "SettingsTabGeneralEnableDiscordRichPresence": "Attiva Discord Rich Presence", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Controlla aggiornamenti all'avvio", + "SettingsTabGeneralShowConfirmExitDialog": "Mostra dialogo \"Conferma Uscita\"", + "SettingsTabGeneralHideCursorOnIdle": "Nascondi cursore inattivo", + "SettingsTabGeneralGameDirectories": "Cartelle dei giochi", + "SettingsTabGeneralAdd": "Aggiungi", + "SettingsTabGeneralRemove": "Rimuovi", + "SettingsTabSystem": "Sistema", + "SettingsTabSystemCore": "Core", + "SettingsTabSystemSystemRegion": "Regione di sistema:", + "SettingsTabSystemSystemRegionJapan": "Giappone", + "SettingsTabSystemSystemRegionUSA": "Stati Uniti d'America", + "SettingsTabSystemSystemRegionEurope": "Europa", + "SettingsTabSystemSystemRegionAustralia": "Australia", + "SettingsTabSystemSystemRegionChina": "Cina", + "SettingsTabSystemSystemRegionKorea": "Corea", + "SettingsTabSystemSystemRegionTaiwan": "Taiwan", + "SettingsTabSystemSystemLanguage": "Lingua di sistema:", + "SettingsTabSystemSystemLanguageJapanese": "Giapponese", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Inglese americano", + "SettingsTabSystemSystemLanguageFrench": "Francese", + "SettingsTabSystemSystemLanguageGerman": "Tedesco", + "SettingsTabSystemSystemLanguageItalian": "Italiano", + "SettingsTabSystemSystemLanguageSpanish": "Spagnolo", + "SettingsTabSystemSystemLanguageChinese": "Cinese", + "SettingsTabSystemSystemLanguageKorean": "Coreano", + "SettingsTabSystemSystemLanguageDutch": "Olandese", + "SettingsTabSystemSystemLanguagePortuguese": "Portoghese", + "SettingsTabSystemSystemLanguageRussian": "Russo", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanese", + "SettingsTabSystemSystemLanguageBritishEnglish": "Inglese britannico", + "SettingsTabSystemSystemLanguageCanadianFrench": "Francese canadese", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Spagnolo latino americano", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Cinese semplificato", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Cinese tradizionale", + "SettingsTabSystemSystemTimeZone": "Fuso orario di sistema:", + "SettingsTabSystemSystemTime": "Data e ora di sistema:", + "SettingsTabSystemEnableVsync": "Attiva VSync", + "SettingsTabSystemEnablePptc": "Attiva PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Attiva controlli d'integrità FS", + "SettingsTabSystemAudioBackend": "Backend audio:", + "SettingsTabSystemAudioBackendDummy": "Dummy", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": " - Possono causare instabilità", + "SettingsTabSystemExpandDramSize": "Espandi dimensione DRAM a 6GB", + "SettingsTabSystemIgnoreMissingServices": "Ignora servizi mancanti", + "SettingsTabGraphics": "Grafica", + "SettingsTabGraphicsEnhancements": "Miglioramenti", + "SettingsTabGraphicsEnableShaderCache": "Attiva Shader Cache", + "SettingsTabGraphicsAnisotropicFiltering": "Filtro anisotropico:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Scala della risoluzione:", + "SettingsTabGraphicsResolutionScaleCustom": "Personalizzata (Non raccomandata)", + "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Rapporto d'aspetto:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Adatta alla finestra", + "SettingsTabGraphicsDeveloperOptions": "Opzioni da sviluppatore", + "SettingsTabGraphicsShaderDumpPath": "Percorso di dump degli shaders:", + "SettingsTabLogging": "Log", + "SettingsTabLoggingLogging": "Log", + "SettingsTabLoggingEnableLoggingToFile": "Salva i log su file", + "SettingsTabLoggingEnableStubLogs": "Attiva Stub Logs", + "SettingsTabLoggingEnableInfoLogs": "Attiva Info Logs", + "SettingsTabLoggingEnableWarningLogs": "Attiva Warning Logs", + "SettingsTabLoggingEnableErrorLogs": "Attiva Error Logs", + "SettingsTabLoggingEnableTraceLogs": "Attiva Trace Logs", + "SettingsTabLoggingEnableGuestLogs": "Attiva Guest Logs", + "SettingsTabLoggingEnableFsAccessLogs": "Attiva Fs Access Logs", + "SettingsTabLoggingFsGlobalAccessLogMode": "Modalità di log accesso globale Fs:", + "SettingsTabLoggingDeveloperOptions": "Opzioni da sviluppatore (AVVISO: Ridurrà le prestazioni)", + "SettingsTabLoggingOpenglLogLevel": "Livello di log OpenGL:", + "SettingsTabLoggingOpenglLogLevelNone": "Nessuno", + "SettingsTabLoggingOpenglLogLevelError": "Errore", + "SettingsTabLoggingOpenglLogLevelPerformance": "Rallentamenti", + "SettingsTabLoggingOpenglLogLevelAll": "Tutto", + "SettingsTabLoggingEnableDebugLogs": "Attiva logs di debug", + "SettingsTabInput": "Input", + "SettingsTabInputEnableDockedMode": "Attiva modalità TV", + "SettingsTabInputDirectKeyboardAccess": "Accesso diretto alla tastiera", + "SettingsButtonSave": "Salva", + "SettingsButtonClose": "Chiudi", + "SettingsButtonApply": "Applica", + "ControllerSettingsPlayer": "Giocatore", + "ControllerSettingsPlayer1": "Giocatore 1", + "ControllerSettingsPlayer2": "Giocatore 2", + "ControllerSettingsPlayer3": "Giocatore 3", + "ControllerSettingsPlayer4": "Giocatore 4", + "ControllerSettingsPlayer5": "Giocatore 5", + "ControllerSettingsPlayer6": "Giocatore 6", + "ControllerSettingsPlayer7": "Giocatore 7", + "ControllerSettingsPlayer8": "Giocatore 8", + "ControllerSettingsHandheld": "Portatile", + "ControllerSettingsInputDevice": "Dispositivo di input", + "ControllerSettingsRefresh": "Ricarica", + "ControllerSettingsDeviceDisabled": "Disabilitato", + "ControllerSettingsControllerType": "Tipo di controller", + "ControllerSettingsControllerTypeHandheld": "Portatile", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "Coppia di JoyCon", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon sinistro", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon destro", + "ControllerSettingsProfile": "Profilo", + "ControllerSettingsProfileDefault": "Predefinito", + "ControllerSettingsLoad": "Carica", + "ControllerSettingsAdd": "Aggiungi", + "ControllerSettingsRemove": "Rimuovi", + "ControllerSettingsButtons": "Pulsanti", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Croce direzionale", + "ControllerSettingsDPadUp": "Su", + "ControllerSettingsDPadDown": "Giù", + "ControllerSettingsDPadLeft": "Sinistra", + "ControllerSettingsDPadRight": "Destra", + "ControllerSettingsLStick": "Stick sinistro", + "ControllerSettingsLStickButton": "Pulsante", + "ControllerSettingsLStickUp": "Su", + "ControllerSettingsLStickDown": "Giù", + "ControllerSettingsLStickLeft": "Sinistra", + "ControllerSettingsLStickRight": "Destra", + "ControllerSettingsLStickStick": "Stick", + "ControllerSettingsLStickInvertXAxis": "Inverti stick X", + "ControllerSettingsLStickInvertYAxis": "Inverti stick Y", + "ControllerSettingsLStickDeadzone": "Zona morta:", + "ControllerSettingsRStick": "Stick destro", + "ControllerSettingsRStickButton": "Pulsante", + "ControllerSettingsRStickUp": "Su", + "ControllerSettingsRStickDown": "Giù", + "ControllerSettingsRStickLeft": "Sinistra", + "ControllerSettingsRStickRight": "Destra", + "ControllerSettingsRStickStick": "Stick", + "ControllerSettingsRStickInvertXAxis": "Inverti stick X", + "ControllerSettingsRStickInvertYAxis": "Inverti stick Y", + "ControllerSettingsRStickDeadzone": "Zona morta:", + "ControllerSettingsTriggersLeft": "Grilletto sinistro", + "ControllerSettingsTriggersRight": "Grilletto destro", + "ControllerSettingsTriggersButtonsLeft": "Pulsante dorsale sinistro", + "ControllerSettingsTriggersButtonsRight": "Pulsante dorsale destro", + "ControllerSettingsTriggers": "Grilletti", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Tasto sinitro", + "ControllerSettingsExtraButtonsRight": "Tasto destro", + "ControllerSettingsMisc": "Miscellanee", + "ControllerSettingsTriggerThreshold": "Sensibilità dei grilletti:", + "ControllerSettingsMotion": "Movimento", + "ControllerSettingsCemuHook": "CemuHook", + "ControllerSettingsMotionEnableMotionControls": "Attiva sensore di movimento", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Usa sensore compatibile con CemuHook", + "ControllerSettingsMotionControllerSlot": "Slot del controller:", + "ControllerSettingsMotionMirrorInput": "Input specchiato", + "ControllerSettingsMotionRightJoyConSlot": "Slot JoyCon destro:", + "ControllerSettingsMotionServerHost": "Server:", + "ControllerSettingsMotionGyroSensitivity": "Sensibilità del giroscopio:", + "ControllerSettingsMotionGyroDeadzone": "Zona morta del giroscopio:", + "ControllerSettingsSave": "Salva", + "ControllerSettingsClose": "Chiudi", + "UserProfilesSelectedUserProfile": "Profilo utente selezionato:", + "UserProfilesSaveProfileName": "Salva nome del profilo", + "UserProfilesChangeProfileImage": "Cambia immagine profilo", + "UserProfilesAvailableUserProfiles": "Profili utente disponibili:", + "UserProfilesAddNewProfile": "Aggiungi nuovo profilo", + "UserProfilesDeleteSelectedProfile": "Elimina il profilo selezionato", + "UserProfilesClose": "Chiudi", + "ProfileImageSelectionTitle": "Selezione dell'immagine profilo", + "ProfileImageSelectionHeader": "Scegli un'immagine profilo", + "ProfileImageSelectionNote": "Puoi importare un'immagine profilo personalizzata o selezionare un avatar dal firmware di sistema", + "ProfileImageSelectionImportImage": "Importa file immagine", + "ProfileImageSelectionSelectAvatar": "Seleziona avatar dal firmware", + "InputDialogTitle": "Input Dialog", + "InputDialogOk": "OK", + "InputDialogCancel": "Annulla", + "InputDialogAddNewProfileTitle": "Scegli il nome profilo", + "InputDialogAddNewProfileHeader": "Digita un nome profilo", + "InputDialogAddNewProfileSubtext": "(Lunghezza massima: {0})", + "AvatarChoose": "Scegli", + "AvatarSetBackgroundColor": "Imposta colore di sfondo", + "AvatarClose": "Chiudi", + "ControllerSettingsLoadProfileToolTip": "Carica profilo", + "ControllerSettingsAddProfileToolTip": "Aggiungi profilo", + "ControllerSettingsRemoveProfileToolTip": "Rimuovi profilo", + "ControllerSettingsSaveProfileToolTip": "Salva profilo", + "MenuBarFileToolsTakeScreenshot": "Fai uno screenshot", + "MenuBarFileToolsHideUi": "Nascondi Ui", + "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", + "ControllerSettingsMotionConfigureCemuHookSettings": "Configura movimento CemuHook", + "ControllerSettingsRumble": "Vibrazione", + "ControllerSettingsRumbleEnable": "Attiva vibrazione", + "ControllerSettingsRumbleStrongMultiplier": "Moltiplicatore vibrazione forte", + "ControllerSettingsRumbleWeakMultiplier": "Moltiplicatore vibrazione debole", + "DialogMessageSaveNotAvailableMessage": "Non ci sono dati di salvataggio per {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Vuoi creare dei dat di salvataggio per questo gioco?", + "DialogConfirmationTitle": "Ryujinx - Conferma", + "DialogUpdaterTitle": "Ryujinx - Updater", + "DialogErrorTitle": "Ryujinx - Errore", + "DialogWarningTitle": "Ryujinx - Avviso", + "DialogExitTitle": "Ryujinx - Uscita", + "DialogErrorMessage": "Ryujinx ha riscontrato un problema", + "DialogExitMessage": "Sei sicuro di voler chiudere Ryujinx?", + "DialogExitSubMessage": "Tutti i dati non salvati andranno persi!", + "DialogMessageCreateSaveErrorMessage": "C'è stato un errore durante la creazione dei dati di salvataggio: {0}", + "DialogMessageFindSaveErrorMessage": "C'è stato un errore durante la ricerca dei dati di salvataggio: {0}", + "FolderDialogExtractTitle": "Scegli una cartella in cui estrarre", + "DialogNcaExtractionMessage": "Estrazione della sezione {0} da {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Estrattore sezione NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "L'estrazione è fallita. L'NCA principale non era presente nel file selezionato.", + "DialogNcaExtractionCheckLogErrorMessage": "L'estrazione è fallita. Leggi il log per più informazioni.", + "DialogNcaExtractionSuccessMessage": "Estrazione completata con successo.", + "DialogUpdaterConvertFailedMessage": "La conversione dell'attuale versione di Ryujinx è fallita.", + "DialogUpdaterCancelUpdateMessage": "Annullando l'aggiornamento!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Stai già usando la versione più recente di Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Si è verificato un errore nel tentativo di ottenere informazioni da Github Release. Questo può verificarsi se una nuova release è in compilazione su GitHub Actions. Riprova tra qualche minuto.", + "DialogUpdaterConvertFailedGithubMessage": "La conversione della versione di Ryujinx ricevuta da Github Release è fallita.", + "DialogUpdaterDownloadingMessage": "Download dell'aggiornamento...", + "DialogUpdaterExtractionMessage": "Estrazione dell'aggiornamento...", + "DialogUpdaterRenamingMessage": "Rinominazione dell'aggiornamento...", + "DialogUpdaterAddingFilesMessage": "Aggiunta 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!", + "DialogUpdaterDirtyBuildSubMessage": "Scarica Ryujinx da https://ryujinx.org/ se stai cercando una versione supportata.", + "DialogRestartRequiredMessage": "Riavvio richiesto", + "DialogThemeRestartMessage": "Il tema è stato salvato. E' richiesto un riavvio per applicare un tema.", + "DialogThemeRestartSubMessage": "Vuoi riavviare?", + "DialogFirmwareInstallEmbeddedMessage": "Vuoi installare il firmware incorporato in questo gioco? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Non è stato trovato alcun firmware installato, ma Ryujinx è riuscito di installare il firmware {0} dal gioco fornito.\nL'emulatore si avvierà adesso.", + "DialogFirmwareNoFirmwareInstalledMessage": "Nessun firmware installato", + "DialogFirmwareInstalledMessage": "Il firmware {0} è stato installato", + "DialogOpenSettingsWindowLabel": "Apri finestra delle impostazioni", + "DialogControllerAppletTitle": "Applet del controller", + "DialogMessageDialogErrorExceptionMessage": "Errore nella visualizzazione del Message Dialog: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Errore nella visualizzazione della tastiera software: {0}", + "DialogErrorAppletErrorExceptionMessage": "Errore nella visualizzazione dell'ErrorApplet Dialog: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nPer più informazioni su come risolvere questo errore, segui la nostra guida all'installazione.", + "DialogUserErrorDialogTitle": "Errore di Ryujinx ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Si è verificato un errore durante il recupero delle informazioni dall'API.", + "DialogAmiiboApiConnectErrorMessage": "Impossibile connettersi al server Amiibo API. Il servizio potrebbe essere fuori uso o potresti dover verificare che la tua connessione internet sia online.", + "DialogProfileInvalidProfileErrorMessage": "Il profilo {0} è incompatibile con l'attuale sistema di configurazione input.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Il profilo predefinito non può essere sovrascritto", + "DialogProfileDeleteProfileTitle": "Eliminazione profilo", + "DialogProfileDeleteProfileMessage": "Quest'azione è irreversibile, sei sicuro di voler continuare?", + "DialogWarning": "Avviso", + "DialogPPTCDeletionMessage": "Stai per eliminare la PPTC cache per :\n\n{0}\n\nSei sicuro di voler proseguire?", + "DialogPPTCDeletionErrorMessage": "Errore nell'eliminazione della PPTC cache a {0}: {1}", + "DialogShaderDeletionMessage": "Stai per eliminare la Shader cache per :\n\n{0}\n\nSei sicuro di voler proseguire?", + "DialogShaderDeletionErrorMessage": "Errore nell'eliminazione della Shader cache a {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx ha incontrato un errore", + "DialogInvalidTitleIdErrorMessage": "Errore UI: Il gioco selezionato non ha un title ID valido", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Un firmware di sistema valido non è stato trovato in {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Installa firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "La versione di sistema {0} sarà installata.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nQuesta sostituirà l'attuale versione di sistema {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nVuoi continuare?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Installazione del firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "La versione di sistema {0} è stata installata.", + "DialogUserProfileDeletionWarningMessage": "Non ci sarebbero altri profili da aprire se il profilo selezionato viene cancellato", + "DialogUserProfileDeletionConfirmMessage": "Vuoi eliminare il profilo selezionato?", + "DialogControllerSettingsModifiedConfirmMessage": "Le attuali impostazioni del controller sono state aggiornate.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Vuoi salvare?", + "DialogDlcLoadNcaErrorMessage": "{0}. File errato: {1}", + "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?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Hai abilitato lo shader dumping, che è progettato per essere usato solo dagli sviluppatori.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Per prestazioni ottimali, si raccomanda di disabilitare lo shader dumping. Vuoi disabilitare lo shader dumping adesso?", + "DialogLoadAppGameAlreadyLoadedMessage": "Un gioco è già stato caricato", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Ferma l'emulazione o chiudi l'emulatore prima di avviare un altro gioco.", + "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.", + "SettingsTabGraphicsFeaturesOptions": "Funzionalità", + "SettingsTabGraphicsBackendMultithreading": "Graphics Backend Multithreading", + "CommonAuto": "Auto", + "CommonOff": "Spento", + "CommonOn": "Acceso", + "InputDialogYes": "Si", + "InputDialogNo": "No", + "DialogProfileInvalidProfileNameErrorMessage": "Il nome del file contiene caratteri non validi. Riprova.", + "MenuBarOptionsPauseEmulation": "Pausa", + "MenuBarOptionsResumeEmulation": "Riprendi", + "AboutUrlTooltipMessage": "Clicca per aprire il sito web di Ryujinx nel tuo browser predefinito.", + "AboutDisclaimerMessage": "Ryujinx non è affiliato con Nintendo™,\no i suoi partner, in alcun modo.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) è usata\nnella nostra emulazione Amiibo.", + "AboutPatreonUrlTooltipMessage": "Clicca per aprire la pagina Patreon di Ryujinx nel tuo browser predefinito.", + "AboutGithubUrlTooltipMessage": "Clicca per aprire la pagina GitHub di Ryujinx nel tuo browser predefinito.", + "AboutDiscordUrlTooltipMessage": "Clicca per aprire un invito al server Discord di Ryujinx nel tuo browser predefinito.", + "AboutTwitterUrlTooltipMessage": "Clicca per aprire la pagina Twitter di Ryujinx nel tuo browser predefinito.", + "AboutRyujinxAboutTitle": "Informazioni:", + "AboutRyujinxAboutContent": "Ryujinx è un emulatore per la Nintendo Switch™.\nPer favore supportaci su Patreon.\nRicevi tutte le ultime notizie sul nostro Twitter o su Discord.\nGli sviluppatori interessati a contribuire possono trovare più informazioni sul nostro GitHub o Discord.", + "AboutRyujinxMaintainersTitle": "Mantenuto da:", + "AboutRyujinxMaintainersContentTooltipMessage": "Clicca per aprire la pagina dei Contributors nel tuo browser predefinito.", + "AboutRyujinxSupprtersTitle": "Supportato su Patreon da:", + "AmiiboSeriesLabel": "Serie Amiibo", + "AmiiboCharacterLabel": "Personaggio", + "AmiiboScanButtonLabel": "Scannerizza", + "AmiiboOptionsShowAllLabel": "Mostra tutti gli amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack: Usa un tag uuid casuale", + "DlcManagerTableHeadingEnabledLabel": "Abilitato", + "DlcManagerTableHeadingTitleIdLabel": "Title ID", + "DlcManagerTableHeadingContainerPathLabel": "Percorso del contenitore", + "DlcManagerTableHeadingFullPathLabel": "Percorso completo", + "DlcManagerRemoveAllButton": "Rimuovi tutti", + "MenuBarOptionsChangeLanguage": "Cambia lingua", + "CommonSort": "Ordina", + "CommonShowNames": "Mostra nomi", + "CommonFavorite": "Preferito", + "OrderAscending": "Crescente", + "OrderDescending": "Decrescente", + "SettingsTabGraphicsFeatures": "Funzionalità", + "ErrorWindowTitle": "Finestra errore", + "ToggleDiscordTooltip": "Attiva o disattiva Discord Rich Presence", + "AddGameDirBoxTooltip": "Inserisci la directory di un gioco per aggiungerlo alla lista", + "AddGameDirTooltip": "Aggiungi la directory di un gioco alla lista", + "RemoveGameDirTooltip": "Rimuovi la directory di gioco selezionata", + "CustomThemeCheckTooltip": "Attiva o disattiva temi personalizzati nella GUI", + "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)", + "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", + "PptcToggleTooltip": "Attiva o disattiva PPTC", + "FsIntegrityToggleTooltip": "Attiva controlli d'integrità sui file dei contenuti di gioco", + "AudioBackendTooltip": "Cambia backend audio", + "MemoryManagerTooltip": "Cambia il modo in cui la memoria guest è mappata e vi si accede. Influisce notevolmente sulle prestazioni della CPU emulata.", + "MemoryManagerSoftwareTooltip": "Usa una software page table per la traduzione degli indirizzi. Massima precisione ma prestazioni più lente.", + "MemoryManagerHostTooltip": "Mappa direttamente la memoria nello spazio degli indirizzi dell'host. Compilazione ed esecuzione JIT molto più veloce.", + "MemoryManagerUnsafeTooltip": "Mappa direttamente la memoria, ma non maschera l'indirizzo all'interno dello spazio degli indirizzi guest prima dell'accesso. Più veloce, ma a costo della sicurezza. L'applicazione guest può accedere alla memoria da qualsiasi punto di Ryujinx, quindi esegui solo programmi di cui ti fidi con questa modalità.", + "DRamTooltip": "Espande l'ammontare di memoria sul sistema emulato da 4GB A 6GB", + "IgnoreMissingServicesTooltip": "Attiva o disattiva l'opzione di ignorare i servizi mancanti", + "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", + "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.", + "ShaderDumpPathTooltip": "Percorso di dump Graphics Shaders", + "FileLogTooltip": "Attiva o disattiva il logging su file", + "StubLogTooltip": "Attiva messaggi stub log", + "InfoLogTooltip": "Attiva messaggi info log", + "WarnLogTooltip": "Attiva messaggi warning log", + "ErrorLogTooltip": "Attiva messaggi error log", + "TraceLogTooltip": "Attiva messaggi trace log", + "GuestLogTooltip": "Attiva messaggi guest log", + "FileAccessLogTooltip": "Attiva messaggi file access log", + "FSAccessLogModeTooltip": "Attiva output FS access log alla console. Le mpdalità possibili sono 0-3", + "DeveloperOptionTooltip": "Usa con attenzione", + "OpenGlLogLevel": "Richiede livelli di log appropriati abilitati", + "DebugLogTooltip": "Attiva messaggi debug log", + "LoadApplicationFileTooltip": "Apri un file explorer per scegliere un file compatibile Switch da caricare", + "LoadApplicationFolderTooltip": "Apri un file explorer per scegliere un file compatibile Switch, applicazione sfusa da caricare", + "OpenRyujinxFolderTooltip": "Apri la cartella del filesystem di Ryujinx", + "OpenRyujinxLogsTooltip": "Apre la cartella dove vengono scritti i log", + "ExitTooltip": "Esci da Ryujinx", + "OpenSettingsTooltip": "Apri finestra delle impostazioni", + "OpenProfileManagerTooltip": "Apri la finestra di gestione dei profili utente", + "StopEmulationTooltip": "Ferma l'emulazione del gioco attuale e torna alla selezione dei giochi", + "CheckUpdatesTooltip": "Controlla la presenza di aggiornamenti di Ryujinx", + "OpenAboutTooltip": "Apri finestra delle informazioni", + "GridSize": "Dimensione griglia", + "GridSizeTooltip": "Cambia la dimensione dei riquardi della griglia", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portoghese Brasiliano", + "AboutRyujinxContributorsButtonHeader": "Vedi tutti i Contributors", + "SettingsTabSystemAudioVolume": "Volume: ", + "AudioVolumeTooltip": "Cambia volume audio", + "SettingsTabSystemEnableInternetAccess": "Attiva Guest Internet Access", + "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", + "ControllerSettingsStickRange": "Raggio", + "DialogStopEmulationTitle": "Ryujinx - Ferma emulazione", + "DialogStopEmulationMessage": "Sei sicuro di voler fermare l'emulazione?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Audio", + "SettingsTabNetwork": "Rete", + "SettingsTabNetworkConnection": "Connessione di rete", + "SettingsTabGraphicsFrameRate": "Frequenza di aggiornamento dell'host:", + "SettingsTabGraphicsFrameRateTooltip": "Imposta la frequenza di aggiornamento dell'host. Imposta a 0 per rimuovere il limite.", + "SettingsTabCpuCache": "Cache CPU", + "SettingsTabCpuMemory": "Memoria CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Per favore aggiorna Ryujinx via FlatHub.", + "UpdaterDisabledWarningTitle": "Updater disabilitato!", + "GameListContextMenuOpenSdModsDirectory": "Apri cartella delle mods Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Apre la cartella aternativa di atmosphere che contiene le mods dell'applicazione", + "ControllerSettingsRotate90": "Ruota in senso orario di 90°", + "IconSize": "Dimensioni icona", + "IconSizeTooltip": "Cambia le dimensioni dell'icona di un gioco", + "MenuBarOptionsShowConsole": "Mostra console", + "ShaderCachePurgeError" : "Errore nella pulizia della shader cache a {0}: {1}", + "UserErrorNoKeys": "Chiavi non trovate", + "UserErrorNoFirmware": "Firmware non trovato", + "UserErrorFirmwareParsingFailed": "Errori di analisi del firmware", + "UserErrorApplicationNotFound": "Applicazione non trovata", + "UserErrorUnknown": "Errore sconosciuto", + "UserErrorUndefined": "Errore non definito", + "UserErrorNoKeysDescription": "Ryujinx non è riuscito a trovare il file 'prod.keys'", + "UserErrorNoFirmwareDescription": "Ryujinx non è riuscito a trovare alcun firmware installato", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx non è riuscito ad analizzare il firmware. Questo di solito è causato da chiavi non aggiornate.", + "UserErrorApplicationNotFoundDescription": "Ryujinx non è riuscito a trovare un'applicazione valida al percorso specificato.", + "UserErrorUnknownDescription": "Si è verificato un errore sconosciuto!", + "UserErrorUndefinedDescription": "Si è verificato un errore sconosciuto! Non dovrebbe succedere, per favore contatta uno sviluppatore!", + "OpenSetupGuideMessage": "Apri la guida all'installazione", + "NoUpdate": "Nessun aggiornamento", + "TitleUpdateVersionLabel": "Versione {0} - {1}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Conferma", + "FileDialogAllTypes": "Tutti i tipi", + "Never": "Mai", + "SwkbdMinCharacters": "Non può avere meno di {0} caratteri", + "SwkbdMinRangeCharacters": "Può avere da {0} a {1} caratteri", + "SoftwareKeyboard": "Tastiera software", + "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", + "UpdaterRenaming": "Rinominazione dei vecchi files...", + "UpdaterRenameFailed": "L'updater non è riuscito a rinominare il file: {0}", + "UpdaterAddingFiles": "Aggiunta nuovi files...", + "UpdaterExtracting": "Estrazione aggiornamento...", + "UpdaterDownloading": "Download aggiornamento...", + "Game": "Gioco", + "Docked": "TV", + "Handheld": "Portatile", + "ConnectionError": "Errore di connessione.", + "AboutPageDeveloperListMore": "{0} e altri ancora...", + "ApiError": "Errore dell'API.", + "LoadingHeading": "Caricamento di {0}", + "CompilingPPTC": "Compilazione PTC", + "CompilingShaders": "Compilazione Shaders" +} diff --git a/Ryujinx.Ava/Assets/Locales/ko_KR.json b/Ryujinx.Ava/Assets/Locales/ko_KR.json new file mode 100644 index 000000000..9bae69dcd --- /dev/null +++ b/Ryujinx.Ava/Assets/Locales/ko_KR.json @@ -0,0 +1,497 @@ +{ + "MenuBarFileOpenApplet": "애플릿 열기", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "독립 실행형 모드에서 Mii 편집기 애플릿 열기", + "SettingsTabInputDirectMouseAccess": "직접 마우스 액세스", + "SettingsTabSystemMemoryManagerMode": "메모리 관리자 모드 :", + "SettingsTabSystemMemoryManagerModeSoftware": "소프트웨어", + "SettingsTabSystemMemoryManagerModeHost": "호스트 (빠른)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "호스트가 확인되지 않음 (가장 빠르고 안전하지 않은)", + "MenuBarFile": "_파일", + "MenuBarFileOpenFromFile": "_서류철음에서 애플리케이션 로드", + "MenuBarFileOpenUnpacked": "_압축을 푼 게임 로드", + "MenuBarFileOpenEmuFolder": "Ryujinx 폴더 열기", + "MenuBarFileOpenLogsFolder": "로그 폴더 열기", + "MenuBarFileExit": "_그만두", + "MenuBarOptions": "옵션", + "MenuBarOptionsToggleFullscreen": "전체 화면 전환", + "MenuBarOptionsStartGamesInFullscreen": "전체 화면 모드에서 게임 열기", + "MenuBarOptionsStopEmulation": "에뮬레이션 중지", + "MenuBarOptionsSettings": "_조절", + "MenuBarOptionsManageUserProfiles": "사용자 프로필 _관리", + "MenuBarActions": "_행위", + "MenuBarOptionsSimulateWakeUpMessage": "깨우기 명령어 시뮬레이션", + "MenuBarActionsScanAmiibo": "Amiibo 스캔", + "MenuBarTools": "_도구", + "MenuBarToolsInstallFirmware": "펌웨어 설치", + "MenuBarFileToolsInstallFirmwareFromFile": "XCI 또는 ZIP에서 펌웨어 설치", + "MenuBarFileToolsInstallFirmwareFromDirectory": "디렉토리에서 펌웨어 설치", + "MenuBarHelp": "돕다", + "MenuBarHelpCheckForUpdates": "업데이트 확인", + "MenuBarHelpAbout": "통지", + "MenuSearch": "찾다···", + "GameListHeaderFavorite": "즐겨찾기", + "GameListHeaderIcon": "상", + "GameListHeaderApplication": "이름", + "GameListHeaderDeveloper": "개발자", + "GameListHeaderVersion": "버전", + "GameListHeaderTimePlayed": "플레이 시간", + "GameListHeaderLastPlayed": "해본 마지막", + "GameListHeaderFileExtension": "파일 확장자", + "GameListHeaderFileSize": "파일 크기", + "GameListHeaderPath": "파일 경로", + "GameListContextMenuOpenUserSaveDirectory": "사용자 저장 폴더 열기", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "응용 프로그램의 사용자 저장이 포함된 폴더를 엽니다", + "GameListContextMenuOpenUserDeviceDirectory": "사용자 장치 폴더 열기", + "GameListContextMenuOpenUserDeviceDirectoryToolTip": "응용 프로그램의 장치 저장이 포함된 폴더를 엽니다", + "GameListContextMenuOpenUserBcatDirectory": "사용자의 BCAT 폴더 열기", + "GameListContextMenuOpenUserBcatDirectoryToolTip": "응용 프로그램의 BCAT 저장이 포함된 폴더를 엽니다", + "GameListContextMenuManageTitleUpdates": "타이틀 업데이트 관리s", + "GameListContextMenuManageTitleUpdatesToolTip": "타이틀 업데이트 관리 창 열기", + "GameListContextMenuManageDlc": "DLC 관리", + "GameListContextMenuManageDlcToolTip": "DLC 관리 창 열기", + "GameListContextMenuOpenModsDirectory": "모드 디렉토리 열기", + "GameListContextMenuOpenModsDirectoryToolTip": "응용 프로그램의 모드가들 포함된 디렉터리를 엽니다", + "GameListContextMenuCacheManagement": "캐시 관리", + "GameListContextMenuCacheManagementPurgePptc": "PPTC 캐시 제거", + "GameListContextMenuCacheManagementPurgePptcToolTip": "응용 프로그램 PPTC 캐시 삭제", + "GameListContextMenuCacheManagementPurgeShaderCache": "셰이더 캐시 제거", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "애플리케이션 셰이더 캐시를 삭제합니다.", + "GameListContextMenuCacheManagementOpenPptcDirectory": "PPTC 디렉토리 열기", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "응용 프로그램 PPTC 캐시가 포함된 디렉터리를 엽니다", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "셰이더 캐시 디렉토리 열기", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "응용 프로그램 셰이더 캐시가 포함된 디렉터리를 엽니다", + "GameListContextMenuExtractData": "데이터 추출", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "애플리케이션의 현재 구성에서 ExeFS 추출 (업데이트 포함)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "애플리케이션의 현재 구성에서 RomFS 추출 (업데이트 포함)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "애플리케이션의 현재 구성에서 로고 섹션 추출 (업데이트 포함)", + "StatusBarGamesLoaded": "로드된 {0}/{1}개의 게임", + "StatusBarSystemVersion": "시스템 버전 : {0}", + "Settings": "조절", + "SettingsTabGeneral": "사용자 인터페이스", + "SettingsTabGeneralGeneral": "일반", + "SettingsTabGeneralEnableDiscordRichPresence": "Discord Rich Presence 활성화", + "SettingsTabGeneralCheckUpdatesOnLaunch": "열 때 업데이트 확인", + "SettingsTabGeneralShowConfirmExitDialog": "\"종료 확인\" 대화 상자 표시", + "SettingsTabGeneralHideCursorOnIdle": "유휴 상태에서 커서 숨기기", + "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": "수직 동기화 사용", + "SettingsTabSystemEnablePptc": "PPTC(Profiled Persistent Translation Cache) 활성화", + "SettingsTabSystemEnableFsIntegrityChecks": "FS 무결성 검사 활성화", + "SettingsTabSystemAudioBackend": "오디오 백엔드 :", + "SettingsTabSystemAudioBackendDummy": "Dummy", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "해킹", + "SettingsTabSystemHacksNote": " - 불안정을 일으킬 수 있음", + "SettingsTabSystemExpandDramSize": "DRAM 크기를 6GB로 확장", + "SettingsTabSystemIgnoreMissingServices": "누락된 서비스 무시", + "SettingsTabGraphics": "제도법", + "SettingsTabGraphicsEnhancements": "개선 사항", + "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": "오류 로그 켜기", + "SettingsTabLoggingEnableGuestLogs": "게스트 로그 켜기", + "SettingsTabLoggingEnableFsAccessLogs": "Fs 액세스 로그 켜기", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs 전역 액세스 로그 모드 :", + "SettingsTabLoggingDeveloperOptions": "개발자 옵션 (경고 : 성능이 저하됩니다.)", + "SettingsTabLoggingOpenglLogLevel": "OpenGL 로그 수준 :", + "SettingsTabLoggingOpenglLogLevelNone": "없음", + "SettingsTabLoggingOpenglLogLevelError": "오류", + "SettingsTabLoggingOpenglLogLevelPerformance": "감속", + "SettingsTabLoggingOpenglLogLevelAll": "모두", + "SettingsTabLoggingEnableDebugLogs": "디버그 로그 사용", + "SettingsTabInput": "입력", + "SettingsTabInputEnableDockedMode": "도킹 모드 활성화", + "SettingsTabInputDirectKeyboardAccess": "직접 키보드 액세스", + "SettingsButtonSave": "구하다", + "SettingsButtonClose": "출구", + "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": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon 페어", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon 왼쪽", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon 오른쪽", + "ControllerSettingsProfile": "프로필", + "ControllerSettingsProfileDefault": "기본", + "ControllerSettingsLoad": "짐", + "ControllerSettingsAdd": "추가하다", + "ControllerSettingsRemove": "제거하다", + "ControllerSettingsButtons": "버튼", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Directional Pad", + "ControllerSettingsDPadUp": "위로", + "ControllerSettingsDPadDown": "아래에", + "ControllerSettingsDPadLeft": "왼쪽", + "ControllerSettingsDPadRight": "오른쪽", + "ControllerSettingsLStick": "왼쪽 스틱", + "ControllerSettingsLStickButton": "단추", + "ControllerSettingsLStickUp": "위로", + "ControllerSettingsLStickDown": "아래에", + "ControllerSettingsLStickLeft": "왼쪽", + "ControllerSettingsLStickRight": "오른쪽", + "ControllerSettingsLStickStick": "막대", + "ControllerSettingsLStickInvertXAxis": "스틱 X축 반전", + "ControllerSettingsLStickInvertYAxis": "스틱 Y축 반전", + "ControllerSettingsLStickDeadzone": "데드 존 :", + "ControllerSettingsRStick": "오른쪽 스틱", + "ControllerSettingsRStickButton": "단추", + "ControllerSettingsRStickUp": "위로", + "ControllerSettingsRStickDown": "아래에", + "ControllerSettingsRStickLeft": "왼쪽", + "ControllerSettingsRStickRight": "오른쪽", + "ControllerSettingsRStickStick": "Stick", + "ControllerSettingsRStickInvertXAxis": "스틱 X축 반전", + "ControllerSettingsRStickInvertYAxis": "스틱 Y축 반전", + "ControllerSettingsRStickDeadzone": "데드 존 :", + "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": "운동", + "ControllerSettingsCemuHook": "CemuHook", + "ControllerSettingsMotionEnableMotionControls": "모션 컨트롤 활성화", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook 호환 모션 사용", + "ControllerSettingsMotionControllerSlot": "컨트롤러 슬롯 :", + "ControllerSettingsMotionMirrorInput": "미러 입력", + "ControllerSettingsMotionRightJoyConSlot": "오른쪽 JoyCon 슬롯 :", + "ControllerSettingsMotionServerHost": "서버 호스트 :", + "ControllerSettingsMotionGyroSensitivity": "자이로 감도 :", + "ControllerSettingsMotionGyroDeadzone": "자이로 데드존 :", + "ControllerSettingsSave": "구하다", + "ControllerSettingsClose": "출구", + "UserProfilesSelectedUserProfile": "선택한 사용자 프로필 :", + "UserProfilesSaveProfileName": "프로필 이름 저장", + "UserProfilesChangeProfileImage": "프로필 이미지 변경", + "UserProfilesAvailableUserProfiles": "사용 가능한 사용자 프로필 :", + "UserProfilesAddNewProfile": "새 프로필 추가", + "UserProfilesDeleteSelectedProfile": "선택한 프로필 삭제", + "UserProfilesClose": "출구", + "ProfileImageSelectionTitle": "프로필 이미지 선택", + "ProfileImageSelectionHeader": "이미지 선택", + "ProfileImageSelectionNote": "사용자 정의 프로필 이미지를 사용하거나 시스템 펌웨어에서 하나를 선택할 수 있습니다", + "ProfileImageSelectionImportImage": "이미지 파일 가져오기", + "ProfileImageSelectionSelectAvatar": "펌웨어 아바타 선택", + "InputDialogTitle": "입력 대화 상자", + "InputDialogOk": "확인", + "InputDialogCancel": "취소", + "InputDialogAddNewProfileTitle": "프로필 이름 선택", + "InputDialogAddNewProfileHeader": "프로필 이름을 입력하세요", + "InputDialogAddNewProfileSubtext": "최대 길이 : {0})", + "AvatarChoose": "선택하다", + "AvatarSetBackgroundColor": "배경색 설정", + "AvatarClose": "출구", + "ControllerSettingsLoadProfileToolTip": "프로필 로드", + "ControllerSettingsAddProfileToolTip": "프로필 추가", + "ControllerSettingsRemoveProfileToolTip": "프로필 제거", + "ControllerSettingsSaveProfileToolTip": "프로필 저장", + "MenuBarFileToolsTakeScreenshot": "스크린 샷을 찍다", + "MenuBarFileToolsHideUi": "사용자 인터페이스 숨기기", + "GameListContextMenuToggleFavorite": "즐겨찾기 설정", + "GameListContextMenuToggleFavoriteToolTip": "이 게임이 즐겨찾기인지 여부를 전환합니다", + "SettingsTabGeneralTheme": "테마", + "SettingsTabGeneralThemeCustomTheme": "사용자 정의 테마 경로", + "SettingsTabGeneralThemeBaseStyle": "기본 스타일", + "SettingsTabGeneralThemeBaseStyleDark": "어두운", + "SettingsTabGeneralThemeBaseStyleLight": "빛", + "SettingsTabGeneralThemeEnableCustomTheme": "사용자 정의 테마 활성화", + "ButtonBrowse": "검색", + "ControllerSettingsMotionConfigureCemuHookSettings": "CemuHook 모션 구성", + "ControllerSettingsRumble": "하인 좌석", + "ControllerSettingsRumbleEnable": "럼블을 활성화", + "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": "{1}에서 섹션 {0} 추출···", + "DialogNcaExtractionTitle": "Ryujinx - NCA 섹션 추출기", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "추출 실패. 선택한 파일에 기본 NCA가 없습니다.", + "DialogNcaExtractionCheckLogErrorMessage": "추출 실패. 자세한 내용은 로그 파일을 읽으십시오.", + "DialogNcaExtractionSuccessMessage": "추출이 성공적으로 완료되었습니다.", + "DialogUpdaterConvertFailedMessage": "현재 Ryujinx 버전을 변환하지 못했습니다.", + "DialogUpdaterCancelUpdateMessage": "업데이트 취소 중!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "이미 최신 버전의 Ryujinx를 사용하고 있습니다!", + "DialogUpdaterFailedToGetVersionMessage": "Github 릴리스에서 릴리스 정보를 가져오는 중에 오류가 발생했습니다. 이는 GitHub Actions에서 새 릴리스를 컴파일하는 경우 발생할 수 있습니다. 몇 분 후에 다시 시도하십시오.", + "DialogUpdaterConvertFailedGithubMessage": "Github Release에서 받은 Ryujinx 버전을 변환하지 못했습니다.", + "DialogUpdaterDownloadingMessage": "업데이트 다운로드 중···", + "DialogUpdaterExtractionMessage": "업데이트 추출···", + "DialogUpdaterRenamingMessage": "업데이트 이름 바꾸기···", + "DialogUpdaterAddingFilesMessage": "새 업데이트 추가···", + "DialogUpdaterCompleteMessage": "업데이트 완료!", + "DialogUpdaterRestartMessage": "지금 Ryujinx를 다시 시작하시겠습니까?", + "DialogUpdaterArchNotSupportedMessage": "지원되는 시스템 아키텍처를 실행하고 있지 않습니다!", + "DialogUpdaterArchNotSupportedSubMessage": "(x86 시스템만 지원됩니다!)", + "DialogUpdaterNoInternetMessage": "인터넷에 연결되어 있지 않습니다!", + "DialogUpdaterNoInternetSubMessage": "인터넷 연결이 작동하는지 확인하십시오!", + "DialogUpdaterDirtyBuildMessage": "Ryujinx의 더러운 빌드는 업데이트할 수 없습니다!", + "DialogUpdaterDirtyBuildSubMessage": "지원되는 버전을 찾고 있다면 https://ryujinx.org/에서 Ryujinx를 다운로드하십시오.", + "DialogRestartRequiredMessage": "재시작 필요", + "DialogThemeRestartMessage": "테마가 저장되었습니다. 테마를 적용하려면 다시 시작해야 합니다.", + "DialogThemeRestartSubMessage": "다시 시작하시겠습니까?", + "DialogFirmwareInstallEmbeddedMessage": "이 게임에 내장된 펌웨어를 설치하시겠습니까? (펌웨어 {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "설치된 펌웨어를 찾을 수 없지만 Ryujinx는 제공된 게임에서 펌웨어 {0} 설치할 수 있었습니다.\n이제 에뮬레이터가 시작됩니다.", + "DialogFirmwareNoFirmwareInstalledMessage": "펌웨어가 설치되지 않았습니다", + "DialogFirmwareInstalledMessage": "펌웨어 {0} 설치되었습니다.", + "DialogOpenSettingsWindowLabel": "설정 창 열기", + "DialogControllerAppletTitle": "컨트롤러 애플릿", + "DialogMessageDialogErrorExceptionMessage": "메시지 대화 상자를 표시하는 동안 오류가 발생했습니다 : {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "소프트웨어 키보드를 표시하는 동안 오류가 발생했습니다 : {0}", + "DialogErrorAppletErrorExceptionMessage": "ErrorApplet 대화 상자를 표시하는 동안 오류가 발생했습니다 : {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\n이 오류를 수정하는 방법에 대한 자세한 내용은 설정 가이드를 따르십시오", + "DialogUserErrorDialogTitle": "Ryuijnx의 오류 ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "API에서 정보를 가져오는 동안 오류가 발생했습니다.", + "DialogAmiiboApiConnectErrorMessage": "Amiibo API 서버에 연결할 수 없습니다. 서비스가 다운되었거나 인터넷 연결이 오프라인인지 확인해야 할 수 있습니다.", + "DialogProfileInvalidProfileErrorMessage": "프로필 AAA는 현재 입력 구성 시스템과 호환되지 않습니다.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "기본 프로필을 덮어쓸 수 없습니다", + "DialogProfileDeleteProfileTitle": "프로필 삭제", + "DialogProfileDeleteProfileMessage": "이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?", + "DialogWarning": "경고", + "DialogPPTCDeletionMessage": "{0}에\n\n 대한 PPTC 캐시를 삭제하려고 합니다.\n\n계속하시겠습니까?", + "DialogPPTCDeletionErrorMessage": "{0} : {1}에서 PPTC 캐시를 제거하는 동안 오류가 발생했습니다.", + "DialogShaderDeletionMessage": "{0}에\n\n 대한 셰이더 캐시를 삭제하려고 합니다.\n\n계속하시겠습니까?", + "DialogShaderDeletionErrorMessage": "{0} : {1}에서 셰이더 캐시를 제거하는 동안 오류가 발생했습니다.", + "DialogRyujinxErrorMessage": "Ryujinx에 오류가 발생했습니다", + "DialogInvalidTitleIdErrorMessage": "UI 오류 : 선택한 게임에 유효한 제목 ID가 없습니다", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "{0}에서 유효한 시스템 펌웨어를 찾을 수 없습니다.", + "DialogFirmwareInstallerFirmwareInstallTitle": "펌웨어 {0} 설치", + "DialogFirmwareInstallerFirmwareInstallMessage": "시스템 버전 {0}가 설치되려고 합니다", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n이것은 현재 설치된 버전 {0}를 대체합니다.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n계속하시겠습니까?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "펌웨어 설치···", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "시스템 버전 {0}가 성공적으로 설치되었습니다.", + "DialogUserProfileDeletionWarningMessage": "선택한 프로필이 삭제되면 사용 가능한 다른 프로필이 없습니다", + "DialogUserProfileDeletionConfirmMessage": "선택한 프로필을 삭제하시겠습니까", + "DialogControllerSettingsModifiedConfirmMessage": "현재 컨트롤러 설정이 업데이트되었습니다.", + "DialogControllerSettingsModifiedConfirmSubMessage": "저장하시겠습니까?", + "DialogDlcLoadNcaErrorMessage": "{0}. 오류 파일 : {1}", + "DialogDlcNoDlcErrorMessage": "지정한 파일에 선택한 타이틀의 DLC가 없습니다!", + "DialogPerformanceCheckLoggingEnabledMessage": "개발자 전용으로 설계된 디버그 로깅을 활성화했습니다.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "최적의 성능을 위해 디버그 로깅을 비활성화하는 것이 좋습니다. 지금 디버그 로깅을 비활성화하시겠습니까?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Y개발자만 사용하도록 설계된 셰이더 덤핑이 활성화되어 있습니다.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "최적의 성능을 위해 셰이더 덤핑을 비활성화하는 것이 좋습니다. 지금 셰이더 덤핑을 비활성화하시겠습니까?", + "DialogLoadAppGameAlreadyLoadedMessage": "다른 게임이 이미 로드되었습니다", + "DialogLoadAppGameAlreadyLoadedSubMessage": "다른 게임을 시작하기 전에 에뮬레이션을 중지하거나 에뮬레이터를 닫으십시오.", + "DialogUpdateAddUpdateErrorMessage": "파일에 선택한 제목에 대한 업데이트가 포함되어 있지 않습니다!", + "DialogSettingsBackendThreadingWarningTitle": "경고 - 백엔드 스레딩", + "DialogSettingsBackendThreadingWarningMessage": "변경 사항을 완전히 적용하려면 이 옵션을 변경한 후 Ryujinx를 다시 시작해야 합니다. 플랫폼에 따라 Ryujinx를 사용할 때 드라이버 자체의 멀티스레딩을 수동으로 비활성화해야 할 수도 있습니다.", + "SettingsTabGraphicsFeaturesOptions": "특징", + "SettingsTabGraphicsBackendMultithreading": "그래픽 백엔드 멀티스레딩 :", + "CommonAuto": "자동적 인", + "CommonOff": "끄다", + "CommonOn": "켜짐", + "InputDialogYes": "네", + "InputDialogNo": "아니", + "DialogProfileInvalidProfileNameErrorMessage": "파일 이름에 잘못된 기호가 있습니다. 다시 시도하십시오.", + "MenuBarOptionsPauseEmulation": "정지시키다", + "MenuBarOptionsResumeEmulation": "재개하다", + "AboutUrlTooltipMessage": "기본 브라우저에서 Ryujinx 웹사이트를 열려면 클릭하십시오.", + "AboutDisclaimerMessage": "Ryujinx는 Nintendo™와 제휴하지 않으며,\n어떤 식으로든 또는 그 파트너.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI(www.amiiboapi.com) 사용\nAmiibo 에뮬레이션에서.", + "AboutPatreonUrlTooltipMessage": "기본 브라우저에서 Ryujinx Patreon 페이지를 열려면 클릭하세요.", + "AboutGithubUrlTooltipMessage": "클릭하여 기본 브라우저에서 Ryujinx GitHub 페이지를 엽니다.", + "AboutDiscordUrlTooltipMessage": "기본 브라우저에서 Ryujinx Discord 서버 초대를 열려면 클릭하세요.", + "AboutTwitterUrlTooltipMessage": "클릭하여 기본 브라우저에서 Ryujinx Twitter 페이지를 엽니다.", + "AboutRyujinxAboutTitle": "에 대한 :", + "AboutRyujinxAboutContent": "Ryujinx는 Nintendo Switch™용 에뮬레이터입니다.\nPatreon에서 지원해 주세요.\n모든 최신 뉴스는 Twitter 또는 Discord에서 확인하세요.\n기고에 관심이 있는 개발자는 GitHub 또는 Discord에서 자세한 내용을 확인할 수 있습니다.", + "AboutRyujinxMaintainersTitle": "유지 관리 :", + "AboutRyujinxMaintainersContentTooltipMessage": "기본 브라우저에서 기여자 페이지를 열려면 클릭하십시오.", + "AboutRyujinxSupprtersTitle": "Patreon에서 지원 :", + "AmiiboSeriesLabel": "Amiibo 시리즈", + "AmiiboCharacterLabel": "성격", + "AmiiboScanButtonLabel": "스캔", + "AmiiboOptionsShowAllLabel": "모든 Amiibo 표시", + "AmiiboOptionsUsRandomTagLabel": "해킹: 임의의 태그 UUID 사용", + "DlcManagerTableHeadingEnabledLabel": "활성화됨", + "DlcManagerTableHeadingTitleIdLabel": "제목 ID", + "DlcManagerTableHeadingContainerPathLabel": "컨테이너 경로", + "DlcManagerTableHeadingFullPathLabel": "전체 경로", + "DlcManagerRemoveAllButton": "모두 제거", + "MenuBarOptionsChangeLanguage": "언어 변경", + "CommonSort": "정렬", + "CommonShowNames": "이름 표시", + "CommonFavorite": "가장 좋아하는", + "OrderAscending": "오름차순", + "OrderDescending": "내림차순", + "SettingsTabGraphicsFeatures": "특징", + "ErrorWindowTitle": "오류 창", + "ToggleDiscordTooltip": "Discord Rich Presence 활성화 또는 비활성화", + "AddGameDirBoxTooltip": "게임 디렉토리를 입력하여 목록에 추가하세요", + "AddGameDirTooltip": "목록에 게임 디렉토리 추가", + "RemoveGameDirTooltip": "선택한 게임 디렉토리 제거", + "CustomThemeCheckTooltip": "GUI에서 사용자 정의 테마 활성화 또는 비활성화", + "CustomThemePathTooltip": "사용자 지정 GUI 테마 경로", + "CustomThemeBrowseTooltip": "사용자 정의 GUI 테마 찾기", + "DockModeToggleTooltip": "도킹 모드 활성화 또는 비활성화", + "DirectKeyboardTooltip": "\"직접 키보드 액세스(HID) 지원\" 활성화 또는 비활성화(텍스트 입력 장치로 키보드에 대한 게임 액세스 제공)", + "DirectMouseTooltip": "\"직접 마우스 액세스(HID) 지원\" 활성화 또는 비활성화(포인팅 장치로 마우스에 대한 게임 액세스 제공)", + "RegionTooltip": "시스템 지역 변경", + "LanguageTooltip": "시스템 언어 변경", + "TimezoneTooltip": "시스템 시간대 변경", + "TimeTooltip": "시스템 시간 변경", + "VSyncToggleTooltip": "수직 동기화 활성화 또는 비활성화", + "PptcToggleTooltip": "PPTC 활성화 또는 비활성화", + "FsIntegrityToggleTooltip": "게임 콘텐츠 파일에 대한 무결성 검사 활성화", + "AudioBackendTooltip": "오디오 백엔드 변경", + "MemoryManagerTooltip": "게스트 메모리가 매핑되고 액세스되는 방식을 변경합니다. 에뮬레이트된 CPU 성능에 큰 영향을 줍니다.", + "MemoryManagerSoftwareTooltip": "주소 변환을 위해 소프트웨어 페이지 테이블을 사용하십시오. 정확도는 가장 높지만 성능은 가장 느립니다.", + "MemoryManagerHostTooltip": "호스트 주소 공간에서 메모리를 직접 매핑합니다. 훨씬 더 빠른 JIT 컴파일 및 실행.", + "MemoryManagerUnsafeTooltip": "메모리를 직접 매핑하지만 액세스하기 전에 게스트 주소 공간 내의 주소를 마스킹하지 마십시오. 더 빠르지만 안전을 희생해야 합니다. 게스트 응용 프로그램은 Ryujinx의 어디에서나 메모리에 액세스할 수 있으므로 이 모드로 신뢰할 수 있는 프로그램만 실행하십시오.", + "DRamTooltip": "에뮬레이트된 시스템의 메모리 양을 4GB에서 6GB로 확장", + "IgnoreMissingServicesTooltip": "누락된 서비스 무시 옵션 활성화 또는 비활성화", + "GraphicsBackendThreadingTooltip": "그래픽 백엔드 멀티스레딩 활성화", + "GalThreadingTooltip": "두 번째 스레드에서 그래픽 백엔드 명령을 실행합니다. 셰이더 컴파일의 런타임 멀티스레딩을 허용하고, 말더듬을 줄이고, 자체 멀티스레딩 지원 없이 드라이버의 성능을 개선합니다. 멀티스레딩이 있는 드라이버에서 약간 다른 최대 성능. 드라이버 내장 멀티스레딩을 올바르게 비활성화하려면 Ryujinx를 다시 시작해야 할 수도 있고 최상의 성능을 얻으려면 수동으로 수행해야 할 수도 있습니다.", + "ShaderCacheToggleTooltip": "셰이더 캐시 활성화 또는 비활성화", + "ResolutionScaleTooltip": "적용 가능한 렌더 타겟에 적용된 해상도 배율", + "ResolutionScaleEntryTooltip": "1.5와 같은 부동 소수점 해상도 스케일. 비적분 스케일은 문제나 충돌을 일으킬 가능성이 더 큽니다.", + "AnisotropyTooltip": "이방성 필터링 수준(게임에서 요청한 값을 사용하려면 자동으로 설정)", + "AspectRatioTooltip": "렌더러 창에 적용된 종횡비.", + "ShaderDumpPathTooltip": "그래픽 셰이더 덤프 경로", + "FileLogTooltip": "디스크의 파일에 대한 로깅 활성화 또는 비활성화", + "StubLogTooltip": "스텁 로그 메시지 인쇄 활성화", + "InfoLogTooltip": "정보 로그 메시지 인쇄 활성화", + "WarnLogTooltip": "경고 로그 메시지 인쇄 활성화", + "ErrorLogTooltip": "오류 로그 메시지 인쇄 활성화", + "GuestLogTooltip": "게스트 로그 메시지 인쇄 활성화", + "FileAccessLogTooltip": "파일 액세스 로그 메시지 인쇄 활성화", + "FSAccessLogModeTooltip": "콘솔에 대한 FS 액세스 로그 출력을 활성화합니다. 가능한 모드는 0-3입니다", + "DeveloperOptionTooltip": "주의해서 사용", + "OpenGlLogLevel": "적절한 로그 수준이 활성화되어 있어야 합니다", + "DebugLogTooltip": "디버그 로그 메시지 인쇄 활성화", + "LoadApplicationFileTooltip": "로드할 Switch 호환 파일을 선택하려면 파일 선택기를 엽니다.", + "LoadApplicationFolderTooltip": "파일 선택기를 열어 로드할 Switch 호환 가능하고 압축을 푼 응용 프로그램을 선택합니다", + "OpenRyujinxFolderTooltip": "Ryujinx 파일 시스템 폴더 열기", + "OpenRyujinxLogsTooltip": "로그가 기록되는 폴더를 엽니다.", + "ExitTooltip": "Ryujinx 종료", + "OpenSettingsTooltip": "설정 창 열기", + "OpenProfileManagerTooltip": "사용자 프로필 관리자 창 열기", + "StopEmulationTooltip": "현재 게임의 에뮬레이션을 중지하고 게임 선택으로 돌아가기", + "CheckUpdatesTooltip": "Ryujinx 업데이트 확인", + "OpenAboutTooltip": "정보 창 열기", + "GridSize": "그리드 크기", + "GridSizeTooltip": "그리드 항목의 크기 변경", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "포르투갈어(브라질)", + "AboutRyujinxContributorsButtonHeader": "모든 기여자 보기", + "SettingsTabSystemAudioVolume": "용량 : ", + "AudioVolumeTooltip": "오디오 볼륨 변경", + "SettingsTabSystemEnableInternetAccess": "게스트 인터넷 액세스 활성화s", + "EnableInternetAccessTooltip": "게스트 인터넷 액세스를 활성화합니다. 활성화된 경우 응용 프로그램은 에뮬레이트된 스위치 콘솔이 인터넷에 연결된 것처럼 작동합니다. 경우에 따라 이 옵션이 비활성화된 경우에도 응용 프로그램이 인터넷에 계속 액세스할 수 있습니다", + "GameListContextMenuManageCheatToolTip" : "치트 관리", + "GameListContextMenuManageCheat" : "치트 관리", + "ControllerSettingsStickRange" : "범위", + "DialogStopEmulationTitle" : "Ryujinx - 에뮬레이션 중지", + "DialogStopEmulationMessage": "에뮬레이션을 중지하시겠습니까?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "오디오", + "SettingsTabNetwork": "회로망", + "SettingsTabNetworkConnection" : "네트워크 연결", + "SettingsTabGraphicsFrameRate" : "호스트 새로 고침 빈도 :", + "SettingsTabGraphicsFrameRateTooltip" : "호스트 새로 고침 빈도를 설정합니다. 제한을 제거하려면 0으로 설정하십시오.", + "SettingsTabCpuCache" : "CPU 캐시", + "SettingsTabCpuMemory" : "CPU 메모리" +} diff --git a/Ryujinx.Ava/Assets/Locales/pt_BR.json b/Ryujinx.Ava/Assets/Locales/pt_BR.json new file mode 100644 index 000000000..466d75d0c --- /dev/null +++ b/Ryujinx.Ava/Assets/Locales/pt_BR.json @@ -0,0 +1,544 @@ +{ + "MenuBarFileOpenApplet": "Abrir Applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Abrir editor Mii em modo avulso", + "SettingsTabInputDirectMouseAccess": "Acesso direto ao mouse", + "SettingsTabSystemMemoryManagerMode": "Modo de gerenciamento de memória:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Hóspede (rápido)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Hóspede sem verificação (mais rápido, inseguro)", + "MenuBarFile": "_Arquivo", + "MenuBarFileOpenFromFile": "_Abrir ROM do jogo...", + "MenuBarFileOpenUnpacked": "Abrir jogo _extraído...", + "MenuBarFileOpenEmuFolder": "Abrir diretório do e_mulador...", + "MenuBarFileOpenLogsFolder": "Abrir diretório de _logs...", + "MenuBarFileExit": "_Sair", + "MenuBarOptions": "_Opções", + "MenuBarOptionsToggleFullscreen": "_Mudar para tela cheia", + "MenuBarOptionsStartGamesInFullscreen": "_Iniciar jogos em tela cheia", + "MenuBarOptionsStopEmulation": "_Encerrar emulação", + "MenuBarOptionsSettings": "_Configurações", + "MenuBarOptionsManageUserProfiles": "_Gerenciar perfis de usuário", + "MenuBarActions": "_Ações", + "MenuBarOptionsSimulateWakeUpMessage": "_Simular mensagem de acordar console", + "MenuBarActionsScanAmiibo": "Escanear um Amiibo", + "MenuBarTools": "_Ferramentas", + "MenuBarToolsInstallFirmware": "_Instalar firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Instalar firmware a partir de um arquivo ZIP/XCI", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Instalar firmware a partir de um diretório", + "MenuBarHelp": "A_juda", + "MenuBarHelpCheckForUpdates": "_Verificar se há atualizações", + "MenuBarHelpAbout": "_Sobre", + "MenuSearch": "Buscar...", + "GameListHeaderFavorite": "Favorito", + "GameListHeaderIcon": "Ícone", + "GameListHeaderApplication": "Nome", + "GameListHeaderDeveloper": "Desenvolvedor", + "GameListHeaderVersion": "Versão", + "GameListHeaderTimePlayed": "Tempo de jogo", + "GameListHeaderLastPlayed": "Último jogo", + "GameListHeaderFileExtension": "Extensão", + "GameListHeaderFileSize": "Tamanho", + "GameListHeaderPath": "Caminho", + "GameListContextMenuOpenUserSaveDirectory": "Abrir diretório de saves do usuário", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Abre o diretório que contém jogos salvos para o usuário atual", + "GameListContextMenuOpenUserDeviceDirectory": "Abrir diretório de saves de dispositivo do usuário", + "GameListContextMenuOpenUserDeviceDirectoryToolTip": "Abre o diretório que contém saves do dispositivo para o usuário atual", + "GameListContextMenuOpenUserBcatDirectory": "Abrir diretório de saves BCAT do usuário", + "GameListContextMenuOpenUserBcatDirectoryToolTip": "Abre o diretório que contém saves BCAT para o usuário atual", + "GameListContextMenuManageTitleUpdates": "Gerenciar atualizações do jogo", + "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", + "GameListContextMenuCacheManagementPurgeShaderCache": "Limpar cache de Shader", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Deleta o cache de Shader armazenado em disco do jogo", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Abrir diretório do cache PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Abre o diretório contendo os arquivos do cache PPTC", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Abrir diretório do cache de Shader", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Abre o diretório contendo os arquivos do cache de Shader", + "GameListContextMenuExtractData": "Extrair dados", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extrai a seção ExeFS do jogo (incluindo atualizações)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extrai a seção RomFS do jogo (incluindo atualizações)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Extrai a seção Logo do jogo (incluindo atualizações)", + "StatusBarGamesLoaded": "{0}/{1} jogos carregados", + "StatusBarSystemVersion": "Versão do firmware: {0}", + "Settings": "Configurações", + "SettingsTabGeneral": "Geral", + "SettingsTabGeneralGeneral": "Geral", + "SettingsTabGeneralEnableDiscordRichPresence": "Habilitar Rich Presence do Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Verificar se há atualizações ao iniciar", + "SettingsTabGeneralShowConfirmExitDialog": "Exibir diálogo de confirmação ao sair", + "SettingsTabGeneralHideCursorOnIdle": "Esconder o cursor quando ocioso", + "SettingsTabGeneralGameDirectories": "Diretórios de jogo", + "SettingsTabGeneralAdd": "Adicionar", + "SettingsTabGeneralRemove": "Remover", + "SettingsTabSystem": "Sistema", + "SettingsTabSystemCore": "Principal", + "SettingsTabSystemSystemRegion": "Região do sistema:", + "SettingsTabSystemSystemRegionJapan": "Japão", + "SettingsTabSystemSystemRegionUSA": "EUA", + "SettingsTabSystemSystemRegionEurope": "Europa", + "SettingsTabSystemSystemRegionAustralia": "Austrália", + "SettingsTabSystemSystemRegionChina": "China", + "SettingsTabSystemSystemRegionKorea": "Coreia", + "SettingsTabSystemSystemRegionTaiwan": "Taiwan", + "SettingsTabSystemSystemLanguage": "Idioma do sistema:", + "SettingsTabSystemSystemLanguageJapanese": "Japonês", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Inglês americano", + "SettingsTabSystemSystemLanguageFrench": "Francês", + "SettingsTabSystemSystemLanguageGerman": "Alemão", + "SettingsTabSystemSystemLanguageItalian": "Italiano", + "SettingsTabSystemSystemLanguageSpanish": "Espanhol", + "SettingsTabSystemSystemLanguageChinese": "Chinês", + "SettingsTabSystemSystemLanguageKorean": "Coreano", + "SettingsTabSystemSystemLanguageDutch": "Holandês", + "SettingsTabSystemSystemLanguagePortuguese": "Português", + "SettingsTabSystemSystemLanguageRussian": "Russo", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanês", + "SettingsTabSystemSystemLanguageBritishEnglish": "Inglês britânico", + "SettingsTabSystemSystemLanguageCanadianFrench": "Francês canadense", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Espanhol latino", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chinês simplificado", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Chinês tradicional", + "SettingsTabSystemSystemTimeZone": "Fuso horário do sistema:", + "SettingsTabSystemSystemTime": "Hora do sistema:", + "SettingsTabSystemEnableVsync": "Habilitar sincronia vertical", + "SettingsTabSystemEnablePptc": "Habilitar PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Habilitar verificação de integridade do sistema de arquivos", + "SettingsTabSystemAudioBackend": "Biblioteca de saída de áudio:", + "SettingsTabSystemAudioBackendDummy": "Nenhuma", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": " - Pode causar instabilidade", + "SettingsTabSystemExpandDramSize": "Expandir memória para 6GB", + "SettingsTabSystemIgnoreMissingServices": "Ignorar serviços não implementados", + "SettingsTabGraphics": "Gráficos", + "SettingsTabGraphicsEnhancements": "Melhorias", + "SettingsTabGraphicsEnableShaderCache": "Habilitar cache de shader", + "SettingsTabGraphicsAnisotropicFiltering": "Filtragem anisotrópica:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Escala de resolução:", + "SettingsTabGraphicsResolutionScaleCustom": "Customizada (não recomendado)", + "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Proporção:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Esticar até caber", + "SettingsTabGraphicsDeveloperOptions": "Opções do desenvolvedor", + "SettingsTabGraphicsShaderDumpPath": "Diretório para despejo de shaders:", + "SettingsTabLogging": "Log", + "SettingsTabLoggingLogging": "Log", + "SettingsTabLoggingEnableLoggingToFile": "Salvar logs em arquivo", + "SettingsTabLoggingEnableStubLogs": "Habilitar logs de stub", + "SettingsTabLoggingEnableInfoLogs": "Habilitar logs de informação", + "SettingsTabLoggingEnableWarningLogs": "Habilitar logs de alerta", + "SettingsTabLoggingEnableErrorLogs": "Habilitar logs de erro", + "SettingsTabLoggingEnableTraceLogs": "Habilitar logs de rastreamento", + "SettingsTabLoggingEnableGuestLogs": "Habilitar logs do programa convidado", + "SettingsTabLoggingEnableFsAccessLogs": "Habilitar logs de acesso ao sistema de arquivos", + "SettingsTabLoggingFsGlobalAccessLogMode": "Modo global de logs do sistema de arquivos:", + "SettingsTabLoggingDeveloperOptions": "Opções do desenvolvedor (AVISO: Vai reduzir a performance)", + "SettingsTabLoggingOpenglLogLevel": "Nível de log do OpenGL:", + "SettingsTabLoggingOpenglLogLevelNone": "Nenhum", + "SettingsTabLoggingOpenglLogLevelError": "Erro", + "SettingsTabLoggingOpenglLogLevelPerformance": "Lentidão", + "SettingsTabLoggingOpenglLogLevelAll": "Todos", + "SettingsTabLoggingEnableDebugLogs": "Habilitar logs de depuração", + "SettingsTabInput": "Controle", + "SettingsTabInputEnableDockedMode": "Habilitar modo TV", + "SettingsTabInputDirectKeyboardAccess": "Acesso direto ao teclado", + "SettingsButtonSave": "Salvar", + "SettingsButtonClose": "Fechar", + "SettingsButtonApply": "Aplicar", + "ControllerSettingsPlayer": "Jogador", + "ControllerSettingsPlayer1": "Jogador 1", + "ControllerSettingsPlayer2": "Jogador 2", + "ControllerSettingsPlayer3": "Jogador 3", + "ControllerSettingsPlayer4": "Jogador 4", + "ControllerSettingsPlayer5": "Jogador 5", + "ControllerSettingsPlayer6": "Jogador 6", + "ControllerSettingsPlayer7": "Jogador 7", + "ControllerSettingsPlayer8": "Jogador 8", + "ControllerSettingsHandheld": "Portátil", + "ControllerSettingsInputDevice": "Dispositivo de entrada", + "ControllerSettingsRefresh": "Atualizar", + "ControllerSettingsDeviceDisabled": "Disabled", + "ControllerSettingsControllerType": "Tipo do controle", + "ControllerSettingsControllerTypeHandheld": "Portátil", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "Par de JoyCon", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon esquerdo", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon direito", + "ControllerSettingsProfile": "Perfil", + "ControllerSettingsProfileDefault": "Padrão", + "ControllerSettingsLoad": "Carregar", + "ControllerSettingsAdd": "Adicionar", + "ControllerSettingsRemove": "Remover", + "ControllerSettingsButtons": "Botões", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Direcional", + "ControllerSettingsDPadUp": "Cima", + "ControllerSettingsDPadDown": "Baixo", + "ControllerSettingsDPadLeft": "Esquerda", + "ControllerSettingsDPadRight": "Direita", + "ControllerSettingsLStick": "Analógico esquerdo", + "ControllerSettingsLStickButton": "Botão", + "ControllerSettingsLStickUp": "Cima", + "ControllerSettingsLStickDown": "Baixo", + "ControllerSettingsLStickLeft": "Esquerda", + "ControllerSettingsLStickRight": "Direita", + "ControllerSettingsLStickStick": "Analógico", + "ControllerSettingsLStickInvertXAxis": "Inverter eixo X", + "ControllerSettingsLStickInvertYAxis": "Inverter eixo Y", + "ControllerSettingsLStickDeadzone": "Zona morta:", + "ControllerSettingsRStick": "Analógico direito", + "ControllerSettingsRStickButton": "Botão", + "ControllerSettingsRStickUp": "Cima", + "ControllerSettingsRStickDown": "Baixo", + "ControllerSettingsRStickLeft": "Esquerda", + "ControllerSettingsRStickRight": "Direita", + "ControllerSettingsRStickStick": "Analógico", + "ControllerSettingsRStickInvertXAxis": "Inverter eixo X", + "ControllerSettingsRStickInvertYAxis": "Inverter eixo Y", + "ControllerSettingsRStickDeadzone": "Zona morta:", + "ControllerSettingsTriggers": "Gatilhos", + "ControllerSettingsTriggersLeft": "Gatilhos esquerda", + "ControllerSettingsTriggersRight": "Gatilhos direita", + "ControllerSettingsTriggersButtonsLeft": "Botões de gatilho esquerda", + "ControllerSettingsTriggersButtonsRight": "Botões de gatilho direita", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Botões esquerda", + "ControllerSettingsExtraButtonsRight": "Botões direita", + "ControllerSettingsMisc": "Miscelâneas", + "ControllerSettingsTriggerThreshold": "Sensibilidade do gatilho:", + "ControllerSettingsMotion": "Sensor de movimento", + "ControllerSettingsCemuHook": "CemuHook", + "ControllerSettingsMotionEnableMotionControls": "Habilitar sensor de movimento", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Usar sensor compatível com CemuHook", + "ControllerSettingsMotionControllerSlot": "Slot do controle:", + "ControllerSettingsMotionMirrorInput": "Espelhar movimento", + "ControllerSettingsMotionRightJoyConSlot": "Slot do JoyCon direito:", + "ControllerSettingsMotionServerHost": "Endereço do servidor:", + "ControllerSettingsMotionGyroSensitivity": "Sensibilidade do giroscópio:", + "ControllerSettingsMotionGyroDeadzone": "Zona morta do giroscópio:", + "ControllerSettingsSave": "Salvar", + "ControllerSettingsClose": "Fechar", + "UserProfilesSelectedUserProfile": "Perfil de usuário selecionado:", + "UserProfilesSaveProfileName": "Salvar nome de perfil", + "UserProfilesChangeProfileImage": "Mudar imagem de perfil", + "UserProfilesAvailableUserProfiles": "Perfis de usuário disponíveis:", + "UserProfilesAddNewProfile": "Adicionar novo perfil", + "UserProfilesDeleteSelectedProfile": "Apagar perfil selecionado", + "UserProfilesClose": "Fechar", + "ProfileImageSelectionTitle": "Seleção da imagem de perfil", + "ProfileImageSelectionHeader": "Escolha uma imagem de perfil", + "ProfileImageSelectionNote": "Você pode importar uma imagem customizada, ou selecionar um avatar do firmware", + "ProfileImageSelectionImportImage": "Importar arquivo de imagem", + "ProfileImageSelectionSelectAvatar": "Selecionar avatar do firmware", + "InputDialogTitle": "Diálogo de texto", + "InputDialogOk": "OK", + "InputDialogCancel": "Cancelar", + "InputDialogAddNewProfileTitle": "Escolha o nome de perfil", + "InputDialogAddNewProfileHeader": "Escreva o nome do perfil", + "InputDialogAddNewProfileSubtext": "(Máximo de caracteres: {0})", + "AvatarChoose": "Escolher", + "AvatarSetBackgroundColor": "Definir cor de fundo", + "AvatarClose": "Fechar", + "ControllerSettingsLoadProfileToolTip": "Carregar perfil", + "ControllerSettingsAddProfileToolTip": "Adicionar perfil", + "ControllerSettingsRemoveProfileToolTip": "Remover perfil", + "ControllerSettingsSaveProfileToolTip": "Salvar perfil", + "MenuBarFileToolsTakeScreenshot": "Salvar captura de tela", + "MenuBarFileToolsHideUi": "Esconder interface gráfica", + "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", + "ControllerSettingsMotionConfigureCemuHookSettings": "Configurar sensor de movimento CemuHook", + "ControllerSettingsRumble": "Vibração", + "ControllerSettingsRumbleEnable": "Habilitar vibração", + "ControllerSettingsRumbleStrongMultiplier": "Multiplicador de vibração forte", + "ControllerSettingsRumbleWeakMultiplier": "Multiplicador de vibração fraca", + "DialogMessageSaveNotAvailableMessage": "Não há jogos salvos para {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Gostaria de criar o diretório de salvamento para esse jogo?", + "DialogConfirmationTitle": "Ryujinx - Confirmação", + "DialogUpdaterTitle": "Ryujinx - Atualizador", + "DialogErrorTitle": "Ryujinx - Erro", + "DialogWarningTitle": "Ryujinx - Alerta", + "DialogExitTitle": "Ryujinx - Sair", + "DialogErrorMessage": "Ryujinx encontrou um erro", + "DialogExitMessage": "Tem certeza que deseja fechar o Ryujinx?", + "DialogExitSubMessage": "Todos os dados que não foram salvos serão perdidos!", + "DialogMessageCreateSaveErrorMessage": "Ocorreu um erro ao criar o diretório de salvamento: {0}", + "DialogMessageFindSaveErrorMessage": "Ocorreu um erro ao tentar encontrar o diretório de salvamento: {0}", + "FolderDialogExtractTitle": "Escolha o diretório onde os arquivos serão extraídos", + "DialogNcaExtractionMessage": "Extraindo seção {0} de {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Extrator de seções NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Falha na extração. O NCA principal não foi encontrado no arquivo selecionado.", + "DialogNcaExtractionCheckLogErrorMessage": "Falha na extração. Leia o arquivo de log para mais informações.", + "DialogNcaExtractionSuccessMessage": "Extração concluída com êxito.", + "DialogUpdaterConvertFailedMessage": "Falha ao converter a versão atual do Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Cancelando atualização!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Você já está usando a versão mais recente do Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Ocorreu um erro ao tentar obter informações de atualização do AppVeyor.", + "DialogUpdaterConvertFailedGithubMessage": "Falha ao converter a versão do Ryujinx recebida do AppVeyor.", + "DialogUpdaterDownloadingMessage": "Baixando atualização...", + "DialogUpdaterExtractionMessage": "Extraindo atualização...", + "DialogUpdaterRenamingMessage": "Renomeando atualização...", + "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!", + "DialogUpdaterDirtyBuildSubMessage": "Por favor, baixe o Ryujinx em https://ryujinx.org/ se está procurando por uma versão suportada.", + "DialogRestartRequiredMessage": "Reinicialização necessária", + "DialogThemeRestartMessage": "O tema foi salvo. Uma reinicialização é necessária para aplicar o tema.", + "DialogThemeRestartSubMessage": "Deseja reiniciar?", + "DialogFirmwareInstallEmbeddedMessage": "Gostaria de instalar o firmware incluso neste jogo? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Nenhum firmware instalado foi encontrado, mas Ryujinx conseguiu instalar o firmware {0} do jogo fornecido.\nO emulador será reiniciado.", + "DialogFirmwareNoFirmwareInstalledMessage": "Firmware não foi instalado", + "DialogFirmwareInstalledMessage": "Firmware {0} foi instalado", + "DialogOpenSettingsWindowLabel": "Abrir janela de configurações", + "DialogControllerAppletTitle": "Applet de controle", + "DialogMessageDialogErrorExceptionMessage": "Erro ao exibir diálogo de mensagem: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Erro ao exibir teclado virtual: {0}", + "DialogErrorAppletErrorExceptionMessage": "Erro ao exibir applet ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nPara mais informações sobre como corrigir esse erro, siga nosso Guia de Configuração.", + "DialogUserErrorDialogTitle": "Erro do Ryujinx ({0})", + "DialogAmiiboApiTitle": "API Amiibo", + "DialogAmiiboApiFailFetchMessage": "Um erro ocorreu ao tentar obter informações da API.", + "DialogAmiiboApiConnectErrorMessage": "Não foi possível conectar ao servidor da API Amiibo. O serviço pode estar fora do ar ou você precisa verificar sua conexão com a Internet.", + "DialogProfileInvalidProfileErrorMessage": "Perfil {0} é incompatível com o sistema de configuração de controle atual.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "O perfil Padrão não pode ser substituído", + "DialogProfileDeleteProfileTitle": "Apagando perfil", + "DialogProfileDeleteProfileMessage": "Essa ação é irreversível, tem certeza que deseja continuar?", + "DialogWarning": "Alerta", + "DialogPPTCDeletionMessage": "Você está prestes a apagar o cache PPTC para :\n\n{0}\n\nTem certeza que deseja continuar?", + "DialogPPTCDeletionErrorMessage": "Erro apagando cache PPTC em {0}: {1}", + "DialogShaderDeletionMessage": "Você está prestes a apagar o cache de Shader para :\n\n{0}\n\nTem certeza que deseja continuar?", + "DialogShaderDeletionErrorMessage": "Erro apagando o cache de Shader em {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx encontrou um erro", + "DialogInvalidTitleIdErrorMessage": "Erro de interface: O jogo selecionado não tem um ID de título válido", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Um firmware de sistema válido não foi encontrado em {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Instalar firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "A versão do sistema {0} será instalada.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nIsso substituirá a versão do sistema atual {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nDeseja continuar?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Instalando firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Versão do sistema {0} instalada com sucesso.", + "DialogUserProfileDeletionWarningMessage": "Não haveria nenhum perfil selecionado se o perfil atual fosse deletado", + "DialogUserProfileDeletionConfirmMessage": "Deseja deletar o perfil selecionado", + "DialogControllerSettingsModifiedConfirmMessage": "As configurações de controle atuais foram atualizadas.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Deseja salvar?", + "DialogDlcLoadNcaErrorMessage": "{0}. Arquivo com erro: {1}", + "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?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "O despejo de shaders está ativo, esse recurso é feito para ser usado apenas por desenvolvedores.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Para melhor performance, é recomendável desabilitar o despejo de shaders. Gostaria de desabilitar o despejo de shaders agora?", + "DialogLoadAppGameAlreadyLoadedMessage": "Um jogo já foi carregado", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Por favor, pare a emulação ou feche o emulador antes de abrir outro jogo.", + "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.", + "SettingsTabGraphicsFeaturesOptions": "Recursos", + "SettingsTabGraphicsBackendMultithreading": "Multithreading da API gráfica:", + "CommonAuto": "Automático", + "CommonOff": "Desligado", + "CommonOn": "Ligado", + "InputDialogYes": "Sim", + "InputDialogNo": "Não", + "DialogProfileInvalidProfileNameErrorMessage": "O nome do arquivo contém caracteres inválidos. Por favor, tente novamente.", + "MenuBarOptionsPauseEmulation": "Pausar", + "MenuBarOptionsResumeEmulation": "Resumir", + "AboutUrlTooltipMessage": "Clique para abrir o site do Ryujinx no seu navegador padrão.", + "AboutDisclaimerMessage": "Ryujinx não é afiliado com a Nintendo™,\nou qualquer um de seus parceiros, de nenhum modo.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) é usado\nem nossa emulação de Amiibo.", + "AboutPatreonUrlTooltipMessage": "Clique para abrir a página do Patreon do Ryujinx no seu navegador padrão.", + "AboutGithubUrlTooltipMessage": "Clique para abrir a página do GitHub do Ryujinx no seu navegador padrão.", + "AboutDiscordUrlTooltipMessage": "Clique para abrir um convite ao servidor do Discord do Ryujinx no seu navegador padrão.", + "AboutTwitterUrlTooltipMessage": "Clique para abrir a página do Twitter do Ryujinx no seu navegador padrão.", + "AboutRyujinxAboutTitle": "Sobre:", + "AboutRyujinxAboutContent": "Ryujinx é um emulador de Nintendo Switch™.\nPor favor, nos dê apoio no Patreon.\nFique por dentro de todas as novidades no Twitter ou Discord.\nDesenvolvedores com interesse em contribuir podem conseguir mais informações no GitHub ou Discord.", + "AboutRyujinxMaintainersTitle": "Mantido por:", + "AboutRyujinxMaintainersContentTooltipMessage": "Clique para abrir a página de contribuidores no seu navegador padrão.", + "AboutRyujinxSupprtersTitle": "Apoiado no Patreon por:", + "AmiiboSeriesLabel": "Franquia Amiibo", + "AmiiboCharacterLabel": "Personagem", + "AmiiboScanButtonLabel": "Escanear", + "AmiiboOptionsShowAllLabel": "Exibir todos os Amiibos", + "AmiiboOptionsUsRandomTagLabel": "Hack: Usar Uuid de tag aleatório", + "DlcManagerTableHeadingEnabledLabel": "Habilitado", + "DlcManagerTableHeadingTitleIdLabel": "ID do título", + "DlcManagerTableHeadingContainerPathLabel": "Caminho do container", + "DlcManagerTableHeadingFullPathLabel": "Caminho completo", + "DlcManagerRemoveAllButton": "Remover todos", + "MenuBarOptionsChangeLanguage": "Mudar idioma", + "CommonSort": "Ordenar", + "CommonShowNames": "Exibir nomes", + "CommonFavorite": "Favorito", + "OrderAscending": "Ascendente", + "OrderDescending": "Descendente", + "SettingsTabGraphicsFeatures": "Recursos", + "ErrorWindowTitle": "Janela de erro", + "ToggleDiscordTooltip": "Habilita ou desabilita Discord Rich Presence", + "AddGameDirBoxTooltip": "Escreva um diretório de jogo para adicionar à lista", + "AddGameDirTooltip": "Adicionar um diretório de jogo à lista", + "RemoveGameDirTooltip": "Remover diretório de jogo selecionado", + "CustomThemeCheckTooltip": "Habilita ou desabilita temas customizados na interface gráfica", + "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)", + "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", + "PptcToggleTooltip": "Habilita ou desabilita PPTC", + "FsIntegrityToggleTooltip": "Habilita ou desabilita verificação de integridade dos arquivos do jogo", + "AudioBackendTooltip": "Mudar biblioteca de áudio", + "MemoryManagerTooltip": "Muda como a memória do sistema convidado é acessada. Tem um grande impacto na performance da CPU emulada.", + "MemoryManagerSoftwareTooltip": "Usar uma tabela de página via software para tradução de endereços. Maior precisão, porém performance mais baixa.", + "MemoryManagerHostTooltip": "Mapeia memória no espaço de endereço hóspede diretamente. Compilação e execução do JIT muito mais rápida.", + "MemoryManagerUnsafeTooltip": "Mapeia memória diretamente, mas sem limitar o endereço ao espaço de endereço do sistema convidado antes de acessar. Mais rápido, porém menos seguro. O aplicativo convidado pode acessar memória de qualquer parte do Ryujinx, então apenas rode programas em que você confia nesse modo.", + "DRamTooltip": "Expande a memória do sistema emulado de 4GB para 6GB", + "IgnoreMissingServicesTooltip": "Habilita ou desabilita a opção de ignorar serviços não implementados", + "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", + "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.", + "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", + "InfoLogTooltip": "Habilita ou desabilita exibição de mensagens informativas", + "WarnLogTooltip": "Habilita ou desabilita exibição de mensagens de alerta", + "ErrorLogTooltip": "Habilita ou desabilita exibição de mensagens de erro", + "TraceLogTooltip": "Habilita ou desabilita exibição de mensagens de rastreamento", + "GuestLogTooltip": "Habilita ou desabilita exibição de mensagens do programa convidado", + "FileAccessLogTooltip": "Habilita ou desabilita exibição de mensagens do acesso de arquivos", + "FSAccessLogModeTooltip": "Habilita exibição de mensagens de acesso ao sistema de arquivos no console. Modos permitidos são 0-3", + "DeveloperOptionTooltip": "Use com cuidado", + "OpenGlLogLevel": "Requer que os níveis de log apropriados estejaam habilitados", + "DebugLogTooltip": "Habilita exibição de mensagens de depuração", + "LoadApplicationFileTooltip": "Abre o navegador de arquivos para seleção de um arquivo do Switch compatível a ser carregado", + "LoadApplicationFolderTooltip": "Abre o navegador de pastas para seleção de pasta extraída do Switch compatível a ser carregada", + "OpenRyujinxFolderTooltip": "Abre o diretório do sistema de arquivos do Ryujinx", + "OpenRyujinxLogsTooltip": "Abre o diretório onde os logs são salvos", + "ExitTooltip": "Sair do Ryujinx", + "OpenSettingsTooltip": "Abrir janela de configurações", + "OpenProfileManagerTooltip": "Abrir janela de gerenciamento de perfis", + "StopEmulationTooltip": "Parar emulação do jogo atual e voltar a seleção de jogos", + "CheckUpdatesTooltip": "Verificar por atualizações para o Ryujinx", + "OpenAboutTooltip": "Abrir janela sobre", + "GridSize": "Tamanho da grade", + "GridSizeTooltip": "Mudar tamanho dos items da grade", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Português do Brasil", + "AboutRyujinxContributorsButtonHeader": "Ver todos os contribuidores", + "SettingsTabSystemAudioVolume": "Volume: ", + "AudioVolumeTooltip": "Mudar volume do áudio", + "SettingsTabSystemEnableInternetAccess": "Habilitar acesso à internet do programa convidado", + "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", + "ControllerSettingsStickRange": "Intervalo", + "DialogStopEmulationTitle": "Ryujinx - Parar emulação", + "DialogStopEmulationMessage": "Tem certeza que deseja parar a emulação?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Áudio", + "SettingsTabNetwork": "Rede", + "SettingsTabNetworkConnection": "Conexão de rede", + "SettingsTabGraphicsFrameRate": "Taxa de atualização do hóspede:", + "SettingsTabGraphicsFrameRateTooltip": "Define a taxa de atualização do hóspede. Coloque em 0 para remover o limite.", + "SettingsTabCpuCache": "Cache da CPU", + "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", + "MenuBarOptionsShowConsole": "Exibir console", + "ShaderCachePurgeError": "Erro ao deletar o shader em {0}: {1}", + "UserErrorNoKeys": "Chaves não encontradas", + "UserErrorNoFirmware": "Firmware não encontrado", + "UserErrorFirmwareParsingFailed": "Erro na leitura do Firmware", + "UserErrorApplicationNotFound": "Aplicativo não encontrado", + "UserErrorUnknown": "Erro desconhecido", + "UserErrorUndefined": "Erro indefinido", + "UserErrorNoKeysDescription": "Ryujinx não conseguiu encontrar o seu arquivo 'prod.keys'", + "UserErrorNoFirmwareDescription": "Ryujinx não conseguiu encontrar nenhum Firmware instalado", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx não conseguiu ler o Firmware fornecido. Geralmente isso é causado por chaves desatualizadas.", + "UserErrorApplicationNotFoundDescription": "Ryujinx não conseguiu encontrar um aplicativo válido no caminho fornecido.", + "UserErrorUnknownDescription": "Um erro desconhecido foi encontrado!", + "UserErrorUndefinedDescription": "Um erro indefinido occoreu! Isso não deveria acontecer, por favor contate um desenvolvedor!", + "OpenSetupGuideMessage": "Abrir o guia de configuração", + "NoUpdate": "Sem atualizações", + "TitleUpdateVersionLabel": "Versão {0} - {1}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Confirmação", + "FileDialogAllTypes": "Todos os tipos", + "Never": "Nunca", + "SwkbdMinCharacters": "Deve ter pelo menos {0} caracteres", + "SwkbdMinRangeCharacters": "Deve ter entre {0}-{1} caracteres", + "SoftwareKeyboard": "Teclado por Software", + "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", + "UpdaterRenaming": "Renomeando arquivos antigos...", + "UpdaterRenameFailed": "O atualizador não conseguiu renomear o arquivo: {0}", + "UpdaterAddingFiles": "Adicionando novos arquivos...", + "UpdaterExtracting": "Extraíndo atualização...", + "UpdaterDownloading": "Baixando atualização...", + "Game": "Jogo", + "Docked": "TV", + "Handheld": "Portátil", + "ConnectionError": "Erro de conexão.", + "AboutPageDeveloperListMore": "{0} e mais...", + "ApiError": "Erro de API." +} diff --git a/Ryujinx.Ava/Assets/Locales/ru_RU.json b/Ryujinx.Ava/Assets/Locales/ru_RU.json new file mode 100644 index 000000000..919afc9cd --- /dev/null +++ b/Ryujinx.Ava/Assets/Locales/ru_RU.json @@ -0,0 +1,497 @@ +{ + "MenuBarFileOpenApplet": "Открыть апплет", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Открыть апплет Mii Editor в автономном режиме.", + "SettingsTabInputDirectMouseAccess": "Прямой доступ с помощью мыши", + "SettingsTabSystemMemoryManagerMode": "Режим диспетчера памяти:", + "SettingsTabSystemMemoryManagerModeSoftware": "Программное обеспечение", + "SettingsTabSystemMemoryManagerModeHost": "Хост (быстро)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Хост не установлен (самый быстрый, небезопасный)", + "MenuBarFile": "_Файл", + "MenuBarFileOpenFromFile": "_Загрузить приложение из файла", + "MenuBarFileOpenUnpacked": "Загрузить _Распакованную игру", + "MenuBarFileOpenEmuFolder": "Открыть папку Ryujinx", + "MenuBarFileOpenLogsFolder": "Открыть папку журналов", + "MenuBarFileExit": "_Выход", + "MenuBarOptions": "Опции", + "MenuBarOptionsToggleFullscreen": "Включить полноэкранный режим", + "MenuBarOptionsStartGamesInFullscreen": "Запустить игру в полноэкранном режиме", + "MenuBarOptionsStopEmulation": "Остановить эмуляцию", + "MenuBarOptionsSettings": "_Параметры", + "MenuBarOptionsManageUserProfiles": "_Управление профилями пользователей", + "MenuBarActions": "_Действия", + "MenuBarOptionsSimulateWakeUpMessage": "Имитировать сообщение пробуждения", + "MenuBarActionsScanAmiibo": "Сканировать Amiibo", + "MenuBarTools": "_Инструменты", + "MenuBarToolsInstallFirmware": "Установить прошивку", + "MenuBarFileToolsInstallFirmwareFromFile": "Установить прошивку из XCI или ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Установить прошивку из каталога", + "MenuBarHelp": "Помощь", + "MenuBarHelpCheckForUpdates": "Проверить обновление", + "MenuBarHelpAbout": "О", + "MenuSearch": "Поиск...", + "GameListHeaderFavorite": "Избранные", + "GameListHeaderIcon": "Значок", + "GameListHeaderApplication": "Название", + "GameListHeaderDeveloper": "Разработчик", + "GameListHeaderVersion": "Версия", + "GameListHeaderTimePlayed": "Время воспроизведения", + "GameListHeaderLastPlayed": "Последняя игра", + "GameListHeaderFileExtension": "Расширение файла", + "GameListHeaderFileSize": "Размер файла", + "GameListHeaderPath": "Путь", + "GameListContextMenuOpenUserSaveDirectory": "Открыть каталог сохранений пользователя", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Открывает каталог, содержащий пользовательское сохранение приложения", + "GameListContextMenuOpenUserDeviceDirectory": "Открыть каталог пользовательских устройств", + "GameListContextMenuOpenUserDeviceDirectoryToolTip": "Открывает каталог, содержащий сохранение устройства приложения", + "GameListContextMenuOpenUserBcatDirectory": "Открыть каталог пользователей BCAT", + "GameListContextMenuOpenUserBcatDirectoryToolTip": "Открывает каталог, содержащий BCAT сохранения приложения.", + "GameListContextMenuManageTitleUpdates": "Управление обновлениями заголовков", + "GameListContextMenuManageTitleUpdatesToolTip": "Открывает окно управления обновлением заголовков", + "GameListContextMenuManageDlc": "Управление DLC", + "GameListContextMenuManageDlcToolTip": "Открывает окно управления DLC", + "GameListContextMenuOpenModsDirectory": "Открыть каталог модификаций", + "GameListContextMenuOpenModsDirectoryToolTip": "Открывает каталог, содержащий модификации приложения", + "GameListContextMenuCacheManagement": "Управление кэшем", + "GameListContextMenuCacheManagementPurgePptc": "Очистить кэш PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Удаляет кэш PPTC приложения", + "GameListContextMenuCacheManagementPurgeShaderCache": "Очистить кэш шейдеров", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Удаляет кеш шейдеров приложения", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Открыть PPTC каталог", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Открывает каталог, содержащий PPTC кэш приложения", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Открыть каталог кэша шейдеров", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Открывает каталог, содержащий кэш шейдеров приложения", + "GameListContextMenuExtractData": "Извлечь данные", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Извлеrftn раздел ExeFS из текущей конфигурации приложения (включая обновления)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Извлекает раздел RomFS из текущей конфигурации приложения (включая обновления)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Извлекает раздел логотипа из текущей конфигурации приложения (включая обновления)", + "StatusBarGamesLoaded": "{0}/{1} Игр загружено", + "StatusBarSystemVersion": "Версия системы: {0}", + "Settings": "Параметры", + "SettingsTabGeneral": "Пользовательский интерфейс", + "SettingsTabGeneralGeneral": "Общее", + "SettingsTabGeneralEnableDiscordRichPresence": "Включить расширенное присутствие Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Проверять наличие обновлений при запуске", + "SettingsTabGeneralShowConfirmExitDialog": "Показать диалоговое окно \"Подтвердить выход\"", + "SettingsTabGeneralHideCursorOnIdle": "Скрыть курсор в режиме ожидания", + "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": "Включить вертикальную синхронизацию", + "SettingsTabSystemEnablePptc": "Включить PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Включить проверку целостности FS", + "SettingsTabSystemAudioBackend": "Аудио бэкэнд:", + "SettingsTabSystemAudioBackendDummy": "Dummy", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Хаки", + "SettingsTabSystemHacksNote": " - Эти многие настройки вызывают нестабильность", + "SettingsTabSystemExpandDramSize": "Увеличение размера DRAM до 6GB", + "SettingsTabSystemIgnoreMissingServices": "Игнорировать отсутствующие службы", + "SettingsTabGraphics": "Графика", + "SettingsTabGraphicsEnhancements": "Улучшения", + "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": "Включить журналы ошибок", + "SettingsTabLoggingEnableGuestLogs": "Включить гостевые журналы", + "SettingsTabLoggingEnableFsAccessLogs": "Включить журналы доступа Fs", + "SettingsTabLoggingFsGlobalAccessLogMode": "Режим журнала глобального доступа Fs:", + "SettingsTabLoggingDeveloperOptions": "Параметры разработчика (ВНИМАНИЕ: снизит производительность)", + "SettingsTabLoggingOpenglLogLevel": "Уровень журнала OpenGL:", + "SettingsTabLoggingOpenglLogLevelNone": "Ничего", + "SettingsTabLoggingOpenglLogLevelError": "Ошибка", + "SettingsTabLoggingOpenglLogLevelPerformance": "Замедления", + "SettingsTabLoggingOpenglLogLevelAll": "Всё", + "SettingsTabLoggingEnableDebugLogs": "Включить журналы отладки", + "SettingsTabInput": "Управление", + "SettingsTabInputEnableDockedMode": "Включить режим закрепления", + "SettingsTabInputDirectKeyboardAccess": "Прямой доступ с клавиатуры", + "SettingsButtonSave": "Сохранить", + "SettingsButtonClose": "Закрыть", + "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": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon Пара", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Левый", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon Правый", + "ControllerSettingsProfile": "Профиль", + "ControllerSettingsProfileDefault": "По умолчанию", + "ControllerSettingsLoad": "Загрузить", + "ControllerSettingsAdd": "Добавить", + "ControllerSettingsRemove": "Удалить", + "ControllerSettingsButtons": "Кнопки", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Directional Pad", + "ControllerSettingsDPadUp": "Верх", + "ControllerSettingsDPadDown": "Низ", + "ControllerSettingsDPadLeft": "Лево", + "ControllerSettingsDPadRight": "Право", + "ControllerSettingsLStick": "Левый стик", + "ControllerSettingsLStickButton": "Кнопки", + "ControllerSettingsLStickUp": "Верх", + "ControllerSettingsLStickDown": "Низ", + "ControllerSettingsLStickLeft": "Лево", + "ControllerSettingsLStickRight": "Право", + "ControllerSettingsLStickStick": "Стик", + "ControllerSettingsLStickInvertXAxis": "Перевернуть стик X", + "ControllerSettingsLStickInvertYAxis": "Перевернуть стик Y", + "ControllerSettingsLStickDeadzone": "Мёртвая зона:", + "ControllerSettingsRStick": "Правый стик", + "ControllerSettingsRStickButton": "Кнопки", + "ControllerSettingsRStickUp": "Верх", + "ControllerSettingsRStickDown": "Низ", + "ControllerSettingsRStickLeft": "Лево", + "ControllerSettingsRStickRight": "Право", + "ControllerSettingsRStickStick": "Стик", + "ControllerSettingsRStickInvertXAxis": "Перевернуть стик X", + "ControllerSettingsRStickInvertYAxis": "Перевернуть стик Y", + "ControllerSettingsRStickDeadzone": "Мёртвая зона:", + "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": "Движение", + "ControllerSettingsCemuHook": "CemuHook", + "ControllerSettingsMotionEnableMotionControls": "Включить управление движением", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Используйте движение, совместимое с CemuHook", + "ControllerSettingsMotionControllerSlot": "Слот контроллера:", + "ControllerSettingsMotionMirrorInput": "Зеркальный ввод", + "ControllerSettingsMotionRightJoyConSlot": "Правый JoyCon слот:", + "ControllerSettingsMotionServerHost": "Хост сервера:", + "ControllerSettingsMotionGyroSensitivity": "Чувствительность гироскопа:", + "ControllerSettingsMotionGyroDeadzone": "Мертвая зона гироскопа:", + "ControllerSettingsSave": "Сохранить", + "ControllerSettingsClose": "Закрыть", + "UserProfilesSelectedUserProfile": "Выбранный пользовательский профиль:", + "UserProfilesSaveProfileName": "Сохранить пользовательский профиль", + "UserProfilesChangeProfileImage": "Изменить изображение профиля", + "UserProfilesAvailableUserProfiles": "Доступные профили пользователей:", + "UserProfilesAddNewProfile": "Добавить новый профиль", + "UserProfilesDeleteSelectedProfile": "Удалить выбранный профиль", + "UserProfilesClose": "Закрыть", + "ProfileImageSelectionTitle": "Выбор изображения профиля", + "ProfileImageSelectionHeader": "Выберите изображение профиля", + "ProfileImageSelectionNote": "Вы можете импортировать собственное изображение профиля или выбрать аватар из системной прошивки.", + "ProfileImageSelectionImportImage": "Импорт файла изображени", + "ProfileImageSelectionSelectAvatar": "Выберите аватар прошивки", + "InputDialogTitle": "Диалоговое окно ввода", + "InputDialogOk": "Да", + "InputDialogCancel": "Закрыть", + "InputDialogAddNewProfileTitle": "Выберите имя профиля", + "InputDialogAddNewProfileHeader": "Пожалуйста, введите имя профиля", + "InputDialogAddNewProfileSubtext": "(Максимальная длина: {0})", + "AvatarChoose": "Выбор", + "AvatarSetBackgroundColor": "Установить цвет фона", + "AvatarClose": "Закрыть", + "ControllerSettingsLoadProfileToolTip": "Загрузить профиль", + "ControllerSettingsAddProfileToolTip": "Добавить профил", + "ControllerSettingsRemoveProfileToolTip": "Удалить профиль", + "ControllerSettingsSaveProfileToolTip": "Сохранить профиль", + "MenuBarFileToolsTakeScreenshot": "Сделать снимок экрана", + "MenuBarFileToolsHideUi": "Спрятать пользовательский интерфейс", + "GameListContextMenuToggleFavorite": "Переключить Избранное", + "GameListContextMenuToggleFavoriteToolTip": "Переключить любимый статус игры", + "SettingsTabGeneralTheme": "Тема", + "SettingsTabGeneralThemeCustomTheme": "Пользовательский путь к теме", + "SettingsTabGeneralThemeBaseStyle": "Базовый стиль", + "SettingsTabGeneralThemeBaseStyleDark": "Тёмная", + "SettingsTabGeneralThemeBaseStyleLight": "Светлая", + "SettingsTabGeneralThemeEnableCustomTheme": "Включить пользовательскую тему", + "ButtonBrowse": "Обзор", + "ControllerSettingsMotionConfigureCemuHookSettings": "Настройка движения CemuHook", + "ControllerSettingsRumble": "Вибрация", + "ControllerSettingsRumbleEnable": "Включить вибрацию", + "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 Release. Это может быть вызвано тем, что GitHub Actions компилирует новый выпуск. Повторите попытку через несколько минут.", + "DialogUpdaterConvertFailedGithubMessage": "Не удалось преобразовать полученную версию Ryujinx из Github Release.", + "DialogUpdaterDownloadingMessage": "Загрузка обновления...", + "DialogUpdaterExtractionMessage": "Извлечение обновления...", + "DialogUpdaterRenamingMessage": "Переименование обновления...", + "DialogUpdaterAddingFilesMessage": "Добавление нового обновления...", + "DialogUpdaterCompleteMessage": "Обновление завершено!", + "DialogUpdaterRestartMessage": "Вы хотите перезапустить Ryujinx сейчас?", + "DialogUpdaterArchNotSupportedMessage": "Вы используете не поддерживаемую системную архитектуру!", + "DialogUpdaterArchNotSupportedSubMessage": "(Поддерживаются только системы x64!)", + "DialogUpdaterNoInternetMessage": "Вы не подключены к Интернету!", + "DialogUpdaterNoInternetSubMessage": "Убедитесь, что у вас есть работающее подключение к Интернету!", + "DialogUpdaterDirtyBuildMessage": "Вы не можете обновить Dirty Build Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Загрузите Ryujinx по адресу https://ryujinx.org/ , если вам нужна поддерживаемая версия.", + "DialogRestartRequiredMessage": "Требуется перезагрузка", + "DialogThemeRestartMessage": "Тема сохранена. Для применения темы требуется перезагрузка.", + "DialogThemeRestartSubMessage": "Вы хотите перезапустить?", + "DialogFirmwareInstallEmbeddedMessage": "Хотите установить прошивку, встроенную в эту игру? (Прошивка {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Установленная прошивка не найдена, но Ryujinx удалось установить прошивку {0} из предоставленной игры.\nЭмулятор запустится.", + "DialogFirmwareNoFirmwareInstalledMessage": "Прошивка не установлена", + "DialogFirmwareInstalledMessage": "Прошивка {0} была установлена", + "DialogOpenSettingsWindowLabel": "Открыть окно настроек", + "DialogControllerAppletTitle": "Апплет контроллера", + "DialogMessageDialogErrorExceptionMessage": "Ошибка отображения диалогового окна сообщений: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Ошибка отображения программной клавиатуры: {0}", + "DialogErrorAppletErrorExceptionMessage": "Ошибка отображения диалогового окна ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nДля получения дополнительной информации о том, как исправить эту ошибку, следуйте нашему Руководству по установке.", + "DialogUserErrorDialogTitle": "Ошибка Ryujinx! ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Произошла ошибка при получении информации из API.", + "DialogAmiiboApiConnectErrorMessage": "Не удалось подключиться к серверу Amiibo 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": "Ryujinx обнаружил ошибку", + "DialogInvalidTitleIdErrorMessage": "Ошибка пользовательского интерфейса: выбранная игра не имеет действительного идентификатора названия.", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Действительная системная прошивка не найдена в {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Установить прошивку {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Будет установлена версия системы {0}.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nЭто заменит текущую версию системы {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nПродолжить?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Установка прошивки...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Версия системы {0} успешно установлена.", + "DialogUserProfileDeletionWarningMessage": "Если выбранный профиль будет удален, другие профили не будут открываться.", + "DialogUserProfileDeletionConfirmMessage": "Вы хотите удалить выбранный профиль", + "DialogControllerSettingsModifiedConfirmMessage": "Текущие настройки контроллера обновлены.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Вы хотите сохранить?", + "DialogDlcLoadNcaErrorMessage": "{0}. Файл с ошибкой: {1}", + "DialogDlcNoDlcErrorMessage": "Указанный файл не содержит DLC для выбранной игры!", + "DialogPerformanceCheckLoggingEnabledMessage": "У вас включено ведение журнала отладки, предназначенное только для разработчиков.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить ведение журнала отладки. Вы хотите отключить ведение журнала отладки сейчас?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "У вас включен сброс шейдеров, который предназначен только для разработчиков.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить сброс шейдеров. Вы хотите отключить сброс шейдеров сейчас?", + "DialogLoadAppGameAlreadyLoadedMessage": "Игра уже загружена", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Пожалуйста, остановите эмуляцию или закройте эмулятор перед запуском другой игры.", + "DialogUpdateAddUpdateErrorMessage": "Указанный файл не содержит обновления для выбранного заголовка!", + "DialogSettingsBackendThreadingWarningTitle": "Предупреждение: многопоточность в бэкенде", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx необходимо перезапустить после изменения этой опции, чтобы она полностью применилась. В зависимости от вашей платформы вам может потребоваться вручную отключить собственную многопоточность вашего драйвера при использовании Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Функции", + "SettingsTabGraphicsBackendMultithreading": "Многопоточность графического бэкенда:", + "CommonAuto": "Автоматически", + "CommonOff": "Выключен", + "CommonOn": "Включен", + "InputDialogYes": "Да", + "InputDialogNo": "Нет", + "DialogProfileInvalidProfileNameErrorMessage": "Имя файла содержит недопустимые символы. Пожалуйста, попробуйте еще раз.", + "MenuBarOptionsPauseEmulation": "Пауза", + "MenuBarOptionsResumeEmulation": "Продолжить", + "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 в браузере по умолчанию.", + "AboutRyujinxAboutTitle": "О программе:", + "AboutRyujinxAboutContent": "Ryujinx — это эмулятор Nintendo Switch™.\nПожалуйста, поддержите нас на Patreon.\nУзнавайте все последние новости в нашем Twitter или Discord.\nРазработчики, заинтересованные в участии, могут узнать больше на нашем GitHub или в Discord.", + "AboutRyujinxMaintainersTitle": "Поддерживается:", + "AboutRyujinxMaintainersContentTooltipMessage": "Нажмите, чтобы открыть страницу Contributors в браузере по умолчанию.", + "AboutRyujinxSupprtersTitle": "Поддерживается на Patreon:", + "AmiiboSeriesLabel": "Серия Amiibo", + "AmiiboCharacterLabel": "Персонаж", + "AmiiboScanButtonLabel": "Сканировать", + "AmiiboOptionsShowAllLabel": "Показать все Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Хак: Использовать случайный тег Uuid", + "DlcManagerTableHeadingEnabledLabel": "Велючено", + "DlcManagerTableHeadingTitleIdLabel": "Идентификатор заголовка", + "DlcManagerTableHeadingContainerPathLabel": "Путь к контейнеру", + "DlcManagerTableHeadingFullPathLabel": "Полный путь", + "DlcManagerRemoveAllButton": "Убрать все", + "MenuBarOptionsChangeLanguage": "Изменить язык", + "CommonSort": "Сортировать", + "CommonShowNames": "Показать названия", + "CommonFavorite": "Избранные", + "OrderAscending": "По возрастанию", + "OrderDescending": "По убыванию", + "SettingsTabGraphicsFeatures": "Функции", + "ErrorWindowTitle": "Окно ошибки", + "ToggleDiscordTooltip": "Включает или отключает Discord Rich Presenc", + "AddGameDirBoxTooltip": "Введите каталог игры, чтобы добавить его в список", + "AddGameDirTooltip": "AДобавить папку с игрой в список", + "RemoveGameDirTooltip": "Удалить выбранный каталог игры", + "CustomThemeCheckTooltip": "Включить или отключить пользовательские темы в графическом интерфейсе", + "CustomThemePathTooltip": "Путь к пользовательской теме графического интерфейса", + "CustomThemeBrowseTooltip": "Обзор пользовательской темы графического интерфейса", + "DockModeToggleTooltip": "Включить или отключить режим закрепления", + "DirectKeyboardTooltip": "Включить или отключить «поддержку прямого доступа к клавиатуре (HID)» (предоставляет играм доступ к клавиатуре как к устройству ввода текста)", + "DirectMouseTooltip": "Включить или отключить «поддержку прямого доступа к мыши (HID)» (предоставляет играм доступ к вашей мыши как указывающему устройству)", + "RegionTooltip": "Изменяет регион системы", + "LanguageTooltip": "Изменяет язык системы", + "TimezoneTooltip": "Изменяет часовой пояс системы", + "TimeTooltip": "Изменяет системное время", + "VSyncToggleTooltip": "Включает или отключает Вертикальную Синхронизацию", + "PptcToggleTooltip": "Включает или отключает PPTC", + "FsIntegrityToggleTooltip": "Включает проверку целостности файлов содержимого игры.", + "AudioBackendTooltip": "Изменяет аудио-бэкэнд", + "MemoryManagerTooltip": "Изменяет способ отображения и доступа к гостевой памяти. Сильно влияет на производительность эмулируемого процессора.", + "MemoryManagerSoftwareTooltip": "Использует таблицу страниц программного обеспечения для преобразования адресов. Самая высокая точность, но самая медленная производительность.", + "MemoryManagerHostTooltip": "Непосредственное отображение памяти в адресном пространстве хоста. Значительно более быстрая JIT-компиляция и выполнение.", + "MemoryManagerUnsafeTooltip": "Напрямую сопоставляет память, но не маскируйте адрес в гостевом адресном пространстве перед доступом. Быстрее, но ценой безопасности. Гостевое приложение может получить доступ к памяти из любой точки Ryujinx, поэтому в этом режиме запускайте только те программы, которым вы доверяете.", + "DRamTooltip": "Увеличивает объем памяти в эмулируемой системе с 4 ГБ до 6 ГБ.", + "IgnoreMissingServicesTooltip": "Включает или отключает параметр игнорирования отсутствующих служб", + "GraphicsBackendThreadingTooltip": "Включает многопоточность графического бэкенда", + "GalThreadingTooltip": "Выполняет команды графического бэкэнда во втором потоке. Обеспечивает многопоточность компиляции шейдеров во время выполнения, уменьшает заикание и повышает производительность драйверов без собственной поддержки многопоточности. Немного различается пиковая производительность на драйверах с многопоточностью. Ryujinx может потребоваться перезапустить, чтобы правильно отключить встроенную многопоточность драйвера, или вам может потребоваться сделать это вручную, чтобы получить максимальную производительность.", + "ShaderCacheToggleTooltip": "Включает или отключает кэш шейдеров", + "ResolutionScaleTooltip": "Масштаб разрешения, применяемый к применимым целям рендеринга", + "ResolutionScaleEntryTooltip": "Шкала разрешения с плавающей запятой, например 1,5. Неинтегральные весы с большей вероятностью вызовут проблемы или сбои.", + "AnisotropyTooltip": "Уровень анизотропной фильтрации (установите значение «Авто», чтобы использовать значение, запрошенное игрой)", + "AspectRatioTooltip": "Соотношение сторон, применяемое к окну рендерера.", + "ShaderDumpPathTooltip": "Путь дампа графических шейдеров", + "FileLogTooltip": "Включает или отключает ведение журнала в файл на диске", + "StubLogTooltip": "Включает печать сообщений журнала-заглушки", + "InfoLogTooltip": "Включает печать сообщений информационного журнала", + "WarnLogTooltip": "Включает печать сообщений журнала предупреждений", + "ErrorLogTooltip": "Включает печать сообщений журнала ошибок", + "GuestLogTooltip": "Включает печать сообщений гостевого журнала", + "FileAccessLogTooltip": "Включает печать сообщений журнала доступа к файлам", + "FSAccessLogModeTooltip": "Включает вывод журнала доступа к FS на консоль. Возможные режимы 0-3", + "DeveloperOptionTooltip": "Используйте с осторожностью", + "OpenGlLogLevel": "Требует включения соответствующих уровней ведения журнала", + "DebugLogTooltip": "Включает печать сообщений журнала отладки", + "LoadApplicationFileTooltip": "Загружает средство выбора файлов, чтобы выбрать файл, совместимый с Switch, для загрузки", + "LoadApplicationFolderTooltip": "Загружает средство выбора файлов, чтобы выбрать распакованное приложение, совместимое с Switch, для загрузки", + "OpenRyujinxFolderTooltip": "Открывает папку файловой системы Ryujinx. ", + "OpenRyujinxLogsTooltip": "Открывает папку, в которую записываются журналы", + "ExitTooltip": "Выходит из Ryujinx", + "OpenSettingsTooltip": "Открывает окно настроек", + "OpenProfileManagerTooltip": "Открывает окно диспетчера профилей пользователей", + "StopEmulationTooltip": "Останавливает эмуляцию текущей игры и вовращение к выбору игры", + "CheckUpdatesTooltip": "Проверяет наличие обновлений Ryujinx", + "OpenAboutTooltip": "Открывает окно «О программе»", + "GridSize": "Размер сетки", + "GridSizeTooltip": "Изменение размера элементов сетки", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Португальский язык (Бразилия)", + "AboutRyujinxContributorsButtonHeader": "Посмотреть всех участников", + "SettingsTabSystemAudioVolume": "Громкость: ", + "AudioVolumeTooltip": "Изменяет громкость звука", + "SettingsTabSystemEnableInternetAccess": "Включить гостевой доступ в Интернет", + "EnableInternetAccessTooltip": "Включает гостевой доступ в Интернет. Если этот параметр включен, приложение будет вести себя так, как если бы эмулированная консоль Switch была подключена к Интернету. Обратите внимание, что в некоторых случаях приложения могут по-прежнему получать доступ к Интернету, даже если эта опция отключена.", + "GameListContextMenuManageCheatToolTip" : "Управление читами", + "GameListContextMenuManageCheat" : "Управление читами", + "ControllerSettingsStickRange" : "Диапазон", + "DialogStopEmulationTitle" : "Ryujinx - Остановить эмуляцию", + "DialogStopEmulationMessage": "Вы уверены, что хотите остановить эмуляцию?", + "SettingsTabCpu": "ЦП", + "SettingsTabAudio": "Аудио", + "SettingsTabNetwork": "Сеть", + "SettingsTabNetworkConnection" : "Подключение к сети", + "SettingsTabGraphicsFrameRate" : "Частота обновления хоста:", + "SettingsTabGraphicsFrameRateTooltip" : "Устанавливает частоту обновления хоста. Установите на 0, чтобы снять ограничение.", + "SettingsTabCpuCache" : "Кэш ЦП", + "SettingsTabCpuMemory" : "Память ЦП" +} diff --git a/Ryujinx.Ava/Assets/Locales/tr_TR.json b/Ryujinx.Ava/Assets/Locales/tr_TR.json new file mode 100644 index 000000000..893d22ec9 --- /dev/null +++ b/Ryujinx.Ava/Assets/Locales/tr_TR.json @@ -0,0 +1,547 @@ +{ + "MenuBarFileOpenApplet": "Applet'i aç", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Mii Editör Applet'ini Bağımsız Mod'da Aç", + "SettingsTabInputDirectMouseAccess": "Doğrudan Mouse Erişimi", + "SettingsTabSystemMemoryManagerMode": "Hafıza Yönetim Modu:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Host (hızlı)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Unchecked (en hızlısı, tehlikeli)", + "MenuBarFile": "_Dosya", + "MenuBarFileOpenFromFile": "_Dosyadan Uygulama Aç", + "MenuBarFileOpenUnpacked": "_Sıkıştırılmamış Oyun Aç", + "MenuBarFileOpenEmuFolder": "Ryujinx Klasörünü aç", + "MenuBarFileOpenLogsFolder": "Logs Klasörünü aç", + "MenuBarFileExit": "_Çıkış", + "MenuBarOptions": "Seçenekler", + "MenuBarOptionsToggleFullscreen": "Tam ekran modu", + "MenuBarOptionsStartGamesInFullscreen": "Oyunları Tam Ekran Modunda başlat", + "MenuBarOptionsStopEmulation": "Emülasyonu durdur", + "MenuBarOptionsSettings": "_Seçenekler", + "MenuBarOptionsManageUserProfiles": "_Kullanıcı profillerini yönet", + "MenuBarActions": "_Eylemler", + "MenuBarOptionsSimulateWakeUpMessage": "Uyandırma mesajı simüle et", + "MenuBarActionsScanAmiibo": "Amiibo tarama", + "MenuBarTools": "_Aletler", + "MenuBarToolsInstallFirmware": "Firmware yükle", + "MenuBarFileToolsInstallFirmwareFromFile": "Firmware yükle (XCI veya ZIP)", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Firmware yükle (Dizin üzerinden)", + "MenuBarHelp": "Yardım", + "MenuBarHelpCheckForUpdates": "Güncellemeleri denetle", + "MenuBarHelpAbout": "Hakkında", + "MenuSearch": "Arama...", + "GameListHeaderFavorite": "Favori", + "GameListHeaderIcon": "Simge", + "GameListHeaderApplication": "Oyun Adı", + "GameListHeaderDeveloper": "Geliştirici", + "GameListHeaderVersion": "Sürüm", + "GameListHeaderTimePlayed": "Oynama süresi", + "GameListHeaderLastPlayed": "Son oynama tarihi", + "GameListHeaderFileExtension": "Dosya Uzantısı", + "GameListHeaderFileSize": "Dosya boyutu", + "GameListHeaderPath": "Yol", + "GameListContextMenuOpenUserSaveDirectory": "Kullanıcı Kayıt Dosyası Dizinini Aç", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Uygulamanın Kullanıcı Kaydının bulunduğu dizini açar", + "GameListContextMenuOpenUserDeviceDirectory": "Kullanıcı Cihaz Dizinini Aç", + "GameListContextMenuOpenUserDeviceDirectoryToolTip": "Uygulamanın Kullanıcı Cihaz bulunduğu dizini açar", + "GameListContextMenuOpenUserBcatDirectory": "Kullanıcı BCAT Dizinini Aç", + "GameListContextMenuOpenUserBcatDirectoryToolTip": "Uygulamanın Kullanıcı BCAT Kaydının bulunduğu dizini açar", + "GameListContextMenuManageTitleUpdates": "Oyun Güncellemelerini Yönet", + "GameListContextMenuManageTitleUpdatesToolTip": "Oyun Güncelleme yönetim penceresini açar", + "GameListContextMenuManageDlc": "DLC 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": "Cache Yönetimi", + "GameListContextMenuCacheManagementPurgePptc": "PPTC Cache'ini temizle", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Uygulamanın PPTC cache'ini siler", + "GameListContextMenuCacheManagementPurgeShaderCache": "Shader Cache'i temizle", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Uygulamanın shader cache'ini siler", + "GameListContextMenuCacheManagementOpenPptcDirectory": "PPTC Dizinini Aç", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Uygulamanın PPTC Cache'inin bulunduğu dizini açar", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Shader Cache Dizinini Aç", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Uygulamanın shader cache'inin bulunduğu dizini açar", + "GameListContextMenuExtractData": "Veri Ayıkla", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Uygulamanın geçerli yapılandırmasından ExeFS kısmını ayıkla (Güncellemeler dahil)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Uygulamanın geçerli yapılandırmasından RomFS kısmını ayıkla (Güncellemeler dahil)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Uygulamanın geçerli yapılandırmasından Logo kısmını ayıkla (Güncellemeler dahil)", + "StatusBarGamesLoaded": "{0}/{1} Oyun Yüklendi", + "StatusBarSystemVersion": "Sistem Sürümü: {0}", + "Settings": "Yapılandırma", + "SettingsTabGeneral": "Kullancı Arayüzü", + "SettingsTabGeneralGeneral": "Genel", + "SettingsTabGeneralEnableDiscordRichPresence": "Discord Rich Presence'i Aç", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Her Açılışta Güncellemeleri Denetle", + "SettingsTabGeneralShowConfirmExitDialog": "\"Çıkışı Onayla\" Diyaloğunu Göster", + "SettingsTabGeneralHideCursorOnIdle": "Hareketsizlik durumunda imleci gizle", + "SettingsTabGeneralGameDirectories": "Oyun Dizinleri", + "SettingsTabGeneralAdd": "Ekle", + "SettingsTabGeneralRemove": "Kaldır", + "SettingsTabSystem": "Sistem", + "SettingsTabSystemCore": "Çekirdek", + "SettingsTabSystemSystemRegion": "Sistem Bölgesi:", + "SettingsTabSystemSystemRegionJapan": "Japonya", + "SettingsTabSystemSystemRegionUSA": "ABD", + "SettingsTabSystemSystemRegionEurope": "Avrupa", + "SettingsTabSystemSystemRegionAustralia": "Avustralya", + "SettingsTabSystemSystemRegionChina": "Çin", + "SettingsTabSystemSystemRegionKorea": "Kore", + "SettingsTabSystemSystemRegionTaiwan": "Tayvan", + "SettingsTabSystemSystemLanguage": "Sistem Dili:", + "SettingsTabSystemSystemLanguageJapanese": "Japonca", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Amerikan İngilizcesi", + "SettingsTabSystemSystemLanguageFrench": "Fransızca", + "SettingsTabSystemSystemLanguageGerman": "Almanca", + "SettingsTabSystemSystemLanguageItalian": "İtalyanca", + "SettingsTabSystemSystemLanguageSpanish": "İspanyolca", + "SettingsTabSystemSystemLanguageChinese": "Çince", + "SettingsTabSystemSystemLanguageKorean": "Korece", + "SettingsTabSystemSystemLanguageDutch": "Flemenkçe", + "SettingsTabSystemSystemLanguagePortuguese": "Portekizce", + "SettingsTabSystemSystemLanguageRussian": "Rusça", + "SettingsTabSystemSystemLanguageTaiwanese": "Tayvanca", + "SettingsTabSystemSystemLanguageBritishEnglish": "İngiliz İngilizcesi", + "SettingsTabSystemSystemLanguageCanadianFrench": "Kanada Fransızcası", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Latin Amerika İspanyolcası", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Basitleştirilmiş Çince", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Geleneksel Çince", + "SettingsTabSystemSystemTimeZone": "Sistem Saat Dilimi:", + "SettingsTabSystemSystemTime": "System Time:", + "SettingsTabSystemEnableVsync": "VSync'i Etkinleştir", + "SettingsTabSystemEnablePptc": "PPTC'yi Etkinleştir (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "FS Bütünlük Kontrollerini Etkinleştir", + "SettingsTabSystemAudioBackend": "Ses Motoru:", + "SettingsTabSystemAudioBackendDummy": "Dummy", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacklar", + "SettingsTabSystemHacksNote": " - Bunlar birçok dengesizlik oluşturabilir", + "SettingsTabSystemExpandDramSize": "DRAM boyutunu 6GB'a genişlet", + "SettingsTabSystemIgnoreMissingServices": "Eksik Servisleri Görmezden Gel", + "SettingsTabGraphics": "Grafikler", + "SettingsTabGraphicsEnhancements": "İyileştirmeler", + "SettingsTabGraphicsEnableShaderCache": "Shader Cache'i Etkinleştir", + "SettingsTabGraphicsAnisotropicFiltering": "Anisotropic Filtering:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Otomatik", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Resolution Scale:", + "SettingsTabGraphicsResolutionScaleCustom": "Özel (Tavsiye Edilmez)", + "SettingsTabGraphicsResolutionScaleNative": "Yerel (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "En-Boy Oranı:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Pencereye Sığdırmak için Genişlet", + "SettingsTabGraphicsDeveloperOptions": "Geliştirici Seçenekleri", + "SettingsTabGraphicsShaderDumpPath": "Grafik Shader Döküm Yolu:", + "SettingsTabLogging": "Loglama", + "SettingsTabLoggingLogging": "Loglama", + "SettingsTabLoggingEnableLoggingToFile": "Logları Dosyaya Kaydetmeyi Etkinleştir", + "SettingsTabLoggingEnableStubLogs": "Stub Loglarını Etkinleştir", + "SettingsTabLoggingEnableInfoLogs": "Bilgi Loglarını Etkinleştir", + "SettingsTabLoggingEnableWarningLogs": "Uyarı Loglarını Etkinleştir", + "SettingsTabLoggingEnableErrorLogs": "Hata Loglarını Etkinleştir", + "SettingsTabLoggingEnableTraceLogs": "Trace Loglarını Etkinleştir", + "SettingsTabLoggingEnableGuestLogs": "Guest Loglarını Etkinleştir", + "SettingsTabLoggingEnableFsAccessLogs": "Fs Erişim Loglarını Etkinleştir", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs Evrensel Erişim Log Modu:", + "SettingsTabLoggingDeveloperOptions": "Geliştirici Seçenekleri (UYARI: Performansı düşürecektir)", + "SettingsTabLoggingOpenglLogLevel": "OpenGL Log Seviyesi:", + "SettingsTabLoggingOpenglLogLevelNone": "Hiç", + "SettingsTabLoggingOpenglLogLevelError": "Hata", + "SettingsTabLoggingOpenglLogLevelPerformance": "Yavaşlamalar", + "SettingsTabLoggingOpenglLogLevelAll": "Her Şey", + "SettingsTabLoggingEnableDebugLogs": "Hata Ayıklama Loglarını Etkinleştir", + "SettingsTabInput": "Giriş Yöntemi", + "SettingsTabInputEnableDockedMode": "Docked Mode'u Etkinleştir", + "SettingsTabInputDirectKeyboardAccess": "Doğrudan Klavye Erişimi", + "SettingsButtonSave": "Kaydet", + "SettingsButtonClose": "Kapat", + "SettingsButtonApply": "Uygula", + "ControllerSettingsPlayer": "Oyuncu", + "ControllerSettingsPlayer1": "Oyuncu 1", + "ControllerSettingsPlayer2": "Oyuncu 2", + "ControllerSettingsPlayer3": "Oyuncu 3", + "ControllerSettingsPlayer4": "Oyuncu 4", + "ControllerSettingsPlayer5": "Oyuncu 5", + "ControllerSettingsPlayer6": "Oyuncu 6", + "ControllerSettingsPlayer7": "Oyuncu 7", + "ControllerSettingsPlayer8": "Oyuncu 8", + "ControllerSettingsHandheld": "Handheld", + "ControllerSettingsInputDevice": "Giriş Cihazı", + "ControllerSettingsRefresh": "Yenile", + "ControllerSettingsDeviceDisabled": "Devre Dışı", + "ControllerSettingsControllerType": "Kontrolcü Tarzı", + "ControllerSettingsControllerTypeHandheld": "Portatif Mod", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon İkilisi", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Sol", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon Sağ", + "ControllerSettingsProfile": "Profil", + "ControllerSettingsProfileDefault": "Varsayılan", + "ControllerSettingsLoad": "Yükle", + "ControllerSettingsAdd": "Ekle", + "ControllerSettingsRemove": "Kaldır", + "ControllerSettingsButtons": "Düğmeler", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Yön Tuşları", + "ControllerSettingsDPadUp": "Yukarı", + "ControllerSettingsDPadDown": "Aşağı", + "ControllerSettingsDPadLeft": "Sol", + "ControllerSettingsDPadRight": "Sağ", + "ControllerSettingsLStick": "Sol Analog", + "ControllerSettingsLStickButton": "Tıklama", + "ControllerSettingsLStickUp": "Yukarı", + "ControllerSettingsLStickDown": "Aşağı", + "ControllerSettingsLStickLeft": "Sol", + "ControllerSettingsLStickRight": "Sağ", + "ControllerSettingsLStickStick": "Tıklama", + "ControllerSettingsLStickInvertXAxis": "X Eksenini Tersine Çevir", + "ControllerSettingsLStickInvertYAxis": "Y Eksenini Tersine Çevir", + "ControllerSettingsLStickDeadzone": "Ölü Bölge:", + "ControllerSettingsRStick": "Sağ Analog", + "ControllerSettingsRStickButton": "Tıklama", + "ControllerSettingsRStickUp": "Yukarı", + "ControllerSettingsRStickDown": "Aşağı", + "ControllerSettingsRStickLeft": "Sol", + "ControllerSettingsRStickRight": "Sağ", + "ControllerSettingsRStickStick": "Tıklama", + "ControllerSettingsRStickInvertXAxis": "X Eksenini Tersine Çevir", + "ControllerSettingsRStickInvertYAxis": "Y Eksenini Tersine Çevir", + "ControllerSettingsRStickDeadzone": "Ölü Bölge:", + "ControllerSettingsTriggersLeft": "Sol Tetikler", + "ControllerSettingsTriggersRight": "Sağ Tetikler", + "ControllerSettingsTriggersButtonsLeft": "Sol Tetik Tuşları", + "ControllerSettingsTriggersButtonsRight": "Sağ Tetik Tuşları", + "ControllerSettingsTriggers": "Tetikler", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Sol Tuşlar", + "ControllerSettingsExtraButtonsRight": "Sağ Tuşlar", + "ControllerSettingsMisc": "Misc.", + "ControllerSettingsTriggerThreshold": "Tetik Eşiği:", + "ControllerSettingsMotion": "Hareket", + "ControllerSettingsCemuHook": "CemuHook", + "ControllerSettingsMotionEnableMotionControls": "Hareket Kontrollerini Etkinleştir", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook Uyumlu Hareket", + "ControllerSettingsMotionControllerSlot": "Kontrolcü Yuvası:", + "ControllerSettingsMotionMirrorInput": "Girişi Aynala", + "ControllerSettingsMotionRightJoyConSlot": "Sağ JoyCon Yuvası:", + "ControllerSettingsMotionServerHost": "Server Host:", + "ControllerSettingsMotionGyroSensitivity": "Gyro Hassasiyeti:", + "ControllerSettingsMotionGyroDeadzone": "Gyro Ölü Bölgesi:", + "ControllerSettingsSave": "Kaydet", + "ControllerSettingsClose": "Kapat", + "UserProfilesSelectedUserProfile": "Aktif Kullanıcı Profili:", + "UserProfilesSaveProfileName": "Profil İsmini Kaydet", + "UserProfilesChangeProfileImage": "Profil Resmini Değiştir", + "UserProfilesAvailableUserProfiles": "Mevcut Kullanıcı Profilleri:", + "UserProfilesAddNewProfile": "Yeni Profil Ekle", + "UserProfilesDeleteSelectedProfile": "Seçili Profili Sil", + "UserProfilesClose": "Kapat", + "ProfileImageSelectionTitle": "Profile Resmi Seçimi", + "ProfileImageSelectionHeader": "Profil Resmi Seç", + "ProfileImageSelectionNote": "Özel bir profil resmi içeri aktarabilir veya sistem avatarlarından birini seçebilirsiniz", + "ProfileImageSelectionImportImage": "Resim İçeri Aktar", + "ProfileImageSelectionSelectAvatar": "Sistem Avatarı Seç", + "InputDialogTitle": "Giriş Yöntemi Diyaloğu", + "InputDialogOk": "OK", + "InputDialogCancel": "İptal", + "InputDialogAddNewProfileTitle": "Profil İsmini Seç", + "InputDialogAddNewProfileHeader": "Profil İsmi girin", + "InputDialogAddNewProfileSubtext": "(Maksimum Uzunluk: {0})", + "AvatarChoose": "Seç", + "AvatarSetBackgroundColor": "Arkaplan Rengi Ayarla", + "AvatarClose": "Kapat", + "ControllerSettingsLoadProfileToolTip": "Profil Yükle", + "ControllerSettingsAddProfileToolTip": "Profil Ekle", + "ControllerSettingsRemoveProfileToolTip": "Profil Kaldır", + "ControllerSettingsSaveProfileToolTip": "Profil Kaydet", + "MenuBarFileToolsTakeScreenshot": "Ekran Görüntüsü Al", + "MenuBarFileToolsHideUi": "Kullanıcı Arayüzünü gizle", + "GameListContextMenuToggleFavorite": "Favori Ayarla", + "GameListContextMenuToggleFavoriteToolTip": "Oyunun Favori Durumunu Aç/Kapat", + "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", + "ControllerSettingsMotionConfigureCemuHookSettings": "CemuHook Hareket Ayarla", + "ControllerSettingsRumble": "Titreşim", + "ControllerSettingsRumbleEnable": "Titreşimi Etkinleştir", + "ControllerSettingsRumbleStrongMultiplier": "Güçlü Titreşim Çarpanı", + "ControllerSettingsRumbleWeakMultiplier": "Zayıf Titreşim Çarpanı", + "DialogMessageSaveNotAvailableMessage": "{0} [{1:x16}] için kayıt verisi yok", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Bu oyun için kayıt verisi oluşturmak ister misiniz?", + "DialogConfirmationTitle": "Ryujinx - Onay", + "DialogUpdaterTitle": "Ryujinx - Güncelleyici", + "DialogErrorTitle": "Ryujinx - Hata", + "DialogWarningTitle": "Ryujinx - Uyarı", + "DialogExitTitle": "Ryujinx - Çıkış", + "DialogErrorMessage": "Ryujinx bir hata ile karşılaştı", + "DialogExitMessage": "Ryujinx'i kapatmak istediğinizden emin misiniz?", + "DialogExitSubMessage": "Kaydedilmeyen bütün veriler kaybedilecek!", + "DialogMessageCreateSaveErrorMessage": "Belirtilen kayıt verisi oluşturulurken hata: {0}", + "DialogMessageFindSaveErrorMessage": "Belirtilen kayıt verisi bulunmaya çalışırken hata: {0}", + "FolderDialogExtractTitle": "İçine ayıklanacak klasörü seç", + "DialogNcaExtractionMessage": "{1} den {0} kısmı ayıklanıyor...", + "DialogNcaExtractionTitle": "Ryujinx - NCA Kısmı Ayıklayıcısı", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Ayıklama hatası. Ana NCA seçilen dosyada bulunamadı.", + "DialogNcaExtractionCheckLogErrorMessage": "Ayıklama hatası. Ek bilgi için log dosyasını okuyun.", + "DialogNcaExtractionSuccessMessage": "Ayıklama başarıyla tamamlandı.", + "DialogUpdaterConvertFailedMessage": "Güncel Ryujinx sürümü çevrilemedi.", + "DialogUpdaterCancelUpdateMessage": "Güncelleme iptal ediliyor!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Zaten Ryujinx'in en güncel sürümünü kullanıyorsunuz!", + "DialogUpdaterFailedToGetVersionMessage": "Github Release'den yayınlama bilgisi alınırken hata oluştu. Bu durum yeni bir sürümün Github Actions tarafından derleniyor olmasından kaynaklanıyor olabilir. Birkaç dakika sonra tekrar deneyin.", + "DialogUpdaterConvertFailedGithubMessage": "Github Release'den alınan Ryujinx sürümü çevrilemedi.", + "DialogUpdaterDownloadingMessage": "Güncelleme indiriliyor...", + "DialogUpdaterExtractionMessage": "Güncelleme ayıklanıyor...", + "DialogUpdaterRenamingMessage": "Güncelleme yeniden adlandırılıyor...", + "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!", + "DialogUpdaterDirtyBuildSubMessage": "Desteklenen bir sürüm arıyorsanız lütfen Ryujinx'i https://ryujinx.org/ sitesinden indirin.", + "DialogRestartRequiredMessage": "Yeniden Başlatma Gerekli", + "DialogThemeRestartMessage": "Tema kaydedildi. Temayı uygulamak için yeniden başlatma gerekiyor.", + "DialogThemeRestartSubMessage": "Yeniden başlatmak ister misiniz", + "DialogFirmwareInstallEmbeddedMessage": "Bu oyunun içine gömülü olan firmware'i yüklemek ister misiniz? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Yüklü firmware bulunamadı ancak Ryujinx sağlanan oyundan firmware {0} 'ı yükledi.\nRyujinx şimdi başlatılacak.", + "DialogFirmwareNoFirmwareInstalledMessage": "Firmware Yüklü Değil", + "DialogFirmwareInstalledMessage": "Firmware {0} yüklendi", + "DialogOpenSettingsWindowLabel": "Seçenekler Penceresini Aç", + "DialogControllerAppletTitle": "Controller Applet", + "DialogMessageDialogErrorExceptionMessage": "Mesaj Diyaloğu gösterilirken hata: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Mesaj Diyaloğu gösterilirken hata: {0}", + "DialogErrorAppletErrorExceptionMessage": "Applet Diyaloğu gösterilirken hata: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nBu hatayı düzeltmek adına daha fazla bilgi için kurulum kılavuzumuzu takip edin.", + "DialogUserErrorDialogTitle": "Ryujinx Hatası ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "API'dan bilgi alırken bir hata oluştu.", + "DialogAmiiboApiConnectErrorMessage": "Amiibo API sunucusuna bağlanılamadı. Sunucu çevrimdışı olabilir veya uygun bir internet bağlantınızın olduğunu kontrol etmeniz gerekebilir.", + "DialogProfileInvalidProfileErrorMessage": "Profil {0} güncel giriş konfigürasyon sistemi ile uyumlu değil.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Varsayılan Profil'in üstüne yazılamaz", + "DialogProfileDeleteProfileTitle": "Profil Siliniyor", + "DialogProfileDeleteProfileMessage": "Bu eylem geri döndürülemez, devam etmek istediğinizden emin misiniz?", + "DialogWarning": "Uyarı", + "DialogPPTCDeletionMessage": "Belirtilen PPTC cache silinecek :\n\n{0}\n\nDevam etmek istediğinizden emin misiniz?", + "DialogPPTCDeletionErrorMessage": "Belirtilen PPTC cache temizlenirken hata {0}: {1}", + "DialogShaderDeletionMessage": "Belirtilen Shader cache silinecek :\n\n{0}\n\nDevam etmek istediğinizden emin misiniz?", + "DialogShaderDeletionErrorMessage": "Belirtilen Shader cache temizlenirken hata {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx bir hata ile karşılaştı", + "DialogInvalidTitleIdErrorMessage": "UI hatası: Seçilen oyun geçerli bir title ID'ye sahip değil", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "{0} da geçerli bir sistem firmware'i bulunamadı.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Firmware {0} Yükle", + "DialogFirmwareInstallerFirmwareInstallMessage": "Sistem sürümü {0} yüklenecek.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nBu şimdiki sistem sürümünün yerini alacak {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nDevam etmek istiyor musunuz?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Firmware yükleniyor...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Sistem sürümü {0} başarıyla yüklendi.", + "DialogUserProfileDeletionWarningMessage": "Seçilen profil silinirse kullanılabilen başka profil kalmayacak", + "DialogUserProfileDeletionConfirmMessage": "Seçilen profili silmek istiyor musunuz", + "DialogControllerSettingsModifiedConfirmMessage": "Güncel kontrolcü seçenekleri güncellendi.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Kaydetmek istiyor musunuz?", + "DialogDlcLoadNcaErrorMessage": "{0}. Hatalı Dosya: {1}", + "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?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Sadece geliştiriler için dizayn edilen Shader Dumping seçeneği etkin.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "En iyi performans için Shader Dumping'in devre dışı bırakılması tavsiye edilir. Shader Dumping seçeneğini şimdi devre dışı bırakmak ister misiniz?", + "DialogLoadAppGameAlreadyLoadedMessage": "Bir oyun zaten yüklendi", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Lütfen yeni bir oyun açmadan önce emülasyonu durdurun veya emülatörü kapatın.", + "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.", + "SettingsTabGraphicsFeaturesOptions": "Özellikler", + "SettingsTabGraphicsBackendMultithreading": "Grafik Backend Multithreading:", + "CommonAuto": "Otomatik", + "CommonOff": "Kapalı", + "CommonOn": "Açık", + "InputDialogYes": "Evet", + "InputDialogNo": "Hayır", + "DialogProfileInvalidProfileNameErrorMessage": "Dosya adı geçerli olmayan karakterler içeriyor. Lütfen tekrar deneyin.", + "MenuBarOptionsPauseEmulation": "Durdur", + "MenuBarOptionsResumeEmulation": "Devam Et", + "AboutUrlTooltipMessage": "Ryujinx'in websitesini varsayılan tarayıcınızda açmak için tıklayın.", + "AboutDisclaimerMessage": "Ryujinx, Nintendo™ veya ortaklarıyla herhangi bir şekilde bağlantılı değildir.", + "AboutAmiiboDisclaimerMessage": "Amiibo emülasyonumuzda \nAmiiboAPI (www.amiiboapi.com) kullanılmaktadır.", + "AboutPatreonUrlTooltipMessage": "Ryujinx'in Patreon sayfasını varsayılan tarayıcınızda açmak için tıklayın.", + "AboutGithubUrlTooltipMessage": "Ryujinx'in GitHub sayfasını varsayılan tarayıcınızda açmak için tıklayın.", + "AboutDiscordUrlTooltipMessage": "Varsayılan tarayıcınızda Ryujinx'in Discord'una bir davet açmak için tıklayın.", + "AboutTwitterUrlTooltipMessage": "Ryujinx'in Twitter sayfasını varsayılan tarayıcınızda açmak için tıklayın.", + "AboutRyujinxAboutTitle": "Hakkında:", + "AboutRyujinxAboutContent": "Ryujinx bir Nintendo Switch™ emülatörüdür.\nLütfen bizi Patreon'da destekleyin.\nEn son haberleri Twitter veya Discord'umuzdan alın.\nKatkıda bulunmak isteyen geliştiriciler GitHub veya Discord üzerinden daha fazla bilgi edinebilir.", + "AboutRyujinxMaintainersTitle": "Geliştiriciler:", + "AboutRyujinxMaintainersContentTooltipMessage": "Varsayılan tarayıcınızda katkıda bulunanlar sayfasını açmak için tıklayın.", + "AboutRyujinxSupprtersTitle": "Patreon Destekleyicileri:", + "AmiiboSeriesLabel": "Amiibo Serisi", + "AmiiboCharacterLabel": "Karakter", + "AmiiboScanButtonLabel": "Tarat", + "AmiiboOptionsShowAllLabel": "Tüm Amiibo'ları Göster", + "AmiiboOptionsUsRandomTagLabel": "Hack: Use Random tag Uuid", + "DlcManagerTableHeadingEnabledLabel": "Etkin", + "DlcManagerTableHeadingTitleIdLabel": "Title ID", + "DlcManagerTableHeadingContainerPathLabel": "Container Yol", + "DlcManagerTableHeadingFullPathLabel": "Tam Yol", + "DlcManagerRemoveAllButton": "Tümünü kaldır", + "MenuBarOptionsChangeLanguage": "Dili Değiştir", + "CommonSort": "Sırala", + "CommonShowNames": "İsimleri Göster", + "CommonFavorite": "Favori", + "OrderAscending": "Artan", + "OrderDescending": "Azalan", + "SettingsTabGraphicsFeatures": "Özellikler", + "ErrorWindowTitle": "Hata Penceresi", + "ToggleDiscordTooltip": "Discord Rich Presence'i Aç/Kapat", + "AddGameDirBoxTooltip": "Listeye eklemek için bir oyun dizini ekleyin", + "AddGameDirTooltip": "Listeye oyun dizini ekle", + "RemoveGameDirTooltip": "Seçilen oyun dizinini kaldır", + "CustomThemeCheckTooltip": "Grafik kullanıcı arayüzünde özel temaları etkinleştir veya devre dışı bırak", + "CustomThemePathTooltip": "Özel grafik kullancı arayüzü teması yolu", + "CustomThemeBrowseTooltip": "Özel grafik kullanıcı arayüzü teması için göz at", + "DockModeToggleTooltip": "Docked Modu etkinleştir veya devre dışı bırak", + "DirectKeyboardTooltip": "Enable or disable \"direct keyboard access (HID) support\" (Provides games access to your keyboard as a text entry device)", + "DirectMouseTooltip": "Enable or disable \"direct mouse access (HID) support\" (Provides games access to your mouse as a pointing device)", + "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": "Dikey senkronizasyonu etkinleştirir veya devre dışı bırakır", + "PptcToggleTooltip": "PPTC'yi etkinleştirir veya devre dışı bırakır", + "FsIntegrityToggleTooltip": "Oyun içerik dosyaları için bütünlük kontrollerini etkinleştirir", + "AudioBackendTooltip": "Ses motorunu değiştirir", + "MemoryManagerTooltip": "Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance.", + "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.", + "DRamTooltip": "Emüle edilen sistemin hafızasını 4GB'dan 6GB'a yükseltir", + "IgnoreMissingServicesTooltip": "Eksik servisleri görmezden gelme seçeneğini etkinleştir veya devre dışı bırak", + "GraphicsBackendThreadingTooltip": "Graphics Backend Multithreading'i etkinleştir", + "GalThreadingTooltip": "Executes graphics backend commands on a second thread. Allows runtime multithreading of shader compilation, reduces stuttering, and improves performance on drivers without multithreading support of their own. Slightly varying peak performance on drivers with multithreading. Ryujinx may need to be restarted to correctly disable driver built-in multithreading, or you may need to do it manually to get the best performance.", + "ShaderCacheToggleTooltip": "Shader Cache'i etkinleştir veya devre dışı bırak", + "ResolutionScaleTooltip": "Resolution Scale applied to applicable render targets", + "ResolutionScaleEntryTooltip": "Floating point resolution scale, such as 1.5. Non-integral scales are more likely to cause issues or crash.", + "AnisotropyTooltip": "Anisotropic Filtering Seviyesi (Oyun tarafından istenen değeri kullanmak için Otomatik seçeneğini kullanın)", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.", + "ShaderDumpPathTooltip": "Graphics Shaders Dump Path", + "FileLogTooltip": "Diskte bir dosyaya loglamayı etkinleştirir veya devre dışı bırakır", + "StubLogTooltip": "Stub log mesajlarını yazdırmayı etkinleştirir", + "InfoLogTooltip": "Bilgi log mesajlarını yazdırmayı etkinleştirir", + "WarnLogTooltip": "Uyarı log mesajlarını yazdırmayı etkinleştirir", + "ErrorLogTooltip": "Hata log mesajlarını yazdırmayı etkinleştirir", + "TraceLogTooltip": "Trace log mesajlarını yazdırmayı etkinleştirir", + "GuestLogTooltip": "Guest log mesajlarını yazdırmayı etkinleştirir", + "FileAccessLogTooltip": "Dosya erişim log mesajlarını yazdırmayı etkinleştirir", + "FSAccessLogModeTooltip": "Enables FS access log output to the console. Possible modes are 0-3", + "DeveloperOptionTooltip": "Dikkatli kullanın", + "OpenGlLogLevel": "Uygun log seviyelerinin etkin olmasını gerektirir", + "DebugLogTooltip": "Debug log mesajlarını yazdırmayı etkinleştirir", + "LoadApplicationFileTooltip": "Switch'e uyumlu bir dosya açmak için bir dosya seçim arayüzü açar", + "LoadApplicationFolderTooltip": "Switch'e uyumlu sıkıştırılmamış uygulama açmak için bir dosya seçim arayüzü açar", + "OpenRyujinxFolderTooltip": "Ryujinx dosya sistemi klasörünü açar", + "OpenRyujinxLogsTooltip": "Log kayıtlarının yazıldığı klasörü açar", + "ExitTooltip": "Ryujinx'i Kapat", + "OpenSettingsTooltip": "Seçenekler penceresini aç", + "OpenProfileManagerTooltip": "Kullanıcı Profil Yönetimi penceresini açar", + "StopEmulationTooltip": "Güncel oyunun emülasyonunu durdurup oyun seçimine geri dönün", + "CheckUpdatesTooltip": "Ryujinx güncellemelerini denetle", + "OpenAboutTooltip": "Hakkında penceresini aç", + "GridSize": "Grid Boyutu", + "GridSizeTooltip": "Grid itemlerinin boyutunu değiştir", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brezilya Portekizcesi", + "AboutRyujinxContributorsButtonHeader": "Tüm katkıda bulunanları gör", + "SettingsTabSystemAudioVolume": "Ses Seviyesi: ", + "AudioVolumeTooltip": "Ses seviyesini değiştir", + "SettingsTabSystemEnableInternetAccess": "Guest Internet Erişimini Etkinleştir", + "EnableInternetAccessTooltip": "Guest internet erişimini etkinleştirir. Etkinleştirilmişse, uygulama emüle edilen Switch konsolu internete bağlıymış gibi davranır. Not: Bazı durumlarda uygulamalar bu seçenek devre dışı olmasına rağmen internete erişebilir", + "GameListContextMenuManageCheatToolTip" : "Hileleri Yönet", + "GameListContextMenuManageCheat" : "Hileleri Yönet", + "ControllerSettingsStickRange" : "Bölge (Range)", + "DialogStopEmulationTitle" : "Ryujinx - Emülasyonu Durdur", + "DialogStopEmulationMessage": "Emülasyonu durdurmak istediğinizden emin misiniz?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Ses", + "SettingsTabNetwork": "Ağ", + "SettingsTabNetworkConnection" : "Ağ Bağlantısı", + "SettingsTabGraphicsFrameRate" : "Host Yenileme Hızı:", + "SettingsTabGraphicsFrameRateTooltip" : "Host yenileme hızını ayarlar. Limiti kaldırmak için 0'ı seçin.", + "SettingsTabCpuCache" : "CPU Cache", + "SettingsTabCpuMemory" : "CPU Hafızası", + "DialogUpdaterFlatpakNotSupportedMessage": "Lütfen Ryujinx'i FlatHub aracılığıyla güncelleyin.", + "UpdaterDisabledWarningTitle": "Güncelleme aracı devre dışı!", + "GameListContextMenuOpenSdModsDirectory": "Atmosphere Modları Dizinini Aç", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Uygulamanın modlarının bulunduğu alternatif atmosphere SD kart dizinini açar", + "ControllerSettingsRotate90": "Saat yönünde 90° döndür", + "IconSize": "Simge Boyutu", + "IconSizeTooltip": "Oyunların simge boyutlarını değiştirir", + "MenuBarOptionsShowConsole": "Konsolu Göster", + "ShaderCachePurgeError" : "Belirtilen shader cache temizlenirken hata {0}: {1}", + "UserErrorNoKeys": "Keys bulunamadı", + "UserErrorNoFirmware": "Firmware bulunamadı", + "UserErrorFirmwareParsingFailed": "Firmware çözümleme hatası", + "UserErrorApplicationNotFound": "Uygulama bulunamadı", + "UserErrorUnknown": "Bilinmeyen hata", + "UserErrorUndefined": "Tanımlanmamış hata", + "UserErrorNoKeysDescription": "Ryujinx 'prod.keys' dosyanızı bulamadı", + "UserErrorNoFirmwareDescription": "Ryujinx yüklü firmware bulamadı", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx verilen firmware'i çözümleyemedi. Bu durum genellikle güncel olmayan key'lerden kaynaklanır.", + "UserErrorApplicationNotFoundDescription": "Ryujinx verilen yolda geçerli bir uygulama bulamadı.", + "UserErrorUnknownDescription": "Bilinmeyen bir hata oluştu!", + "UserErrorUndefinedDescription": "Tanımlanmamış bir hata oluştu! Bu durumla karşılaşılmamalı, lütfen bir geliştirici ile iletişime geçin!", + "OpenSetupGuideMessage": "Kurulum kılavuzunu aç", + "NoUpdate": "Güncelleme Yok", + "TitleUpdateVersionLabel": "Sürüm {0} - {1}", + "RyujinxInfo": "Ryujinx - Bilgi", + "RyujinxConfirm": "Ryujinx - Onay", + "FileDialogAllTypes": "Tüm tarzlar", + "Never": "Hiçbir Zaman", + "SwkbdMinCharacters": "En az {0} karakter uzunluğunda olmalı", + "SwkbdMinRangeCharacters": "En az {0}-{1} karakter uzunluğunda olmalı", + "SoftwareKeyboard": "Software Klavyesi", + "DialogControllerAppletMessagePlayerRange": "Uygulama {0} oyuncu(lar) talep eder player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.", + "DialogControllerAppletMessage": "Application requests exactly {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.", + "DialogControllerAppletDockModeSet": "Docked mode set. Handheld is also invalid.\n\n", + "UpdaterRenaming": "Eski dosyalar yeniden adlandırılıyor...", + "UpdaterRenameFailed": "Güncelleme aracı belirilen dosyayı yeniden adlandıramadı: {0}", + "UpdaterAddingFiles": "Yeni dosyalar ekleniyor...", + "UpdaterExtracting": "Güncelleme ayıklanıyor...", + "UpdaterDownloading": "Güncelleme indiriliyor...", + "Game": "Oyun", + "Docked": "Docked", + "Handheld": "Handheld", + "ConnectionError": "Bağlantı Hatası.", + "AboutPageDeveloperListMore": "{0} ve daha fazla...", + "ApiError": "API Hatası.", + "LoadingHeading": "Yükleniyor {0}", + "CompilingPPTC": "PTC derleniyor", + "CompilingShaders": "Shaderlar derleniyor" +} diff --git a/Ryujinx.Ava/Assets/Styles/BaseDark.xaml b/Ryujinx.Ava/Assets/Styles/BaseDark.xaml new file mode 100644 index 000000000..b80cb11fa --- /dev/null +++ b/Ryujinx.Ava/Assets/Styles/BaseDark.xaml @@ -0,0 +1,56 @@ + + + + + + + +

+ + + + + + + + + + + +